diff options
Diffstat (limited to 'spec')
11 files changed, 248 insertions, 80 deletions
diff --git a/spec/deprecation_toolkit_env.rb b/spec/deprecation_toolkit_env.rb index 57d32b5423c..00d66ff3cdc 100644 --- a/spec/deprecation_toolkit_env.rb +++ b/spec/deprecation_toolkit_env.rb @@ -55,9 +55,9 @@ module DeprecationToolkitEnv # one by one def self.allowed_kwarg_warning_paths %w[ - activerecord-6.0.3.6/lib/active_record/migration.rb - activesupport-6.0.3.6/lib/active_support/cache.rb - activerecord-6.0.3.6/lib/active_record/relation.rb + activerecord-6.0.3.7/lib/active_record/migration.rb + activesupport-6.0.3.7/lib/active_support/cache.rb + activerecord-6.0.3.7/lib/active_record/relation.rb asciidoctor-2.0.12/lib/asciidoctor/extensions.rb attr_encrypted-3.1.0/lib/attr_encrypted/adapters/active_record.rb ] diff --git a/spec/frontend/issues_list/components/issues_list_app_spec.js b/spec/frontend/issues_list/components/issues_list_app_spec.js index 5d83bf0142f..8a91dca51c2 100644 --- a/spec/frontend/issues_list/components/issues_list_app_spec.js +++ b/spec/frontend/issues_list/components/issues_list_app_spec.js @@ -18,6 +18,15 @@ import { PAGE_SIZE_MANUAL, PARAM_DUE_DATE, RELATIVE_POSITION_DESC, + TOKEN_TYPE_ASSIGNEE, + TOKEN_TYPE_AUTHOR, + TOKEN_TYPE_CONFIDENTIAL, + TOKEN_TYPE_EPIC, + TOKEN_TYPE_ITERATION, + TOKEN_TYPE_LABEL, + TOKEN_TYPE_MILESTONE, + TOKEN_TYPE_MY_REACTION, + TOKEN_TYPE_WEIGHT, urlSortParams, } from '~/issues_list/constants'; import eventHub from '~/issues_list/eventhub'; @@ -39,8 +48,8 @@ describe('IssuesListApp component', () => { endpoint: 'api/endpoint', exportCsvPath: 'export/csv/path', hasBlockedIssuesFeature: true, - hasIssues: true, hasIssueWeightsFeature: true, + hasProjectIssues: true, isSignedIn: false, issuesPath: 'path/to/issues', jiraIntegrationPath: 'jira/integration/path', @@ -320,7 +329,7 @@ describe('IssuesListApp component', () => { beforeEach(async () => { global.jsdom.reconfigure({ url: `${TEST_HOST}?search=no+results` }); - wrapper = mountComponent({ provide: { hasIssues: true }, mountFn: mount }); + wrapper = mountComponent({ provide: { hasProjectIssues: true }, mountFn: mount }); await waitForPromises(); }); @@ -336,7 +345,7 @@ describe('IssuesListApp component', () => { describe('when "Open" tab has no issues', () => { beforeEach(async () => { - wrapper = mountComponent({ provide: { hasIssues: true }, mountFn: mount }); + wrapper = mountComponent({ provide: { hasProjectIssues: true }, mountFn: mount }); await waitForPromises(); }); @@ -356,7 +365,7 @@ describe('IssuesListApp component', () => { url: setUrlParams({ state: IssuableStates.Closed }, TEST_HOST), }); - wrapper = mountComponent({ provide: { hasIssues: true }, mountFn: mount }); + wrapper = mountComponent({ provide: { hasProjectIssues: true }, mountFn: mount }); await waitForPromises(); }); @@ -374,7 +383,7 @@ describe('IssuesListApp component', () => { describe('when user is logged in', () => { beforeEach(() => { wrapper = mountComponent({ - provide: { hasIssues: false, isSignedIn: true }, + provide: { hasProjectIssues: false, isSignedIn: true }, mountFn: mount, }); }); @@ -413,7 +422,7 @@ describe('IssuesListApp component', () => { describe('when user is logged out', () => { beforeEach(() => { wrapper = mountComponent({ - provide: { hasIssues: false, isSignedIn: false }, + provide: { hasProjectIssues: false, isSignedIn: false }, }); }); @@ -430,6 +439,100 @@ describe('IssuesListApp component', () => { }); }); + describe('tokens', () => { + describe('when user is signed out', () => { + beforeEach(() => { + wrapper = mountComponent({ + provide: { + isSignedIn: false, + }, + }); + }); + + it('does not render My-Reaction or Confidential tokens', () => { + expect(findIssuableList().props('searchTokens')).not.toMatchObject([ + { type: TOKEN_TYPE_MY_REACTION }, + { type: TOKEN_TYPE_CONFIDENTIAL }, + ]); + }); + }); + + describe('when iterations are not available', () => { + beforeEach(() => { + wrapper = mountComponent({ + provide: { + projectIterationsPath: '', + }, + }); + }); + + it('does not render Iteration token', () => { + expect(findIssuableList().props('searchTokens')).not.toMatchObject([ + { type: TOKEN_TYPE_ITERATION }, + ]); + }); + }); + + describe('when epics are not available', () => { + beforeEach(() => { + wrapper = mountComponent({ + provide: { + groupEpicsPath: '', + }, + }); + }); + + it('does not render Epic token', () => { + expect(findIssuableList().props('searchTokens')).not.toMatchObject([ + { type: TOKEN_TYPE_EPIC }, + ]); + }); + }); + + describe('when weights are not available', () => { + beforeEach(() => { + wrapper = mountComponent({ + provide: { + groupEpicsPath: '', + }, + }); + }); + + it('does not render Weight token', () => { + expect(findIssuableList().props('searchTokens')).not.toMatchObject([ + { type: TOKEN_TYPE_WEIGHT }, + ]); + }); + }); + + describe('when all tokens are available', () => { + beforeEach(() => { + wrapper = mountComponent({ + provide: { + isSignedIn: true, + projectIterationsPath: 'project/iterations/path', + groupEpicsPath: 'group/epics/path', + hasIssueWeightsFeature: true, + }, + }); + }); + + it('renders all tokens', () => { + expect(findIssuableList().props('searchTokens')).toMatchObject([ + { type: TOKEN_TYPE_AUTHOR }, + { type: TOKEN_TYPE_ASSIGNEE }, + { type: TOKEN_TYPE_MILESTONE }, + { type: TOKEN_TYPE_LABEL }, + { type: TOKEN_TYPE_MY_REACTION }, + { type: TOKEN_TYPE_CONFIDENTIAL }, + { type: TOKEN_TYPE_ITERATION }, + { type: TOKEN_TYPE_EPIC }, + { type: TOKEN_TYPE_WEIGHT }, + ]); + }); + }); + }); + describe('events', () => { describe('when "click-tab" event is emitted by IssuableList', () => { beforeEach(() => { diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js index 3b50927dcc6..e2f8dbd8ceb 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js @@ -3,6 +3,7 @@ import { GlFilteredSearchTokenSegment, GlFilteredSearchSuggestion, GlDropdownDivider, + GlLoadingIcon, } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; @@ -35,6 +36,7 @@ function createComponent(options = {}) { value = { data: '' }, active = false, stubs = defaultStubs, + data = {}, } = options; return mount(AuthorToken, { propsData: { @@ -47,20 +49,32 @@ function createComponent(options = {}) { alignSuggestions: function fakeAlignSuggestions() {}, suggestionsListClass: 'custom-class', }, + data() { + return { ...data }; + }, stubs, }); } describe('AuthorToken', () => { + const originalGon = window.gon; + const currentUserLength = 1; let mock; let wrapper; beforeEach(() => { + window.gon = { + ...originalGon, + current_user_id: 13, + current_user_fullname: 'Administrator', + current_username: 'root', + current_user_avatar_url: 'avatar/url', + }; mock = new MockAdapter(axios); - wrapper = createComponent(); }); afterEach(() => { + window.gon = originalGon; mock.restore(); wrapper.destroy(); }); @@ -91,6 +105,8 @@ describe('AuthorToken', () => { describe('fetchAuthorBySearchTerm', () => { it('calls `config.fetchAuthors` with provided searchTerm param', () => { + wrapper = createComponent(); + jest.spyOn(wrapper.vm.config, 'fetchAuthors'); wrapper.vm.fetchAuthorBySearchTerm(mockAuthors[0].username); @@ -102,6 +118,8 @@ describe('AuthorToken', () => { }); it('sets response to `authors` when request is succesful', () => { + wrapper = createComponent(); + jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockResolvedValue(mockAuthors); wrapper.vm.fetchAuthorBySearchTerm('root'); @@ -112,6 +130,8 @@ describe('AuthorToken', () => { }); it('calls `createFlash` with flash error message when request fails', () => { + wrapper = createComponent(); + jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockRejectedValue({}); wrapper.vm.fetchAuthorBySearchTerm('root'); @@ -122,6 +142,8 @@ describe('AuthorToken', () => { }); it('sets `loading` to false when request completes', () => { + wrapper = createComponent(); + jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockRejectedValue({}); wrapper.vm.fetchAuthorBySearchTerm('root'); @@ -133,21 +155,16 @@ describe('AuthorToken', () => { }); describe('template', () => { - beforeEach(() => { - wrapper.setData({ - authors: mockAuthors, - }); - - return wrapper.vm.$nextTick(); - }); - it('renders gl-filtered-search-token component', () => { + wrapper = createComponent({ data: { authors: mockAuthors } }); + expect(wrapper.find(GlFilteredSearchToken).exists()).toBe(true); }); it('renders token item when value is selected', () => { - wrapper.setProps({ + wrapper = createComponent({ value: { data: mockAuthors[0].username }, + data: { authors: mockAuthors }, }); return wrapper.vm.$nextTick(() => { @@ -172,7 +189,7 @@ describe('AuthorToken', () => { const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); - expect(suggestions).toHaveLength(defaultAuthors.length); + expect(suggestions).toHaveLength(defaultAuthors.length + currentUserLength); defaultAuthors.forEach((label, index) => { expect(suggestions.at(index).text()).toBe(label.text); }); @@ -189,7 +206,6 @@ describe('AuthorToken', () => { suggestionsSegment.vm.$emit('activate'); await wrapper.vm.$nextTick(); - expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false); expect(wrapper.find(GlDropdownDivider).exists()).toBe(false); }); @@ -206,8 +222,28 @@ describe('AuthorToken', () => { const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); - expect(suggestions).toHaveLength(1); + expect(suggestions).toHaveLength(1 + currentUserLength); expect(suggestions.at(0).text()).toBe(DEFAULT_LABEL_ANY.text); }); + + describe('when loading', () => { + beforeEach(() => { + wrapper = createComponent({ + active: true, + config: { ...mockAuthorToken, defaultAuthors: [] }, + stubs: { Portal: true }, + }); + }); + + it('shows loading icon', () => { + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); + }); + + it('shows current user', () => { + const firstSuggestion = wrapper.findComponent(GlFilteredSearchSuggestion).text(); + expect(firstSuggestion).toContain('Administrator'); + expect(firstSuggestion).toContain('@root'); + }); + }); }); }); diff --git a/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js index 5c29c267c99..2658fa4a706 100644 --- a/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js +++ b/spec/frontend/vue_shared/components/issue/issue_assignees_spec.js @@ -91,7 +91,7 @@ describe('IssueAssigneesComponent', () => { }); it('computes alt text for assignee avatar', () => { - expect(vm.avatarUrlTitle(mockAssigneesList[0])).toBe('Avatar for Terrell Graham'); + expect(vm.avatarUrlTitle(mockAssigneesList[0])).toBe('Assigned to Terrell Graham'); }); it('renders component root element with class `issue-assignees`', () => { @@ -106,7 +106,7 @@ describe('IssueAssigneesComponent', () => { const expected = mockAssigneesList.slice(0, TEST_MAX_VISIBLE - 1).map((x) => expect.objectContaining({ linkHref: x.web_url, - imgAlt: `Avatar for ${x.name}`, + imgAlt: `Assigned to ${x.name}`, imgCssClasses: TEST_CSS_CLASSES, imgSrc: x.avatar_url, imgSize: TEST_ICON_SIZE, diff --git a/spec/graphql/mutations/commits/create_spec.rb b/spec/graphql/mutations/commits/create_spec.rb index 152b5d87da0..097e70bada6 100644 --- a/spec/graphql/mutations/commits/create_spec.rb +++ b/spec/graphql/mutations/commits/create_spec.rb @@ -74,6 +74,10 @@ RSpec.describe Mutations::Commits::Create do expect(commit_pipeline_path).to match(%r(pipelines/sha/\w+)) end + it 'returns the content of the commit' do + expect(subject[:content]).to eq(actions.pluck(:content)) + end + it 'returns a new commit' do expect(mutated_commit).to have_attributes(message: message, project: project) expect(subject[:errors]).to be_empty @@ -166,6 +170,7 @@ RSpec.describe Mutations::Commits::Create do it 'returns a new commit' do expect(mutated_commit).to have_attributes(message: message, project: project) expect(subject[:errors]).to be_empty + expect(subject[:content]).to eq(actions.pluck(:content)) expect_to_contain_deltas([ a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: 'ANOTHER_FILE.md') diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 17fe90c830e..ceca83d107b 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -304,7 +304,7 @@ RSpec.describe IssuesHelper do empty_state_svg_path: '#', endpoint: expose_path(api_v4_projects_issues_path(id: project.id)), export_csv_path: export_csv_project_issues_path(project), - has_issues: project_issues(project).exists?.to_s, + has_project_issues: project_issues(project).exists?.to_s, import_csv_issues_path: '#', initial_email: project.new_issuable_address(current_user, 'issue'), is_signed_in: current_user.present?.to_s, diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb index e3507df5736..47f9bcc08d2 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb @@ -356,4 +356,29 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m subject end end + + describe '.for_configuration' do + let!(:migration) do + create( + :batched_background_migration, + job_class_name: 'MyJobClass', + table_name: :projects, + column_name: :id, + job_arguments: [[:id], [:id_convert_to_bigint]] + ) + end + + before do + create(:batched_background_migration, job_class_name: 'OtherClass') + create(:batched_background_migration, table_name: 'other_table') + create(:batched_background_migration, column_name: 'other_column') + create(:batched_background_migration, job_arguments: %w[other arguments]) + end + + it 'finds the migration matching the given configuration parameters' do + actual = described_class.for_configuration('MyJobClass', :projects, :id, [[:id], [:id_convert_to_bigint]]) + + expect(actual).to contain_exactly(migration) + end + end end diff --git a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb index c6d456964cf..85083bccaca 100644 --- a/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb @@ -269,6 +269,38 @@ RSpec.describe Gitlab::Database::Migrations::BackgroundMigrationHelpers do allow(Gitlab::Database::PgClass).to receive(:for_table).and_call_original end + context 'when such migration already exists' do + it 'does not create duplicate migration' do + create( + :batched_background_migration, + job_class_name: 'MyJobClass', + table_name: :projects, + column_name: :id, + interval: 10.minutes, + min_value: 5, + max_value: 1005, + batch_class_name: 'MyBatchClass', + batch_size: 200, + sub_batch_size: 20, + job_arguments: [[:id], [:id_convert_to_bigint]] + ) + + expect do + model.queue_batched_background_migration( + 'MyJobClass', + :projects, + :id, + [:id], [:id_convert_to_bigint], + job_interval: 5.minutes, + batch_min_value: 5, + batch_max_value: 1000, + batch_class_name: 'MyBatchClass', + batch_size: 100, + sub_batch_size: 10) + end.not_to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count } + end + end + it 'creates the database record for the migration' do expect(Gitlab::Database::PgClass).to receive(:for_table).with(:projects).and_return(pgclass_info) diff --git a/spec/lib/gitlab/git/remote_repository_spec.rb b/spec/lib/gitlab/git/remote_repository_spec.rb index 84c17234ae4..c7bc81573a6 100644 --- a/spec/lib/gitlab/git/remote_repository_spec.rb +++ b/spec/lib/gitlab/git/remote_repository_spec.rb @@ -58,45 +58,4 @@ RSpec.describe Gitlab::Git::RemoteRepository, :seed_helper do it { expect(subject.same_repository?(other_repository)).to eq(result) } end end - - describe '#fetch_env' do - let(:remote_repository) { described_class.new(repository) } - - let(:gitaly_client) { double(:gitaly_client) } - let(:address) { 'fake-address' } - let(:token) { 'fake-token' } - - subject { remote_repository.fetch_env } - - before do - allow(remote_repository).to receive(:gitaly_client).and_return(gitaly_client) - - expect(gitaly_client).to receive(:address).with(repository.storage).and_return(address) - expect(gitaly_client).to receive(:token).with(repository.storage).and_return(token) - end - - it { expect(subject).to be_a(Hash) } - it { expect(subject['GITALY_ADDRESS']).to eq(address) } - it { expect(subject['GITALY_TOKEN']).to eq(token) } - it { expect(subject['GITALY_WD']).to eq(Dir.pwd) } - - it 'creates a plausible GIT_SSH_COMMAND' do - git_ssh_command = subject['GIT_SSH_COMMAND'] - - expect(git_ssh_command).to start_with('/') - expect(git_ssh_command).to end_with('/gitaly-ssh upload-pack') - end - - it 'creates a plausible GITALY_PAYLOAD' do - req = Gitaly::SSHUploadPackRequest.decode_json(subject['GITALY_PAYLOAD']) - - expect(remote_repository.gitaly_repository).to eq(req.repository) - end - - context 'when the token is blank' do - let(:token) { '' } - - it { expect(subject.keys).not_to include('GITALY_TOKEN') } - end - end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 33233171813..c2f49b10639 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1880,6 +1880,26 @@ RSpec.describe Ci::Build do it { is_expected.not_to be_retryable } end + + context 'when a canceled build has been retried already' do + before do + project.add_developer(user) + build.cancel! + described_class.retry(build, user) + end + + context 'when prevent_retry_of_retried_jobs feature flag is enabled' do + it { is_expected.not_to be_retryable } + end + + context 'when prevent_retry_of_retried_jobs feature flag is disabled' do + before do + stub_feature_flags(prevent_retry_of_retried_jobs: false) + end + + it { is_expected.to be_retryable } + end + end end end diff --git a/spec/workers/build_hooks_worker_spec.rb b/spec/workers/build_hooks_worker_spec.rb index 62e1a4fd294..5f7e7e5fb00 100644 --- a/spec/workers/build_hooks_worker_spec.rb +++ b/spec/workers/build_hooks_worker_spec.rb @@ -24,20 +24,8 @@ RSpec.describe BuildHooksWorker do end describe '.perform_async' do - context 'when delayed_perform_for_build_hooks_worker feature flag is disabled' do - before do - stub_feature_flags(delayed_perform_for_build_hooks_worker: false) - end - - it 'delays scheduling a job by calling perform_in with default delay' do - expect(described_class).to receive(:perform_in).with(ApplicationWorker::DEFAULT_DELAY_INTERVAL.second, 123) - - described_class.perform_async(123) - end - end - - it 'delays scheduling a job by calling perform_in' do - expect(described_class).to receive(:perform_in).with(described_class::DATA_CONSISTENCY_DELAY.second, 123) + it 'delays scheduling a job by calling perform_in with default delay' do + expect(described_class).to receive(:perform_in).with(ApplicationWorker::DEFAULT_DELAY_INTERVAL.second, 123) described_class.perform_async(123) end |