diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-08 15:07:09 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-08 15:07:09 +0300 |
commit | e44c3e4832e43c77e9c29fad6e49f8d6066d7f5c (patch) | |
tree | 892f4505093dd5ffd60e238a8b74b35f021654ae /spec | |
parent | aa1c2a29b8ddc82141f826eacd169d3d7ff66611 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
20 files changed, 336 insertions, 160 deletions
diff --git a/spec/features/projects/settings/secure_files_spec.rb b/spec/features/projects/settings/secure_files_spec.rb index 7ff1a5f3568..5f94e215a5f 100644 --- a/spec/features/projects/settings/secure_files_spec.rb +++ b/spec/features/projects/settings/secure_files_spec.rb @@ -46,7 +46,7 @@ RSpec.describe 'Secure Files', :js, feature_category: :groups_and_projects do within '#js-secure-files' do expect(page).to have_content(file.name) - find('button.btn-danger-secondary').click + find('[data-testid="delete-button"]').click end expect(page).to have_content("Delete #{file.name}?") diff --git a/spec/frontend/super_sidebar/components/context_switcher_spec.js b/spec/frontend/super_sidebar/components/context_switcher_spec.js index 4317f451377..dd8f39e7cb7 100644 --- a/spec/frontend/super_sidebar/components/context_switcher_spec.js +++ b/spec/frontend/super_sidebar/components/context_switcher_spec.js @@ -15,7 +15,7 @@ import { trackContextAccess, formatContextSwitcherItems } from '~/super_sidebar/ import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import waitForPromises from 'helpers/wait_for_promises'; import { stubComponent } from 'helpers/stub_component'; -import { searchUserProjectsAndGroupsResponseMock } from '../mock_data'; +import { contextSwitcherLinks, searchUserProjectsAndGroupsResponseMock } from '../mock_data'; jest.mock('~/super_sidebar/utils', () => ({ getStorageKeyFor: jest.requireActual('~/super_sidebar/utils').getStorageKeyFor, @@ -26,9 +26,6 @@ jest.mock('~/super_sidebar/utils', () => ({ })); const focusInputMock = jest.fn(); -const persistentLinks = [ - { title: 'Explore', link: '/explore', icon: 'compass', link_classes: 'persistent-link-class' }, -]; const username = 'root'; const projectsPath = 'projectsPath'; const groupsPath = 'groupsPath'; @@ -71,8 +68,10 @@ describe('ContextSwitcher component', () => { wrapper = shallowMountExtended(ContextSwitcher, { apolloProvider: mockApollo, + provide: { + contextSwitcherLinks, + }, propsData: { - persistentLinks, username, projectsPath, groupsPath, @@ -107,14 +106,14 @@ describe('ContextSwitcher component', () => { createWrapper(); }); - it('renders the persistent links', () => { + it('renders the context switcher links', () => { const navItems = findNavItems(); const firstNavItem = navItems.at(0); - expect(navItems.length).toBe(persistentLinks.length); - expect(firstNavItem.props('item')).toBe(persistentLinks[0]); + expect(navItems.length).toBe(contextSwitcherLinks.length); + expect(firstNavItem.props('item')).toBe(contextSwitcherLinks[0]); expect(firstNavItem.props('linkClasses')).toEqual({ - [persistentLinks[0].link_classes]: persistentLinks[0].link_classes, + [contextSwitcherLinks[0].link_classes]: contextSwitcherLinks[0].link_classes, }); }); diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js index 52e9aa52c14..0fb6585e8ca 100644 --- a/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js +++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js @@ -4,36 +4,42 @@ import Vuex from 'vuex'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import GlobalSearchDefaultItems from '~/super_sidebar/components/global_search/components/global_search_default_items.vue'; import { MOCK_SEARCH_CONTEXT, MOCK_DEFAULT_SEARCH_OPTIONS } from '../mock_data'; +import { contextSwitcherLinks } from '../../../mock_data'; Vue.use(Vuex); describe('GlobalSearchDefaultItems', () => { let wrapper; - const createComponent = (initialState, props) => { + const createComponent = ({ + storeState, + mockDefaultSearchOptions = MOCK_DEFAULT_SEARCH_OPTIONS, + ...options + } = {}) => { const store = new Vuex.Store({ state: { searchContext: MOCK_SEARCH_CONTEXT, - ...initialState, + ...storeState, }, getters: { - defaultSearchOptions: () => MOCK_DEFAULT_SEARCH_OPTIONS, + defaultSearchOptions: () => mockDefaultSearchOptions, }, }); wrapper = shallowMountExtended(GlobalSearchDefaultItems, { store, - propsData: { - ...props, + provide: { + contextSwitcherLinks, }, stubs: { GlDisclosureDropdownGroup, }, + ...options, }); }; - const findItems = () => wrapper.findAllComponents(GlDisclosureDropdownItem); - const findItemsData = () => findItems().wrappers.map((w) => w.props('item')); + const findGroups = () => wrapper.findAllComponents(GlDisclosureDropdownGroup); + const findItems = (root = wrapper) => root.findAllComponents(GlDisclosureDropdownItem); describe('template', () => { describe('Dropdown items', () => { @@ -41,12 +47,39 @@ describe('GlobalSearchDefaultItems', () => { createComponent(); }); - it('renders item for each option in defaultSearchOptions', () => { - expect(findItems()).toHaveLength(MOCK_DEFAULT_SEARCH_OPTIONS.length); + it('renders two groups', () => { + const groups = findGroups(); + + expect(groups).toHaveLength(2); + + const actualNames = groups.wrappers.map((group) => group.props('group').name); + expect(actualNames).toEqual(['Places', 'All GitLab']); + }); + + it('renders context switcher links in first group', () => { + const group = findGroups().at(0); + expect(group.props('group').name).toBe('Places'); + + const items = findItems(group); + expect(items).toHaveLength(contextSwitcherLinks.length); + }); + + it('renders default search options in second group', () => { + const group = findGroups().at(1); + expect(group.props('group').name).toBe('All GitLab'); + + const items = findItems(group); + expect(items).toHaveLength(MOCK_DEFAULT_SEARCH_OPTIONS.length); + }); + }); + + describe('Empty groups', () => { + beforeEach(() => { + createComponent({ mockDefaultSearchOptions: [], provide: { contextSwitcherLinks: [] } }); }); - it('provides the `item` prop to the `GlDisclosureDropdownItem` component', () => { - expect(findItemsData()).toStrictEqual(MOCK_DEFAULT_SEARCH_OPTIONS); + it('does not render groups with no items', () => { + expect(findGroups()).toHaveLength(0); }); }); @@ -55,13 +88,15 @@ describe('GlobalSearchDefaultItems', () => { ${null} | ${null} | ${'All GitLab'} ${{ name: 'Test Group' }} | ${null} | ${'Test Group'} ${{ name: 'Test Group' }} | ${{ name: 'Test Project' }} | ${'Test Project'} - `('Group Header', ({ group, project, groupHeader }) => { + `('Current context header', ({ group, project, groupHeader }) => { describe(`when group is ${group?.name} and project is ${project?.name}`, () => { beforeEach(() => { createComponent({ - searchContext: { - group, - project, + storeState: { + searchContext: { + group, + project, + }, }, }); }); diff --git a/spec/frontend/super_sidebar/components/global_search/mock_data.js b/spec/frontend/super_sidebar/components/global_search/mock_data.js index ad7e7b0b30b..dfa8b458844 100644 --- a/spec/frontend/super_sidebar/components/global_search/mock_data.js +++ b/spec/frontend/super_sidebar/components/global_search/mock_data.js @@ -62,6 +62,24 @@ export const MOCK_SEARCH_CONTEXT = { group_metadata: {}, }; +export const MOCK_GROUP_SEARCH_CONTEXT = { + ...MOCK_SEARCH_CONTEXT, + group: MOCK_GROUP, + group_metadata: { + issues_path: `${MOCK_GROUP.path}/issues`, + mr_path: `${MOCK_GROUP.path}/merge_requests`, + }, +}; + +export const MOCK_PROJECT_SEARCH_CONTEXT = { + ...MOCK_GROUP_SEARCH_CONTEXT, + project: MOCK_PROJECT, + project_metadata: { + issues_path: `${MOCK_PROJECT.path}/issues`, + mr_path: `${MOCK_PROJECT.path}/merge_requests`, + }, +}; + export const MOCK_DEFAULT_SEARCH_OPTIONS = [ { text: MSG_ISSUES_ASSIGNED_TO_ME, diff --git a/spec/frontend/super_sidebar/components/global_search/store/getters_spec.js b/spec/frontend/super_sidebar/components/global_search/store/getters_spec.js index 68583d04b31..de636d1feec 100644 --- a/spec/frontend/super_sidebar/components/global_search/store/getters_spec.js +++ b/spec/frontend/super_sidebar/components/global_search/store/getters_spec.js @@ -7,6 +7,8 @@ import { MOCK_MR_PATH, MOCK_AUTOCOMPLETE_PATH, MOCK_SEARCH_CONTEXT, + MOCK_GROUP_SEARCH_CONTEXT, + MOCK_PROJECT_SEARCH_CONTEXT, MOCK_DEFAULT_SEARCH_OPTIONS, MOCK_SCOPED_SEARCH_OPTIONS, MOCK_SCOPED_SEARCH_GROUP, @@ -74,37 +76,47 @@ describe('Global Search Store Getters', () => { }); describe.each` - group | group_metadata | project | project_metadata | expectedPath - ${null} | ${null} | ${null} | ${null} | ${MOCK_ISSUE_PATH} - ${{ name: 'Test Group' }} | ${{ issues_path: 'group/path' }} | ${null} | ${null} | ${'group/path'} - ${{ name: 'Test Group' }} | ${{ issues_path: 'group/path' }} | ${{ name: 'Test Project' }} | ${{ issues_path: 'project/path' }} | ${'project/path'} - `('scopedIssuesPath', ({ group, group_metadata, project, project_metadata, expectedPath }) => { - describe(`when group is ${group?.name} and project is ${project?.name}`, () => { - beforeEach(() => { - createState({ - searchContext: { - group, - group_metadata, - project, - project_metadata, - }, + group | group_metadata | project | project_metadata | user | expectedPath + ${null} | ${null} | ${null} | ${null} | ${'a_user'} | ${MOCK_ISSUE_PATH} + ${null} | ${null} | ${null} | ${null} | ${null} | ${false} + ${{ name: 'Test Group' }} | ${{ issues_path: 'group/path' }} | ${null} | ${null} | ${null} | ${'group/path'} + ${{ name: 'Test Group' }} | ${{ issues_path: 'group/path' }} | ${{ id: '123' }} | ${{ issues_path: 'project/path' }} | ${null} | ${'project/path'} + ${{ name: 'Test Group' }} | ${{ issues_path: 'group/path' }} | ${{ id: '123' }} | ${{}} | ${null} | ${false} + `( + 'scopedIssuesPath', + ({ group, group_metadata, project, project_metadata, user, expectedPath }) => { + describe(`when group is ${group?.name} and project is ${project?.name}`, () => { + beforeEach(() => { + window.gon.current_username = user; + + createState({ + searchContext: { + group, + group_metadata, + project, + project_metadata, + }, + }); }); - }); - it(`should return ${expectedPath}`, () => { - expect(getters.scopedIssuesPath(state)).toBe(expectedPath); + it(`should return ${expectedPath}`, () => { + expect(getters.scopedIssuesPath(state)).toBe(expectedPath); + }); }); - }); - }); + }, + ); describe.each` - group | group_metadata | project | project_metadata | expectedPath - ${null} | ${null} | ${null} | ${null} | ${MOCK_MR_PATH} - ${{ name: 'Test Group' }} | ${{ mr_path: 'group/path' }} | ${null} | ${null} | ${'group/path'} - ${{ name: 'Test Group' }} | ${{ mr_path: 'group/path' }} | ${{ name: 'Test Project' }} | ${{ mr_path: 'project/path' }} | ${'project/path'} - `('scopedMRPath', ({ group, group_metadata, project, project_metadata, expectedPath }) => { + group | group_metadata | project | project_metadata | user | expectedPath + ${null} | ${null} | ${null} | ${null} | ${'a_user'} | ${MOCK_MR_PATH} + ${null} | ${null} | ${null} | ${null} | ${null} | ${false} + ${{ name: 'Test Group' }} | ${{ mr_path: 'group/path' }} | ${null} | ${null} | ${null} | ${'group/path'} + ${{ name: 'Test Group' }} | ${{ mr_path: 'group/path' }} | ${{ name: 'Test Project' }} | ${{ mr_path: 'project/path' }} | ${null} | ${'project/path'} + `('scopedMRPath', ({ group, group_metadata, project, project_metadata, user, expectedPath }) => { describe(`when group is ${group?.name} and project is ${project?.name}`, () => { beforeEach(() => { + window.gon.current_username = user; + createState({ searchContext: { group, @@ -227,27 +239,88 @@ describe('Global Search Store Getters', () => { }); describe('defaultSearchOptions', () => { - const mockGetters = { - scopedIssuesPath: MOCK_ISSUE_PATH, - scopedMRPath: MOCK_MR_PATH, - }; + let mockGetters; beforeEach(() => { createState(); - window.gon.current_username = MOCK_USERNAME; + mockGetters = { + scopedIssuesPath: MOCK_ISSUE_PATH, + scopedMRPath: MOCK_MR_PATH, + }; }); - it('returns the correct array', () => { - expect(getters.defaultSearchOptions(state, mockGetters)).toStrictEqual( - MOCK_DEFAULT_SEARCH_OPTIONS, - ); + describe('with a user', () => { + beforeEach(() => { + window.gon.current_username = MOCK_USERNAME; + }); + + it('returns the correct array', () => { + expect(getters.defaultSearchOptions(state, mockGetters)).toStrictEqual( + MOCK_DEFAULT_SEARCH_OPTIONS, + ); + }); + + it('returns the correct array if issues path is false', () => { + mockGetters.scopedIssuesPath = undefined; + expect(getters.defaultSearchOptions(state, mockGetters)).toStrictEqual( + MOCK_DEFAULT_SEARCH_OPTIONS.slice(2, MOCK_DEFAULT_SEARCH_OPTIONS.length), + ); + }); }); - it('returns the correct array if issues path is false', () => { - mockGetters.scopedIssuesPath = undefined; - expect(getters.defaultSearchOptions(state, mockGetters)).toStrictEqual( - MOCK_DEFAULT_SEARCH_OPTIONS.slice(2, MOCK_DEFAULT_SEARCH_OPTIONS.length), - ); + describe('without a user', () => { + describe('with no project or group context', () => { + beforeEach(() => { + mockGetters = { + scopedIssuesPath: false, + scopedMRPath: false, + }; + }); + + it('returns an empty array', () => { + expect(getters.defaultSearchOptions(state, mockGetters)).toEqual([]); + }); + }); + + describe('with a group context', () => { + beforeEach(() => { + createState({ + searchContext: MOCK_GROUP_SEARCH_CONTEXT, + }); + + mockGetters = { + scopedIssuesPath: state.searchContext.group_metadata.issues_path, + scopedMRPath: state.searchContext.group_metadata.mr_path, + }; + }); + + it('returns recent issues/merge requests options', () => { + expect(getters.defaultSearchOptions(state, mockGetters)).toEqual([ + { href: '/mock-group/issues', text: 'Recent issues' }, + { href: '/mock-group/merge_requests', text: 'Recent merge requests' }, + ]); + }); + }); + + describe('with a project context', () => { + beforeEach(() => { + createState({ + searchContext: MOCK_PROJECT_SEARCH_CONTEXT, + }); + + mockGetters = { + scopedIssuesPath: state.searchContext.project_metadata.issues_path, + scopedMRPath: state.searchContext.project_metadata.mr_path, + }; + }); + + it('returns recent issues/merge requests options', () => { + expect(getters.defaultSearchOptions(state, mockGetters)).toEqual([ + { href: '/mock-project/issues', text: 'Recent issues' }, + { href: '/mock-project/merge_requests', text: 'Recent merge requests' }, + ]); + }); + }); }); }); diff --git a/spec/frontend/super_sidebar/mock_data.js b/spec/frontend/super_sidebar/mock_data.js index df45360a898..0d34329c60d 100644 --- a/spec/frontend/super_sidebar/mock_data.js +++ b/spec/frontend/super_sidebar/mock_data.js @@ -71,6 +71,10 @@ export const mergeRequestMenuGroup = [ }, ]; +export const contextSwitcherLinks = [ + { title: 'Explore', link: '/explore', icon: 'compass', link_classes: 'persistent-link-class' }, +]; + export const sidebarData = { is_logged_in: true, current_menu_items: [], @@ -104,7 +108,7 @@ export const sidebarData = { gitlab_version_check: { severity: 'success' }, gitlab_com_and_canary: false, canary_toggle_com_url: 'https://next.gitlab.com', - context_switcher_links: [], + context_switcher_links: contextSwitcherLinks, search: { search_path: '/search', }, diff --git a/spec/graphql/types/ci/detailed_status_type_spec.rb b/spec/graphql/types/ci/detailed_status_type_spec.rb index 69fb2bc43c0..81ab1b52552 100644 --- a/spec/graphql/types/ci/detailed_status_type_spec.rb +++ b/spec/graphql/types/ci/detailed_status_type_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Types::Ci::DetailedStatusType do include GraphqlHelpers - let_it_be(:stage) { create(:ci_stage, status: :manual) } + let_it_be(:stage) { create(:ci_stage, status: :skipped) } specify { expect(described_class.graphql_name).to eq('DetailedStatus') } diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb index 3d675a65d99..4109eb01caa 100644 --- a/spec/helpers/sidebars_helper_spec.rb +++ b/spec/helpers/sidebars_helper_spec.rb @@ -374,11 +374,17 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do describe 'context switcher persistent links' do let_it_be(:public_link) do [ - { title: s_('Navigation|Your work'), link: '/', icon: 'work' }, { title: s_('Navigation|Explore'), link: '/explore', icon: 'compass' } ] end + let_it_be(:public_links_for_user) do + [ + { title: s_('Navigation|Your work'), link: '/', icon: 'work' }, + *public_link + ] + end + let_it_be(:admin_area_link) do { title: s_('Navigation|Admin Area'), link: '/admin', icon: 'admin' } end @@ -396,12 +402,20 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do helper.super_sidebar_context(user, group: nil, project: nil, panel: panel, panel_type: panel_type) end - context 'when user is not an admin' do - it 'returns only the public links' do + context 'when user is not logged in' do + let(:user) { nil } + + it 'returns only the public links for an anonymous user' do expect(subject[:context_switcher_links]).to eq(public_link) end end + context 'when user is not an admin' do + it 'returns only the public links for a user' do + expect(subject[:context_switcher_links]).to eq(public_links_for_user) + end + end + context 'when user is an admin' do before do allow(user).to receive(:admin?).and_return(true) @@ -420,7 +434,7 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do it 'returns public links, admin area and leave admin mode links' do expect(subject[:context_switcher_links]).to eq([ - *public_link, + *public_links_for_user, admin_area_link, leave_admin_mode_link ]) @@ -430,7 +444,7 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do context 'when admin mode is off' do it 'returns public links and enter admin mode link' do expect(subject[:context_switcher_links]).to eq([ - *public_link, + *public_links_for_user, enter_admin_mode_link ]) end @@ -444,7 +458,7 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do it 'returns public links and admin area link' do expect(subject[:context_switcher_links]).to eq([ - *public_link, + *public_links_for_user, admin_area_link ]) end diff --git a/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb b/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb index 582c0fe1b1b..af8b5240e40 100644 --- a/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb +++ b/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Gitlab::BackgroundMigration::RemoveBackfilledJobArtifactsExpireAt describe '#perform' do let(:job_artifact) { table(:ci_job_artifacts, database: :ci) } + let(:jobs) { table(:ci_builds, database: :ci) { |model| model.primary_key = :id } } let(:test_worker) do described_class.new( @@ -85,7 +86,7 @@ RSpec.describe Gitlab::BackgroundMigration::RemoveBackfilledJobArtifactsExpireAt private def create_job_artifact(id:, file_type:, expire_at:) - job = table(:ci_builds, database: :ci).create!(id: id, partition_id: 100) + job = jobs.create!(partition_id: 100) job_artifact.create!( id: id, job_id: job.id, expire_at: expire_at, project_id: project.id, file_type: file_type, partition_id: 100 diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index 702341a7ea7..35d44281072 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Status::Stage::Factory, feature_category: :continuous_integration do +RSpec.describe Gitlab::Ci::Status::Stage::Factory do let(:user) { create(:user) } let(:project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, project: project) } @@ -62,7 +62,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::Factory, feature_category: :continuous end context 'when stage has manual builds' do - Ci::HasStatus::BLOCKED_STATUS.each do |core_status| + (Ci::HasStatus::BLOCKED_STATUS + ['skipped']).each do |core_status| context "when status is #{core_status}" do let(:stage) { create(:ci_stage, pipeline: pipeline, status: core_status) } diff --git a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb index e23645c106b..9fdaddc083e 100644 --- a/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/play_manual_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Status::Stage::PlayManual, feature_category: :continuous_integration do +RSpec.describe Gitlab::Ci::Status::Stage::PlayManual do let(:stage) { double('stage') } let(:play_manual) { described_class.new(stage) } @@ -48,7 +48,7 @@ RSpec.describe Gitlab::Ci::Status::Stage::PlayManual, feature_category: :continu context 'when stage is skipped' do let(:stage) { create(:ci_stage, status: :skipped) } - it { is_expected.to be_falsy } + it { is_expected.to be_truthy } end context 'when stage is manual' do diff --git a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb index d03d4f64a0f..9fa24e5637f 100644 --- a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb +++ b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb @@ -24,26 +24,6 @@ RSpec.describe Gitlab::Cleanup::OrphanJobArtifactFilesBatch do expect(batch.lost_and_found.count).to eq(1) expect(batch.lost_and_found.first.artifact_id).to eq(orphan_artifact.id) end - - it 'does not mix up job ID and artifact ID' do - # take maximum ID of both tables to avoid any collision - max_id = [Ci::Build.maximum(:id), Ci::JobArtifact.maximum(:id)].compact.max.to_i - job_a = create(:ci_build, id: max_id + 1) - job_b = create(:ci_build, id: max_id + 2) - # reuse the build IDs for the job artifact IDs, but swap them - job_artifact_b = create(:ci_job_artifact, :archive, job: job_b, id: max_id + 1) - job_artifact_a = create(:ci_job_artifact, :archive, job: job_a, id: max_id + 2) - - batch << artifact_path(job_artifact_a) - batch << artifact_path(job_artifact_b) - - job_artifact_b.delete - - batch.clean! - - expect(File.exist?(job_artifact_a.file.path)).to be_truthy - expect(File.exist?(job_artifact_b.file.path)).to be_falsey - end end context 'with dry run' do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 641cc13adb7..6d01f480cd0 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -820,6 +820,7 @@ project: - scan_result_policy_reads - project_state - security_policy_bots +- target_branch_rules award_emoji: - awardable - user diff --git a/spec/models/ci/runner_manager_spec.rb b/spec/models/ci/runner_manager_spec.rb index 575064f0bea..d69bf1a0da0 100644 --- a/spec/models/ci/runner_manager_spec.rb +++ b/spec/models/ci/runner_manager_spec.rb @@ -112,6 +112,16 @@ RSpec.describe Ci::RunnerManager, feature_category: :runner_fleet, type: :model end end + describe '.order_id_desc' do + subject(:scope) { described_class.order_id_desc } + + let_it_be(:runner_manager1) { create(:ci_runner_machine) } + let_it_be(:runner_manager2) { create(:ci_runner_machine) } + + specify { expect(described_class.all).to eq([runner_manager1, runner_manager2]) } + it { is_expected.to eq([runner_manager2, runner_manager1]) } + end + describe '#status', :freeze_time do let(:runner_manager) { build(:ci_runner_machine, created_at: 8.days.ago) } diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 1be50083cd4..79e92082ee1 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::Stage, :models, feature_category: :continuous_integration do +RSpec.describe Ci::Stage, :models do let_it_be(:pipeline) { create(:ci_empty_pipeline) } let(:stage) { create(:ci_stage, pipeline: pipeline, project: pipeline.project) } diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb index c8706ae9698..3f6d39435fd 100644 --- a/spec/requests/api/graphql/ci/runners_spec.rb +++ b/spec/requests/api/graphql/ci/runners_spec.rb @@ -34,67 +34,116 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do QUERY end - let(:query) do - %( - query { - runners(type:#{runner_type},status:#{status}) { - #{fields} + context 'with filters' do + let(:query) do + %( + query { + runners(type: #{runner_type}, status: #{status}) { + #{fields} + } } - } - ) - end - - before do - allow_next_instance_of(::Gitlab::Ci::RunnerUpgradeCheck) do |instance| - allow(instance).to receive(:check_runner_upgrade_suggestion) + ) end - post_graphql(query, current_user: current_user) - end - - shared_examples 'a working graphql query returning expected runner' do - it_behaves_like 'a working graphql query' + before do + allow_next_instance_of(::Gitlab::Ci::RunnerUpgradeCheck) do |instance| + allow(instance).to receive(:check_runner_upgrade_suggestion) + end - it 'returns expected runner' do - expect(runners_graphql_data['nodes']).to contain_exactly(a_graphql_entity_for(expected_runner)) + post_graphql(query, current_user: current_user) end - it 'does not execute more queries per runner', :aggregate_failures do - # warm-up license cache and so on: - personal_access_token = create(:personal_access_token, user: current_user) - args = { current_user: current_user, token: { personal_access_token: personal_access_token } } - post_graphql(query, **args) - expect(graphql_data_at(:runners, :nodes)).not_to be_empty + shared_examples 'a working graphql query returning expected runner' do + it_behaves_like 'a working graphql query' + + it 'returns expected runner' do + expect(runners_graphql_data['nodes']).to contain_exactly(a_graphql_entity_for(expected_runner)) + end + + it 'does not execute more queries per runner', :aggregate_failures do + # warm-up license cache and so on: + personal_access_token = create(:personal_access_token, user: current_user) + args = { current_user: current_user, token: { personal_access_token: personal_access_token } } + post_graphql(query, **args) + expect(graphql_data_at(:runners, :nodes)).not_to be_empty - admin2 = create(:admin) - personal_access_token = create(:personal_access_token, user: admin2) - args = { current_user: admin2, token: { personal_access_token: personal_access_token } } - control = ActiveRecord::QueryRecorder.new { post_graphql(query, **args) } + admin2 = create(:admin) + personal_access_token = create(:personal_access_token, user: admin2) + args = { current_user: admin2, token: { personal_access_token: personal_access_token } } + control = ActiveRecord::QueryRecorder.new { post_graphql(query, **args) } - create(:ci_runner, :instance, version: '14.0.0', tag_list: %w[tag5 tag6], creator: admin2) - create(:ci_runner, :project, version: '14.0.1', projects: [project], tag_list: %w[tag3 tag8], - creator: current_user) + create(:ci_runner, :instance, version: '14.0.0', tag_list: %w[tag5 tag6], creator: admin2) + create(:ci_runner, :project, version: '14.0.1', projects: [project], tag_list: %w[tag3 tag8], + creator: current_user) - expect { post_graphql(query, **args) }.not_to exceed_query_limit(control) + expect { post_graphql(query, **args) }.not_to exceed_query_limit(control) + end end - end - context 'runner_type is INSTANCE_TYPE and status is ACTIVE' do - let(:runner_type) { 'INSTANCE_TYPE' } - let(:status) { 'ACTIVE' } + context 'runner_type is INSTANCE_TYPE and status is ACTIVE' do + let(:runner_type) { 'INSTANCE_TYPE' } + let(:status) { 'ACTIVE' } - let!(:expected_runner) { instance_runner } + let!(:expected_runner) { instance_runner } - it_behaves_like 'a working graphql query returning expected runner' - end + it_behaves_like 'a working graphql query returning expected runner' + end - context 'runner_type is PROJECT_TYPE and status is NEVER_CONTACTED' do - let(:runner_type) { 'PROJECT_TYPE' } - let(:status) { 'NEVER_CONTACTED' } + context 'runner_type is PROJECT_TYPE and status is NEVER_CONTACTED' do + let(:runner_type) { 'PROJECT_TYPE' } + let(:status) { 'NEVER_CONTACTED' } - let!(:expected_runner) { project_runner } + let!(:expected_runner) { project_runner } + + it_behaves_like 'a working graphql query returning expected runner' + end + end - it_behaves_like 'a working graphql query returning expected runner' + context 'without filters' do + context 'with managers requested for multiple runners' do + let(:fields) do + <<~QUERY + nodes { + managers { + nodes { + #{all_graphql_fields_for('CiRunnerManager', max_depth: 1)} + } + } + } + QUERY + end + + let(:query) do + %( + query { + runners { + #{fields} + } + } + ) + end + + it 'does not execute more queries per runner', :aggregate_failures do + # warm-up license cache and so on: + personal_access_token = create(:personal_access_token, user: current_user) + args = { current_user: current_user, token: { personal_access_token: personal_access_token } } + post_graphql(query, **args) + expect(graphql_data_at(:runners, :nodes)).not_to be_empty + + admin2 = create(:admin) + personal_access_token = create(:personal_access_token, user: admin2) + args = { current_user: admin2, token: { personal_access_token: personal_access_token } } + control = ActiveRecord::QueryRecorder.new { post_graphql(query, **args) } + + create(:ci_runner, :instance, :with_runner_manager, version: '14.0.0', tag_list: %w[tag5 tag6], + creator: admin2) + create(:ci_runner, :project, :with_runner_manager, version: '14.0.1', projects: [project], + tag_list: %w[tag3 tag8], + creator: current_user) + + expect { post_graphql(query, **args) }.not_to exceed_query_limit(control) + end + end end end diff --git a/spec/serializers/stage_entity_spec.rb b/spec/serializers/stage_entity_spec.rb index fe8ee027245..5cb5724ebdc 100644 --- a/spec/serializers/stage_entity_spec.rb +++ b/spec/serializers/stage_entity_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe StageEntity, feature_category: :continuous_integration do +RSpec.describe StageEntity do let(:pipeline) { create(:ci_pipeline) } let(:request) { double('request') } let(:user) { create(:user) } @@ -76,8 +76,8 @@ RSpec.describe StageEntity, feature_category: :continuous_integration do context 'with a skipped stage ' do let(:stage) { create(:ci_stage, status: 'skipped') } - it 'does not contain play_all_manual' do - expect(subject[:status][:action]).not_to be_present + it 'contains play_all_manual' do + expect(subject[:status][:action]).to be_present end end diff --git a/spec/support/shared_examples/ci/stage_shared_examples.rb b/spec/support/shared_examples/ci/stage_shared_examples.rb index cdb1058e584..a2849e00d27 100644 --- a/spec/support/shared_examples/ci/stage_shared_examples.rb +++ b/spec/support/shared_examples/ci/stage_shared_examples.rb @@ -21,7 +21,7 @@ RSpec.shared_examples 'manual playable stage' do |stage_type| context 'when is skipped' do let(:status) { 'skipped' } - it { is_expected.to be_falsy } + it { is_expected.to be_truthy } end end end diff --git a/spec/support/shared_examples/helpers/super_sidebar_shared_examples.rb b/spec/support/shared_examples/helpers/super_sidebar_shared_examples.rb index 636b9870bd7..9da804b3140 100644 --- a/spec/support/shared_examples/helpers/super_sidebar_shared_examples.rb +++ b/spec/support/shared_examples/helpers/super_sidebar_shared_examples.rb @@ -32,4 +32,6 @@ RSpec.shared_examples 'logged-out super-sidebar context' do it_behaves_like 'shared super sidebar context' it { is_expected.to include({ is_logged_in: false }) } + + it { expect(subject[:context_switcher_links]).to be_an(Array) } end diff --git a/spec/tooling/danger/stable_branch_spec.rb b/spec/tooling/danger/stable_branch_spec.rb index 439a878a5e6..69e68f983fd 100644 --- a/spec/tooling/danger/stable_branch_spec.rb +++ b/spec/tooling/danger/stable_branch_spec.rb @@ -59,6 +59,8 @@ RSpec.describe Tooling::Danger::StableBranch, feature_category: :delivery do end context 'when not applicable' do + let(:current_stable_branch) { '15-1-stable-ee' } + where(:stable_branch?, :security_mr?) do true | true false | true @@ -67,7 +69,7 @@ RSpec.describe Tooling::Danger::StableBranch, feature_category: :delivery do with_them do before do - allow(fake_helper).to receive(:mr_target_branch).and_return(stable_branch? ? '15-1-stable-ee' : 'main') + allow(fake_helper).to receive(:mr_target_branch).and_return(stable_branch? ? current_stable_branch : 'main') allow(fake_helper).to receive(:security_mr?).and_return(security_mr?) end @@ -239,7 +241,7 @@ RSpec.describe Tooling::Danger::StableBranch, feature_category: :delivery do end context 'when not an applicable version' do - let(:target_branch) { '14-9-stable-ee' } + let(:target_branch) { '15-0-stable-ee' } it 'warns about the package-and-test pipeline and the version' do expect(stable_branch).to receive(:warn).with(described_class::WARN_PACKAGE_AND_TEST_MESSAGE) @@ -297,18 +299,6 @@ RSpec.describe Tooling::Danger::StableBranch, feature_category: :delivery do it_behaves_like 'without a failure' end - - context 'when too many version API requests are made' do - let(:parsed_response) { [{ 'version' => '15.0.0' }] } - - it 'adds a warning' do - expect(HTTParty).to receive(:get).and_return(version_response).at_least(10).times - expect(stable_branch).to receive(:warn).with(described_class::WARN_PACKAGE_AND_TEST_MESSAGE) - expect(stable_branch).to receive(:warn).with(described_class::FAILED_VERSION_REQUEST_MESSAGE) - - subject - end - end end end |