diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-01 21:07:43 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-01 21:07:43 +0300 |
commit | ad1e76fb4d1392c890c8b5e218a256a416d5a50b (patch) | |
tree | 51e5541bb1f1a799e288701bc1170a3b1a9a7393 /spec | |
parent | 8b1036168b0d395c379cbbaf457e256860147405 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
17 files changed, 207 insertions, 137 deletions
diff --git a/spec/features/markdown/observability_spec.rb b/spec/features/markdown/observability_spec.rb index e57bfafe05e..0c5aba5326f 100644 --- a/spec/features/markdown/observability_spec.rb +++ b/spec/features/markdown/observability_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Observability rendering', :js, feature_category: :metrics do let_it_be(:observable_url) { "https://observe.gitlab.com/#{group.id}/some-dashboard" } let_it_be(:expected) do - %(<iframe src="#{observable_url}?theme=light&kiosk" frameborder="0") + %(<iframe src="#{observable_url}?theme=light&kiosk=inline-embed" frameborder="0") end before do diff --git a/spec/frontend/behaviors/markdown/render_observability_spec.js b/spec/frontend/behaviors/markdown/render_observability_spec.js index c87d11742dc..7ca426d8cd6 100644 --- a/spec/frontend/behaviors/markdown/render_observability_spec.js +++ b/spec/frontend/behaviors/markdown/render_observability_spec.js @@ -3,7 +3,9 @@ import * as ColorUtils from '~/lib/utils/color_utils'; describe('Observability iframe renderer', () => { const findObservabilityIframes = (theme = 'light') => - document.querySelectorAll(`iframe[src="https://observe.gitlab.com/?theme=${theme}&kiosk"]`); + document.querySelectorAll( + `iframe[src="https://observe.gitlab.com/?theme=${theme}&kiosk=inline-embed"]`, + ); const renderEmbeddedObservability = () => { renderObservability([...document.querySelectorAll('.js-render-observability')]); diff --git a/spec/frontend/clusters/forms/components/integration_form_spec.js b/spec/frontend/clusters/forms/components/integration_form_spec.js index b17886a5826..396f8215b9f 100644 --- a/spec/frontend/clusters/forms/components/integration_form_spec.js +++ b/spec/frontend/clusters/forms/components/integration_form_spec.js @@ -1,6 +1,6 @@ import { GlToggle, GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import Vue, { nextTick } from 'vue'; +import Vue from 'vue'; import Vuex from 'vuex'; import IntegrationForm from '~/clusters/forms/components/integration_form.vue'; import { createStore } from '~/clusters/forms/stores/index'; @@ -27,17 +27,9 @@ describe('ClusterIntegrationForm', () => { }); }; - const destroyWrapper = () => { - wrapper.destroy(); - wrapper = null; - }; - const findSubmitButton = () => wrapper.findComponent(GlButton); const findGlToggle = () => wrapper.findComponent(GlToggle); - - afterEach(() => { - destroyWrapper(); - }); + const findClusterEnvironmentScopeInput = () => wrapper.find('[id="cluster_environment_scope"]'); describe('rendering', () => { beforeEach(() => createWrapper()); @@ -50,7 +42,9 @@ describe('ClusterIntegrationForm', () => { }); it('sets the envScope to default', () => { - expect(wrapper.find('[id="cluster_environment_scope"]').attributes('value')).toBe('*'); + expect(findClusterEnvironmentScopeInput().attributes('value')).toBe( + defaultStoreValues.environmentScope, + ); }); it('sets the baseDomain to default', () => { @@ -76,20 +70,15 @@ describe('ClusterIntegrationForm', () => { beforeEach(() => createWrapper()); it('enables the submit button on changing toggle to different value', async () => { - await nextTick(); - // setData is a bad approach because it changes the internal implementation which we should not touch - // but our GlFormInput lacks the ability to set a new value. - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - await wrapper.setData({ toggleEnabled: !defaultStoreValues.enabled }); + await findGlToggle().vm.$emit('change', false); expect(findSubmitButton().props('disabled')).toBe(false); }); it('enables the submit button on changing input values', async () => { - await nextTick(); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - await wrapper.setData({ envScope: `${defaultStoreValues.environmentScope}1` }); + await findClusterEnvironmentScopeInput().vm.$emit( + 'input', + `${defaultStoreValues.environmentScope}1`, + ); expect(findSubmitButton().props('disabled')).toBe(false); }); }); diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js index 3d8cbaa6b89..14c3ac8b8fe 100644 --- a/spec/frontend/diffs/components/app_spec.js +++ b/spec/frontend/diffs/components/app_spec.js @@ -59,6 +59,7 @@ describe('diffs/components/app', () => { endpoint: TEST_ENDPOINT, endpointMetadata: `${TEST_HOST}/diff/endpointMetadata`, endpointBatch: `${TEST_HOST}/diff/endpointBatch`, + endpointDiffForPath: TEST_ENDPOINT, endpointCoverage: `${TEST_HOST}/diff/endpointCoverage`, endpointCodequality: '', projectPath: 'namespace/project', diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js index b5e773ecdab..b44b75382ad 100644 --- a/spec/frontend/diffs/store/actions_spec.js +++ b/spec/frontend/diffs/store/actions_spec.js @@ -69,6 +69,7 @@ describe('DiffsStoreActions', () => { const endpoint = '/diffs/set/endpoint'; const endpointMetadata = '/diffs/set/endpoint/metadata'; const endpointBatch = '/diffs/set/endpoint/batch'; + const endpointDiffForPath = '/diffs/set/endpoint/path'; const endpointCoverage = '/diffs/set/coverage_reports'; const projectPath = '/root/project'; const dismissEndpoint = '/-/user_callouts'; @@ -83,6 +84,7 @@ describe('DiffsStoreActions', () => { { endpoint, endpointBatch, + endpointDiffForPath, endpointMetadata, endpointCoverage, projectPath, @@ -93,6 +95,7 @@ describe('DiffsStoreActions', () => { { endpoint: '', endpointBatch: '', + endpointDiffForPath: '', endpointMetadata: '', endpointCoverage: '', projectPath: '', @@ -106,6 +109,7 @@ describe('DiffsStoreActions', () => { endpoint, endpointMetadata, endpointBatch, + endpointDiffForPath, endpointCoverage, projectPath, dismissEndpoint, diff --git a/spec/frontend/invite_members/components/invite_groups_modal_spec.js b/spec/frontend/invite_members/components/invite_groups_modal_spec.js index c2a55517405..0e17b6bc07f 100644 --- a/spec/frontend/invite_members/components/invite_groups_modal_spec.js +++ b/spec/frontend/invite_members/components/invite_groups_modal_spec.js @@ -58,11 +58,13 @@ describe('InviteGroupsModal', () => { findMembersFormGroup().attributes('invalid-feedback'); const findBase = () => wrapper.findComponent(InviteModalBase); const triggerGroupSelect = (val) => findGroupSelect().vm.$emit('input', val); - const emitEventFromModal = (eventName) => () => - findModal().vm.$emit(eventName, { preventDefault: jest.fn() }); - const hideModal = emitEventFromModal('hidden'); - const clickInviteButton = emitEventFromModal('primary'); - const clickCancelButton = emitEventFromModal('cancel'); + const hideModal = () => findModal().vm.$emit('hidden', { preventDefault: jest.fn() }); + + const emitClickFromModal = (testId) => () => + wrapper.findByTestId(testId).vm.$emit('click', { preventDefault: jest.fn() }); + + const clickInviteButton = emitClickFromModal('invite-modal-submit'); + const clickCancelButton = emitClickFromModal('invite-modal-cancel'); describe('displaying the correct introText and form group description', () => { describe('when inviting to a project', () => { diff --git a/spec/frontend/invite_members/components/invite_members_modal_spec.js b/spec/frontend/invite_members/components/invite_members_modal_spec.js index 9687d528321..41ffe9b92c9 100644 --- a/spec/frontend/invite_members/components/invite_members_modal_spec.js +++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js @@ -134,10 +134,15 @@ describe('InviteMembersModal', () => { `${Object.keys(invitationsApiResponse.EXPANDED_RESTRICTED.message)[element]}: ${ Object.values(invitationsApiResponse.EXPANDED_RESTRICTED.message)[element] }`; - const emitEventFromModal = (eventName) => () => - findModal().vm.$emit(eventName, { preventDefault: jest.fn() }); - const clickInviteButton = emitEventFromModal('primary'); - const clickCancelButton = emitEventFromModal('cancel'); + const findActionButton = () => wrapper.findByTestId('invite-modal-submit'); + const findCancelButton = () => wrapper.findByTestId('invite-modal-cancel'); + + const emitClickFromModal = (findButton) => () => + findButton().vm.$emit('click', { preventDefault: jest.fn() }); + + const clickInviteButton = emitClickFromModal(findActionButton); + const clickCancelButton = emitClickFromModal(findCancelButton); + const findMembersFormGroup = () => wrapper.findByTestId('members-form-group'); const membersFormGroupInvalidFeedback = () => findMembersFormGroup().attributes('invalid-feedback'); @@ -368,13 +373,11 @@ describe('InviteMembersModal', () => { it('tracks actions', async () => { trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - const mockEvent = { preventDefault: jest.fn() }; - await triggerOpenModal({ mode: 'celebrate', source: ON_CELEBRATION_TRACK_LABEL }); expectTracking('render', ON_CELEBRATION_TRACK_LABEL); - findModal().vm.$emit('cancel', mockEvent); + clickCancelButton(); expectTracking('click_cancel', ON_CELEBRATION_TRACK_LABEL); findModal().vm.$emit('close'); @@ -411,13 +414,11 @@ describe('InviteMembersModal', () => { trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - const mockEvent = { preventDefault: jest.fn() }; - await triggerOpenModal(source); expectTracking('render', label); - findModal().vm.$emit('cancel', mockEvent); + clickCancelButton(); expectTracking('click_cancel', label); findModal().vm.$emit('close'); @@ -734,7 +735,7 @@ describe('InviteMembersModal', () => { expect(membersFormGroupInvalidFeedback()).toBe(expectedSyntaxError); expect(findMembersSelect().props('exceptionState')).toBe(false); - expect(findModal().props('actionPrimary').attributes.loading).toBe(false); + expect(findActionButton().props('loading')).toBe(false); }); it('clears the error when the modal is hidden', async () => { @@ -746,7 +747,7 @@ describe('InviteMembersModal', () => { expect(membersFormGroupInvalidFeedback()).toBe(expectedSyntaxError); expect(findMembersSelect().props('exceptionState')).toBe(false); - expect(findModal().props('actionPrimary').attributes.loading).toBe(false); + expect(findActionButton().props('loading')).toBe(false); findModal().vm.$emit('hidden'); @@ -768,7 +769,7 @@ describe('InviteMembersModal', () => { expect(findMemberErrorAlert().text()).toContain(expectedEmailRestrictedError); expect(membersFormGroupInvalidFeedback()).toBe(''); expect(findMembersSelect().props('exceptionState')).not.toBe(false); - expect(findModal().props('actionPrimary').attributes.loading).toBe(false); + expect(findActionButton().props('loading')).toBe(false); }); it('displays all errors when there are multiple emails that return a restricted error message', async () => { 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 f34f9902514..7e49977432d 100644 --- a/spec/frontend/invite_members/components/invite_modal_base_spec.js +++ b/spec/frontend/invite_members/components/invite_modal_base_spec.js @@ -66,8 +66,8 @@ describe('InviteModalBase', () => { const findIntroText = () => wrapper.findByTestId('modal-base-intro-text').text(); const findMembersFormGroup = () => wrapper.findByTestId('members-form-group'); const findDisabledInput = () => wrapper.findByTestId('disabled-input'); - const findCancelButton = () => wrapper.find('.js-modal-action-cancel'); - const findActionButton = () => wrapper.find('.js-modal-action-primary'); + const findCancelButton = () => wrapper.findByTestId('invite-modal-cancel'); + const findActionButton = () => wrapper.findByTestId('invite-modal-submit'); describe('rendering the modal', () => { let trackingSpy; @@ -88,20 +88,19 @@ describe('InviteModalBase', () => { }); it('renders the Cancel button text correctly', () => { - expect(wrapper.findComponent(GlModal).props('actionCancel')).toMatchObject({ - text: CANCEL_BUTTON_TEXT, - }); + expect(findCancelButton().text()).toBe(CANCEL_BUTTON_TEXT); }); it('renders the Invite button correctly', () => { - expect(wrapper.findComponent(GlModal).props('actionPrimary')).toMatchObject({ - text: INVITE_BUTTON_TEXT, - attributes: { - variant: 'confirm', - disabled: false, - loading: false, - 'data-qa-selector': 'invite_button', - }, + const actionButton = findActionButton(); + + expect(actionButton.text()).toBe(INVITE_BUTTON_TEXT); + expect(actionButton.attributes('data-qa-selector')).toBe('invite_button'); + + expect(actionButton.props()).toMatchObject({ + variant: 'confirm', + disabled: false, + loading: false, }); }); @@ -235,7 +234,7 @@ describe('InviteModalBase', () => { }, }); - expect(wrapper.findComponent(GlModal).props('actionPrimary').attributes.loading).toBe(true); + expect(findActionButton().props('loading')).toBe(true); }); it('with invalidFeedbackMessage, set members form group exception state', () => { diff --git a/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js b/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js index dd3e55c82bb..e2640de1f94 100644 --- a/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js +++ b/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js @@ -31,10 +31,6 @@ describe('DropdownWidget component', () => { }, }); - // We need to mock out `showDropdown` which - // invokes `show` method of BDropdown used inside GlDropdown. - // Context: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54895#note_524281679 - jest.spyOn(wrapper.vm, 'showDropdown').mockImplementation(); jest.spyOn(findDropdown().vm, 'hide').mockImplementation(); }; diff --git a/spec/mailers/emails/issues_spec.rb b/spec/mailers/emails/issues_spec.rb index 21e07c0252d..b5f3972f38e 100644 --- a/spec/mailers/emails/issues_spec.rb +++ b/spec/mailers/emails/issues_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' require 'email_spec' -RSpec.describe Emails::Issues do +RSpec.describe Emails::Issues, feature_category: :team_planning do include EmailSpec::Matchers it 'adds email methods to Notify' do @@ -54,38 +54,6 @@ RSpec.describe Emails::Issues do subject { Notify.issues_csv_email(user, empty_project, "dummy content", export_status) } - include_context 'gitlab email notification' - - it 'attachment has csv mime type' do - expect(attachment.mime_type).to eq 'text/csv' - end - - it 'generates a useful filename' do - expect(attachment.filename).to include(Date.today.year.to_s) - expect(attachment.filename).to include('issues') - expect(attachment.filename).to include('myproject') - expect(attachment.filename).to end_with('.csv') - end - - it 'mentions number of issues and project name' do - expect(subject).to have_content '3' - expect(subject).to have_content empty_project.name - end - - it "doesn't need to mention truncation by default" do - expect(subject).not_to have_content 'truncated' - end - - context 'when truncated' do - let(:export_status) { { truncated: true, rows_expected: 12, rows_written: 10 } } - - it 'mentions that the csv has been truncated' do - expect(subject).to have_content 'truncated' - end - - it 'mentions the number of issues written and expected' do - expect(subject).to have_content '10 of 12 issues' - end - end + it_behaves_like 'export csv email', 'issues' end end diff --git a/spec/mailers/emails/work_items_spec.rb b/spec/mailers/emails/work_items_spec.rb new file mode 100644 index 00000000000..eb2c751388d --- /dev/null +++ b/spec/mailers/emails/work_items_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'email_spec' + +RSpec.describe Emails::WorkItems, feature_category: :team_planning do + describe '#export_work_items_csv_email' do + let(:user) { build_stubbed(:user) } + let(:empty_project) { build_stubbed(:project, path: 'myproject') } + let(:export_status) { { truncated: false, rows_expected: 3, rows_written: 3 } } + let(:attachment) { subject.attachments.first } + + subject { Notify.export_work_items_csv_email(user, empty_project, "dummy content", export_status) } + + it_behaves_like 'export csv email', 'work_items' + end +end diff --git a/spec/models/members/member_role_spec.rb b/spec/models/members/member_role_spec.rb index 1ee86376e02..69d164e1942 100644 --- a/spec/models/members/member_role_spec.rb +++ b/spec/models/members/member_role_spec.rb @@ -35,6 +35,30 @@ RSpec.describe MemberRole, feature_category: :system_access do end end + context 'for max_count_per_group_hierarchy' do + let_it_be(:group) { create(:group) } + + subject(:member_role) { build(:member_role, namespace: group) } + + context 'when number of member roles is below limit' do + it 'is valid' do + is_expected.to be_valid + end + end + + context 'when number of member roles is above limit' do + before do + stub_const('MemberRole::MAX_COUNT_PER_GROUP_HIERARCHY', 1) + create(:member_role, namespace: group) + group.reload + end + + it 'is invalid' do + is_expected.to be_invalid + end + end + end + context 'when for namespace' do let_it_be(:root_group) { create(:group) } diff --git a/spec/requests/api/graphql/mutations/issues/bulk_update_spec.rb b/spec/requests/api/graphql/mutations/issues/bulk_update_spec.rb index b9c83311908..b729585a89b 100644 --- a/spec/requests/api/graphql/mutations/issues/bulk_update_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/bulk_update_spec.rb @@ -8,7 +8,9 @@ RSpec.describe 'Bulk update issues', feature_category: :team_planning do let_it_be(:developer) { create(:user) } let_it_be(:group) { create(:group).tap { |group| group.add_developer(developer) } } let_it_be(:project) { create(:project, group: group) } - let_it_be(:updatable_issues, reload: true) { create_list(:issue, 2, project: project) } + let_it_be(:label1) { create(:group_label, group: group) } + let_it_be(:label2) { create(:group_label, group: group) } + let_it_be(:updatable_issues, reload: true) { create_list(:issue, 2, project: project, label_ids: [label1.id]) } let_it_be(:milestone) { create(:milestone, group: group) } let(:parent) { project } @@ -21,10 +23,36 @@ RSpec.describe 'Bulk update issues', feature_category: :team_planning do let(:additional_arguments) do { assignee_ids: [current_user.to_gid.to_s], - milestone_id: milestone.to_gid.to_s + milestone_id: milestone.to_gid.to_s, + state_event: :CLOSE, + add_label_ids: [label2.to_gid.to_s], + remove_label_ids: [label1.to_gid.to_s], + subscription_event: :UNSUBSCRIBE } end + before_all do + updatable_issues.each { |i| i.subscribe(developer, project) } + end + + context 'when Gitlab is FOSS only' do + unless Gitlab.ee? + context 'when parent is a group' do + let(:parent) { group } + + it 'does not allow bulk updating issues at the group level' do + post_graphql_mutation(mutation, current_user: current_user) + + expect(graphql_errors).to contain_exactly( + hash_including( + 'message' => match(/does not represent an instance of IssueParent/) + ) + ) + end + end + end + end + context 'when the `bulk_update_issues_mutation` feature flag is disabled' do before do stub_feature_flags(bulk_update_issues_mutation: false) @@ -67,6 +95,11 @@ RSpec.describe 'Bulk update issues', feature_category: :team_planning do updatable_issues.each(&:reload) end.to change { updatable_issues.flat_map(&:assignee_ids) }.from([]).to([current_user.id] * 2) .and(change { updatable_issues.map(&:milestone_id) }.from([nil] * 2).to([milestone.id] * 2)) + .and(change { updatable_issues.map(&:state) }.from(['opened'] * 2).to(['closed'] * 2)) + .and(change { updatable_issues.flat_map(&:label_ids) }.from([label1.id] * 2).to([label2.id] * 2)) + .and( + change { updatable_issues.map { |i| i.subscribed?(developer, project) } }.from([true] * 2).to([false] * 2) + ) expect(mutation_response).to include( 'updatedIssueCount' => updatable_issues.count @@ -88,37 +121,6 @@ RSpec.describe 'Bulk update issues', feature_category: :team_planning do end end - context 'when scoping to a parent group' do - let(:parent) { group } - - it 'updates all issues' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - updatable_issues.each(&:reload) - end.to change { updatable_issues.flat_map(&:assignee_ids) }.from([]).to([current_user.id] * 2) - .and(change { updatable_issues.map(&:milestone_id) }.from([nil] * 2).to([milestone.id] * 2)) - - expect(mutation_response).to include( - 'updatedIssueCount' => updatable_issues.count - ) - end - - context 'when current user cannot read the specified group' do - let(:parent) { create(:group, :private) } - - it 'returns a resource not found error' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(graphql_errors).to contain_exactly( - hash_including( - 'message' => "The resource that you are attempting to access does not exist or you don't have " \ - 'permission to perform this action' - ) - ) - end - end - end - context 'when setting arguments to null or none' do let(:additional_arguments) { { assignee_ids: [], milestone_id: nil } } diff --git a/spec/services/clusters/agent_tokens/revoke_service_spec.rb b/spec/services/clusters/agent_tokens/revoke_service_spec.rb index 24485a5f66d..9e511de0a13 100644 --- a/spec/services/clusters/agent_tokens/revoke_service_spec.rb +++ b/spec/services/clusters/agent_tokens/revoke_service_spec.rb @@ -4,6 +4,8 @@ require 'spec_helper' RSpec.describe Clusters::AgentTokens::RevokeService, feature_category: :kubernetes_management do describe '#execute' do + subject { described_class.new(token: agent_token, current_user: user).execute } + let(:agent) { create(:cluster_agent) } let(:agent_token) { create(:cluster_agent_token, agent: agent) } let(:project) { agent.project } @@ -20,10 +22,24 @@ RSpec.describe Clusters::AgentTokens::RevokeService, feature_category: :kubernet context 'when user revokes agent token' do it 'succeeds' do - described_class.new(token: agent_token, current_user: user).execute + subject expect(agent_token.revoked?).to be true end + + it 'creates an activity event' do + expect { subject }.to change { ::Clusters::Agents::ActivityEvent.count }.by(1) + + event = agent.activity_events.last + + expect(event).to have_attributes( + kind: 'token_revoked', + level: 'info', + recorded_at: agent_token.reload.updated_at, + user: user, + agent_token: agent_token + ) + end end context 'when there is a validation failure' do @@ -32,24 +48,26 @@ RSpec.describe Clusters::AgentTokens::RevokeService, feature_category: :kubernet end it 'fails without raising an error', :aggregate_failures do - result = described_class.new(token: agent_token, current_user: user).execute + expect(subject[:status]).to eq(:error) + expect(subject[:message]).to eq(["Name can't be blank"]) + end - expect(result[:status]).to eq(:error) - expect(result[:message]).to eq(["Name can't be blank"]) + it 'does not create an activity event' do + expect { subject }.not_to change { ::Clusters::Agents::ActivityEvent.count } end end end context 'when user is not authorized' do - let(:unauthorized_user) { create(:user) } + let(:user) { create(:user) } before do - project.add_guest(unauthorized_user) + project.add_guest(user) end context 'when user attempts to revoke agent token' do it 'fails' do - described_class.new(token: agent_token, current_user: unauthorized_user).execute + subject expect(agent_token.revoked?).to be false end diff --git a/spec/services/clusters/agents/create_activity_event_service_spec.rb b/spec/services/clusters/agents/create_activity_event_service_spec.rb index 7a8f0e16d60..3da8ecddb8d 100644 --- a/spec/services/clusters/agents/create_activity_event_service_spec.rb +++ b/spec/services/clusters/agents/create_activity_event_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Clusters::Agents::CreateActivityEventService do +RSpec.describe Clusters::Agents::CreateActivityEventService, feature_category: :kubernetes_management do let_it_be(:agent) { create(:cluster_agent) } let_it_be(:token) { create(:cluster_agent_token, agent: agent) } let_it_be(:user) { create(:user) } @@ -40,5 +40,16 @@ RSpec.describe Clusters::Agents::CreateActivityEventService do subject end + + context 'when activity event creation fails' do + let(:params) { {} } + + it 'tracks the exception without raising' do + expect(Gitlab::ErrorTracking).to receive(:track_exception) + .with(instance_of(ActiveRecord::RecordInvalid), agent_id: agent.id) + + subject + end + end end end diff --git a/spec/services/work_items/export_csv_service_spec.rb b/spec/services/work_items/export_csv_service_spec.rb index 0718d3b686a..7c22312ce1f 100644 --- a/spec/services/work_items/export_csv_service_spec.rb +++ b/spec/services/work_items/export_csv_service_spec.rb @@ -30,9 +30,8 @@ RSpec.describe WorkItems::ExportCsvService, :with_license, feature_category: :te end describe '#email' do - # TODO - will be implemented as part of https://gitlab.com/gitlab-org/gitlab/-/issues/379082 - xit 'emails csv' do - expect { subject.email(user) }.o change { ActionMailer::Base.deliveries.count }.from(0).to(1) + it 'emails csv' do + expect { subject.email(user) }.to change { ActionMailer::Base.deliveries.count }.from(0).to(1) end end diff --git a/spec/support/shared_examples/mailers/export_csv_shared_examples.rb b/spec/support/shared_examples/mailers/export_csv_shared_examples.rb new file mode 100644 index 00000000000..731d7c810f9 --- /dev/null +++ b/spec/support/shared_examples/mailers/export_csv_shared_examples.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'export csv email' do |collection_type| + include_context 'gitlab email notification' + + it 'attachment has csv mime type' do + expect(attachment.mime_type).to eq 'text/csv' + end + + it 'generates a useful filename' do + expect(attachment.filename).to include(Date.today.year.to_s) + expect(attachment.filename).to include(collection_type) + expect(attachment.filename).to include('myproject') + expect(attachment.filename).to end_with('.csv') + end + + it 'mentions number of objects and project name' do + expect(subject).to have_content '3' + expect(subject).to have_content empty_project.name + end + + it "doesn't need to mention truncation by default" do + expect(subject).not_to have_content 'truncated' + end + + context 'when truncated' do + let(:export_status) { { truncated: true, rows_expected: 12, rows_written: 10 } } + + it 'mentions that the csv has been truncated' do + expect(subject).to have_content 'truncated' + end + + it 'mentions the number of objects written and expected' do + expect(subject).to have_content "10 of 12 #{collection_type.humanize.downcase}" + end + end +end |