diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-10 18:12:35 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-10 18:12:35 +0000 |
commit | 6fd750c19206412cfc52b49a70b56147d839c52f (patch) | |
tree | 04607e6c9864c09dd312d6bfc3efe9cc5f81c762 /spec | |
parent | 26881dd926cfac47c9603d44e8d5a504ab8c4a14 (diff) | |
download | gitlab-ce-6fd750c19206412cfc52b49a70b56147d839c52f.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
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) } |