diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-06 00:09:04 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-06 00:09:04 +0300 |
commit | 96e23b2017cbe56969771960f6c274c5d3599397 (patch) | |
tree | b8b17da1ab080dd41fc64fc0262de2cf16754559 /spec | |
parent | 2f1a81fd16ff9968d6b986f8a407d963bc2218f9 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
41 files changed, 961 insertions, 911 deletions
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index 22804339fef..5f03d721fe7 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -3,7 +3,6 @@ require 'spec_helper' RSpec.describe Projects::EnvironmentsController, feature_category: :continuous_delivery do - include MetricsDashboardHelpers include KubernetesHelpers let_it_be(:project) { create(:project, :repository) } @@ -15,7 +14,6 @@ RSpec.describe Projects::EnvironmentsController, feature_category: :continuous_d let!(:environment) { create(:environment, name: 'production', project: project) } before do - stub_feature_flags(remove_monitor_metrics: false) sign_in(user) end @@ -538,405 +536,6 @@ RSpec.describe Projects::EnvironmentsController, feature_category: :continuous_d end end - describe 'GET #metrics_redirect' do - it 'redirects to metrics dashboard page' do - get :metrics_redirect, params: { namespace_id: project.namespace, project_id: project } - - expect(response).to redirect_to(project_metrics_dashboard_path(project)) - end - - context 'when metrics dashboard feature is unavailable' do - before do - stub_feature_flags(remove_monitor_metrics: true) - end - - it 'returns 404 not found' do - get :metrics_redirect, params: { namespace_id: project.namespace, project_id: project } - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - - describe 'GET #metrics' do - before do - allow(controller).to receive(:environment).and_return(environment) - end - - context 'when environment has no metrics' do - it 'redirects to metrics dashboard page' do - expect(environment).not_to receive(:metrics) - - get :metrics, params: environment_params - - expect(response).to redirect_to(project_metrics_dashboard_path(project, environment: environment)) - end - - context 'when requesting metrics as JSON' do - it 'returns a metrics JSON document' do - expect(environment).to receive(:metrics).and_return(nil) - - get :metrics, params: environment_params(format: :json) - - expect(response).to have_gitlab_http_status(:no_content) - expect(json_response).to eq({}) - end - end - end - - context 'when environment has some metrics' do - before do - expect(environment).to receive(:metrics).and_return({ - success: true, - metrics: {}, - last_update: 42 - }) - end - - it 'returns a metrics JSON document' do - get :metrics, params: environment_params(format: :json) - - expect(response).to be_ok - expect(json_response['success']).to be(true) - expect(json_response['metrics']).to eq({}) - expect(json_response['last_update']).to eq(42) - end - end - - context 'permissions' do - before do - allow(controller).to receive(:can?).and_return true - end - - it 'checks :metrics_dashboard ability' do - expect(controller).to receive(:can?).with(anything, :metrics_dashboard, anything) - - get :metrics, params: environment_params - end - end - - context 'with anonymous user and public dashboard visibility' do - let(:project) { create(:project, :public) } - let(:user) { create(:user) } - - it 'redirects to metrics dashboard page' do - project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) - - get :metrics, params: environment_params - - expect(response).to redirect_to(project_metrics_dashboard_path(project, environment: environment)) - end - end - - context 'when metrics dashboard feature is unavailable' do - before do - stub_feature_flags(remove_monitor_metrics: true) - end - - it 'returns 404 not found' do - expect(environment).not_to receive(:metrics) - - get :metrics, params: environment_params - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - - describe 'GET #additional_metrics' do - let(:window_params) { { start: '1554702993.5398998', end: '1554717396.996232' } } - - before do - allow(controller).to receive(:environment).and_return(environment) - end - - context 'when environment has no metrics' do - before do - expect(environment).to receive(:additional_metrics).and_return(nil) - end - - context 'when requesting metrics as JSON' do - it 'returns a metrics JSON document' do - additional_metrics(window_params) - - expect(response).to have_gitlab_http_status(:no_content) - expect(json_response).to eq({}) - end - end - end - - context 'when environment has some metrics' do - before do - expect(environment) - .to receive(:additional_metrics) - .and_return({ - success: true, - data: {}, - last_update: 42 - }) - end - - it 'returns a metrics JSON document' do - additional_metrics(window_params) - - expect(response).to be_ok - expect(json_response['success']).to be(true) - expect(json_response['data']).to eq({}) - expect(json_response['last_update']).to eq(42) - end - end - - context 'when time params are missing' do - it 'raises an error when window params are missing' do - expect { additional_metrics } - .to raise_error(ActionController::ParameterMissing) - end - end - - context 'when only one time param is provided' do - it 'raises an error when start is missing' do - expect { additional_metrics(end: '1552647300.651094') } - .to raise_error(ActionController::ParameterMissing) - end - - it 'raises an error when end is missing' do - expect { additional_metrics(start: '1552647300.651094') } - .to raise_error(ActionController::ParameterMissing) - end - end - - context 'permissions' do - before do - allow(controller).to receive(:can?).and_return true - end - - it 'checks :metrics_dashboard ability' do - expect(controller).to receive(:can?).with(anything, :metrics_dashboard, anything) - - get :metrics, params: environment_params - end - end - - context 'with anonymous user and public dashboard visibility' do - let(:project) { create(:project, :public) } - let(:user) { create(:user) } - - it 'does not fail' do - allow(environment) - .to receive(:additional_metrics) - .and_return({ - success: true, - data: {}, - last_update: 42 - }) - project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) - - additional_metrics(window_params) - - expect(response).to have_gitlab_http_status(:ok) - end - end - - context 'when metrics dashboard feature is unavailable' do - before do - stub_feature_flags(remove_monitor_metrics: true) - end - - it 'returns 404 not found' do - additional_metrics(window_params) - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - - describe 'GET #metrics_dashboard' do - let(:metrics_dashboard_req_params) { environment_params(dashboard_params) } - - shared_examples_for '200 response' do - it_behaves_like 'GET #metrics_dashboard correctly formatted response' do - let(:expected_keys) { %w(dashboard status metrics_data) } - let(:status_code) { :ok } - end - end - - shared_examples_for 'error response' do |status_code| - it_behaves_like 'GET #metrics_dashboard correctly formatted response' do - let(:expected_keys) { %w(message status) } - let(:status_code) { status_code } - end - end - - shared_examples_for 'includes all dashboards' do - it 'includes info for all findable dashboard' do - get :metrics_dashboard, params: environment_params(dashboard_params) - - expect(json_response).to have_key('all_dashboards') - expect(json_response['all_dashboards']).to be_an_instance_of(Array) - expect(json_response['all_dashboards']).to all(include('path', 'default', 'display_name')) - end - end - - shared_examples_for 'the default dashboard' do - it_behaves_like 'includes all dashboards' - it_behaves_like 'GET #metrics_dashboard for dashboard', 'Environment metrics' - end - - shared_examples_for 'the specified dashboard' do |expected_dashboard| - it_behaves_like 'includes all dashboards' - - it_behaves_like 'GET #metrics_dashboard for dashboard', expected_dashboard - - context 'when the dashboard cannot not be processed' do - before do - allow(YAML).to receive(:safe_load).and_return({}) - end - - it_behaves_like 'error response', :unprocessable_entity - end - end - - shared_examples_for 'specified dashboard embed' do |expected_titles| - it_behaves_like '200 response' - - it 'contains only the specified charts' do - get :metrics_dashboard, params: environment_params(dashboard_params) - - dashboard = json_response['dashboard'] - panel_group = dashboard['panel_groups'].first - titles = panel_group['panels'].map { |panel| panel['title'] } - - expect(dashboard['dashboard']).to be_nil - expect(dashboard['panel_groups'].length).to eq 1 - expect(panel_group['group']).to be_nil - expect(titles).to eq expected_titles - end - end - - shared_examples_for 'the default dynamic dashboard' do - it_behaves_like 'specified dashboard embed', ['Memory Usage (Total)', 'Core Usage (Total)'] - end - - shared_examples_for 'dashboard can be specified' do - context 'when dashboard is specified' do - let(:dashboard_path) { '.gitlab/dashboards/test.yml' } - let(:dashboard_params) { { format: :json, dashboard: dashboard_path } } - - it_behaves_like 'error response', :not_found - - context 'when the project dashboard is available' do - let(:dashboard_yml) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') } - let(:project) { project_with_dashboard(dashboard_path, dashboard_yml) } - let(:environment) { create(:environment, name: 'production', project: project) } - - before do - project.add_maintainer(user) - end - - it_behaves_like 'the specified dashboard', 'Test Dashboard' - end - - context 'when the specified dashboard is the default dashboard' do - let(:dashboard_path) { system_dashboard_path } - - it_behaves_like 'the default dashboard' - end - end - end - - shared_examples_for 'dashboard can be embedded' do - context 'when the embedded flag is included' do - let(:dashboard_params) { { format: :json, embedded: true } } - - it_behaves_like 'the default dynamic dashboard' - - context 'when incomplete dashboard params are provided' do - let(:dashboard_params) { { format: :json, embedded: true, title: 'Title' } } - - # The title param should be ignored. - it_behaves_like 'the default dynamic dashboard' - end - - context 'when invalid params are provided' do - let(:dashboard_params) { { format: :json, embedded: true, metric_id: 16 } } - - # The superfluous param should be ignored. - it_behaves_like 'the default dynamic dashboard' - end - - context 'when the dashboard is correctly specified' do - let(:dashboard_params) do - { - format: :json, - embedded: true, - dashboard: system_dashboard_path, - group: business_metric_title, - title: 'title', - y_label: 'y_label' - } - end - - it_behaves_like 'error response', :not_found - - context 'and exists' do - let!(:metric) { create(:prometheus_metric, project: project) } - - it_behaves_like 'specified dashboard embed', ['title'] - end - end - end - end - - shared_examples_for 'dashboard cannot be specified' do - context 'when dashboard is specified' do - let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } } - - it_behaves_like 'the default dashboard' - end - end - - let(:dashboard_params) { { format: :json } } - - it_behaves_like 'the default dashboard' - it_behaves_like 'dashboard can be specified' - it_behaves_like 'dashboard can be embedded' - - context 'with anonymous user and public dashboard visibility' do - let(:project) { create(:project, :public) } - let(:user) { create(:user) } - - before do - project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED) - end - - it_behaves_like 'the default dashboard' - end - - context 'permissions' do - before do - allow(controller).to receive(:can?).and_return true - end - - it 'checks :metrics_dashboard ability' do - expect(controller).to receive(:can?).with(anything, :metrics_dashboard, anything) - - get :metrics, params: environment_params - end - - context 'when metrics dashboard feature is unavailable' do - before do - stub_feature_flags(remove_monitor_metrics: true) - end - - it 'returns 404 not found' do - get :metrics_dashboard, params: environment_params(dashboard_params) - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - end - describe 'GET #search' do before do create(:environment, name: 'staging', project: project) @@ -1053,8 +652,4 @@ RSpec.describe Projects::EnvironmentsController, feature_category: :continuous_d def environment_params(opts = {}) opts.reverse_merge(namespace_id: project.namespace, project_id: project, id: environment.id) end - - def additional_metrics(opts = {}) - get :additional_metrics, params: environment_params(format: :json, **opts) - end end diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb index 04dbd9ab671..770c1991144 100644 --- a/spec/controllers/projects/settings/operations_controller_spec.rb +++ b/spec/controllers/projects/settings/operations_controller_spec.rb @@ -11,8 +11,6 @@ RSpec.describe Projects::Settings::OperationsController, feature_category: :inci end before do - stub_feature_flags(remove_monitor_metrics: false) - sign_in(user) end @@ -67,20 +65,6 @@ RSpec.describe Projects::Settings::OperationsController, feature_category: :inci end end - shared_examples 'PATCHable without metrics dashboard' do - context 'when metrics dashboard feature is unavailable' do - before do - stub_feature_flags(remove_monitor_metrics: true) - end - - include_examples 'PATCHable' do - let(:permitted_params) do - ActionController::Parameters.new({}).permit! - end - end - end - end - describe 'GET #show' do it 'renders show template' do get :show, params: project_params(project) @@ -339,38 +323,6 @@ RSpec.describe Projects::Settings::OperationsController, feature_category: :inci end end - context 'metrics dashboard setting', feature_category: :metrics do - describe 'PATCH #update' do - let(:params) do - { - metrics_setting_attributes: { - external_dashboard_url: 'https://gitlab.com' - } - } - end - - include_examples 'PATCHable' - include_examples 'PATCHable without metrics dashboard' - end - end - - context 'grafana integration', feature_category: :metrics do - describe 'PATCH #update' do - let(:params) do - { - grafana_integration_attributes: { - grafana_url: 'https://grafana.gitlab.com', - token: 'eyJrIjoicDRlRTREdjhhOEZ5WjZPWXUzazJOSW0zZHJUejVOd3IiLCJuIjoiVGVzdCBLZXkiLCJpZCI6MX0=', - enabled: 'true' - } - } - end - - include_examples 'PATCHable' - include_examples 'PATCHable without metrics dashboard' - end - end - context 'prometheus integration' do describe 'POST #reset_alerting_token' do context 'with existing alerting setting' do diff --git a/spec/features/markdown/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb index a31ad5a868e..9bb0220004a 100644 --- a/spec/features/markdown/markdown_spec.rb +++ b/spec/features/markdown/markdown_spec.rb @@ -59,7 +59,7 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures, feature_category: :team_p aggregate_failures 'allows Markdown in tables' do expect(doc.at_css('td:contains("Baz")').children.to_html) - .to eq '<strong>Baz</strong>' + .to eq_no_sourcepos '<strong>Baz</strong>' end aggregate_failures 'parses fenced code blocks' do @@ -167,13 +167,13 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures, feature_category: :team_p it 'allows markup inside link elements' do aggregate_failures do expect(doc.at_css('a[href="#link-emphasis"]').to_html) - .to eq %{<a href="#link-emphasis"><em>text</em></a>} + .to eq_no_sourcepos %{<a href="#link-emphasis"><em>text</em></a>} expect(doc.at_css('a[href="#link-strong"]').to_html) - .to eq %{<a href="#link-strong"><strong>text</strong></a>} + .to eq_no_sourcepos %{<a href="#link-strong"><strong>text</strong></a>} expect(doc.at_css('a[href="#link-code"]').to_html) - .to eq %{<a href="#link-code"><code>text</code></a>} + .to eq_no_sourcepos %{<a href="#link-code"><code>text</code></a>} end end end diff --git a/spec/features/merge_request/user_comments_on_whitespace_hidden_diff_spec.rb b/spec/features/merge_request/user_comments_on_whitespace_hidden_diff_spec.rb index 08976d3d2e2..c13fe8d1e45 100644 --- a/spec/features/merge_request/user_comments_on_whitespace_hidden_diff_spec.rb +++ b/spec/features/merge_request/user_comments_on_whitespace_hidden_diff_spec.rb @@ -27,22 +27,35 @@ RSpec.describe 'User comments on a diff with whitespace changes', :js, feature_c wait_for_requests end - it 'allows commenting on line combinations that are not present in the real diff' do - # Comment on line combination old: 19, new 20 - # This line combination does not exist when whitespace is shown - click_diff_line( - find_by_scrolling('div[data-path="files/ruby/popen.rb"] .left-side a[data-linenumber="19"]').find(:xpath, - '../..'), 'left') - - page.within('.js-discussion-note-form') do - fill_in(:note_note, with: 'Comment on diff with whitespace') - click_button('Add comment now') + context 'when commenting on line combinations that are not present in the real diff' do + before do + # Comment on line combination old: 19, new 20 + # This line combination does not exist when whitespace is shown + click_diff_line( + find_by_scrolling('div[data-path="files/ruby/popen.rb"] .left-side a[data-linenumber="19"]').find(:xpath, + '../..'), 'left') + page.within('.js-discussion-note-form') do + fill_in(:note_note, with: 'Comment on diff with whitespace') + click_button('Add comment now') + end + + wait_for_requests end - wait_for_requests + it 'shows the comments in the diff' do + page.within('.notes_holder') do + expect(page).to have_content('Comment on diff with whitespace') + end + end - page.within('.notes_holder') do - expect(page).to have_content('Comment on diff with whitespace') + it 'allows replies to comments in the diff' do + click_button('Reply to comment') + fill_in(:note_note, with: 'reply to whitespace comment') + click_button('Add comment now') + wait_for_requests + page.within('.notes_holder') do + expect(page).to have_content('reply to whitespace comment') + end end end end diff --git a/spec/features/project_group_variables_spec.rb b/spec/features/project_group_variables_spec.rb index 966c05bb4cb..001b3ff2e41 100644 --- a/spec/features/project_group_variables_spec.rb +++ b/spec/features/project_group_variables_spec.rb @@ -26,39 +26,131 @@ RSpec.describe 'Project group variables', :js, feature_category: :secrets_manage group.add_owner(user) end - it 'project in group shows inherited vars from ancestor group' do - visit project_path - expect(page).to have_content(key1) - expect(page).to have_content(group.name) + shared_examples 'renders the haml column headers' do + it "shows inherited CI variables table with correct columns" do + page.within('.inherited-ci-variable-table') do + columns = find_all('th') + + expect(columns[0].text).to eq('Key') + expect(columns[1].text).to eq('Environments') + expect(columns[2].text).to eq('Group') + end + end end - it 'project in subgroup shows inherited vars from all ancestor groups' do - visit project2_path - expect(page).to have_content(key1) - expect(page).to have_content(key2) - expect(page).to have_content(group.name) - expect(page).to have_content(subgroup.name) + shared_examples 'renders the vue app column headers' do + it "shows inherited CI variables table with correct columns" do + page.within('[data-testid="inherited-ci-variable-table"]') do + # Wait for vue app to load + wait_for_requests + + columns = find_all('[role=columnheader]') + + expect(columns[0].text).to eq('Type') + expect(columns[1].text).to eq('Key') + expect(columns[2].text).to eq('Options') + expect(columns[3].text).to eq('Environments') + expect(columns[4].text).to eq('Group') + end + end end - it 'project in nested subgroup shows inherited vars from all ancestor groups' do - visit project3_path - expect(page).to have_content(key1) - expect(page).to have_content(key2) - expect(page).to have_content(key3) - expect(page).to have_content(group.name) - expect(page).to have_content(subgroup.name) - expect(page).to have_content(subgroup_nested.name) + context 'when feature flag ci_vueify_inherited_group_variables is disabled' do + before do + stub_feature_flags(ci_vueify_inherited_group_variables: false) + end + + describe 'project in group' do + before do + visit project_path + end + + it_behaves_like 'renders the haml column headers' + + it 'shows inherited variable info from ancestor group' do + visit project_path + + expect(page).to have_content(key1) + expect(page).to have_content(group.name) + end + end + + describe 'project in subgroup' do + before do + visit project2_path + end + + it_behaves_like 'renders the haml column headers' + + it 'shows inherited variable info from all ancestor groups' do + visit project2_path + + expect(page).to have_content(key1) + expect(page).to have_content(key2) + expect(page).to have_content(group.name) + expect(page).to have_content(subgroup.name) + end + end + + describe 'project in nested subgroup' do + before do + visit project3_path + end + + it_behaves_like 'renders the haml column headers' + + it 'shows inherited variable info from all ancestor groups' do + visit project3_path + + expect(page).to have_content(key1) + expect(page).to have_content(key2) + expect(page).to have_content(key3) + expect(page).to have_content(group.name) + expect(page).to have_content(subgroup.name) + expect(page).to have_content(subgroup_nested.name) + end + end + + it 'project origin keys link to ancestor groups ci_cd settings' do + visit project_path + + find('.group-origin-link').click + + wait_for_requests + + page.within('[data-testid="ci-variable-table"]') do + expect(find('.js-ci-variable-row:nth-child(1) [data-label="Key"]').text).to eq(key1) + end + end end - it 'project origin keys link to ancestor groups ci_cd settings' do - visit project_path + context 'when feature flag ci_vueify_inherited_group_variables is enabled' do + before do + stub_feature_flags(ci_vueify_inherited_group_variables: true) + end + + describe 'project in group' do + before do + visit project_path + end - find('.group-origin-link').click + it_behaves_like 'renders the vue app column headers' + end + + describe 'project in subgroup' do + before do + visit project2_path + end + + it_behaves_like 'renders the vue app column headers' + end - wait_for_requests + describe 'project in nested subgroup' do + before do + visit project3_path + end - page.within('[data-testid="ci-variable-table"]') do - expect(find('.js-ci-variable-row:nth-child(1) [data-label="Key"]').text).to eq(key1) + it_behaves_like 'renders the vue app column headers' end end end diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb index 0a4075be02f..d0111d0b9c8 100644 --- a/spec/features/projects/releases/user_views_releases_spec.rb +++ b/spec/features/projects/releases/user_views_releases_spec.rb @@ -24,6 +24,17 @@ RSpec.describe 'User views releases', :js, feature_category: :continuous_deliver stub_default_url_options(host: 'localhost') end + shared_examples 'when the project does not have releases' do + before do + project.releases.delete_all + visit project_releases_path(project) + end + + it 'sees an empty state' do + expect(page).to have_selector('[data-testid="gl-empty-state-content"]') + end + end + context('when the user is a maintainer') do before do sign_in(maintainer) @@ -110,6 +121,8 @@ RSpec.describe 'User views releases', :js, feature_category: :continuous_deliver it_behaves_like 'releases sort order' end end + + it_behaves_like 'when the project does not have releases' end context('when the user is a guest') do @@ -130,5 +143,7 @@ RSpec.describe 'User views releases', :js, feature_category: :continuous_deliver expect(page).not_to have_content(release_v3.commit.short_id) end end + + it_behaves_like 'when the project does not have releases' end end diff --git a/spec/frontend/behaviors/markdown/utils_spec.js b/spec/frontend/behaviors/markdown/utils_spec.js new file mode 100644 index 00000000000..f4e7ca621d9 --- /dev/null +++ b/spec/frontend/behaviors/markdown/utils_spec.js @@ -0,0 +1,18 @@ +import { toggleMarkCheckboxes } from '~/behaviors/markdown/utils'; + +describe('toggleMarkCheckboxes', () => { + const rawMarkdown = `- [x] todo 1\n- [ ] todo 2`; + + it.each` + assertionName | sourcepos | checkboxChecked | expectedMarkdown + ${'marks'} | ${'2:1-2:12'} | ${true} | ${'- [x] todo 1\n- [x] todo 2'} + ${'unmarks'} | ${'1:1-1:12'} | ${false} | ${'- [ ] todo 1\n- [ ] todo 2'} + `( + '$assertionName the checkbox at correct position', + ({ sourcepos, checkboxChecked, expectedMarkdown }) => { + expect(toggleMarkCheckboxes({ rawMarkdown, sourcepos, checkboxChecked })).toEqual( + expectedMarkdown, + ); + }, + ); +}); diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js index a25d325f7a1..f7b90c3da30 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js @@ -46,6 +46,7 @@ Vue.use(VueApollo); const mockProvide = { endpoint: '/variables', isGroup: false, + isInheritedGroupVars: false, isProject: false, }; diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js index 0b28cb06cec..e953fbb7031 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js @@ -1,9 +1,9 @@ -import { GlAlert } from '@gitlab/ui'; +import { GlAlert, GlKeysetPagination } from '@gitlab/ui'; import { sprintf } from '~/locale'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import CiVariableTable from '~/ci/ci_variable_list/components/ci_variable_table.vue'; import { EXCEEDS_VARIABLE_LIMIT_TEXT, projectString } from '~/ci/ci_variable_list/constants'; -import { mockVariables } from '../mocks'; +import { mockInheritedVariables, mockVariables } from '../mocks'; describe('Ci variable table', () => { let wrapper; @@ -29,6 +29,7 @@ describe('Ci variable table', () => { glFeatures: { ciVariablesPages: false, }, + isInheritedGroupVars: false, ...provide, }, }); @@ -43,6 +44,10 @@ describe('Ci variable table', () => { const findRevealedValues = () => wrapper.findAllByTestId('revealedValue'); const findOptionsValues = (rowIndex) => wrapper.findAllByTestId('ci-variable-table-row-options').at(rowIndex).text(); + const findTableColumnText = (index) => wrapper.findAll('th').at(index).text(); + const findGroupCiCdSettingsLink = (rowIndex) => + wrapper.findAllByTestId('ci-variable-table-row-cicd-path').at(rowIndex).attributes('href'); + const findKeysetPagination = () => wrapper.findComponent(GlKeysetPagination); const generateExceedsVariableLimitText = (entity, currentVariableCount, maxVariableLimit) => { return sprintf(EXCEEDS_VARIABLE_LIMIT_TEXT, { entity, currentVariableCount, maxVariableLimit }); @@ -69,17 +74,26 @@ describe('Ci variable table', () => { }); }); - describe('When table has variables', () => { + describe('When table has CI variables', () => { beforeEach(() => { createComponent({ provide }); }); - it('does not display the empty message', () => { - expect(findEmptyVariablesPlaceholder().exists()).toBe(false); + // last column is for the edit button, which has no text + it.each` + index | text + ${0} | ${'Type'} + ${1} | ${'Key (Click to sort descending)'} + ${2} | ${'Value'} + ${3} | ${'Options'} + ${4} | ${'Environments'} + ${5} | ${''} + `('renders the $text column', ({ index, text }) => { + expect(findTableColumnText(index)).toEqual(text); }); - it('displays the reveal button', () => { - expect(findRevealButton().exists()).toBe(true); + it('does not display the empty message', () => { + expect(findEmptyVariablesPlaceholder().exists()).toBe(false); }); it('displays the correct amount of variables', () => { @@ -91,11 +105,59 @@ describe('Ci variable table', () => { expect(findOptionsValues(1)).toBe('Masked'); }); + it('renders action buttons', () => { + expect(findRevealButton().exists()).toBe(true); + expect(findAddButton().exists()).toBe(true); + expect(findEditButton().exists()).toBe(true); + }); + it('enables the Add Variable button', () => { expect(findAddButton().props('disabled')).toBe(false); }); }); + describe('When table has inherited CI variables', () => { + beforeEach(() => { + createComponent({ + props: { variables: mockInheritedVariables }, + provide: { isInheritedGroupVars: true, ...provide }, + }); + }); + + it.each` + index | text + ${0} | ${'Type'} + ${1} | ${'Key'} + ${2} | ${'Options'} + ${3} | ${'Environments'} + ${4} | ${'Group'} + `('renders the $text column', ({ index, text }) => { + expect(findTableColumnText(index)).toEqual(text); + }); + + it('does not render action buttons', () => { + expect(findRevealButton().exists()).toBe(false); + expect(findAddButton().exists()).toBe(false); + expect(findEditButton().exists()).toBe(false); + expect(findKeysetPagination().exists()).toBe(false); + }); + + it('displays the correct amount of variables', () => { + expect(wrapper.findAll('.js-ci-variable-row')).toHaveLength(mockInheritedVariables.length); + }); + + it('displays the correct variable options', () => { + expect(findOptionsValues(0)).toBe('Protected, Masked, Expanded'); + expect(findOptionsValues(1)).toBe(''); + expect(findOptionsValues(2)).toBe('Protected'); + }); + + it('displays link to the group settings', () => { + expect(findGroupCiCdSettingsLink(0)).toBe(mockInheritedVariables[0].groupCiCdSettingsPath); + expect(findGroupCiCdSettingsLink(1)).toBe(mockInheritedVariables[1].groupCiCdSettingsPath); + }); + }); + describe('When variables have exceeded the max limit', () => { beforeEach(() => { createComponent({ diff --git a/spec/frontend/ci/ci_variable_list/mocks.js b/spec/frontend/ci/ci_variable_list/mocks.js index f9450803308..9c9c99ad5ea 100644 --- a/spec/frontend/ci/ci_variable_list/mocks.js +++ b/spec/frontend/ci/ci_variable_list/mocks.js @@ -51,6 +51,45 @@ export const mockVariables = (kind) => { ]; }; +export const mockInheritedVariables = [ + { + id: 'gid://gitlab/Ci::GroupVariable/120', + key: 'INHERITED_VAR_1', + variableType: 'ENV_VAR', + environmentScope: '*', + masked: true, + protected: true, + raw: false, + groupName: 'group-name', + groupCiCdSettingsPath: '/groups/group-name/-/settings/ci_cd', + __typename: 'InheritedCiVariable', + }, + { + id: 'gid://gitlab/Ci::GroupVariable/121', + key: 'INHERITED_VAR_2', + variableType: 'ENV_VAR', + environmentScope: 'staging', + masked: false, + protected: false, + raw: true, + groupName: 'subgroup-name', + groupCiCdSettingsPath: '/groups/group-name/subgroup-name/-/settings/ci_cd', + __typename: 'InheritedCiVariable', + }, + { + id: 'gid://gitlab/Ci::GroupVariable/122', + key: 'INHERITED_VAR_3', + variableType: 'FILE', + environmentScope: 'production', + masked: false, + protected: true, + raw: true, + groupName: 'subgroup-name', + groupCiCdSettingsPath: '/groups/group-name/subgroup-name/-/settings/ci_cd', + __typename: 'InheritedCiVariable', + }, +]; + export const mockVariablesWithScopes = (kind) => mockVariables(kind).map((variable) => { return { ...variable, environmentScope: '*' }; diff --git a/spec/frontend/ci/inherited_ci_variables/components/inherited_ci_variables_app_spec.js b/spec/frontend/ci/inherited_ci_variables/components/inherited_ci_variables_app_spec.js new file mode 100644 index 00000000000..0af026cfec4 --- /dev/null +++ b/spec/frontend/ci/inherited_ci_variables/components/inherited_ci_variables_app_spec.js @@ -0,0 +1,114 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { shallowMount } from '@vue/test-utils'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { createAlert } from '~/alert'; +import CiVariableTable from '~/ci/ci_variable_list/components/ci_variable_table.vue'; +import InheritedCiVariablesApp, { + i18n, + FETCH_LIMIT, + VARIABLES_PER_FETCH, +} from '~/ci/inherited_ci_variables/components/inherited_ci_variables_app.vue'; +import getInheritedCiVariables from '~/ci/inherited_ci_variables/graphql/queries/inherited_ci_variables.query.graphql'; +import { mockInheritedCiVariables } from '../mocks'; + +jest.mock('~/alert'); +Vue.use(VueApollo); + +describe('Inherited CI Variables Component', () => { + let wrapper; + let mockApollo; + let mockVariables; + + const defaultProvide = { + projectPath: 'namespace/project', + projectId: '1', + }; + + const findCiTable = () => wrapper.findComponent(CiVariableTable); + + // eslint-disable-next-line consistent-return + function createComponentWithApollo({ isLoading = false } = {}) { + const handlers = [[getInheritedCiVariables, mockVariables]]; + + mockApollo = createMockApollo(handlers); + + wrapper = shallowMount(InheritedCiVariablesApp, { + provide: defaultProvide, + apolloProvider: mockApollo, + }); + + if (!isLoading) { + return waitForPromises(); + } + } + + beforeEach(() => { + mockVariables = jest.fn(); + }); + + describe('while variables are being fetched', () => { + beforeEach(() => { + mockVariables.mockResolvedValue(mockInheritedCiVariables()); + createComponentWithApollo({ isLoading: true }); + }); + + it('shows a loading icon', () => { + expect(findCiTable().props('isLoading')).toBe(true); + }); + }); + + describe('when there are more variables to fetch', () => { + beforeEach(async () => { + mockVariables.mockResolvedValue(mockInheritedCiVariables({ withNextPage: true })); + + await createComponentWithApollo(); + }); + + it('re-fetches the query up to <FETCH_LIMIT> times', () => { + expect(mockVariables).toHaveBeenCalledTimes(FETCH_LIMIT); + }); + + it('shows alert message when calls have exceeded FETCH_LIMIT', () => { + expect(createAlert).toHaveBeenCalledWith({ message: i18n.tooManyCallsError }); + }); + }); + + describe('when variables are fetched successfully', () => { + beforeEach(async () => { + mockVariables.mockResolvedValue(mockInheritedCiVariables()); + + await createComponentWithApollo(); + }); + + it('query was called with the correct arguments', () => { + expect(mockVariables).toHaveBeenCalledWith({ + first: VARIABLES_PER_FETCH, + fullPath: defaultProvide.projectPath, + }); + }); + + it('passes down variables to the table component', () => { + expect(findCiTable().props('variables')).toEqual( + mockInheritedCiVariables().data.project.inheritedCiVariables.nodes, + ); + }); + + it('createAlert was not called', () => { + expect(createAlert).not.toHaveBeenCalled(); + }); + }); + + describe('when fetch error occurs', () => { + beforeEach(async () => { + mockVariables.mockRejectedValue(); + + await createComponentWithApollo(); + }); + + it('shows alert message with the expected error message', () => { + expect(createAlert).toHaveBeenCalledWith({ message: i18n.fetchError }); + }); + }); +}); diff --git a/spec/frontend/ci/inherited_ci_variables/mocks.js b/spec/frontend/ci/inherited_ci_variables/mocks.js new file mode 100644 index 00000000000..841ba0a0043 --- /dev/null +++ b/spec/frontend/ci/inherited_ci_variables/mocks.js @@ -0,0 +1,44 @@ +export const mockInheritedCiVariables = ({ withNextPage = false } = {}) => ({ + data: { + project: { + __typename: 'Project', + id: 'gid://gitlab/Project/38', + inheritedCiVariables: { + __typename: `InheritedCiVariableConnection`, + pageInfo: { + startCursor: 'adsjsd12kldpsa', + endCursor: 'adsjsd12kldpsa', + hasPreviousPage: withNextPage, + hasNextPage: withNextPage, + __typename: 'PageInfo', + }, + nodes: [ + { + __typename: `InheritedCiVariable`, + id: 'gid://gitlab/Ci::GroupVariable/1', + environmentScope: '*', + groupName: 'group_abc', + groupCiCdSettingsPath: '/groups/group_abc/-/settings/ci_cd', + key: 'GROUP_VAR', + masked: false, + protected: true, + raw: false, + variableType: 'ENV_VAR', + }, + { + __typename: `InheritedCiVariable`, + id: 'gid://gitlab/Ci::GroupVariable/2', + environmentScope: '*', + groupName: 'subgroup_xyz', + groupCiCdSettingsPath: '/groups/group_abc/subgroup_xyz/-/settings/ci_cd', + key: 'SUB_GROUP_VAR', + masked: true, + protected: false, + raw: true, + variableType: 'ENV_VAR', + }, + ], + }, + }, + }, +}); diff --git a/spec/frontend/design_management/components/design_description/description_form_spec.js b/spec/frontend/design_management/components/design_description/description_form_spec.js new file mode 100644 index 00000000000..8c01023b1a8 --- /dev/null +++ b/spec/frontend/design_management/components/design_description/description_form_spec.js @@ -0,0 +1,299 @@ +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; + +import { GlAlert } from '@gitlab/ui'; + +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; + +import DescriptionForm from '~/design_management/components/design_description/description_form.vue'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; +import updateDesignDescriptionMutation from '~/design_management/graphql/mutations/update_design_description.mutation.graphql'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { renderGFM } from '~/behaviors/markdown/render_gfm'; + +import { designFactory, designUpdateFactory } from '../../mock_data/apollo_mock'; + +jest.mock('~/behaviors/markdown/render_gfm'); + +Vue.use(VueApollo); + +describe('Design description form', () => { + const formFieldProps = { + id: 'design-description', + name: 'design-description', + placeholder: 'Write a comment or drag your files here…', + 'aria-label': 'Design description', + }; + const mockDesign = designFactory(); + const mockDesignVariables = { + fullPath: '', + iid: '1', + filenames: ['test.jpg'], + atVersion: null, + }; + + const mockDesignResponse = designUpdateFactory(); + const mockDesignUpdateMutationHandler = jest.fn().mockResolvedValue(mockDesignResponse); + let wrapper; + let mockApollo; + + const createComponent = ({ + design = mockDesign, + descriptionText = '', + showEditor = false, + isSubmitting = false, + designVariables = mockDesignVariables, + contentEditorOnIssues = false, + designUpdateMutationHandler = mockDesignUpdateMutationHandler, + } = {}) => { + mockApollo = createMockApollo([[updateDesignDescriptionMutation, designUpdateMutationHandler]]); + wrapper = mountExtended(DescriptionForm, { + propsData: { + design, + markdownPreviewPath: '/gitlab-org/gitlab-test/preview_markdown?target_type=Issue', + designVariables, + }, + provide: { + glFeatures: { + contentEditorOnIssues, + }, + }, + apolloProvider: mockApollo, + data() { + return { + formFieldProps, + descriptionText, + showEditor, + isSubmitting, + }; + }, + }); + }; + + afterEach(() => { + mockApollo = null; + }); + + const findDesignContent = () => wrapper.findByTestId('design-description-content'); + const findDesignNoneBlock = () => wrapper.findByTestId('design-description-none'); + const findEditDescriptionButton = () => wrapper.findByTestId('edit-description'); + const findSaveDescriptionButton = () => wrapper.findByTestId('save-description'); + const findMarkdownEditor = () => wrapper.findComponent(MarkdownEditor); + const findTextarea = () => wrapper.find('textarea'); + const findCheckboxAtIndex = (index) => wrapper.findAll('input[type="checkbox"]').at(index); + const findAlert = () => wrapper.findComponent(GlAlert); + + describe('user has updateDesign permission', () => { + const ctrlKey = { + ctrlKey: true, + }; + const metaKey = { + metaKey: true, + }; + const mockDescription = 'Hello world'; + const errorMessage = 'Could not update description. Please try again.'; + + beforeEach(() => { + createComponent(); + }); + + it('renders description content with the edit button', () => { + expect(findDesignContent().text()).toEqual('Test description'); + expect(findEditDescriptionButton().exists()).toBe(true); + }); + + it('renders none when description is empty', () => { + createComponent({ design: designFactory({ description: '', descriptionHtml: '' }) }); + + expect(findDesignNoneBlock().text()).toEqual('None'); + }); + + it('renders save button when editor is open', () => { + createComponent({ + design: designFactory({ description: '', descriptionHtml: '' }), + showEditor: true, + }); + + expect(findSaveDescriptionButton().exists()).toBe(true); + expect(findSaveDescriptionButton().attributes('disabled')).toBeUndefined(); + }); + + it('renders the markdown editor with default props', () => { + createComponent({ + showEditor: true, + descriptionText: 'Test description', + }); + + expect(findMarkdownEditor().exists()).toBe(true); + expect(findMarkdownEditor().props()).toMatchObject({ + value: 'Test description', + renderMarkdownPath: '/gitlab-org/gitlab-test/preview_markdown?target_type=Issue', + enableContentEditor: false, + formFieldProps, + autofocus: true, + enableAutocomplete: true, + supportsQuickActions: false, + autosaveKey: `Issue/${getIdFromGraphQLId(mockDesign.issue.id)}/Design/${getIdFromGraphQLId( + mockDesign.id, + )}`, + markdownDocsPath: '/help/user/markdown', + quickActionsDocsPath: '/help/user/project/quick_actions', + }); + }); + + it.each` + isKeyEvent | assertionName | key | keyData + ${true} | ${'Ctrl + Enter keypress'} | ${'ctrl'} | ${ctrlKey} + ${true} | ${'Meta + Enter keypress'} | ${'meta'} | ${metaKey} + ${false} | ${'Save button click'} | ${''} | ${null} + `( + 'hides form and calls mutation when form is submitted via $assertionName', + async ({ isKeyEvent, keyData }) => { + const mockDesignUpdateResponseHandler = jest.fn().mockResolvedValue( + designUpdateFactory({ + description: mockDescription, + descriptionHtml: `<p data-sourcepos="1:1-1:16" dir="auto">${mockDescription}</p>`, + }), + ); + + createComponent({ + showEditor: true, + designUpdateMutationHandler: mockDesignUpdateResponseHandler, + }); + + findMarkdownEditor().vm.$emit('input', 'Hello world'); + if (isKeyEvent) { + findTextarea().trigger('keydown.enter', keyData); + } else { + findSaveDescriptionButton().vm.$emit('click'); + } + + await nextTick(); + + expect(mockDesignUpdateResponseHandler).toHaveBeenCalledWith({ + input: { + description: 'Hello world', + id: 'gid::/gitlab/Design/1', + }, + }); + + await waitForPromises(); + + expect(findMarkdownEditor().exists()).toBe(false); + }, + ); + + it('shows error message when mutation fails', async () => { + const failureHandler = jest.fn().mockRejectedValue(new Error(errorMessage)); + createComponent({ + showEditor: true, + descriptionText: 'Hello world', + designUpdateMutationHandler: failureHandler, + }); + + findMarkdownEditor().vm.$emit('input', 'Hello world'); + findSaveDescriptionButton().vm.$emit('click'); + + await waitForPromises(); + + expect(findAlert().exists()).toBe(true); + expect(findAlert().text()).toBe(errorMessage); + }); + }); + + describe('content has checkboxes', () => { + const mockCheckboxDescription = '- [x] todo 1\n- [ ] todo 2'; + const mockCheckboxDescriptionHtml = `<ul dir="auto" class="task-list" data-sourcepos="1:1-4:0"> + <li class="task-list-item" data-sourcepos="1:1-2:15"> + <input checked="" class="task-list-item-checkbox" type="checkbox"> todo 1</li> + <li class="task-list-item" data-sourcepos="2:1-2:15"> + <input class="task-list-item-checkbox" type="checkbox"> todo 2</li> + </ul>`; + const checkboxDesignDescription = designFactory({ + updateDesign: true, + description: mockCheckboxDescription, + descriptionHtml: mockCheckboxDescriptionHtml, + }); + const mockCheckedDescriptionUpdateResponseHandler = jest.fn().mockResolvedValue( + designUpdateFactory({ + description: '- [x] todo 1\n- [x] todo 2', + descriptionHtml: `<ul dir="auto" class="task-list" data-sourcepos="1:1-4:0"> + <li class="task-list-item" data-sourcepos="1:1-2:15"> + <input checked="" class="task-list-item-checkbox" type="checkbox"> todo 1</li> + <li class="task-list-item" data-sourcepos="2:1-2:15"> + <input class="task-list-item-checkbox" type="checkbox"> todo 2</li> + </ul>`, + }), + ); + const mockUnCheckedDescriptionUpdateResponseHandler = jest.fn().mockResolvedValue( + designUpdateFactory({ + description: '- [ ] todo 1\n- [ ] todo 2', + descriptionHtml: `<ul dir="auto" class="task-list" data-sourcepos="1:1-4:0"> + <li class="task-list-item" data-sourcepos="1:1-2:15"> + <input class="task-list-item-checkbox" type="checkbox"> todo 1</li> + <li class="task-list-item" data-sourcepos="2:1-2:15"> + <input class="task-list-item-checkbox" type="checkbox"> todo 2</li> + </ul>`, + }), + ); + + it.each` + assertionName | mockDesignUpdateResponseHandler | checkboxIndex | checked | expectedDesignDescription + ${'checked'} | ${mockCheckedDescriptionUpdateResponseHandler} | ${1} | ${true} | ${'- [x] todo 1\n- [x] todo 2'} + ${'unchecked'} | ${mockUnCheckedDescriptionUpdateResponseHandler} | ${0} | ${false} | ${'- [ ] todo 1\n- [ ] todo 2'} + `( + 'updates the store object when checkbox is $assertionName', + async ({ + mockDesignUpdateResponseHandler, + checkboxIndex, + checked, + expectedDesignDescription, + }) => { + createComponent({ + design: checkboxDesignDescription, + descriptionText: mockCheckboxDescription, + designUpdateMutationHandler: mockDesignUpdateResponseHandler, + }); + + findCheckboxAtIndex(checkboxIndex).setChecked(checked); + + expect(mockDesignUpdateResponseHandler).toHaveBeenCalledWith({ + input: { + description: expectedDesignDescription, + id: 'gid::/gitlab/Design/1', + }, + }); + + await waitForPromises(); + + expect(renderGFM).toHaveBeenCalled(); + }, + ); + + it('disables checkbox while updating', () => { + createComponent({ + design: checkboxDesignDescription, + descriptionText: mockCheckboxDescription, + }); + + findCheckboxAtIndex(1).setChecked(); + + expect(findCheckboxAtIndex(1).attributes().disabled).toBeDefined(); + }); + }); + + describe('user has no updateDesign permission', () => { + beforeEach(() => { + const designWithNoUpdateUserPermission = designFactory({ + updateDesign: false, + }); + createComponent({ design: designWithNoUpdateUserPermission }); + }); + + it('does not render edit button', () => { + expect(findEditDescriptionButton().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/design_management/components/design_sidebar_spec.js b/spec/frontend/design_management/components/design_sidebar_spec.js index 90424175417..e3f056df4c6 100644 --- a/spec/frontend/design_management/components/design_sidebar_spec.js +++ b/spec/frontend/design_management/components/design_sidebar_spec.js @@ -26,6 +26,13 @@ const $route = { }, }; +const mockDesignVariables = { + fullPath: 'project-path', + iid: '1', + filenames: ['gid::/gitlab/Design/1'], + atVersion: null, +}; + const mutate = jest.fn().mockResolvedValue(); describe('Design management design sidebar component', () => { @@ -47,6 +54,7 @@ describe('Design management design sidebar component', () => { resolvedDiscussionsExpanded: false, markdownPreviewPath: '', isLoading: false, + designVariables: mockDesignVariables, ...props, }, mocks: { diff --git a/spec/frontend/design_management/mock_data/apollo_mock.js b/spec/frontend/design_management/mock_data/apollo_mock.js index 18e08ecd729..063df9366e9 100644 --- a/spec/frontend/design_management/mock_data/apollo_mock.js +++ b/spec/frontend/design_management/mock_data/apollo_mock.js @@ -119,6 +119,8 @@ export const reorderedDesigns = [ notesCount: 2, image: 'image-2', imageV432x230: 'image-2', + description: '', + descriptionHtml: '', currentUserTodos: { __typename: 'ToDo', nodes: [], @@ -132,6 +134,8 @@ export const reorderedDesigns = [ notesCount: 3, image: 'image-1', imageV432x230: 'image-1', + description: '', + descriptionHtml: '', currentUserTodos: { __typename: 'ToDo', nodes: [], @@ -145,6 +149,8 @@ export const reorderedDesigns = [ notesCount: 1, image: 'image-3', imageV432x230: 'image-3', + description: '', + descriptionHtml: '', currentUserTodos: { __typename: 'ToDo', nodes: [], @@ -320,3 +326,59 @@ export const mockCreateImageNoteDiffResponse = { }, }, }; + +export const designFactory = ({ + updateDesign = true, + discussions = {}, + description = 'Test description', + descriptionHtml = '<p data-sourcepos="1:1-1:16" dir="auto">Test description</p>', +} = {}) => ({ + id: 'gid::/gitlab/Design/1', + iid: 1, + filename: 'test.jpg', + fullPath: 'full-design-path', + image: 'test.jpg', + description, + descriptionHtml, + updatedAt: '01-01-2019', + updatedBy: { + name: 'test', + }, + issue: { + id: 'gid::/gitlab/Issue/1', + title: 'My precious issue', + webPath: 'full-issue-path', + webUrl: 'full-issue-url', + participants: { + nodes: [ + { + name: 'Administrator', + username: 'root', + webUrl: 'link-to-author', + avatarUrl: 'link-to-avatar', + __typename: 'UserCore', + }, + ], + __typename: 'UserCoreConnection', + }, + userPermissions: { + updateDesign, + __typename: 'IssuePermissions', + }, + __typename: 'Issue', + }, + discussions, + __typename: 'Design', +}); + +export const designUpdateFactory = (options) => { + return { + data: { + designManagementUpdate: { + errors: [], + design: designFactory(options), + }, + __typename: 'DesignManagementUpdatePayload', + }, + }; +}; diff --git a/spec/frontend/design_management/mock_data/design.js b/spec/frontend/design_management/mock_data/design.js index f2a3a800969..8379408b27c 100644 --- a/spec/frontend/design_management/mock_data/design.js +++ b/spec/frontend/design_management/mock_data/design.js @@ -3,6 +3,8 @@ export default { filename: 'test.jpg', fullPath: 'full-design-path', image: 'test.jpg', + description: 'Test description', + descriptionHtml: 'Test description', updatedAt: '01-01-2019', updatedBy: { name: 'test', diff --git a/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap b/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap index 18b63082e4a..bd37d917faa 100644 --- a/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap +++ b/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap @@ -61,6 +61,12 @@ exports[`Design management design index page renders design index 1`] = ` ull-issue-path </a> + <description-form-stub + design="[object Object]" + designvariables="[object Object]" + markdownpreviewpath="/project-path/preview_markdown?target_type=Issue" + /> + <participants-stub class="gl-mb-4" lazy="true" @@ -192,6 +198,12 @@ exports[`Design management design index page with error GlAlert is rendered in c ull-issue-path </a> + <description-form-stub + design="[object Object]" + designvariables="[object Object]" + markdownpreviewpath="/project-path/preview_markdown?target_type=Issue" + /> + <participants-stub class="gl-mb-4" lazy="true" diff --git a/spec/frontend/design_management/pages/design/index_spec.js b/spec/frontend/design_management/pages/design/index_spec.js index fcb03ea3700..6cddb0cbbf1 100644 --- a/spec/frontend/design_management/pages/design/index_spec.js +++ b/spec/frontend/design_management/pages/design/index_spec.js @@ -188,6 +188,12 @@ describe('Design management design index page', () => { markdownPreviewPath: '/project-path/preview_markdown?target_type=Issue', resolvedDiscussionsExpanded: false, isLoading: false, + designVariables: { + fullPath: 'project-path', + iid: '1', + filenames: ['gid::/gitlab/Design/1'], + atVersion: null, + }, }); }); diff --git a/spec/frontend/design_management/pages/index_spec.js b/spec/frontend/design_management/pages/index_spec.js index 0a3f513918c..90caf956126 100644 --- a/spec/frontend/design_management/pages/index_spec.js +++ b/spec/frontend/design_management/pages/index_spec.js @@ -310,6 +310,8 @@ describe('Design management index page', () => { }, image: '', imageV432x230: '', + description: '', + descriptionHtml: '', filename: 'test', fullPath: '', event: 'NONE', diff --git a/spec/frontend/design_management/utils/design_management_utils_spec.js b/spec/frontend/design_management/utils/design_management_utils_spec.js index dc6056badb9..cbfe8e3a243 100644 --- a/spec/frontend/design_management/utils/design_management_utils_spec.js +++ b/spec/frontend/design_management/utils/design_management_utils_spec.js @@ -89,6 +89,8 @@ describe('optimistic responses', () => { id: -1, image: '', imageV432x230: '', + description: '', + descriptionHtml: '', filename: 'test', fullPath: '', notesCount: 0, diff --git a/spec/lib/banzai/filter/inline_alert_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_alert_metrics_filter_spec.rb index be40195f001..cc6d9c67b1b 100644 --- a/spec/lib/banzai/filter/inline_alert_metrics_filter_spec.rb +++ b/spec/lib/banzai/filter/inline_alert_metrics_filter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Banzai::Filter::InlineAlertMetricsFilter do +RSpec.describe Banzai::Filter::InlineAlertMetricsFilter, feature_category: :metrics do include FilterSpecHelper let(:params) { ['foo', 'bar', 12] } diff --git a/spec/lib/banzai/filter/inline_cluster_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_cluster_metrics_filter_spec.rb index fe048daa601..364a8160094 100644 --- a/spec/lib/banzai/filter/inline_cluster_metrics_filter_spec.rb +++ b/spec/lib/banzai/filter/inline_cluster_metrics_filter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Banzai::Filter::InlineClusterMetricsFilter do +RSpec.describe Banzai::Filter::InlineClusterMetricsFilter, feature_category: :metrics do include FilterSpecHelper let!(:cluster) { create(:cluster) } diff --git a/spec/lib/banzai/filter/inline_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb index cdebd886b16..2a8f4507429 100644 --- a/spec/lib/banzai/filter/inline_metrics_filter_spec.rb +++ b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Banzai::Filter::InlineMetricsFilter do +RSpec.describe Banzai::Filter::InlineMetricsFilter, feature_category: :metrics do include FilterSpecHelper let(:environment_id) { 12 } diff --git a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb index 9ccea1cc3e9..3512ae97f54 100644 --- a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb +++ b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Banzai::Filter::InlineMetricsRedactorFilter do +RSpec.describe Banzai::Filter::InlineMetricsRedactorFilter, feature_category: :metrics do include FilterSpecHelper let_it_be(:project) { create(:project) } diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb index 64d65528426..251e6efe50b 100644 --- a/spec/lib/banzai/filter/markdown_filter_spec.rb +++ b/spec/lib/banzai/filter/markdown_filter_spec.rb @@ -23,54 +23,43 @@ RSpec.describe Banzai::Filter::MarkdownFilter, feature_category: :team_planning end describe 'code block' do - context 'using CommonMark' do - before do - stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark) - end + it 'adds language to lang attribute when specified' do + result = filter("```html\nsome code\n```", no_sourcepos: true) - it 'adds language to lang attribute when specified' do - result = filter("```html\nsome code\n```", no_sourcepos: true) - - expect(result).to start_with('<pre lang="html"><code>') - end + expect(result).to start_with('<pre lang="html"><code>') + end - it 'does not add language to lang attribute when not specified' do - result = filter("```\nsome code\n```", no_sourcepos: true) + it 'does not add language to lang attribute when not specified' do + result = filter("```\nsome code\n```", no_sourcepos: true) - expect(result).to start_with('<pre><code>') - end + expect(result).to start_with('<pre><code>') + end - it 'works with utf8 chars in language' do - result = filter("```日\nsome code\n```", no_sourcepos: true) + it 'works with utf8 chars in language' do + result = filter("```日\nsome code\n```", no_sourcepos: true) - expect(result).to start_with('<pre lang="日"><code>') - end + expect(result).to start_with('<pre lang="日"><code>') + end - it 'works with additional language parameters' do - result = filter("```ruby:red gem foo\nsome code\n```", no_sourcepos: true) + it 'works with additional language parameters' do + result = filter("```ruby:red gem foo\nsome code\n```", no_sourcepos: true) - expect(result).to start_with('<pre lang="ruby:red" data-meta="gem foo"><code>') - end + expect(result).to include('lang="ruby:red"') + expect(result).to include('data-meta="gem foo"') end end describe 'source line position' do - context 'using CommonMark' do - before do - stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :common_mark) - end - - it 'defaults to add data-sourcepos' do - result = filter('test') + it 'defaults to add data-sourcepos' do + result = filter('test') - expect(result).to eq '<p data-sourcepos="1:1-1:4">test</p>' - end + expect(result).to eq '<p data-sourcepos="1:1-1:4">test</p>' + end - it 'disables data-sourcepos' do - result = filter('test', no_sourcepos: true) + it 'disables data-sourcepos' do + result = filter('test', no_sourcepos: true) - expect(result).to eq '<p>test</p>' - end + expect(result).to eq '<p>test</p>' end end diff --git a/spec/lib/banzai/filter/truncate_visible_filter_spec.rb b/spec/lib/banzai/filter/truncate_visible_filter_spec.rb index 404b23a886f..0d352850682 100644 --- a/spec/lib/banzai/filter/truncate_visible_filter_spec.rb +++ b/spec/lib/banzai/filter/truncate_visible_filter_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Banzai::Filter::TruncateVisibleFilter, feature_category: :team_pl # Since we're truncating nodes of an html document, actually use the # full pipeline to generate full documents. def convert_markdown(text, context = {}) - Banzai::Pipeline::FullPipeline.to_html(text, { project: project }.merge(context)) + Banzai::Pipeline::FullPipeline.to_html(text, { project: project, no_sourcepos: true }.merge(context)) end shared_examples_for 'truncates text' do diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb index 6654eec7241..5d56035f6df 100644 --- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb @@ -9,6 +9,10 @@ RSpec.describe Banzai::Pipeline::FullPipeline, feature_category: :team_planning let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } + before do + stub_commonmark_sourcepos_disabled + end + it 'handles markdown inside a reference' do markdown = "[some `code` inside](#{issue.to_reference})" result = described_class.call(markdown, project: project) @@ -135,6 +139,8 @@ RSpec.describe Banzai::Pipeline::FullPipeline, feature_category: :team_planning end it 'does not insert a table of contents' do + stub_commonmark_sourcepos_disabled + output = described_class.to_html(invalid_markdown, project: project) expect(output).to include("test #{tag_html}") @@ -163,6 +169,8 @@ RSpec.describe Banzai::Pipeline::FullPipeline, feature_category: :team_planning end it 'converts user reference with escaped underscore because of italics' do + stub_commonmark_sourcepos_disabled + markdown = '_@test\__' output = described_class.to_html(markdown, project: project) diff --git a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb index e8008aeaf57..c19d890a703 100644 --- a/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb +++ b/spec/lib/gitlab/auth/ldap/auth_hash_spec.rb @@ -27,12 +27,12 @@ RSpec.describe Gitlab::Auth::Ldap::AuthHash do end let(:raw_info) do - { - uid: ['123456'], - email: ['johnsmith@example.com'], - cn: ['Smith, J.'], - fullName: ['John Smith'] - } + Net::LDAP::Entry.new.tap do |entry| + entry['uid'] = ['123456'] + entry['email'] = ['johnsmith@example.com'] + entry['cn'] = ['Smith, J.'] + entry['fullName'] = ['John Smith'] + end end context "without overridden attributes" do diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb index 81d423598f2..2246272d3af 100644 --- a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb +++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb @@ -27,10 +27,14 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do end let(:markdown) { '`Foo`' } - let(:html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Foo</code></p>' } + let(:html) { '<p dir="auto"><code>Foo</code></p>' } let(:updated_markdown) { '`Bar`' } - let(:updated_html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Bar</code></p>' } + let(:updated_html) { '<p dir="auto"><code>Bar</code></p>' } + + before do + stub_commonmark_sourcepos_disabled + end context 'an unchanged markdown field' do let(:thing) { klass.new(project_id: project.id, namespace_id: project.project_namespace_id, title: markdown) } diff --git a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb index 8e75009099d..071f6e090c6 100644 --- a/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb +++ b/spec/lib/gitlab/markdown_cache/redis/extension_spec.rb @@ -82,9 +82,13 @@ RSpec.describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cach end describe "#refresh_markdown_cache!" do + before do + stub_commonmark_sourcepos_disabled + end + it "stores the value in redis" do expected_results = { "title_html" => "`Hello`", - "description_html" => "<p data-sourcepos=\"1:1-1:7\" dir=\"auto\"><code>World</code></p>", + "description_html" => "<p dir=\"auto\"><code>World</code></p>", "cached_markdown_version" => cache_version.to_s } thing.refresh_markdown_cache! @@ -101,7 +105,7 @@ RSpec.describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cach thing.refresh_markdown_cache! expect(thing.title_html).to eq('`Hello`') - expect(thing.description_html).to eq("<p data-sourcepos=\"1:1-1:7\" dir=\"auto\"><code>World</code></p>") + expect(thing.description_html).to eq("<p dir=\"auto\"><code>World</code></p>") expect(thing.cached_markdown_version).to eq(cache_version) end end diff --git a/spec/migrations/20230412141541_reschedule_links_avoiding_duplication_spec.rb b/spec/migrations/20230412141541_reschedule_links_avoiding_duplication_spec.rb index 06eccf03ca4..342504ca3c5 100644 --- a/spec/migrations/20230412141541_reschedule_links_avoiding_duplication_spec.rb +++ b/spec/migrations/20230412141541_reschedule_links_avoiding_duplication_spec.rb @@ -10,13 +10,7 @@ RSpec.describe RescheduleLinksAvoidingDuplication, :migration, feature_category: it 'schedules a batched background migration' do migrate! - expect(migration).to have_scheduled_batched_migration( - table_name: :vulnerability_occurrences, - column_name: :id, - interval: described_class::DELAY_INTERVAL, - batch_size: described_class::BATCH_SIZE, - sub_batch_size: described_class::SUB_BATCH_SIZE - ) + expect(migration).not_to have_scheduled_batched_migration end end diff --git a/spec/migrations/20230522111534_reschedule_migration_for_links_from_metadata_spec.rb b/spec/migrations/20230522111534_reschedule_migration_for_links_from_metadata_spec.rb new file mode 100644 index 00000000000..efaef3e6892 --- /dev/null +++ b/spec/migrations/20230522111534_reschedule_migration_for_links_from_metadata_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe RescheduleMigrationForLinksFromMetadata, :migration, feature_category: :vulnerability_management do + let(:migration) { described_class::MIGRATION } + + describe '#up' do + it 'schedules a batched background migration' do + migrate! + + expect(migration).to have_scheduled_batched_migration( + table_name: :vulnerability_occurrences, + column_name: :id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + end + end + + describe '#down' do + it 'deletes all batched migration records' do + migrate! + schema_migrate_down! + + expect(migration).not_to have_scheduled_batched_migration + end + end +end diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index f85f636ebe5..97e43f3494c 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -41,10 +41,10 @@ RSpec.describe CacheMarkdownField, :clean_gitlab_redis_cache do end let(:markdown) { '`Foo`' } - let(:html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Foo</code></p>' } + let(:html) { '<p dir="auto"><code>Foo</code></p>' } let(:updated_markdown) { '`Bar`' } - let(:updated_html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Bar</code></p>' } + let(:updated_html) { '<p dir="auto"><code>Bar</code></p>' } let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 } @@ -112,6 +112,7 @@ RSpec.describe CacheMarkdownField, :clean_gitlab_redis_cache do let(:thing) { klass.new(description: markdown, description_html: html, cached_markdown_version: cache_version) } before do + stub_commonmark_sourcepos_disabled thing.description = updated_markdown end @@ -139,6 +140,7 @@ RSpec.describe CacheMarkdownField, :clean_gitlab_redis_cache do let(:thing) { klass.new(description: markdown, description_html: html, cached_markdown_version: cache_version) } before do + stub_commonmark_sourcepos_disabled thing.description = updated_markdown end @@ -253,6 +255,7 @@ RSpec.describe CacheMarkdownField, :clean_gitlab_redis_cache do context 'when the markdown cache is up to date' do before do + stub_commonmark_sourcepos_disabled thing.try(:save) end @@ -269,6 +272,7 @@ RSpec.describe CacheMarkdownField, :clean_gitlab_redis_cache do context 'when a field can be cached' do it 'returns the html' do + stub_commonmark_sourcepos_disabled thing.description = updated_markdown expect(thing.rendered_field_content(:description)).to eq updated_html @@ -332,6 +336,7 @@ RSpec.describe CacheMarkdownField, :clean_gitlab_redis_cache do describe '#save' do context 'when cache is outdated' do before do + stub_commonmark_sourcepos_disabled thing.cached_markdown_version += 1 end @@ -433,6 +438,7 @@ RSpec.describe CacheMarkdownField, :clean_gitlab_redis_cache do end it 'correctly updates cached HTML even if refresh_markdown_cache is called before updating the attribute' do + stub_commonmark_sourcepos_disabled thing.refresh_markdown_cache thing.update!(description: updated_markdown) diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb index fdfc4ec7cc4..f51c994ca5d 100644 --- a/spec/models/diff_discussion_spec.rb +++ b/spec/models/diff_discussion_spec.rb @@ -14,8 +14,8 @@ RSpec.describe DiffDiscussion do describe '#reply_attributes' do it 'includes position and original_position' do attributes = subject.reply_attributes - expect(attributes[:position]).to eq(diff_note.position.to_json) - expect(attributes[:original_position]).to eq(diff_note.original_position.to_json) + expect(attributes[:position]).to eq(Gitlab::Json.dump(diff_note.position.to_h)) + expect(attributes[:original_position]).to eq(Gitlab::Json.dump(diff_note.original_position.to_h)) end end diff --git a/spec/models/release_highlight_spec.rb b/spec/models/release_highlight_spec.rb index d51998cc556..50a607040b6 100644 --- a/spec/models/release_highlight_spec.rb +++ b/spec/models/release_highlight_spec.rb @@ -65,7 +65,9 @@ RSpec.describe ReleaseHighlight, :clean_gitlab_redis_cache, feature_category: :r end it 'parses the description as markdown and returns html, and links are target="_blank"' do - expect(subject[:items].first['description']).to match('<p data-sourcepos="1:1-1:62" dir="auto">bright and sunshinin\' <a href="https://en.wikipedia.org/wiki/Day" rel="nofollow noreferrer noopener" target="_blank">day</a></p>') + stub_commonmark_sourcepos_disabled + + expect(subject[:items].first['description']).to eq('<p dir="auto">bright and sunshinin\' <a href="https://en.wikipedia.org/wiki/Day" rel="nofollow noreferrer noopener" target="_blank">day</a></p>') end it 'logs an error if theres an error parsing markdown for an item, and skips it' do diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb index 52b548ce8b9..63a657f3962 100644 --- a/spec/requests/api/graphql/ci/runner_spec.rb +++ b/spec/requests/api/graphql/ci/runner_spec.rb @@ -74,6 +74,8 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do end it 'retrieves expected fields' do + stub_commonmark_sourcepos_disabled + post_graphql(query, current_user: user) runner_data = graphql_data_at(:runner) diff --git a/spec/requests/projects/metrics_dashboard_spec.rb b/spec/requests/projects/metrics_dashboard_spec.rb deleted file mode 100644 index 7e94bc6134d..00000000000 --- a/spec/requests/projects/metrics_dashboard_spec.rb +++ /dev/null @@ -1,171 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Projects::MetricsDashboardController', feature_category: :metrics do - let_it_be(:project) { create(:project) } - let_it_be(:environment) { create(:environment, project: project) } - let_it_be(:environment2) { create(:environment, project: project) } - let_it_be(:user) { project.first_owner } - - before do - stub_feature_flags(remove_monitor_metrics: false) - project.add_developer(user) - login_as(user) - stub_feature_flags(remove_monitor_metrics: false) - end - - shared_examples 'metrics dashboard is unavailable' do - context 'when metrics dashboard feature is unavailable' do - before do - stub_feature_flags(remove_monitor_metrics: true) - end - - it 'returns 404 not found' do - send_request - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - - describe 'GET /:namespace/:project/-/metrics' do - include_examples 'metrics dashboard is unavailable' - - it "redirects to default environment's metrics dashboard" do - send_request - expect(response).to redirect_to(dashboard_route(environment: environment)) - end - - it 'assigns default_environment' do - send_request - expect(assigns(:default_environment).id).to eq(environment.id) - end - - it 'retains existing parameters when redirecting' do - params = { - dashboard_path: '.gitlab/dashboards/dashboard_path.yml', - page: 'panel/new', - group: 'System metrics (Kubernetes)', - title: 'Memory Usage (Pod average)', - y_label: 'Memory Used per Pod (MB)' - } - send_request(params) - - expect(response).to redirect_to(dashboard_route(params.merge(environment: environment.id))) - end - - context 'with remove_monitor_metrics returning true' do - before do - stub_feature_flags(remove_monitor_metrics: true) - end - - it 'renders 404 page' do - send_request - expect(response).to have_gitlab_http_status(:not_found) - end - end - - context 'with anonymous user and public dashboard visibility' do - let(:anonymous_user) { create(:user) } - let(:project) do - create(:project, :public, :metrics_dashboard_enabled) - end - - before do - project.update!(metrics_dashboard_access_level: 'enabled') - - login_as(anonymous_user) - end - - it 'returns 200' do - send_request - - expect(response).to have_gitlab_http_status(:ok) - end - end - end - - describe 'GET /:namespace/:project/-/metrics?environment=:environment.id' do - include_examples 'metrics dashboard is unavailable' - - it 'returns 200' do - send_request(environment: environment2.id) - expect(response).to have_gitlab_http_status(:ok) - end - - it 'assigns query param environment' do - send_request(environment: environment2.id) - expect(assigns(:environment).id).to eq(environment2.id) - end - - context 'when query param environment does not exist' do - it 'responds with 404' do - send_request(environment: non_existing_record_id) - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - - describe 'GET /:namespace/:project/-/metrics/:dashboard_path' do - let(:dashboard_path) { '.gitlab/dashboards/dashboard_path.yml' } - - include_examples 'metrics dashboard is unavailable' - - it 'returns 200' do - send_request(dashboard_path: dashboard_path, environment: environment.id) - expect(response).to have_gitlab_http_status(:ok) - end - - it 'assigns environment' do - send_request(dashboard_path: dashboard_path, environment: environment.id) - expect(assigns(:environment).id).to eq(environment.id) - end - end - - describe 'GET :/namespace/:project/-/metrics/:dashboard_path?environment=:environment.id' do - let(:dashboard_path) { '.gitlab/dashboards/dashboard_path.yml' } - - include_examples 'metrics dashboard is unavailable' - - it 'returns 200' do - send_request(dahboard_path: dashboard_path, environment: environment.id) - expect(response).to have_gitlab_http_status(:ok) - end - - it 'assigns query param environment' do - send_request(dashboard_path: dashboard_path, environment: environment2.id) - expect(assigns(:environment).id).to eq(environment2.id) - end - - context 'when query param environment does not exist' do - it 'responds with 404' do - send_request(dashboard_path: dashboard_path, environment: non_existing_record_id) - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - - describe 'GET :/namespace/:project/-/metrics/:page' do - include_examples 'metrics dashboard is unavailable' - - it 'returns 200 with path param page' do - send_request(page: 'panel/new', environment: environment.id) - - expect(response).to have_gitlab_http_status(:ok) - end - - it 'returns 200 with dashboard and path param page' do - send_request(dashboard_path: 'dashboard.yml', page: 'panel/new', environment: environment.id) - - expect(response).to have_gitlab_http_status(:ok) - end - end - - def send_request(params = {}) - get dashboard_route(params) - end - - def dashboard_route(params = {}) - namespace_project_metrics_dashboard_path(namespace_id: project.namespace, project_id: project, **params) - end -end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index c2458d3485f..c78adc2dcef 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -916,73 +916,6 @@ RSpec.describe 'project routing' do end end - describe Projects::MetricsDashboardController, 'routing' do - it 'routes to #show with no dashboard_path' do - expect(get: "/gitlab/gitlabhq/-/metrics").to route_to( - "projects/metrics_dashboard#show", - **base_params - ) - end - - it 'routes to #show with only dashboard_path' do - expect(get: "/gitlab/gitlabhq/-/metrics/dashboard1.yml").to route_to( - "projects/metrics_dashboard#show", - dashboard_path: 'dashboard1.yml', - **base_params - ) - end - - it 'routes to #show' do - expect(get: "/gitlab/gitlabhq/-/metrics/panel/new").to route_to( - "projects/metrics_dashboard#show", - **base_params - ) - end - - it 'routes to #show with dashboard_path' do - expect(get: "/gitlab/gitlabhq/-/metrics/config%2Fprometheus%2Fcommon_metrics.yml/panel/new").to route_to( - "projects/metrics_dashboard#show", - dashboard_path: 'config/prometheus/common_metrics.yml', - **base_params - ) - end - - it 'routes to 404 with invalid page' do - expect(get: "/gitlab/gitlabhq/-/metrics/invalid_page").to route_to( - 'application#route_not_found', - unmatched_route: 'gitlab/gitlabhq/-/metrics/invalid_page' - ) - end - - it 'routes to 404 without format for invalid page' do - expect(get: "/gitlab/gitlabhq/-/metrics/invalid_page.md").to route_to( - 'application#route_not_found', - unmatched_route: 'gitlab/gitlabhq/-/metrics/invalid_page.md' - ) - end - - it 'routes to 404 with invalid dashboard_path' do - expect(get: "/gitlab/gitlabhq/-/metrics/invalid_dashboard").to route_to( - 'application#route_not_found', - unmatched_route: 'gitlab/gitlabhq/-/metrics/invalid_dashboard' - ) - end - - it 'routes to 404 with invalid dashboard_path and valid page' do - expect(get: "/gitlab/gitlabhq/-/metrics/dashboard1/panel/new").to route_to( - 'application#route_not_found', - unmatched_route: 'gitlab/gitlabhq/-/metrics/dashboard1/panel/new' - ) - end - - it 'routes to 404 with valid dashboard_path and invalid page' do - expect(get: "/gitlab/gitlabhq/-/metrics/dashboard1.yml/invalid_page").to route_to( - 'application#route_not_found', - unmatched_route: 'gitlab/gitlabhq/-/metrics/dashboard1.yml/invalid_page' - ) - end - end - context 'with a non-existent project' do it 'routes to 404 with get request' do expect(get: "/gitlab/not_exist").to route_to( diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb index da45d3c8ebc..5f9b1a59bf9 100644 --- a/spec/services/projects/operations/update_service_spec.rb +++ b/spec/services/projects/operations/update_service_spec.rb @@ -92,61 +92,6 @@ RSpec.describe Projects::Operations::UpdateService, feature_category: :groups_an end end - context 'metrics dashboard setting' do - let(:params) do - { - metrics_setting_attributes: { - external_dashboard_url: 'http://gitlab.com', - dashboard_timezone: 'utc' - } - } - end - - context 'without existing metrics dashboard setting' do - it 'creates a setting' do - expect(result[:status]).to eq(:success) - - expect(project.reload.metrics_setting.external_dashboard_url).to eq( - 'http://gitlab.com' - ) - expect(project.metrics_setting.dashboard_timezone).to eq('utc') - end - end - - context 'with existing metrics dashboard setting' do - before do - create(:project_metrics_setting, project: project) - end - - it 'updates the settings' do - expect(result[:status]).to eq(:success) - - expect(project.reload.metrics_setting.external_dashboard_url).to eq( - 'http://gitlab.com' - ) - expect(project.metrics_setting.dashboard_timezone).to eq('utc') - end - end - - context 'with blank external_dashboard_url' do - let(:params) do - { - metrics_setting_attributes: { - external_dashboard_url: '', - dashboard_timezone: 'utc' - } - } - end - - it 'updates dashboard_timezone' do - expect(result[:status]).to eq(:success) - - expect(project.reload.metrics_setting.external_dashboard_url).to be(nil) - expect(project.metrics_setting.dashboard_timezone).to eq('utc') - end - end - end - context 'error tracking' do context 'with existing error tracking setting' do let(:params) do @@ -354,62 +299,6 @@ RSpec.describe Projects::Operations::UpdateService, feature_category: :groups_an end end - context 'grafana integration' do - let(:params) do - { - grafana_integration_attributes: { - grafana_url: 'http://new.grafana.com', - token: 'VerySecureToken=' - } - } - end - - context 'without existing grafana integration' do - it 'creates an integration' do - expect(result[:status]).to eq(:success) - - expected_attrs = params[:grafana_integration_attributes] - integration = project.reload.grafana_integration - - expect(integration.grafana_url).to eq(expected_attrs[:grafana_url]) - expect(integration.send(:token)).to eq(expected_attrs[:token]) - end - end - - context 'with an existing grafana integration' do - before do - create(:grafana_integration, project: project) - end - - it 'updates the settings' do - expect(result[:status]).to eq(:success) - - expected_attrs = params[:grafana_integration_attributes] - integration = project.reload.grafana_integration - - expect(integration.grafana_url).to eq(expected_attrs[:grafana_url]) - expect(integration.send(:token)).to eq(expected_attrs[:token]) - end - - context 'with all grafana attributes blank in params' do - let(:params) do - { - grafana_integration_attributes: { - grafana_url: '', - token: '' - } - } - end - - it 'destroys the metrics_setting entry in DB' do - expect(result[:status]).to eq(:success) - - expect(project.reload.grafana_integration).to be_nil - end - end - end - end - context 'prometheus integration' do context 'prometheus params were passed into service' do let!(:prometheus_integration) do diff --git a/spec/support/helpers/markdown_helpers.rb b/spec/support/helpers/markdown_helpers.rb new file mode 100644 index 00000000000..9a25238465a --- /dev/null +++ b/spec/support/helpers/markdown_helpers.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module MarkdownHelpers + def remove_sourcepos(html) + html.gsub(/\ ?data-sourcepos=".*?"/, '') + end +end diff --git a/spec/support/matchers/sourcepos_matchers.rb b/spec/support/matchers/sourcepos_matchers.rb new file mode 100644 index 00000000000..903fe2bd201 --- /dev/null +++ b/spec/support/matchers/sourcepos_matchers.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# remove data-sourcepos from compare +RSpec::Matchers.define :eq_no_sourcepos do |expected| + include MarkdownHelpers + + match do |actual| + remove_sourcepos(actual) == expected + end + + description do + "equal ignoring sourcepos #{expected}" + end +end |