Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-08-05 18:12:12 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-05 18:12:12 +0300
commit8ec882085e734458ffe0fff8e2e4b72bc3871419 (patch)
tree6869bb67f3e66e9de828bc47a08577efa1e296c6 /spec
parentf63850d9d6c3a81e78c93995c904ed6c0785ef19 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/ci/runners.rb4
-rw-r--r--spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js21
-rw-r--r--spec/frontend/content_editor/components/wrappers/__snapshots__/table_of_contents_spec.js.snap115
-rw-r--r--spec/frontend/content_editor/components/wrappers/table_of_contents_spec.js84
-rw-r--r--spec/frontend/content_editor/services/table_of_contents_utils_spec.js96
-rw-r--r--spec/frontend_integration/content_editor/content_editor_integration_spec.js31
-rw-r--r--spec/migrations/20220506154054_create_sync_namespace_details_trigger_spec.rb76
-rw-r--r--spec/migrations/20220524184149_create_sync_project_namespace_details_trigger_spec.rb73
-rw-r--r--spec/models/namespace/detail_spec.rb40
-rw-r--r--spec/models/namespace_spec.rb1
-rw-r--r--spec/models/repository_spec.rb14
-rw-r--r--spec/requests/api/ci/runner/runners_post_spec.rb9
-rw-r--r--spec/requests/api/project_import_spec.rb2
-rw-r--r--spec/services/branches/create_service_spec.rb27
-rw-r--r--spec/services/ci/runners/register_runner_service_spec.rb148
-rw-r--r--spec/services/ci/runners/reset_registration_token_service_spec.rb6
-rw-r--r--spec/services/ci/runners/unassign_runner_service_spec.rb28
-rw-r--r--spec/services/groups/create_service_spec.rb9
-rw-r--r--spec/support/shared_examples/features/content_editor_shared_examples.rb5
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