Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-03-01 21:07:43 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-03-01 21:07:43 +0300
commitad1e76fb4d1392c890c8b5e218a256a416d5a50b (patch)
tree51e5541bb1f1a799e288701bc1170a3b1a9a7393 /spec
parent8b1036168b0d395c379cbbaf457e256860147405 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/markdown/observability_spec.rb2
-rw-r--r--spec/frontend/behaviors/markdown/render_observability_spec.js4
-rw-r--r--spec/frontend/clusters/forms/components/integration_form_spec.js31
-rw-r--r--spec/frontend/diffs/components/app_spec.js1
-rw-r--r--spec/frontend/diffs/store/actions_spec.js4
-rw-r--r--spec/frontend/invite_members/components/invite_groups_modal_spec.js12
-rw-r--r--spec/frontend/invite_members/components/invite_members_modal_spec.js27
-rw-r--r--spec/frontend/invite_members/components/invite_modal_base_spec.js27
-rw-r--r--spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js4
-rw-r--r--spec/mailers/emails/issues_spec.rb36
-rw-r--r--spec/mailers/emails/work_items_spec.rb17
-rw-r--r--spec/models/members/member_role_spec.rb24
-rw-r--r--spec/requests/api/graphql/mutations/issues/bulk_update_spec.rb68
-rw-r--r--spec/services/clusters/agent_tokens/revoke_service_spec.rb32
-rw-r--r--spec/services/clusters/agents/create_activity_event_service_spec.rb13
-rw-r--r--spec/services/work_items/export_csv_service_spec.rb5
-rw-r--r--spec/support/shared_examples/mailers/export_csv_shared_examples.rb37
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&amp;kiosk" frameborder="0")
+ %(<iframe src="#{observable_url}?theme=light&amp;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