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>2022-08-09 18:11:31 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-09 18:11:31 +0300
commit283318c20561cc040b62397060771efa74db0d90 (patch)
tree31b724e53806352b1bff5e1e460e6f4445c4e0a0 /spec
parent1f229cdc22b5b32989bcff2037d8925c75703671 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/finders/groups/accepting_project_transfers_finder_spec.rb63
-rw-r--r--spec/frontend/__helpers__/vuex_action_helper_spec.js2
-rw-r--r--spec/frontend/blob/3d_viewer/mesh_object_spec.js4
-rw-r--r--spec/frontend/design_management/pages/index_spec.js6
-rw-r--r--spec/frontend/helpers/diffs_helper_spec.js32
-rw-r--r--spec/frontend/ide/lib/diff/diff_spec.js8
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/add_namespace_button_spec.js1
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js8
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js16
-rw-r--r--spec/frontend/pages/admin/application_settings/account_and_limits_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/changed_file_icon_spec.js4
-rw-r--r--spec/lib/gitlab/audit/auditor_spec.rb258
-rw-r--r--spec/lib/gitlab/audit/null_target_spec.rb25
-rw-r--r--spec/lib/gitlab/audit/target_spec.rb47
-rw-r--r--spec/lib/gitlab/database/background_migration/batched_migration_spec.rb43
-rw-r--r--spec/models/group_group_link_spec.rb48
-rw-r--r--spec/models/group_spec.rb14
-rw-r--r--spec/services/audit_events/build_service_spec.rb154
-rw-r--r--spec/workers/pages/invalidate_domain_cache_worker_spec.rb66
19 files changed, 762 insertions, 45 deletions
diff --git a/spec/finders/groups/accepting_project_transfers_finder_spec.rb b/spec/finders/groups/accepting_project_transfers_finder_spec.rb
new file mode 100644
index 00000000000..26cd373593c
--- /dev/null
+++ b/spec/finders/groups/accepting_project_transfers_finder_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::AcceptingProjectTransfersFinder do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group_where_direct_owner) { create(:group) }
+ let_it_be(:subgroup_of_group_where_direct_owner) { create(:group, parent: group_where_direct_owner) }
+ let_it_be(:group_where_direct_maintainer) { create(:group) }
+ let_it_be(:group_where_direct_maintainer_but_cant_create_projects) do
+ create(:group, project_creation_level: Gitlab::Access::NO_ONE_PROJECT_ACCESS)
+ end
+
+ let_it_be(:group_where_direct_developer) { create(:group) }
+ let_it_be(:shared_with_group_where_direct_owner_as_owner) { create(:group) }
+ let_it_be(:shared_with_group_where_direct_owner_as_guest) { create(:group) }
+ let_it_be(:shared_with_group_where_direct_owner_as_maintainer) { create(:group) }
+ let_it_be(:shared_with_group_where_direct_developer_as_owner) { create(:group) }
+ let_it_be(:subgroup_of_shared_with_group_where_direct_owner_as_maintainer) do
+ create(:group, parent: shared_with_group_where_direct_owner_as_maintainer)
+ end
+
+ before do
+ group_where_direct_owner.add_owner(user)
+ group_where_direct_maintainer.add_maintainer(user)
+ group_where_direct_developer.add_developer(user)
+
+ create(:group_group_link, :owner,
+ shared_with_group: group_where_direct_owner,
+ shared_group: shared_with_group_where_direct_owner_as_owner
+ )
+
+ create(:group_group_link, :guest,
+ shared_with_group: group_where_direct_owner,
+ shared_group: shared_with_group_where_direct_owner_as_guest
+ )
+
+ create(:group_group_link, :maintainer,
+ shared_with_group: group_where_direct_owner,
+ shared_group: shared_with_group_where_direct_owner_as_maintainer
+ )
+
+ create(:group_group_link, :owner,
+ shared_with_group: group_where_direct_developer,
+ shared_group: shared_with_group_where_direct_developer_as_owner
+ )
+ end
+
+ describe '#execute' do
+ subject(:result) { described_class.new(user).execute }
+
+ it 'only returns groups where the user has access to transfer projects to' do
+ expect(result).to match_array([
+ group_where_direct_owner,
+ subgroup_of_group_where_direct_owner,
+ group_where_direct_maintainer,
+ shared_with_group_where_direct_owner_as_owner,
+ shared_with_group_where_direct_owner_as_maintainer,
+ subgroup_of_shared_with_group_where_direct_owner_as_maintainer
+ ])
+ end
+ end
+end
diff --git a/spec/frontend/__helpers__/vuex_action_helper_spec.js b/spec/frontend/__helpers__/vuex_action_helper_spec.js
index 5bb2b3b26e2..182aea9c1c5 100644
--- a/spec/frontend/__helpers__/vuex_action_helper_spec.js
+++ b/spec/frontend/__helpers__/vuex_action_helper_spec.js
@@ -76,7 +76,7 @@ describe.each([testActionFn, testActionFnWithOptionsArg])(
const promise = testAction(() => {}, null, {}, assertion.mutations, assertion.actions);
- originalExpect(promise instanceof Promise).toBeTruthy();
+ originalExpect(promise instanceof Promise).toBe(true);
return promise;
});
diff --git a/spec/frontend/blob/3d_viewer/mesh_object_spec.js b/spec/frontend/blob/3d_viewer/mesh_object_spec.js
index 60be285039f..3014af073f5 100644
--- a/spec/frontend/blob/3d_viewer/mesh_object_spec.js
+++ b/spec/frontend/blob/3d_viewer/mesh_object_spec.js
@@ -5,7 +5,7 @@ describe('Mesh object', () => {
it('defaults to non-wireframe material', () => {
const object = new MeshObject(new BoxGeometry(10, 10, 10));
- expect(object.material.wireframe).toBeFalsy();
+ expect(object.material.wireframe).toBe(false);
});
it('changes to wirefame material', () => {
@@ -13,7 +13,7 @@ describe('Mesh object', () => {
object.changeMaterial('wireframe');
- expect(object.material.wireframe).toBeTruthy();
+ expect(object.material.wireframe).toBe(true);
});
it('scales object down', () => {
diff --git a/spec/frontend/design_management/pages/index_spec.js b/spec/frontend/design_management/pages/index_spec.js
index 21be7bd148b..321d55cbaef 100644
--- a/spec/frontend/design_management/pages/index_spec.js
+++ b/spec/frontend/design_management/pages/index_spec.js
@@ -369,7 +369,7 @@ describe('Design management index page', () => {
findDropzone().vm.$emit('change', [{ name: 'test' }]);
expect(mutate).toHaveBeenCalledWith(mutationVariables);
expect(wrapper.vm.filesToBeSaved).toEqual([{ name: 'test' }]);
- expect(wrapper.vm.isSaving).toBeTruthy();
+ expect(wrapper.vm.isSaving).toBe(true);
expect(dropzoneClasses()).toContain('design-list-item');
expect(dropzoneClasses()).toContain('design-list-item-new');
});
@@ -399,7 +399,7 @@ describe('Design management index page', () => {
await nextTick();
expect(wrapper.vm.filesToBeSaved).toEqual([]);
- expect(wrapper.vm.isSaving).toBeFalsy();
+ expect(wrapper.vm.isSaving).toBe(false);
expect(wrapper.vm.isLatestVersion).toBe(true);
});
@@ -412,7 +412,7 @@ describe('Design management index page', () => {
wrapper.vm.onUploadDesignError();
await nextTick();
expect(wrapper.vm.filesToBeSaved).toEqual([]);
- expect(wrapper.vm.isSaving).toBeFalsy();
+ expect(wrapper.vm.isSaving).toBe(false);
expect(findDesignUpdateAlert().exists()).toBe(true);
expect(findDesignUpdateAlert().text()).toBe(UPLOAD_DESIGN_ERROR);
});
diff --git a/spec/frontend/helpers/diffs_helper_spec.js b/spec/frontend/helpers/diffs_helper_spec.js
index b223d48bf5c..c1ac7fac3fd 100644
--- a/spec/frontend/helpers/diffs_helper_spec.js
+++ b/spec/frontend/helpers/diffs_helper_spec.js
@@ -14,45 +14,45 @@ describe('diffs helper', () => {
describe('hasInlineLines', () => {
it('is false when the file does not exist', () => {
- expect(diffsHelper.hasInlineLines()).toBeFalsy();
+ expect(diffsHelper.hasInlineLines()).toBe(false);
});
it('is false when the file does not have the highlighted_diff_lines property', () => {
const missingInline = getDiffFile({ highlighted_diff_lines: undefined });
- expect(diffsHelper.hasInlineLines(missingInline)).toBeFalsy();
+ expect(diffsHelper.hasInlineLines(missingInline)).toBe(false);
});
it('is false when the file has zero highlighted_diff_lines', () => {
const emptyInline = getDiffFile({ highlighted_diff_lines: [] });
- expect(diffsHelper.hasInlineLines(emptyInline)).toBeFalsy();
+ expect(diffsHelper.hasInlineLines(emptyInline)).toBe(false);
});
it('is true when the file has at least 1 highlighted_diff_lines', () => {
- expect(diffsHelper.hasInlineLines(getDiffFile())).toBeTruthy();
+ expect(diffsHelper.hasInlineLines(getDiffFile())).toBe(true);
});
});
describe('hasParallelLines', () => {
it('is false when the file does not exist', () => {
- expect(diffsHelper.hasParallelLines()).toBeFalsy();
+ expect(diffsHelper.hasParallelLines()).toBe(false);
});
it('is false when the file does not have the parallel_diff_lines property', () => {
const missingInline = getDiffFile({ parallel_diff_lines: undefined });
- expect(diffsHelper.hasParallelLines(missingInline)).toBeFalsy();
+ expect(diffsHelper.hasParallelLines(missingInline)).toBe(false);
});
it('is false when the file has zero parallel_diff_lines', () => {
const emptyInline = getDiffFile({ parallel_diff_lines: [] });
- expect(diffsHelper.hasParallelLines(emptyInline)).toBeFalsy();
+ expect(diffsHelper.hasParallelLines(emptyInline)).toBe(false);
});
it('is true when the file has at least 1 parallel_diff_lines', () => {
- expect(diffsHelper.hasParallelLines(getDiffFile())).toBeTruthy();
+ expect(diffsHelper.hasParallelLines(getDiffFile())).toBe(true);
});
});
@@ -61,16 +61,16 @@ describe('diffs helper', () => {
const noParallelLines = getDiffFile({ parallel_diff_lines: undefined });
const emptyParallelLines = getDiffFile({ parallel_diff_lines: [] });
- expect(diffsHelper.isSingleViewStyle(noParallelLines)).toBeTruthy();
- expect(diffsHelper.isSingleViewStyle(emptyParallelLines)).toBeTruthy();
+ expect(diffsHelper.isSingleViewStyle(noParallelLines)).toBe(true);
+ expect(diffsHelper.isSingleViewStyle(emptyParallelLines)).toBe(true);
});
it('is true when the file has at least 1 parallel line but no inline lines for any reason', () => {
const noInlineLines = getDiffFile({ highlighted_diff_lines: undefined });
const emptyInlineLines = getDiffFile({ highlighted_diff_lines: [] });
- expect(diffsHelper.isSingleViewStyle(noInlineLines)).toBeTruthy();
- expect(diffsHelper.isSingleViewStyle(emptyInlineLines)).toBeTruthy();
+ expect(diffsHelper.isSingleViewStyle(noInlineLines)).toBe(true);
+ expect(diffsHelper.isSingleViewStyle(emptyInlineLines)).toBe(true);
});
it('is true when the file does not have any inline lines or parallel lines for any reason', () => {
@@ -83,13 +83,13 @@ describe('diffs helper', () => {
parallel_diff_lines: [],
});
- expect(diffsHelper.isSingleViewStyle(noLines)).toBeTruthy();
- expect(diffsHelper.isSingleViewStyle(emptyLines)).toBeTruthy();
- expect(diffsHelper.isSingleViewStyle()).toBeTruthy();
+ expect(diffsHelper.isSingleViewStyle(noLines)).toBe(true);
+ expect(diffsHelper.isSingleViewStyle(emptyLines)).toBe(true);
+ expect(diffsHelper.isSingleViewStyle()).toBe(true);
});
it('is false when the file has both inline and parallel lines', () => {
- expect(diffsHelper.isSingleViewStyle(getDiffFile())).toBeFalsy();
+ expect(diffsHelper.isSingleViewStyle(getDiffFile())).toBe(false);
});
});
diff --git a/spec/frontend/ide/lib/diff/diff_spec.js b/spec/frontend/ide/lib/diff/diff_spec.js
index 901f9e7cfd1..208ed9bf759 100644
--- a/spec/frontend/ide/lib/diff/diff_spec.js
+++ b/spec/frontend/ide/lib/diff/diff_spec.js
@@ -18,8 +18,8 @@ describe('Multi-file editor library diff calculator', () => {
({ originalContent, newContent, lineNumber }) => {
const diff = computeDiff(originalContent, newContent)[0];
- expect(diff.added).toBeTruthy();
- expect(diff.modified).toBeTruthy();
+ expect(diff.added).toBe(true);
+ expect(diff.modified).toBe(true);
expect(diff.removed).toBeUndefined();
expect(diff.lineNumber).toBe(lineNumber);
},
@@ -36,7 +36,7 @@ describe('Multi-file editor library diff calculator', () => {
({ originalContent, newContent, lineNumber }) => {
const diff = computeDiff(originalContent, newContent)[0];
- expect(diff.added).toBeTruthy();
+ expect(diff.added).toBe(true);
expect(diff.modified).toBeUndefined();
expect(diff.removed).toBeUndefined();
expect(diff.lineNumber).toBe(lineNumber);
@@ -56,7 +56,7 @@ describe('Multi-file editor library diff calculator', () => {
expect(diff.added).toBeUndefined();
expect(diff.modified).toBe(modified);
- expect(diff.removed).toBeTruthy();
+ expect(diff.removed).toBe(true);
expect(diff.lineNumber).toBe(lineNumber);
},
);
diff --git a/spec/frontend/jira_connect/subscriptions/components/add_namespace_button_spec.js b/spec/frontend/jira_connect/subscriptions/components/add_namespace_button_spec.js
index 5ec1b7b7932..9f92ad2adc1 100644
--- a/spec/frontend/jira_connect/subscriptions/components/add_namespace_button_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/add_namespace_button_spec.js
@@ -38,7 +38,6 @@ describe('AddNamespaceButton', () => {
it('button is bound to the modal', () => {
const { value } = getBinding(findButton().element, 'gl-modal');
- expect(value).toBeTruthy();
expect(value).toBe(ADD_NAMESPACE_MODAL_ID);
});
});
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index 73846aeac96..a2ace8857ed 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -628,7 +628,7 @@ describe('common_utils', () => {
it('returns an empty object if `conversionFunction` parameter is not a function', () => {
const result = commonUtils.convertObjectProps(null, mockObjects.convertObjectProps.obj);
- expect(isEmptyObject(result)).toBeTruthy();
+ expect(isEmptyObject(result)).toBe(true);
});
});
@@ -645,9 +645,9 @@ describe('common_utils', () => {
: commonUtils[functionName];
it('returns an empty object if `obj` parameter is null, undefined or an empty object', () => {
- expect(isEmptyObject(testFunction(null))).toBeTruthy();
- expect(isEmptyObject(testFunction())).toBeTruthy();
- expect(isEmptyObject(testFunction({}))).toBeTruthy();
+ expect(isEmptyObject(testFunction(null))).toBe(true);
+ expect(isEmptyObject(testFunction())).toBe(true);
+ expect(isEmptyObject(testFunction({}))).toBe(true);
});
it('converts object properties', () => {
diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js
index f7d5906663d..b2932ef3eb9 100644
--- a/spec/frontend/lib/utils/text_markdown_spec.js
+++ b/spec/frontend/lib/utils/text_markdown_spec.js
@@ -387,6 +387,22 @@ describe('init markdown', () => {
expect(textArea.value).toEqual(text);
});
+
+ it.each`
+ keyEvent
+ ${new KeyboardEvent('keydown', { key: ']', metaKey: false })}
+ ${new KeyboardEvent('keydown', { key: ']', metaKey: true, shiftKey: true })}
+ ${new KeyboardEvent('keydown', { key: ']', metaKey: true, altKey: true })}
+ ${new KeyboardEvent('keydown', { key: ']', metaKey: true, ctrlKey: true })}
+ `('does not indent if meta is not set', ({ keyEvent }) => {
+ const text = '012\n456\n89';
+ textArea.value = text;
+ textArea.setSelectionRange(0, 0);
+
+ textArea.dispatchEvent(keyEvent);
+
+ expect(textArea.value).toEqual(text);
+ });
});
describe('with selection', () => {
diff --git a/spec/frontend/pages/admin/application_settings/account_and_limits_spec.js b/spec/frontend/pages/admin/application_settings/account_and_limits_spec.js
index 542eb2f3ab8..85ed94b748d 100644
--- a/spec/frontend/pages/admin/application_settings/account_and_limits_spec.js
+++ b/spec/frontend/pages/admin/application_settings/account_and_limits_spec.js
@@ -23,17 +23,17 @@ describe('AccountAndLimits', () => {
describe('Changing of userInternalRegex when userDefaultExternal', () => {
it('is unchecked', () => {
- expect($userDefaultExternal.prop('checked')).toBeFalsy();
+ expect($userDefaultExternal.prop('checked')).toBe(false);
expect($userInternalRegex.placeholder).toEqual(PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE);
- expect($userInternalRegex.readOnly).toBeTruthy();
+ expect($userInternalRegex.readOnly).toBe(true);
});
it('is checked', () => {
if (!$userDefaultExternal.prop('checked')) $userDefaultExternal.click();
- expect($userDefaultExternal.prop('checked')).toBeTruthy();
+ expect($userDefaultExternal.prop('checked')).toBe(true);
expect($userInternalRegex.placeholder).toEqual(PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE);
- expect($userInternalRegex.readOnly).toBeFalsy();
+ expect($userInternalRegex.readOnly).toBe(false);
});
});
});
diff --git a/spec/frontend/vue_shared/components/changed_file_icon_spec.js b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
index 229fa245284..ea708b6f3fe 100644
--- a/spec/frontend/vue_shared/components/changed_file_icon_spec.js
+++ b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
@@ -51,7 +51,7 @@ describe('Changed file icon', () => {
showTooltip: false,
});
- expect(findTooltipText()).toBeFalsy();
+ expect(findTooltipText()).toBeUndefined();
});
describe.each`
@@ -87,7 +87,7 @@ describe('Changed file icon', () => {
});
it('does not have tooltip text', () => {
- expect(findTooltipText()).toBeFalsy();
+ expect(findTooltipText()).toBeUndefined();
});
});
diff --git a/spec/lib/gitlab/audit/auditor_spec.rb b/spec/lib/gitlab/audit/auditor_spec.rb
new file mode 100644
index 00000000000..fc5917ca583
--- /dev/null
+++ b/spec/lib/gitlab/audit/auditor_spec.rb
@@ -0,0 +1,258 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Audit::Auditor do
+ let(:name) { 'audit_operation' }
+ let(:author) { create(:user) }
+ let(:group) { create(:group) }
+ let(:provider) { 'standard' }
+ let(:context) do
+ { name: name,
+ author: author,
+ scope: group,
+ target: group,
+ authentication_event: true,
+ authentication_provider: provider,
+ message: "Signed in using standard authentication" }
+ end
+
+ let(:logger) { instance_spy(Gitlab::AuditJsonLogger) }
+
+ subject(:auditor) { described_class }
+
+ describe '.audit' do
+ context 'when authentication event' do
+ let(:audit!) { auditor.audit(context) }
+
+ it 'creates an authentication event' do
+ expect(AuthenticationEvent).to receive(:new).with(
+ {
+ user: author,
+ user_name: author.name,
+ ip_address: author.current_sign_in_ip,
+ result: AuthenticationEvent.results[:success],
+ provider: provider
+ }
+ ).and_call_original
+
+ audit!
+ end
+
+ it 'logs audit events to database', :aggregate_failures do
+ freeze_time do
+ audit!
+
+ audit_event = AuditEvent.last
+
+ expect(audit_event.author_id).to eq(author.id)
+ expect(audit_event.entity_id).to eq(group.id)
+ expect(audit_event.entity_type).to eq(group.class.name)
+ expect(audit_event.created_at).to eq(Time.zone.now)
+ expect(audit_event.details[:target_id]).to eq(group.id)
+ expect(audit_event.details[:target_type]).to eq(group.class.name)
+ end
+ end
+
+ it 'logs audit events to file' do
+ expect(::Gitlab::AuditJsonLogger).to receive(:build).and_return(logger)
+
+ audit!
+
+ expect(logger).to have_received(:info).with(
+ hash_including(
+ 'author_id' => author.id,
+ 'author_name' => author.name,
+ 'entity_id' => group.id,
+ 'entity_type' => group.class.name,
+ 'details' => kind_of(Hash)
+ )
+ )
+ end
+
+ context 'when overriding the create datetime' do
+ let(:context) do
+ { name: name,
+ author: author,
+ scope: group,
+ target: group,
+ created_at: 3.weeks.ago,
+ authentication_event: true,
+ authentication_provider: provider,
+ message: "Signed in using standard authentication" }
+ end
+
+ it 'logs audit events to database', :aggregate_failures do
+ freeze_time do
+ audit!
+
+ audit_event = AuditEvent.last
+
+ expect(audit_event.author_id).to eq(author.id)
+ expect(audit_event.entity_id).to eq(group.id)
+ expect(audit_event.entity_type).to eq(group.class.name)
+ expect(audit_event.created_at).to eq(3.weeks.ago)
+ expect(audit_event.details[:target_id]).to eq(group.id)
+ expect(audit_event.details[:target_type]).to eq(group.class.name)
+ end
+ end
+
+ it 'logs audit events to file' do
+ freeze_time do
+ expect(::Gitlab::AuditJsonLogger).to receive(:build).and_return(logger)
+
+ audit!
+
+ expect(logger).to have_received(:info).with(
+ hash_including(
+ 'author_id' => author.id,
+ 'author_name' => author.name,
+ 'entity_id' => group.id,
+ 'entity_type' => group.class.name,
+ 'details' => kind_of(Hash),
+ 'created_at' => 3.weeks.ago.iso8601(3)
+ )
+ )
+ end
+ end
+ end
+
+ context 'when overriding the additional_details' do
+ additional_details = { action: :custom, from: false, to: true }
+ let(:context) do
+ { name: name,
+ author: author,
+ scope: group,
+ target: group,
+ created_at: Time.zone.now,
+ additional_details: additional_details,
+ authentication_event: true,
+ authentication_provider: provider,
+ message: "Signed in using standard authentication" }
+ end
+
+ it 'logs audit events to database' do
+ freeze_time do
+ audit!
+
+ expect(AuditEvent.last.details).to include(additional_details)
+ end
+ end
+
+ it 'logs audit events to file' do
+ freeze_time do
+ expect(::Gitlab::AuditJsonLogger).to receive(:build).and_return(logger)
+
+ audit!
+
+ expect(logger).to have_received(:info).with(
+ hash_including(
+ 'details' => hash_including('action' => 'custom', 'from' => 'false', 'to' => 'true'),
+ 'action' => 'custom',
+ 'from' => 'false',
+ 'to' => 'true'
+ )
+ )
+ end
+ end
+ end
+
+ context 'when overriding the target_details' do
+ target_details = "this is my target details"
+ let(:context) do
+ {
+ name: name,
+ author: author,
+ scope: group,
+ target: group,
+ created_at: Time.zone.now,
+ target_details: target_details,
+ authentication_event: true,
+ authentication_provider: provider,
+ message: "Signed in using standard authentication"
+ }
+ end
+
+ it 'logs audit events to database' do
+ freeze_time do
+ audit!
+
+ audit_event = AuditEvent.last
+ expect(audit_event.details).to include({ target_details: target_details })
+ expect(audit_event.target_details).to eq(target_details)
+ end
+ end
+
+ it 'logs audit events to file' do
+ freeze_time do
+ expect(::Gitlab::AuditJsonLogger).to receive(:build).and_return(logger)
+
+ audit!
+
+ expect(logger).to have_received(:info).with(
+ hash_including(
+ 'details' => hash_including('target_details' => target_details),
+ 'target_details' => target_details
+ )
+ )
+ end
+ end
+ end
+ end
+
+ context 'when authentication event is false' do
+ let(:context) do
+ { name: name, author: author, scope: group,
+ target: group, authentication_event: false, message: "sample message" }
+ end
+
+ it 'does not create an authentication event' do
+ expect { auditor.audit(context) }.not_to change(AuthenticationEvent, :count)
+ end
+ end
+
+ context 'when authentication event is invalid' do
+ let(:audit!) { auditor.audit(context) }
+
+ before do
+ allow(AuthenticationEvent).to receive(:new).and_raise(ActiveRecord::RecordInvalid)
+ allow(Gitlab::ErrorTracking).to receive(:track_exception)
+ end
+
+ it 'tracks error' do
+ audit!
+
+ expect(Gitlab::ErrorTracking).to have_received(:track_exception).with(
+ kind_of(ActiveRecord::RecordInvalid),
+ { audit_operation: name }
+ )
+ end
+
+ it 'does not throw exception' do
+ expect { auditor.audit(context) }.not_to raise_exception
+ end
+ end
+
+ context 'when audit events are invalid' do
+ let(:audit!) { auditor.audit(context) }
+
+ before do
+ allow(AuditEvent).to receive(:bulk_insert!).and_raise(ActiveRecord::RecordInvalid)
+ allow(Gitlab::ErrorTracking).to receive(:track_exception)
+ end
+
+ it 'tracks error' do
+ audit!
+
+ expect(Gitlab::ErrorTracking).to have_received(:track_exception).with(
+ kind_of(ActiveRecord::RecordInvalid),
+ { audit_operation: name }
+ )
+ end
+
+ it 'does not throw exception' do
+ expect { auditor.audit(context) }.not_to raise_exception
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/audit/null_target_spec.rb b/spec/lib/gitlab/audit/null_target_spec.rb
new file mode 100644
index 00000000000..f192e0cd8db
--- /dev/null
+++ b/spec/lib/gitlab/audit/null_target_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Audit::NullTarget do
+ subject { described_class.new }
+
+ describe '#id' do
+ it 'returns nil' do
+ expect(subject.id).to eq(nil)
+ end
+ end
+
+ describe '#type' do
+ it 'returns nil' do
+ expect(subject.type).to eq(nil)
+ end
+ end
+
+ describe '#details' do
+ it 'returns nil' do
+ expect(subject.details).to eq(nil)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/audit/target_spec.rb b/spec/lib/gitlab/audit/target_spec.rb
new file mode 100644
index 00000000000..4748b4599fe
--- /dev/null
+++ b/spec/lib/gitlab/audit/target_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Audit::Target do
+ let(:object) { double('object') } # rubocop:disable Rspec/VerifiedDoubles
+
+ subject { described_class.new(object) }
+
+ describe '#id' do
+ it 'returns object id' do
+ allow(object).to receive(:id).and_return(object_id)
+
+ expect(subject.id).to eq(object_id)
+ end
+ end
+
+ describe '#type' do
+ it 'returns object class name' do
+ allow(object).to receive_message_chain(:class, :name).and_return('User')
+
+ expect(subject.type).to eq('User')
+ end
+ end
+
+ describe '#details' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:name, :audit_details, :details) do
+ 'jackie' | 'wanderer' | 'jackie'
+ 'jackie' | nil | 'jackie'
+ nil | 'wanderer' | 'wanderer'
+ nil | nil | 'unknown'
+ end
+
+ before do
+ allow(object).to receive(:name).and_return(name) if name
+ allow(object).to receive(:audit_details).and_return(audit_details) if audit_details
+ end
+
+ with_them do
+ it 'returns details' do
+ expect(subject.details).to eq(details)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
index 9b5bcdf4af9..06c2bc32db3 100644
--- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
+++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb
@@ -617,6 +617,49 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
end
end
+ describe '#progress' do
+ subject { migration.progress }
+
+ context 'when the migration is finished' do
+ let(:migration) do
+ create(:batched_background_migration, :finished, total_tuple_count: 1).tap do |record|
+ create(:batched_background_migration_job, :succeeded, batched_migration: record, batch_size: 1)
+ end
+ end
+
+ it 'returns 100' do
+ expect(subject).to be 100
+ end
+ end
+
+ context 'when the migration does not have jobs' do
+ let(:migration) { create(:batched_background_migration, :active) }
+
+ it 'returns zero' do
+ expect(subject).to be 0
+ end
+ end
+
+ context 'when the `total_tuple_count` is zero' do
+ let(:migration) { create(:batched_background_migration, :active, total_tuple_count: 0) }
+ let!(:batched_job) { create(:batched_background_migration_job, :succeeded, batched_migration: migration) }
+
+ it 'returns nil' do
+ expect(subject).to be nil
+ end
+ end
+
+ context 'when migration has completed jobs' do
+ let(:migration) { create(:batched_background_migration, :active, total_tuple_count: 100) }
+
+ let!(:batched_job) { create(:batched_background_migration_job, :succeeded, batched_migration: migration, batch_size: 8) }
+
+ it 'calculates the progress' do
+ expect(subject).to be 8
+ end
+ end
+ end
+
describe '.for_configuration' do
let!(:attributes) do
{
diff --git a/spec/models/group_group_link_spec.rb b/spec/models/group_group_link_spec.rb
index 72c700e7981..1d5e011c3f4 100644
--- a/spec/models/group_group_link_spec.rb
+++ b/spec/models/group_group_link_spec.rb
@@ -30,6 +30,54 @@ RSpec.describe GroupGroupLink do
end
end
+ describe '.with_owner_or_maintainer_access' do
+ let_it_be(:group_group_link_maintainer) { create :group_group_link, :maintainer }
+ let_it_be(:group_group_link_owner) { create :group_group_link, :owner }
+ let_it_be(:group_group_link_reporter) { create :group_group_link, :reporter }
+ let_it_be(:group_group_link_guest) { create :group_group_link, :guest }
+
+ it 'returns all records which have OWNER or MAINTAINER access' do
+ expect(described_class.with_owner_or_maintainer_access).to match_array([
+ group_group_link_maintainer,
+ group_group_link_owner
+ ])
+ end
+ end
+
+ context 'access via group shares' do
+ let_it_be(:shared_with_group_1) { create(:group) }
+ let_it_be(:shared_with_group_2) { create(:group) }
+ let_it_be(:shared_with_group_3) { create(:group) }
+ let_it_be(:shared_group_1) { create(:group) }
+ let_it_be(:shared_group_2) { create(:group) }
+ let_it_be(:shared_group_3) { create(:group) }
+ let_it_be(:shared_group_1_subgroup) { create(:group, parent: shared_group_1) }
+
+ before do
+ create :group_group_link, shared_with_group: shared_with_group_1, shared_group: shared_group_1
+ create :group_group_link, shared_with_group: shared_with_group_2, shared_group: shared_group_2
+ create :group_group_link, shared_with_group: shared_with_group_3, shared_group: shared_group_3
+ end
+
+ describe '.groups_accessible_via' do
+ it 'returns other groups that you can get access to, via the group shares of the specified groups' do
+ group_ids = [shared_with_group_1.id, shared_with_group_2.id]
+ expected_result = Group.id_in([shared_group_1.id, shared_group_1_subgroup.id, shared_group_2.id])
+
+ expect(described_class.groups_accessible_via(group_ids)).to match_array(expected_result)
+ end
+ end
+
+ describe '.groups_having_access_to' do
+ it 'returns all other groups that are having access to these specified groups, via group share' do
+ group_ids = [shared_group_1.id, shared_group_2.id]
+ expected_result = Group.id_in([shared_with_group_1.id, shared_with_group_2.id])
+
+ expect(described_class.groups_having_access_to(group_ids)).to match_array(expected_result)
+ end
+ end
+ end
+
describe '.distinct_on_shared_with_group_id_with_group_access' do
let_it_be(:sub_shared_group) { create(:group, parent: shared_group) }
let_it_be(:other_group) { create(:group) }
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 5e4685ac148..61662411ac8 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -806,6 +806,20 @@ RSpec.describe Group do
end
end
+ describe '.project_creation_allowed' do
+ let_it_be(:group_1) { create(:group, project_creation_level: Gitlab::Access::NO_ONE_PROJECT_ACCESS) }
+ let_it_be(:group_2) { create(:group, project_creation_level: Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
+ let_it_be(:group_3) { create(:group, project_creation_level: Gitlab::Access::MAINTAINER_PROJECT_ACCESS) }
+ let_it_be(:group_4) { create(:group, project_creation_level: nil) }
+
+ it 'only includes groups where project creation is allowed' do
+ result = described_class.project_creation_allowed
+
+ expect(result).to include(group_2, group_3, group_4)
+ expect(result).not_to include(group_1)
+ end
+ end
+
describe 'by_ids_or_paths' do
let(:group_path) { 'group_path' }
let!(:group) { create(:group, path: group_path) }
diff --git a/spec/services/audit_events/build_service_spec.rb b/spec/services/audit_events/build_service_spec.rb
new file mode 100644
index 00000000000..caf405a53aa
--- /dev/null
+++ b/spec/services/audit_events/build_service_spec.rb
@@ -0,0 +1,154 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe AuditEvents::BuildService do
+ let(:author) { build_stubbed(:author, current_sign_in_ip: '127.0.0.1') }
+ let(:deploy_token) { build_stubbed(:deploy_token, user: author) }
+ let(:scope) { build_stubbed(:group) }
+ let(:target) { build_stubbed(:project) }
+ let(:ip_address) { '192.168.8.8' }
+ let(:message) { 'Added an interesting field from project Gotham' }
+ let(:additional_details) { { action: :custom } }
+
+ subject(:service) do
+ described_class.new(
+ author: author,
+ scope: scope,
+ target: target,
+ message: message,
+ additional_details: additional_details,
+ ip_address: ip_address
+ )
+ end
+
+ describe '#execute', :request_store do
+ subject(:event) { service.execute }
+
+ before do
+ allow(Gitlab::RequestContext.instance).to receive(:client_ip).and_return(ip_address)
+ end
+
+ it 'sets correct attributes', :aggregate_failures do
+ freeze_time do
+ expect(event).to have_attributes(
+ author_id: author.id,
+ author_name: author.name,
+ entity_id: scope.id,
+ entity_type: scope.class.name)
+
+ expect(event.details).to eq(
+ author_name: author.name,
+ author_class: author.class.name,
+ target_id: target.id,
+ target_type: target.class.name,
+ target_details: target.name,
+ custom_message: message,
+ action: :custom)
+
+ expect(event.ip_address).to be_nil
+ expect(event.created_at).to eq(DateTime.current)
+ end
+ end
+
+ context 'when IP address is not provided' do
+ let(:ip_address) { nil }
+
+ it 'uses author current_sign_in_ip' do
+ expect(event.ip_address).to be_nil
+ end
+ end
+
+ context 'when overriding target details' do
+ subject(:service) do
+ described_class.new(
+ author: author,
+ scope: scope,
+ target: target,
+ message: message,
+ target_details: "This is my target details"
+ )
+ end
+
+ it 'uses correct target details' do
+ expect(event.target_details).to eq("This is my target details")
+ end
+ end
+
+ context 'when deploy token is passed as author' do
+ let(:service) do
+ described_class.new(
+ author: deploy_token,
+ scope: scope,
+ target: target,
+ message: message
+ )
+ end
+
+ it 'expect author to be user' do
+ expect(event.author_id).to eq(-2)
+ expect(event.author_name).to eq(deploy_token.name)
+ end
+ end
+
+ context 'when deploy key is passed as author' do
+ let(:deploy_key) { build_stubbed(:deploy_key, user: author) }
+
+ let(:service) do
+ described_class.new(
+ author: deploy_key,
+ scope: scope,
+ target: target,
+ message: message
+ )
+ end
+
+ it 'expect author to be deploy key' do
+ expect(event.author_id).to eq(-3)
+ expect(event.author_name).to eq(deploy_key.name)
+ end
+ end
+
+ context 'when author is passed as UnauthenticatedAuthor' do
+ let(:service) do
+ described_class.new(
+ author: ::Gitlab::Audit::UnauthenticatedAuthor.new,
+ scope: scope,
+ target: target,
+ message: message
+ )
+ end
+
+ it 'sets author as unauthenticated user' do
+ expect(event.author).to be_an_instance_of(::Gitlab::Audit::UnauthenticatedAuthor)
+ expect(event.author_name).to eq('An unauthenticated user')
+ end
+ end
+
+ context 'when attributes are missing' do
+ context 'when author is missing' do
+ let(:author) { nil }
+
+ it { expect { service }.to raise_error(described_class::MissingAttributeError) }
+ end
+
+ context 'when scope is missing' do
+ let(:scope) { nil }
+
+ it { expect { service }.to raise_error(described_class::MissingAttributeError) }
+ end
+
+ context 'when target is missing' do
+ let(:target) { nil }
+
+ it { expect { service }.to raise_error(described_class::MissingAttributeError) }
+ end
+
+ context 'when message is missing' do
+ let(:message) { nil }
+
+ it { expect { service }.to raise_error(described_class::MissingAttributeError) }
+ end
+ end
+ end
+end
diff --git a/spec/workers/pages/invalidate_domain_cache_worker_spec.rb b/spec/workers/pages/invalidate_domain_cache_worker_spec.rb
index 15a2c389482..49dcd18d64e 100644
--- a/spec/workers/pages/invalidate_domain_cache_worker_spec.rb
+++ b/spec/workers/pages/invalidate_domain_cache_worker_spec.rb
@@ -13,8 +13,8 @@ RSpec.describe Pages::InvalidateDomainCacheWorker do
it_behaves_like 'subscribes to event'
it 'clears the cache with Gitlab::Pages::CacheControl' do
- caches.each do |cache_type, cache_id|
- expect_next_instance_of(Gitlab::Pages::CacheControl, type: cache_type, id: cache_id) do |cache_control|
+ caches.each do |cache|
+ expect_next_instance_of(Gitlab::Pages::CacheControl, type: cache[:type], id: cache[:id]) do |cache_control|
expect(cache_control).to receive(:clear_cache)
end
end
@@ -26,27 +26,42 @@ RSpec.describe Pages::InvalidateDomainCacheWorker do
it_behaves_like 'clears caches with',
event_class: Pages::PageDeployedEvent,
event_data: { project_id: 1, namespace_id: 2, root_namespace_id: 3 },
- caches: { namespace: 3, project: 1 }
+ caches: [
+ { type: :namespace, id: 3 },
+ { type: :project, id: 1 }
+ ]
it_behaves_like 'clears caches with',
event_class: Pages::PageDeletedEvent,
event_data: { project_id: 1, namespace_id: 2, root_namespace_id: 3 },
- caches: { namespace: 3, project: 1 }
+ caches: [
+ { type: :namespace, id: 3 },
+ { type: :project, id: 1 }
+ ]
it_behaves_like 'clears caches with',
event_class: Projects::ProjectDeletedEvent,
event_data: { project_id: 1, namespace_id: 2, root_namespace_id: 3 },
- caches: { namespace: 3, project: 1 }
+ caches: [
+ { type: :namespace, id: 3 },
+ { type: :project, id: 1 }
+ ]
it_behaves_like 'clears caches with',
event_class: Projects::ProjectCreatedEvent,
event_data: { project_id: 1, namespace_id: 2, root_namespace_id: 3 },
- caches: { namespace: 3, project: 1 }
+ caches: [
+ { type: :namespace, id: 3 },
+ { type: :project, id: 1 }
+ ]
it_behaves_like 'clears caches with',
event_class: Projects::ProjectArchivedEvent,
event_data: { project_id: 1, namespace_id: 2, root_namespace_id: 3 },
- caches: { namespace: 3, project: 1 }
+ caches: [
+ { type: :namespace, id: 3 },
+ { type: :project, id: 1 }
+ ]
it_behaves_like 'clears caches with',
event_class: Projects::ProjectPathChangedEvent,
@@ -57,5 +72,40 @@ RSpec.describe Pages::InvalidateDomainCacheWorker do
old_path: 'old_path',
new_path: 'new_path'
},
- caches: { namespace: 3, project: 1 }
+ caches: [
+ { type: :namespace, id: 3 },
+ { type: :project, id: 1 }
+ ]
+
+ it_behaves_like 'clears caches with',
+ event_class: Projects::ProjectTransferedEvent,
+ event_data: {
+ project_id: 1,
+ old_namespace_id: 2,
+ old_root_namespace_id: 3,
+ new_namespace_id: 4,
+ new_root_namespace_id: 5
+ },
+ caches: [
+ { type: :project, id: 1 },
+ { type: :namespace, id: 3 },
+ { type: :namespace, id: 5 }
+ ]
+
+ context 'when namespace based cache keys are duplicated' do
+ # de-dups namespace cache keys
+ it_behaves_like 'clears caches with',
+ event_class: Projects::ProjectTransferedEvent,
+ event_data: {
+ project_id: 1,
+ old_namespace_id: 2,
+ old_root_namespace_id: 5,
+ new_namespace_id: 4,
+ new_root_namespace_id: 5
+ },
+ caches: [
+ { type: :project, id: 1 },
+ { type: :namespace, id: 5 }
+ ]
+ end
end