diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-29 12:10:11 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-29 12:10:11 +0300 |
commit | 38e4bfea582e8c755dd21613bf21658b1771449b (patch) | |
tree | 0856b453061d24face108bd980cd5a63b09ead21 /spec | |
parent | 64d80d99a907c9b5ac0d72b6a958916c496e31b1 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
18 files changed, 301 insertions, 328 deletions
diff --git a/spec/deprecation_toolkit_env.rb b/spec/deprecation_toolkit_env.rb index 4bd04eabe69..2359765fedd 100644 --- a/spec/deprecation_toolkit_env.rb +++ b/spec/deprecation_toolkit_env.rb @@ -57,7 +57,6 @@ module DeprecationToolkitEnv %w[ activerecord-6.0.3.6/lib/active_record/migration.rb activesupport-6.0.3.6/lib/active_support/cache.rb - carrierwave-1.3.1/lib/carrierwave/sanitized_file.rb activerecord-6.0.3.6/lib/active_record/relation.rb asciidoctor-2.0.12/lib/asciidoctor/extensions.rb ] diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index 04b4caa52fe..0566ce968d2 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -130,30 +130,7 @@ RSpec.describe 'Issue Sidebar' do end end - context 'when invite_members_version_b experiment is enabled' do - before do - stub_experiment_for_subject(invite_members_version_b: true) - end - - it 'shows a link for inviting members and follows through to modal' do - project.add_developer(user) - visit_issue(project, issue2) - - open_assignees_dropdown - - page.within '.dropdown-menu-user' do - expect(page).to have_link('Invite members', href: '#') - expect(page).to have_selector('[data-track-event="click_invite_members_version_b"]') - expect(page).to have_selector('[data-track-label="edit_assignee"]') - end - - click_link 'Invite members' - - expect(page).to have_content("Oops, this feature isn't ready yet") - end - end - - context 'when invite_members_version_b experiment is disabled' do + context 'when user cannot invite members in assignee dropdown' do it 'shows author in assignee dropdown and no invite link' do project.add_developer(user) visit_issue(project, issue2) diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js index 8417ac9a41a..3758723c571 100644 --- a/spec/frontend/boards/stores/actions_spec.js +++ b/spec/frontend/boards/stores/actions_spec.js @@ -9,7 +9,7 @@ import { formatIssue, getMoveData, } from '~/boards/boards_util'; -import { inactiveId, ISSUABLE, ListType } from '~/boards/constants'; +import { inactiveId, ISSUABLE, ListType, issuableTypes } from '~/boards/constants'; import destroyBoardListMutation from '~/boards/graphql/board_list_destroy.mutation.graphql'; import issueCreateMutation from '~/boards/graphql/issue_create.mutation.graphql'; import actions, { gqlClient } from '~/boards/stores/actions'; @@ -459,7 +459,7 @@ describe('updateList', () => { boardType: 'group', disabled: false, boardLists: [{ type: 'closed' }], - issuableType: 'issue', + issuableType: issuableTypes.issue, }; testAction( @@ -503,6 +503,7 @@ describe('removeList', () => { beforeEach(() => { state = { boardLists: mockListsById, + issuableType: issuableTypes.issue, }; }); @@ -1375,7 +1376,7 @@ describe('setActiveItemSubscribed', () => { [mockActiveIssue.id]: mockActiveIssue, }, fullPath: 'gitlab-org', - issuableType: 'issue', + issuableType: issuableTypes.issue, }; const getters = { activeBoardItem: mockActiveIssue, isEpicBoard: false }; const subscribedState = true; @@ -1483,7 +1484,7 @@ describe('setActiveIssueMilestone', () => { describe('setActiveItemTitle', () => { const state = { boardItems: { [mockIssue.id]: mockIssue }, - issuableType: 'issue', + issuableType: issuableTypes.issue, fullPath: 'path/f', }; const getters = { activeBoardItem: mockIssue, isEpicBoard: false }; diff --git a/spec/frontend/invite_member/components/invite_member_modal_spec.js b/spec/frontend/invite_member/components/invite_member_modal_spec.js deleted file mode 100644 index 03e3da2d5ef..00000000000 --- a/spec/frontend/invite_member/components/invite_member_modal_spec.js +++ /dev/null @@ -1,67 +0,0 @@ -import { GlLink, GlModal } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import { stubComponent } from 'helpers/stub_component'; -import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper'; -import InviteMemberModal from '~/invite_member/components/invite_member_modal.vue'; - -const memberPath = 'member_path'; - -const GlEmoji = { template: '<img />' }; -const createComponent = () => { - return shallowMount(InviteMemberModal, { - propsData: { - membersPath: memberPath, - }, - stubs: { - GlEmoji, - GlModal: stubComponent(GlModal, { - template: '<div><slot name="modal-title"></slot><slot></slot></div>', - }), - }, - }); -}; - -describe('InviteMemberModal', () => { - let wrapper; - - beforeEach(() => { - wrapper = createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - const findLink = () => wrapper.find(GlLink); - - describe('rendering the modal', () => { - it('renders the modal with the correct title', () => { - expect(wrapper.text()).toContain("Oops, this feature isn't ready yet"); - }); - - describe('rendering the see who link', () => { - it('renders the correct link', () => { - expect(findLink().attributes('href')).toBe(memberPath); - }); - }); - }); - - describe('tracking', () => { - let trackingSpy; - - afterEach(() => { - unmockTracking(); - }); - - it('send an event when go to pipelines is clicked', () => { - trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn); - - triggerEvent(findLink().element); - - expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_who_can_invite_link', { - label: 'invite_members_message', - }); - }); - }); -}); diff --git a/spec/frontend/invite_member/components/invite_member_trigger_mock_data.js b/spec/frontend/invite_member/components/invite_member_trigger_mock_data.js deleted file mode 100644 index 9b34a8027e9..00000000000 --- a/spec/frontend/invite_member/components/invite_member_trigger_mock_data.js +++ /dev/null @@ -1,7 +0,0 @@ -const triggerProvides = { - displayText: 'Invite member', - event: 'click_invite_members_version_b', - label: 'edit_assignee', -}; - -export default triggerProvides; diff --git a/spec/frontend/invite_member/components/invite_member_trigger_spec.js b/spec/frontend/invite_member/components/invite_member_trigger_spec.js deleted file mode 100644 index 630e2dbfc16..00000000000 --- a/spec/frontend/invite_member/components/invite_member_trigger_spec.js +++ /dev/null @@ -1,48 +0,0 @@ -import { GlLink } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper'; -import InviteMemberTrigger from '~/invite_member/components/invite_member_trigger.vue'; -import triggerProvides from './invite_member_trigger_mock_data'; - -const createComponent = () => { - return shallowMount(InviteMemberTrigger, { propsData: triggerProvides }); -}; - -describe('InviteMemberTrigger', () => { - let wrapper; - - beforeEach(() => { - wrapper = createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - const findLink = () => wrapper.find(GlLink); - - describe('displayText', () => { - it('includes the correct displayText for the link', () => { - expect(findLink().text()).toBe(triggerProvides.displayText); - }); - }); - - describe('tracking', () => { - let trackingSpy; - - afterEach(() => { - unmockTracking(); - }); - - it('send an event when go to pipelines is clicked', () => { - trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn); - - triggerEvent(findLink().element); - - expect(trackingSpy).toHaveBeenCalledWith('_category_', triggerProvides.event, { - label: triggerProvides.label, - }); - }); - }); -}); diff --git a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js index 543bc1c128a..2973c25b936 100644 --- a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js +++ b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js @@ -533,7 +533,7 @@ describe('Sidebar assignees widget', () => { expect(findInviteMembersLink().exists()).toBe(false); }); - it('does not render invite members link if `directlyInviteMembers` and `indirectlyInviteMembers` were not passed', async () => { + it('does not render invite members link if `directlyInviteMembers` was not passed', async () => { createComponent(); await waitForPromises(); expect(findInviteMembersLink().exists()).toBe(false); @@ -548,14 +548,4 @@ describe('Sidebar assignees widget', () => { await waitForPromises(); expect(findInviteMembersLink().exists()).toBe(true); }); - - it('renders invite members link if `indirectlyInviteMembers` is true', async () => { - createComponent({ - provide: { - indirectlyInviteMembers: true, - }, - }); - await waitForPromises(); - expect(findInviteMembersLink().exists()).toBe(true); - }); }); diff --git a/spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js index 06f7da3d1ab..cfbe7227915 100644 --- a/spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js +++ b/spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js @@ -1,25 +1,14 @@ import { shallowMount } from '@vue/test-utils'; -import InviteMemberModal from '~/invite_member/components/invite_member_modal.vue'; -import InviteMemberTrigger from '~/invite_member/components/invite_member_trigger.vue'; import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue'; import SidebarInviteMembers from '~/sidebar/components/assignees/sidebar_invite_members.vue'; -const testProjectMembersPath = 'test-path'; - describe('Sidebar invite members component', () => { let wrapper; const findDirectInviteLink = () => wrapper.findComponent(InviteMembersTrigger); - const findIndirectInviteLink = () => wrapper.findComponent(InviteMemberTrigger); - const findInviteModal = () => wrapper.findComponent(InviteMemberModal); - const createComponent = ({ directlyInviteMembers = false } = {}) => { - wrapper = shallowMount(SidebarInviteMembers, { - provide: { - directlyInviteMembers, - projectMembersPath: testProjectMembersPath, - }, - }); + const createComponent = () => { + wrapper = shallowMount(SidebarInviteMembers); }; afterEach(() => { @@ -28,32 +17,11 @@ describe('Sidebar invite members component', () => { describe('when directly inviting members', () => { beforeEach(() => { - createComponent({ directlyInviteMembers: true }); + createComponent(); }); it('renders a direct link to project members path', () => { expect(findDirectInviteLink().exists()).toBe(true); }); - - it('does not render invite members trigger and modal components', () => { - expect(findIndirectInviteLink().exists()).toBe(false); - expect(findInviteModal().exists()).toBe(false); - }); - }); - - describe('when indirectly inviting members', () => { - beforeEach(() => { - createComponent(); - }); - - it('does not render a direct link to project members path', () => { - expect(findDirectInviteLink().exists()).toBe(false); - }); - - it('does not render invite members trigger and modal components', () => { - expect(findIndirectInviteLink().exists()).toBe(true); - expect(findInviteModal().exists()).toBe(true); - expect(findInviteModal().props('membersPath')).toBe(testProjectMembersPath); - }); }); }); diff --git a/spec/frontend/users_select/test_helper.js b/spec/frontend/users_select/test_helper.js index 89bbbba9913..c5adbe9bb09 100644 --- a/spec/frontend/users_select/test_helper.js +++ b/spec/frontend/users_select/test_helper.js @@ -1,7 +1,7 @@ -import { waitFor } from '@testing-library/dom'; import MockAdapter from 'axios-mock-adapter'; import { memoize, cloneDeep } from 'lodash'; import { getFixture, getJSONFixture } from 'helpers/fixtures'; +import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; import UsersSelect from '~/users_select'; @@ -103,8 +103,10 @@ export const setAssignees = (...users) => { ); }; export const toggleDropdown = () => findUserSearchButton().click(); -export const waitForDropdownItems = () => - waitFor(() => expect(findDropdownItem(getUsersFixtureAt(0))).not.toBeNull()); +export const waitForDropdownItems = async () => { + await axios.waitForAll(); + await waitForPromises(); +}; // assertion helpers --------------------------------------------------------- export const createUnassignedExpectation = () => { diff --git a/spec/helpers/invite_members_helper_spec.rb b/spec/helpers/invite_members_helper_spec.rb index 7ddf7d059e5..dbe4f970a99 100644 --- a/spec/helpers/invite_members_helper_spec.rb +++ b/spec/helpers/invite_members_helper_spec.rb @@ -12,21 +12,6 @@ RSpec.describe InviteMembersHelper do helper.extend(Gitlab::Experimentation::ControllerConcern) end - describe '#show_invite_members_track_event' do - it 'shows values when can directly invite members' do - allow(helper).to receive(:directly_invite_members?).and_return(true) - - expect(helper.show_invite_members_track_event).to eq 'show_invite_members' - end - - it 'shows values when can indirectly invite members' do - allow(helper).to receive(:directly_invite_members?).and_return(false) - allow(helper).to receive(:indirectly_invite_members?).and_return(true) - - expect(helper.show_invite_members_track_event).to eq 'show_invite_members_version_b' - end - end - context 'with project' do before do assign(:project, project) @@ -87,38 +72,6 @@ RSpec.describe InviteMembersHelper do end end end - - describe "#indirectly_invite_members?" do - context 'when a user is a developer' do - before do - allow(helper).to receive(:current_user) { developer } - end - - it 'returns false' do - allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_b) { false } - - expect(helper.indirectly_invite_members?).to eq false - end - - it 'returns true' do - allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_b) { true } - - expect(helper.indirectly_invite_members?).to eq true - end - end - - context 'when a user is an owner' do - before do - allow(helper).to receive(:current_user) { owner } - end - - it 'returns false' do - allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_b) { true } - - expect(helper.indirectly_invite_members?).to eq false - end - end - end end context 'with group' do diff --git a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb index abee1fec80a..78e0b7627e9 100644 --- a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb @@ -9,6 +9,42 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d it { is_expected.to belong_to(:batched_migration).with_foreign_key(:batched_background_migration_id) } end + describe 'scopes' do + let_it_be(:fixed_time) { Time.new(2021, 04, 27, 10, 00, 00, 00) } + + let_it_be(:pending_job) { create(:batched_background_migration_job, status: :pending, updated_at: fixed_time) } + let_it_be(:running_job) { create(:batched_background_migration_job, status: :running, updated_at: fixed_time) } + let_it_be(:stuck_job) { create(:batched_background_migration_job, status: :pending, updated_at: fixed_time - described_class::STUCK_JOBS_TIMEOUT) } + let_it_be(:failed_job) { create(:batched_background_migration_job, status: :failed, attempts: 1) } + + before_all do + create(:batched_background_migration_job, status: :failed, attempts: described_class::MAX_ATTEMPTS) + create(:batched_background_migration_job, status: :succeeded) + end + + before do + travel_to fixed_time + end + + describe '.active' do + it 'returns active jobs' do + expect(described_class.active).to contain_exactly(pending_job, running_job, stuck_job) + end + end + + describe '.stuck' do + it 'returns stuck jobs' do + expect(described_class.stuck).to contain_exactly(stuck_job) + end + end + + describe '.retriable' do + it 'returns retriable jobs' do + expect(described_class.retriable).to contain_exactly(failed_job, stuck_job) + end + end + end + describe 'delegated batched_migration attributes' do let(:batched_job) { build(:batched_background_migration_job) } let(:batched_migration) { batched_job.batched_migration } diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb index 79b21172dc6..9f0493ab0d7 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb @@ -17,9 +17,9 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do end it 'marks the migration as finished' do - relation = Gitlab::Database::BackgroundMigration::BatchedMigration.finished.where(id: migration.id) + runner.run_migration_job(migration) - expect { runner.run_migration_job(migration) }.to change { relation.count }.by(1) + expect(migration.reload).to be_finished end end @@ -92,7 +92,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do let!(:event3) { create(:event) } let!(:migration) do - create(:batched_background_migration, :active, batch_size: 2, min_value: event1.id, max_value: event3.id) + create(:batched_background_migration, :active, batch_size: 2, min_value: event1.id, max_value: event2.id) end let!(:previous_job) do @@ -101,14 +101,24 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do min_value: event1.id, max_value: event2.id, batch_size: 2, - sub_batch_size: 1) + sub_batch_size: 1, + status: :succeeded + ) end let(:job_relation) do Gitlab::Database::BackgroundMigration::BatchedJob.where(batched_background_migration_id: migration.id) end + context 'when the migration has no batches remaining' do + it_behaves_like 'it has completed the migration' + end + context 'when the migration has batches to process' do + before do + migration.update!(max_value: event3.id) + end + it 'runs the migration job for the next batch' do expect(migration_wrapper).to receive(:perform) do |job_record| expect(job_record).to eq(job_relation.last) @@ -132,17 +142,82 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do end end - context 'when the migration has no batches remaining' do + context 'when migration has failed jobs' do before do - create(:batched_background_migration_job, - batched_migration: migration, - min_value: event3.id, - max_value: event3.id, - batch_size: 2, - sub_batch_size: 1) + previous_job.update!(status: :failed) end - it_behaves_like 'it has completed the migration' + it 'retries the failed job' do + expect(migration_wrapper).to receive(:perform) do |job_record| + expect(job_record).to eq(previous_job) + end + + expect { runner.run_migration_job(migration) }.to change { job_relation.count }.by(0) + end + + context 'when failed job has reached the maximum number of attempts' do + before do + previous_job.update!(attempts: Gitlab::Database::BackgroundMigration::BatchedJob::MAX_ATTEMPTS) + end + + it 'marks the migration as failed' do + expect(migration_wrapper).not_to receive(:perform) + + expect { runner.run_migration_job(migration) }.to change { job_relation.count }.by(0) + + expect(migration).to be_failed + end + end + end + + context 'when migration has stuck jobs' do + before do + previous_job.update!(status: :running, updated_at: 1.hour.ago - Gitlab::Database::BackgroundMigration::BatchedJob::STUCK_JOBS_TIMEOUT) + end + + it 'retries the stuck job' do + expect(migration_wrapper).to receive(:perform) do |job_record| + expect(job_record).to eq(previous_job) + end + + expect { runner.run_migration_job(migration.reload) }.to change { job_relation.count }.by(0) + end + end + + context 'when migration has possible stuck jobs' do + before do + previous_job.update!(status: :running, updated_at: 1.hour.from_now - Gitlab::Database::BackgroundMigration::BatchedJob::STUCK_JOBS_TIMEOUT) + end + + it 'keeps the migration active' do + expect(migration_wrapper).not_to receive(:perform) + + expect { runner.run_migration_job(migration) }.to change { job_relation.count }.by(0) + + expect(migration.reload).to be_active + end + end + + context 'when the migration has batches to process and failed jobs' do + before do + migration.update!(max_value: event3.id) + previous_job.update!(status: :failed) + end + + it 'runs next batch then retries the failed job' do + expect(migration_wrapper).to receive(:perform) do |job_record| + expect(job_record).to eq(job_relation.last) + job_record.update!(status: :succeeded) + end + + expect { runner.run_migration_job(migration) }.to change { job_relation.count }.by(1) + + expect(migration_wrapper).to receive(:perform) do |job_record| + expect(job_record).to eq(previous_job) + end + + expect { runner.run_migration_job(migration.reload) }.to change { job_relation.count }.by(0) + end end end end @@ -189,10 +264,12 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do it 'runs all jobs inline until finishing the migration' do expect(migration_wrapper).to receive(:perform) do |job_record| expect(job_record).to eq(job_relation.first) + job_record.update!(status: :succeeded) end expect(migration_wrapper).to receive(:perform) do |job_record| expect(job_record).to eq(job_relation.last) + job_record.update!(status: :succeeded) end expect { runner.run_entire_migration(migration) }.to change { job_relation.count }.by(2) diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb index fdbc2286502..987f2c5a935 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb @@ -49,6 +49,42 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, ' end end + context 'when running a job that failed previously' do + let!(:job_record) do + create(:batched_background_migration_job, + batched_migration: active_migration, + pause_ms: pause_ms, + attempts: 1, + status: :failed, + finished_at: 1.hour.ago, + metrics: { 'my_metrics' => 'some_value' } + ) + end + + it 'increments attempts and updates other fields' do + updated_metrics = { 'updated_metrics' => 'some_value' } + + expect(job_instance).to receive(:perform) + expect(job_instance).to receive(:batch_metrics).and_return(updated_metrics) + + expect(job_record).to receive(:update!).with( + hash_including(attempts: 2, status: :running, finished_at: nil, metrics: {}) + ).and_call_original + + freeze_time do + subject + + job_record.reload + + expect(job_record).not_to be_failed + expect(job_record.attempts).to eq(2) + expect(job_record.started_at).to eq(Time.current) + expect(job_record.finished_at).to eq(Time.current) + expect(job_record.metrics).to eq(updated_metrics) + end + end + end + context 'reporting prometheus metrics' do let(:labels) { job_record.batched_migration.prometheus_labels } diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb index 5fef14bd2a0..10bfa9e8d0e 100644 --- a/spec/lib/gitlab/experimentation_spec.rb +++ b/spec/lib/gitlab/experimentation_spec.rb @@ -7,7 +7,6 @@ require 'spec_helper' RSpec.describe Gitlab::Experimentation::EXPERIMENTS do it 'temporarily ensures we know what experiments exist for backwards compatibility' do expected_experiment_keys = [ - :invite_members_version_b, :invite_members_empty_group_version_a, :contact_sales_btn_in_app ] diff --git a/spec/lib/sidebars/projects/menus/ci_cd_menu_spec.rb b/spec/lib/sidebars/projects/menus/ci_cd_menu_spec.rb new file mode 100644 index 00000000000..89b03e1c918 --- /dev/null +++ b/spec/lib/sidebars/projects/menus/ci_cd_menu_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Sidebars::Projects::Menus::CiCdMenu do + let(:project) { build(:project) } + let(:user) { project.owner } + let(:can_view_pipeline_editor) { true } + let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project, current_ref: 'master', can_view_pipeline_editor: can_view_pipeline_editor) } + + subject { described_class.new(context) } + + describe '#render?' do + context 'when user cannot read builds' do + let(:user) { nil } + + it 'returns false' do + expect(subject.render?).to eq false + end + end + + context 'when user can read builds' do + it 'returns true' do + expect(subject.render?).to eq true + end + end + end + + describe 'Pipelines Editor' do + subject { described_class.new(context).items.index { |e| e.item_id == :pipelines_editor } } + + context 'when user cannot view pipeline editor' do + let(:can_view_pipeline_editor) { false } + + it 'does not include pipeline editor menu item' do + is_expected.to be_nil + end + end + + context 'when user can view pipeline editor' do + it 'includes pipeline editor menu item' do + is_expected.not_to be_nil + end + end + end + + describe 'Artifacts' do + subject { described_class.new(context).items.index { |e| e.item_id == :artifacts } } + + context 'when feature flag :artifacts_management_page is disabled' do + it 'does not include artifacts menu item' do + stub_feature_flags(artifacts_management_page: false) + + is_expected.to be_nil + end + end + + context 'when feature flag :artifacts_management_page is enabled' do + it 'includes artifacts menu item' do + stub_feature_flags(artifacts_management_page: true) + + is_expected.not_to be_nil + end + end + end +end diff --git a/spec/models/integration_spec.rb b/spec/models/concerns/integration_spec.rb index 781e2aece56..781e2aece56 100644 --- a/spec/models/integration_spec.rb +++ b/spec/models/concerns/integration_spec.rb diff --git a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb index 49c3674277d..736c353c2aa 100644 --- a/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb +++ b/spec/support/shared_examples/features/issuable_invite_members_shared_examples.rb @@ -22,32 +22,7 @@ RSpec.shared_examples 'issuable invite members experiments' do end end - context 'when invite_members_version_b experiment is enabled' do - before do - stub_experiment_for_subject(invite_members_version_b: true) - end - - it 'shows a link for inviting members and follows through to modal' do - project.add_developer(user) - visit issuable_path - - find('.block.assignee .edit-link').click - - wait_for_requests - - page.within '.dropdown-menu-user' do - expect(page).to have_link('Invite Members', href: '#') - expect(page).to have_selector('[data-track-event="click_invite_members_version_b"]') - expect(page).to have_selector('[data-track-label="edit_assignee"]') - end - - click_link 'Invite Members' - - expect(page).to have_content("Oops, this feature isn't ready yet") - end - end - - context 'when invite_members_version_b experiment is disabled' do + context 'when user cannot invite members in assignee dropdown' do it 'shows author in assignee dropdown and no invite link' do project.add_developer(user) visit issuable_path diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb index c501c418466..16362aed1cd 100644 --- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb @@ -257,6 +257,64 @@ RSpec.describe 'layouts/nav/sidebar/_project' do end end + describe 'CI/CD' do + it 'has a link to pipelines page' do + render + + expect(rendered).to have_link('CI/CD', href: project_pipelines_path(project)) + end + + describe 'Artifacts' do + it 'has a link to the artifacts page' do + render + + expect(rendered).to have_link('Artifacts', href: project_artifacts_path(project)) + end + end + + describe 'Jobs' do + it 'has a link to the jobs page' do + render + + expect(rendered).to have_link('Jobs', href: project_jobs_path(project)) + end + end + + describe 'Pipeline Schedules' do + it 'has a link to the pipeline schedules page' do + render + + expect(rendered).to have_link('Schedules', href: pipeline_schedules_path(project)) + end + end + + describe 'Pipelines' do + it 'has a link to the pipelines page' do + render + + expect(rendered).to have_link('Pipelines', href: project_pipelines_path(project)) + end + end + + describe 'Pipeline Editor' do + it 'has a link to the pipeline editor' do + render + + expect(rendered).to have_link('Editor', href: project_ci_pipeline_editor_path(project)) + end + + context 'when user cannot access pipeline editor' do + it 'does not has a link to the pipeline editor' do + allow(view).to receive(:can_view_pipeline_editor?).and_return(false) + + render + + expect(rendered).not_to have_link('Editor', href: project_ci_pipeline_editor_path(project)) + end + end + end + end + describe 'packages tab' do before do stub_container_registry_config(enabled: true) @@ -419,48 +477,6 @@ RSpec.describe 'layouts/nav/sidebar/_project' do end end - describe 'ci/cd settings tab' do - before do - project.update!(archived: project_archived) - end - - context 'when project is archived' do - let(:project_archived) { true } - - it 'does not show the ci/cd settings tab' do - render - - expect(rendered).not_to have_link('CI/CD', href: project_settings_ci_cd_path(project)) - end - end - - context 'when project is active' do - let(:project_archived) { false } - - it 'shows the ci/cd settings tab' do - render - - expect(rendered).to have_link('CI/CD', href: project_settings_ci_cd_path(project)) - end - end - end - - describe 'pipeline editor link' do - it 'shows the pipeline editor link' do - render - - expect(rendered).to have_link('Editor', href: project_ci_pipeline_editor_path(project)) - end - - it 'does not show the pipeline editor link' do - allow(view).to receive(:can_view_pipeline_editor?).and_return(false) - - render - - expect(rendered).not_to have_link('Editor', href: project_ci_pipeline_editor_path(project)) - end - end - describe 'operations settings tab' do describe 'archive projects' do before do |