diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-17 21:08:54 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-17 21:08:54 +0300 |
commit | 1038f06b8654472558735796de54647888dabec4 (patch) | |
tree | 598742afff03a355d27236bc6b8966d47c66e737 /spec | |
parent | 6c41e447edac3453ae0df99fb9232ec71b679b75 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
29 files changed, 635 insertions, 140 deletions
diff --git a/spec/controllers/groups/releases_controller_spec.rb b/spec/controllers/groups/releases_controller_spec.rb index 582a77b1c50..8b08f913e10 100644 --- a/spec/controllers/groups/releases_controller_spec.rb +++ b/spec/controllers/groups/releases_controller_spec.rb @@ -20,11 +20,11 @@ RSpec.describe Groups::ReleasesController do context 'as json' do let(:format) { :json } - subject { get :index, params: { group_id: group }, format: format } + subject(:index) { get :index, params: { group_id: group }, format: format } context 'json_response' do before do - subject + index end it 'returns an application/json content_type' do @@ -38,7 +38,7 @@ RSpec.describe Groups::ReleasesController do context 'the user is not authorized' do before do - subject + index end it 'does not return any releases' do @@ -54,12 +54,38 @@ RSpec.describe Groups::ReleasesController do it "returns all group's public and private project's releases as JSON, ordered by released_at" do sign_in(guest) - subject + index expect(json_response.map {|r| r['tag'] } ).to match_array(%w(p2 p1 v2 v1)) end end + context 'group_releases_finder_inoperator feature flag' do + before do + sign_in(guest) + end + + it 'calls old code when disabled' do + stub_feature_flags(group_releases_finder_inoperator: false) + + allow(ReleasesFinder).to receive(:new).and_call_original + + index + + expect(ReleasesFinder).to have_received(:new) + end + + it 'calls new code when enabled' do + stub_feature_flags(group_releases_finder_inoperator: true) + + allow(Releases::GroupReleasesFinder).to receive(:new).and_call_original + + index + + expect(Releases::GroupReleasesFinder).to have_received(:new) + end + end + context 'N+1 queries' do it 'avoids N+1 database queries' do control_count = ActiveRecord::QueryRecorder.new { subject }.count diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 33e22c377a3..df93bd773a6 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -707,6 +707,20 @@ RSpec.describe 'Admin updates settings' do include_examples 'regular throttle rate limit settings' end + + it 'changes search rate limits' do + visit network_admin_application_settings_path + + page.within('.as-search-limits') do + fill_in 'Maximum number of requests per minute for an authenticated user', with: 98 + fill_in 'Maximum number of requests per minute for an unauthenticated IP address', with: 76 + click_button 'Save changes' + end + + expect(page).to have_content "Application settings saved successfully" + expect(current_settings.search_rate_limit).to eq(98) + expect(current_settings.search_rate_limit_unauthenticated).to eq(76) + end end context 'Preferences page' do diff --git a/spec/finders/releases/group_releases_finder_spec.rb b/spec/finders/releases/group_releases_finder_spec.rb new file mode 100644 index 00000000000..b8899a8ee40 --- /dev/null +++ b/spec/finders/releases/group_releases_finder_spec.rb @@ -0,0 +1,204 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Releases::GroupReleasesFinder do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:project, :repository, group: group) } + let(:params) { {} } + let(:args) { {} } + let(:repository) { project.repository } + let(:v1_0_0) { create(:release, project: project, tag: 'v1.0.0') } + let(:v1_1_0) { create(:release, project: project, tag: 'v1.1.0') } + let(:v1_1_1) { create(:release, project: project, tag: 'v1.1.1') } + + before do + v1_0_0.update_attribute(:released_at, 2.days.ago) + v1_1_0.update_attribute(:released_at, 1.day.ago) + v1_1_1.update_attribute(:released_at, 0.5.days.ago) + end + + shared_examples_for 'when the user is not part of the project' do + it 'returns no releases' do + is_expected.to be_empty + end + end + + shared_examples_for 'when the user is not part of the group' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_release, group).and_return(false) + end + + it 'returns no releases' do + is_expected.to be_empty + end + end + + shared_examples_for 'preload' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_release, group).and_return(true) + end + + it 'preloads associations' do + expect(Release).to receive(:preloaded).once.and_call_original + + releases + end + + context 'when preload is false' do + let(:args) { { preload: false } } + + it 'does not preload associations' do + expect(Release).not_to receive(:preloaded) + + releases + end + end + end + + describe 'when parent is a group' do + context 'without subgroups' do + let(:project2) { create(:project, :repository, namespace: group) } + let!(:v6) { create(:release, project: project2, tag: 'v6') } + + subject(:releases) { described_class.new(group, user, params).execute(**args) } + + it_behaves_like 'preload' + it_behaves_like 'when the user is not part of the group' + + context 'when the user is a project guest on one sibling project' do + before do + project.add_guest(user) + end + + it 'does not return any releases' do + expect(releases.size).to eq(0) + expect(releases).to eq([]) + end + end + + context 'when the user is a guest on the group' do + before do + group.add_guest(user) + v1_0_0.update_attribute(:released_at, 3.days.ago) + v6.update_attribute(:released_at, 2.days.ago) + v1_1_0.update_attribute(:released_at, 1.day.ago) + v1_1_1.update_attribute(:released_at, v1_1_0.released_at) + end + + it 'sorts by release date and id' do + expect(releases.size).to eq(4) + expect(releases).to eq([v1_1_1, v1_1_0, v6, v1_0_0]) + end + end + end + + describe 'with subgroups' do + let(:params) { { include_subgroups: true } } + + subject(:releases) { described_class.new(group, user, params).execute(**args) } + + context 'with a single-level subgroup' do + let(:subgroup) { create(:group, parent: group) } + let(:project2) { create(:project, :repository, namespace: subgroup) } + let!(:v6) { create(:release, project: project2, tag: 'v6') } + + it_behaves_like 'when the user is not part of the group' + + context 'when the user a project guest in the subgroup project' do + before do + project2.add_guest(user) + end + + it 'does not return any releases' do + expect(releases).to match_array([]) + end + end + + context 'when the user is a guest on the group' do + before do + group.add_guest(user) + v6.update_attribute(:released_at, 2.days.ago) + end + + it 'returns all releases' do + expect(releases).to match_array([v1_1_1, v1_1_0, v1_0_0, v6]) + end + end + end + + context 'with a multi-level subgroup' do + let(:subgroup) { create(:group, parent: group) } + let(:subsubgroup) { create(:group, parent: subgroup) } + let(:project2) { create(:project, :repository, namespace: subgroup) } + let(:project3) { create(:project, :repository, namespace: subsubgroup) } + let!(:v6) { create(:release, project: project2, tag: 'v6') } + let!(:p3) { create(:release, project: project3, tag: 'p3') } + + before do + v6.update_attribute(:released_at, 2.days.ago) + p3.update_attribute(:released_at, 3.days.ago) + end + + it_behaves_like 'when the user is not part of the group' + + context 'when the user a project guest in the subgroup and subsubgroup project' do + before do + project2.add_guest(user) + project3.add_guest(user) + end + + it 'does not return any releases' do + expect(releases).to match_array([]) + end + end + + context 'when the user a project guest in the subsubgroup project' do + before do + project3.add_guest(user) + end + + it 'does not return any releases' do + expect(releases).to match_array([]) + end + end + + context 'when the user a guest on the group' do + before do + group.add_guest(user) + end + + it 'returns all releases' do + expect(releases).to match_array([v1_1_1, v1_1_0, v6, v1_0_0, p3]) + end + end + + context 'performance testing' do + shared_examples 'avoids N+1 queries' do |query_params = {}| + context 'with subgroups' do + let(:params) { query_params } + + it 'include_subgroups avoids N+1 queries' do + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do + releases + end.count + + subgroups = create_list(:group, 10, parent: group) + projects = create_list(:project, 10, namespace: subgroups[0]) + create_list(:release, 10, project: projects[0], author: user) + + expect do + releases + end.not_to exceed_all_query_limit(control_count) + end + end + end + + it_behaves_like 'avoids N+1 queries' + it_behaves_like 'avoids N+1 queries', { simple: true } + end + end + end + end +end diff --git a/spec/frontend/content_editor/extensions/code_block_highlight_spec.js b/spec/frontend/content_editor/extensions/code_block_highlight_spec.js index 05fa0f79ef0..02e5b1dc271 100644 --- a/spec/frontend/content_editor/extensions/code_block_highlight_spec.js +++ b/spec/frontend/content_editor/extensions/code_block_highlight_spec.js @@ -1,5 +1,5 @@ import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight'; -import { createTestEditor } from '../test_utils'; +import { createTestEditor, createDocBuilder, triggerNodeInputRule } from '../test_utils'; const CODE_BLOCK_HTML = `<pre class="code highlight js-syntax-highlight language-javascript" lang="javascript" v-pre="true"> <code> @@ -12,34 +12,78 @@ const CODE_BLOCK_HTML = `<pre class="code highlight js-syntax-highlight language describe('content_editor/extensions/code_block_highlight', () => { let parsedCodeBlockHtmlFixture; let tiptapEditor; + let doc; + let codeBlock; + let languageLoader; const parseHTML = (html) => new DOMParser().parseFromString(html, 'text/html'); const preElement = () => parsedCodeBlockHtmlFixture.querySelector('pre'); beforeEach(() => { - tiptapEditor = createTestEditor({ extensions: [CodeBlockHighlight] }); - parsedCodeBlockHtmlFixture = parseHTML(CODE_BLOCK_HTML); + languageLoader = { loadLanguages: jest.fn() }; + tiptapEditor = createTestEditor({ + extensions: [CodeBlockHighlight.configure({ languageLoader })], + }); - tiptapEditor.commands.setContent(CODE_BLOCK_HTML); + ({ + builders: { doc, codeBlock }, + } = createDocBuilder({ + tiptapEditor, + names: { + codeBlock: { nodeType: CodeBlockHighlight.name }, + }, + })); }); - it('extracts language and params attributes from Markdown API output', () => { - const language = preElement().getAttribute('lang'); + describe('when parsing HTML', () => { + beforeEach(() => { + parsedCodeBlockHtmlFixture = parseHTML(CODE_BLOCK_HTML); - expect(tiptapEditor.getJSON().content[0].attrs).toMatchObject({ - language, + tiptapEditor.commands.setContent(CODE_BLOCK_HTML); + }); + it('extracts language and params attributes from Markdown API output', () => { + const language = preElement().getAttribute('lang'); + + expect(tiptapEditor.getJSON().content[0].attrs).toMatchObject({ + language, + }); + }); + + it('adds code, highlight, and js-syntax-highlight to code block element', () => { + const editorHtmlOutput = parseHTML(tiptapEditor.getHTML()).querySelector('pre'); + + expect(editorHtmlOutput.classList.toString()).toContain('code highlight js-syntax-highlight'); }); - }); - it('adds code, highlight, and js-syntax-highlight to code block element', () => { - const editorHtmlOutput = parseHTML(tiptapEditor.getHTML()).querySelector('pre'); + it('adds content-editor-code-block class to the pre element', () => { + const editorHtmlOutput = parseHTML(tiptapEditor.getHTML()).querySelector('pre'); - expect(editorHtmlOutput.classList.toString()).toContain('code highlight js-syntax-highlight'); + expect(editorHtmlOutput.classList.toString()).toContain('content-editor-code-block'); + }); }); - it('adds content-editor-code-block class to the pre element', () => { - const editorHtmlOutput = parseHTML(tiptapEditor.getHTML()).querySelector('pre'); + describe.each` + inputRule + ${'```'} + ${'~~~'} + `('when typing $inputRule input rule', ({ inputRule }) => { + const language = 'javascript'; + + beforeEach(() => { + triggerNodeInputRule({ + tiptapEditor, + inputRuleText: `${inputRule}${language} `, + }); + }); + + it('creates a new code block and loads related language', () => { + const expectedDoc = doc(codeBlock({ language })); - expect(editorHtmlOutput.classList.toString()).toContain('content-editor-code-block'); + expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON()); + }); + + it('loads language when language loader is available', () => { + expect(languageLoader.loadLanguages).toHaveBeenCalledWith([language]); + }); }); }); diff --git a/spec/frontend/content_editor/services/code_block_language_loader_spec.js b/spec/frontend/content_editor/services/code_block_language_loader_spec.js new file mode 100644 index 00000000000..bb97c9afa41 --- /dev/null +++ b/spec/frontend/content_editor/services/code_block_language_loader_spec.js @@ -0,0 +1,70 @@ +import CodeBlockLanguageBlocker from '~/content_editor/services/code_block_language_loader'; + +describe('content_editor/services/code_block_language_loader', () => { + let languageLoader; + let lowlight; + + beforeEach(() => { + lowlight = { + languages: [], + registerLanguage: jest + .fn() + .mockImplementation((language) => lowlight.languages.push(language)), + registered: jest.fn().mockImplementation((language) => lowlight.languages.includes(language)), + }; + languageLoader = new CodeBlockLanguageBlocker(lowlight); + }); + + describe('loadLanguages', () => { + it('loads highlight.js language packages identified by a list of languages', async () => { + const languages = ['javascript', 'ruby']; + + await languageLoader.loadLanguages(languages); + + languages.forEach((language) => { + expect(lowlight.registerLanguage).toHaveBeenCalledWith(language, expect.any(Function)); + }); + }); + + describe('when language is already registered', () => { + it('does not load the language again', async () => { + const languages = ['javascript']; + + await languageLoader.loadLanguages(languages); + await languageLoader.loadLanguages(languages); + + expect(lowlight.registerLanguage).toHaveBeenCalledTimes(1); + }); + }); + }); + + describe('loadLanguagesFromDOM', () => { + it('loads highlight.js language packages identified by pre tags in a DOM fragment', async () => { + const parser = new DOMParser(); + const { body } = parser.parseFromString( + ` + <pre lang="javascript"></pre> + <pre lang="ruby"></pre> + `, + 'text/html', + ); + + await languageLoader.loadLanguagesFromDOM(body); + + expect(lowlight.registerLanguage).toHaveBeenCalledWith('javascript', expect.any(Function)); + expect(lowlight.registerLanguage).toHaveBeenCalledWith('ruby', expect.any(Function)); + }); + }); + + describe('isLanguageLoaded', () => { + it('returns true when a language is registered', async () => { + const language = 'javascript'; + + expect(languageLoader.isLanguageLoaded(language)).toBe(false); + + await languageLoader.loadLanguages([language]); + + expect(languageLoader.isLanguageLoaded(language)).toBe(true); + }); + }); +}); diff --git a/spec/frontend/content_editor/services/content_editor_spec.js b/spec/frontend/content_editor/services/content_editor_spec.js index 3bc72b13302..5b7a27b501d 100644 --- a/spec/frontend/content_editor/services/content_editor_spec.js +++ b/spec/frontend/content_editor/services/content_editor_spec.js @@ -11,6 +11,7 @@ describe('content_editor/services/content_editor', () => { let contentEditor; let serializer; let deserializer; + let languageLoader; let eventHub; let doc; let p; @@ -27,8 +28,15 @@ describe('content_editor/services/content_editor', () => { serializer = { deserialize: jest.fn() }; deserializer = { deserialize: jest.fn() }; + languageLoader = { loadLanguagesFromDOM: jest.fn() }; eventHub = eventHubFactory(); - contentEditor = new ContentEditor({ tiptapEditor, serializer, deserializer, eventHub }); + contentEditor = new ContentEditor({ + tiptapEditor, + serializer, + deserializer, + eventHub, + languageLoader, + }); }); describe('.dispose', () => { @@ -43,10 +51,12 @@ describe('content_editor/services/content_editor', () => { describe('when setSerializedContent succeeds', () => { let document; + const dom = {}; + const testMarkdown = '**bold text**'; beforeEach(() => { document = doc(p('document')); - deserializer.deserialize.mockResolvedValueOnce({ document }); + deserializer.deserialize.mockResolvedValueOnce({ document, dom }); }); it('emits loadingContent and loadingSuccess event in the eventHub', () => { @@ -59,14 +69,20 @@ describe('content_editor/services/content_editor', () => { expect(loadingContentEmitted).toBe(true); }); - contentEditor.setSerializedContent('**bold text**'); + contentEditor.setSerializedContent(testMarkdown); }); it('sets the deserialized document in the tiptap editor object', async () => { - await contentEditor.setSerializedContent('**bold text**'); + await contentEditor.setSerializedContent(testMarkdown); expect(contentEditor.tiptapEditor.state.doc.toJSON()).toEqual(document.toJSON()); }); + + it('passes deserialized DOM document to language loader', async () => { + await contentEditor.setSerializedContent(testMarkdown); + + expect(languageLoader.loadLanguagesFromDOM).toHaveBeenCalledWith(dom); + }); }); describe('when setSerializedContent fails', () => { diff --git a/spec/frontend/fixtures/merge_requests.rb b/spec/frontend/fixtures/merge_requests.rb index 1eae854eca3..cb4eb43b88d 100644 --- a/spec/frontend/fixtures/merge_requests.rb +++ b/spec/frontend/fixtures/merge_requests.rb @@ -140,7 +140,7 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: query_name = 'ready_to_merge.query.graphql' it "#{base_output_path}#{query_name}.json" do - query = get_graphql_query_as_string("#{base_input_path}#{query_name}", ee: true) + query = get_graphql_query_as_string("#{base_input_path}#{query_name}", ee: Gitlab.ee?) post_graphql(query, current_user: user, variables: { projectPath: project.full_path, iid: merge_request.iid.to_s }) diff --git a/spec/frontend/security_configuration/components/training_provider_list_spec.js b/spec/frontend/security_configuration/components/training_provider_list_spec.js index db56f77b60e..b8c1bef0ddd 100644 --- a/spec/frontend/security_configuration/components/training_provider_list_spec.js +++ b/spec/frontend/security_configuration/components/training_provider_list_spec.js @@ -12,6 +12,7 @@ import { TRACK_PROVIDER_LEARN_MORE_CLICK_ACTION, TRACK_PROVIDER_LEARN_MORE_CLICK_LABEL, } from '~/security_configuration/constants'; +import { TEMP_PROVIDER_URLS } from '~/security_configuration/components/constants'; import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue'; import { updateSecurityTrainingOptimisticResponse } from '~/security_configuration/graphql/cache_utils'; import securityTrainingProvidersQuery from '~/security_configuration/graphql/security_training_providers.query.graphql'; @@ -145,55 +146,60 @@ describe('TrainingProviderList component', () => { expect(findCards()).toHaveLength(TEST_TRAINING_PROVIDERS_DEFAULT.data.length); }); - TEST_TRAINING_PROVIDERS_DEFAULT.data.forEach( - ({ name, description, url, isEnabled }, index) => { - it(`shows the name for card ${index}`, () => { - expect(findCards().at(index).text()).toContain(name); - }); + TEST_TRAINING_PROVIDERS_DEFAULT.data.forEach(({ name, description, isEnabled }, index) => { + it(`shows the name for card ${index}`, () => { + expect(findCards().at(index).text()).toContain(name); + }); - it(`shows the description for card ${index}`, () => { - expect(findCards().at(index).text()).toContain(description); - }); + it(`shows the description for card ${index}`, () => { + expect(findCards().at(index).text()).toContain(description); + }); + + it(`shows the learn more link for enabled card ${index}`, () => { + const learnMoreLink = findCards().at(index).find(GlLink); + const tempLogo = TEMP_PROVIDER_URLS[name]; - it(`shows the learn more link for card ${index}`, () => { - expect(findLinks().at(index).attributes()).toEqual({ + if (tempLogo) { + expect(learnMoreLink.attributes()).toEqual({ target: '_blank', - href: url, + href: TEMP_PROVIDER_URLS[name], }); - }); + } else { + expect(learnMoreLink.exists()).toBe(false); + } + }); - it(`shows the toggle with the correct value for card ${index}`, () => { - expect(findToggles().at(index).props('value')).toEqual(isEnabled); - }); + it(`shows the toggle with the correct value for card ${index}`, () => { + expect(findToggles().at(index).props('value')).toEqual(isEnabled); + }); - it(`shows a radio button to select the provider as primary within card ${index}`, () => { - const primaryProviderRadioForCurrentCard = findPrimaryProviderRadios().at(index); + it(`shows a radio button to select the provider as primary within card ${index}`, () => { + const primaryProviderRadioForCurrentCard = findPrimaryProviderRadios().at(index); - // if the given provider is not enabled it should not be possible select it as primary - expect(primaryProviderRadioForCurrentCard.find('input').attributes('disabled')).toBe( - isEnabled ? undefined : 'disabled', - ); + // if the given provider is not enabled it should not be possible select it as primary + expect(primaryProviderRadioForCurrentCard.find('input').attributes('disabled')).toBe( + isEnabled ? undefined : 'disabled', + ); - expect(primaryProviderRadioForCurrentCard.text()).toBe( - TrainingProviderList.i18n.primaryTraining, - ); - }); + expect(primaryProviderRadioForCurrentCard.text()).toBe( + TrainingProviderList.i18n.primaryTraining, + ); + }); - it('shows a info-tooltip that describes the purpose of a primary provider', () => { - const infoIcon = findPrimaryProviderRadios().at(index).find(GlIcon); - const tooltip = getBinding(infoIcon.element, 'gl-tooltip'); + it('shows a info-tooltip that describes the purpose of a primary provider', () => { + const infoIcon = findPrimaryProviderRadios().at(index).find(GlIcon); + const tooltip = getBinding(infoIcon.element, 'gl-tooltip'); - expect(infoIcon.props()).toMatchObject({ - name: 'information-o', - }); - expect(tooltip.value).toBe(TrainingProviderList.i18n.primaryTrainingDescription); + expect(infoIcon.props()).toMatchObject({ + name: 'information-o', }); + expect(tooltip.value).toBe(TrainingProviderList.i18n.primaryTrainingDescription); + }); - it('does not show loader when query is populated', () => { - expect(findLoader().exists()).toBe(false); - }); - }, - ); + it('does not show loader when query is populated', () => { + expect(findLoader().exists()).toBe(false); + }); + }); }); describe('provider logo', () => { diff --git a/spec/frontend/security_configuration/mock_data.js b/spec/frontend/security_configuration/mock_data.js index 55f5c20e45d..18a480bf082 100644 --- a/spec/frontend/security_configuration/mock_data.js +++ b/spec/frontend/security_configuration/mock_data.js @@ -1,6 +1,6 @@ export const testProjectPath = 'foo/bar'; export const testProviderIds = [101, 102, 103]; -export const testProviderName = ['Vendor Name 1', 'Vendor Name 2', 'Vendor Name 3']; +export const testProviderName = ['Kontra', 'Secure Code Warrior', 'Other Vendor']; export const testTrainingUrls = [ 'https://www.vendornameone.com/url', 'https://www.vendornametwo.com/url', diff --git a/spec/graphql/resolvers/board_list_issues_resolver_spec.rb b/spec/graphql/resolvers/board_list_issues_resolver_spec.rb index a6b536e1158..392385d2a30 100644 --- a/spec/graphql/resolvers/board_list_issues_resolver_spec.rb +++ b/spec/graphql/resolvers/board_list_issues_resolver_spec.rb @@ -25,10 +25,10 @@ RSpec.describe Resolvers::BoardListIssuesResolver do let(:wildcard_started) { 'STARTED' } let(:filters) { { milestone_title: ["started"], milestone_wildcard_id: wildcard_started } } - it 'raises a mutually exclusive filter error when milestone wildcard and title are provided' do - expect do + it 'generates a mutually exclusive filter error when milestone wildcard and title are provided' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do resolve_board_list_issues(args: { filters: filters }) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end end it 'returns the issues in the correct order' do diff --git a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb index b0fc78af2af..8b9874c3580 100644 --- a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb @@ -26,8 +26,10 @@ RSpec.describe Resolvers::DesignManagement::VersionInCollectionResolver do subject(:result) { resolve_version(issue.design_collection) } context 'Neither id nor sha is passed as parameters' do - it 'raises an appropriate error' do - expect { result }.to raise_error(appropriate_error) + it 'generates an appropriate error' do + expect_graphql_error_to_be_created(appropriate_error) do + result + end end end diff --git a/spec/graphql/resolvers/design_management/version_resolver_spec.rb b/spec/graphql/resolvers/design_management/version_resolver_spec.rb index af1e6a73d09..ab1d7d4d9c5 100644 --- a/spec/graphql/resolvers/design_management/version_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/version_resolver_spec.rb @@ -22,8 +22,10 @@ RSpec.describe Resolvers::DesignManagement::VersionResolver do context 'the current user is not authorized' do let(:current_user) { create(:user) } - it 'raises an error on resolution' do - expect { resolve_version }.to raise_error(::Gitlab::Graphql::Errors::ResourceNotAvailable) + it 'generates an error on resolution' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do + resolve_version + end end end diff --git a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb index 2c9c3a47650..d98138f6385 100644 --- a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb @@ -98,8 +98,10 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do } end - it 'raises a suitable error' do - expect { result }.to raise_error(GraphQL::ExecutionError) + it 'generates a suitable error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do + result + end end end end diff --git a/spec/graphql/resolvers/group_issues_resolver_spec.rb b/spec/graphql/resolvers/group_issues_resolver_spec.rb index e17429560ac..f5f6086cc09 100644 --- a/spec/graphql/resolvers/group_issues_resolver_spec.rb +++ b/spec/graphql/resolvers/group_issues_resolver_spec.rb @@ -86,10 +86,10 @@ RSpec.describe Resolvers::GroupIssuesResolver do end context 'release_tag filter' do - it 'returns an error when trying to filter by negated release_tag' do - expect do + it 'generates an error when trying to filter by negated release_tag' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'releaseTag filter is not allowed when parent is a group.') do resolve_issues(not: { release_tag: ['v1.0'] }) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'releaseTag filter is not allowed when parent is a group.') + end end end end diff --git a/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb b/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb index 3fbd9bd2368..77f4ce4cac5 100644 --- a/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb +++ b/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb @@ -70,10 +70,10 @@ RSpec.describe Resolvers::IssueStatusCountsResolver do end context 'when both assignee_username and assignee_usernames are provided' do - it 'raises a mutually exclusive filter error' do - expect do + it 'generates a mutually exclusive filter error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'only one of [assigneeUsernames, assigneeUsername] arguments is allowed at the same time.') do resolve_issue_status_counts(assignee_usernames: [current_user.username], assignee_username: current_user.username) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'only one of [assigneeUsernames, assigneeUsername] arguments is allowed at the same time.') + end end end diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 326c105a358..5e9a3d0a68b 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -78,10 +78,10 @@ RSpec.describe Resolvers::IssuesResolver do expect(resolve_issues(milestone_wildcard_id: wildcard_none)).to contain_exactly(issue2) end - it 'raises a mutually exclusive filter error when wildcard and title are provided' do - expect do + it 'generates a mutually exclusive filter error when wildcard and title are provided' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'only one of [milestoneTitle, milestoneWildcardId] arguments is allowed at the same time.') do resolve_issues(milestone_title: ["started milestone"], milestone_wildcard_id: wildcard_started) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'only one of [milestoneTitle, milestoneWildcardId] arguments is allowed at the same time.') + end end context 'negated filtering' do @@ -97,10 +97,10 @@ RSpec.describe Resolvers::IssuesResolver do expect(resolve_issues(not: { milestone_wildcard_id: wildcard_upcoming })).to contain_exactly(issue6) end - it 'raises a mutually exclusive filter error when wildcard and title are provided as negated filters' do - expect do + it 'generates a mutually exclusive filter error when wildcard and title are provided as negated filters' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'only one of [milestoneTitle, milestoneWildcardId] arguments is allowed at the same time.') do resolve_issues(not: { milestone_title: ["started milestone"], milestone_wildcard_id: wildcard_started }) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'only one of [milestoneTitle, milestoneWildcardId] arguments is allowed at the same time.') + end end end end @@ -122,10 +122,10 @@ RSpec.describe Resolvers::IssuesResolver do end context 'when release_tag_wildcard_id is also provided' do - it 'raises a mutually eclusive argument error' do - expect do + it 'generates a mutually eclusive argument error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'only one of [releaseTag, releaseTagWildcardId] arguments is allowed at the same time.') do resolve_issues(release_tag: [release1.tag], release_tag_wildcard_id: 'ANY') - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'only one of [releaseTag, releaseTagWildcardId] arguments is allowed at the same time.') + end end end end @@ -191,10 +191,10 @@ RSpec.describe Resolvers::IssuesResolver do end context 'when both assignee_username and assignee_usernames are provided' do - it 'raises a mutually exclusive filter error' do - expect do + it 'generates a mutually exclusive filter error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'only one of [assigneeUsernames, assigneeUsername] arguments is allowed at the same time.') do resolve_issues(assignee_usernames: [assignee.username], assignee_username: assignee.username) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'only one of [assigneeUsernames, assigneeUsername] arguments is allowed at the same time.') + end end end end @@ -331,11 +331,12 @@ RSpec.describe Resolvers::IssuesResolver do stub_feature_flags(disable_anonymous_search: true) end - it 'returns an error' do + it 'generates an error' do error_message = "User must be authenticated to include the `search` argument." - expect { resolve(described_class, obj: public_project, args: { search: 'test' }, ctx: { current_user: nil }) } - .to raise_error(Gitlab::Graphql::Errors::ArgumentError, error_message) + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, error_message) do + resolve(described_class, obj: public_project, args: { search: 'test' }, ctx: { current_user: nil }) + end end end diff --git a/spec/graphql/resolvers/kas/agent_configurations_resolver_spec.rb b/spec/graphql/resolvers/kas/agent_configurations_resolver_spec.rb index bdb1ced46ae..e4cf62b0361 100644 --- a/spec/graphql/resolvers/kas/agent_configurations_resolver_spec.rb +++ b/spec/graphql/resolvers/kas/agent_configurations_resolver_spec.rb @@ -34,8 +34,10 @@ RSpec.describe Resolvers::Kas::AgentConfigurationsResolver do allow(kas_client).to receive(:list_agent_config_files).and_raise(GRPC::DeadlineExceeded) end - it 'raises a graphql error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable, 'GRPC::DeadlineExceeded') + it 'generates a graphql error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable, 'GRPC::DeadlineExceeded') do + subject + end end end diff --git a/spec/graphql/resolvers/labels_resolver_spec.rb b/spec/graphql/resolvers/labels_resolver_spec.rb index be6229553d7..efd2596b9eb 100644 --- a/spec/graphql/resolvers/labels_resolver_spec.rb +++ b/spec/graphql/resolvers/labels_resolver_spec.rb @@ -28,7 +28,9 @@ RSpec.describe Resolvers::LabelsResolver do describe '#resolve' do context 'with unauthorized user' do it 'returns no labels' do - expect { resolve_labels(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do + resolve_labels(project) + end end end diff --git a/spec/graphql/resolvers/package_pipelines_resolver_spec.rb b/spec/graphql/resolvers/package_pipelines_resolver_spec.rb index 892dc641201..c757c876616 100644 --- a/spec/graphql/resolvers/package_pipelines_resolver_spec.rb +++ b/spec/graphql/resolvers/package_pipelines_resolver_spec.rb @@ -25,32 +25,40 @@ RSpec.describe Resolvers::PackagePipelinesResolver do context 'with invalid after' do let(:args) { { first: 1, after: 'not_json_string' } } - it 'raises argument error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + it 'generates an argument error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do + subject + end end end context 'with invalid after key' do let(:args) { { first: 1, after: encode_cursor(foo: 3) } } - it 'raises argument error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + it 'generates an argument error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do + subject + end end end context 'with invalid before' do let(:args) { { last: 1, before: 'not_json_string' } } - it 'raises argument error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + it 'generates an argument error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do + subject + end end end context 'with invalid before key' do let(:args) { { last: 1, before: encode_cursor(foo: 3) } } - it 'raises argument error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + it 'generates an argument error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do + subject + end end end diff --git a/spec/graphql/resolvers/paginated_tree_resolver_spec.rb b/spec/graphql/resolvers/paginated_tree_resolver_spec.rb index 82b05937aa3..4b05e9076d7 100644 --- a/spec/graphql/resolvers/paginated_tree_resolver_spec.rb +++ b/spec/graphql/resolvers/paginated_tree_resolver_spec.rb @@ -65,7 +65,11 @@ RSpec.describe Resolvers::PaginatedTreeResolver do context 'when cursor is invalid' do let(:args) { super().merge(after: 'invalid') } - it { expect { subject }.to raise_error(Gitlab::Graphql::Errors::ArgumentError) } + it 'generates an error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do + subject + end + end end it 'returns all tree entries during cursor pagination' do diff --git a/spec/graphql/resolvers/project_milestones_resolver_spec.rb b/spec/graphql/resolvers/project_milestones_resolver_spec.rb index e168291c804..2cf490c2b6a 100644 --- a/spec/graphql/resolvers/project_milestones_resolver_spec.rb +++ b/spec/graphql/resolvers/project_milestones_resolver_spec.rb @@ -103,27 +103,27 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do end context 'when start date is after end_date' do - it 'raises error' do - expect do + it 'generates an error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'startDate is after endDate') do resolve_project_milestones(start_date: Time.now, end_date: Time.now - 2.days) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "startDate is after endDate") + end end end end context 'when only start_date is present' do - it 'raises error' do - expect do + it 'generates an error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, /Both startDate and endDate/) do resolve_project_milestones(start_date: Time.now) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /Both startDate and endDate/) + end end end context 'when only end_date is present' do - it 'raises error' do - expect do + it 'generates an error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, /Both startDate and endDate/) do resolve_project_milestones(end_date: Time.now) - end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /Both startDate and endDate/) + end end end @@ -174,12 +174,12 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do end context 'when user cannot read milestones' do - it 'raises error' do + it 'generates an error' do unauthorized_user = create(:user) - expect do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do resolve_project_milestones({}, { current_user: unauthorized_user }) - end.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end end end end diff --git a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb index 6a8aa39f3b2..398f8f52269 100644 --- a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb +++ b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb @@ -85,13 +85,15 @@ RSpec.describe Resolvers::ProjectPipelineResolver do end it 'errors when no iid or sha is passed' do - expect { resolve_pipeline(project, {}) } - .to raise_error(Gitlab::Graphql::Errors::ArgumentError) + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do + resolve_pipeline(project, {}) + end end it 'errors when both iid and sha are passed' do - expect { resolve_pipeline(project, { iid: '1234', sha: 'sha' }) } - .to raise_error(Gitlab::Graphql::Errors::ArgumentError) + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do + resolve_pipeline(project, { iid: '1234', sha: 'sha' }) + end end context 'when the pipeline is a dangling pipeline' do diff --git a/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb index c6d8c518fb7..b95bab41e3e 100644 --- a/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb +++ b/spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb @@ -14,10 +14,10 @@ RSpec.describe Resolvers::Projects::JiraProjectsResolver do let_it_be(:project) { create(:project) } shared_examples 'no project service access' do - it 'raises error' do - expect do + it 'generates an error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do resolve_jira_projects - end.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end end end @@ -89,11 +89,14 @@ RSpec.describe Resolvers::Projects::JiraProjectsResolver do .to_raise(JIRA::HTTPError.new(double(message: '{"errorMessages":["Some failure"]}'))) end - it 'raises failure error' do + it 'generates a failure error' do config_docs_link_url = Rails.application.routes.url_helpers.help_page_path('integration/jira/configure') docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: config_docs_link_url } error_message = 'An error occurred while requesting data from Jira: Some failure. Check your %{docs_link_start}Jira integration configuration</a> and try again.' % { docs_link_start: docs_link_start } - expect { resolve_jira_projects }.to raise_error(error_message) + + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::BaseError, error_message) do + resolve_jira_projects + end end end end diff --git a/spec/graphql/resolvers/timelog_resolver_spec.rb b/spec/graphql/resolvers/timelog_resolver_spec.rb index 82ed572c3ee..84fa2932829 100644 --- a/spec/graphql/resolvers/timelog_resolver_spec.rb +++ b/spec/graphql/resolvers/timelog_resolver_spec.rb @@ -85,27 +85,30 @@ RSpec.describe Resolvers::TimelogResolver do context 'when start_time and start_date are present' do let(:args) { { start_time: 6.days.ago, start_date: 6.days.ago } } - it 'returns correct error' do - expect { timelogs } - .to raise_error(error_class, /Provide either a start date or time, but not both/) + it 'generates an error' do + expect_graphql_error_to_be_created(error_class, /Provide either a start date or time, but not both/) do + timelogs + end end end context 'when end_time and end_date are present' do let(:args) { { end_time: 2.days.ago, end_date: 2.days.ago } } - it 'returns correct error' do - expect { timelogs } - .to raise_error(error_class, /Provide either an end date or time, but not both/) + it 'generates an error' do + expect_graphql_error_to_be_created(error_class, /Provide either an end date or time, but not both/) do + timelogs + end end end context 'when start argument is after end argument' do let(:args) { { start_time: 2.days.ago, end_time: 6.days.ago } } - it 'returns correct error' do - expect { timelogs } - .to raise_error(error_class, /Start argument must be before End argument/) + it 'generates an error' do + expect_graphql_error_to_be_created(error_class, /Start argument must be before End argument/) do + timelogs + end end end end @@ -276,9 +279,10 @@ RSpec.describe Resolvers::TimelogResolver do let(:args) { {} } let(:extra_args) { {} } - it 'returns correct error' do - expect { timelogs } - .to raise_error(error_class, /Provide at least one argument/) + it 'generates an error' do + expect_graphql_error_to_be_created(error_class, /Provide at least one argument/) do + timelogs + end end end diff --git a/spec/lib/gitlab/background_migration/reset_duplicate_ci_runners_token_encrypted_values_on_projects_spec.rb b/spec/lib/gitlab/background_migration/reset_duplicate_ci_runners_token_encrypted_values_on_projects_spec.rb new file mode 100644 index 00000000000..6aea549b136 --- /dev/null +++ b/spec/lib/gitlab/background_migration/reset_duplicate_ci_runners_token_encrypted_values_on_projects_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::ResetDuplicateCiRunnersTokenEncryptedValuesOnProjects do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + + let(:perform) { described_class.new.perform(1, 4) } + + before do + namespaces.create!(id: 123, name: 'sample', path: 'sample') + + projects.create!(id: 1, namespace_id: 123, runners_token_encrypted: 'duplicate') + projects.create!(id: 2, namespace_id: 123, runners_token_encrypted: 'a-runners-token') + projects.create!(id: 3, namespace_id: 123, runners_token_encrypted: 'duplicate') + projects.create!(id: 4, namespace_id: 123, runners_token_encrypted: nil) + projects.create!(id: 5, namespace_id: 123, runners_token_encrypted: 'duplicate-2') + projects.create!(id: 6, namespace_id: 123, runners_token_encrypted: 'duplicate-2') + end + + describe '#up' do + before do + stub_const("#{described_class}::SUB_BATCH_SIZE", 2) + end + + it 'nullifies duplicate tokens', :aggregate_failures do + perform + + expect(projects.count).to eq(6) + expect(projects.all.pluck(:id, :runners_token_encrypted).to_h).to eq( + { 1 => nil, 2 => 'a-runners-token', 3 => nil, 4 => nil, 5 => 'duplicate-2', 6 => 'duplicate-2' } + ) + expect(projects.pluck(:runners_token_encrypted).uniq).to match_array [nil, 'a-runners-token', 'duplicate-2'] + end + end +end diff --git a/spec/lib/gitlab/background_migration/reset_duplicate_ci_runners_token_values_on_projects_spec.rb b/spec/lib/gitlab/background_migration/reset_duplicate_ci_runners_token_values_on_projects_spec.rb new file mode 100644 index 00000000000..cbe762c2680 --- /dev/null +++ b/spec/lib/gitlab/background_migration/reset_duplicate_ci_runners_token_values_on_projects_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::ResetDuplicateCiRunnersTokenValuesOnProjects do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + + let(:perform) { described_class.new.perform(1, 4) } + + before do + namespaces.create!(id: 123, name: 'sample', path: 'sample') + + projects.create!(id: 1, namespace_id: 123, runners_token: 'duplicate') + projects.create!(id: 2, namespace_id: 123, runners_token: 'a-runners-token') + projects.create!(id: 3, namespace_id: 123, runners_token: 'duplicate') + projects.create!(id: 4, namespace_id: 123, runners_token: nil) + projects.create!(id: 5, namespace_id: 123, runners_token: 'duplicate-2') + projects.create!(id: 6, namespace_id: 123, runners_token: 'duplicate-2') + end + + describe '#up' do + before do + stub_const("#{described_class}::SUB_BATCH_SIZE", 2) + end + + it 'nullifies duplicate tokens', :aggregate_failures do + perform + + expect(projects.count).to eq(6) + expect(projects.all.pluck(:id, :runners_token).to_h).to eq( + { 1 => nil, 2 => 'a-runners-token', 3 => nil, 4 => nil, 5 => 'duplicate-2', 6 => 'duplicate-2' } + ) + expect(projects.pluck(:runners_token).uniq).to match_array [nil, 'a-runners-token', 'duplicate-2'] + end + end +end diff --git a/spec/models/users/credit_card_validation_spec.rb b/spec/models/users/credit_card_validation_spec.rb index 43edf7ed093..34cfd500c26 100644 --- a/spec/models/users/credit_card_validation_spec.rb +++ b/spec/models/users/credit_card_validation_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Users::CreditCardValidation do it { is_expected.to belong_to(:user) } - it { is_expected.to validate_length_of(:holder_name).is_at_most(26) } + it { is_expected.to validate_length_of(:holder_name).is_at_most(50) } it { is_expected.to validate_length_of(:network).is_at_most(32) } it { is_expected.to validate_numericality_of(:last_digits).is_less_than_or_equal_to(9999) } diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index f42fc7aabc2..1d199a72d1d 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -783,6 +783,13 @@ RSpec.describe API::Repositories do expect(response).to have_gitlab_http_status(:ok) expect(json_response['notes']).to be_present end + + context 'when previous tag version does not exist' do + it_behaves_like '422 response' do + let(:request) { get api("/projects/#{project.id}/repository/changelog", user), params: { version: 'v0.0.0' } } + let(:message) { 'Failed to generate the changelog: The commit start range is unspecified, and no previous tag could be found to use instead' } + end + end end describe 'POST /projects/:id/repository/changelog' do diff --git a/spec/support/shared_examples/graphql/members_shared_examples.rb b/spec/support/shared_examples/graphql/members_shared_examples.rb index b0bdd27a95f..8e9e22f4359 100644 --- a/spec/support/shared_examples/graphql/members_shared_examples.rb +++ b/spec/support/shared_examples/graphql/members_shared_examples.rb @@ -76,8 +76,10 @@ RSpec.shared_examples 'querying members with a group' do resolve(described_class, obj: resource, args: base_args.merge(args), ctx: { current_user: other_user }) end - it 'raises an error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + it 'generates an error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do + subject + end end end end |