diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-01-26 18:09:04 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-01-26 18:09:04 +0300 |
commit | ee24c7d68f57a67754a5d1e2ea99f688029d14bd (patch) | |
tree | 8391744a26dd3f77c4bb1bbb55672ba0e066d969 /spec | |
parent | a1c0b634f78f51389fd3ec390a1803afa3de49a2 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
21 files changed, 447 insertions, 220 deletions
diff --git a/spec/frontend/invite_members/components/invite_modal_base_spec.js b/spec/frontend/invite_members/components/invite_modal_base_spec.js index e1b90332f6c..4c7022c8684 100644 --- a/spec/frontend/invite_members/components/invite_modal_base_spec.js +++ b/spec/frontend/invite_members/components/invite_modal_base_spec.js @@ -18,6 +18,7 @@ import { INVITE_BUTTON_TEXT_DISABLED, INVITE_BUTTON_TEXT, ON_SHOW_TRACK_LABEL, + ON_CELEBRATION_TRACK_LABEL, } from '~/invite_members/constants'; import { propsData, membersPath, purchasePath } from '../mock_data/modal_base'; @@ -237,16 +238,16 @@ describe('InviteModalBase', () => { const mockEvent = { preventDefault: jest.fn() }; modal.vm.$emit('shown'); - expectTracking('render'); + expectTracking('render', ON_CELEBRATION_TRACK_LABEL); modal.vm.$emit('primary', mockEvent); - expectTracking('click_invite'); + expectTracking('click_invite', ON_CELEBRATION_TRACK_LABEL); modal.vm.$emit('cancel', mockEvent); - expectTracking('click_cancel'); + expectTracking('click_cancel', ON_CELEBRATION_TRACK_LABEL); modal.vm.$emit('close'); - expectTracking('click_x'); + expectTracking('click_x', ON_CELEBRATION_TRACK_LABEL); unmockTracking(); }); diff --git a/spec/frontend/issues/list/components/empty_state_without_any_issues_spec.js b/spec/frontend/issues/list/components/empty_state_without_any_issues_spec.js index f2d12511707..0a2e4e7c671 100644 --- a/spec/frontend/issues/list/components/empty_state_without_any_issues_spec.js +++ b/spec/frontend/issues/list/components/empty_state_without_any_issues_spec.js @@ -2,7 +2,7 @@ import { GlEmptyState, GlLink } from '@gitlab/ui'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue'; import EmptyStateWithoutAnyIssues from '~/issues/list/components/empty_state_without_any_issues.vue'; -import NewIssueDropdown from '~/vue_shared/components/new_issue_dropdown/new_issue_dropdown.vue'; +import NewResourceDropdown from '~/vue_shared/components/new_resource_dropdown/new_resource_dropdown.vue'; import { i18n } from '~/issues/list/constants'; describe('EmptyStateWithoutAnyIssues component', () => { @@ -32,7 +32,7 @@ describe('EmptyStateWithoutAnyIssues component', () => { wrapper.findByRole('link', { name: i18n.noIssuesDescription }); const findJiraDocsLink = () => wrapper.findByRole('link', { name: 'Enable the Jira integration' }); - const findNewIssueDropdown = () => wrapper.findComponent(NewIssueDropdown); + const findNewResourceDropdown = () => wrapper.findComponent(NewResourceDropdown); const findNewIssueLink = () => wrapper.findByRole('link', { name: i18n.newIssueLabel }); const findNewProjectLink = () => wrapper.findByRole('link', { name: i18n.newProjectLabel }); @@ -47,7 +47,7 @@ describe('EmptyStateWithoutAnyIssues component', () => { ...provide, }, stubs: { - NewIssueDropdown: true, + NewResourceDropdown: true, }, }); }; @@ -156,7 +156,7 @@ describe('EmptyStateWithoutAnyIssues component', () => { it('renders', () => { mountComponent({ props: { showNewIssueDropdown: true } }); - expect(findNewIssueDropdown().exists()).toBe(true); + expect(findNewResourceDropdown().exists()).toBe(true); }); }); @@ -164,7 +164,7 @@ describe('EmptyStateWithoutAnyIssues component', () => { it('does not render', () => { mountComponent({ props: { showNewIssueDropdown: false } }); - expect(findNewIssueDropdown().exists()).toBe(false); + expect(findNewResourceDropdown().exists()).toBe(false); }); }); }); diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js index c1e2a460c08..dc01f37df27 100644 --- a/spec/frontend/issues/list/components/issues_list_app_spec.js +++ b/spec/frontend/issues/list/components/issues_list_app_spec.js @@ -30,7 +30,7 @@ import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/con import EmptyStateWithAnyIssues from '~/issues/list/components/empty_state_with_any_issues.vue'; import EmptyStateWithoutAnyIssues from '~/issues/list/components/empty_state_without_any_issues.vue'; import IssuesListApp from '~/issues/list/components/issues_list_app.vue'; -import NewIssueDropdown from '~/vue_shared/components/new_issue_dropdown/new_issue_dropdown.vue'; +import NewResourceDropdown from '~/vue_shared/components/new_resource_dropdown/new_resource_dropdown.vue'; import { CREATED_DESC, RELATIVE_POSITION, @@ -130,7 +130,7 @@ describe('CE IssuesListApp component', () => { const findGlButtons = () => wrapper.findAllComponents(GlButton); const findGlButtonAt = (index) => findGlButtons().at(index); const findIssuableList = () => wrapper.findComponent(IssuableList); - const findNewIssueDropdown = () => wrapper.findComponent(NewIssueDropdown); + const findNewResourceDropdown = () => wrapper.findComponent(NewResourceDropdown); const findLabelsToken = () => findIssuableList() @@ -320,13 +320,13 @@ describe('CE IssuesListApp component', () => { it('does not render in a project context', () => { wrapper = mountComponent({ provide: { isProject: true }, mountFn: mount }); - expect(findNewIssueDropdown().exists()).toBe(false); + expect(findNewResourceDropdown().exists()).toBe(false); }); it('renders in a group context', () => { wrapper = mountComponent({ provide: { isProject: false }, mountFn: mount }); - expect(findNewIssueDropdown().exists()).toBe(true); + expect(findNewResourceDropdown().exists()).toBe(true); }); }); }); diff --git a/spec/frontend/vue_shared/components/new_issue_dropdown/mock_data.js b/spec/frontend/vue_shared/components/new_resource_dropdown/mock_data.js index 792506cda7a..19b1453e8ac 100644 --- a/spec/frontend/vue_shared/components/new_issue_dropdown/mock_data.js +++ b/spec/frontend/vue_shared/components/new_resource_dropdown/mock_data.js @@ -17,7 +17,6 @@ export const emptySearchProjectsWithinGroupQueryResponse = { export const project1 = { id: 'gid://gitlab/Group/26', - issuesEnabled: true, name: 'Super Mario Project', nameWithNamespace: 'Mushroom Kingdom / Super Mario Project', webUrl: 'https://127.0.0.1:3000/mushroom-kingdom/super-mario-project', @@ -25,7 +24,6 @@ export const project1 = { export const project2 = { id: 'gid://gitlab/Group/59', - issuesEnabled: false, name: 'Mario Kart Project', nameWithNamespace: 'Mushroom Kingdom / Mario Kart Project', webUrl: 'https://127.0.0.1:3000/mushroom-kingdom/mario-kart-project', @@ -33,7 +31,6 @@ export const project2 = { export const project3 = { id: 'gid://gitlab/Group/103', - issuesEnabled: true, name: 'Mario Party Project', nameWithNamespace: 'Mushroom Kingdom / Mario Party Project', webUrl: 'https://127.0.0.1:3000/mushroom-kingdom/mario-party-project', diff --git a/spec/frontend/vue_shared/components/new_issue_dropdown/new_issue_dropdown_spec.js b/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js index be9529202a7..31320b1d2a6 100644 --- a/spec/frontend/vue_shared/components/new_issue_dropdown/new_issue_dropdown_spec.js +++ b/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js @@ -4,8 +4,9 @@ import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import NewIssueDropdown from '~/vue_shared/components/new_issue_dropdown/new_issue_dropdown.vue'; -import searchUserProjectsQuery from '~/vue_shared/components/new_issue_dropdown/graphql/search_user_projects.query.graphql'; +import NewResourceDropdown from '~/vue_shared/components/new_resource_dropdown/new_resource_dropdown.vue'; +import searchUserProjectsWithIssuesEnabledQuery from '~/vue_shared/components/new_resource_dropdown/graphql/search_user_projects_with_issues_enabled.query.graphql'; +import { RESOURCE_TYPES } from '~/vue_shared/components/new_resource_dropdown/constants'; import searchProjectsWithinGroupQuery from '~/issues/list/queries/search_projects.query.graphql'; import { DASH_SCOPE, joinPaths } from '~/lib/utils/url_utility'; import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants'; @@ -14,6 +15,7 @@ import { emptySearchProjectsQueryResponse, emptySearchProjectsWithinGroupQueryResponse, project1, + project2, project3, searchProjectsQueryResponse, searchProjectsWithinGroupQueryResponse, @@ -21,7 +23,7 @@ import { jest.mock('~/flash'); -describe('NewIssueDropdown component', () => { +describe('NewResourceDropdown component', () => { useLocalStorageSpy(); let wrapper; @@ -37,7 +39,7 @@ describe('NewIssueDropdown component', () => { const mountComponent = ({ search = '', - query = searchUserProjectsQuery, + query = searchUserProjectsWithIssuesEnabledQuery, queryResponse = searchProjectsQueryResponse, mountFn = shallowMount, propsData = {}, @@ -45,7 +47,7 @@ describe('NewIssueDropdown component', () => { const requestHandlers = [[query, jest.fn().mockResolvedValue(queryResponse)]]; const apolloProvider = createMockApollo(requestHandlers); - return mountFn(NewIssueDropdown, { + return mountFn(NewResourceDropdown, { apolloProvider, propsData, data() { @@ -76,7 +78,9 @@ describe('NewIssueDropdown component', () => { it('renders a label for the dropdown toggle button', () => { wrapper = mountComponent(); - expect(findDropdown().attributes('toggle-text')).toBe(NewIssueDropdown.i18n.toggleButtonLabel); + expect(findDropdown().attributes('toggle-text')).toBe( + NewResourceDropdown.i18n.toggleButtonLabel, + ); }); it('focuses on input when dropdown is shown', async () => { @@ -90,18 +94,19 @@ describe('NewIssueDropdown component', () => { }); describe.each` - description | propsData | query | queryResponse | emptyResponse - ${'by default'} | ${undefined} | ${searchUserProjectsQuery} | ${searchProjectsQueryResponse} | ${emptySearchProjectsQueryResponse} - ${'within a group'} | ${withinGroupProps} | ${searchProjectsWithinGroupQuery} | ${searchProjectsWithinGroupQueryResponse} | ${emptySearchProjectsWithinGroupQueryResponse} + description | propsData | query | queryResponse | emptyResponse + ${'by default'} | ${undefined} | ${searchUserProjectsWithIssuesEnabledQuery} | ${searchProjectsQueryResponse} | ${emptySearchProjectsQueryResponse} + ${'within a group'} | ${withinGroupProps} | ${searchProjectsWithinGroupQuery} | ${searchProjectsWithinGroupQueryResponse} | ${emptySearchProjectsWithinGroupQueryResponse} `('$description', ({ propsData, query, queryResponse, emptyResponse }) => { - it('renders projects with issues enabled', async () => { + it('renders projects options', async () => { wrapper = mountComponent({ mountFn: mount, query, queryResponse, propsData }); await showDropdown(); const listItems = wrapper.findAll('li'); expect(listItems.at(0).text()).toBe(project1.nameWithNamespace); - expect(listItems.at(1).text()).toBe(project3.nameWithNamespace); + expect(listItems.at(1).text()).toBe(project2.nameWithNamespace); + expect(listItems.at(2).text()).toBe(project3.nameWithNamespace); }); it('renders `No matches found` when there are no matches', async () => { @@ -115,41 +120,60 @@ describe('NewIssueDropdown component', () => { await showDropdown(); - expect(wrapper.find('li').text()).toBe(NewIssueDropdown.i18n.noMatchesFound); + expect(wrapper.find('li').text()).toBe(NewResourceDropdown.i18n.noMatchesFound); }); - describe('when no project is selected', () => { - beforeEach(() => { - wrapper = mountComponent({ query, queryResponse, propsData }); - }); - - it('dropdown button is not a link', () => { - expect(findDropdown().attributes('split-href')).toBeUndefined(); - }); - - it('displays default text on the dropdown button', () => { - expect(findDropdown().props('text')).toBe(NewIssueDropdown.i18n.defaultDropdownText); - }); - }); - - describe('when a project is selected', () => { - beforeEach(async () => { - wrapper = mountComponent({ mountFn: mount, query, queryResponse, propsData }); - await showDropdown(); - - wrapper.findComponent(GlDropdownItem).vm.$emit('click', project1); - }); - - it('dropdown button is a link', () => { - const href = joinPaths(project1.webUrl, DASH_SCOPE, 'issues/new'); - - expect(findDropdown().attributes('split-href')).toBe(href); - }); - - it('displays project name on the dropdown button', () => { - expect(findDropdown().props('text')).toBe(`New issue in ${project1.name}`); - }); - }); + describe.each` + resourceType | expectedDefaultLabel | expectedPath | expectedLabel + ${'issue'} | ${'Select project to create issue'} | ${'issues/new'} | ${'New issue in'} + ${'merge-request'} | ${'Select project to create merge request'} | ${'merge_requests/new'} | ${'New merge request in'} + ${'milestone'} | ${'Select project to create milestone'} | ${'milestones/new'} | ${'New milestone in'} + `( + 'with resource type $resourceType', + ({ resourceType, expectedDefaultLabel, expectedPath, expectedLabel }) => { + describe('when no project is selected', () => { + beforeEach(() => { + wrapper = mountComponent({ + query, + queryResponse, + propsData: { ...propsData, resourceType }, + }); + }); + + it('dropdown button is not a link', () => { + expect(findDropdown().attributes('split-href')).toBeUndefined(); + }); + + it('displays default text on the dropdown button', () => { + expect(findDropdown().props('text')).toBe(expectedDefaultLabel); + }); + }); + + describe('when a project is selected', () => { + beforeEach(async () => { + wrapper = mountComponent({ + mountFn: mount, + query, + queryResponse, + propsData: { ...propsData, resourceType }, + }); + await showDropdown(); + + wrapper.findComponent(GlDropdownItem).vm.$emit('click', project1); + }); + + it('dropdown button is a link', () => { + const href = joinPaths(project1.webUrl, DASH_SCOPE, expectedPath); + + expect(findDropdown().attributes('split-href')).toBe(href); + }); + + it('displays project name on the dropdown button', () => { + expect(findDropdown().props('text')).toBe(`${expectedLabel} ${project1.name}`); + }); + }); + }, + ); }); describe('without localStorage', () => { @@ -201,5 +225,38 @@ describe('NewIssueDropdown component', () => { ); expect(dropdown.props('text')).toBe(`New issue in ${project1.name}`); }); + + describe.each(RESOURCE_TYPES)('with resource type %s', (resourceType) => { + it('computes the local storage key without a group', async () => { + wrapper = mountComponent({ + mountFn: mount, + propsData: { resourceType, withLocalStorage: true }, + }); + await showDropdown(); + wrapper.findComponent(GlDropdownItem).vm.$emit('click', project1); + await nextTick(); + + expect(localStorage.setItem).toHaveBeenLastCalledWith( + `group--new-${resourceType}-recent-project`, + expect.any(String), + ); + }); + + it('computes the local storage key with a group', async () => { + const groupId = '22'; + wrapper = mountComponent({ + mountFn: mount, + propsData: { groupId, resourceType, withLocalStorage: true }, + }); + await showDropdown(); + wrapper.findComponent(GlDropdownItem).vm.$emit('click', project1); + await nextTick(); + + expect(localStorage.setItem).toHaveBeenLastCalledWith( + `group-${groupId}-new-${resourceType}-recent-project`, + expect.any(String), + ); + }); + }); }); }); diff --git a/spec/graphql/types/permission_types/work_item_spec.rb b/spec/graphql/types/permission_types/work_item_spec.rb index e604ce5d6e0..db6d78b1538 100644 --- a/spec/graphql/types/permission_types/work_item_spec.rb +++ b/spec/graphql/types/permission_types/work_item_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Types::PermissionTypes::WorkItem do it do expected_permissions = [ - :read_work_item, :update_work_item, :delete_work_item + :read_work_item, :update_work_item, :delete_work_item, :admin_work_item ] expected_permissions.each do |permission| diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb index 0ba92d1e92d..400ca10e645 100644 --- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb @@ -2,7 +2,9 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Config::External::File::Project do +RSpec.describe Gitlab::Ci::Config::External::File::Project, feature_category: :pipeline_authoring do + include RepoHelpers + let_it_be(:context_project) { create(:project) } let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } @@ -12,11 +14,12 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) } let(:project_file) { described_class.new(params, context) } let(:variables) { project.predefined_variables.to_runner_variables } + let(:project_sha) { project.commit.sha } let(:context_params) do { project: context_project, - sha: '12345', + sha: project_sha, user: context_user, parent_pipeline: parent_pipeline, variables: variables @@ -76,10 +79,10 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do { project: project.full_path, file: '/file.yml' } end - let(:root_ref_sha) { project.repository.root_ref_sha } - - before do - stub_project_blob(root_ref_sha, '/file.yml') { 'image: image:1.0' } + around(:all) do |example| + create_and_delete_files(project, { '/file.yml' => 'image: image:1.0' }) do + example.run + end end it { is_expected.to be_truthy } @@ -99,10 +102,10 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do { project: project.full_path, ref: 'master', file: '/file.yml' } end - let(:ref_sha) { project.commit('master').sha } - - before do - stub_project_blob(ref_sha, '/file.yml') { 'image: image:1.0' } + around(:all) do |example| + create_and_delete_files(project, { '/file.yml' => 'image: image:1.0' }) do + example.run + end end it { is_expected.to be_truthy } @@ -114,15 +117,16 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do end let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'GITLAB_TOKEN', 'value' => 'secret_file', 'masked' => true }]) } - let(:root_ref_sha) { project.repository.root_ref_sha } - before do - stub_project_blob(root_ref_sha, '/secret_file.yml') { '' } + around(:all) do |example| + create_and_delete_files(project, { '/secret_file.yml' => '' }) do + example.run + end end it 'returns false' do expect(valid?).to be_falsy - expect(project_file.error_message).to include("Project `#{project.full_path}` file `/xxxxxxxxxxx.yml` is empty!") + expect(project_file.error_message).to include("Project `#{project.full_path}` file `xxxxxxxxxxx.yml` is empty!") end end @@ -146,7 +150,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do it 'returns false' do expect(valid?).to be_falsy - expect(project_file.error_message).to include("Project `#{project.full_path}` file `/xxxxxxxxxxxxxxxxxxx.yml` does not exist!") + expect(project_file.error_message).to include("Project `#{project.full_path}` file `xxxxxxxxxxxxxxxxxxx.yml` does not exist!") end end @@ -157,7 +161,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do it 'returns false' do expect(valid?).to be_falsy - expect(project_file.error_message).to include('Included file `/invalid-file` does not have YAML extension!') + expect(project_file.error_message).to include('Included file `invalid-file` does not have YAML extension!') end end @@ -200,7 +204,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do is_expected.to include( user: user, project: project, - sha: project.commit('master').id, + sha: project_sha, parent_pipeline: parent_pipeline, variables: project.predefined_variables.to_runner_variables) end @@ -216,11 +220,11 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do it { is_expected.to eq( context_project: context_project.full_path, - context_sha: '12345', + context_sha: project_sha, type: :file, - location: '/file.yml', - blob: "http://localhost/#{project.full_path}/-/blob/#{project.commit('master').id}/file.yml", - raw: "http://localhost/#{project.full_path}/-/raw/#{project.commit('master').id}/file.yml", + location: 'file.yml', + blob: "http://localhost/#{project.full_path}/-/blob/#{project_sha}/file.yml", + raw: "http://localhost/#{project.full_path}/-/raw/#{project_sha}/file.yml", extra: { project: project.full_path, ref: 'HEAD' } ) } @@ -239,9 +243,9 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do it { is_expected.to eq( context_project: context_project.full_path, - context_sha: '12345', + context_sha: project_sha, type: :file, - location: '/file.yml', + location: 'file.yml', blob: nil, raw: nil, extra: { project: 'xxxxxxxxxxxxxxxxxxxxxxxx', ref: 'xxxxxxxxxxxxxxxxxxxxxxxx' } @@ -249,12 +253,4 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do } end end - - private - - def stub_project_blob(ref, path) - allow_next_instance_of(Repository) do |instance| - allow(instance).to receive(:blob_data_at).with(ref, path) { yield } - end - end end diff --git a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb index fa67b134406..4303f279cac 100644 --- a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb @@ -94,6 +94,56 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category: end end + context 'when files are project files' do + let_it_be(:included_project) { create(:project, :repository, namespace: project.namespace, creator: user) } + + let(:files) do + [ + Gitlab::Ci::Config::External::File::Project.new( + { file: 'myfolder/file1.yml', project: included_project.full_path }, context + ), + Gitlab::Ci::Config::External::File::Project.new( + { file: 'myfolder/file2.yml', project: included_project.full_path }, context + ), + Gitlab::Ci::Config::External::File::Project.new( + { file: 'myfolder/file3.yml', project: included_project.full_path }, context + ) + ] + end + + around(:all) do |example| + create_and_delete_files(included_project, project_files) do + example.run + end + end + + it 'returns an array of file objects' do + expect(process.map(&:location)).to contain_exactly( + 'myfolder/file1.yml', 'myfolder/file2.yml', 'myfolder/file3.yml' + ) + end + + it 'adds files to the expandset' do + expect { process }.to change { context.expandset.count }.by(3) + end + + it 'calls Gitaly only once for all files', :request_store do + # 1 for project.commit.id, 3 for the sha check, 1 for the files + expect { process }.to change { Gitlab::GitalyClient.get_request_count }.by(5) + end + + context 'when the FF ci_batch_request_for_local_and_project_includes is disabled' do + before do + stub_feature_flags(ci_batch_request_for_local_and_project_includes: false) + end + + it 'calls Gitaly for each file', :request_store do + # 1 for project.commit.id, 3 for the sha check, 3 for the files + expect { process }.to change { Gitlab::GitalyClient.get_request_count }.by(7) + end + end + end + context 'when a file includes other files' do let(:files) do [ diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb index b397bfa38ee..344e9095fab 100644 --- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' # This will be moved from a `shared_context` to a `describe` once every feature flag is removed. -RSpec.shared_context 'gitlab_ci_config_external_mapper' do +# - ci_batch_request_for_local_and_project_includes_enabled is also removed with the FF. +RSpec.shared_context 'gitlab_ci_config_external_mapper' do |ci_batch_request_for_local_and_project_includes_enabled| include StubRequests include RepoHelpers @@ -167,7 +168,11 @@ RSpec.shared_context 'gitlab_ci_config_external_mapper' do an_instance_of(Gitlab::Ci::Config::External::File::Project)) end - it_behaves_like 'logging config file fetch', 'config_file_fetch_project_content_duration_s', 2 + if ci_batch_request_for_local_and_project_includes_enabled + it_behaves_like 'logging config file fetch', 'config_file_fetch_project_content_duration_s', 1 + else + it_behaves_like 'logging config file fetch', 'config_file_fetch_project_content_duration_s', 2 + end end end @@ -465,13 +470,13 @@ RSpec.shared_context 'gitlab_ci_config_external_mapper' do end RSpec.describe Gitlab::Ci::Config::External::Mapper, feature_category: :pipeline_authoring do - it_behaves_like 'gitlab_ci_config_external_mapper' + it_behaves_like 'gitlab_ci_config_external_mapper', true context 'when the FF ci_batch_request_for_local_and_project_includes is disabled' do before do stub_feature_flags(ci_batch_request_for_local_and_project_includes: false) end - it_behaves_like 'gitlab_ci_config_external_mapper' + it_behaves_like 'gitlab_ci_config_external_mapper', false end end diff --git a/spec/lib/gitlab/ci/config/external/processor_spec.rb b/spec/lib/gitlab/ci/config/external/processor_spec.rb index e0080b252c6..311b433b7d2 100644 --- a/spec/lib/gitlab/ci/config/external/processor_spec.rb +++ b/spec/lib/gitlab/ci/config/external/processor_spec.rb @@ -334,7 +334,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel context_project: project.full_path, context_sha: sha }, { type: :file, - location: '/templates/my-workflow.yml', + location: 'templates/my-workflow.yml', blob: "http://localhost/#{another_project.full_path}/-/blob/#{another_project.commit.sha}/templates/my-workflow.yml", raw: "http://localhost/#{another_project.full_path}/-/raw/#{another_project.commit.sha}/templates/my-workflow.yml", extra: { project: another_project.full_path, ref: 'HEAD' }, @@ -465,7 +465,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel expect(context.includes).to contain_exactly( { type: :file, - location: '/templates/my-build.yml', + location: 'templates/my-build.yml', blob: "http://localhost/#{another_project.full_path}/-/blob/#{another_project.commit.sha}/templates/my-build.yml", raw: "http://localhost/#{another_project.full_path}/-/raw/#{another_project.commit.sha}/templates/my-build.yml", extra: { project: another_project.full_path, ref: 'HEAD' }, @@ -474,7 +474,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor, feature_category: :pipel { type: :file, blob: "http://localhost/#{another_project.full_path}/-/blob/#{another_project.commit.sha}/templates/my-test.yml", raw: "http://localhost/#{another_project.full_path}/-/raw/#{another_project.commit.sha}/templates/my-test.yml", - location: '/templates/my-test.yml', + location: 'templates/my-test.yml', extra: { project: another_project.full_path, ref: 'HEAD' }, context_project: project.full_path, context_sha: sha } diff --git a/spec/lib/gitlab/ci/parsers/instrumentation_spec.rb b/spec/lib/gitlab/ci/parsers/instrumentation_spec.rb index 30bcce21be2..6772c62ab93 100644 --- a/spec/lib/gitlab/ci/parsers/instrumentation_spec.rb +++ b/spec/lib/gitlab/ci/parsers/instrumentation_spec.rb @@ -8,14 +8,14 @@ RSpec.describe Gitlab::Ci::Parsers::Instrumentation do Class.new do prepend Gitlab::Ci::Parsers::Instrumentation - def parse!(arg1, arg2) + def parse!(arg1, arg2:) "parse #{arg1} #{arg2}" end end end it 'sets metrics for duration of parsing' do - result = parser_class.new.parse!('hello', 'world') + result = parser_class.new.parse!('hello', arg2: 'world') expect(result).to eq('parse hello world') diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb deleted file mode 100644 index bec80a43a76..00000000000 --- a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb +++ /dev/null @@ -1,72 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Ci::Pipeline::Chain::CreateDeployments, feature_category: :continuous_integration do - let_it_be(:project) { create(:project, :repository) } - let_it_be(:user) { create(:user) } - - let(:stage) { build(:ci_stage, project: project, statuses: [job]) } - let(:pipeline) { create(:ci_pipeline, project: project, stages: [stage]) } - - let(:command) do - Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user) - end - - let(:step) { described_class.new(pipeline, command) } - - describe '#perform!' do - subject { step.perform! } - - before do - stub_feature_flags(move_create_deployments_to_worker: false) - job.pipeline = pipeline - end - - context 'when a pipeline contains a deployment job' do - let!(:job) { build(:ci_build, :start_review_app, project: project) } - let!(:environment) { create(:environment, project: project, name: job.expanded_environment_name) } - - it 'creates a deployment record' do - expect { subject }.to change { Deployment.count }.by(1) - - job.reset - expect(job.deployment.project).to eq(job.project) - expect(job.deployment.ref).to eq(job.ref) - expect(job.deployment.sha).to eq(job.sha) - expect(job.deployment.deployable).to eq(job) - expect(job.deployment.deployable_type).to eq('CommitStatus') - expect(job.deployment.environment).to eq(job.persisted_environment) - end - - context 'when the corresponding environment does not exist' do - let!(:environment) {} - - it 'does not create a deployment record' do - expect { subject }.not_to change { Deployment.count } - - expect(job.deployment).to be_nil - end - end - end - - context 'when a pipeline contains a teardown job' do - let!(:job) { build(:ci_build, :stop_review_app, project: project) } - let!(:environment) { create(:environment, name: job.expanded_environment_name) } - - it 'does not create a deployment record' do - expect { subject }.not_to change { Deployment.count } - - expect(job.deployment).to be_nil - end - end - - context 'when a pipeline does not contain a deployment job' do - let!(:job) { build(:ci_build, project: project) } - - it 'does not create any deployments' do - expect { subject }.not_to change { Deployment.count } - end - end - end -end diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index a554e0b26a8..c44bb64a5c0 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -81,6 +81,10 @@ RSpec.describe Gitlab do describe '.com?' do context 'when not simulating SaaS' do + before do + stub_env('GITLAB_SIMULATE_SAAS', '0') + end + it "is true when on #{Gitlab::Saas.com_url}" do stub_config_setting(url: Gitlab::Saas.com_url) @@ -114,31 +118,17 @@ RSpec.describe Gitlab do end end - context 'when simulating SaaS' do - before do - stub_const('Gitlab::GITLAB_SIMULATE_SAAS', '1') - end - - it 'is false in tests' do - expect(described_class.com?).to eq false - end - - it 'is true in development' do - stub_rails_env('development') + it 'is true when GITLAB_SIMULATE_SAAS is true and in development' do + stub_rails_env('development') + stub_env('GITLAB_SIMULATE_SAAS', '1') - expect(described_class.com?).to eq true - end + expect(described_class.com?).to eq true + end - # See ee/spec/lib/gitlab_spec.rb for EE-specific changes to this behavior. - context 'in a production environment' do - before do - stub_rails_env('production') - end + it 'is false when GITLAB_SIMULATE_SAAS is true and in test' do + stub_env('GITLAB_SIMULATE_SAAS', '1') - it 'is false' do - expect(described_class.com?).to eq false - end - end + expect(described_class.com?).to eq false end end @@ -246,6 +236,54 @@ RSpec.describe Gitlab do end end + describe '.simulate_com?' do + subject { described_class.simulate_com? } + + context 'when GITLAB_SIMULATE_SAAS is true' do + before do + stub_env('GITLAB_SIMULATE_SAAS', '1') + end + + it 'is false when test env' do + expect(subject).to eq false + end + + it 'is true when dev env' do + stub_rails_env('development') + + expect(subject).to eq true + end + + it 'is false when env is not dev' do + stub_rails_env('production') + + expect(subject).to eq false + end + end + + context 'when GITLAB_SIMULATE_SAAS is false' do + before do + stub_env('GITLAB_SIMULATE_SAAS', '0') + end + + it 'is false when test env' do + expect(subject).to eq false + end + + it 'is false when dev env' do + stub_rails_env('development') + + expect(subject).to eq false + end + + it 'is false when env is not dev or test' do + stub_rails_env('production') + + expect(subject).to eq false + end + end + end + describe '.dev_or_test_env?' do subject { described_class.dev_or_test_env? } diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 3cea65ce699..47430dc6651 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -23,17 +23,19 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration do it { is_expected.to belong_to(:trigger_request) } it { is_expected.to belong_to(:erased_by) } - it { is_expected.to have_many(:needs) } - it { is_expected.to have_many(:sourced_pipelines) } - it { is_expected.to have_one(:sourced_pipeline) } - it { is_expected.to have_many(:job_variables) } - it { is_expected.to have_many(:report_results) } - it { is_expected.to have_many(:pages_deployments) } + it { is_expected.to have_many(:needs).with_foreign_key(:build_id) } + it { is_expected.to have_many(:sourced_pipelines).with_foreign_key(:source_job_id) } + it { is_expected.to have_one(:sourced_pipeline).with_foreign_key(:source_job_id) } + it { is_expected.to have_many(:job_variables).with_foreign_key(:job_id) } + it { is_expected.to have_many(:report_results).with_foreign_key(:build_id) } + it { is_expected.to have_many(:pages_deployments).with_foreign_key(:ci_build_id) } it { is_expected.to have_one(:deployment) } - it { is_expected.to have_one(:runner_session) } - it { is_expected.to have_one(:trace_metadata) } - it { is_expected.to have_many(:terraform_state_versions).inverse_of(:build) } + it { is_expected.to have_one(:runner_session).with_foreign_key(:build_id) } + it { is_expected.to have_one(:trace_metadata).with_foreign_key(:build_id) } + it { is_expected.to have_one(:runtime_metadata).with_foreign_key(:build_id) } + it { is_expected.to have_one(:pending_state).with_foreign_key(:build_id) } + it { is_expected.to have_many(:terraform_state_versions).inverse_of(:build).with_foreign_key(:ci_build_id) } it { is_expected.to validate_presence_of(:ref) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 2ea6c396117..f277dd054ee 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -7260,6 +7260,54 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do end end + describe '#group_protected_branches' do + subject { project.group_protected_branches } + + let(:project) { create(:project, group: group) } + let(:group) { create(:group) } + let(:protected_branch) { create(:protected_branch, group: group, project: nil) } + + it 'returns protected branches of the group' do + is_expected.to match_array([protected_branch]) + end + + context 'when project belongs to namespace' do + let(:project) { create(:project) } + + it 'returns empty relation' do + is_expected.to be_empty + end + end + end + + describe '#all_protected_branches' do + let(:group) { create(:group) } + let!(:group_protected_branch) { create(:protected_branch, group: group, project: nil) } + let!(:project_protected_branch) { create(:protected_branch, project: subject) } + + subject { create(:project, group: group) } + + context 'when feature flag `group_protected_branches` enabled' do + before do + stub_feature_flags(group_protected_branches: true) + end + + it 'return all protected branches' do + expect(subject.all_protected_branches).to match_array([group_protected_branch, project_protected_branch]) + end + end + + context 'when feature flag `group_protected_branches` disabled' do + before do + stub_feature_flags(group_protected_branches: false) + end + + it 'return only project-level protected branches' do + expect(subject.all_protected_branches).to match_array([project_protected_branch]) + end + end + end + describe '#lfs_objects_oids' do let(:project) { create(:project) } let(:lfs_object) { create(:lfs_object) } diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index 94c463dfe43..d5e9b04f03e 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -341,23 +341,61 @@ RSpec.describe ProtectedBranch do end describe "#allow_force_push?" do - context "when the attr allow_force_push is true" do - let(:subject_branch) { create(:protected_branch, allow_force_push: true, name: "foo") } + context "when feature flag disabled" do + before do + stub_feature_flags(group_protected_branches: false) + end + + let(:subject_branch) { create(:protected_branch, allow_force_push: allow_force_push, name: "foo") } + let(:project) { subject_branch.project } - it "returns true" do - project = subject_branch.project + context "when the attr allow_force_push is true" do + let(:allow_force_push) { true } - expect(described_class.allow_force_push?(project, "foo")).to eq(true) + it "returns true" do + expect(described_class.allow_force_push?(project, "foo")).to eq(true) + end + end + + context "when the attr allow_force_push is false" do + let(:allow_force_push) { false } + + it "returns false" do + expect(described_class.allow_force_push?(project, "foo")).to eq(false) + end end end - context "when the attr allow_force_push is false" do - let(:subject_branch) { create(:protected_branch, allow_force_push: false, name: "foo") } + context "when feature flag enabled" do + using RSpec::Parameterized::TableSyntax - it "returns false" do - project = subject_branch.project + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } - expect(described_class.allow_force_push?(project, "foo")).to eq(false) + where(:group_level_value, :project_level_value, :result) do + true | false | true + false | true | false + true | nil | true + false | nil | false + nil | nil | false + end + + with_them do + before do + stub_feature_flags(group_protected_branches: true) + + unless group_level_value.nil? + create(:protected_branch, allow_force_push: group_level_value, name: "foo", project: nil, group: group) + end + + unless project_level_value.nil? + create(:protected_branch, allow_force_push: project_level_value, name: "foo", project: project) + end + end + + it "returns result" do + expect(described_class.allow_force_push?(project, "foo")).to eq(result) + end end end end @@ -390,6 +428,36 @@ RSpec.describe ProtectedBranch do end end + describe '.protected_refs' do + let_it_be(:project) { create(:project) } + + subject { described_class.protected_refs(project) } + + context 'when feature flag enabled' do + before do + stub_feature_flags(group_protected_branches: true) + end + + it 'call `all_protected_branches`' do + expect(project).to receive(:all_protected_branches) + + subject + end + end + + context 'when feature flag disabled' do + before do + stub_feature_flags(group_protected_branches: false) + end + + it 'call `protected_branches`' do + expect(project).to receive(:protected_branches) + + subject + end + end + end + describe '.by_name' do let!(:protected_branch) { create(:protected_branch, name: 'master') } let!(:another_protected_branch) { create(:protected_branch, name: 'stable') } diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index eba1a06b5e4..058ddaebd79 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -279,7 +279,7 @@ RSpec.describe API::Branches, feature_category: :source_code_management do expect do get api(route, current_user), params: { per_page: 100 } - end.not_to exceed_query_limit(control) + end.not_to exceed_query_limit(control).with_threshold(1) end end diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb index 6b5d437df83..1f321d1dec3 100644 --- a/spec/requests/api/graphql/work_item_spec.rb +++ b/spec/requests/api/graphql/work_item_spec.rb @@ -55,7 +55,12 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do 'title' => work_item.title, 'confidential' => work_item.confidential, 'workItemType' => hash_including('id' => work_item.work_item_type.to_gid.to_s), - 'userPermissions' => { 'readWorkItem' => true, 'updateWorkItem' => true, 'deleteWorkItem' => false }, + 'userPermissions' => { + 'readWorkItem' => true, + 'updateWorkItem' => true, + 'deleteWorkItem' => false, + 'adminWorkItem' => true + }, 'project' => hash_including('id' => project.to_gid.to_s, 'fullPath' => project.full_path) ) end diff --git a/spec/services/projects/protect_default_branch_service_spec.rb b/spec/services/projects/protect_default_branch_service_spec.rb index c8aa421cdd4..9f9e89ff8f8 100644 --- a/spec/services/projects/protect_default_branch_service_spec.rb +++ b/spec/services/projects/protect_default_branch_service_spec.rb @@ -233,6 +233,38 @@ RSpec.describe Projects::ProtectDefaultBranchService do end end + describe '#protected_branch_exists?' do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + + let(:default_branch) { "default-branch" } + + before do + allow(project).to receive(:default_branch).and_return(default_branch) + create(:protected_branch, project: nil, group: group, name: default_branch) + end + + context 'when feature flag `group_protected_branches` disabled' do + before do + stub_feature_flags(group_protected_branches: false) + end + + it 'return false' do + expect(service.protected_branch_exists?).to eq(false) + end + end + + context 'when feature flag `group_protected_branches` enabled' do + before do + stub_feature_flags(group_protected_branches: true) + end + + it 'return true' do + expect(service.protected_branch_exists?).to eq(true) + end + end + end + describe '#default_branch' do it 'returns the default branch of the project' do allow(project) diff --git a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb index 6f4072ba762..532ef99f25f 100644 --- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb +++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb @@ -1003,7 +1003,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context let(:params) { { issue_types: ['nonsense'] } } it 'returns no items' do - expect(items).to eq(items_model.none) + expect(items.none?).to eq(true) end end end diff --git a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb index 974fc8f402a..0ef9ab25505 100644 --- a/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/integrations/slack_mattermost_notifier_shared_examples.rb @@ -276,7 +276,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name describe 'Push events' do let_it_be(:user) { create(:user) } - let_it_be_with_reload(:project) { create(:project, :repository, creator: user) } + let_it_be_with_refind(:project) { create(:project, :repository, creator: user) } before do allow(chat_integration).to receive_messages( @@ -520,7 +520,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name describe 'Pipeline events' do let_it_be(:user) { create(:user) } - let_it_be_with_reload(:project) { create(:project, :repository, creator: user) } + let_it_be_with_refind(:project) { create(:project, :repository, creator: user) } let(:pipeline) do create(:ci_pipeline, project: project, status: status, @@ -671,7 +671,7 @@ RSpec.shared_examples Integrations::SlackMattermostNotifier do |integration_name describe 'Deployment events' do let_it_be(:user) { create(:user) } - let_it_be_with_reload(:project) { create(:project, :repository, creator: user) } + let_it_be_with_refind(:project) { create(:project, :repository, creator: user) } let_it_be(:deployment) do create(:deployment, :success, project: project, sha: project.commit.sha, ref: project.default_branch) |