diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-05 15:12:12 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-05 15:12:12 +0000 |
commit | 8ec882085e734458ffe0fff8e2e4b72bc3871419 (patch) | |
tree | 6869bb67f3e66e9de828bc47a08577efa1e296c6 /spec | |
parent | f63850d9d6c3a81e78c93995c904ed6c0785ef19 (diff) | |
download | gitlab-ce-8ec882085e734458ffe0fff8e2e4b72bc3871419.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
19 files changed, 698 insertions, 91 deletions
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb index 18026412261..4758986b47c 100644 --- a/spec/factories/ci/runners.rb +++ b/spec/factories/ci/runners.rb @@ -18,11 +18,11 @@ FactoryBot.define do after(:build) do |runner, evaluator| evaluator.projects.each do |proj| - runner.runner_projects << build(:ci_runner_project, project: proj) + runner.runner_projects << build(:ci_runner_project, runner: runner, project: proj) end evaluator.groups.each do |group| - runner.runner_namespaces << build(:ci_runner_namespace, namespace: group) + runner.runner_namespaces << build(:ci_runner_namespace, runner: runner, namespace: group) end end diff --git a/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js b/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js index 351fd967719..62fec8d4e72 100644 --- a/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js +++ b/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js @@ -37,16 +37,17 @@ describe('content_editor/components/toolbar_more_dropdown', () => { }); describe.each` - name | contentType | command | params - ${'Code block'} | ${'codeBlock'} | ${'setNode'} | ${['codeBlock']} - ${'Details block'} | ${'details'} | ${'toggleList'} | ${['details', 'detailsContent']} - ${'Bullet list'} | ${'bulletList'} | ${'toggleList'} | ${['bulletList', 'listItem']} - ${'Ordered list'} | ${'orderedList'} | ${'toggleList'} | ${['orderedList', 'listItem']} - ${'Task list'} | ${'taskList'} | ${'toggleList'} | ${['taskList', 'taskItem']} - ${'Mermaid diagram'} | ${'diagram'} | ${'setNode'} | ${['diagram', { language: 'mermaid' }]} - ${'PlantUML diagram'} | ${'diagram'} | ${'setNode'} | ${['diagram', { language: 'plantuml' }]} - ${'Horizontal rule'} | ${'horizontalRule'} | ${'setHorizontalRule'} | ${[]} - `('when option $label is clicked', ({ name, command, contentType, params }) => { + name | contentType | command | params + ${'Code block'} | ${'codeBlock'} | ${'setNode'} | ${['codeBlock']} + ${'Details block'} | ${'details'} | ${'toggleList'} | ${['details', 'detailsContent']} + ${'Bullet list'} | ${'bulletList'} | ${'toggleList'} | ${['bulletList', 'listItem']} + ${'Ordered list'} | ${'orderedList'} | ${'toggleList'} | ${['orderedList', 'listItem']} + ${'Task list'} | ${'taskList'} | ${'toggleList'} | ${['taskList', 'taskItem']} + ${'Mermaid diagram'} | ${'diagram'} | ${'setNode'} | ${['diagram', { language: 'mermaid' }]} + ${'PlantUML diagram'} | ${'diagram'} | ${'setNode'} | ${['diagram', { language: 'plantuml' }]} + ${'Table of contents'} | ${'tableOfContents'} | ${'insertTableOfContents'} | ${[]} + ${'Horizontal rule'} | ${'horizontalRule'} | ${'setHorizontalRule'} | ${[]} + `('when option $name is clicked', ({ name, command, contentType, params }) => { let commands; let btn; diff --git a/spec/frontend/content_editor/components/wrappers/__snapshots__/table_of_contents_spec.js.snap b/spec/frontend/content_editor/components/wrappers/__snapshots__/table_of_contents_spec.js.snap new file mode 100644 index 00000000000..fb091419ad9 --- /dev/null +++ b/spec/frontend/content_editor/components/wrappers/__snapshots__/table_of_contents_spec.js.snap @@ -0,0 +1,115 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`content/components/wrappers/table_of_contents collects all headings and renders a nested list of headings 1`] = ` +<div + class="table-of-contents gl-border-1 gl-border-solid gl-border-gray-100 gl-mb-5 gl-p-4!" + data-testid="table-of-contents" +> + + Table of contents + + <li> + <a + href="#" + > + + Heading 1 + + </a> + + <ul> + <li> + <a + href="#" + > + + Heading 1.1 + + </a> + + <ul> + <li> + <a + href="#" + > + + Heading 1.1.1 + + </a> + + <!----> + </li> + </ul> + </li> + <li> + <a + href="#" + > + + Heading 1.2 + + </a> + + <ul> + <li> + <a + href="#" + > + + Heading 1.2.1 + + </a> + + <!----> + </li> + </ul> + </li> + <li> + <a + href="#" + > + + Heading 1.3 + + </a> + + <!----> + </li> + <li> + <a + href="#" + > + + Heading 1.4 + + </a> + + <ul> + <li> + <a + href="#" + > + + Heading 1.4.1 + + </a> + + <!----> + </li> + </ul> + </li> + </ul> + </li> + <li> + <a + href="#" + > + + Heading 2 + + </a> + + <!----> + </li> +</div> +`; diff --git a/spec/frontend/content_editor/components/wrappers/table_of_contents_spec.js b/spec/frontend/content_editor/components/wrappers/table_of_contents_spec.js new file mode 100644 index 00000000000..bfda89a8b09 --- /dev/null +++ b/spec/frontend/content_editor/components/wrappers/table_of_contents_spec.js @@ -0,0 +1,84 @@ +import { nextTick } from 'vue'; +import { NodeViewWrapper } from '@tiptap/vue-2'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { stubComponent } from 'helpers/stub_component'; +import eventHubFactory from '~/helpers/event_hub_factory'; +import Heading from '~/content_editor/extensions/heading'; +import Diagram from '~/content_editor/extensions/diagram'; +import TableOfContentsWrapper from '~/content_editor/components/wrappers/table_of_contents.vue'; +import { createTestEditor, createDocBuilder, emitEditorEvent } from '../../test_utils'; + +describe('content/components/wrappers/table_of_contents', () => { + let wrapper; + let tiptapEditor; + let contentEditor; + let eventHub; + + const buildEditor = () => { + tiptapEditor = createTestEditor({ extensions: [Heading, Diagram] }); + contentEditor = { renderDiagram: jest.fn().mockResolvedValue('url/to/some/diagram') }; + eventHub = eventHubFactory(); + }; + + const createWrapper = async () => { + wrapper = mountExtended(TableOfContentsWrapper, { + propsData: { + editor: tiptapEditor, + node: { + attrs: {}, + }, + }, + stubs: { + NodeViewWrapper: stubComponent(NodeViewWrapper), + }, + provide: { + contentEditor, + tiptapEditor, + eventHub, + }, + }); + }; + + beforeEach(async () => { + buildEditor(); + createWrapper(); + + const { + builders: { heading, doc }, + } = createDocBuilder({ + tiptapEditor, + names: { + heading: { nodeType: Heading.name }, + }, + }); + + const initialDoc = doc( + heading({ level: 1 }, 'Heading 1'), + heading({ level: 2 }, 'Heading 1.1'), + heading({ level: 3 }, 'Heading 1.1.1'), + heading({ level: 2 }, 'Heading 1.2'), + heading({ level: 3 }, 'Heading 1.2.1'), + heading({ level: 2 }, 'Heading 1.3'), + heading({ level: 2 }, 'Heading 1.4'), + heading({ level: 3 }, 'Heading 1.4.1'), + heading({ level: 1 }, 'Heading 2'), + ); + + tiptapEditor.commands.setContent(initialDoc.toJSON()); + + await emitEditorEvent({ event: 'update', tiptapEditor }); + await nextTick(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders a node-view-wrapper as a ul element', () => { + expect(wrapper.findComponent(NodeViewWrapper).props().as).toBe('ul'); + }); + + it('collects all headings and renders a nested list of headings', () => { + expect(wrapper.findComponent(NodeViewWrapper).element).toMatchSnapshot(); + }); +}); diff --git a/spec/frontend/content_editor/services/table_of_contents_utils_spec.js b/spec/frontend/content_editor/services/table_of_contents_utils_spec.js new file mode 100644 index 00000000000..7f63c2171c2 --- /dev/null +++ b/spec/frontend/content_editor/services/table_of_contents_utils_spec.js @@ -0,0 +1,96 @@ +import Heading from '~/content_editor/extensions/heading'; +import { toTree, getHeadings } from '~/content_editor/services/table_of_contents_utils'; +import { createTestEditor, createDocBuilder } from '../test_utils'; + +describe('content_editor/services/table_of_content_utils', () => { + describe('toTree', () => { + it('should fills in gaps in heading levels and convert headings to a tree', () => { + expect( + toTree([ + { level: 3, text: '3' }, + { level: 2, text: '2' }, + ]), + ).toEqual([ + expect.objectContaining({ + level: 1, + text: '', + subHeadings: [ + expect.objectContaining({ + level: 2, + text: '', + subHeadings: [expect.objectContaining({ level: 3, text: '3', subHeadings: [] })], + }), + expect.objectContaining({ level: 2, text: '2', subHeadings: [] }), + ], + }), + ]); + }); + }); + + describe('getHeadings', () => { + const tiptapEditor = createTestEditor({ + extensions: [Heading], + }); + + const { + builders: { heading, doc }, + } = createDocBuilder({ + tiptapEditor, + names: { + heading: { nodeType: Heading.name }, + }, + }); + + it('gets all headings as a tree in a tiptap document', () => { + const initialDoc = doc( + heading({ level: 1 }, 'Heading 1'), + heading({ level: 2 }, 'Heading 1.1'), + heading({ level: 3 }, 'Heading 1.1.1'), + heading({ level: 2 }, 'Heading 1.2'), + heading({ level: 3 }, 'Heading 1.2.1'), + heading({ level: 2 }, 'Heading 1.3'), + heading({ level: 2 }, 'Heading 1.4'), + heading({ level: 3 }, 'Heading 1.4.1'), + heading({ level: 1 }, 'Heading 2'), + ); + + tiptapEditor.commands.setContent(initialDoc.toJSON()); + + expect(getHeadings(tiptapEditor)).toEqual([ + expect.objectContaining({ + level: 1, + text: 'Heading 1', + subHeadings: [ + expect.objectContaining({ + level: 2, + text: 'Heading 1.1', + subHeadings: [ + expect.objectContaining({ level: 3, text: 'Heading 1.1.1', subHeadings: [] }), + ], + }), + expect.objectContaining({ + level: 2, + text: 'Heading 1.2', + subHeadings: [ + expect.objectContaining({ level: 3, text: 'Heading 1.2.1', subHeadings: [] }), + ], + }), + expect.objectContaining({ level: 2, text: 'Heading 1.3', subHeadings: [] }), + expect.objectContaining({ + level: 2, + text: 'Heading 1.4', + subHeadings: [ + expect.objectContaining({ level: 3, text: 'Heading 1.4.1', subHeadings: [] }), + ], + }), + ], + }), + expect.objectContaining({ + level: 1, + text: 'Heading 2', + subHeadings: [], + }), + ]); + }); + }); +}); diff --git a/spec/frontend_integration/content_editor/content_editor_integration_spec.js b/spec/frontend_integration/content_editor/content_editor_integration_spec.js index 4d400a383e3..12cd6dcad83 100644 --- a/spec/frontend_integration/content_editor/content_editor_integration_spec.js +++ b/spec/frontend_integration/content_editor/content_editor_integration_spec.js @@ -95,4 +95,35 @@ This reference tag is a mix of letters and numbers [^footnote]. expect(wrapper.find('pre').text()).toContain('[gitlab]: https://gitlab.com'); }); }); + + it('renders table of contents', async () => { + jest.useFakeTimers(); + + buildWrapper(); + + renderMarkdown.mockResolvedValue(` +<ul class="section-nav"> +</ul> +<h1 dir="auto" data-sourcepos="3:1-3:11"> + Heading 1 +</h1> +<h2 dir="auto" data-sourcepos="5:1-5:12"> + Heading 2 +</h2> + `); + + await contentEditorService.setSerializedContent(` +[TOC] + +# Heading 1 + +## Heading 2 + `); + + await nextTick(); + jest.runAllTimers(); + + expect(wrapper.findByTestId('table-of-contents').text()).toContain('Heading 1'); + expect(wrapper.findByTestId('table-of-contents').text()).toContain('Heading 2'); + }); }); diff --git a/spec/migrations/20220506154054_create_sync_namespace_details_trigger_spec.rb b/spec/migrations/20220506154054_create_sync_namespace_details_trigger_spec.rb new file mode 100644 index 00000000000..411b1eacb86 --- /dev/null +++ b/spec/migrations/20220506154054_create_sync_namespace_details_trigger_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe CreateSyncNamespaceDetailsTrigger do + let(:migration) { described_class.new } + let(:namespaces) { table(:namespaces) } + let(:namespace_details) { table(:namespace_details) } + let!(:timestamp) { Time.new(2020, 01, 01).utc } + + let(:synced_attributes) do + { + description: 'description', + description_html: '<p>description</p>', + cached_markdown_version: 1966080, + created_at: timestamp, + updated_at: timestamp + } + end + + let(:other_attributes) do + { + name: 'name', + path: 'path' + } + end + + let(:attributes) { other_attributes.merge(synced_attributes) } + + describe '#up' do + before do + migrate! + end + + describe 'INSERT trigger' do + it 'creates a namespace_detail record' do + expect do + namespaces.create!(attributes) + end.to change(namespace_details, :count).by(1) + end + + it 'the created namespace_details record has matching attributes' do + namespaces.create!(attributes) + synced_namespace_details = namespace_details.last + + expect(synced_namespace_details).to have_attributes(synced_attributes) + end + end + + describe 'UPDATE trigger' do + let!(:namespace) { namespaces.create!(attributes) } + + it 'updates the attribute in the synced namespace_details record' do + namespace.update!(description: 'new_description') + + synced_namespace_details = namespace_details.last + expect(synced_namespace_details.description).to eq('new_description') + end + end + end + + describe '#down' do + before do + migration.up + migration.down + end + + it 'drops the trigger' do + expect do + namespaces.create!(attributes) + end.not_to change(namespace_details, :count) + end + end +end diff --git a/spec/migrations/20220524184149_create_sync_project_namespace_details_trigger_spec.rb b/spec/migrations/20220524184149_create_sync_project_namespace_details_trigger_spec.rb new file mode 100644 index 00000000000..f85a59357e1 --- /dev/null +++ b/spec/migrations/20220524184149_create_sync_project_namespace_details_trigger_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe CreateSyncProjectNamespaceDetailsTrigger do + let(:migration) { described_class.new } + let(:projects) { table(:projects) } + let(:namespaces) { table(:namespaces) } + let(:namespace_details) { table(:namespace_details) } + let!(:timestamp) { Time.new(2020, 01, 01).utc } + let!(:project_namespace) { namespaces.create!(name: 'name', path: 'path') } + let!(:namespace) { namespaces.create!(name: 'group', path: 'group_path') } + + let(:synced_attributes) do + { + description: 'description', + description_html: '<p>description</p>', + cached_markdown_version: 1966080, + updated_at: timestamp + } + end + + let(:other_attributes) do + { + name: 'project_name', + project_namespace_id: project_namespace.id, + namespace_id: namespace.id + } + end + + let(:attributes) { other_attributes.merge(synced_attributes) } + + describe '#up' do + before do + migrate! + end + + describe 'INSERT trigger' do + it 'the created namespace_details record has matching attributes' do + project = projects.create!(attributes) + synced_namespace_details = namespace_details.find_by(namespace_id: project.project_namespace_id) + + expect(synced_namespace_details).to have_attributes(synced_attributes) + end + end + + describe 'UPDATE trigger' do + let!(:project) { projects.create!(attributes) } + + it 'updates the attribute in the synced namespace_details record' do + project.update!(description: 'new_description') + + synced_namespace_details = namespace_details.find_by(namespace_id: project.project_namespace_id) + expect(synced_namespace_details.description).to eq('new_description') + end + end + end + + describe '#down' do + before do + migration.up + migration.down + end + + it 'drops the trigger' do + expect do + projects.create!(attributes) + end.not_to change(namespace_details, :count) + end + end +end diff --git a/spec/models/namespace/detail_spec.rb b/spec/models/namespace/detail_spec.rb new file mode 100644 index 00000000000..1bb756c441b --- /dev/null +++ b/spec/models/namespace/detail_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Namespace::Detail, type: :model do + describe 'associations' do + it { is_expected.to belong_to :namespace } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:namespace) } + end + + context 'when namespace description changes' do + let(:namespace) { create(:namespace, description: "old") } + + it 'changes namespace details description' do + expect { namespace.update!(description: "new") } + .to change { namespace.namespace_details.description }.from("old").to("new") + end + end + + context 'when project description changes' do + let(:project) { create(:project, description: "old") } + + it 'changes project namespace details description' do + expect { project.update!(description: "new") } + .to change { project.project_namespace.namespace_details.description }.from("old").to("new") + end + end + + context 'when group description changes' do + let(:group) { create(:group, description: "old") } + + it 'changes group namespace details description' do + expect { group.update!(description: "new") } + .to change { group.namespace_details.description }.from("old").to("new") + end + end +end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 2a5794b31d2..f9441a7a5e1 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -22,6 +22,7 @@ RSpec.describe Namespace do it { is_expected.to have_one :root_storage_statistics } it { is_expected.to have_one :aggregation_schedule } it { is_expected.to have_one :namespace_settings } + it { is_expected.to have_one :namespace_details } it { is_expected.to have_one(:namespace_statistics) } it { is_expected.to have_many :custom_emoji } it { is_expected.to have_one :package_setting_relation } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 2e9bdb5c4d7..780cf7b104a 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1469,6 +1469,20 @@ RSpec.describe Repository do expect(repository.find_branch(branch_name)).to be_nil end end + + it 'expires branches cache' do + expect(repository).to receive(:expire_branches_cache) + + subject + end + + context 'when expire_cache: false' do + it 'does not expire branches cache' do + expect(repository).not_to receive(:expire_branches_cache) + + repository.add_branch(user, branch_name, target, expire_cache: false) + end + end end shared_examples 'asymmetric cached method' do |method| diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb index 50ace7adccb..47302046865 100644 --- a/spec/requests/api/ci/runner/runners_post_spec.rb +++ b/spec/requests/api/ci/runner/runners_post_spec.rb @@ -16,7 +16,8 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do context 'when invalid token is provided' do it 'returns 403 error' do allow_next_instance_of(::Ci::Runners::RegisterRunnerService) do |service| - allow(service).to receive(:execute).and_return(nil) + allow(service).to receive(:execute) + .and_return(ServiceResponse.error(message: 'invalid token supplied', http_status: :forbidden)) end post api('/runners'), params: { token: 'invalid' } @@ -58,7 +59,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do expect(service).to receive(:execute) .once .with('valid token', a_hash_including(expected_params)) - .and_return(new_runner) + .and_return(ServiceResponse.success(payload: { runner: new_runner })) end end @@ -113,7 +114,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do .once .with('valid token', a_hash_including('maintenance_note' => 'Some maintainer notes') .and(excluding('maintainter_note' => anything))) - .and_return(new_runner) + .and_return(ServiceResponse.success(payload: { runner: new_runner })) end request @@ -139,7 +140,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do expect(service).to receive(:execute) .once .with('valid token', a_hash_including(expected_params)) - .and_return(new_runner) + .and_return(ServiceResponse.success(payload: { runner: new_runner })) end request diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb index 8655e5b0238..afe5a7d4a21 100644 --- a/spec/requests/api/project_import_spec.rb +++ b/spec/requests/api/project_import_spec.rb @@ -47,7 +47,7 @@ RSpec.describe API::ProjectImport, :aggregate_failures do it 'executes a limited number of queries' do control_count = ActiveRecord::QueryRecorder.new { subject }.count - expect(control_count).to be <= 108 + expect(control_count).to be <= 109 end it 'schedules an import using a namespace' do diff --git a/spec/services/branches/create_service_spec.rb b/spec/services/branches/create_service_spec.rb index 5904ef9b5cf..26cc1a0665e 100644 --- a/spec/services/branches/create_service_spec.rb +++ b/spec/services/branches/create_service_spec.rb @@ -93,7 +93,12 @@ RSpec.describe Branches::CreateService, :use_clean_rails_redis_caching do let(:branches) { { 'master' => 'master', '' => 'master', 'failed_branch' => 'master' } } it 'returns all errors' do - allow(project.repository).to receive(:add_branch).with(user, 'failed_branch', 'master').and_return(false) + allow(project.repository).to receive(:add_branch).with( + user, + 'failed_branch', + 'master', + expire_cache: false + ).and_return(false) expect(subject[:status]).to eq(:error) expect(subject[:message]).to match_array( @@ -117,6 +122,26 @@ RSpec.describe Branches::CreateService, :use_clean_rails_redis_caching do expect(control.by_command(:sadd).count).to eq(1) end end + + context 'without N+1 branch cache expiration' do + let(:branches) { { 'branch_1' => 'master', 'branch_2' => 'master', 'branch_3' => 'master' } } + + it 'triggers branch cache expiration only once' do + expect(project.repository).to receive(:expire_branches_cache).once + + subject + end + + context 'when branches were not added' do + let(:branches) { { 'master' => 'master' } } + + it 'does not trigger branch expiration' do + expect(project.repository).not_to receive(:expire_branches_cache) + + subject + end + end + end end describe '#execute' do diff --git a/spec/services/ci/runners/register_runner_service_spec.rb b/spec/services/ci/runners/register_runner_service_spec.rb index f233075224b..6d7b39de21e 100644 --- a/spec/services/ci/runners/register_runner_service_spec.rb +++ b/spec/services/ci/runners/register_runner_service_spec.rb @@ -6,6 +6,7 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do let(:registration_token) { 'abcdefg123456' } let(:token) {} let(:args) { {} } + let(:runner) { execute.payload[:runner] } before do stub_feature_flags(runner_registration_control: false) @@ -13,21 +14,25 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do stub_application_setting(valid_runner_registrars: ApplicationSetting::VALID_RUNNER_REGISTRAR_TYPES) end - subject(:runner) { described_class.new.execute(token, args) } + subject(:execute) { described_class.new.execute(token, args) } context 'when no token is provided' do let(:token) { '' } - it 'returns nil' do - is_expected.to be_nil + it 'returns error response' do + expect(execute).to be_error + expect(execute.message).to eq 'invalid token supplied' + expect(execute.http_status).to eq :forbidden end end context 'when invalid token is provided' do let(:token) { 'invalid' } - it 'returns nil' do - is_expected.to be_nil + it 'returns error response' do + expect(execute).to be_error + expect(execute.message).to eq 'invalid token supplied' + expect(execute.http_status).to eq :forbidden end end @@ -36,12 +41,14 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do let(:token) { registration_token } it 'creates runner with default values' do - is_expected.to be_an_instance_of(::Ci::Runner) - expect(subject.persisted?).to be_truthy - expect(subject.run_untagged).to be true - expect(subject.active).to be true - expect(subject.token).not_to eq(registration_token) - expect(subject).to be_instance_type + expect(execute).to be_success + + expect(runner).to be_an_instance_of(::Ci::Runner) + expect(runner.persisted?).to be_truthy + expect(runner.run_untagged).to be true + expect(runner.active).to be true + expect(runner.token).not_to eq(registration_token) + expect(runner).to be_instance_type end context 'with non-default arguments' do @@ -67,25 +74,27 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do end it 'creates runner with specified values', :aggregate_failures do - is_expected.to be_an_instance_of(::Ci::Runner) - expect(subject.active).to eq args[:active] - expect(subject.locked).to eq args[:locked] - expect(subject.run_untagged).to eq args[:run_untagged] - expect(subject.tags).to contain_exactly( + expect(execute).to be_success + + expect(runner).to be_an_instance_of(::Ci::Runner) + expect(runner.active).to eq args[:active] + expect(runner.locked).to eq args[:locked] + expect(runner.run_untagged).to eq args[:run_untagged] + expect(runner.tags).to contain_exactly( an_object_having_attributes(name: 'tag1'), an_object_having_attributes(name: 'tag2') ) - expect(subject.access_level).to eq args[:access_level] - expect(subject.maximum_timeout).to eq args[:maximum_timeout] - expect(subject.name).to eq args[:name] - expect(subject.version).to eq args[:version] - expect(subject.revision).to eq args[:revision] - expect(subject.platform).to eq args[:platform] - expect(subject.architecture).to eq args[:architecture] - expect(subject.ip_address).to eq args[:ip_address] - - expect(Ci::Runner.tagged_with('tag1')).to include(subject) - expect(Ci::Runner.tagged_with('tag2')).to include(subject) + expect(runner.access_level).to eq args[:access_level] + expect(runner.maximum_timeout).to eq args[:maximum_timeout] + expect(runner.name).to eq args[:name] + expect(runner.version).to eq args[:version] + expect(runner.revision).to eq args[:revision] + expect(runner.platform).to eq args[:platform] + expect(runner.architecture).to eq args[:architecture] + expect(runner.ip_address).to eq args[:ip_address] + + expect(Ci::Runner.tagged_with('tag1')).to include(runner) + expect(Ci::Runner.tagged_with('tag2')).to include(runner) end end @@ -95,8 +104,10 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do end it 'creates runner with token expiration' do - is_expected.to be_an_instance_of(::Ci::Runner) - expect(subject.token_expires_at).to eq(5.days.from_now) + expect(execute).to be_success + + expect(runner).to be_an_instance_of(::Ci::Runner) + expect(runner.token_expires_at).to eq(5.days.from_now) end end end @@ -106,12 +117,14 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do let(:token) { project.runners_token } it 'creates project runner' do - is_expected.to be_an_instance_of(::Ci::Runner) + expect(execute).to be_success + + expect(runner).to be_an_instance_of(::Ci::Runner) expect(project.runners.size).to eq(1) - is_expected.to eq(project.runners.first) - expect(subject.token).not_to eq(registration_token) - expect(subject.token).not_to eq(project.runners_token) - expect(subject).to be_project_type + expect(runner).to eq(project.runners.first) + expect(runner.token).not_to eq(registration_token) + expect(runner.token).not_to eq(project.runners_token) + expect(runner).to be_project_type end context 'when it exceeds the application limits' do @@ -121,9 +134,13 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do end it 'does not create runner' do - is_expected.to be_an_instance_of(::Ci::Runner) - expect(subject.persisted?).to be_falsey - expect(subject.errors.messages).to eq('runner_projects.base': ['Maximum number of ci registered project runners (1) exceeded']) + expect(execute).to be_success + + expect(runner).to be_an_instance_of(::Ci::Runner) + expect(runner.persisted?).to be_falsey + expect(runner.errors.messages).to eq( + 'runner_projects.base': ['Maximum number of ci registered project runners (1) exceeded'] + ) expect(project.runners.reload.size).to eq(1) end end @@ -135,8 +152,10 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do end it 'creates runner' do - is_expected.to be_an_instance_of(::Ci::Runner) - expect(subject.errors).to be_empty + expect(execute).to be_success + + expect(runner).to be_an_instance_of(::Ci::Runner) + expect(runner.errors).to be_empty expect(project.runners.reload.size).to eq(2) expect(project.runners.recent.size).to eq(1) end @@ -153,15 +172,18 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do end it 'returns 403 error' do - is_expected.to be_nil + expect(execute).to be_error + expect(execute.http_status).to eq :forbidden end end context 'when feature flag is disabled' do it 'registers the runner' do - is_expected.to be_an_instance_of(::Ci::Runner) - expect(subject.errors).to be_empty - expect(subject.active).to be true + expect(execute).to be_success + + expect(runner).to be_an_instance_of(::Ci::Runner) + expect(runner.errors).to be_empty + expect(runner.active).to be true end end end @@ -172,12 +194,14 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do let(:token) { group.runners_token } it 'creates a group runner' do - is_expected.to be_an_instance_of(::Ci::Runner) - expect(subject.errors).to be_empty + expect(execute).to be_success + + expect(runner).to be_an_instance_of(::Ci::Runner) + expect(runner.errors).to be_empty expect(group.runners.reload.size).to eq(1) - expect(subject.token).not_to eq(registration_token) - expect(subject.token).not_to eq(group.runners_token) - expect(subject).to be_group_type + expect(runner.token).not_to eq(registration_token) + expect(runner.token).not_to eq(group.runners_token) + expect(runner).to be_group_type end context 'when it exceeds the application limits' do @@ -187,9 +211,13 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do end it 'does not create runner' do - is_expected.to be_an_instance_of(::Ci::Runner) - expect(subject.persisted?).to be_falsey - expect(subject.errors.messages).to eq('runner_namespaces.base': ['Maximum number of ci registered group runners (1) exceeded']) + expect(execute).to be_success + + expect(runner).to be_an_instance_of(::Ci::Runner) + expect(runner.persisted?).to be_falsey + expect(runner.errors.messages).to eq( + 'runner_namespaces.base': ['Maximum number of ci registered group runners (1) exceeded'] + ) expect(group.runners.reload.size).to eq(1) end end @@ -202,8 +230,10 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do end it 'creates runner' do - is_expected.to be_an_instance_of(::Ci::Runner) - expect(subject.errors).to be_empty + expect(execute).to be_success + + expect(runner).to be_an_instance_of(::Ci::Runner) + expect(runner.errors).to be_empty expect(group.runners.reload.size).to eq(3) expect(group.runners.recent.size).to eq(1) end @@ -219,16 +249,18 @@ RSpec.describe ::Ci::Runners::RegisterRunnerService, '#execute' do stub_feature_flags(runner_registration_control: true) end - it 'returns nil' do - is_expected.to be_nil + it 'returns error response' do + is_expected.to be_error end end context 'when feature flag is disabled' do it 'registers the runner' do - is_expected.to be_an_instance_of(::Ci::Runner) - expect(subject.errors).to be_empty - expect(subject.active).to be true + expect(execute).to be_success + + expect(runner).to be_an_instance_of(::Ci::Runner) + expect(runner.errors).to be_empty + expect(runner.active).to be true end end end diff --git a/spec/services/ci/runners/reset_registration_token_service_spec.rb b/spec/services/ci/runners/reset_registration_token_service_spec.rb index f96838cea98..79059712032 100644 --- a/spec/services/ci/runners/reset_registration_token_service_spec.rb +++ b/spec/services/ci/runners/reset_registration_token_service_spec.rb @@ -15,7 +15,7 @@ RSpec.describe ::Ci::Runners::ResetRegistrationTokenService, '#execute' do it 'does not reset registration token and returns error response' do expect(scope).not_to receive(token_reset_method_name) - is_expected.to be_error + expect(execute).to be_error end end @@ -25,7 +25,7 @@ RSpec.describe ::Ci::Runners::ResetRegistrationTokenService, '#execute' do it 'does not reset registration token and returns error response' do expect(scope).not_to receive(token_reset_method_name) - is_expected.to be_error + expect(execute).to be_error end end @@ -37,7 +37,7 @@ RSpec.describe ::Ci::Runners::ResetRegistrationTokenService, '#execute' do expect(scope).to receive(token_method_name).once.and_return("#{token_method_name} return value") end - is_expected.to be_success + expect(execute).to be_success expect(execute.payload[:new_registration_token]).to eq("#{token_method_name} return value") end end diff --git a/spec/services/ci/runners/unassign_runner_service_spec.rb b/spec/services/ci/runners/unassign_runner_service_spec.rb index 3fb6925f4bd..cf710cf6893 100644 --- a/spec/services/ci/runners/unassign_runner_service_spec.rb +++ b/spec/services/ci/runners/unassign_runner_service_spec.rb @@ -3,21 +3,21 @@ require 'spec_helper' RSpec.describe ::Ci::Runners::UnassignRunnerService, '#execute' do - subject(:service) { described_class.new(runner_project, user).execute } - - let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) } let_it_be(:project) { create(:project) } + let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) } let(:runner_project) { runner.runner_projects.last } + subject(:execute) { described_class.new(runner_project, user).execute } + context 'without user' do let(:user) { nil } it 'does not destroy runner_project', :aggregate_failures do expect(runner_project).not_to receive(:destroy) - expect { service }.not_to change { runner.runner_projects.count }.from(1) + expect { execute }.not_to change { runner.runner_projects.count }.from(1) - is_expected.to eq(false) + is_expected.to be_error end end @@ -27,17 +27,27 @@ RSpec.describe ::Ci::Runners::UnassignRunnerService, '#execute' do it 'does not call destroy on runner_project' do expect(runner).not_to receive(:destroy) - service + is_expected.to be_error end end context 'with admin user', :enable_admin_mode do let(:user) { create_default(:user, :admin) } - it 'destroys runner_project' do - expect(runner_project).to receive(:destroy).once + context 'with destroy returning false' do + it 'returns error response' do + expect(runner_project).to receive(:destroy).once.and_return(false) + + is_expected.to be_error + end + end + + context 'with destroy returning true' do + it 'returns success response' do + expect(runner_project).to receive(:destroy).once.and_return(true) - service + is_expected.to be_success + end end end end diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index 6e074f451c4..0cfde9ef434 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -176,6 +176,15 @@ RSpec.describe Groups::CreateService, '#execute' do end end + describe 'creating a details record' do + let(:service) { described_class.new(user, group_params) } + + it 'create the details record connected to the group' do + group = subject + expect(group.namespace_details).to be_persisted + end + end + describe 'create service for the group' do let(:service) { described_class.new(user, group_params) } let(:created_group) { service.execute } diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb index 0ea82f37db0..3fa7beea97e 100644 --- a/spec/support/shared_examples/features/content_editor_shared_examples.rb +++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb @@ -13,9 +13,8 @@ RSpec.shared_examples 'edits content using the content editor' do expect(page).to have_css('[data-testid="formatting-bubble-menu"]') end - it 'does not show a formatting bubble menu for code' do - find(content_editor_testid).send_keys 'This is a `code`' - find(content_editor_testid).send_keys [:shift, :left] + it 'does not show a formatting bubble menu for code blocks' do + find(content_editor_testid).send_keys '```js ' expect(page).not_to have_css('[data-testid="formatting-bubble-menu"]') end |