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>2021-05-11 18:10:20 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-11 18:10:20 +0300
commite3042fc5ced749e693ccef81b3f5838c55d5480c (patch)
treee004dca26da0ec413d5c9ebf174962a008fde0bb /spec
parentc33a9adb709ffb40f816e66eb0c98cc750d6cd43 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/groups/group_members_controller_spec.rb15
-rw-r--r--spec/finders/concerns/packages/finder_helper_spec.rb25
-rw-r--r--spec/finders/packages/composer/packages_finder_spec.rb25
-rw-r--r--spec/finders/packages/conan/package_finder_spec.rb3
-rw-r--r--spec/finders/packages/generic/package_finder_spec.rb7
-rw-r--r--spec/finders/packages/go/package_finder_spec.rb13
-rw-r--r--spec/finders/packages/maven/package_finder_spec.rb12
-rw-r--r--spec/finders/packages/npm/package_finder_spec.rb10
-rw-r--r--spec/finders/packages/nuget/package_finder_spec.rb10
-rw-r--r--spec/finders/packages/package_finder_spec.rb12
-rw-r--r--spec/frontend/pipeline_editor/components/drawer/cards/first_pipeline_card_spec.js47
-rw-r--r--spec/frontend/pipeline_editor/components/drawer/cards/getting_started_card_spec.js26
-rw-r--r--spec/frontend/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js51
-rw-r--r--spec/frontend/pipeline_editor/components/drawer/cards/visualize_and_lint_card_spec.js26
-rw-r--r--spec/frontend/pipeline_editor/components/drawer/pipeline_editor_drawer_spec.js25
-rw-r--r--spec/frontend/pipeline_editor/components/drawer/ui/demo_job_pill_spec.js27
-rw-r--r--spec/frontend/pipeline_editor/components/drawer/ui/pipeline_visual_reference_spec.js31
-rw-r--r--spec/frontend/registry/explorer/components/details_page/details_header_spec.js101
-rw-r--r--spec/frontend/registry/explorer/mock_data.js11
-rw-r--r--spec/frontend/registry/explorer/pages/details_spec.js1
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js40
-rw-r--r--spec/helpers/ci/pipeline_editor_helper_spec.rb10
-rw-r--r--spec/lib/banzai/cross_project_reference_spec.rb15
-rw-r--r--spec/lib/banzai/filter/references/abstract_reference_filter_spec.rb80
-rw-r--r--spec/lib/banzai/filter/references/issue_reference_filter_spec.rb18
-rw-r--r--spec/lib/banzai/filter/references/reference_cache_spec.rb143
-rw-r--r--spec/lib/bulk_imports/clients/http_spec.rb102
-rw-r--r--spec/lib/gitlab/background_migration/backfill_namespace_traversal_ids_children_spec.rb21
-rw-r--r--spec/lib/gitlab/background_migration/backfill_namespace_traversal_ids_roots_spec.rb21
-rw-r--r--spec/models/board_group_recent_visit_spec.rb60
-rw-r--r--spec/models/board_project_recent_visit_spec.rb60
-rw-r--r--spec/models/bulk_imports/entity_spec.rb9
-rw-r--r--spec/models/packages/package_spec.rb34
-rw-r--r--spec/services/boards/visits/create_service_spec.rb43
-rw-r--r--spec/services/packages/nuget/search_service_spec.rb12
-rw-r--r--spec/support/shared_examples/finders/packages_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb65
-rw-r--r--spec/support/shared_examples/services/boards/create_service_shared_examples.rb25
-rw-r--r--spec/workers/bulk_import_worker_spec.rb3
-rw-r--r--spec/workers/bulk_imports/export_request_worker_spec.rb30
40 files changed, 908 insertions, 365 deletions
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
index 54b190a220a..b666f73110a 100644
--- a/spec/controllers/groups/group_members_controller_spec.rb
+++ b/spec/controllers/groups/group_members_controller_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe Groups::GroupMembersController do
expect(response).to render_template(:index)
end
- context 'user with owner access' do
+ context 'when user can manage members' do
let_it_be(:invited) { create_list(:group_member, 3, :invited, group: group) }
before do
@@ -71,6 +71,19 @@ RSpec.describe Groups::GroupMembersController do
end
end
+ context 'when user cannot manage members' do
+ before do
+ sign_in(user)
+ end
+
+ it 'does not assign invited members or skip_groups', :aggregate_failures do
+ get :index, params: { group_id: group }
+
+ expect(assigns(:invited_members)).to be_nil
+ expect(assigns(:skip_groups)).to be_nil
+ end
+ end
+
context 'when user has owner access to subgroup' do
let_it_be(:nested_group) { create(:group, parent: group) }
let_it_be(:nested_group_user) { create(:user) }
diff --git a/spec/finders/concerns/packages/finder_helper_spec.rb b/spec/finders/concerns/packages/finder_helper_spec.rb
index c1740ee1796..bad4c482bc6 100644
--- a/spec/finders/concerns/packages/finder_helper_spec.rb
+++ b/spec/finders/concerns/packages/finder_helper_spec.rb
@@ -3,6 +3,30 @@
require 'spec_helper'
RSpec.describe ::Packages::FinderHelper do
+ describe '#packages_for_project' do
+ let_it_be_with_reload(:project1) { create(:project) }
+ let_it_be(:package1) { create(:package, project: project1) }
+ let_it_be(:package2) { create(:package, :error, project: project1) }
+ let_it_be(:project2) { create(:project) }
+ let_it_be(:package3) { create(:package, project: project2) }
+
+ let(:finder_class) do
+ Class.new do
+ include ::Packages::FinderHelper
+
+ def execute(project1)
+ packages_for_project(project1)
+ end
+ end
+ end
+
+ let(:finder) { finder_class.new }
+
+ subject { finder.execute(project1) }
+
+ it { is_expected.to eq [package1]}
+ end
+
describe '#packages_visible_to_user' do
using RSpec::Parameterized::TableSyntax
@@ -12,6 +36,7 @@ RSpec.describe ::Packages::FinderHelper do
let_it_be_with_reload(:subgroup) { create(:group, parent: group) }
let_it_be_with_reload(:project2) { create(:project, namespace: subgroup) }
let_it_be(:package2) { create(:package, project: project2) }
+ let_it_be(:package3) { create(:package, :error, project: project2) }
let(:finder_class) do
Class.new do
diff --git a/spec/finders/packages/composer/packages_finder_spec.rb b/spec/finders/packages/composer/packages_finder_spec.rb
new file mode 100644
index 00000000000..d4328827de3
--- /dev/null
+++ b/spec/finders/packages/composer/packages_finder_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe ::Packages::Composer::PackagesFinder do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+
+ let(:params) { {} }
+
+ describe '#execute' do
+ let_it_be(:composer_package) { create(:composer_package, project: project) }
+ let_it_be(:composer_package2) { create(:composer_package, project: project) }
+ let_it_be(:error_package) { create(:composer_package, :error, project: project) }
+ let_it_be(:composer_package3) { create(:composer_package) }
+
+ subject { described_class.new(user, group, params).execute }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it { is_expected.to match_array([composer_package, composer_package2]) }
+ end
+end
diff --git a/spec/finders/packages/conan/package_finder_spec.rb b/spec/finders/packages/conan/package_finder_spec.rb
index 936a0e5ff4b..b26f8900090 100644
--- a/spec/finders/packages/conan/package_finder_spec.rb
+++ b/spec/finders/packages/conan/package_finder_spec.rb
@@ -11,7 +11,8 @@ RSpec.describe ::Packages::Conan::PackageFinder do
subject { described_class.new(user, query: query).execute }
- context 'packages that are not visible to user' do
+ context 'packages that are not installable' do
+ let!(:conan_package3) { create(:conan_package, :error, project: project) }
let!(:non_visible_project) { create(:project, :private) }
let!(:non_visible_conan_package) { create(:conan_package, project: non_visible_project) }
let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" }
diff --git a/spec/finders/packages/generic/package_finder_spec.rb b/spec/finders/packages/generic/package_finder_spec.rb
index ed34268e7a9..707f943b285 100644
--- a/spec/finders/packages/generic/package_finder_spec.rb
+++ b/spec/finders/packages/generic/package_finder_spec.rb
@@ -23,6 +23,13 @@ RSpec.describe ::Packages::Generic::PackageFinder do
expect(found_package).to eq(package)
end
+ it 'does not find uninstallable packages' do
+ error_package = create(:generic_package, :error, project: project)
+
+ expect { finder.execute!(error_package.name, error_package.version) }
+ .to raise_error(ActiveRecord::RecordNotFound)
+ end
+
it 'raises ActiveRecord::RecordNotFound if package is not found' do
expect { finder.execute!(package.name, '3.1.4') }
.to raise_error(ActiveRecord::RecordNotFound)
diff --git a/spec/finders/packages/go/package_finder_spec.rb b/spec/finders/packages/go/package_finder_spec.rb
index b6fad1e7061..dbcb8255d47 100644
--- a/spec/finders/packages/go/package_finder_spec.rb
+++ b/spec/finders/packages/go/package_finder_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Packages::Go::PackageFinder do
let_it_be(:mod) { create :go_module, project: project }
let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.1' }
- let_it_be(:package) { create :golang_package, project: project, name: mod.name, version: 'v1.0.1' }
+ let_it_be_with_refind(:package) { create :golang_package, project: project, name: mod.name, version: 'v1.0.1' }
let(:finder) { described_class.new(project, mod_name, version_name) }
@@ -54,6 +54,17 @@ RSpec.describe Packages::Go::PackageFinder do
it { is_expected.to eq(package) }
end
+ context 'with an uninstallable package' do
+ let(:mod_name) { mod.name }
+ let(:version_name) { version.name }
+
+ before do
+ package.update_column(:status, 1)
+ end
+
+ it { is_expected.to eq(nil) }
+ end
+
context 'with an invalid name' do
let(:mod_name) { 'foo/bar' }
let(:version_name) { 'baz' }
diff --git a/spec/finders/packages/maven/package_finder_spec.rb b/spec/finders/packages/maven/package_finder_spec.rb
index 9a6bb675248..d5f521ff895 100644
--- a/spec/finders/packages/maven/package_finder_spec.rb
+++ b/spec/finders/packages/maven/package_finder_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe ::Packages::Maven::PackageFinder do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, namespace: group) }
- let_it_be(:package) { create(:maven_package, project: project) }
+ let_it_be_with_refind(:package) { create(:maven_package, project: project) }
let(:param_path) { nil }
let(:param_project) { nil }
@@ -36,6 +36,16 @@ RSpec.describe ::Packages::Maven::PackageFinder do
expect { subject }.to raise_error(ActiveRecord::RecordNotFound)
end
end
+
+ context 'with an uninstallable package' do
+ let(:param_path) { package.maven_metadatum.path }
+
+ before do
+ package.update_column(:status, 1)
+ end
+
+ it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
+ end
end
context 'within the project' do
diff --git a/spec/finders/packages/npm/package_finder_spec.rb b/spec/finders/packages/npm/package_finder_spec.rb
index f021d800f31..a995f3b96c4 100644
--- a/spec/finders/packages/npm/package_finder_spec.rb
+++ b/spec/finders/packages/npm/package_finder_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
RSpec.describe ::Packages::Npm::PackageFinder do
let_it_be_with_reload(:project) { create(:project)}
- let_it_be(:package) { create(:npm_package, project: project) }
+ let_it_be_with_refind(:package) { create(:npm_package, project: project) }
let(:project) { package.project }
let(:package_name) { package.name }
@@ -46,6 +46,14 @@ RSpec.describe ::Packages::Npm::PackageFinder do
it { is_expected.to be_empty }
end
+
+ context 'with an uninstallable package' do
+ before do
+ package.update_column(:status, 1)
+ end
+
+ it { is_expected.to be_empty }
+ end
end
subject { finder.execute }
diff --git a/spec/finders/packages/nuget/package_finder_spec.rb b/spec/finders/packages/nuget/package_finder_spec.rb
index 10b5f6c8ec2..59cca2d06dc 100644
--- a/spec/finders/packages/nuget/package_finder_spec.rb
+++ b/spec/finders/packages/nuget/package_finder_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Packages::Nuget::PackageFinder do
let_it_be(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:project) { create(:project, namespace: subgroup) }
- let_it_be(:package1) { create(:nuget_package, project: project) }
+ let_it_be_with_refind(:package1) { create(:nuget_package, project: project) }
let_it_be(:package2) { create(:nuget_package, name: package1.name, version: '2.0.0', project: project) }
let_it_be(:package3) { create(:nuget_package, name: 'Another.Dummy.Package', project: project) }
let_it_be(:other_package_1) { create(:nuget_package, name: package1.name, version: package1.version) }
@@ -33,6 +33,14 @@ RSpec.describe Packages::Nuget::PackageFinder do
it { is_expected.to be_empty }
end
+ context 'with an uninstallable package' do
+ before do
+ package1.update_column(:status, 1)
+ end
+
+ it { is_expected.to contain_exactly(package2) }
+ end
+
context 'with valid version' do
let(:package_version) { '2.0.0' }
diff --git a/spec/finders/packages/package_finder_spec.rb b/spec/finders/packages/package_finder_spec.rb
index e8c7404a612..6a1d857dad4 100644
--- a/spec/finders/packages/package_finder_spec.rb
+++ b/spec/finders/packages/package_finder_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe ::Packages::PackageFinder do
let_it_be(:project) { create(:project) }
- let_it_be(:maven_package) { create(:maven_package, project: project) }
+ let_it_be_with_refind(:maven_package) { create(:maven_package, project: project) }
describe '#execute' do
let(:package_id) { maven_package.id }
@@ -13,6 +13,16 @@ RSpec.describe ::Packages::PackageFinder do
it { is_expected.to eq(maven_package) }
+ context 'with non-displayable package' do
+ before do
+ maven_package.update_column(:status, 1)
+ end
+
+ it 'raises an exception' do
+ expect { subject }.to raise_exception(ActiveRecord::RecordNotFound)
+ end
+ end
+
context 'processing packages' do
let_it_be(:nuget_package) { create(:nuget_package, project: project, name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) }
let(:package_id) { nuget_package.id }
diff --git a/spec/frontend/pipeline_editor/components/drawer/cards/first_pipeline_card_spec.js b/spec/frontend/pipeline_editor/components/drawer/cards/first_pipeline_card_spec.js
new file mode 100644
index 00000000000..8a4f07c4d88
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/drawer/cards/first_pipeline_card_spec.js
@@ -0,0 +1,47 @@
+import { getByRole } from '@testing-library/dom';
+import { mount } from '@vue/test-utils';
+import FirstPipelineCard from '~/pipeline_editor/components/drawer/cards/first_pipeline_card.vue';
+import PipelineVisualReference from '~/pipeline_editor/components/drawer/ui/pipeline_visual_reference.vue';
+
+describe('First pipeline card', () => {
+ let wrapper;
+
+ const defaultProvide = {
+ ciExamplesHelpPagePath: '/pipelines/examples',
+ runnerHelpPagePath: '/help/runners',
+ };
+
+ const createComponent = () => {
+ wrapper = mount(FirstPipelineCard, {
+ provide: {
+ ...defaultProvide,
+ },
+ });
+ };
+
+ const getLinkByName = (name) => getByRole(wrapper.element, 'link', { name }).href;
+ const findPipelinesLink = () => getLinkByName(/examples and templates/i);
+ const findRunnersLink = () => getLinkByName(/make sure your instance has runners available/i);
+ const findVisualReference = () => wrapper.findComponent(PipelineVisualReference);
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the title', () => {
+ expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.title);
+ });
+
+ it('renders the content', () => {
+ expect(findVisualReference().exists()).toBe(true);
+ });
+
+ it('renders the links', () => {
+ expect(findRunnersLink()).toContain(defaultProvide.runnerHelpPagePath);
+ expect(findPipelinesLink()).toContain(defaultProvide.ciExamplesHelpPagePath);
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/drawer/cards/getting_started_card_spec.js b/spec/frontend/pipeline_editor/components/drawer/cards/getting_started_card_spec.js
new file mode 100644
index 00000000000..c592e959068
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/drawer/cards/getting_started_card_spec.js
@@ -0,0 +1,26 @@
+import { shallowMount } from '@vue/test-utils';
+import GettingStartedCard from '~/pipeline_editor/components/drawer/cards/getting_started_card.vue';
+
+describe('Getting started card', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(GettingStartedCard);
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the title', () => {
+ expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.title);
+ });
+
+ it('renders the content', () => {
+ expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.firstParagraph);
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js b/spec/frontend/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js
new file mode 100644
index 00000000000..3c8821d05a7
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js
@@ -0,0 +1,51 @@
+import { getByRole } from '@testing-library/dom';
+import { mount } from '@vue/test-utils';
+import PipelineConfigReferenceCard from '~/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue';
+
+describe('Pipeline config reference card', () => {
+ let wrapper;
+
+ const defaultProvide = {
+ ciExamplesHelpPagePath: 'help/ci/examples/',
+ ciHelpPagePath: 'help/ci/introduction',
+ needsHelpPagePath: 'help/ci/yaml#needs',
+ ymlHelpPagePath: 'help/ci/yaml',
+ };
+
+ const createComponent = () => {
+ wrapper = mount(PipelineConfigReferenceCard, {
+ provide: {
+ ...defaultProvide,
+ },
+ });
+ };
+
+ const getLinkByName = (name) => getByRole(wrapper.element, 'link', { name }).href;
+ const findCiExamplesLink = () => getLinkByName(/CI\/CD examples and templates/i);
+ const findCiIntroLink = () => getLinkByName(/GitLab CI\/CD concepts/i);
+ const findNeedsLink = () => getLinkByName(/Needs keyword/i);
+ const findYmlSyntaxLink = () => getLinkByName(/.gitlab-ci.yml syntax reference/i);
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the title', () => {
+ expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.title);
+ });
+
+ it('renders the content', () => {
+ expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.firstParagraph);
+ });
+
+ it('renders the links', () => {
+ expect(findCiExamplesLink()).toContain(defaultProvide.ciExamplesHelpPagePath);
+ expect(findCiIntroLink()).toContain(defaultProvide.ciHelpPagePath);
+ expect(findNeedsLink()).toContain(defaultProvide.needsHelpPagePath);
+ expect(findYmlSyntaxLink()).toContain(defaultProvide.ymlHelpPagePath);
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/drawer/cards/visualize_and_lint_card_spec.js b/spec/frontend/pipeline_editor/components/drawer/cards/visualize_and_lint_card_spec.js
new file mode 100644
index 00000000000..bebd2484c1d
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/drawer/cards/visualize_and_lint_card_spec.js
@@ -0,0 +1,26 @@
+import { shallowMount } from '@vue/test-utils';
+import VisualizeAndLintCard from '~/pipeline_editor/components/drawer/cards/getting_started_card.vue';
+
+describe('Visual and Lint card', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(VisualizeAndLintCard);
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the title', () => {
+ expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.title);
+ });
+
+ it('renders the content', () => {
+ expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.firstParagraph);
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/drawer/pipeline_editor_drawer_spec.js b/spec/frontend/pipeline_editor/components/drawer/pipeline_editor_drawer_spec.js
index 587373c99b4..fea7d90de52 100644
--- a/spec/frontend/pipeline_editor/components/drawer/pipeline_editor_drawer_spec.js
+++ b/spec/frontend/pipeline_editor/components/drawer/pipeline_editor_drawer_spec.js
@@ -1,4 +1,9 @@
+import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import FirstPipelineCard from '~/pipeline_editor/components/drawer/cards/first_pipeline_card.vue';
+import GettingStartedCard from '~/pipeline_editor/components/drawer/cards/getting_started_card.vue';
+import PipelineConfigReferenceCard from '~/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue';
+import VisualizeAndLintCard from '~/pipeline_editor/components/drawer/cards/visualize_and_lint_card.vue';
import PipelineEditorDrawer from '~/pipeline_editor/components/drawer/pipeline_editor_drawer.vue';
describe('Pipeline editor drawer', () => {
@@ -8,7 +13,12 @@ describe('Pipeline editor drawer', () => {
wrapper = shallowMount(PipelineEditorDrawer);
};
- const findToggleBtn = () => wrapper.find('[data-testid="toggleBtn"]');
+ const findFirstPipelineCard = () => wrapper.findComponent(FirstPipelineCard);
+ const findGettingStartedCard = () => wrapper.findComponent(GettingStartedCard);
+ const findPipelineConfigReferenceCard = () => wrapper.findComponent(PipelineConfigReferenceCard);
+ const findToggleBtn = () => wrapper.findComponent(GlButton);
+ const findVisualizeAndLintCard = () => wrapper.findComponent(VisualizeAndLintCard);
+
const findArrowIcon = () => wrapper.find('[data-testid="toggle-icon"]');
const findCollapseText = () => wrapper.find('[data-testid="collapse-text"]');
const findDrawerContent = () => wrapper.find('[data-testid="drawer-content"]');
@@ -24,7 +34,7 @@ describe('Pipeline editor drawer', () => {
createComponent();
});
- it('show the left facing arrow icon', () => {
+ it('shows the left facing arrow icon', () => {
expect(findArrowIcon().props('name')).toBe('chevron-double-lg-left');
});
@@ -51,7 +61,7 @@ describe('Pipeline editor drawer', () => {
await clickToggleBtn();
});
- it('show the right facing arrow icon', () => {
+ it('shows the right facing arrow icon', () => {
expect(findArrowIcon().props('name')).toBe('chevron-double-lg-right');
});
@@ -59,10 +69,17 @@ describe('Pipeline editor drawer', () => {
expect(findCollapseText().exists()).toBe(true);
});
- it('show the drawer content', () => {
+ it('shows the drawer content', () => {
expect(findDrawerContent().exists()).toBe(true);
});
+ it('shows all the introduction cards', () => {
+ expect(findFirstPipelineCard().exists()).toBe(true);
+ expect(findGettingStartedCard().exists()).toBe(true);
+ expect(findPipelineConfigReferenceCard().exists()).toBe(true);
+ expect(findVisualizeAndLintCard().exists()).toBe(true);
+ });
+
it('can close the drawer by clicking on the toggle button', async () => {
expect(findDrawerContent().exists()).toBe(true);
diff --git a/spec/frontend/pipeline_editor/components/drawer/ui/demo_job_pill_spec.js b/spec/frontend/pipeline_editor/components/drawer/ui/demo_job_pill_spec.js
new file mode 100644
index 00000000000..edd2b45569a
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/drawer/ui/demo_job_pill_spec.js
@@ -0,0 +1,27 @@
+import { shallowMount } from '@vue/test-utils';
+import DemoJobPill from '~/pipeline_editor/components/drawer/ui/demo_job_pill.vue';
+
+describe('Demo job pill', () => {
+ let wrapper;
+ const jobName = 'my-build-job';
+
+ const createComponent = () => {
+ wrapper = shallowMount(DemoJobPill, {
+ propsData: {
+ jobName,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the jobName', () => {
+ expect(wrapper.text()).toContain(jobName);
+ });
+});
diff --git a/spec/frontend/pipeline_editor/components/drawer/ui/pipeline_visual_reference_spec.js b/spec/frontend/pipeline_editor/components/drawer/ui/pipeline_visual_reference_spec.js
new file mode 100644
index 00000000000..e4834544484
--- /dev/null
+++ b/spec/frontend/pipeline_editor/components/drawer/ui/pipeline_visual_reference_spec.js
@@ -0,0 +1,31 @@
+import { shallowMount } from '@vue/test-utils';
+import DemoJobPill from '~/pipeline_editor/components/drawer/ui/demo_job_pill.vue';
+import PipelineVisualReference from '~/pipeline_editor/components/drawer/ui/pipeline_visual_reference.vue';
+
+describe('Demo job pill', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(PipelineVisualReference);
+ };
+
+ const findAllDemoJobPills = () => wrapper.findAllComponents(DemoJobPill);
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders all stage names', () => {
+ expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.stageNames.build);
+ expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.stageNames.test);
+ expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.stageNames.deploy);
+ });
+
+ it('renders all job pills', () => {
+ expect(findAllDemoJobPills()).toHaveLength(4);
+ });
+});
diff --git a/spec/frontend/registry/explorer/components/details_page/details_header_spec.js b/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
index 4fe44a3307a..632f506f4ae 100644
--- a/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
+++ b/spec/frontend/registry/explorer/components/details_page/details_header_spec.js
@@ -1,7 +1,10 @@
import { GlButton, GlIcon } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
import { useFakeDate } from 'helpers/fake_date';
+import createMockApollo from 'helpers/mock_apollo_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import waitForPromises from 'helpers/wait_for_promises';
import component from '~/registry/explorer/components/details_page/details_header.vue';
import {
UNSCHEDULED_STATUS,
@@ -16,15 +19,18 @@ import {
ROOT_IMAGE_TEXT,
ROOT_IMAGE_TOOLTIP,
} from '~/registry/explorer/constants';
+import getContainerRepositoryTagCountQuery from '~/registry/explorer/graphql/queries/get_container_repository_tags_count.query.graphql';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
+import { imageTagsCountMock } from '../../mock_data';
describe('Details Header', () => {
let wrapper;
+ let apolloProvider;
+ let localVue;
const defaultImage = {
name: 'foo',
updatedAt: '2020-11-03T13:29:21Z',
- tagsCount: 10,
canDelete: true,
project: {
visibility: 'public',
@@ -51,12 +57,31 @@ describe('Details Header', () => {
await wrapper.vm.$nextTick();
};
- const mountComponent = (propsData = { image: defaultImage }) => {
+ const mountComponent = ({
+ propsData = { image: defaultImage },
+ resolver = jest.fn().mockResolvedValue(imageTagsCountMock()),
+ $apollo = undefined,
+ } = {}) => {
+ const mocks = {};
+
+ if ($apollo) {
+ mocks.$apollo = $apollo;
+ } else {
+ localVue = createLocalVue();
+ localVue.use(VueApollo);
+
+ const requestHandlers = [[getContainerRepositoryTagCountQuery, resolver]];
+ apolloProvider = createMockApollo(requestHandlers);
+ }
+
wrapper = shallowMount(component, {
+ localVue,
+ apolloProvider,
propsData,
directives: {
GlTooltip: createMockDirective(),
},
+ mocks,
stubs: {
TitleArea,
},
@@ -64,41 +89,48 @@ describe('Details Header', () => {
};
afterEach(() => {
+ // if we want to mix createMockApollo and manual mocks we need to reset everything
wrapper.destroy();
+ apolloProvider = undefined;
+ localVue = undefined;
wrapper = null;
});
+
describe('image name', () => {
describe('missing image name', () => {
- it('root image ', () => {
- mountComponent({ image: { ...defaultImage, name: '' } });
+ beforeEach(() => {
+ mountComponent({ propsData: { image: { ...defaultImage, name: '' } } });
+
+ return waitForPromises();
+ });
+ it('root image ', () => {
expect(findTitle().text()).toBe(ROOT_IMAGE_TEXT);
});
it('has an icon', () => {
- mountComponent({ image: { ...defaultImage, name: '' } });
-
expect(findInfoIcon().exists()).toBe(true);
expect(findInfoIcon().props('name')).toBe('information-o');
});
it('has a tooltip', () => {
- mountComponent({ image: { ...defaultImage, name: '' } });
-
const tooltip = getBinding(findInfoIcon().element, 'gl-tooltip');
expect(tooltip.value).toBe(ROOT_IMAGE_TOOLTIP);
});
});
describe('with image name present', () => {
- it('shows image.name ', () => {
+ beforeEach(() => {
mountComponent();
+
+ return waitForPromises();
+ });
+
+ it('shows image.name ', () => {
expect(findTitle().text()).toContain('foo');
});
it('has no icon', () => {
- mountComponent();
-
expect(findInfoIcon().exists()).toBe(false);
});
});
@@ -111,12 +143,6 @@ describe('Details Header', () => {
expect(findDeleteButton().exists()).toBe(true);
});
- it('is hidden while loading', () => {
- mountComponent({ image: defaultImage, metadataLoading: true });
-
- expect(findDeleteButton().exists()).toBe(false);
- });
-
it('has the correct text', () => {
mountComponent();
@@ -149,7 +175,7 @@ describe('Details Header', () => {
`(
'when canDelete is $canDelete and disabled is $disabled is $isDisabled that the button is disabled',
({ canDelete, disabled, isDisabled }) => {
- mountComponent({ image: { ...defaultImage, canDelete }, disabled });
+ mountComponent({ propsData: { image: { ...defaultImage, canDelete }, disabled } });
expect(findDeleteButton().props('disabled')).toBe(isDisabled);
},
@@ -158,15 +184,32 @@ describe('Details Header', () => {
describe('metadata items', () => {
describe('tags count', () => {
+ it('displays "-- tags" while loading', async () => {
+ // here we are forced to mock apollo because `waitForMetadataItems` waits
+ // for two ticks, de facto allowing the promise to resolve, so there is
+ // no way to catch the component as both rendered and in loading state
+ mountComponent({ $apollo: { queries: { containerRepository: { loading: true } } } });
+
+ await waitForMetadataItems();
+
+ expect(findTagsCount().props('text')).toBe('-- tags');
+ });
+
it('when there is more than one tag has the correct text', async () => {
mountComponent();
+
+ await waitForPromises();
await waitForMetadataItems();
- expect(findTagsCount().props('text')).toBe('10 tags');
+ expect(findTagsCount().props('text')).toBe('13 tags');
});
it('when there is one tag has the correct text', async () => {
- mountComponent({ image: { ...defaultImage, tagsCount: 1 } });
+ mountComponent({
+ resolver: jest.fn().mockResolvedValue(imageTagsCountMock({ tagsCount: 1 })),
+ });
+
+ await waitForPromises();
await waitForMetadataItems();
expect(findTagsCount().props('text')).toBe('1 tag');
@@ -208,11 +251,13 @@ describe('Details Header', () => {
'when the status is $status the text is $text and the tooltip is $tooltip',
async ({ status, text, tooltip }) => {
mountComponent({
- image: {
- ...defaultImage,
- expirationPolicyCleanupStatus: status,
- project: {
- containerExpirationPolicy: { enabled: true, nextRunAt: '2021-01-03T14:29:21Z' },
+ propsData: {
+ image: {
+ ...defaultImage,
+ expirationPolicyCleanupStatus: status,
+ project: {
+ containerExpirationPolicy: { enabled: true, nextRunAt: '2021-01-03T14:29:21Z' },
+ },
},
},
});
@@ -242,7 +287,9 @@ describe('Details Header', () => {
expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye');
});
it('shows an eye slashed when the project is not public', async () => {
- mountComponent({ image: { ...defaultImage, project: { visibility: 'private' } } });
+ mountComponent({
+ propsData: { image: { ...defaultImage, project: { visibility: 'private' } } },
+ });
await waitForMetadataItems();
expect(findLastUpdatedAndVisibility().props('icon')).toBe('eye-slash');
diff --git a/spec/frontend/registry/explorer/mock_data.js b/spec/frontend/registry/explorer/mock_data.js
index 7d544b71466..fe258dcd4e8 100644
--- a/spec/frontend/registry/explorer/mock_data.js
+++ b/spec/frontend/registry/explorer/mock_data.js
@@ -113,7 +113,6 @@ export const containerRepositoryMock = {
canDelete: true,
createdAt: '2020-11-03T13:29:21Z',
updatedAt: '2020-11-03T13:29:21Z',
- tagsCount: 13,
expirationPolicyStartedAt: null,
expirationPolicyCleanupStatus: 'UNSCHEDULED',
project: {
@@ -175,6 +174,16 @@ export const imageTagsMock = (nodes = tagsMock) => ({
},
});
+export const imageTagsCountMock = (override) => ({
+ data: {
+ containerRepository: {
+ id: containerRepositoryMock.id,
+ tagsCount: 13,
+ ...override,
+ },
+ },
+});
+
export const graphQLImageDetailsMock = (override) => ({
data: {
containerRepository: {
diff --git a/spec/frontend/registry/explorer/pages/details_spec.js b/spec/frontend/registry/explorer/pages/details_spec.js
index eb01fb1a7e6..022f6e71fe6 100644
--- a/spec/frontend/registry/explorer/pages/details_spec.js
+++ b/spec/frontend/registry/explorer/pages/details_spec.js
@@ -292,7 +292,6 @@ describe('Details Page', () => {
await waitForApolloRequestRender();
expect(findDetailsHeader().props()).toMatchObject({
- metadataLoading: false,
image: {
name: containerRepositoryMock.name,
project: {
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js b/spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js
index a5d91468ef2..eb6e3711e2e 100644
--- a/spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_view_button_spec.js
@@ -1,4 +1,5 @@
-import { mount } from '@vue/test-utils';
+import { GlDropdown, GlLink } from '@gitlab/ui';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import DeploymentViewButton from '~/vue_merge_request_widget/components/deployment/deployment_view_button.vue';
import ReviewAppLink from '~/vue_merge_request_widget/components/review_app_link.vue';
import { deploymentMockData } from './deployment_mock_data';
@@ -11,14 +12,14 @@ const appButtonText = {
describe('Deployment View App button', () => {
let wrapper;
- const factory = (options = {}) => {
- wrapper = mount(DeploymentViewButton, {
+ const createComponent = (options = {}) => {
+ wrapper = mountExtended(DeploymentViewButton, {
...options,
});
};
beforeEach(() => {
- factory({
+ createComponent({
propsData: {
deployment: deploymentMockData,
appButtonText,
@@ -30,15 +31,21 @@ describe('Deployment View App button', () => {
wrapper.destroy();
});
+ const findReviewAppLink = () => wrapper.findComponent(ReviewAppLink);
+ const findMrWigdetDeploymentDropdown = () => wrapper.findComponent(GlDropdown);
+ const findMrWigdetDeploymentDropdownIcon = () =>
+ wrapper.findByTestId('mr-wigdet-deployment-dropdown-icon');
+ const findDeployUrlMenuItems = () => wrapper.findAllComponents(GlLink);
+
describe('text', () => {
it('renders text as passed', () => {
- expect(wrapper.find(ReviewAppLink).text()).toContain(appButtonText.text);
+ expect(findReviewAppLink().props().display.text).toBe(appButtonText.text);
});
});
describe('without changes', () => {
beforeEach(() => {
- factory({
+ createComponent({
propsData: {
deployment: { ...deploymentMockData, changes: null },
appButtonText,
@@ -47,13 +54,13 @@ describe('Deployment View App button', () => {
});
it('renders the link to the review app without dropdown', () => {
- expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(false);
+ expect(findMrWigdetDeploymentDropdown().exists()).toBe(false);
});
});
describe('with a single change', () => {
beforeEach(() => {
- factory({
+ createComponent({
propsData: {
deployment: { ...deploymentMockData, changes: [deploymentMockData.changes[0]] },
appButtonText,
@@ -62,21 +69,20 @@ describe('Deployment View App button', () => {
});
it('renders the link to the review app without dropdown', () => {
- expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(false);
+ expect(findMrWigdetDeploymentDropdown().exists()).toBe(false);
+ expect(findMrWigdetDeploymentDropdownIcon().exists()).toBe(false);
});
it('renders the link to the review app linked to to the first change', () => {
const expectedUrl = deploymentMockData.changes[0].external_url;
- const deployUrl = wrapper.find('.js-deploy-url');
- expect(deployUrl.attributes().href).not.toBeNull();
- expect(deployUrl.attributes().href).toEqual(expectedUrl);
+ expect(findReviewAppLink().attributes('href')).toBe(expectedUrl);
});
});
describe('with multiple changes', () => {
beforeEach(() => {
- factory({
+ createComponent({
propsData: {
deployment: deploymentMockData,
appButtonText,
@@ -85,18 +91,18 @@ describe('Deployment View App button', () => {
});
it('renders the link to the review app with dropdown', () => {
- expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(true);
+ expect(findMrWigdetDeploymentDropdown().exists()).toBe(true);
+ expect(findMrWigdetDeploymentDropdownIcon().exists()).toBe(true);
});
it('renders all the links to the review apps', () => {
- const allUrls = wrapper.findAll('.js-deploy-url-menu-item').wrappers;
+ const allUrls = findDeployUrlMenuItems().wrappers;
const expectedUrls = deploymentMockData.changes.map((change) => change.external_url);
expectedUrls.forEach((expectedUrl, idx) => {
const deployUrl = allUrls[idx];
- expect(deployUrl.attributes().href).not.toBeNull();
- expect(deployUrl.attributes().href).toEqual(expectedUrl);
+ expect(deployUrl.attributes('href')).toBe(expectedUrl);
});
});
});
diff --git a/spec/helpers/ci/pipeline_editor_helper_spec.rb b/spec/helpers/ci/pipeline_editor_helper_spec.rb
index e276796f3ec..aacfc3b91c6 100644
--- a/spec/helpers/ci/pipeline_editor_helper_spec.rb
+++ b/spec/helpers/ci/pipeline_editor_helper_spec.rb
@@ -40,16 +40,21 @@ RSpec.describe Ci::PipelineEditorHelper do
it 'returns pipeline editor data' do
expect(pipeline_editor_data).to eq({
"ci-config-path": project.ci_config_path_or_default,
+ "ci-examples-help-page-path" => help_page_path('ci/examples/README'),
+ "ci-help-page-path" => help_page_path('ci/README'),
"commit-sha" => project.commit.sha,
"default-branch" => project.default_branch,
"empty-state-illustration-path" => 'foo',
"initial-branch-name": nil,
"lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'),
+ "needs-help-page-path" => help_page_path('ci/yaml/README', anchor: 'needs'),
"new-merge-request-path" => '/mock/project/-/merge_requests/new',
"pipeline_etag" => graphql_etag_pipeline_sha_path(project.commit.sha),
+ "pipeline-page-path" => project_pipelines_path(project),
"project-path" => project.path,
"project-full-path" => project.full_path,
"project-namespace" => project.namespace.full_path,
+ "runner-help-page-path" => help_page_path('ci/runners/README'),
"yml-help-page-path" => help_page_path('ci/yaml/README')
})
end
@@ -61,16 +66,21 @@ RSpec.describe Ci::PipelineEditorHelper do
it 'returns pipeline editor data' do
expect(pipeline_editor_data).to eq({
"ci-config-path": project.ci_config_path_or_default,
+ "ci-examples-help-page-path" => help_page_path('ci/examples/README'),
+ "ci-help-page-path" => help_page_path('ci/README'),
"commit-sha" => '',
"default-branch" => project.default_branch,
"empty-state-illustration-path" => 'foo',
"initial-branch-name": nil,
"lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'),
+ "needs-help-page-path" => help_page_path('ci/yaml/README', anchor: 'needs'),
"new-merge-request-path" => '/mock/project/-/merge_requests/new',
"pipeline_etag" => '',
+ "pipeline-page-path" => project_pipelines_path(project),
"project-path" => project.path,
"project-full-path" => project.full_path,
"project-namespace" => project.namespace.full_path,
+ "runner-help-page-path" => help_page_path('ci/runners/README'),
"yml-help-page-path" => help_page_path('ci/yaml/README')
})
end
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index 95b78ceb5d5..60ff15a88e0 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -4,10 +4,12 @@ require 'spec_helper'
RSpec.describe Banzai::CrossProjectReference do
let(:including_class) { Class.new.include(described_class).new }
+ let(:reference_cache) { Banzai::Filter::References::ReferenceCache.new(including_class, {})}
before do
allow(including_class).to receive(:context).and_return({})
allow(including_class).to receive(:parent_from_ref).and_call_original
+ allow(including_class).to receive(:reference_cache).and_return(reference_cache)
end
describe '#parent_from_ref' do
@@ -47,5 +49,18 @@ RSpec.describe Banzai::CrossProjectReference do
expect(including_class.parent_from_ref('cross/reference')).to eq project2
end
end
+
+ context 'when reference cache is loaded' do
+ let(:project2) { double('referenced project') }
+
+ before do
+ allow(reference_cache).to receive(:cache_loaded?).and_return(true)
+ allow(reference_cache).to receive(:parent_per_reference).and_return({ 'cross/reference' => project2 })
+ end
+
+ it 'pulls from the reference cache' do
+ expect(including_class.parent_from_ref('cross/reference')).to eq project2
+ end
+ end
end
end
diff --git a/spec/lib/banzai/filter/references/abstract_reference_filter_spec.rb b/spec/lib/banzai/filter/references/abstract_reference_filter_spec.rb
index d10b52bf7d0..3cb3ebc42a6 100644
--- a/spec/lib/banzai/filter/references/abstract_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/abstract_reference_filter_spec.rb
@@ -8,18 +8,6 @@ RSpec.describe Banzai::Filter::References::AbstractReferenceFilter do
let(:doc) { Nokogiri::HTML.fragment('') }
let(:filter) { described_class.new(doc, project: project) }
- describe '#references_per_parent' do
- let(:doc) { Nokogiri::HTML.fragment("#1 #{project.full_path}#2 #2") }
-
- it 'returns a Hash containing references grouped per parent paths' do
- expect(described_class).to receive(:object_class).exactly(6).times.and_return(Issue)
-
- refs = filter.references_per_parent
-
- expect(refs).to match(a_hash_including(project.full_path => contain_exactly(1, 2)))
- end
- end
-
describe '#data_attributes_for' do
let_it_be(:issue) { create(:issue, project: project) }
@@ -32,74 +20,6 @@ RSpec.describe Banzai::Filter::References::AbstractReferenceFilter do
end
end
- describe '#parent_per_reference' do
- it 'returns a Hash containing projects grouped per parent paths' do
- expect(filter).to receive(:references_per_parent)
- .and_return({ project.full_path => Set.new([1]) })
-
- expect(filter.parent_per_reference)
- .to eq({ project.full_path => project })
- end
- end
-
- describe '#find_for_paths' do
- context 'with RequestStore disabled' do
- it 'returns a list of Projects for a list of paths' do
- expect(filter.find_for_paths([project.full_path]))
- .to eq([project])
- end
-
- it "return an empty array for paths that don't exist" do
- expect(filter.find_for_paths(['nonexistent/project']))
- .to eq([])
- end
- end
-
- context 'with RequestStore enabled', :request_store do
- it 'returns a list of Projects for a list of paths' do
- expect(filter.find_for_paths([project.full_path]))
- .to eq([project])
- end
-
- context 'when no project with that path exists' do
- it 'returns no value' do
- expect(filter.find_for_paths(['nonexistent/project']))
- .to eq([])
- end
-
- it 'adds the ref to the project refs cache' do
- project_refs_cache = {}
- allow(filter).to receive(:refs_cache).and_return(project_refs_cache)
-
- filter.find_for_paths(['nonexistent/project'])
-
- expect(project_refs_cache).to eq({ 'nonexistent/project' => nil })
- end
-
- context 'when the project refs cache includes nil values' do
- before do
- # adds { 'nonexistent/project' => nil } to cache
- filter.from_ref_cached('nonexistent/project')
- end
-
- it "return an empty array for paths that don't exist" do
- expect(filter.find_for_paths(['nonexistent/project']))
- .to eq([])
- end
- end
- end
- end
- end
-
- describe '#current_parent_path' do
- it 'returns the path of the current parent' do
- doc = Nokogiri::HTML.fragment('')
- filter = described_class.new(doc, project: project)
-
- expect(filter.current_parent_path).to eq(project.full_path)
- end
- end
-
context 'abstract methods' do
describe '#find_object' do
it 'raises NotImplementedError' do
diff --git a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb
index 0e1cb1ade74..88c2494b243 100644
--- a/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/issue_reference_filter_spec.rb
@@ -470,24 +470,6 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter do
end
end
- describe '#records_per_parent' do
- context 'using an internal issue tracker' do
- it 'returns a Hash containing the issues per project' do
- doc = Nokogiri::HTML.fragment('')
- filter = described_class.new(doc, project: project)
-
- expect(filter).to receive(:parent_per_reference)
- .and_return({ project.full_path => project })
-
- expect(filter).to receive(:references_per_parent)
- .and_return({ project.full_path => Set.new([issue.iid]) })
-
- expect(filter.records_per_parent)
- .to eq({ project => { issue.iid => issue } })
- end
- end
- end
-
describe '.references_in' do
let(:merge_request) { create(:merge_request) }
diff --git a/spec/lib/banzai/filter/references/reference_cache_spec.rb b/spec/lib/banzai/filter/references/reference_cache_spec.rb
new file mode 100644
index 00000000000..2e37e34bba5
--- /dev/null
+++ b/spec/lib/banzai/filter/references/reference_cache_spec.rb
@@ -0,0 +1,143 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Filter::References::ReferenceCache do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:project2) { create(:project) }
+ let_it_be(:issue1) { create(:issue, project: project) }
+ let_it_be(:issue2) { create(:issue, project: project) }
+ let_it_be(:issue3) { create(:issue, project: project2) }
+ let_it_be(:doc) { Nokogiri::HTML.fragment("#{issue1.to_reference} #{issue2.to_reference} #{issue3.to_reference(full: true)}") }
+
+ let(:filter_class) { Banzai::Filter::References::IssueReferenceFilter }
+ let(:filter) { filter_class.new(doc, project: project) }
+ let(:cache) { described_class.new(filter, { project: project }) }
+
+ describe '#load_references_per_parent' do
+ it 'loads references grouped per parent paths' do
+ cache.load_references_per_parent(filter.nodes)
+
+ expect(cache.references_per_parent).to eq({ project.full_path => [issue1.iid, issue2.iid].to_set,
+ project2.full_path => [issue3.iid].to_set })
+ end
+ end
+
+ describe '#load_parent_per_reference' do
+ it 'returns a Hash containing projects grouped per parent paths' do
+ cache.load_references_per_parent(filter.nodes)
+ cache.load_parent_per_reference
+
+ expect(cache.parent_per_reference).to match({ project.full_path => project, project2.full_path => project2 })
+ end
+ end
+
+ describe '#load_records_per_parent' do
+ it 'returns a Hash containing projects grouped per parent paths' do
+ cache.load_references_per_parent(filter.nodes)
+ cache.load_parent_per_reference
+ cache.load_records_per_parent
+
+ expect(cache.records_per_parent).to match({ project => { issue1.iid => issue1, issue2.iid => issue2 },
+ project2 => { issue3.iid => issue3 } })
+ end
+ end
+
+ describe '#initialize_reference_cache' do
+ it 'does not have an N+1 query problem with cross projects' do
+ doc_single = Nokogiri::HTML.fragment("#1")
+ filter_single = filter_class.new(doc_single, project: project)
+ cache_single = described_class.new(filter_single, { project: project })
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ cache_single.load_references_per_parent(filter_single.nodes)
+ cache_single.load_parent_per_reference
+ cache_single.load_records_per_parent
+ end.count
+
+ # Since this is an issue filter that is not batching issue queries
+ # across projects, we have to account for that.
+ # 1 for both projects, 1 for issues in each project == 3
+ max_count = control_count + 1
+
+ expect do
+ cache.load_references_per_parent(filter.nodes)
+ cache.load_parent_per_reference
+ cache.load_records_per_parent
+ end.not_to exceed_query_limit(max_count)
+ end
+ end
+
+ describe '#find_for_paths' do
+ context 'with RequestStore disabled' do
+ it 'returns a list of Projects for a list of paths' do
+ expect(cache.find_for_paths([project.full_path]))
+ .to eq([project])
+ end
+
+ it 'return an empty array for paths that do not exist' do
+ expect(cache.find_for_paths(['nonexistent/project']))
+ .to eq([])
+ end
+ end
+
+ context 'with RequestStore enabled', :request_store do
+ it 'returns a list of Projects for a list of paths' do
+ expect(cache.find_for_paths([project.full_path]))
+ .to eq([project])
+ end
+
+ context 'when no project with that path exists' do
+ it 'returns no value' do
+ expect(cache.find_for_paths(['nonexistent/project']))
+ .to eq([])
+ end
+
+ it 'adds the ref to the project refs cache' do
+ project_refs_cache = {}
+ allow(cache).to receive(:refs_cache).and_return(project_refs_cache)
+
+ cache.find_for_paths(['nonexistent/project'])
+
+ expect(project_refs_cache).to eq({ 'nonexistent/project' => nil })
+ end
+ end
+ end
+ end
+
+ describe '#current_parent_path' do
+ it 'returns the path of the current parent' do
+ expect(cache.current_parent_path).to eq project.full_path
+ end
+ end
+
+ describe '#current_project_namespace_path' do
+ it 'returns the path of the current project namespace' do
+ expect(cache.current_project_namespace_path).to eq project.namespace.full_path
+ end
+ end
+
+ describe '#full_project_path' do
+ it 'returns current parent path when no ref specified' do
+ expect(cache.full_project_path('something', nil)).to eq cache.current_parent_path
+ end
+
+ it 'returns combined namespace and project ref' do
+ expect(cache.full_project_path('something', 'cool')).to eq 'something/cool'
+ end
+
+ it 'returns uses default namespace and project ref when namespace nil' do
+ expect(cache.full_project_path(nil, 'cool')).to eq "#{project.namespace.full_path}/cool"
+ end
+ end
+
+ describe '#full_group_path' do
+ it 'returns current parent path when no group ref specified' do
+ expect(cache.full_group_path(nil)).to eq cache.current_parent_path
+ end
+
+ it 'returns group ref' do
+ expect(cache.full_group_path('cool_group')).to eq 'cool_group'
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/clients/http_spec.rb b/spec/lib/bulk_imports/clients/http_spec.rb
index 2d841b7fac2..213fa23675e 100644
--- a/spec/lib/bulk_imports/clients/http_spec.rb
+++ b/spec/lib/bulk_imports/clients/http_spec.rb
@@ -8,66 +8,23 @@ RSpec.describe BulkImports::Clients::Http do
let(:uri) { 'http://gitlab.example' }
let(:token) { 'token' }
let(:resource) { 'resource' }
+ let(:response_double) { double(code: 200, success?: true, parsed_response: {}) }
subject { described_class.new(uri: uri, token: token) }
- describe '#get' do
- let(:response_double) { double(code: 200, success?: true, parsed_response: {}) }
-
- shared_examples 'performs network request' do
- it 'performs network request' do
- expect(Gitlab::HTTP).to receive(:get).with(*expected_args).and_return(response_double)
-
- subject.get(resource)
- end
- end
-
- describe 'request query' do
- include_examples 'performs network request' do
- let(:expected_args) do
- [
- anything,
- hash_including(
- query: {
- page: described_class::DEFAULT_PAGE,
- per_page: described_class::DEFAULT_PER_PAGE
- }
- )
- ]
- end
- end
- end
-
- describe 'request headers' do
- include_examples 'performs network request' do
- let(:expected_args) do
- [
- anything,
- hash_including(
- headers: {
- 'Content-Type' => 'application/json',
- 'Authorization' => "Bearer #{token}"
- }
- )
- ]
- end
- end
- end
+ shared_examples 'performs network request' do
+ it 'performs network request' do
+ expect(Gitlab::HTTP).to receive(method).with(*expected_args).and_return(response_double)
- describe 'request uri' do
- include_examples 'performs network request' do
- let(:expected_args) do
- ['http://gitlab.example:80/api/v4/resource', anything]
- end
- end
+ subject.public_send(method, resource)
end
context 'error handling' do
context 'when error occurred' do
it 'raises ConnectionError' do
- allow(Gitlab::HTTP).to receive(:get).and_raise(Errno::ECONNREFUSED)
+ allow(Gitlab::HTTP).to receive(method).and_raise(Errno::ECONNREFUSED)
- expect { subject.get(resource) }.to raise_exception(described_class::ConnectionError)
+ expect { subject.public_send(method, resource) }.to raise_exception(described_class::ConnectionError)
end
end
@@ -75,12 +32,34 @@ RSpec.describe BulkImports::Clients::Http do
it 'raises ConnectionError' do
response_double = double(code: 503, success?: false)
- allow(Gitlab::HTTP).to receive(:get).and_return(response_double)
+ allow(Gitlab::HTTP).to receive(method).and_return(response_double)
- expect { subject.get(resource) }.to raise_exception(described_class::ConnectionError)
+ expect { subject.public_send(method, resource) }.to raise_exception(described_class::ConnectionError)
end
end
end
+ end
+
+ describe '#get' do
+ let(:method) { :get }
+
+ include_examples 'performs network request' do
+ let(:expected_args) do
+ [
+ 'http://gitlab.example:80/api/v4/resource',
+ hash_including(
+ query: {
+ page: described_class::DEFAULT_PAGE,
+ per_page: described_class::DEFAULT_PER_PAGE
+ },
+ headers: {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Bearer #{token}"
+ }
+ )
+ ]
+ end
+ end
describe '#each_page' do
let(:objects1) { [{ object: 1 }, { object: 2 }] }
@@ -129,4 +108,23 @@ RSpec.describe BulkImports::Clients::Http do
end
end
end
+
+ describe '#post' do
+ let(:method) { :post }
+
+ include_examples 'performs network request' do
+ let(:expected_args) do
+ [
+ 'http://gitlab.example:80/api/v4/resource',
+ hash_including(
+ body: {},
+ headers: {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Bearer #{token}"
+ }
+ )
+ ]
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/background_migration/backfill_namespace_traversal_ids_children_spec.rb b/spec/lib/gitlab/background_migration/backfill_namespace_traversal_ids_children_spec.rb
new file mode 100644
index 00000000000..35928deff82
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_namespace_traversal_ids_children_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillNamespaceTraversalIdsChildren, :migration, schema: 20210506065000 do
+ let(:namespaces_table) { table(:namespaces) }
+
+ let!(:user_namespace) { namespaces_table.create!(id: 1, name: 'user', path: 'user', type: nil) }
+ let!(:root_group) { namespaces_table.create!(id: 2, name: 'group', path: 'group', type: 'Group', parent_id: nil) }
+ let!(:sub_group) { namespaces_table.create!(id: 3, name: 'subgroup', path: 'subgroup', type: 'Group', parent_id: 2) }
+
+ describe '#perform' do
+ it 'backfills traversal_ids for child namespaces' do
+ described_class.new.perform(1, 3, 5)
+
+ expect(user_namespace.reload.traversal_ids).to eq([])
+ expect(root_group.reload.traversal_ids).to eq([])
+ expect(sub_group.reload.traversal_ids).to eq([root_group.id, sub_group.id])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_namespace_traversal_ids_roots_spec.rb b/spec/lib/gitlab/background_migration/backfill_namespace_traversal_ids_roots_spec.rb
new file mode 100644
index 00000000000..96e43275972
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_namespace_traversal_ids_roots_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillNamespaceTraversalIdsRoots, :migration, schema: 20210506065000 do
+ let(:namespaces_table) { table(:namespaces) }
+
+ let!(:user_namespace) { namespaces_table.create!(id: 1, name: 'user', path: 'user', type: nil) }
+ let!(:root_group) { namespaces_table.create!(id: 2, name: 'group', path: 'group', type: 'Group', parent_id: nil) }
+ let!(:sub_group) { namespaces_table.create!(id: 3, name: 'subgroup', path: 'subgroup', type: 'Group', parent_id: 2) }
+
+ describe '#perform' do
+ it 'backfills traversal_ids for root namespaces' do
+ described_class.new.perform(1, 3, 5)
+
+ expect(user_namespace.reload.traversal_ids).to eq([user_namespace.id])
+ expect(root_group.reload.traversal_ids).to eq([root_group.id])
+ expect(sub_group.reload.traversal_ids).to eq([])
+ end
+ end
+end
diff --git a/spec/models/board_group_recent_visit_spec.rb b/spec/models/board_group_recent_visit_spec.rb
index c6fbd263072..d2d287d8e24 100644
--- a/spec/models/board_group_recent_visit_spec.rb
+++ b/spec/models/board_group_recent_visit_spec.rb
@@ -3,9 +3,8 @@
require 'spec_helper'
RSpec.describe BoardGroupRecentVisit do
- let(:user) { create(:user) }
- let(:group) { create(:group) }
- let(:board) { create(:board, group: group) }
+ let_it_be(:board_parent) { create(:group) }
+ let_it_be(:board) { create(:board, group: board_parent) }
describe 'relationships' do
it { is_expected.to belong_to(:user) }
@@ -19,56 +18,9 @@ RSpec.describe BoardGroupRecentVisit do
it { is_expected.to validate_presence_of(:board) }
end
- describe '#visited' do
- it 'creates a visit if one does not exists' do
- expect { described_class.visited!(user, board) }.to change(described_class, :count).by(1)
- end
-
- shared_examples 'was visited previously' do
- let!(:visit) { create :board_group_recent_visit, group: board.group, board: board, user: user, updated_at: 7.days.ago }
-
- it 'updates the timestamp' do
- freeze_time do
- described_class.visited!(user, board)
-
- expect(described_class.count).to eq 1
- expect(described_class.first.updated_at).to be_like_time(Time.zone.now)
- end
- end
- end
-
- it_behaves_like 'was visited previously'
-
- context 'when we try to create a visit that is not unique' do
- before do
- expect(described_class).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique, 'record not unique')
- expect(described_class).to receive(:find_or_create_by).and_return(visit)
- end
-
- it_behaves_like 'was visited previously'
- end
- end
-
- describe '#latest' do
- def create_visit(time)
- create :board_group_recent_visit, group: group, user: user, updated_at: time
- end
-
- it 'returns the most recent visited' do
- create_visit(7.days.ago)
- create_visit(5.days.ago)
- recent = create_visit(1.day.ago)
-
- expect(described_class.latest(user, group)).to eq recent
- end
-
- it 'returns last 3 visited boards' do
- create_visit(7.days.ago)
- visit1 = create_visit(3.days.ago)
- visit2 = create_visit(2.days.ago)
- visit3 = create_visit(5.days.ago)
-
- expect(described_class.latest(user, group, count: 3)).to eq([visit2, visit1, visit3])
- end
+ it_behaves_like 'boards recent visit' do
+ let_it_be(:board_relation) { :board }
+ let_it_be(:board_parent_relation) { :group }
+ let_it_be(:visit_relation) { :board_group_recent_visit }
end
end
diff --git a/spec/models/board_project_recent_visit_spec.rb b/spec/models/board_project_recent_visit_spec.rb
index 145a4f5b1a7..262c3a8faaa 100644
--- a/spec/models/board_project_recent_visit_spec.rb
+++ b/spec/models/board_project_recent_visit_spec.rb
@@ -3,9 +3,8 @@
require 'spec_helper'
RSpec.describe BoardProjectRecentVisit do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
- let(:board) { create(:board, project: project) }
+ let_it_be(:board_parent) { create(:project) }
+ let_it_be(:board) { create(:board, project: board_parent) }
describe 'relationships' do
it { is_expected.to belong_to(:user) }
@@ -19,56 +18,9 @@ RSpec.describe BoardProjectRecentVisit do
it { is_expected.to validate_presence_of(:board) }
end
- describe '#visited' do
- it 'creates a visit if one does not exists' do
- expect { described_class.visited!(user, board) }.to change(described_class, :count).by(1)
- end
-
- shared_examples 'was visited previously' do
- let!(:visit) { create :board_project_recent_visit, project: board.project, board: board, user: user, updated_at: 7.days.ago }
-
- it 'updates the timestamp' do
- freeze_time do
- described_class.visited!(user, board)
-
- expect(described_class.count).to eq 1
- expect(described_class.first.updated_at).to be_like_time(Time.zone.now)
- end
- end
- end
-
- it_behaves_like 'was visited previously'
-
- context 'when we try to create a visit that is not unique' do
- before do
- expect(described_class).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique, 'record not unique')
- expect(described_class).to receive(:find_or_create_by).and_return(visit)
- end
-
- it_behaves_like 'was visited previously'
- end
- end
-
- describe '#latest' do
- def create_visit(time)
- create :board_project_recent_visit, project: project, user: user, updated_at: time
- end
-
- it 'returns the most recent visited' do
- create_visit(7.days.ago)
- create_visit(5.days.ago)
- recent = create_visit(1.day.ago)
-
- expect(described_class.latest(user, project)).to eq recent
- end
-
- it 'returns last 3 visited boards' do
- create_visit(7.days.ago)
- visit1 = create_visit(3.days.ago)
- visit2 = create_visit(2.days.ago)
- visit3 = create_visit(5.days.ago)
-
- expect(described_class.latest(user, project, count: 3)).to eq([visit2, visit1, visit3])
- end
+ it_behaves_like 'boards recent visit' do
+ let_it_be(:board_relation) { :board }
+ let_it_be(:board_parent_relation) { :project }
+ let_it_be(:visit_relation) { :board_project_recent_visit }
end
end
diff --git a/spec/models/bulk_imports/entity_spec.rb b/spec/models/bulk_imports/entity_spec.rb
index 652ea431696..d1b7125a6e6 100644
--- a/spec/models/bulk_imports/entity_spec.rb
+++ b/spec/models/bulk_imports/entity_spec.rb
@@ -125,4 +125,13 @@ RSpec.describe BulkImports::Entity, type: :model do
end
end
end
+
+ describe '#encoded_source_full_path' do
+ it 'encodes entity source full path' do
+ expected = 'foo%2Fbar'
+ entity = build(:bulk_import_entity, source_full_path: 'foo/bar')
+
+ expect(entity.encoded_source_full_path).to eq(expected)
+ end
+ end
end
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
index 5d5351eb9fe..ffdb9fc988c 100644
--- a/spec/models/packages/package_spec.rb
+++ b/spec/models/packages/package_spec.rb
@@ -660,27 +660,37 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.to match_array([pypi_package]) }
end
- describe '.displayable' do
+ context 'status scopes' do
let_it_be(:hidden_package) { create(:maven_package, :hidden) }
let_it_be(:processing_package) { create(:maven_package, :processing) }
let_it_be(:error_package) { create(:maven_package, :error) }
- subject { described_class.displayable }
+ describe '.displayable' do
+ subject { described_class.displayable }
- it 'does not include non-displayable packages', :aggregate_failures do
- is_expected.to include(error_package)
- is_expected.not_to include(hidden_package)
- is_expected.not_to include(processing_package)
+ it 'does not include non-displayable packages', :aggregate_failures do
+ is_expected.to include(error_package)
+ is_expected.not_to include(hidden_package)
+ is_expected.not_to include(processing_package)
+ end
end
- end
- describe '.with_status' do
- let_it_be(:hidden_package) { create(:maven_package, :hidden) }
+ describe '.installable' do
+ subject { described_class.installable }
- subject { described_class.with_status(:hidden) }
+ it 'does not include non-displayable packages', :aggregate_failures do
+ is_expected.not_to include(error_package)
+ is_expected.not_to include(hidden_package)
+ is_expected.not_to include(processing_package)
+ end
+ end
+
+ describe '.with_status' do
+ subject { described_class.with_status(:hidden) }
- it 'returns packages with specified status' do
- is_expected.to match_array([hidden_package])
+ it 'returns packages with specified status' do
+ is_expected.to match_array([hidden_package])
+ end
end
end
end
diff --git a/spec/services/boards/visits/create_service_spec.rb b/spec/services/boards/visits/create_service_spec.rb
index 64faa2cf07b..8910345d170 100644
--- a/spec/services/boards/visits/create_service_spec.rb
+++ b/spec/services/boards/visits/create_service_spec.rb
@@ -7,47 +7,20 @@ RSpec.describe Boards::Visits::CreateService do
let(:user) { create(:user) }
context 'when a project board' do
- let(:project) { create(:project) }
- let(:project_board) { create(:board, project: project) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:board) { create(:board, project: project) }
- subject(:service) { described_class.new(project_board.resource_parent, user) }
+ let_it_be(:model) { BoardProjectRecentVisit }
- it 'returns nil when there is no user' do
- service.current_user = nil
-
- expect(service.execute(project_board)).to eq nil
- end
-
- it 'returns nil when database is read-only' do
- allow(Gitlab::Database).to receive(:read_only?) { true }
-
- expect(service.execute(project_board)).to eq nil
- end
-
- it 'records the visit' do
- expect(BoardProjectRecentVisit).to receive(:visited!).once
-
- service.execute(project_board)
- end
+ it_behaves_like 'boards recent visit create service'
end
context 'when a group board' do
- let(:group) { create(:group) }
- let(:group_board) { create(:board, group: group) }
-
- subject(:service) { described_class.new(group_board.resource_parent, user) }
-
- it 'returns nil when there is no user' do
- service.current_user = nil
-
- expect(service.execute(group_board)).to eq nil
- end
-
- it 'records the visit' do
- expect(BoardGroupRecentVisit).to receive(:visited!).once
+ let_it_be(:group) { create(:group) }
+ let_it_be(:board) { create(:board, group: group) }
+ let_it_be(:model) { BoardGroupRecentVisit }
- service.execute(group_board)
- end
+ it_behaves_like 'boards recent visit create service'
end
end
end
diff --git a/spec/services/packages/nuget/search_service_spec.rb b/spec/services/packages/nuget/search_service_spec.rb
index db758dc6672..1838065c5be 100644
--- a/spec/services/packages/nuget/search_service_spec.rb
+++ b/spec/services/packages/nuget/search_service_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Packages::Nuget::SearchService do
let_it_be(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:project) { create(:project, namespace: subgroup) }
- let_it_be(:package_a) { create(:nuget_package, project: project, name: 'DummyPackageA') }
+ let_it_be_with_refind(:package_a) { create(:nuget_package, project: project, name: 'DummyPackageA') }
let_it_be(:packages_b) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageB') }
let_it_be(:packages_c) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageC') }
let_it_be(:package_d) { create(:nuget_package, project: project, name: 'FooBarD') }
@@ -79,6 +79,16 @@ RSpec.describe Packages::Nuget::SearchService do
it { expect_search_results 4, package_a, packages_b, packages_c, package_d }
end
+ context 'with non-displayable packages' do
+ let(:search_term) { '' }
+
+ before do
+ package_a.update_column(:status, 1)
+ end
+
+ it { expect_search_results 3, packages_b, packages_c, package_d }
+ end
+
context 'with prefix search term' do
let(:search_term) { 'dummy' }
diff --git a/spec/support/shared_examples/finders/packages_shared_examples.rb b/spec/support/shared_examples/finders/packages_shared_examples.rb
index 2d4e8d0df1f..b3ec2336cca 100644
--- a/spec/support/shared_examples/finders/packages_shared_examples.rb
+++ b/spec/support/shared_examples/finders/packages_shared_examples.rb
@@ -20,9 +20,11 @@ end
RSpec.shared_examples 'concerning package statuses' do
let_it_be(:hidden_package) { create(:maven_package, :hidden, project: project) }
+ let_it_be(:error_package) { create(:maven_package, :error, project: project) }
- context 'hidden packages' do
+ context 'displayable packages' do
it { is_expected.not_to include(hidden_package) }
+ it { is_expected.to include(error_package) }
end
context 'with status param' do
diff --git a/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb b/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb
new file mode 100644
index 00000000000..68ea460dabc
--- /dev/null
+++ b/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'boards recent visit' do
+ let_it_be(:user) { create(:user) }
+
+ describe '#visited' do
+ it 'creates a visit if one does not exists' do
+ expect { described_class.visited!(user, board) }.to change(described_class, :count).by(1)
+ end
+
+ shared_examples 'was visited previously' do
+ let_it_be(:visit) do
+ create(visit_relation,
+ board_parent_relation => board_parent,
+ board_relation => board,
+ user: user,
+ updated_at: 7.days.ago
+ )
+ end
+
+ it 'updates the timestamp' do
+ freeze_time do
+ described_class.visited!(user, board)
+
+ expect(described_class.count).to eq 1
+ expect(described_class.first.updated_at).to be_like_time(Time.zone.now)
+ end
+ end
+ end
+
+ it_behaves_like 'was visited previously'
+
+ context 'when we try to create a visit that is not unique' do
+ before do
+ expect(described_class).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique, 'record not unique')
+ expect(described_class).to receive(:find_or_create_by).and_return(visit)
+ end
+
+ it_behaves_like 'was visited previously'
+ end
+ end
+
+ describe '#latest' do
+ def create_visit(time)
+ create(visit_relation, board_parent_relation => board_parent, user: user, updated_at: time)
+ end
+
+ it 'returns the most recent visited' do
+ create_visit(7.days.ago)
+ create_visit(5.days.ago)
+ recent = create_visit(1.day.ago)
+
+ expect(described_class.latest(user, board_parent)).to eq recent
+ end
+
+ it 'returns last 3 visited boards' do
+ create_visit(7.days.ago)
+ visit1 = create_visit(3.days.ago)
+ visit2 = create_visit(2.days.ago)
+ visit3 = create_visit(5.days.ago)
+
+ expect(described_class.latest(user, board_parent, count: 3)).to eq([visit2, visit1, visit3])
+ end
+ end
+end
diff --git a/spec/support/shared_examples/services/boards/create_service_shared_examples.rb b/spec/support/shared_examples/services/boards/create_service_shared_examples.rb
new file mode 100644
index 00000000000..63b5e3a5a84
--- /dev/null
+++ b/spec/support/shared_examples/services/boards/create_service_shared_examples.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'boards recent visit create service' do
+ let_it_be(:user) { create(:user) }
+
+ subject(:service) { described_class.new(board.resource_parent, user) }
+
+ it 'returns nil when there is no user' do
+ service.current_user = nil
+
+ expect(service.execute(board)).to be_nil
+ end
+
+ it 'returns nil when database is read only' do
+ allow(Gitlab::Database).to receive(:read_only?) { true }
+
+ expect(service.execute(board)).to be_nil
+ end
+
+ it 'records the visit' do
+ expect(model).to receive(:visited!).once
+
+ service.execute(board)
+ end
+end
diff --git a/spec/workers/bulk_import_worker_spec.rb b/spec/workers/bulk_import_worker_spec.rb
index 5964ec45563..9119394f250 100644
--- a/spec/workers/bulk_import_worker_spec.rb
+++ b/spec/workers/bulk_import_worker_spec.rb
@@ -69,7 +69,7 @@ RSpec.describe BulkImportWorker do
end
context 'when there are created entities to process' do
- it 'marks a batch of entities as started, enqueues BulkImports::EntityWorker and reenqueues' do
+ it 'marks a batch of entities as started, enqueues EntityWorker, ExportRequestWorker and reenqueues' do
stub_const("#{described_class}::DEFAULT_BATCH_SIZE", 1)
bulk_import = create(:bulk_import, :created)
@@ -78,6 +78,7 @@ RSpec.describe BulkImportWorker do
expect(described_class).to receive(:perform_in).with(described_class::PERFORM_DELAY, bulk_import.id)
expect(BulkImports::EntityWorker).to receive(:perform_async)
+ expect(BulkImports::ExportRequestWorker).to receive(:perform_async)
subject.perform(bulk_import.id)
diff --git a/spec/workers/bulk_imports/export_request_worker_spec.rb b/spec/workers/bulk_imports/export_request_worker_spec.rb
new file mode 100644
index 00000000000..f7838279212
--- /dev/null
+++ b/spec/workers/bulk_imports/export_request_worker_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::ExportRequestWorker do
+ let_it_be(:bulk_import) { create(:bulk_import) }
+ let_it_be(:config) { create(:bulk_import_configuration, bulk_import: bulk_import) }
+ let_it_be(:entity) { create(:bulk_import_entity, source_full_path: 'foo/bar', bulk_import: bulk_import) }
+
+ let(:response_double) { double(code: 200, success?: true, parsed_response: {}) }
+ let(:job_args) { [entity.id] }
+
+ describe '#perform' do
+ before do
+ allow(Gitlab::HTTP).to receive(:post).and_return(response_double)
+ end
+
+ include_examples 'an idempotent worker' do
+ it 'requests relations export' do
+ expected = "/groups/foo%2Fbar/export_relations"
+
+ expect_next_instance_of(BulkImports::Clients::Http) do |client|
+ expect(client).to receive(:post).with(expected).twice
+ end
+
+ perform_multiple(job_args)
+ end
+ end
+ end
+end