summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-11-10 18:12:35 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-11-10 18:12:35 +0000
commit6fd750c19206412cfc52b49a70b56147d839c52f (patch)
tree04607e6c9864c09dd312d6bfc3efe9cc5f81c762 /spec
parent26881dd926cfac47c9603d44e8d5a504ab8c4a14 (diff)
downloadgitlab-ce-6fd750c19206412cfc52b49a70b56147d839c52f.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb6
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb21
-rw-r--r--spec/controllers/registrations_controller_spec.rb5
-rw-r--r--spec/factories/namespaces/project_namespaces.rb2
-rw-r--r--spec/factories/packages/npm/metadata.rb18
-rw-r--r--spec/features/signed_commits_spec.rb15
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/packages/npm_package_version.json12
-rw-r--r--spec/fixtures/packages/npm/payload.json3
-rw-r--r--spec/fixtures/packages/npm/payload_with_duplicated_packages.json3
-rw-r--r--spec/frontend/__helpers__/experimentation_helper.js29
-rw-r--r--spec/frontend/boards/components/new_board_button_spec.js10
-rw-r--r--spec/frontend/diffs/components/diff_file_header_spec.js10
-rw-r--r--spec/frontend/experimentation/utils_spec.js171
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/app_spec.js165
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js50
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js8
-rw-r--r--spec/frontend/vue_mr_widget/stores/get_state_key_spec.js8
-rw-r--r--spec/graphql/mutations/merge_requests/set_wip_spec.rb55
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb2
-rw-r--r--spec/graphql/types/mutation_type_spec.rb8
-rw-r--r--spec/lib/bulk_imports/ndjson_pipeline_spec.rb3
-rw-r--r--spec/lib/bulk_imports/projects/stage_spec.rb3
-rw-r--r--spec/lib/gitlab/application_rate_limiter_spec.rb141
-rw-r--r--spec/lib/gitlab/background_migration/populate_personal_snippet_statistics_spec.rb4
-rw-r--r--spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb4
-rw-r--r--spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb3
-rw-r--r--spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb3
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb69
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb11
-rw-r--r--spec/models/concerns/loaded_in_group_list_spec.rb2
-rw-r--r--spec/models/group_spec.rb2
-rw-r--r--spec/models/namespace_spec.rb15
-rw-r--r--spec/models/namespaces/project_namespace_spec.rb2
-rw-r--r--spec/models/packages/npm/metadatum_spec.rb50
-rw-r--r--spec/models/packages/package_spec.rb1
-rw-r--r--spec/models/project_spec.rb68
-rw-r--r--spec/policies/namespaces/project_namespace_policy_spec.rb3
-rw-r--r--spec/presenters/packages/npm/package_presenter_spec.rb88
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb (renamed from spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb)8
-rw-r--r--spec/requests/api/namespaces_spec.rb2
-rw-r--r--spec/requests/api/npm_project_packages_spec.rb20
-rw-r--r--spec/requests/api/project_import_spec.rb2
-rw-r--r--spec/services/dependency_proxy/find_or_create_blob_service_spec.rb2
-rw-r--r--spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb2
-rw-r--r--spec/services/groups/transfer_service_spec.rb51
-rw-r--r--spec/services/packages/npm/create_package_service_spec.rb29
-rw-r--r--spec/services/projects/create_service_spec.rb21
-rw-r--r--spec/services/projects/transfer_service_spec.rb6
-rw-r--r--spec/spec_helper.rb8
-rw-r--r--spec/support/helpers/gpg_helpers.rb1
-rw-r--r--spec/support/helpers/test_env.rb2
-rw-r--r--spec/support/matchers/project_namespace_matcher.rb28
-rw-r--r--spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/namespaces/traversal_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb15
-rw-r--r--spec/views/jira_connect/subscriptions/index.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/_published_experiments.html.haml_spec.rb35
-rw-r--r--spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb4
59 files changed, 849 insertions, 481 deletions
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index f0aa351bee0..cf528b414c0 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -62,7 +62,7 @@ RSpec.describe Dashboard::TodosController do
create(:issue, project: project, assignees: [user])
group_2 = create(:group)
group_2.add_owner(user)
- project_2 = create(:project)
+ project_2 = create(:project, namespace: user.namespace)
project_2.add_developer(user)
merge_request_2 = create(:merge_request, source_project: project_2)
create(:todo, project: project, author: author, user: user, target: merge_request_2)
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
index 3d7636b1f30..5b1c6777523 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -86,10 +86,11 @@ RSpec.describe Projects::MergeRequests::DiffsController do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
+ let(:maintainer) { true }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
before do
- project.add_maintainer(user)
+ project.add_maintainer(user) if maintainer
sign_in(user)
end
@@ -383,8 +384,9 @@ RSpec.describe Projects::MergeRequests::DiffsController do
end
context 'when the user cannot view the merge request' do
+ let(:maintainer) { false }
+
before do
- project.team.truncate
diff_for_path(old_path: existing_path, new_path: existing_path)
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 438fc2f2106..e2fc867db44 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -10,7 +10,8 @@ RSpec.describe Projects::MergeRequestsController do
let_it_be_with_reload(:project_public_with_private_builds) { create(:project, :repository, :public, :builds_private) }
let(:user) { project.owner }
- let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
+ let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: merge_request_source_project, allow_maintainer_to_push: false) }
+ let(:merge_request_source_project) { project }
before do
sign_in(user)
@@ -2073,8 +2074,6 @@ RSpec.describe Projects::MergeRequestsController do
end
describe 'POST #rebase' do
- let(:viewer) { user }
-
def post_rebase
post :rebase, params: { namespace_id: project.namespace, project_id: project, id: merge_request }
end
@@ -2085,7 +2084,7 @@ RSpec.describe Projects::MergeRequestsController do
context 'successfully' do
it 'enqeues a RebaseWorker' do
- expect_rebase_worker_for(viewer)
+ expect_rebase_worker_for(user)
post_rebase
@@ -2108,17 +2107,17 @@ RSpec.describe Projects::MergeRequestsController do
context 'with a forked project' do
let(:forked_project) { fork_project(project, fork_owner, repository: true) }
let(:fork_owner) { create(:user) }
+ let(:merge_request_source_project) { forked_project }
- before do
- project.add_developer(fork_owner)
+ context 'user cannot push to source branch' do
+ before do
+ project.add_developer(fork_owner)
- merge_request.update!(source_project: forked_project)
- forked_project.add_reporter(user)
- end
+ forked_project.add_reporter(user)
+ end
- context 'user cannot push to source branch' do
it 'returns 404' do
- expect_rebase_worker_for(viewer).never
+ expect_rebase_worker_for(user).never
post_rebase
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index a25c597edb2..baf500c2b57 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -499,13 +499,12 @@ RSpec.describe RegistrationsController do
expect(User.last.name).to eq("#{base_user_params[:first_name]} #{base_user_params[:last_name]}")
end
- it 'sets the username and caller_id in the context' do
+ it 'sets the caller_id in the context' do
expect(controller).to receive(:create).and_wrap_original do |m, *args|
m.call(*args)
expect(Gitlab::ApplicationContext.current)
- .to include('meta.user' => base_user_params[:username],
- 'meta.caller_id' => 'RegistrationsController#create')
+ .to include('meta.caller_id' => 'RegistrationsController#create')
end
subject
diff --git a/spec/factories/namespaces/project_namespaces.rb b/spec/factories/namespaces/project_namespaces.rb
index ca9fc5f8768..6bf17088741 100644
--- a/spec/factories/namespaces/project_namespaces.rb
+++ b/spec/factories/namespaces/project_namespaces.rb
@@ -2,7 +2,7 @@
FactoryBot.define do
factory :project_namespace, class: 'Namespaces::ProjectNamespace' do
- project
+ association :project, factory: :project, strategy: :build
parent { project.namespace }
visibility_level { project.visibility_level }
name { project.name }
diff --git a/spec/factories/packages/npm/metadata.rb b/spec/factories/packages/npm/metadata.rb
new file mode 100644
index 00000000000..c8acaa10199
--- /dev/null
+++ b/spec/factories/packages/npm/metadata.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :npm_metadatum, class: 'Packages::Npm::Metadatum' do
+ package { association(:npm_package) }
+
+ package_json do
+ {
+ 'name': package.name,
+ 'version': package.version,
+ 'dist': {
+ 'tarball': 'http://localhost/tarball.tgz',
+ 'shasum': '1234567890'
+ }
+ }
+ end
+ end
+end
diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb
index 70b53f23508..610a80eb12c 100644
--- a/spec/features/signed_commits_spec.rb
+++ b/spec/features/signed_commits_spec.rb
@@ -115,6 +115,19 @@ RSpec.describe 'GPG signed commits' do
end
end
+ it 'unverified signature: commit contains multiple GPG signatures' do
+ user_1_key
+
+ visit project_commit_path(project, GpgHelpers::MULTIPLE_SIGNATURES_SHA)
+ wait_for_all_requests
+
+ page.find('.gpg-status-box', text: 'Unverified').click
+
+ within '.popover' do
+ expect(page).to have_content "This commit was signed with multiple signatures."
+ end
+ end
+
it 'verified and the gpg user has a gitlab profile' do
user_1_key
@@ -169,7 +182,7 @@ RSpec.describe 'GPG signed commits' do
page.find('.gpg-status-box', text: 'Unverified').click
within '.popover' do
- expect(page).to have_content 'This commit was signed with an unverified signature'
+ expect(page).to have_content 'This commit was signed with multiple signatures.'
end
end
end
diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_version.json b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_version.json
index 3e74dc0a1c2..64969d71250 100644
--- a/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_version.json
+++ b/spec/fixtures/api/schemas/public_api/v4/packages/npm_package_version.json
@@ -36,11 +36,11 @@
".{1,}": { "type": "string" }
}
},
- "deprecated": {
- "type": "object",
- "patternProperties": {
- ".{1,}": { "type": "string" }
- }
- }
+ "deprecated": { "type": "string"},
+ "bin": { "type": "string" },
+ "directories": { "type": "array" },
+ "engines": { "type": "object" },
+ "_hasShrinkwrap": { "type": "boolean" },
+ "additionalProperties": true
}
}
diff --git a/spec/fixtures/packages/npm/payload.json b/spec/fixtures/packages/npm/payload.json
index 664aa636001..5ecb013b9bf 100644
--- a/spec/fixtures/packages/npm/payload.json
+++ b/spec/fixtures/packages/npm/payload.json
@@ -14,7 +14,8 @@
"express":"^4.16.4"
},
"dist":{
- "shasum":"f572d396fae9206628714fb2ce00f72e94f2258f"
+ "shasum":"f572d396fae9206628714fb2ce00f72e94f2258f",
+ "tarball":"http://localhost/npm/package.tgz"
}
}
},
diff --git a/spec/fixtures/packages/npm/payload_with_duplicated_packages.json b/spec/fixtures/packages/npm/payload_with_duplicated_packages.json
index a6ea8760bd5..bc4a7b3f55a 100644
--- a/spec/fixtures/packages/npm/payload_with_duplicated_packages.json
+++ b/spec/fixtures/packages/npm/payload_with_duplicated_packages.json
@@ -28,7 +28,8 @@
"express":"^4.16.4"
},
"dist":{
- "shasum":"f572d396fae9206628714fb2ce00f72e94f2258f"
+ "shasum":"f572d396fae9206628714fb2ce00f72e94f2258f",
+ "tarball":"http://localhost/npm/package.tgz"
}
}
},
diff --git a/spec/frontend/__helpers__/experimentation_helper.js b/spec/frontend/__helpers__/experimentation_helper.js
index 7a2ef61216a..e0156226acc 100644
--- a/spec/frontend/__helpers__/experimentation_helper.js
+++ b/spec/frontend/__helpers__/experimentation_helper.js
@@ -1,5 +1,6 @@
import { merge } from 'lodash';
+// This helper is for specs that use `gitlab/experimentation` module
export function withGonExperiment(experimentKey, value = true) {
let origGon;
@@ -12,16 +13,26 @@ export function withGonExperiment(experimentKey, value = true) {
window.gon = origGon;
});
}
-// This helper is for specs that use `gitlab-experiment` utilities, which have a different schema that gets pushed via Gon compared to `Experimentation Module`
-export function assignGitlabExperiment(experimentKey, variant) {
- let origGon;
- beforeEach(() => {
- origGon = window.gon;
- window.gon = { experiment: { [experimentKey]: { variant } } };
- });
+// The following helper is for specs that use `gitlab-experiment` utilities,
+// which have a different schema that gets pushed to the frontend compared to
+// the `Experimentation` Module.
+//
+// Usage: stubExperiments({ experiment_feature_flag_name: 'variant_name', ... })
+export function stubExperiments(experiments = {}) {
+ // Deprecated
+ window.gon = window.gon || {};
+ window.gon.experiment = window.gon.experiment || {};
+ // Preferred
+ window.gl = window.gl || {};
+ window.gl.experiments = window.gl.experiemnts || {};
- afterEach(() => {
- window.gon = origGon;
+ Object.entries(experiments).forEach(([name, variant]) => {
+ const experimentData = { experiment: name, variant };
+
+ // Deprecated
+ window.gon.experiment[name] = experimentData;
+ // Preferred
+ window.gl.experiments[name] = experimentData;
});
}
diff --git a/spec/frontend/boards/components/new_board_button_spec.js b/spec/frontend/boards/components/new_board_button_spec.js
index e66c31953f6..075fe225ec2 100644
--- a/spec/frontend/boards/components/new_board_button_spec.js
+++ b/spec/frontend/boards/components/new_board_button_spec.js
@@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import NewBoardButton from '~/boards/components/new_board_button.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import { assignGitlabExperiment } from 'helpers/experimentation_helper';
+import { stubExperiments } from 'helpers/experimentation_helper';
import eventHub from '~/boards/eventhub';
const FEATURE = 'prominent_create_board_btn';
@@ -28,7 +28,9 @@ describe('NewBoardButton', () => {
});
describe('control variant', () => {
- assignGitlabExperiment(FEATURE, 'control');
+ beforeAll(() => {
+ stubExperiments({ [FEATURE]: 'control' });
+ });
it('renders nothing', () => {
wrapper = createComponent();
@@ -38,7 +40,9 @@ describe('NewBoardButton', () => {
});
describe('candidate variant', () => {
- assignGitlabExperiment(FEATURE, 'candidate');
+ beforeAll(() => {
+ stubExperiments({ [FEATURE]: 'candidate' });
+ });
it('renders New board button when `candidate` variant', () => {
wrapper = createComponent();
diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js
index e39b761b54d..342b4bfcc50 100644
--- a/spec/frontend/diffs/components/diff_file_header_spec.js
+++ b/spec/frontend/diffs/components/diff_file_header_spec.js
@@ -241,18 +241,19 @@ describe('DiffFileHeader component', () => {
});
describe('for any file', () => {
- const otherModes = Object.keys(diffViewerModes).filter((m) => m !== 'mode_changed');
+ const allModes = Object.keys(diffViewerModes).map((m) => [m]);
- it('for mode_changed file mode displays mode changes', () => {
+ it.each(allModes)('for %s file mode displays mode changes', (mode) => {
createComponent({
props: {
diffFile: {
...diffFile,
+ mode_changed: true,
a_mode: 'old-mode',
b_mode: 'new-mode',
viewer: {
...diffFile.viewer,
- name: diffViewerModes.mode_changed,
+ name: diffViewerModes[mode],
},
},
},
@@ -260,13 +261,14 @@ describe('DiffFileHeader component', () => {
expect(findModeChangedLine().text()).toMatch(/old-mode.+new-mode/);
});
- it.each(otherModes.map((m) => [m]))(
+ it.each(allModes.filter((m) => m[0] !== 'mode_changed'))(
'for %s file mode does not display mode changes',
(mode) => {
createComponent({
props: {
diffFile: {
...diffFile,
+ mode_changed: false,
a_mode: 'old-mode',
b_mode: 'new-mode',
viewer: {
diff --git a/spec/frontend/experimentation/utils_spec.js b/spec/frontend/experimentation/utils_spec.js
index e9b7d445473..df24828ceb9 100644
--- a/spec/frontend/experimentation/utils_spec.js
+++ b/spec/frontend/experimentation/utils_spec.js
@@ -1,4 +1,4 @@
-import { assignGitlabExperiment } from 'helpers/experimentation_helper';
+import { stubExperiments } from 'helpers/experimentation_helper';
import {
DEFAULT_VARIANT,
CANDIDATE_VARIANT,
@@ -7,15 +7,45 @@ import {
import * as experimentUtils from '~/experimentation/utils';
describe('experiment Utilities', () => {
- const TEST_KEY = 'abc';
+ const ABC_KEY = 'abc';
+ const DEF_KEY = 'def';
+
+ let origGon;
+ let origGl;
+
+ beforeEach(() => {
+ origGon = window.gon;
+ origGl = window.gl;
+ window.gon.experiment = {};
+ window.gl.experiments = {};
+ });
+
+ afterEach(() => {
+ window.gon = origGon;
+ window.gl = origGl;
+ });
describe('getExperimentData', () => {
+ const ABC_DATA = '_abc_data_';
+ const ABC_DATA2 = '_updated_abc_data_';
+ const DEF_DATA = '_def_data_';
+
describe.each`
- gon | input | output
- ${[TEST_KEY, '_data_']} | ${[TEST_KEY]} | ${{ variant: '_data_' }}
- ${[]} | ${[TEST_KEY]} | ${undefined}
- `('with input=$input and gon=$gon', ({ gon, input, output }) => {
- assignGitlabExperiment(...gon);
+ gonData | glData | input | output
+ ${[ABC_KEY, ABC_DATA]} | ${[]} | ${[ABC_KEY]} | ${{ experiment: ABC_KEY, variant: ABC_DATA }}
+ ${[]} | ${[ABC_KEY, ABC_DATA]} | ${[ABC_KEY]} | ${{ experiment: ABC_KEY, variant: ABC_DATA }}
+ ${[ABC_KEY, ABC_DATA]} | ${[DEF_KEY, DEF_DATA]} | ${[ABC_KEY]} | ${{ experiment: ABC_KEY, variant: ABC_DATA }}
+ ${[ABC_KEY, ABC_DATA]} | ${[DEF_KEY, DEF_DATA]} | ${[DEF_KEY]} | ${{ experiment: DEF_KEY, variant: DEF_DATA }}
+ ${[ABC_KEY, ABC_DATA]} | ${[ABC_KEY, ABC_DATA2]} | ${[ABC_KEY]} | ${{ experiment: ABC_KEY, variant: ABC_DATA2 }}
+ ${[]} | ${[]} | ${[ABC_KEY]} | ${undefined}
+ `('with input=$input, gon=$gonData, & gl=$glData', ({ gonData, glData, input, output }) => {
+ beforeEach(() => {
+ const [gonKey, gonVariant] = gonData;
+ const [glKey, glVariant] = glData;
+
+ if (gonKey) window.gon.experiment[gonKey] = { experiment: gonKey, variant: gonVariant };
+ if (glKey) window.gl.experiments[glKey] = { experiment: glKey, variant: glVariant };
+ });
it(`returns ${output}`, () => {
expect(experimentUtils.getExperimentData(...input)).toEqual(output);
@@ -25,66 +55,47 @@ describe('experiment Utilities', () => {
describe('getAllExperimentContexts', () => {
const schema = TRACKING_CONTEXT_SCHEMA;
- let origGon;
-
- beforeEach(() => {
- origGon = window.gon;
- });
-
- afterEach(() => {
- window.gon = origGon;
- });
it('collects all of the experiment contexts into a single array', () => {
- const experiments = [
- { experiment: 'abc', variant: 'candidate' },
- { experiment: 'def', variant: 'control' },
- { experiment: 'ghi', variant: 'blue' },
- ];
- window.gon = {
- experiment: experiments.reduce((collector, { experiment, variant }) => {
- return { ...collector, [experiment]: { experiment, variant } };
- }, {}),
- };
+ const experiments = { [ABC_KEY]: 'candidate', [DEF_KEY]: 'control', ghi: 'blue' };
+
+ stubExperiments(experiments);
expect(experimentUtils.getAllExperimentContexts()).toEqual(
- experiments.map((data) => ({ schema, data })),
+ Object.entries(experiments).map(([experiment, variant]) => ({
+ schema,
+ data: { experiment, variant },
+ })),
);
});
it('returns an empty array if there are no experiments', () => {
- window.gon.experiment = {};
-
expect(experimentUtils.getAllExperimentContexts()).toEqual([]);
});
-
- it('includes all additional experiment data', () => {
- const experiment = 'experimentWithCustomData';
- const data = { experiment, variant: 'control', color: 'blue', style: 'rounded' };
- window.gon.experiment[experiment] = data;
-
- expect(experimentUtils.getAllExperimentContexts()).toContainEqual({ schema, data });
- });
});
describe('isExperimentVariant', () => {
describe.each`
- gon | input | output
- ${[TEST_KEY, DEFAULT_VARIANT]} | ${[TEST_KEY, DEFAULT_VARIANT]} | ${true}
- ${[TEST_KEY, '_variant_name']} | ${[TEST_KEY, '_variant_name']} | ${true}
- ${[TEST_KEY, '_variant_name']} | ${[TEST_KEY, '_bogus_name']} | ${false}
- ${[TEST_KEY, '_variant_name']} | ${['boguskey', '_variant_name']} | ${false}
- ${[]} | ${[TEST_KEY, '_variant_name']} | ${false}
- `('with input=$input and gon=$gon', ({ gon, input, output }) => {
- assignGitlabExperiment(...gon);
-
- it(`returns ${output}`, () => {
- expect(experimentUtils.isExperimentVariant(...input)).toEqual(output);
- });
- });
+ experiment | variant | input | output
+ ${ABC_KEY} | ${DEFAULT_VARIANT} | ${[ABC_KEY, DEFAULT_VARIANT]} | ${true}
+ ${ABC_KEY} | ${'_variant_name'} | ${[ABC_KEY, '_variant_name']} | ${true}
+ ${ABC_KEY} | ${'_variant_name'} | ${[ABC_KEY, '_bogus_name']} | ${false}
+ ${ABC_KEY} | ${'_variant_name'} | ${['boguskey', '_variant_name']} | ${false}
+ ${undefined} | ${undefined} | ${[ABC_KEY, '_variant_name']} | ${false}
+ `(
+ 'with input=$input, experiment=$experiment, variant=$variant',
+ ({ experiment, variant, input, output }) => {
+ it(`returns ${output}`, () => {
+ if (experiment) stubExperiments({ [experiment]: variant });
+
+ expect(experimentUtils.isExperimentVariant(...input)).toEqual(output);
+ });
+ },
+ );
});
describe('experiment', () => {
+ const experiment = 'marley';
const useSpy = jest.fn();
const controlSpy = jest.fn();
const trySpy = jest.fn();
@@ -98,49 +109,62 @@ describe('experiment Utilities', () => {
};
describe('when there is no experiment data', () => {
- it('calls control variant', () => {
- experimentUtils.experiment('marley', variants);
+ it('calls the use variant', () => {
+ experimentUtils.experiment(experiment, variants);
expect(useSpy).toHaveBeenCalled();
});
+
+ describe("when 'control' is provided instead of 'use'", () => {
+ it('calls the control variant', () => {
+ experimentUtils.experiment(experiment, { control: controlSpy });
+ expect(controlSpy).toHaveBeenCalled();
+ });
+ });
});
describe('when experiment variant is "control"', () => {
- assignGitlabExperiment('marley', DEFAULT_VARIANT);
+ beforeEach(() => {
+ stubExperiments({ [experiment]: DEFAULT_VARIANT });
+ });
- it('calls the control variant', () => {
- experimentUtils.experiment('marley', variants);
+ it('calls the use variant', () => {
+ experimentUtils.experiment(experiment, variants);
expect(useSpy).toHaveBeenCalled();
});
describe("when 'control' is provided instead of 'use'", () => {
it('calls the control variant', () => {
- experimentUtils.experiment('marley', { control: controlSpy });
+ experimentUtils.experiment(experiment, { control: controlSpy });
expect(controlSpy).toHaveBeenCalled();
});
});
});
describe('when experiment variant is "candidate"', () => {
- assignGitlabExperiment('marley', CANDIDATE_VARIANT);
+ beforeEach(() => {
+ stubExperiments({ [experiment]: CANDIDATE_VARIANT });
+ });
- it('calls the candidate variant', () => {
- experimentUtils.experiment('marley', variants);
+ it('calls the try variant', () => {
+ experimentUtils.experiment(experiment, variants);
expect(trySpy).toHaveBeenCalled();
});
describe("when 'candidate' is provided instead of 'try'", () => {
- it('calls the control variant', () => {
- experimentUtils.experiment('marley', { candidate: candidateSpy });
+ it('calls the candidate variant', () => {
+ experimentUtils.experiment(experiment, { candidate: candidateSpy });
expect(candidateSpy).toHaveBeenCalled();
});
});
});
describe('when experiment variant is "get_up_stand_up"', () => {
- assignGitlabExperiment('marley', 'get_up_stand_up');
+ beforeEach(() => {
+ stubExperiments({ [experiment]: 'get_up_stand_up' });
+ });
it('calls the get-up-stand-up variant', () => {
- experimentUtils.experiment('marley', variants);
+ experimentUtils.experiment(experiment, variants);
expect(getUpStandUpSpy).toHaveBeenCalled();
});
});
@@ -148,14 +172,17 @@ describe('experiment Utilities', () => {
describe('getExperimentVariant', () => {
it.each`
- gon | input | output
- ${{ experiment: { [TEST_KEY]: { variant: DEFAULT_VARIANT } } }} | ${[TEST_KEY]} | ${DEFAULT_VARIANT}
- ${{ experiment: { [TEST_KEY]: { variant: CANDIDATE_VARIANT } } }} | ${[TEST_KEY]} | ${CANDIDATE_VARIANT}
- ${{}} | ${[TEST_KEY]} | ${DEFAULT_VARIANT}
- `('with input=$input and gon=$gon, returns $output', ({ gon, input, output }) => {
- window.gon = gon;
-
- expect(experimentUtils.getExperimentVariant(...input)).toEqual(output);
- });
+ experiment | variant | input | output
+ ${ABC_KEY} | ${DEFAULT_VARIANT} | ${ABC_KEY} | ${DEFAULT_VARIANT}
+ ${ABC_KEY} | ${CANDIDATE_VARIANT} | ${ABC_KEY} | ${CANDIDATE_VARIANT}
+ ${undefined} | ${undefined} | ${ABC_KEY} | ${DEFAULT_VARIANT}
+ `(
+ 'with input=$input, experiment=$experiment, & variant=$variant; returns $output',
+ ({ experiment, variant, input, output }) => {
+ stubExperiments({ [experiment]: variant });
+
+ expect(experimentUtils.getExperimentVariant(input)).toEqual(output);
+ },
+ );
});
});
diff --git a/spec/frontend/jira_connect/subscriptions/components/app_spec.js b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
index b63236dec82..8e464968453 100644
--- a/spec/frontend/jira_connect/subscriptions/components/app_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
@@ -1,12 +1,14 @@
-import { GlAlert, GlLink } from '@gitlab/ui';
+import { GlAlert, GlLink, GlEmptyState } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import JiraConnectApp from '~/jira_connect/subscriptions/components/app.vue';
import AddNamespaceButton from '~/jira_connect/subscriptions/components/add_namespace_button.vue';
import SignInButton from '~/jira_connect/subscriptions/components/sign_in_button.vue';
+import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue';
import createStore from '~/jira_connect/subscriptions/store';
import { SET_ALERT } from '~/jira_connect/subscriptions/store/mutation_types';
import { __ } from '~/locale';
+import { mockSubscription } from '../mock_data';
jest.mock('~/jira_connect/subscriptions/utils', () => ({
retrieveAlert: jest.fn().mockReturnValue({ message: 'error message' }),
@@ -20,6 +22,8 @@ describe('JiraConnectApp', () => {
const findAlertLink = () => findAlert().findComponent(GlLink);
const findSignInButton = () => wrapper.findComponent(SignInButton);
const findAddNamespaceButton = () => wrapper.findComponent(AddNamespaceButton);
+ const findSubscriptionsList = () => wrapper.findComponent(SubscriptionsList);
+ const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const createComponent = ({ provide, mountFn = shallowMount } = {}) => {
store = createStore();
@@ -36,91 +40,114 @@ describe('JiraConnectApp', () => {
describe('template', () => {
describe.each`
- scenario | usersPath | expectSignInButton | expectNamespaceButton
- ${'user is not signed in'} | ${'/users'} | ${true} | ${false}
- ${'user is signed in'} | ${undefined} | ${false} | ${true}
- `('when $scenario', ({ usersPath, expectSignInButton, expectNamespaceButton }) => {
- beforeEach(() => {
- createComponent({
- provide: {
- usersPath,
- },
+ scenario | usersPath | subscriptions | expectSignInButton | expectEmptyState | expectNamespaceButton | expectSubscriptionsList
+ ${'user is not signed in with subscriptions'} | ${'/users'} | ${[mockSubscription]} | ${true} | ${false} | ${false} | ${true}
+ ${'user is not signed in without subscriptions'} | ${'/users'} | ${undefined} | ${true} | ${false} | ${false} | ${false}
+ ${'user is signed in with subscriptions'} | ${undefined} | ${[mockSubscription]} | ${false} | ${false} | ${true} | ${true}
+ ${'user is signed in without subscriptions'} | ${undefined} | ${undefined} | ${false} | ${true} | ${false} | ${false}
+ `(
+ 'when $scenario',
+ ({
+ usersPath,
+ expectSignInButton,
+ subscriptions,
+ expectEmptyState,
+ expectNamespaceButton,
+ expectSubscriptionsList,
+ }) => {
+ beforeEach(() => {
+ createComponent({
+ provide: {
+ usersPath,
+ subscriptions,
+ },
+ });
});
- });
- it('renders sign in button as expected', () => {
- expect(findSignInButton().exists()).toBe(expectSignInButton);
- });
+ it(`${expectSignInButton ? 'renders' : 'does not render'} sign in button`, () => {
+ expect(findSignInButton().exists()).toBe(expectSignInButton);
+ });
- it('renders "Add Namespace" button as expected', () => {
- expect(findAddNamespaceButton().exists()).toBe(expectNamespaceButton);
- });
- });
+ it(`${expectEmptyState ? 'renders' : 'does not render'} empty state`, () => {
+ expect(findEmptyState().exists()).toBe(expectEmptyState);
+ });
- describe('alert', () => {
- it.each`
- message | variant | alertShouldRender
- ${'Test error'} | ${'danger'} | ${true}
- ${'Test notice'} | ${'info'} | ${true}
- ${''} | ${undefined} | ${false}
- ${undefined} | ${undefined} | ${false}
- `(
- 'renders correct alert when message is `$message` and variant is `$variant`',
- async ({ message, alertShouldRender, variant }) => {
- createComponent();
-
- store.commit(SET_ALERT, { message, variant });
- await wrapper.vm.$nextTick();
-
- const alert = findAlert();
-
- expect(alert.exists()).toBe(alertShouldRender);
- if (alertShouldRender) {
- expect(alert.isVisible()).toBe(alertShouldRender);
- expect(alert.html()).toContain(message);
- expect(alert.props('variant')).toBe(variant);
- expect(findAlertLink().exists()).toBe(false);
- }
- },
- );
-
- it('hides alert on @dismiss event', async () => {
+ it(`${
+ expectNamespaceButton ? 'renders' : 'does not render'
+ } button to add namespace`, () => {
+ expect(findAddNamespaceButton().exists()).toBe(expectNamespaceButton);
+ });
+
+ it(`${expectSubscriptionsList ? 'renders' : 'does not render'} subscriptions list`, () => {
+ expect(findSubscriptionsList().exists()).toBe(expectSubscriptionsList);
+ });
+ },
+ );
+ });
+
+ describe('alert', () => {
+ it.each`
+ message | variant | alertShouldRender
+ ${'Test error'} | ${'danger'} | ${true}
+ ${'Test notice'} | ${'info'} | ${true}
+ ${''} | ${undefined} | ${false}
+ ${undefined} | ${undefined} | ${false}
+ `(
+ 'renders correct alert when message is `$message` and variant is `$variant`',
+ async ({ message, alertShouldRender, variant }) => {
createComponent();
- store.commit(SET_ALERT, { message: 'test message' });
+ store.commit(SET_ALERT, { message, variant });
await wrapper.vm.$nextTick();
- findAlert().vm.$emit('dismiss');
- await wrapper.vm.$nextTick();
+ const alert = findAlert();
- expect(findAlert().exists()).toBe(false);
- });
+ expect(alert.exists()).toBe(alertShouldRender);
+ if (alertShouldRender) {
+ expect(alert.isVisible()).toBe(alertShouldRender);
+ expect(alert.html()).toContain(message);
+ expect(alert.props('variant')).toBe(variant);
+ expect(findAlertLink().exists()).toBe(false);
+ }
+ },
+ );
- it('renders link when `linkUrl` is set', async () => {
- createComponent({ mountFn: mount });
+ it('hides alert on @dismiss event', async () => {
+ createComponent();
- store.commit(SET_ALERT, {
- message: __('test message %{linkStart}test link%{linkEnd}'),
- linkUrl: 'https://gitlab.com',
- });
- await wrapper.vm.$nextTick();
+ store.commit(SET_ALERT, { message: 'test message' });
+ await wrapper.vm.$nextTick();
+
+ findAlert().vm.$emit('dismiss');
+ await wrapper.vm.$nextTick();
+
+ expect(findAlert().exists()).toBe(false);
+ });
- const alertLink = findAlertLink();
+ it('renders link when `linkUrl` is set', async () => {
+ createComponent({ mountFn: mount });
- expect(alertLink.exists()).toBe(true);
- expect(alertLink.text()).toContain('test link');
- expect(alertLink.attributes('href')).toBe('https://gitlab.com');
+ store.commit(SET_ALERT, {
+ message: __('test message %{linkStart}test link%{linkEnd}'),
+ linkUrl: 'https://gitlab.com',
});
+ await wrapper.vm.$nextTick();
- describe('when alert is set in localStoage', () => {
- it('renders alert on mount', () => {
- createComponent();
+ const alertLink = findAlertLink();
- const alert = findAlert();
+ expect(alertLink.exists()).toBe(true);
+ expect(alertLink.text()).toContain('test link');
+ expect(alertLink.attributes('href')).toBe('https://gitlab.com');
+ });
- expect(alert.exists()).toBe(true);
- expect(alert.html()).toContain('error message');
- });
+ describe('when alert is set in localStoage', () => {
+ it('renders alert on mount', () => {
+ createComponent();
+
+ const alert = findAlert();
+
+ expect(alert.exists()).toBe(true);
+ expect(alert.html()).toContain('error message');
});
});
});
diff --git a/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js b/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js
index 32b43765843..4e4a2b58600 100644
--- a/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js
@@ -1,12 +1,15 @@
-import { GlButton, GlEmptyState, GlTable } from '@gitlab/ui';
-import { mount, shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import * as JiraConnectApi from '~/jira_connect/subscriptions/api';
+import GroupItemName from '~/jira_connect/subscriptions/components/group_item_name.vue';
+
import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue';
import createStore from '~/jira_connect/subscriptions/store';
import { SET_ALERT } from '~/jira_connect/subscriptions/store/mutation_types';
import { reloadPage } from '~/jira_connect/subscriptions/utils';
+import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { mockSubscription } from '../mock_data';
jest.mock('~/jira_connect/subscriptions/utils');
@@ -15,11 +18,13 @@ describe('SubscriptionsList', () => {
let wrapper;
let store;
- const createComponent = ({ mountFn = shallowMount, provide = {} } = {}) => {
+ const createComponent = () => {
store = createStore();
- wrapper = mountFn(SubscriptionsList, {
- provide,
+ wrapper = mount(SubscriptionsList, {
+ provide: {
+ subscriptions: [mockSubscription],
+ },
store,
});
};
@@ -28,28 +33,28 @@ describe('SubscriptionsList', () => {
wrapper.destroy();
});
- const findGlEmptyState = () => wrapper.findComponent(GlEmptyState);
- const findGlTable = () => wrapper.findComponent(GlTable);
- const findUnlinkButton = () => findGlTable().findComponent(GlButton);
+ const findUnlinkButton = () => wrapper.findComponent(GlButton);
const clickUnlinkButton = () => findUnlinkButton().trigger('click');
describe('template', () => {
- it('renders GlEmptyState when subscriptions is empty', () => {
+ beforeEach(() => {
createComponent();
+ });
+
+ it('renders "name" cell correctly', () => {
+ const groupItemNames = wrapper.findAllComponents(GroupItemName);
+ expect(groupItemNames.wrappers).toHaveLength(1);
- expect(findGlEmptyState().exists()).toBe(true);
- expect(findGlTable().exists()).toBe(false);
+ const item = groupItemNames.at(0);
+ expect(item.props('group')).toBe(mockSubscription.group);
});
- it('renders GlTable when subscriptions are present', () => {
- createComponent({
- provide: {
- subscriptions: [mockSubscription],
- },
- });
+ it('renders "created at" cell correctly', () => {
+ const timeAgoTooltips = wrapper.findAllComponents(TimeagoTooltip);
+ expect(timeAgoTooltips.wrappers).toHaveLength(1);
- expect(findGlEmptyState().exists()).toBe(false);
- expect(findGlTable().exists()).toBe(true);
+ const item = timeAgoTooltips.at(0);
+ expect(item.props('time')).toBe(mockSubscription.created_at);
});
});
@@ -57,12 +62,7 @@ describe('SubscriptionsList', () => {
let removeSubscriptionSpy;
beforeEach(() => {
- createComponent({
- mountFn: mount,
- provide: {
- subscriptions: [mockSubscription],
- },
- });
+ createComponent();
removeSubscriptionSpy = jest.spyOn(JiraConnectApi, 'removeSubscription').mockResolvedValue();
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js
index be15e4df66d..0fb0d5b0b68 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js
@@ -46,7 +46,7 @@ describe('Wip', () => {
is_new_mr_data: true,
};
- describe('handleRemoveWIP', () => {
+ describe('handleRemoveDraft', () => {
it('should make a request to service and handle response', (done) => {
const vm = createComponent();
@@ -59,7 +59,7 @@ describe('Wip', () => {
}),
);
- vm.handleRemoveWIP();
+ vm.handleRemoveDraft();
setImmediate(() => {
expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
@@ -84,7 +84,7 @@ describe('Wip', () => {
expect(el.innerText).toContain('This merge request is still a draft.');
expect(el.querySelector('button').getAttribute('disabled')).toBeTruthy();
expect(el.querySelector('button').innerText).toContain('Merge');
- expect(el.querySelector('.js-remove-wip').innerText.replace(/\s\s+/g, ' ')).toContain(
+ expect(el.querySelector('.js-remove-draft').innerText.replace(/\s\s+/g, ' ')).toContain(
'Mark as ready',
);
});
@@ -93,7 +93,7 @@ describe('Wip', () => {
vm.mr.removeWIPPath = '';
Vue.nextTick(() => {
- expect(el.querySelector('.js-remove-wip')).toEqual(null);
+ expect(el.querySelector('.js-remove-draft')).toEqual(null);
done();
});
});
diff --git a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
index 4d0e5e71f27..fc760f5c5be 100644
--- a/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
+++ b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
@@ -15,7 +15,7 @@ describe('getStateKey', () => {
branchMissing: false,
commitsCount: 2,
hasConflicts: false,
- workInProgress: false,
+ draft: false,
};
const bound = getStateKey.bind(context);
@@ -49,9 +49,9 @@ describe('getStateKey', () => {
expect(bound()).toEqual('unresolvedDiscussions');
- context.workInProgress = true;
+ context.draft = true;
- expect(bound()).toEqual('workInProgress');
+ expect(bound()).toEqual('draft');
context.onlyAllowMergeIfPipelineSucceeds = true;
context.isPipelineFailed = true;
@@ -99,7 +99,7 @@ describe('getStateKey', () => {
branchMissing: false,
commitsCount: 2,
hasConflicts: false,
- workInProgress: false,
+ draft: false,
};
const bound = getStateKey.bind(context);
diff --git a/spec/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/graphql/mutations/merge_requests/set_wip_spec.rb
deleted file mode 100644
index fae9c4f7fe0..00000000000
--- a/spec/graphql/mutations/merge_requests/set_wip_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Mutations::MergeRequests::SetWip do
- let(:merge_request) { create(:merge_request) }
- let(:user) { create(:user) }
-
- subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
-
- specify { expect(described_class).to require_graphql_authorizations(:update_merge_request) }
-
- describe '#resolve' do
- let(:wip) { true }
- let(:mutated_merge_request) { subject[:merge_request] }
-
- subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, wip: wip) }
-
- it_behaves_like 'permission level for merge request mutation is correctly verified'
-
- context 'when the user can update the merge request' do
- before do
- merge_request.project.add_developer(user)
- end
-
- it 'returns the merge request as a wip' do
- expect(mutated_merge_request).to eq(merge_request)
- expect(mutated_merge_request).to be_work_in_progress
- expect(subject[:errors]).to be_empty
- end
-
- it 'returns errors merge request could not be updated' do
- # Make the merge request invalid
- merge_request.allow_broken = true
- merge_request.update!(source_project: nil)
-
- expect(subject[:errors]).not_to be_empty
- end
-
- context 'when passing wip as false' do
- let(:wip) { false }
-
- it 'removes `wip` from the title' do
- merge_request.update!(title: "WIP: working on it")
-
- expect(mutated_merge_request).not_to be_work_in_progress
- end
-
- it 'does not do anything if the title did not start with wip' do
- expect(mutated_merge_request).not_to be_work_in_progress
- end
- end
- end
- end
-end
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index bc3ccb0d9ba..b17b7c32289 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
notes discussions user_permissions id iid title title_html description
description_html state created_at updated_at source_project target_project
project project_id source_project_id target_project_id source_branch
- target_branch work_in_progress draft merge_when_pipeline_succeeds diff_head_sha
+ target_branch draft merge_when_pipeline_succeeds diff_head_sha
merge_commit_sha user_notes_count user_discussions_count should_remove_source_branch
diff_refs diff_stats diff_stats_summary
force_remove_source_branch
diff --git a/spec/graphql/types/mutation_type_spec.rb b/spec/graphql/types/mutation_type_spec.rb
index c1a5c93c85b..95d835c88cf 100644
--- a/spec/graphql/types/mutation_type_spec.rb
+++ b/spec/graphql/types/mutation_type_spec.rb
@@ -3,14 +3,6 @@
require 'spec_helper'
RSpec.describe Types::MutationType do
- it 'is expected to have the deprecated MergeRequestSetWip' do
- field = get_field('MergeRequestSetWip')
-
- expect(field).to be_present
- expect(field.deprecation_reason).to be_present
- expect(field.resolver).to eq(Mutations::MergeRequests::SetWip)
- end
-
it 'is expected to have the MergeRequestSetDraft' do
expect(described_class).to have_graphql_mutation(Mutations::MergeRequests::SetDraft)
end
diff --git a/spec/lib/bulk_imports/ndjson_pipeline_spec.rb b/spec/lib/bulk_imports/ndjson_pipeline_spec.rb
index 7d156c2c3df..c5197fb29d9 100644
--- a/spec/lib/bulk_imports/ndjson_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/ndjson_pipeline_spec.rb
@@ -111,6 +111,7 @@ RSpec.describe BulkImports::NdjsonPipeline do
context = double(portable: group, current_user: user, import_export_config: config, bulk_import: import_double, entity: entity_double)
allow(subject).to receive(:import_export_config).and_return(config)
allow(subject).to receive(:context).and_return(context)
+ relation_object = double
expect(Gitlab::ImportExport::Group::RelationFactory)
.to receive(:create)
@@ -124,6 +125,8 @@ RSpec.describe BulkImports::NdjsonPipeline do
user: user,
excluded_keys: nil
)
+ .and_return(relation_object)
+ expect(relation_object).to receive(:assign_attributes).with(group: group)
subject.transform(context, data)
end
diff --git a/spec/lib/bulk_imports/projects/stage_spec.rb b/spec/lib/bulk_imports/projects/stage_spec.rb
index 685bf223f9c..7c07c4f7b3d 100644
--- a/spec/lib/bulk_imports/projects/stage_spec.rb
+++ b/spec/lib/bulk_imports/projects/stage_spec.rb
@@ -27,7 +27,8 @@ RSpec.describe BulkImports::Projects::Stage do
describe '#pipelines' do
it 'list all the pipelines with their stage number, ordered by stage' do
- expect(subject.pipelines).to eq(pipelines)
+ expect(subject.pipelines & pipelines).to eq(pipelines)
+ expect(subject.pipelines.last.last).to eq(BulkImports::Common::Pipelines::EntityFinisher)
end
end
end
diff --git a/spec/lib/gitlab/application_rate_limiter_spec.rb b/spec/lib/gitlab/application_rate_limiter_spec.rb
index 0ce7ae8d3aa..c74bcf8d678 100644
--- a/spec/lib/gitlab/application_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/application_rate_limiter_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Gitlab::ApplicationRateLimiter do
subject { described_class }
- describe '.throttled?' do
+ describe '.throttled?', :clean_gitlab_redis_rate_limiting do
let(:rate_limits) do
{
test_action: {
@@ -56,136 +56,51 @@ RSpec.describe Gitlab::ApplicationRateLimiter do
end
end
- context 'when rate_limiter_safe_increment is disabled' do
- let(:redis) { double('redis') }
- let(:key) { rate_limits.keys[0] }
+ shared_examples 'throttles based on key and scope' do
+ let(:start_time) { Time.current.beginning_of_hour }
- before do
- allow(Gitlab::Redis::RateLimiting).to receive(:with).and_yield(redis)
-
- stub_feature_flags(rate_limiter_safe_increment: false)
- end
-
- shared_examples 'action rate limiter' do
- it 'increases the throttle count and sets the expiration time' do
- expect(redis).to receive(:incr).with(cache_key).and_return(1)
- expect(redis).to receive(:expire).with(cache_key, 120)
-
- expect(subject.throttled?(key, scope: scope)).to be_falsy
+ it 'returns true when threshold is exceeded' do
+ travel_to(start_time) do
+ expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
end
- it 'returns true if the key is throttled' do
- expect(redis).to receive(:incr).with(cache_key).and_return(2)
- expect(redis).not_to receive(:expire)
+ travel_to(start_time + 1.minute) do
+ expect(subject.throttled?(:test_action, scope: scope)).to eq(true)
- expect(subject.throttled?(key, scope: scope)).to be_truthy
- end
-
- context 'when throttling is disabled' do
- it 'returns false and does not set expiration time' do
- expect(redis).not_to receive(:incr)
- expect(redis).not_to receive(:expire)
-
- expect(subject.throttled?(key, scope: scope, threshold: 0)).to be_falsy
- end
+ # Assert that it does not affect other actions or scope
+ expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
+ expect(subject.throttled?(:test_action, scope: [user])).to eq(false)
end
end
- context 'when the key is an array of only ActiveRecord models' do
- let(:scope) { [user, project] }
+ it 'returns false when interval has elapsed' do
+ travel_to(start_time) do
+ expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
- let(:cache_key) do
- "application_rate_limiter:test_action:user:#{user.id}:project:#{project.id}"
+ # another_action has a threshold of 3 so we simulate 2 requests
+ expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
+ expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
end
- it_behaves_like 'action rate limiter'
-
- context 'when a scope attribute is nil' do
- let(:scope) { [user, nil] }
-
- let(:cache_key) do
- "application_rate_limiter:test_action:user:#{user.id}"
- end
-
- it_behaves_like 'action rate limiter'
- end
- end
-
- context 'when the key is a combination of ActiveRecord models and strings' do
- let(:project) { create(:project, :public, :repository) }
- let(:commit) { project.repository.commit }
- let(:path) { 'app/controllers/groups_controller.rb' }
- let(:scope) { [project, commit, path] }
-
- let(:cache_key) do
- "application_rate_limiter:test_action:project:#{project.id}:commit:#{commit.sha}:#{path}"
- end
-
- it_behaves_like 'action rate limiter'
-
- context 'when a scope attribute is nil' do
- let(:scope) { [project, commit, nil] }
-
- let(:cache_key) do
- "application_rate_limiter:test_action:project:#{project.id}:commit:#{commit.sha}"
- end
+ travel_to(start_time + 2.minutes) do
+ expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
- it_behaves_like 'action rate limiter'
+ # Assert that another_action has its own interval that hasn't elapsed
+ expect(subject.throttled?(:another_action, scope: scope)).to eq(true)
end
end
end
- context 'when rate_limiter_safe_increment is enabled', :clean_gitlab_redis_rate_limiting do
- before do
- stub_feature_flags(rate_limiter_safe_increment: true)
- end
-
- shared_examples 'throttles based on key and scope' do
- let(:start_time) { Time.current.beginning_of_hour }
-
- it 'returns true when threshold is exceeded' do
- travel_to(start_time) do
- expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
- end
-
- travel_to(start_time + 1.minute) do
- expect(subject.throttled?(:test_action, scope: scope)).to eq(true)
-
- # Assert that it does not affect other actions or scope
- expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
- expect(subject.throttled?(:test_action, scope: [user])).to eq(false)
- end
- end
-
- it 'returns false when interval has elapsed' do
- travel_to(start_time) do
- expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
-
- # another_action has a threshold of 3 so we simulate 2 requests
- expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
- expect(subject.throttled?(:another_action, scope: scope)).to eq(false)
- end
-
- travel_to(start_time + 2.minutes) do
- expect(subject.throttled?(:test_action, scope: scope)).to eq(false)
-
- # Assert that another_action has its own interval that hasn't elapsed
- expect(subject.throttled?(:another_action, scope: scope)).to eq(true)
- end
- end
- end
-
- context 'when using ActiveRecord models as scope' do
- let(:scope) { [user, project] }
+ context 'when using ActiveRecord models as scope' do
+ let(:scope) { [user, project] }
- it_behaves_like 'throttles based on key and scope'
- end
+ it_behaves_like 'throttles based on key and scope'
+ end
- context 'when using ActiveRecord models and strings as scope' do
- let(:scope) { [project, 'app/controllers/groups_controller.rb'] }
+ context 'when using ActiveRecord models and strings as scope' do
+ let(:scope) { [project, 'app/controllers/groups_controller.rb'] }
- it_behaves_like 'throttles based on key and scope'
- end
+ it_behaves_like 'throttles based on key and scope'
end
end
diff --git a/spec/lib/gitlab/background_migration/populate_personal_snippet_statistics_spec.rb b/spec/lib/gitlab/background_migration/populate_personal_snippet_statistics_spec.rb
index e746451b1b9..f9628849dbf 100644
--- a/spec/lib/gitlab/background_migration/populate_personal_snippet_statistics_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_personal_snippet_statistics_spec.rb
@@ -111,11 +111,11 @@ RSpec.describe Gitlab::BackgroundMigration::PopulatePersonalSnippetStatistics do
if with_repo
allow(snippet).to receive(:disk_path).and_return(disk_path(snippet))
+ raw_repository(snippet).create_repository
+
TestEnv.copy_repo(snippet,
bare_repo: TestEnv.factory_repo_path_bare,
refs: TestEnv::BRANCH_SHA)
-
- raw_repository(snippet).create_repository
end
end
end
diff --git a/spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb b/spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb
index 897f5e81372..7884e0d97c0 100644
--- a/spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb
@@ -183,11 +183,11 @@ RSpec.describe Gitlab::BackgroundMigration::PopulateProjectSnippetStatistics do
if with_repo
allow(snippet).to receive(:disk_path).and_return(disk_path(snippet))
+ raw_repository(snippet).create_repository
+
TestEnv.copy_repo(snippet,
bare_repo: TestEnv.factory_repo_path_bare,
refs: TestEnv::BRANCH_SHA)
-
- raw_repository(snippet).create_repository
end
end
end
diff --git a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
index cdcc862c376..9d49db1f018 100644
--- a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
@@ -38,7 +38,8 @@ RSpec.describe Gitlab::Database::Count::ReltuplesCountStrategy do
it 'returns nil counts for inherited tables' do
models.each { |model| expect(model).not_to receive(:count) }
- expect(subject).to eq({ Namespace => 3 })
+ # 3 Namespaces as parents for each Project and 3 ProjectNamespaces(for each Project)
+ expect(subject).to eq({ Namespace => 6 })
end
end
diff --git a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
index c2028f8c238..2f261aebf02 100644
--- a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
@@ -47,7 +47,8 @@ RSpec.describe Gitlab::Database::Count::TablesampleCountStrategy do
result = subject
expect(result[Project]).to eq(3)
expect(result[Group]).to eq(1)
- expect(result[Namespace]).to eq(4)
+ # 1-Group, 3 namespaces for each project and 3 project namespaces for each project
+ expect(result[Namespace]).to eq(7)
end
end
diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
index 55102554508..20d5972bd88 100644
--- a/spec/lib/gitlab/gpg/commit_spec.rb
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -136,7 +136,7 @@ RSpec.describe Gitlab::Gpg::Commit do
it 'returns a valid signature' do
verified_signature = double('verified-signature', fingerprint: GpgHelpers::User1.fingerprint, valid?: true)
allow(GPGME::Crypto).to receive(:new).and_return(crypto)
- allow(crypto).to receive(:verify).and_return(verified_signature)
+ allow(crypto).to receive(:verify).and_yield(verified_signature)
signature = described_class.new(commit).signature
@@ -178,7 +178,7 @@ RSpec.describe Gitlab::Gpg::Commit do
keyid = GpgHelpers::User1.fingerprint.last(16)
verified_signature = double('verified-signature', fingerprint: keyid, valid?: true)
allow(GPGME::Crypto).to receive(:new).and_return(crypto)
- allow(crypto).to receive(:verify).and_return(verified_signature)
+ allow(crypto).to receive(:verify).and_yield(verified_signature)
signature = described_class.new(commit).signature
@@ -194,6 +194,71 @@ RSpec.describe Gitlab::Gpg::Commit do
end
end
+ context 'commit with multiple signatures' do
+ let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first }
+
+ let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) }
+
+ let!(:gpg_key) do
+ create :gpg_key, key: GpgHelpers::User1.public_key, user: user
+ end
+
+ let!(:crypto) { instance_double(GPGME::Crypto) }
+
+ before do
+ fake_signature = [
+ GpgHelpers::User1.signed_commit_signature,
+ GpgHelpers::User1.signed_commit_base_data
+ ]
+
+ allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily)
+ .with(Gitlab::Git::Repository, commit_sha)
+ .and_return(fake_signature)
+ end
+
+ it 'returns an invalid signatures error' do
+ verified_signature = double('verified-signature', fingerprint: GpgHelpers::User1.fingerprint, valid?: true)
+ allow(GPGME::Crypto).to receive(:new).and_return(crypto)
+ allow(crypto).to receive(:verify).and_yield(verified_signature).and_yield(verified_signature)
+
+ signature = described_class.new(commit).signature
+
+ expect(signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ gpg_key_user_name: GpgHelpers::User1.names.first,
+ gpg_key_user_email: GpgHelpers::User1.emails.first,
+ verification_status: 'multiple_signatures'
+ )
+ end
+
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(multiple_gpg_signatures: false)
+ end
+
+ it 'returns an valid signature' do
+ verified_signature = double('verified-signature', fingerprint: GpgHelpers::User1.fingerprint, valid?: true)
+ allow(GPGME::Crypto).to receive(:new).and_return(crypto)
+ allow(crypto).to receive(:verify).and_yield(verified_signature).and_yield(verified_signature)
+
+ signature = described_class.new(commit).signature
+
+ expect(signature).to have_attributes(
+ commit_sha: commit_sha,
+ project: project,
+ gpg_key: gpg_key,
+ gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
+ gpg_key_user_name: GpgHelpers::User1.names.first,
+ gpg_key_user_email: GpgHelpers::User1.emails.first,
+ verification_status: 'verified'
+ )
+ end
+ end
+ end
+
context 'commit signed with a subkey' do
let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User3.emails.first }
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index 2f38ed58727..f0ba0f0459d 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -425,6 +425,9 @@ RSpec.describe Gitlab::PathRegex do
it { is_expected.not_to match('gitlab.org/') }
it { is_expected.not_to match('/gitlab.org') }
it { is_expected.not_to match('gitlab git') }
+ it { is_expected.not_to match('gitlab?') }
+ it { is_expected.to match('gitlab.org-') }
+ it { is_expected.to match('gitlab.org_') }
end
describe '.project_path_format_regex' do
@@ -437,6 +440,14 @@ RSpec.describe Gitlab::PathRegex do
it { is_expected.not_to match('?gitlab') }
it { is_expected.not_to match('git lab') }
it { is_expected.not_to match('gitlab.git') }
+ it { is_expected.not_to match('gitlab?') }
+ it { is_expected.not_to match('gitlab git') }
+ it { is_expected.to match('gitlab.org') }
+ it { is_expected.to match('gitlab.org-') }
+ it { is_expected.to match('gitlab.org_') }
+ it { is_expected.to match('gitlab.org.') }
+ it { is_expected.not_to match('gitlab.org/') }
+ it { is_expected.not_to match('/gitlab.org') }
end
context 'repository routes' do
diff --git a/spec/models/concerns/loaded_in_group_list_spec.rb b/spec/models/concerns/loaded_in_group_list_spec.rb
index 94a9a8cde12..d38e842c666 100644
--- a/spec/models/concerns/loaded_in_group_list_spec.rb
+++ b/spec/models/concerns/loaded_in_group_list_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe LoadedInGroupList do
context 'with project namespaces' do
let_it_be(:group1) { create(:group, parent: parent) }
let_it_be(:group2) { create(:group, parent: parent) }
- let_it_be(:project_namespace) { create(:project_namespace, project: project) }
+ let_it_be(:project_namespace) { project.project_namespace }
it 'does not include project_namespaces in the count of subgroups' do
expect(found_group.preloaded_subgroup_count).to eq(3)
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index c326b2d6fcd..2467ef541b2 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -566,7 +566,7 @@ RSpec.describe Group do
context 'when project namespace exists in the group' do
let!(:project) { create(:project, group: group) }
- let!(:project_namespace) { create(:project_namespace, project: project) }
+ let!(:project_namespace) { project.project_namespace }
it 'filters out project namespace' do
expect(group.descendants.find_by_id(project_namespace.id)).to be_nil
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 46a22f64591..8f5860c799c 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -32,9 +32,10 @@ RSpec.describe Namespace do
describe '#children' do
let_it_be(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
- let_it_be(:project_namespace) { create(:project_namespace, parent: group) }
+ let_it_be(:project_with_namespace) { create(:project, namespace: group) }
it 'excludes project namespaces' do
+ expect(project_with_namespace.project_namespace.parent).to eq(group)
expect(group.children).to match_array([subgroup])
end
end
@@ -239,7 +240,9 @@ RSpec.describe Namespace do
let(:namespace) { build(:project_namespace) }
it 'allows to update path to single char' do
- namespace = create(:project_namespace)
+ project = create(:project)
+ namespace = project.project_namespace
+
namespace.update!(path: 'j')
expect(namespace).to be_valid
@@ -342,9 +345,13 @@ RSpec.describe Namespace do
describe '.without_project_namespaces' do
let_it_be(:user_namespace) { create(:user_namespace) }
- let_it_be(:project_namespace) { create(:project_namespace) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:project_namespace) { project.project_namespace }
it 'excludes project namespaces' do
+ expect(project_namespace).not_to be_nil
+ expect(project_namespace.parent).not_to be_nil
+ expect(described_class.all).to include(project_namespace)
expect(described_class.without_project_namespaces).to match_array([namespace, namespace1, namespace2, namespace1sub, namespace2sub, user_namespace, project_namespace.parent])
end
end
@@ -562,7 +569,7 @@ RSpec.describe Namespace do
context 'with project namespaces' do
let_it_be(:project) { create(:project, namespace: parent_group, path: 'some-new-path') }
- let_it_be(:project_namespace) { create(:project_namespace, project: project) }
+ let_it_be(:project_namespace) { project.project_namespace }
it 'does not return project namespace' do
search_result = described_class.search('path')
diff --git a/spec/models/namespaces/project_namespace_spec.rb b/spec/models/namespaces/project_namespace_spec.rb
index f38e8aa85d0..4416c49f1bf 100644
--- a/spec/models/namespaces/project_namespace_spec.rb
+++ b/spec/models/namespaces/project_namespace_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe Namespaces::ProjectNamespace, type: :model do
# using delete rather than destroy due to `delete` skipping AR hooks/callbacks
# so it's ensured to work at the DB level. Uses ON DELETE CASCADE on foreign key
let_it_be(:project) { create(:project) }
- let_it_be(:project_namespace) { create(:project_namespace, project: project) }
+ let_it_be(:project_namespace) { project.project_namespace }
it 'also deletes the associated project' do
project_namespace.delete
diff --git a/spec/models/packages/npm/metadatum_spec.rb b/spec/models/packages/npm/metadatum_spec.rb
new file mode 100644
index 00000000000..ff8cce5310e
--- /dev/null
+++ b/spec/models/packages/npm/metadatum_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Packages::Npm::Metadatum, type: :model do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:package).inverse_of(:npm_metadatum) }
+ end
+
+ describe 'validations' do
+ describe 'package', :aggregate_failures do
+ it { is_expected.to validate_presence_of(:package) }
+
+ it 'ensure npm package type' do
+ metadatum = build(:npm_metadatum)
+
+ metadatum.package = build(:nuget_package)
+
+ expect(metadatum).not_to be_valid
+ expect(metadatum.errors).to contain_exactly('Package type must be NPM')
+ end
+ end
+
+ describe 'package_json', :aggregate_failures do
+ let(:valid_json) { { 'name' => 'foo', 'version' => 'v1.0', 'dist' => { 'tarball' => 'x', 'shasum' => 'x' } } }
+
+ it { is_expected.to allow_value(valid_json).for(:package_json) }
+ it { is_expected.to allow_value(valid_json.merge('extra-field': { 'foo': 'bar' })).for(:package_json) }
+ it { is_expected.to allow_value(with_dist { |dist| dist.merge('extra-field': 'x') }).for(:package_json) }
+
+ %w[name version dist].each do |field|
+ it { is_expected.not_to allow_value(valid_json.except(field)).for(:package_json) }
+ end
+
+ %w[tarball shasum].each do |field|
+ it { is_expected.not_to allow_value(with_dist { |dist| dist.except(field) }).for(:package_json) }
+ end
+
+ it { is_expected.not_to allow_value({}).for(:package_json) }
+
+ it { is_expected.not_to allow_value(test: 'test' * 10000).for(:package_json) }
+
+ def with_dist
+ valid_json.tap do |h|
+ h['dist'] = yield(h['dist'])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
index 12a2a2a94a6..3fca139fc12 100644
--- a/spec/models/packages/package_spec.rb
+++ b/spec/models/packages/package_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.to have_one(:debian_distribution).through(:debian_publication).source(:distribution).inverse_of(:packages).class_name('Packages::Debian::ProjectDistribution') }
it { is_expected.to have_one(:nuget_metadatum).inverse_of(:package) }
it { is_expected.to have_one(:rubygems_metadatum).inverse_of(:package) }
+ it { is_expected.to have_one(:npm_metadatum).inverse_of(:package) }
end
describe '.with_debian_codename' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 4b3a6c731d5..109504ae1d8 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe Project, factory_default: :keep do
describe 'associations' do
it { is_expected.to belong_to(:group) }
it { is_expected.to belong_to(:namespace) }
- it { is_expected.to belong_to(:project_namespace).class_name('Namespaces::ProjectNamespace').with_foreign_key('project_namespace_id').inverse_of(:project) }
+ it { is_expected.to belong_to(:project_namespace).class_name('Namespaces::ProjectNamespace').with_foreign_key('project_namespace_id') }
it { is_expected.to belong_to(:creator).class_name('User') }
it { is_expected.to belong_to(:pool_repository) }
it { is_expected.to have_many(:users) }
@@ -191,7 +191,7 @@ RSpec.describe Project, factory_default: :keep do
# using delete rather than destroy due to `delete` skipping AR hooks/callbacks
# so it's ensured to work at the DB level. Uses AFTER DELETE trigger.
let_it_be(:project) { create(:project) }
- let_it_be(:project_namespace) { create(:project_namespace, project: project) }
+ let_it_be(:project_namespace) { project.project_namespace }
it 'also deletes the associated ProjectNamespace' do
project.delete
@@ -233,6 +233,58 @@ RSpec.describe Project, factory_default: :keep do
expect(project.project_setting).to be_an_instance_of(ProjectSetting)
expect(project.project_setting).to be_new_record
end
+
+ context 'with project namespaces' do
+ it 'automatically creates a project namespace' do
+ project = build(:project, path: 'hopefully-valid-path1')
+ project.save!
+
+ expect(project).to be_persisted
+ expect(project.project_namespace).to be_persisted
+ expect(project.project_namespace).to be_in_sync_with_project(project)
+ end
+
+ context 'with FF disabled' do
+ before do
+ stub_feature_flags(create_project_namespace_on_project_create: false)
+ end
+
+ it 'does not create a project namespace' do
+ project = build(:project, path: 'hopefully-valid-path2')
+ project.save!
+
+ expect(project).to be_persisted
+ expect(project.project_namespace).to be_nil
+ end
+ end
+ end
+ end
+
+ context 'updating a project' do
+ context 'with project namespaces' do
+ it 'keeps project namespace in sync with project' do
+ project = create(:project)
+ project.update!(path: 'hopefully-valid-path1')
+
+ expect(project).to be_persisted
+ expect(project.project_namespace).to be_persisted
+ expect(project.project_namespace).to be_in_sync_with_project(project)
+ end
+
+ context 'with FF disabled' do
+ before do
+ stub_feature_flags(create_project_namespace_on_project_create: false)
+ end
+
+ it 'does not create a project namespace when project is updated' do
+ project = create(:project)
+ project.update!(path: 'hopefully-valid-path1')
+
+ expect(project).to be_persisted
+ expect(project.project_namespace).to be_nil
+ end
+ end
+ end
end
context 'updating cd_cd_settings' do
@@ -322,6 +374,18 @@ RSpec.describe Project, factory_default: :keep do
create(:project)
end
+ context 'validates project namespace creation' do
+ it 'does not create project namespace if project is not created' do
+ project = build(:project, path: 'tree')
+
+ project.valid?
+
+ expect(project).not_to be_valid
+ expect(project).to be_new_record
+ expect(project.project_namespace).to be_new_record
+ end
+ end
+
context 'repository storages inclusion' do
let(:project2) { build(:project, repository_storage: 'missing') }
diff --git a/spec/policies/namespaces/project_namespace_policy_spec.rb b/spec/policies/namespaces/project_namespace_policy_spec.rb
index 22f3ccec1f8..5bb38deb498 100644
--- a/spec/policies/namespaces/project_namespace_policy_spec.rb
+++ b/spec/policies/namespaces/project_namespace_policy_spec.rb
@@ -4,7 +4,8 @@ require 'spec_helper'
RSpec.describe NamespacePolicy do
let_it_be(:parent) { create(:namespace) }
- let_it_be(:namespace) { create(:project_namespace, parent: parent) }
+ let_it_be(:project) { create(:project, namespace: parent) }
+ let_it_be(:namespace) { project.project_namespace }
let(:permissions) do
[:owner_access, :create_projects, :admin_namespace, :read_namespace,
diff --git a/spec/presenters/packages/npm/package_presenter_spec.rb b/spec/presenters/packages/npm/package_presenter_spec.rb
index 65f69d4056b..49046492ab4 100644
--- a/spec/presenters/packages/npm/package_presenter_spec.rb
+++ b/spec/presenters/packages/npm/package_presenter_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe ::Packages::Npm::PackagePresenter do
+ using RSpec::Parameterized::TableSyntax
+
let_it_be(:project) { create(:project) }
let_it_be(:package_name) { "@#{project.root_namespace.path}/test" }
let_it_be(:package1) { create(:npm_package, version: '2.0.4', project: project, name: package_name) }
@@ -13,42 +15,88 @@ RSpec.describe ::Packages::Npm::PackagePresenter do
let(:presenter) { described_class.new(package_name, packages) }
describe '#versions' do
- subject { presenter.versions }
+ let_it_be('package_json') do
+ {
+ 'name': package_name,
+ 'version': '2.0.4',
+ 'deprecated': 'warning!',
+ 'bin': './cli.js',
+ 'directories': ['lib'],
+ 'engines': { 'npm': '^7.5.6' },
+ '_hasShrinkwrap': false,
+ 'dist': {
+ 'tarball': 'http://localhost/tarball.tgz',
+ 'shasum': '1234567890'
+ },
+ 'custom_field': 'foo_bar'
+ }
+ end
- context 'for packages without dependencies' do
- it { is_expected.to be_a(Hash) }
- it { expect(subject[package1.version].with_indifferent_access).to match_schema('public_api/v4/packages/npm_package_version') }
- it { expect(subject[package2.version].with_indifferent_access).to match_schema('public_api/v4/packages/npm_package_version') }
+ let(:presenter) { described_class.new(package_name, packages, include_metadata: include_metadata) }
- ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type|
- it { expect(subject.dig(package1.version, dependency_type)).to be nil }
- it { expect(subject.dig(package2.version, dependency_type)).to be nil }
- end
+ subject { presenter.versions }
- it 'avoids N+1 database queries' do
- check_n_plus_one(:versions) do
- create_list(:npm_package, 5, project: project, name: package_name)
+ where(:has_dependencies, :has_metadatum, :include_metadata) do
+ true | true | true
+ false | true | true
+ true | false | true
+ false | false | true
+
+ # TODO : to remove along with packages_npm_abbreviated_metadata
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/344827
+ true | true | false
+ false | true | false
+ true | false | false
+ false | false | false
+ end
+
+ with_them do
+ if params[:has_dependencies]
+ ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type|
+ let_it_be("package_dependency_link_for_#{dependency_type}") { create(:packages_dependency_link, package: package1, dependency_type: dependency_type) }
end
end
- end
- context 'for packages with dependencies' do
- ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type|
- let_it_be("package_dependency_link_for_#{dependency_type}") { create(:packages_dependency_link, package: package1, dependency_type: dependency_type) }
+ if params[:has_metadatum]
+ let_it_be('package_metadatadum') { create(:npm_metadatum, package: package1, package_json: package_json) }
end
it { is_expected.to be_a(Hash) }
it { expect(subject[package1.version].with_indifferent_access).to match_schema('public_api/v4/packages/npm_package_version') }
it { expect(subject[package2.version].with_indifferent_access).to match_schema('public_api/v4/packages/npm_package_version') }
- ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type|
- it { expect(subject.dig(package1.version, dependency_type.to_s)).to be_any }
+ it { expect(subject[package1.version]['custom_field']).to be_blank }
+
+ context 'dependencies' do
+ ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type|
+ if params[:has_dependencies]
+ it { expect(subject.dig(package1.version, dependency_type.to_s)).to be_any }
+ else
+ it { expect(subject.dig(package1.version, dependency_type)).to be nil }
+ end
+
+ it { expect(subject.dig(package2.version, dependency_type)).to be nil }
+ end
+ end
+
+ context 'metadatum' do
+ ::Packages::Npm::PackagePresenter::PACKAGE_JSON_ALLOWED_FIELDS.each do |metadata_field|
+ if params[:has_metadatum] && params[:include_metadata]
+ it { expect(subject.dig(package1.version, metadata_field)).not_to be nil }
+ else
+ it { expect(subject.dig(package1.version, metadata_field)).to be nil }
+ end
+
+ it { expect(subject.dig(package2.version, metadata_field)).to be nil }
+ end
end
it 'avoids N+1 database queries' do
check_n_plus_one(:versions) do
create_list(:npm_package, 5, project: project, name: package_name).each do |npm_package|
- ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type|
- create(:packages_dependency_link, package: npm_package, dependency_type: dependency_type)
+ if has_dependencies
+ ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type|
+ create(:packages_dependency_link, package: npm_package, dependency_type: dependency_type)
+ end
end
end
end
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb
index 2143abd3031..bea2365eaa6 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_wip_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_draft_spec.rb
@@ -8,14 +8,14 @@ RSpec.describe 'Setting Draft status of a merge request' do
let(:current_user) { create(:user) }
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project }
- let(:input) { { wip: true } }
+ let(:input) { { draft: true } }
let(:mutation) do
variables = {
project_path: project.full_path,
iid: merge_request.iid.to_s
}
- graphql_mutation(:merge_request_set_wip, variables.merge(input),
+ graphql_mutation(:merge_request_set_draft, variables.merge(input),
<<-QL.strip_heredoc
clientMutationId
errors
@@ -28,7 +28,7 @@ RSpec.describe 'Setting Draft status of a merge request' do
end
def mutation_response
- graphql_mutation_response(:merge_request_set_wip)
+ graphql_mutation_response(:merge_request_set_draft)
end
before do
@@ -58,7 +58,7 @@ RSpec.describe 'Setting Draft status of a merge request' do
end
context 'when passing Draft false as input' do
- let(:input) { { wip: false } }
+ let(:input) { { draft: false } }
it 'does not do anything if the merge reqeust was not marked draft' do
post_graphql_mutation(mutation, current_user: current_user)
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 43bbc6f3ca8..01dbf523071 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe API::Namespaces do
let_it_be(:group1) { create(:group, name: 'group.one') }
let_it_be(:group2) { create(:group, :nested) }
let_it_be(:project) { create(:project, namespace: group2, name: group2.name, path: group2.path) }
- let_it_be(:project_namespace) { create(:project_namespace, project: project) }
+ let_it_be(:project_namespace) { project.project_namespace }
describe "GET /namespaces" do
context "when unauthenticated" do
diff --git a/spec/requests/api/npm_project_packages_spec.rb b/spec/requests/api/npm_project_packages_spec.rb
index 0d04c2cad5b..7c3f1890095 100644
--- a/spec/requests/api/npm_project_packages_spec.rb
+++ b/spec/requests/api/npm_project_packages_spec.rb
@@ -180,6 +180,7 @@ RSpec.describe API::NpmProjectPackages do
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
.and change { Packages::Tag.count }.by(1)
+ .and change { Packages::Npm::Metadatum.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
end
@@ -317,6 +318,25 @@ RSpec.describe API::NpmProjectPackages do
end
end
end
+
+ context 'with a too large metadata structure' do
+ let(:package_name) { "@#{group.path}/my_package_name" }
+ let(:params) do
+ upload_params(package_name: package_name, package_version: '1.2.3').tap do |h|
+ h['versions']['1.2.3']['test'] = 'test' * 10000
+ end
+ end
+
+ it_behaves_like 'not a package tracking event'
+
+ it 'returns an error' do
+ expect { upload_package_with_token }
+ .not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response.body).to include('Validation failed: Package json structure is too large')
+ end
+ end
end
def upload_package(package_name, params = {})
diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb
index 0c9e125cc90..097d374640c 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 do
it 'executes a limited number of queries' do
control_count = ActiveRecord::QueryRecorder.new { subject }.count
- expect(control_count).to be <= 100
+ expect(control_count).to be <= 101
end
it 'schedules an import using a namespace' do
diff --git a/spec/services/dependency_proxy/find_or_create_blob_service_spec.rb b/spec/services/dependency_proxy/find_or_create_blob_service_spec.rb
index 20b0546effa..5f7afdf699a 100644
--- a/spec/services/dependency_proxy/find_or_create_blob_service_spec.rb
+++ b/spec/services/dependency_proxy/find_or_create_blob_service_spec.rb
@@ -39,7 +39,7 @@ RSpec.describe DependencyProxy::FindOrCreateBlobService do
let(:blob_sha) { blob.file_name.sub('.gz', '') }
it 'uses cached blob instead of downloading one' do
- expect { subject }.to change { blob.reload.updated_at }
+ expect { subject }.to change { blob.reload.read_at }
expect(subject[:status]).to eq(:success)
expect(subject[:blob]).to be_a(DependencyProxy::Blob)
diff --git a/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb b/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb
index 407ee42d345..ef608c9b113 100644
--- a/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb
+++ b/spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb
@@ -84,7 +84,7 @@ RSpec.describe DependencyProxy::FindOrCreateManifestService do
shared_examples 'using the cached manifest' do
it 'uses cached manifest instead of downloading one', :aggregate_failures do
- subject
+ expect { subject }.to change { dependency_proxy_manifest.reload.read_at }
expect(subject[:status]).to eq(:success)
expect(subject[:manifest]).to be_a(DependencyProxy::Manifest)
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index 21aa213a8ff..6712dccd249 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -185,9 +185,7 @@ RSpec.describe Groups::TransferService, :sidekiq_inline do
context 'when projects have project namespaces' do
let_it_be(:project1) { create(:project, :private, namespace: group) }
- let_it_be(:project_namespace1) { create(:project_namespace, project: project1) }
let_it_be(:project2) { create(:project, :private, namespace: group) }
- let_it_be(:project_namespace2) { create(:project_namespace, project: project2) }
it_behaves_like 'project namespace path is in sync with project path' do
let(:group_full_path) { "#{group.path}" }
@@ -250,33 +248,42 @@ RSpec.describe Groups::TransferService, :sidekiq_inline do
let_it_be(:membership) { create(:group_member, :owner, group: new_parent_group, user: user) }
let_it_be(:project) { create(:project, path: 'foo', namespace: new_parent_group) }
- before do
- group.update_attribute(:path, 'foo')
- end
-
- it 'returns false' do
- expect(transfer_service.execute(new_parent_group)).to be_falsy
- end
-
it 'adds an error on group' do
- transfer_service.execute(new_parent_group)
- expect(transfer_service.error).to eq('Transfer failed: Validation failed: Group URL has already been taken')
+ expect(transfer_service.execute(new_parent_group)).to be_falsy
+ expect(transfer_service.error).to eq('Transfer failed: The parent group already has a subgroup or a project with the same path.')
end
- context 'when projects have project namespaces' do
- let!(:project_namespace) { create(:project_namespace, project: project) }
-
+ # currently when a project is created it gets a corresponding project namespace
+ # so we test the case where a project without a project namespace is transferred
+ # for backward compatibility
+ context 'without project namespace' do
before do
- transfer_service.execute(new_parent_group)
+ project_namespace = project.project_namespace
+ project.update_column(:project_namespace_id, nil)
+ project_namespace.delete
end
- it_behaves_like 'project namespace path is in sync with project path' do
- let(:group_full_path) { "#{new_parent_group.full_path}" }
- let(:projects_with_project_namespace) { [project] }
+ it 'adds an error on group' do
+ expect(project.reload.project_namespace).to be_nil
+ expect(transfer_service.execute(new_parent_group)).to be_falsy
+ expect(transfer_service.error).to eq('Transfer failed: Validation failed: Group URL has already been taken')
end
end
end
+ context 'when projects have project namespaces' do
+ let_it_be(:project) { create(:project, path: 'foo', namespace: new_parent_group) }
+
+ before do
+ transfer_service.execute(new_parent_group)
+ end
+
+ it_behaves_like 'project namespace path is in sync with project path' do
+ let(:group_full_path) { "#{new_parent_group.full_path}" }
+ let(:projects_with_project_namespace) { [project] }
+ end
+ end
+
context 'when the group is allowed to be transferred' do
let_it_be(:new_parent_group, reload: true) { create(:group, :public) }
let_it_be(:new_parent_group_integration) { create(:integrations_slack, group: new_parent_group, project: nil, webhook: 'http://new-group.slack.com') }
@@ -445,8 +452,6 @@ RSpec.describe Groups::TransferService, :sidekiq_inline do
context 'when transferring a group with project descendants' do
let!(:project1) { create(:project, :repository, :private, namespace: group) }
let!(:project2) { create(:project, :repository, :internal, namespace: group) }
- let!(:project_namespace1) { create(:project_namespace, project: project1) }
- let!(:project_namespace2) { create(:project_namespace, project: project2) }
before do
TestEnv.clean_test_path
@@ -483,8 +488,6 @@ RSpec.describe Groups::TransferService, :sidekiq_inline do
let!(:project1) { create(:project, :repository, :public, namespace: group) }
let!(:project2) { create(:project, :repository, :public, namespace: group) }
let!(:new_parent_group) { create(:group, :private) }
- let!(:project_namespace1) { create(:project_namespace, project: project1) }
- let!(:project_namespace2) { create(:project_namespace, project: project2) }
it 'updates projects visibility to match the new parent' do
group.projects.each do |project|
@@ -504,8 +507,6 @@ RSpec.describe Groups::TransferService, :sidekiq_inline do
let!(:project2) { create(:project, :repository, :internal, namespace: group) }
let!(:subgroup1) { create(:group, :private, parent: group) }
let!(:subgroup2) { create(:group, :internal, parent: group) }
- let!(:project_namespace1) { create(:project_namespace, project: project1) }
- let!(:project_namespace2) { create(:project_namespace, project: project2) }
before do
TestEnv.clean_test_path
diff --git a/spec/services/packages/npm/create_package_service_spec.rb b/spec/services/packages/npm/create_package_service_spec.rb
index ba5729eaf59..9598355a640 100644
--- a/spec/services/packages/npm/create_package_service_spec.rb
+++ b/spec/services/packages/npm/create_package_service_spec.rb
@@ -16,6 +16,7 @@ RSpec.describe Packages::Npm::CreatePackageService do
let(:override) { {} }
let(:package_name) { "@#{namespace.path}/my-app" }
+ let(:version_data) { params.dig('versions', '1.0.1') }
subject { described_class.new(project, user, params).execute }
@@ -25,6 +26,7 @@ RSpec.describe Packages::Npm::CreatePackageService do
.to change { Packages::Package.count }.by(1)
.and change { Packages::Package.npm.count }.by(1)
.and change { Packages::Tag.count }.by(1)
+ .and change { Packages::Npm::Metadatum.count }.by(1)
end
it_behaves_like 'assigns the package creator' do
@@ -40,6 +42,8 @@ RSpec.describe Packages::Npm::CreatePackageService do
expect(package.version).to eq(version)
end
+ it { expect(subject.npm_metadatum.package_json).to eq(version_data) }
+
it { expect(subject.name).to eq(package_name) }
it { expect(subject.version).to eq(version) }
@@ -54,6 +58,31 @@ RSpec.describe Packages::Npm::CreatePackageService do
expect { subject }.to change { Packages::PackageFileBuildInfo.count }.by(1)
end
end
+
+ context 'with a too large metadata structure' do
+ before do
+ params[:versions][version][:test] = 'test' * 10000
+ end
+
+ it 'does not create the package' do
+ expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Package json structure is too large')
+ .and not_change { Packages::Package.count }
+ .and not_change { Packages::Package.npm.count }
+ .and not_change { Packages::Tag.count }
+ .and not_change { Packages::Npm::Metadatum.count }
+ end
+ end
+
+ context 'with packages_npm_abbreviated_metadata disabled' do
+ before do
+ stub_feature_flags(packages_npm_abbreviated_metadata: false)
+ end
+
+ it 'creates a package without metadatum' do
+ expect { subject }
+ .not_to change { Packages::Npm::Metadatum.count }
+ end
+ end
end
describe '#execute' do
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index bce65d19aae..d903523840c 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -49,6 +49,7 @@ RSpec.describe Projects::CreateService, '#execute' do
it 'keeps them as specified' do
expect(project.name).to eq('one')
expect(project.path).to eq('two')
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
@@ -58,6 +59,7 @@ RSpec.describe Projects::CreateService, '#execute' do
it 'sets name == path' do
expect(project.path).to eq('one.two_three-four')
expect(project.name).to eq(project.path)
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
@@ -67,6 +69,7 @@ RSpec.describe Projects::CreateService, '#execute' do
it 'sets path == name' do
expect(project.name).to eq('one.two_three-four')
expect(project.path).to eq(project.name)
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
@@ -78,6 +81,7 @@ RSpec.describe Projects::CreateService, '#execute' do
it 'parameterizes the name' do
expect(project.name).to eq('one.two_three-four and five')
expect(project.path).to eq('one-two_three-four-and-five')
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
end
@@ -111,13 +115,14 @@ RSpec.describe Projects::CreateService, '#execute' do
end
context 'user namespace' do
- it do
+ it 'creates a project in user namespace' do
project = create_project(user, opts)
expect(project).to be_valid
expect(project.owner).to eq(user)
expect(project.team.maintainers).to include(user)
expect(project.namespace).to eq(user.namespace)
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
@@ -151,6 +156,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project.owner).to eq(user)
expect(project.team.maintainers).to contain_exactly(user)
expect(project.namespace).to eq(user.namespace)
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
@@ -160,6 +166,7 @@ RSpec.describe Projects::CreateService, '#execute' do
project = create_project(admin, opts)
expect(project).not_to be_persisted
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
end
@@ -183,6 +190,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project.namespace).to eq(group)
expect(project.team.owners).to include(user)
expect(user.authorized_projects).to include(project)
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
@@ -339,6 +347,7 @@ RSpec.describe Projects::CreateService, '#execute' do
end
imported_project
+ expect(imported_project.project_namespace).to be_in_sync_with_project(imported_project)
end
it 'stores import data and URL' do
@@ -406,6 +415,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project.visibility_level).to eq(project_level)
expect(project).to be_saved
expect(project).to be_valid
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
end
@@ -424,6 +434,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project.errors.messages[:visibility_level].first).to(
match('restricted by your GitLab administrator')
)
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
it 'does not allow a restricted visibility level for admins when admin mode is disabled' do
@@ -493,6 +504,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to be_valid
expect(project.owner).to eq(user)
expect(project.namespace).to eq(user.namespace)
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
context 'when another repository already exists on disk' do
@@ -522,6 +534,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to respond_to(:errors)
expect(project.errors.messages).to have_key(:base)
expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
it 'does not allow to import project when path matches existing repository on disk' do
@@ -531,6 +544,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to respond_to(:errors)
expect(project.errors.messages).to have_key(:base)
expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
@@ -555,6 +569,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to respond_to(:errors)
expect(project.errors.messages).to have_key(:base)
expect(project.errors.messages[:base].first).to match('There is already a repository with that name on disk')
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
end
@@ -870,6 +885,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to be_valid
expect(project.shared_runners_enabled).to eq(expected_result_for_project)
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
end
@@ -890,6 +906,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to be_valid
expect(project.shared_runners_enabled).to eq(expected_result_for_project)
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
end
@@ -907,6 +924,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project.persisted?).to eq(false)
expect(project).to be_invalid
expect(project.errors[:shared_runners_enabled]).to include('cannot be enabled because parent group does not allow it')
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
end
@@ -926,6 +944,7 @@ RSpec.describe Projects::CreateService, '#execute' do
expect(project).to be_valid
expect(project.shared_runners_enabled).to eq(expected_result)
+ expect(project.project_namespace).to be_in_sync_with_project(project)
end
end
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index b539b01066e..2164580a158 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -66,8 +66,6 @@ RSpec.describe Projects::TransferService do
end
context 'when project has an associated project namespace' do
- let!(:project_namespace) { create(:project_namespace, project: project) }
-
it 'keeps project namespace in sync with project' do
transfer_result = execute_transfer
@@ -272,8 +270,6 @@ RSpec.describe Projects::TransferService do
end
context 'when project has an associated project namespace' do
- let!(:project_namespace) { create(:project_namespace, project: project) }
-
it 'keeps project namespace in sync with project' do
attempt_project_transfer
@@ -294,8 +290,6 @@ RSpec.describe Projects::TransferService do
end
context 'when project has an associated project namespace' do
- let!(:project_namespace) { create(:project_namespace, project: project) }
-
it 'keeps project namespace in sync with project' do
transfer_result = execute_transfer
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 6a0d9ea0669..47aea2c1c37 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -197,6 +197,14 @@ RSpec.configure do |config|
if ENV['CI'] || ENV['RETRIES']
# This includes the first try, i.e. tests will be run 4 times before failing.
config.default_retry_count = ENV.fetch('RETRIES', 3).to_i + 1
+
+ # Do not retry controller tests because rspec-retry cannot properly
+ # reset the controller which may contain data from last attempt. See
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73360
+ config.around(:each, type: :controller) do |example|
+ example.run_with_retry(retry: 1)
+ end
+
config.exceptions_to_hard_fail = [DeprecationToolkitEnv::DeprecationBehaviors::SelectiveRaise::RaiseDisallowedDeprecation]
end
diff --git a/spec/support/helpers/gpg_helpers.rb b/spec/support/helpers/gpg_helpers.rb
index 813c6176317..81e669aab57 100644
--- a/spec/support/helpers/gpg_helpers.rb
+++ b/spec/support/helpers/gpg_helpers.rb
@@ -4,6 +4,7 @@ module GpgHelpers
SIGNED_COMMIT_SHA = '8a852d50dda17cc8fd1408d2fd0c5b0f24c76ca4'
SIGNED_AND_AUTHORED_SHA = '3c1d9a0266cb0c62d926f4a6c649beed561846f5'
DIFFERING_EMAIL_SHA = 'a17a9f66543673edf0a3d1c6b93bdda3fe600f32'
+ MULTIPLE_SIGNATURES_SHA = 'c7794c14268d67ad8a2d5f066d706539afc75a96'
module User1
extend self
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 3b89820be07..ae5687c1e2d 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -9,7 +9,7 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
- 'signed-commits' => '6101e87',
+ 'signed-commits' => 'c7794c1',
'not-merged-branch' => 'b83d6e3',
'branch-merged' => '498214d',
'empty-branch' => '7efb185',
diff --git a/spec/support/matchers/project_namespace_matcher.rb b/spec/support/matchers/project_namespace_matcher.rb
new file mode 100644
index 00000000000..95aa5429679
--- /dev/null
+++ b/spec/support/matchers/project_namespace_matcher.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :be_in_sync_with_project do |project|
+ match do |project_namespace|
+ # if project is not persisted make sure we do not have a persisted project_namespace for it
+ break false if project.new_record? && project_namespace&.persisted?
+ # don't really care if project is not in sync if the project was never persisted.
+ break true if project.new_record? && !project_namespace.present?
+
+ project_namespace.present? &&
+ project.name == project_namespace.name &&
+ project.path == project_namespace.path &&
+ project.namespace == project_namespace.parent &&
+ project.visibility_level == project_namespace.visibility_level &&
+ project.shared_runners_enabled == project_namespace.shared_runners_enabled
+ end
+
+ failure_message_when_negated do |project_namespace|
+ if project.new_record? && project_namespace&.persisted?
+ "expected that a non persisted project #{project} does not have a persisted project namespace #{project_namespace}"
+ else
+ <<-MSG
+ expected that the project's attributes name, path, namespace_id, visibility_level, shared_runners_enabled
+ are in sync with the corresponding project namespace attributes
+ MSG
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb b/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb
index a4e0d6c871e..2d08de297a3 100644
--- a/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb
@@ -11,18 +11,18 @@ RSpec.shared_examples 'ttl_expirable' do
it { is_expected.to validate_presence_of(:status) }
end
- describe '.updated_before' do
+ describe '.read_before' do
# rubocop:disable Rails/SaveBang
let_it_be_with_reload(:item1) { create(class_symbol) }
let_it_be(:item2) { create(class_symbol) }
# rubocop:enable Rails/SaveBang
before do
- item1.update_column(:updated_at, 1.month.ago)
+ item1.update_column(:read_at, 1.month.ago)
end
it 'returns items with created at older than the supplied number of days' do
- expect(described_class.updated_before(10)).to contain_exactly(item1)
+ expect(described_class.read_before(10)).to contain_exactly(item1)
end
end
@@ -48,4 +48,13 @@ RSpec.shared_examples 'ttl_expirable' do
expect(described_class.lock_next_by(:created_at)).to contain_exactly(item3)
end
end
+
+ describe '#read', :freeze_time do
+ let_it_be(:old_read_at) { 1.day.ago }
+ let_it_be(:item1) { create(class_symbol, read_at: old_read_at) }
+
+ it 'updates read_at' do
+ expect { item1.read! }.to change { item1.reload.read_at }
+ end
+ end
end
diff --git a/spec/support/shared_examples/namespaces/traversal_examples.rb b/spec/support/shared_examples/namespaces/traversal_examples.rb
index 0e60476f10a..ac6a843663f 100644
--- a/spec/support/shared_examples/namespaces/traversal_examples.rb
+++ b/spec/support/shared_examples/namespaces/traversal_examples.rb
@@ -23,7 +23,7 @@ RSpec.shared_examples 'namespace traversal' do
let_it_be(:very_deep_nested_group) { create(:group, parent: deep_nested_group) }
let_it_be(:groups) { [group, nested_group, deep_nested_group, very_deep_nested_group] }
let_it_be(:project) { create(:project, group: nested_group) }
- let_it_be(:project_namespace) { create(:project_namespace, project: project) }
+ let_it_be(:project_namespace) { project.project_namespace }
describe '#root_ancestor' do
it 'returns the correct root ancestor' do
diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
index 2af7b616659..19677e92001 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
@@ -8,6 +8,8 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
let_it_be(:package_dependency_link3) { create(:packages_dependency_link, package: package, dependency_type: :bundleDependencies) }
let_it_be(:package_dependency_link4) { create(:packages_dependency_link, package: package, dependency_type: :peerDependencies) }
+ let_it_be(:package_metadatum) { create(:npm_metadatum, package: package) }
+
let(:headers) { {} }
subject { get(url, headers: headers) }
@@ -39,6 +41,19 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project|
# query count can slightly change between the examples so we're using a custom threshold
expect { get(url, headers: headers) }.not_to exceed_query_limit(control).with_threshold(4)
end
+
+ context 'with packages_npm_abbreviated_metadata disabled' do
+ before do
+ stub_feature_flags(packages_npm_abbreviated_metadata: false)
+ end
+
+ it 'calls the presenter without including metadata' do
+ expect(::Packages::Npm::PackagePresenter)
+ .to receive(:new).with(anything, anything, include_metadata: false).and_call_original
+
+ subject
+ end
+ end
end
shared_examples 'reject metadata request' do |status:|
diff --git a/spec/views/jira_connect/subscriptions/index.html.haml_spec.rb b/spec/views/jira_connect/subscriptions/index.html.haml_spec.rb
index dcc36c93327..0a4d283a983 100644
--- a/spec/views/jira_connect/subscriptions/index.html.haml_spec.rb
+++ b/spec/views/jira_connect/subscriptions/index.html.haml_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe 'jira_connect/subscriptions/index.html.haml' do
before do
allow(view).to receive(:current_user).and_return(user)
- assign(:subscriptions, [])
+ assign(:subscriptions, create_list(:jira_connect_subscription, 1))
end
context 'when the user is signed in' do
diff --git a/spec/views/layouts/_published_experiments.html.haml_spec.rb b/spec/views/layouts/_published_experiments.html.haml_spec.rb
new file mode 100644
index 00000000000..d1ade8ddd6e
--- /dev/null
+++ b/spec/views/layouts/_published_experiments.html.haml_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'layouts/_published_experiments', :experiment do
+ before do
+ stub_const('TestControlExperiment', ApplicationExperiment)
+ stub_const('TestCandidateExperiment', ApplicationExperiment)
+ stub_const('TestExcludedExperiment', ApplicationExperiment)
+
+ TestControlExperiment.new('test_control').tap do |e|
+ e.variant(:control)
+ e.publish
+ end
+ TestCandidateExperiment.new('test_candidate').tap do |e|
+ e.variant(:candidate)
+ e.publish
+ end
+ TestExcludedExperiment.new('test_excluded').tap do |e|
+ e.exclude!
+ e.publish
+ end
+
+ render
+ end
+
+ it 'renders out data for all non-excluded, published experiments' do
+ output = rendered
+
+ expect(output).to include('gl.experiments = {')
+ expect(output).to match(/"test_control":\{[^}]*"variant":"control"/)
+ expect(output).to match(/"test_candidate":\{[^}]*"variant":"candidate"/)
+ expect(output).not_to include('"test_excluded"')
+ end
+end
diff --git a/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb b/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb
index d3234f4c212..ae0cb097ebf 100644
--- a/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb
+++ b/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb
@@ -12,8 +12,8 @@ RSpec.describe DependencyProxy::ImageTtlGroupPolicyWorker do
subject { worker.perform }
context 'when there are images to expire' do
- let_it_be_with_reload(:old_blob) { create(:dependency_proxy_blob, group: group, updated_at: 1.year.ago) }
- let_it_be_with_reload(:old_manifest) { create(:dependency_proxy_manifest, group: group, updated_at: 1.year.ago) }
+ let_it_be_with_reload(:old_blob) { create(:dependency_proxy_blob, group: group, read_at: 1.year.ago) }
+ let_it_be_with_reload(:old_manifest) { create(:dependency_proxy_manifest, group: group, read_at: 1.year.ago) }
let_it_be_with_reload(:new_blob) { create(:dependency_proxy_blob, group: group) }
let_it_be_with_reload(:new_manifest) { create(:dependency_proxy_manifest, group: group) }