diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-22 21:10:38 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-22 21:10:38 +0300 |
commit | 7e2f555a6dc37839727dee130d8ed4421b680d42 (patch) | |
tree | f2bcca7814bbaa6ee50e14830bc3a74307b1148e /spec | |
parent | 4ec96676406da695de083b4f394290902da2a964 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
9 files changed, 268 insertions, 67 deletions
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 452a5700e08..4df9109875e 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -49,6 +49,11 @@ RSpec.describe 'Runners', feature_category: :runner_fleet do it_behaves_like 'creates runner and shows register page' do let(:register_path_pattern) { register_project_runner_path(project, '.*') } end + + it 'shows the locked field' do + expect(page).to have_selector('input[type="checkbox"][name="locked"]') + expect(page).to have_content(_('Lock to current projects')) + end end end diff --git a/spec/frontend/analytics/cycle_analytics/components/filter_bar_spec.js b/spec/frontend/analytics/cycle_analytics/components/filter_bar_spec.js index f1b3af39199..f57d8559ddf 100644 --- a/spec/frontend/analytics/cycle_analytics/components/filter_bar_spec.js +++ b/spec/frontend/analytics/cycle_analytics/components/filter_bar_spec.js @@ -119,6 +119,10 @@ describe('Filter bar', () => { it('renders FilteredSearchBar component', () => { expect(findFilteredSearch().exists()).toBe(true); }); + + it('passes the `terms-as-tokens` prop', () => { + expect(findFilteredSearch().props('termsAsTokens')).toBe(true); + }); }); describe('when the state has data', () => { diff --git a/spec/frontend/ci/runner/components/runner_create_form_spec.js b/spec/frontend/ci/runner/components/runner_create_form_spec.js index 329dd2f73ee..243d23aeb38 100644 --- a/spec/frontend/ci/runner/components/runner_create_form_spec.js +++ b/spec/frontend/ci/runner/components/runner_create_form_spec.js @@ -21,12 +21,14 @@ jest.mock('~/ci/runner/sentry_utils'); const mockCreatedRunner = runnerCreateResult.data.runnerCreate.runner; const defaultRunnerModel = { + runnerType: INSTANCE_TYPE, description: '', accessLevel: DEFAULT_ACCESS_LEVEL, paused: false, maintenanceNote: '', maximumTimeout: '', runUntagged: false, + locked: false, tagList: '', }; @@ -81,6 +83,7 @@ describe('RunnerCreateForm', () => { findRunnerFormFields().vm.$emit('input', { ...defaultRunnerModel, + runnerType: props.runnerType, description: 'My runner', maximumTimeout: 0, tagList: 'tag1, tag2', diff --git a/spec/frontend/ci/runner/components/runner_form_fields_spec.js b/spec/frontend/ci/runner/components/runner_form_fields_spec.js index 5b429645d17..0e2f2aa2e91 100644 --- a/spec/frontend/ci/runner/components/runner_form_fields_spec.js +++ b/spec/frontend/ci/runner/components/runner_form_fields_spec.js @@ -1,7 +1,11 @@ import { nextTick } from 'vue'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import RunnerFormFields from '~/ci/runner/components/runner_form_fields.vue'; -import { ACCESS_LEVEL_NOT_PROTECTED, ACCESS_LEVEL_REF_PROTECTED } from '~/ci/runner/constants'; +import { + ACCESS_LEVEL_NOT_PROTECTED, + ACCESS_LEVEL_REF_PROTECTED, + PROJECT_TYPE, +} from '~/ci/runner/constants'; const mockDescription = 'My description'; const mockMaxTimeout = 60; @@ -63,6 +67,30 @@ describe('RunnerFormFields', () => { }); }); + it('locked checkbox is not shown', () => { + createComponent(); + + expect(findInput('locked').exists()).toBe(false); + }); + + it('when runner is of project type, locked checkbox can be checked', async () => { + createComponent({ + runner: { + runnerType: PROJECT_TYPE, + locked: false, + }, + }); + + findInput('locked').setChecked(true); + + await nextTick(); + + expect(wrapper.emitted('input')[0][0]).toEqual({ + runnerType: PROJECT_TYPE, + locked: true, + }); + }); + it('unchecks checkbox fields', async () => { createComponent({ runner: { diff --git a/spec/frontend/content_editor/extensions/code_spec.js b/spec/frontend/content_editor/extensions/code_spec.js index 0a54ac6a96b..4d8629a35c0 100644 --- a/spec/frontend/content_editor/extensions/code_spec.js +++ b/spec/frontend/content_editor/extensions/code_spec.js @@ -1,8 +1,60 @@ +import Bold from '~/content_editor/extensions/bold'; import Code from '~/content_editor/extensions/code'; -import { EXTENSION_PRIORITY_LOWER } from '~/content_editor/constants'; +import { createTestEditor, createDocBuilder } from '../test_utils'; describe('content_editor/extensions/code', () => { - it('has a lower loading priority', () => { - expect(Code.config.priority).toBe(EXTENSION_PRIORITY_LOWER); + let tiptapEditor; + let doc; + let p; + let bold; + let code; + + beforeEach(() => { + tiptapEditor = createTestEditor({ extensions: [Bold, Code] }); + + ({ + builders: { doc, p, bold, code }, + } = createDocBuilder({ + tiptapEditor, + names: { + bold: { markType: Bold.name }, + code: { markType: Code.name }, + }, + })); + }); + + it.each` + markOrder | description + ${['bold', 'code']} | ${'bold is toggled before code'} + ${['code', 'bold']} | ${'code is toggled before bold'} + `('has a lower loading priority, when $description', ({ markOrder }) => { + const initialDoc = doc(p('code block')); + const expectedDoc = doc(p(bold(code('code block')))); + + tiptapEditor.commands.setContent(initialDoc.toJSON()); + tiptapEditor.commands.selectAll(); + markOrder.forEach((mark) => tiptapEditor.commands.toggleMark(mark)); + + expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON()); + }); + + describe('shortcut: RightArrow', () => { + it('exits the code block', () => { + const initialDoc = doc(p('You can write ', code('java'))); + const expectedDoc = doc(p('You can write ', code('javascript'), ' here')); + const pos = 25; + + tiptapEditor.commands.setContent(initialDoc.toJSON()); + tiptapEditor.commands.setTextSelection(pos); + + // insert 'script' after 'java' within the code block + tiptapEditor.commands.insertContent({ type: 'text', text: 'script' }); + + // insert ' here' after the code block + tiptapEditor.commands.keyboardShortcut('ArrowRight'); + tiptapEditor.commands.insertContent({ type: 'text', text: 'here' }); + + expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON()); + }); }); }); diff --git a/spec/frontend/environments/edit_environment_spec.js b/spec/frontend/environments/edit_environment_spec.js index 34f338fabe6..cc28e12788b 100644 --- a/spec/frontend/environments/edit_environment_spec.js +++ b/spec/frontend/environments/edit_environment_spec.js @@ -1,5 +1,7 @@ import { GlLoadingIcon } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import EditEnvironment from '~/environments/components/edit_environment.vue'; @@ -7,49 +9,56 @@ import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { visitUrl } from '~/lib/utils/url_utility'; +import getEnvironment from '~/environments/graphql/queries/environment.query.graphql'; +import { __ } from '~/locale'; +import createMockApollo from '../__helpers__/mock_apollo_helper'; jest.mock('~/lib/utils/url_utility'); jest.mock('~/alert'); -const DEFAULT_OPTS = { - provide: { - projectEnvironmentsPath: '/projects/environments', - updateEnvironmentPath: '/proejcts/environments/1', - protectedEnvironmentSettingsPath: '/projects/1/settings/ci_cd', - }, - propsData: { environment: { id: '0', name: 'foo', external_url: 'https://foo.example.com' } }, +const environment = { id: '1', name: 'foo', externalUrl: 'https://foo.example.com' }; +const resolvedEnvironment = { project: { id: '1', environment } }; + +const provide = { + projectEnvironmentsPath: '/projects/environments', + updateEnvironmentPath: '/projects/environments/1', + protectedEnvironmentSettingsPath: '/projects/1/settings/ci_cd', + projectPath: '/path/to/project', + environmentName: environment.name, }; describe('~/environments/components/edit.vue', () => { + Vue.use(VueApollo); + let wrapper; let mock; - const createWrapper = (opts = {}) => - mountExtended(EditEnvironment, { - ...DEFAULT_OPTS, - ...opts, - }); + const createWrapper = () => { + const mockApollo = createMockApollo([ + [getEnvironment, jest.fn().mockResolvedValue({ data: resolvedEnvironment })], + ]); - beforeEach(() => { - mock = new MockAdapter(axios); - wrapper = createWrapper(); - }); + return mountExtended(EditEnvironment, { + provide, + apolloProvider: mockApollo, + }); + }; afterEach(() => { mock.restore(); }); - const findNameInput = () => wrapper.findByLabelText('Name'); - const findExternalUrlInput = () => wrapper.findByLabelText('External URL'); - const findForm = () => wrapper.findByRole('form', { name: 'Edit environment' }); + const findNameInput = () => wrapper.findByLabelText(__('Name')); + const findExternalUrlInput = () => wrapper.findByLabelText(__('External URL')); + const findForm = () => wrapper.findByRole('form', { name: __('Edit environment') }); const showsLoading = () => wrapper.findComponent(GlLoadingIcon).exists(); const submitForm = async (expected, response) => { mock - .onPut(DEFAULT_OPTS.provide.updateEnvironmentPath, { + .onPut(provide.updateEnvironmentPath, { external_url: expected.url, - id: '0', + id: '1', }) .reply(...response); await findExternalUrlInput().setValue(expected.url); @@ -58,48 +67,66 @@ describe('~/environments/components/edit.vue', () => { await waitForPromises(); }; - it('sets the title to Edit environment', () => { - const header = wrapper.findByRole('heading', { name: 'Edit environment' }); - expect(header.exists()).toBe(true); - }); + describe('default', () => { + beforeEach(async () => { + mock = new MockAdapter(axios); + wrapper = createWrapper(); + await waitForPromises(); + }); - it('shows loader after form is submitted', async () => { - const expected = { url: 'https://google.ca' }; + it('sets the title to Edit environment', () => { + const header = wrapper.findByRole('heading', { name: __('Edit environment') }); + expect(header.exists()).toBe(true); + }); - expect(showsLoading()).toBe(false); + it('shows loader after form is submitted', async () => { + const expected = { url: 'https://google.ca' }; - await submitForm(expected, [HTTP_STATUS_OK, { path: '/test' }]); + expect(showsLoading()).toBe(false); - expect(showsLoading()).toBe(true); - }); + await submitForm(expected, [HTTP_STATUS_OK, { path: '/test' }]); + + expect(showsLoading()).toBe(true); + }); - it('submits the updated environment on submit', async () => { - const expected = { url: 'https://google.ca' }; + it('submits the updated environment on submit', async () => { + const expected = { url: 'https://google.ca' }; - await submitForm(expected, [HTTP_STATUS_OK, { path: '/test' }]); + await submitForm(expected, [HTTP_STATUS_OK, { path: '/test' }]); - expect(visitUrl).toHaveBeenCalledWith('/test'); - }); + expect(visitUrl).toHaveBeenCalledWith('/test'); + }); - it('shows errors on error', async () => { - const expected = { url: 'https://google.ca' }; + it('shows errors on error', async () => { + const expected = { url: 'https://google.ca' }; - await submitForm(expected, [HTTP_STATUS_BAD_REQUEST, { message: ['uh oh!'] }]); + await submitForm(expected, [HTTP_STATUS_BAD_REQUEST, { message: ['uh oh!'] }]); - expect(createAlert).toHaveBeenCalledWith({ message: 'uh oh!' }); - expect(showsLoading()).toBe(false); - }); + expect(createAlert).toHaveBeenCalledWith({ message: 'uh oh!' }); + expect(showsLoading()).toBe(false); + }); - it('renders a disabled "Name" field', () => { - const nameInput = findNameInput(); + it('renders a disabled "Name" field', () => { + const nameInput = findNameInput(); - expect(nameInput.attributes().disabled).toBe('disabled'); - expect(nameInput.element.value).toBe('foo'); + expect(nameInput.attributes().disabled).toBe('disabled'); + expect(nameInput.element.value).toBe(environment.name); + }); + + it('renders an "External URL" field', () => { + const urlInput = findExternalUrlInput(); + + expect(urlInput.element.value).toBe(environment.externalUrl); + }); }); - it('renders an "External URL" field', () => { - const urlInput = findExternalUrlInput(); + describe('when environment query is loading', () => { + beforeEach(() => { + wrapper = createWrapper(); + }); - expect(urlInput.element.value).toBe('https://foo.example.com'); + it('renders loading icon', () => { + expect(showsLoading()).toBe(true); + }); }); }); diff --git a/spec/models/import_failure_spec.rb b/spec/models/import_failure_spec.rb index 101da1212cf..a8ada156dd7 100644 --- a/spec/models/import_failure_spec.rb +++ b/spec/models/import_failure_spec.rb @@ -36,23 +36,39 @@ RSpec.describe ImportFailure do describe 'Associations' do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:group) } + it { is_expected.to belong_to(:user) } end describe 'Validations' do - context 'has no group' do + let_it_be(:group) { build(:group) } + let_it_be(:project) { build(:project) } + let_it_be(:user) { build(:user) } + + context 'has project' do + before do + allow(subject).to receive(:project).and_return(project) + end + + it { is_expected.to validate_absence_of(:group) } + it { is_expected.to validate_absence_of(:user) } + end + + context 'has group' do before do - allow(subject).to receive(:group).and_return(nil) + allow(subject).to receive(:group).and_return(group) end - it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_absence_of(:project) } + it { is_expected.to validate_absence_of(:user) } end - context 'has no project' do + context 'has user' do before do - allow(subject).to receive(:project).and_return(nil) + allow(subject).to receive(:user).and_return(user) end - it { is_expected.to validate_presence_of(:group) } + it { is_expected.to validate_absence_of(:project) } + it { is_expected.to validate_absence_of(:group) } end describe '#external_identifiers' do diff --git a/spec/support/shared_examples/features/runners_shared_examples.rb b/spec/support/shared_examples/features/runners_shared_examples.rb index 7edf306183e..4da684c4c10 100644 --- a/spec/support/shared_examples/features/runners_shared_examples.rb +++ b/spec/support/shared_examples/features/runners_shared_examples.rb @@ -232,7 +232,7 @@ RSpec.shared_examples 'creates runner and shows register page' do before do fill_in s_('Runners|Runner description'), with: 'runner-foo' fill_in s_('Runners|Tags'), with: 'tag1' - click_on _('Submit') + click_on s_('Runners|Create runner') wait_for_requests end diff --git a/spec/workers/gitlab/github_gists_import/import_gist_worker_spec.rb b/spec/workers/gitlab/github_gists_import/import_gist_worker_spec.rb index fcbe1b2cf99..2e89263bcf3 100644 --- a/spec/workers/gitlab/github_gists_import/import_gist_worker_spec.rb +++ b/spec/workers/gitlab/github_gists_import/import_gist_worker_spec.rb @@ -87,10 +87,13 @@ RSpec.describe Gitlab::GithubGistsImport::ImportGistWorker, feature_category: :i context 'when failure' do context 'when importer raised an error' do - it 'raises an error' do - exception = StandardError.new('_some_error_') + let(:exception) { StandardError.new('_some_error_') } + + before do + allow(importer).to receive(:execute).and_raise(exception) + end - expect(importer).to receive(:execute).and_raise(exception) + it 'raises an error' do expect(Gitlab::GithubImport::Logger) .to receive(:error) .with(log_attributes.merge('message' => 'importer failed', 'error.message' => '_some_error_')) @@ -103,8 +106,11 @@ RSpec.describe Gitlab::GithubGistsImport::ImportGistWorker, feature_category: :i context 'when importer returns error' do let(:importer_result) { instance_double('ServiceResponse', errors: 'error_message', success?: false) } + before do + allow(importer).to receive(:execute).and_return(importer_result) + end + it 'tracks and logs error' do - expect(importer).to receive(:execute).and_return(importer_result) expect(Gitlab::GithubImport::Logger) .to receive(:error) .with(log_attributes.merge('message' => 'importer failed', 'error.message' => 'error_message')) @@ -120,14 +126,56 @@ RSpec.describe Gitlab::GithubGistsImport::ImportGistWorker, feature_category: :i status: 'failed' ) end + + it 'persists failure' do + expect { subject.perform(user.id, gist_hash, 'some_key') } + .to change { ImportFailure.where(user: user).count }.from(0).to(1) + + expect(ImportFailure.where(user_id: user.id).first).to have_attributes( + source: 'Gitlab::GithubGistsImport::Importer::GistImporter', + exception_class: 'Gitlab::GithubGistsImport::Importer::GistImporter::FileCountLimitError', + exception_message: 'Snippet maximum file count exceeded', + external_identifiers: { + 'id' => '055b70' + } + ) + end end end describe '.sidekiq_retries_exhausted' do - it 'sends snowplow event' do - job = { 'args' => [user.id, 'some_key', '1'], 'jid' => '123' } + subject(:sidekiq_retries_exhausted) do + described_class.sidekiq_retries_exhausted_block.call(job, StandardError.new) + end - described_class.sidekiq_retries_exhausted_block.call(job) + let(:args) { [user.id, gist_hash, '1'] } + + let(:job) do + { + 'args' => args, + 'jid' => '123', + 'correlation_id' => 'abc', + 'error_class' => 'StandardError', + 'error_message' => 'Some error' + } + end + + it 'persists failure' do + expect { sidekiq_retries_exhausted }.to change { ImportFailure.where(user: user).count }.from(0).to(1) + + expect(ImportFailure.where(user_id: user.id).first).to have_attributes( + source: 'Gitlab::GithubGistsImport::Importer::GistImporter', + exception_class: 'StandardError', + exception_message: 'Some error', + correlation_id_value: 'abc', + external_identifiers: { + 'id' => '055b70' + } + ) + end + + it 'sends snowplow event' do + sidekiq_retries_exhausted expect_snowplow_event( category: 'Gitlab::GithubGistsImport::ImportGistWorker', @@ -137,6 +185,24 @@ RSpec.describe Gitlab::GithubGistsImport::ImportGistWorker, feature_category: :i status: 'failed' ) end + + it 'notifies the JobWaiter' do + expect(Gitlab::JobWaiter) + .to receive(:notify) + .with(job['args'].last, job['jid']) + + sidekiq_retries_exhausted + end + + context 'when not all arguments are given' do + let(:args) { [user.id, gist_hash] } + + it 'does not notify the JobWaiter' do + expect(Gitlab::JobWaiter).not_to receive(:notify) + + sidekiq_retries_exhausted + end + end end end end |