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-01 15:12:10 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-01 15:12:10 +0300
commit9c5341dd0832c3af377191c461c800e1aa048b10 (patch)
treee1343570ed06960c320200c8a35f2675a6ec2b48 /spec
parent46f35a616740504125aaf2c7d20a8bc7ff755ec1 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/components/docs/01_overview.html.erb20
-rw-r--r--spec/components/previews/pajamas/alert_component_preview.rb19
-rw-r--r--spec/components/previews/pajamas/avatar_component_preview.rb27
-rw-r--r--spec/components/previews/pajamas/banner_component_preview.rb54
-rw-r--r--spec/components/previews/pajamas/button_component_preview.rb56
-rw-r--r--spec/components/previews/pajamas/card_component_preview.rb27
-rw-r--r--spec/components/previews/pajamas/spinner_component_preview.rb22
-rw-r--r--spec/frontend/__helpers__/mocks/axios_utils.js2
-rw-r--r--spec/frontend/__helpers__/vuex_action_helper.js6
-rw-r--r--spec/frontend/__helpers__/wait_for_promises.js6
-rw-r--r--spec/frontend/__helpers__/web_worker_transformer.js2
-rw-r--r--spec/frontend/boards/project_select_spec.js3
-rw-r--r--spec/frontend/captcha/wait_for_captcha_to_be_solved_spec.js5
-rw-r--r--spec/frontend/clusters_list/components/install_agent_modal_spec.js1
-rw-r--r--spec/frontend/groups/components/app_spec.js24
-rw-r--r--spec/frontend/ide/components/ide_sidebar_nav_spec.js4
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js15
-rw-r--r--spec/frontend/lib/utils/rails_ujs_spec.js12
-rw-r--r--spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js17
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js59
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js11
-rw-r--r--spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js9
-rw-r--r--spec/frontend/runner/admin_runners/admin_runners_app_spec.js10
-rw-r--r--spec/frontend/runner/components/runner_assigned_item_spec.js16
-rw-r--r--spec/frontend/runner/components/runner_projects_spec.js1
-rw-r--r--spec/frontend/runner/group_runner_show/group_runner_show_app_spec.js5
-rw-r--r--spec/frontend/runner/group_runners/group_runners_app_spec.js10
-rw-r--r--spec/frontend/runner/mock_data.js9
-rw-r--r--spec/frontend/sidebar/components/incidents/escalation_status_spec.js5
-rw-r--r--spec/frontend/test_setup.js2
-rw-r--r--spec/frontend/work_items/components/work_item_assignees_spec.js2
-rw-r--r--spec/lib/gitlab/bare_repository_import/repository_spec.rb19
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb26
-rw-r--r--spec/lib/gitlab/git_access_spec.rb10
-rw-r--r--spec/lib/gitlab/gitaly_client/repository_service_spec.rb12
-rw-r--r--spec/models/members/group_member_spec.rb28
-rw-r--r--spec/models/members/project_member_spec.rb9
-rw-r--r--spec/models/namespace_spec.rb15
-rw-r--r--spec/models/project_spec.rb11
-rw-r--r--spec/models/remote_mirror_spec.rb12
-rw-r--r--spec/policies/group_policy_spec.rb27
-rw-r--r--spec/policies/namespaces/user_namespace_policy_spec.rb2
-rw-r--r--spec/requests/api/api_spec.rb50
-rw-r--r--spec/requests/api/invitations_spec.rb2
-rw-r--r--spec/services/members/groups/creator_service_spec.rb5
-rw-r--r--spec/services/projects/after_rename_service_spec.rb9
-rw-r--r--spec/services/projects/create_service_spec.rb6
-rw-r--r--spec/services/projects/hashed_storage/migrate_repository_service_spec.rb6
-rw-r--r--spec/services/projects/hashed_storage/rollback_repository_service_spec.rb8
-rw-r--r--spec/services/projects/transfer_service_spec.rb14
-rw-r--r--spec/spec_helper.rb1
-rw-r--r--spec/support/helpers/stub_member.rb8
-rw-r--r--spec/support/helpers/stubbed_member.rb28
-rw-r--r--spec/support/matchers/event_store.rb4
-rw-r--r--spec/tooling/danger/customer_success_spec.rb91
-rw-r--r--spec/workers/concerns/waitable_worker_spec.rb40
56 files changed, 653 insertions, 251 deletions
diff --git a/spec/components/docs/01_overview.html.erb b/spec/components/docs/01_overview.html.erb
new file mode 100644
index 00000000000..da4178ebcb5
--- /dev/null
+++ b/spec/components/docs/01_overview.html.erb
@@ -0,0 +1,20 @@
+---
+title: Welcome to our Lookbook 👋
+---
+
+<p>With Lookbook we can navigate, inspect and interact with our ViewComponent previews.</p>
+
+<h2>Usage</h2>
+
+<ul>
+ <li>Use the sidebar on the left to navigate our component previews.</li>
+ <li>Many previews can be interacted with by making changes in the <em>Params</em> tab.</li>
+ <li>Some previews have additional usage instructions in their <em>Notes</em> tab.</li>
+</ul>
+
+<h2>Learn more</h2>
+
+<ul>
+ <li>Learn all about <a href="https://viewcomponent.org/">ViewComponent</a> and <a href="https://github.com/allmarkedup/lookbook">Lookbook</a>.</li>
+ <li>Have a look at our ViewComponent page in the <a href="https://docs.gitlab.com/ee/development/fe_guide/view_component.html">Frontend development docs</a>.</li>
+</ul>
diff --git a/spec/components/previews/pajamas/alert_component_preview.rb b/spec/components/previews/pajamas/alert_component_preview.rb
new file mode 100644
index 00000000000..9a6b77715f5
--- /dev/null
+++ b/spec/components/previews/pajamas/alert_component_preview.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+module Pajamas
+ class AlertComponentPreview < ViewComponent::Preview
+ # @param body text
+ # @param dismissible toggle
+ # @param variant select [info, warning, success, danger, tip]
+ def default(body: nil, dismissible: true, variant: :info)
+ render(Pajamas::AlertComponent.new(
+ title: "Title",
+ dismissible: dismissible,
+ variant: variant.to_sym
+ )) do |c|
+ if body
+ c.with_body { body }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/components/previews/pajamas/avatar_component_preview.rb b/spec/components/previews/pajamas/avatar_component_preview.rb
new file mode 100644
index 00000000000..e5cdde1ccef
--- /dev/null
+++ b/spec/components/previews/pajamas/avatar_component_preview.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+module Pajamas
+ class AvatarComponentPreview < ViewComponent::Preview
+ # Avatar
+ # ----
+ # See its design reference [here](https://design.gitlab.com/components/avatar).
+ def default
+ user
+ end
+
+ # We show user avatars in a circle.
+ # @param size select [16, 24, 32, 48, 64, 96]
+ def user(size: 64)
+ render(Pajamas::AvatarComponent.new(User.first, size: size))
+ end
+
+ # @param size select [16, 24, 32, 48, 64, 96]
+ def project(size: 64)
+ render(Pajamas::AvatarComponent.new(Project.first, size: size))
+ end
+
+ # @param size select [16, 24, 32, 48, 64, 96]
+ def group(size: 64)
+ render(Pajamas::AvatarComponent.new(Group.first, size: size))
+ end
+ end
+end
diff --git a/spec/components/previews/pajamas/banner_component_preview.rb b/spec/components/previews/pajamas/banner_component_preview.rb
new file mode 100644
index 00000000000..861e3ff95dc
--- /dev/null
+++ b/spec/components/previews/pajamas/banner_component_preview.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+module Pajamas
+ class BannerComponentPreview < ViewComponent::Preview
+ # Banner
+ # ----
+ # See its design reference [here](https://design.gitlab.com/components/banner).
+ #
+ # @param button_text text
+ # @param button_link text
+ # @param content textarea
+ # @param embedded toggle
+ # @param variant select [introduction, promotion]
+ def default(
+ button_text: "Learn more",
+ button_link: "https://about.gitlab.com/",
+ content: "Add your message here.",
+ embedded: false,
+ variant: :promotion
+ )
+ render(Pajamas::BannerComponent.new(
+ button_text: button_text,
+ button_link: button_link,
+ embedded: embedded,
+ svg_path: "illustrations/autodevops.svg",
+ variant: variant
+ )) do |c|
+ content_tag :p, content
+ end
+ end
+
+ # Use the `primary_action` slot instead of `button_text` and `button_link` if you need something more special,
+ # like rendering a partial that holds your button.
+ def with_primary_action_slot
+ render(Pajamas::BannerComponent.new) do |c|
+ c.primary_action do
+ # You could also `render` another partial here.
+ tag.button "I'm special", class: "btn btn-md btn-confirm gl-button"
+ end
+ content_tag :p, "This banner uses the primary_action slot."
+ end
+ end
+
+ # Use the `illustration` slot instead of `svg_path` if your illustration is not part or the asset pipeline,
+ # but for example, an inline SVG via `custom_icon`.
+ def with_illustration_slot
+ render(Pajamas::BannerComponent.new) do |c|
+ c.illustration do
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="white" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-thumbs-up"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path></svg>'.html_safe # rubocop:disable Layout/LineLength
+ end
+ content_tag :p, "This banner uses the illustration slot."
+ end
+ end
+ end
+end
diff --git a/spec/components/previews/pajamas/button_component_preview.rb b/spec/components/previews/pajamas/button_component_preview.rb
new file mode 100644
index 00000000000..1f61d9cf2bc
--- /dev/null
+++ b/spec/components/previews/pajamas/button_component_preview.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+module Pajamas
+ class ButtonComponentPreview < ViewComponent::Preview
+ # Button
+ # ----
+ # See its design reference [here](https://design.gitlab.com/components/banner).
+ #
+ # @param category select [primary, secondary, tertiary]
+ # @param variant select [default, confirm, danger, dashed, link, reset]
+ # @param size select [small, medium]
+ # @param type select [button, reset, submit]
+ # @param disabled toggle
+ # @param loading toggle
+ # @param block toggle
+ # @param selected toggle
+ # @param icon text
+ # @param text text
+ def default( # rubocop:disable Metrics/ParameterLists
+ category: :primary,
+ variant: :default,
+ size: :medium,
+ type: :button,
+ disabled: false,
+ loading: false,
+ block: false,
+ selected: false,
+ icon: "pencil",
+ text: "Edit"
+ )
+ render(Pajamas::ButtonComponent.new(
+ category: category,
+ variant: variant,
+ size: size,
+ type: type,
+ disabled: disabled,
+ loading: loading,
+ block: block,
+ selected: selected,
+ icon: icon
+ )) do
+ text.presence
+ end
+ end
+
+ # The component can also be used to create links that look and feel like buttons.
+ # Just provide a `href` and optionally a `target` to create an `<a>` tag.
+ def link
+ render(Pajamas::ButtonComponent.new(
+ href: "https://gitlab.com",
+ target: "_blank"
+ )) do
+ "This is a link"
+ end
+ end
+ end
+end
diff --git a/spec/components/previews/pajamas/card_component_preview.rb b/spec/components/previews/pajamas/card_component_preview.rb
new file mode 100644
index 00000000000..61d1f8db9e1
--- /dev/null
+++ b/spec/components/previews/pajamas/card_component_preview.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+module Pajamas
+ class CardComponentPreview < ViewComponent::Preview
+ # Card
+ # ----
+ # See its design reference [here](https://design.gitlab.com/components/card).
+ #
+ # @param header text
+ # @param body textarea
+ # @param footer text
+ def default(header: nil, body: "Every card has a body.", footer: nil)
+ render(Pajamas::CardComponent.new) do |c|
+ if header
+ c.with_header { header }
+ end
+
+ c.with_body do
+ content_tag(:p, body)
+ end
+
+ if footer
+ c.with_footer { footer }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/components/previews/pajamas/spinner_component_preview.rb b/spec/components/previews/pajamas/spinner_component_preview.rb
new file mode 100644
index 00000000000..149bfddcfc2
--- /dev/null
+++ b/spec/components/previews/pajamas/spinner_component_preview.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+module Pajamas
+ class SpinnerComponentPreview < ViewComponent::Preview
+ # Spinner
+ # ----
+ # See its design reference [here](https://design.gitlab.com/components/spinner).
+ #
+ # @param inline toggle
+ # @param label text
+ # @param size select [[small, sm], [medium, md], [large, lg], [extra large, xl]]
+ def default(inline: false, label: "Loading", size: :md)
+ render(Pajamas::SpinnerComponent.new(inline: inline, label: label, size: size))
+ end
+
+ # Use a light spinner on dark backgrounds
+ #
+ # @display bg_color "#222"
+ def light
+ render(Pajamas::SpinnerComponent.new(color: :light))
+ end
+ end
+end
diff --git a/spec/frontend/__helpers__/mocks/axios_utils.js b/spec/frontend/__helpers__/mocks/axios_utils.js
index b1efd29dc8d..60644c84a57 100644
--- a/spec/frontend/__helpers__/mocks/axios_utils.js
+++ b/spec/frontend/__helpers__/mocks/axios_utils.js
@@ -1,4 +1,6 @@
import EventEmitter from 'events';
+// eslint-disable-next-line no-restricted-syntax
+import { setImmediate } from 'timers';
const axios = jest.requireActual('~/lib/utils/axios_utils').default;
diff --git a/spec/frontend/__helpers__/vuex_action_helper.js b/spec/frontend/__helpers__/vuex_action_helper.js
index ab2637d6024..bdd5a0a9034 100644
--- a/spec/frontend/__helpers__/vuex_action_helper.js
+++ b/spec/frontend/__helpers__/vuex_action_helper.js
@@ -1,5 +1,7 @@
-/**
- * Helper for testing action with expected mutations inspired in
+// eslint-disable-next-line no-restricted-syntax
+import { setImmediate } from 'timers';
+
+/** Helper for testing action with expected mutations inspired in
* https://vuex.vuejs.org/en/testing.html
*
* @param {(Function|Object)} action to be tested, or object of named parameters
diff --git a/spec/frontend/__helpers__/wait_for_promises.js b/spec/frontend/__helpers__/wait_for_promises.js
index 753c3c5d92b..5a15b8b74b5 100644
--- a/spec/frontend/__helpers__/wait_for_promises.js
+++ b/spec/frontend/__helpers__/wait_for_promises.js
@@ -1,4 +1,2 @@
-export default () =>
- new Promise((resolve) => {
- requestAnimationFrame(resolve);
- });
+// eslint-disable-next-line no-restricted-syntax
+export default () => new Promise(jest.requireActual('timers').setImmediate);
diff --git a/spec/frontend/__helpers__/web_worker_transformer.js b/spec/frontend/__helpers__/web_worker_transformer.js
index 5b2f7d77947..767ab3f5675 100644
--- a/spec/frontend/__helpers__/web_worker_transformer.js
+++ b/spec/frontend/__helpers__/web_worker_transformer.js
@@ -6,7 +6,7 @@ const babelJestTransformer = require('babel-jest');
// [1]: https://webpack.js.org/loaders/worker-loader/
module.exports = {
process: (contentArg, filename, ...args) => {
- const { code: content } = babelJestTransformer.process(contentArg, filename, ...args);
+ const { code: content } = babelJestTransformer.default.process(contentArg, filename, ...args);
return `const { FakeWebWorker } = require("helpers/web_worker_fake");
module.exports = class JestTransformedWorker extends FakeWebWorker {
diff --git a/spec/frontend/boards/project_select_spec.js b/spec/frontend/boards/project_select_spec.js
index c45cd545155..769d34a4ada 100644
--- a/spec/frontend/boards/project_select_spec.js
+++ b/spec/frontend/boards/project_select_spec.js
@@ -10,7 +10,6 @@ import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import ProjectSelect from '~/boards/components/project_select.vue';
import defaultState from '~/boards/stores/state';
-import waitForPromises from 'helpers/wait_for_promises';
import { mockList, mockActiveGroupProjects } from './mock_data';
@@ -133,7 +132,7 @@ describe('ProjectSelect component', () => {
const dropdownToggle = findGlDropdown().find('.dropdown-toggle');
await dropdownToggle.trigger('click');
- await waitForPromises();
+ jest.runOnlyPendingTimers();
await nextTick();
const searchInput = findGlDropdown().findComponent(GlFormInput).element;
diff --git a/spec/frontend/captcha/wait_for_captcha_to_be_solved_spec.js b/spec/frontend/captcha/wait_for_captcha_to_be_solved_spec.js
index 08d031a4fa7..2263d2bbeed 100644
--- a/spec/frontend/captcha/wait_for_captcha_to_be_solved_spec.js
+++ b/spec/frontend/captcha/wait_for_captcha_to_be_solved_spec.js
@@ -1,3 +1,4 @@
+import { nextTick } from 'vue';
import CaptchaModal from '~/captcha/captcha_modal.vue';
import { waitForCaptchaToBeSolved } from '~/captcha/wait_for_captcha_to_be_solved';
@@ -15,7 +16,7 @@ describe('waitForCaptchaToBeSolved', () => {
it('opens a modal, resolves with captcha response on success', async () => {
CaptchaModal.mounted.mockImplementationOnce(function mounted() {
- requestAnimationFrame(() => {
+ return nextTick().then(() => {
this.$emit('receivedCaptchaResponse', response);
this.$emit('hidden');
});
@@ -36,7 +37,7 @@ describe('waitForCaptchaToBeSolved', () => {
it("opens a modal, rejects with error in case the captcha isn't solved", async () => {
CaptchaModal.mounted.mockImplementationOnce(function mounted() {
- requestAnimationFrame(() => {
+ return nextTick().then(() => {
this.$emit('receivedCaptchaResponse', null);
this.$emit('hidden');
});
diff --git a/spec/frontend/clusters_list/components/install_agent_modal_spec.js b/spec/frontend/clusters_list/components/install_agent_modal_spec.js
index 29884675b24..964dd005a27 100644
--- a/spec/frontend/clusters_list/components/install_agent_modal_spec.js
+++ b/spec/frontend/clusters_list/components/install_agent_modal_spec.js
@@ -150,7 +150,6 @@ describe('InstallAgentModal', () => {
});
it("doesn't render agent installation instructions", () => {
- expect(findModal().text()).not.toContain(i18n.basicInstallTitle);
expect(findModal().findComponent(GlFormInputGroup).exists()).toBe(false);
expect(findModal().findComponent(GlAlert).exists()).toBe(false);
});
diff --git a/spec/frontend/groups/components/app_spec.js b/spec/frontend/groups/components/app_spec.js
index 9e4666ffc70..a6bbea648d2 100644
--- a/spec/frontend/groups/components/app_spec.js
+++ b/spec/frontend/groups/components/app_spec.js
@@ -85,30 +85,6 @@ describe('AppComponent', () => {
await nextTick();
});
- describe('computed', () => {
- describe('groups', () => {
- it('should return list of groups from store', () => {
- jest.spyOn(vm.store, 'getGroups').mockImplementation(() => {});
-
- const { groups } = vm;
-
- expect(vm.store.getGroups).toHaveBeenCalled();
- expect(groups).not.toBeDefined();
- });
- });
-
- describe('pageInfo', () => {
- it('should return pagination info from store', () => {
- jest.spyOn(vm.store, 'getPaginationInfo').mockImplementation(() => {});
-
- const { pageInfo } = vm;
-
- expect(vm.store.getPaginationInfo).toHaveBeenCalled();
- expect(pageInfo).not.toBeDefined();
- });
- });
- });
-
describe('methods', () => {
describe('fetchGroups', () => {
it('should call `getGroups` with all the params provided', () => {
diff --git a/spec/frontend/ide/components/ide_sidebar_nav_spec.js b/spec/frontend/ide/components/ide_sidebar_nav_spec.js
index 2ea0c250794..33b33fb62fd 100644
--- a/spec/frontend/ide/components/ide_sidebar_nav_spec.js
+++ b/spec/frontend/ide/components/ide_sidebar_nav_spec.js
@@ -8,12 +8,12 @@ import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
const TEST_TABS = [
{
title: 'Lorem',
- icon: 'angle-up',
+ icon: 'chevron-lg-up',
views: [{ name: 'lorem-1' }, { name: 'lorem-2' }],
},
{
title: 'Ipsum',
- icon: 'angle-down',
+ icon: 'chevron-lg-down',
views: [{ name: 'ipsum-1' }, { name: 'ipsum-2' }],
},
];
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index 7cf101a5e59..73846aeac96 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -292,16 +292,11 @@ describe('common_utils', () => {
const spy = jest.fn();
const debouncedSpy = commonUtils.debounceByAnimationFrame(spy);
- return new Promise((resolve) => {
- window.requestAnimationFrame(() => {
- debouncedSpy();
- debouncedSpy();
- window.requestAnimationFrame(() => {
- expect(spy).toHaveBeenCalledTimes(1);
- resolve();
- });
- });
- });
+ debouncedSpy();
+ debouncedSpy();
+ jest.runOnlyPendingTimers();
+
+ expect(spy).toHaveBeenCalledTimes(1);
});
});
diff --git a/spec/frontend/lib/utils/rails_ujs_spec.js b/spec/frontend/lib/utils/rails_ujs_spec.js
index c10301523c9..da9cc5c6f3c 100644
--- a/spec/frontend/lib/utils/rails_ujs_spec.js
+++ b/spec/frontend/lib/utils/rails_ujs_spec.js
@@ -18,14 +18,12 @@ function mockXHRResponse({ responseText, responseContentType } = {}) {
.mockReturnValue(responseContentType);
jest.spyOn(global.XMLHttpRequest.prototype, 'send').mockImplementation(function send() {
- requestAnimationFrame(() => {
- Object.defineProperties(this, {
- readyState: { value: XMLHttpRequest.DONE },
- status: { value: 200 },
- response: { value: responseText },
- });
- this.onreadystatechange();
+ Object.defineProperties(this, {
+ readyState: { value: XMLHttpRequest.DONE },
+ status: { value: 200 },
+ response: { value: responseText },
});
+ this.onreadystatechange();
});
}
diff --git a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
index d990d5ad22b..cf28ffeabed 100644
--- a/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
+++ b/spec/frontend/pipeline_editor/components/pipeline_editor_tabs_spec.js
@@ -4,6 +4,7 @@ import VueApollo from 'vue-apollo';
import Vue, { nextTick } from 'vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import CiConfigMergedPreview from '~/pipeline_editor/components/editor/ci_config_merged_preview.vue';
import CiLint from '~/pipeline_editor/components/lint/ci_lint.vue';
import CiValidate from '~/pipeline_editor/components/validate/ci_validate.vue';
@@ -22,6 +23,7 @@ import {
} from '~/pipeline_editor/constants';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.query.graphql';
+import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.query.graphql';
import {
mockBlobContentQueryResponse,
mockCiLintPath,
@@ -81,6 +83,15 @@ describe('Pipeline editor tabs component', () => {
const createComponentWithApollo = ({ props, provide = {}, mountFn = shallowMount } = {}) => {
const handlers = [[getBlobContent, mockBlobContentData]];
mockApollo = createMockApollo(handlers);
+ mockApollo.clients.defaultClient.cache.writeQuery({
+ query: getAppStatus,
+ data: {
+ app: {
+ __typename: 'PipelineEditorApp',
+ status: EDITOR_APP_STATUS_VALID,
+ },
+ },
+ });
createComponent({
props,
@@ -203,7 +214,7 @@ describe('Pipeline editor tabs component', () => {
});
describe('if badge has been dismissed before', () => {
- beforeEach(() => {
+ it('does not render badge if it has been dismissed before', async () => {
localStorage.setItem(VALIDATE_TAB_BADGE_DISMISSED_KEY, 'true');
mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse);
createComponentWithApollo({
@@ -217,9 +228,9 @@ describe('Pipeline editor tabs component', () => {
validateTabIllustrationPath: 'path/to/svg',
},
});
- });
- it('does not render badge if it has been dismissed before', () => {
+ await waitForPromises();
+
expect(findBadge().exists()).toBe(false);
});
});
diff --git a/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js b/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js
index 1ff32b03344..e712cdeaea2 100644
--- a/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js
@@ -1,4 +1,5 @@
import { GlDropdown } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
@@ -61,11 +62,10 @@ describe('Pipelines stage component', () => {
const findMergeTrainWarning = () => wrapper.find('[data-testid="warning-message-merge-trains"]');
const findLoadingState = () => wrapper.find('[data-testid="pipeline-stage-loading-state"]');
- const openStageDropdown = () => {
- findDropdownToggle().trigger('click');
- return new Promise((resolve) => {
- wrapper.vm.$root.$on('bv::dropdown::show', resolve);
- });
+ const openStageDropdown = async () => {
+ await findDropdownToggle().trigger('click');
+ await waitForPromises();
+ await nextTick();
};
describe('loading state', () => {
@@ -77,7 +77,10 @@ describe('Pipelines stage component', () => {
await openStageDropdown();
});
- it('displays loading state while jobs are being fetched', () => {
+ it('displays loading state while jobs are being fetched', async () => {
+ jest.runOnlyPendingTimers();
+ await nextTick();
+
expect(findLoadingState().exists()).toBe(true);
expect(findLoadingState().text()).toBe(PipelineStage.i18n.loadingText);
});
@@ -98,46 +101,41 @@ describe('Pipelines stage component', () => {
expect(glTooltipDirectiveMock.mock.calls[0][1].modifiers.ds0).toBe(true);
});
- it('should render a dropdown with the status icon', () => {
+ it('renders a dropdown with the status icon', () => {
expect(findDropdown().exists()).toBe(true);
expect(findDropdownToggle().exists()).toBe(true);
expect(findCiIcon().exists()).toBe(true);
});
- it('should render a borderless ci-icon', () => {
+ it('renders a borderless ci-icon', () => {
expect(findCiIcon().exists()).toBe(true);
expect(findCiIcon().props('isBorderless')).toBe(true);
expect(findCiIcon().classes('borderless')).toBe(true);
});
- it('should render a ci-icon with a custom border class', () => {
+ it('renders a ci-icon with a custom border class', () => {
expect(findCiIcon().exists()).toBe(true);
expect(findCiIcon().classes('gl-border')).toBe(true);
});
});
- describe('when update dropdown is changed', () => {
- beforeEach(() => {
- createComponent();
- });
- });
-
describe('when user opens dropdown and stage request is successful', () => {
beforeEach(async () => {
mock.onGet(dropdownPath).reply(200, stageReply);
createComponent();
await openStageDropdown();
+ await jest.runAllTimers();
await axios.waitForAll();
});
- it('should render the received data and emit `clickedDropdown` event', async () => {
+ it('renders the received data and emit `clickedDropdown` event', async () => {
expect(findDropdownMenu().text()).toContain(stageReply.latest_statuses[0].name);
expect(findDropdownMenuTitle().text()).toContain(stageReply.name);
expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
});
- it('should refresh when updateDropdown is set to true', async () => {
+ it('refreshes when updateDropdown is set to true', async () => {
expect(mock.history.get).toHaveLength(1);
wrapper.setProps({ updateDropdown: true });
@@ -148,15 +146,14 @@ describe('Pipelines stage component', () => {
});
describe('when user opens dropdown and stage request fails', () => {
- beforeEach(async () => {
+ it('should close the dropdown', async () => {
mock.onGet(dropdownPath).reply(500);
createComponent();
await openStageDropdown();
await axios.waitForAll();
- });
+ await waitForPromises();
- it('should close the dropdown', () => {
expect(findDropdown().classes('show')).toBe(false);
});
});
@@ -181,26 +178,29 @@ describe('Pipelines stage component', () => {
it('should update the stage to request the new endpoint provided', async () => {
await openStageDropdown();
- await axios.waitForAll();
+ jest.runOnlyPendingTimers();
+ await waitForPromises();
expect(findDropdownMenu().text()).toContain('this is the updated content');
});
});
describe('pipelineActionRequestComplete', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mock.onGet(dropdownPath).reply(200, stageReply);
mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
createComponent();
+ await waitForPromises();
+ await nextTick();
});
const clickCiAction = async () => {
await openStageDropdown();
- await axios.waitForAll();
+ jest.runOnlyPendingTimers();
+ await waitForPromises();
- findCiActionBtn().trigger('click');
- await axios.waitForAll();
+ await findCiActionBtn().trigger('click');
};
it('closes dropdown when job item action is clicked', async () => {
@@ -211,29 +211,30 @@ describe('Pipelines stage component', () => {
expect(hidden).toHaveBeenCalledTimes(0);
await clickCiAction();
+ await waitForPromises();
expect(hidden).toHaveBeenCalledTimes(1);
});
it('emits `pipelineActionRequestComplete` when job item action is clicked', async () => {
await clickCiAction();
+ await waitForPromises();
expect(wrapper.emitted('pipelineActionRequestComplete')).toHaveLength(1);
});
});
describe('With merge trains enabled', () => {
- beforeEach(async () => {
+ it('shows a warning on the dropdown', async () => {
mock.onGet(dropdownPath).reply(200, stageReply);
createComponent({
isMergeTrain: true,
});
await openStageDropdown();
- await axios.waitForAll();
- });
+ jest.runOnlyPendingTimers();
+ await waitForPromises();
- it('shows a warning on the dropdown', () => {
const warning = findMergeTrainWarning();
expect(warning.text()).toBe('Merge train pipeline jobs can not be retried');
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index ad6d650670a..0bed24e588e 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -45,6 +45,7 @@ describe('Pipelines', () => {
ciLintPath: '/ci/lint',
resetCachePath: `${mockProjectPath}/settings/ci_cd/reset_cache`,
newPipelinePath: `${mockProjectPath}/pipelines/new`,
+
ciRunnerSettingsPath: `${mockProjectPath}/-/settings/ci_cd#js-runners-settings`,
};
@@ -654,7 +655,12 @@ describe('Pipelines', () => {
// Mock init a polling cycle
wrapper.vm.poll.options.notificationCallback(true);
- findStagesDropdownToggle().trigger('click');
+ await findStagesDropdownToggle().trigger('click');
+ jest.runOnlyPendingTimers();
+
+ // cancelMock is getting overwritten in pipelines_service.js#L29
+ // so we have to spy on it again here
+ cancelMock = jest.spyOn(wrapper.vm.service.cancelationSource, 'cancel');
await waitForPromises();
@@ -664,7 +670,8 @@ describe('Pipelines', () => {
});
it('stops polling & restarts polling', async () => {
- findStagesDropdownToggle().trigger('click');
+ await findStagesDropdownToggle().trigger('click');
+ jest.runOnlyPendingTimers();
await waitForPromises();
expect(cancelMock).not.toHaveBeenCalled();
diff --git a/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js b/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js
index f820951cffc..509681c5a77 100644
--- a/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js
+++ b/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js
@@ -14,6 +14,7 @@ import RunnerPauseButton from '~/runner/components/runner_pause_button.vue';
import RunnerDeleteButton from '~/runner/components/runner_delete_button.vue';
import RunnerEditButton from '~/runner/components/runner_edit_button.vue';
import RunnersJobs from '~/runner/components/runner_jobs.vue';
+
import runnerQuery from '~/runner/graphql/show/runner.query.graphql';
import AdminRunnerShowApp from '~/runner/admin_runner_show/admin_runner_show_app.vue';
import { captureException } from '~/runner/sentry_utils';
@@ -182,17 +183,19 @@ describe('AdminRunnerShowApp', () => {
});
describe('When loading', () => {
- beforeEach(() => {
+ it('does not show runner details', () => {
mockRunnerQueryResult();
createComponent();
- });
- it('does not show runner details', () => {
expect(findRunnerDetails().exists()).toBe(false);
});
it('does not show runner jobs', () => {
+ mockRunnerQueryResult();
+
+ createComponent();
+
expect(findRunnersJobs().exists()).toBe(false);
});
});
diff --git a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
index fac99ed395a..bff1cb1164d 100644
--- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
+++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
@@ -50,6 +50,7 @@ import {
allRunnersDataPaginated,
onlineContactTimeoutSecs,
staleTimeoutSecs,
+ emptyPageInfo,
emptyStateSvgPath,
emptyStateFilteredSvgPath,
} from '../mock_data';
@@ -380,13 +381,20 @@ describe('AdminRunnersApp', () => {
beforeEach(async () => {
mockRunnersHandler.mockResolvedValue({
data: {
- runners: { nodes: [] },
+ runners: {
+ nodes: [],
+ pageInfo: emptyPageInfo,
+ },
},
});
await createComponent();
});
+ it('shows no errors', () => {
+ expect(createAlert).not.toHaveBeenCalled();
+ });
+
it('shows an empty state', () => {
expect(findRunnerListEmptyState().props('isSearchFiltered')).toBe(false);
});
diff --git a/spec/frontend/runner/components/runner_assigned_item_spec.js b/spec/frontend/runner/components/runner_assigned_item_spec.js
index 1ff6983fbe7..cc09046c000 100644
--- a/spec/frontend/runner/components/runner_assigned_item_spec.js
+++ b/spec/frontend/runner/components/runner_assigned_item_spec.js
@@ -1,10 +1,12 @@
-import { GlAvatar } from '@gitlab/ui';
+import { GlAvatar, GlBadge } from '@gitlab/ui';
+import { s__ } from '~/locale';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RunnerAssignedItem from '~/runner/components/runner_assigned_item.vue';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
const mockHref = '/group/project';
const mockName = 'Project';
+const mockDescription = 'Project description';
const mockFullName = 'Group / Project';
const mockAvatarUrl = '/avatar.png';
@@ -12,6 +14,7 @@ describe('RunnerAssignedItem', () => {
let wrapper;
const findAvatar = () => wrapper.findByTestId('item-avatar');
+ const findBadge = () => wrapper.findComponent(GlBadge);
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMountExtended(RunnerAssignedItem, {
@@ -20,6 +23,7 @@ describe('RunnerAssignedItem', () => {
name: mockName,
fullName: mockFullName,
avatarUrl: mockAvatarUrl,
+ description: mockDescription,
...props,
},
});
@@ -51,4 +55,14 @@ describe('RunnerAssignedItem', () => {
expect(groupFullName.attributes('href')).toBe(mockHref);
});
+
+ it('Shows description', () => {
+ expect(wrapper.text()).toContain(mockDescription);
+ });
+
+ it('Shows owner badge', () => {
+ createComponent({ props: { isOwner: true } });
+
+ expect(findBadge().text()).toBe(s__('Runner|Owner'));
+ });
});
diff --git a/spec/frontend/runner/components/runner_projects_spec.js b/spec/frontend/runner/components/runner_projects_spec.js
index 6932b3b5197..c988fb8477d 100644
--- a/spec/frontend/runner/components/runner_projects_spec.js
+++ b/spec/frontend/runner/components/runner_projects_spec.js
@@ -95,6 +95,7 @@ describe('RunnerProjects', () => {
name,
fullName: nameWithNamespace,
avatarUrl,
+ isOwner: true, // first project is always owner
});
});
diff --git a/spec/frontend/runner/group_runner_show/group_runner_show_app_spec.js b/spec/frontend/runner/group_runner_show/group_runner_show_app_spec.js
index d80a15f1fc3..cee1d436942 100644
--- a/spec/frontend/runner/group_runner_show/group_runner_show_app_spec.js
+++ b/spec/frontend/runner/group_runner_show/group_runner_show_app_spec.js
@@ -178,13 +178,10 @@ describe('GroupRunnerShowApp', () => {
});
describe('When loading', () => {
- beforeEach(() => {
+ it('does not show runner details', () => {
mockRunnerQueryResult();
createComponent();
- });
-
- it('does not show runner details', () => {
expect(findRunnerDetails().exists()).toBe(false);
});
});
diff --git a/spec/frontend/runner/group_runners/group_runners_app_spec.js b/spec/frontend/runner/group_runners/group_runners_app_spec.js
index 2aa631f270f..99b6273627c 100644
--- a/spec/frontend/runner/group_runners/group_runners_app_spec.js
+++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js
@@ -47,6 +47,7 @@ import {
groupRunnersCountData,
onlineContactTimeoutSecs,
staleTimeoutSecs,
+ emptyPageInfo,
emptyStateSvgPath,
emptyStateFilteredSvgPath,
} from '../mock_data';
@@ -331,13 +332,20 @@ describe('GroupRunnersApp', () => {
data: {
group: {
id: '1',
- runners: { nodes: [] },
+ runners: {
+ edges: [],
+ pageInfo: emptyPageInfo,
+ },
},
},
});
await createComponent();
});
+ it('shows no errors', () => {
+ expect(createAlert).not.toHaveBeenCalled();
+ });
+
it('shows an empty state', async () => {
expect(findRunnerListEmptyState().exists()).toBe(true);
});
diff --git a/spec/frontend/runner/mock_data.js b/spec/frontend/runner/mock_data.js
index e5472ace817..e73d888bd9f 100644
--- a/spec/frontend/runner/mock_data.js
+++ b/spec/frontend/runner/mock_data.js
@@ -19,6 +19,14 @@ import groupRunnersCountData from 'test_fixtures/graphql/runner/list/group_runne
import { RUNNER_PAGE_SIZE } from '~/runner/constants';
+const emptyPageInfo = {
+ __typename: 'PageInfo',
+ hasNextPage: false,
+ hasPreviousPage: false,
+ startCursor: '',
+ endCursor: '',
+};
+
// Other mock data
// Mock searches and their corresponding urls
@@ -233,6 +241,7 @@ export {
groupRunnersData,
groupRunnersDataPaginated,
groupRunnersCountData,
+ emptyPageInfo,
runnerData,
runnerWithGroupData,
runnerProjectsData,
diff --git a/spec/frontend/sidebar/components/incidents/escalation_status_spec.js b/spec/frontend/sidebar/components/incidents/escalation_status_spec.js
index 8d8c10d10f1..83764cb6739 100644
--- a/spec/frontend/sidebar/components/incidents/escalation_status_spec.js
+++ b/spec/frontend/sidebar/components/incidents/escalation_status_spec.js
@@ -1,4 +1,5 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import EscalationStatus from '~/sidebar/components/incidents/escalation_status.vue';
@@ -61,6 +62,8 @@ describe('EscalationStatus', () => {
createComponent();
// Open dropdown
await toggleDropdown();
+ jest.runOnlyPendingTimers();
+ await nextTick();
expect(findDropdownMenu().classes('show')).toBe(true);
@@ -74,6 +77,8 @@ describe('EscalationStatus', () => {
createComponent({ preventDropdownClose: true });
// Open dropdown
await toggleDropdown();
+ jest.runOnlyPendingTimers();
+ await nextTick();
expect(findDropdownMenu().classes('show')).toBe(true);
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index b4626625f31..bcd7c651fa7 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -1,4 +1,6 @@
/* Setup for unit test environment */
+// eslint-disable-next-line no-restricted-syntax
+import { setImmediate } from 'timers';
import 'helpers/shared_test_setup';
import { initializeTestTimeout } from 'helpers/timeout';
diff --git a/spec/frontend/work_items/components/work_item_assignees_spec.js b/spec/frontend/work_items/components/work_item_assignees_spec.js
index 299949a4baa..547034bad45 100644
--- a/spec/frontend/work_items/components/work_item_assignees_spec.js
+++ b/spec/frontend/work_items/components/work_item_assignees_spec.js
@@ -204,7 +204,7 @@ describe('WorkItemAssignees component', () => {
expect(findTokenSelector().props('dropdownItems')).toHaveLength(2);
});
- it('should search for users with correct key after text input', async () => {
+ it('searches for users with correct key after text input', async () => {
const searchKey = 'Hello';
findTokenSelector().vm.$emit('focus');
diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
index d29447ee376..becfdced5fb 100644
--- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
@@ -54,16 +54,16 @@ RSpec.describe ::Gitlab::BareRepositoryImport::Repository do
end
context 'hashed storage' do
- let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' }
let(:hashed_path) { "@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b" }
let(:root_path) { TestEnv.repos_path }
let(:repo_path) { File.join(root_path, "#{hashed_path}.git") }
let(:wiki_path) { File.join(root_path, "#{hashed_path}.wiki.git") }
let(:raw_repository) { Gitlab::Git::Repository.new('default', "#{hashed_path}.git", nil, nil) }
+ let(:full_path) { 'to/repo' }
before do
raw_repository.create_repository
- raw_repository.set_full_path(full_path: 'to/repo')
+ raw_repository.set_full_path(full_path: full_path) if full_path
end
after do
@@ -95,16 +95,17 @@ RSpec.describe ::Gitlab::BareRepositoryImport::Repository do
expect(subject).not_to be_processable
end
- it 'returns false when group and project name are missing' do
- repository = Rugged::Repository.new(repo_path)
- repository.config.delete('gitlab.fullpath')
-
- expect(subject).not_to be_processable
- end
-
it 'returns true when group path and project name are present' do
expect(subject).to be_processable
end
+
+ context 'group and project name are missing' do
+ let(:full_path) { nil }
+
+ it 'returns false' do
+ expect(subject).not_to be_processable
+ end
+ end
end
describe '#project_full_path' do
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index e20d5b928c4..0b5c066430d 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -2017,17 +2017,14 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
describe '#set_full_path' do
before do
- repository_rugged.config["gitlab.fullpath"] = repository_path
+ repository.set_full_path(full_path: repository_path)
end
context 'is given a path' do
it 'writes it to disk' do
repository.set_full_path(full_path: "not-the/real-path.git")
- config = File.read(File.join(repository_path, "config"))
-
- expect(config).to include("[gitlab]")
- expect(config).to include("fullpath = not-the/real-path.git")
+ expect(repository.full_path).to eq('not-the/real-path.git')
end
end
@@ -2035,15 +2032,12 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
it 'does not write it to disk' do
repository.set_full_path(full_path: "")
- config = File.read(File.join(repository_path, "config"))
-
- expect(config).to include("[gitlab]")
- expect(config).to include("fullpath = #{repository_path}")
+ expect(repository.full_path).to eq(repository_path)
end
end
context 'repository does not exist' do
- it 'raises NoRepository and does not call Gitaly WriteConfig' do
+ it 'raises NoRepository and does not call SetFullPath' do
repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '', 'group/project')
expect(repository.gitaly_repository_client).not_to receive(:set_full_path)
@@ -2055,6 +2049,18 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#full_path' do
+ let(:full_path) { 'some/path' }
+
+ before do
+ repository.set_full_path(full_path: full_path)
+ end
+
+ it 'returns the full path' do
+ expect(repository.full_path).to eq(full_path)
+ end
+ end
+
describe '#merge_to_ref' do
let(:repository) { mutable_repository }
let(:branch_head) { '6d394385cf567f80a8fd85055db1ab4c5295806f' }
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 31a5bc737ba..8577cad1011 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -153,11 +153,6 @@ RSpec.describe Gitlab::GitAccess, :aggregate_failures do
end
it 'logs' do
- allow(Gitlab::AppJsonLogger).to receive(:info).with(
- hash_including(
- "class" => "AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker"
- )
- )
expect(Gitlab::AppJsonLogger).to receive(:info).with(
message: 'Actor was :ci',
project_id: project.id
@@ -751,11 +746,6 @@ RSpec.describe Gitlab::GitAccess, :aggregate_failures do
it 'logs' do
expect(Gitlab::AppJsonLogger).to receive(:info).with(
- hash_including(
- "class" => "AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker"
- )
- ).once
- expect(Gitlab::AppJsonLogger).to receive(:info).with(
message: 'Actor was :ci',
project_id: project.id
).once
diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
index 39de9a65390..2baa1573676 100644
--- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb
@@ -351,4 +351,16 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
client.set_full_path(path)
end
end
+
+ describe '#full_path' do
+ let(:path) { 'repo/path' }
+
+ it 'sends a full_path message' do
+ expect_any_instance_of(Gitaly::RepositoryService::Stub)
+ .to receive(:full_path)
+ .and_return(double(path: path))
+
+ expect(client.full_path).to eq(path)
+ end
+ end
end
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index c076346c619..c6266f15340 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -166,11 +166,12 @@ RSpec.describe GroupMember do
let_it_be(:project_c) { create(:project, group: group) }
let_it_be(:user) { create(:user) }
- shared_examples_for 'calls UserProjectAccessChangedService to recalculate authorizations' do
- it 'calls UserProjectAccessChangedService to recalculate authorizations' do
- expect_next_instance_of(UserProjectAccessChangedService, user.id) do |service|
- expect(service).to receive(:execute).with(blocking: blocking)
- end
+ shared_examples_for 'calls AuthorizedProjectsWorker inline to recalculate authorizations' do
+ # this is inline with the overridden behaviour in stubbed_member.rb
+ it 'calls AuthorizedProjectsWorker inline to recalculate authorizations' do
+ worker_instance = AuthorizedProjectsWorker.new
+ expect(AuthorizedProjectsWorker).to receive(:new).and_return(worker_instance)
+ expect(worker_instance).to receive(:perform).with(user.id)
action
end
@@ -178,15 +179,14 @@ RSpec.describe GroupMember do
context 'on create' do
let(:action) { group.add_member(user, Gitlab::Access::GUEST) }
- let(:blocking) { true }
- it 'changes access level', :sidekiq_inline do
+ it 'changes access level' do
expect { action }.to change { user.can?(:guest_access, project_a) }.from(false).to(true)
.and change { user.can?(:guest_access, project_b) }.from(false).to(true)
.and change { user.can?(:guest_access, project_c) }.from(false).to(true)
end
- it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
+ it_behaves_like 'calls AuthorizedProjectsWorker inline to recalculate authorizations'
end
context 'on update' do
@@ -195,15 +195,14 @@ RSpec.describe GroupMember do
end
let(:action) { group.members.find_by(user: user).update!(access_level: Gitlab::Access::DEVELOPER) }
- let(:blocking) { true }
- it 'changes access level', :sidekiq_inline do
+ it 'changes access level' do
expect { action }.to change { user.can?(:developer_access, project_a) }.from(false).to(true)
.and change { user.can?(:developer_access, project_b) }.from(false).to(true)
.and change { user.can?(:developer_access, project_c) }.from(false).to(true)
end
- it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
+ it_behaves_like 'calls AuthorizedProjectsWorker inline to recalculate authorizations'
end
context 'on destroy' do
@@ -212,7 +211,6 @@ RSpec.describe GroupMember do
end
let(:action) { group.members.find_by(user: user).destroy! }
- let(:blocking) { false }
it 'changes access level', :sidekiq_inline do
expect { action }.to change { user.can?(:guest_access, project_a) }.from(true).to(false)
@@ -220,7 +218,11 @@ RSpec.describe GroupMember do
.and change { user.can?(:guest_access, project_c) }.from(true).to(false)
end
- it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
+ it 'schedules an AuthorizedProjectsWorker job to recalculate authorizations' do
+ expect(AuthorizedProjectsWorker).to receive(:bulk_perform_async).with([[user.id]])
+
+ action
+ end
end
end
end
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 39d9d25a98c..99fc5dc14df 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -213,10 +213,11 @@ RSpec.describe ProjectMember do
let_it_be(:user) { create(:user) }
shared_examples_for 'calls AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker inline to recalculate authorizations' do
- it 'calls AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker' do
- expect(AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker).to receive(:bulk_perform_and_wait).with(
- [[project.id, user.id]]
- )
+ # this is inline with the overridden behaviour in stubbed_member.rb
+ it 'calls AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker inline' do
+ worker_instance = AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker.new
+ expect(AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker).to receive(:new).and_return(worker_instance)
+ expect(worker_instance).to receive(:perform).with(project.id, user.id)
action
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 106e6a9faf0..695cd98fa29 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -4,7 +4,6 @@ require 'spec_helper'
RSpec.describe Namespace do
include ProjectForksHelper
- include GitHelpers
include ReloadHelpers
let_it_be(:group_sti_name) { Group.sti_name }
@@ -1076,9 +1075,9 @@ RSpec.describe Namespace do
it 'updates project full path in .git/config' do
parent.update!(path: 'mygroup_new')
- expect(project_rugged(project_in_parent_group).config['gitlab.fullpath']).to eq "mygroup_new/#{project_in_parent_group.path}"
- expect(project_rugged(hashed_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{hashed_project_in_subgroup.path}"
- expect(project_rugged(legacy_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{legacy_project_in_subgroup.path}"
+ expect(project_in_parent_group.reload.repository.full_path).to eq "mygroup_new/#{project_in_parent_group.path}"
+ expect(hashed_project_in_subgroup.reload.repository.full_path).to eq "mygroup_new/mysubgroup/#{hashed_project_in_subgroup.path}"
+ expect(legacy_project_in_subgroup.reload.repository.full_path).to eq "mygroup_new/mysubgroup/#{legacy_project_in_subgroup.path}"
end
it 'updates the project storage location' do
@@ -1092,14 +1091,6 @@ RSpec.describe Namespace do
expect(repository_hashed_project_in_subgroup.reload.disk_path).to eq hashed_project_in_subgroup.disk_path
expect(repository_legacy_project_in_subgroup.reload.disk_path).to eq "mygroup_moved/mysubgroup/#{legacy_project_in_subgroup.path}"
end
-
- def project_rugged(project)
- # Routes are loaded when creating the projects, so we need to manually
- # reload them for the below code to be aware of the above UPDATE.
- project.route.reload
-
- rugged_repo(project.repository)
- end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index cbaf3d0b3d5..b434b2c0332 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -4,7 +4,6 @@ require 'spec_helper'
RSpec.describe Project, factory_default: :keep do
include ProjectForksHelper
- include GitHelpers
include ExternalAuthorizationServiceHelpers
include ReloadHelpers
include StubGitlabCalls
@@ -5741,16 +5740,18 @@ RSpec.describe Project, factory_default: :keep do
describe '#set_full_path' do
let_it_be(:project) { create(:project, :repository) }
+ let(:repository) { project.repository.raw }
+
it 'writes full path in .git/config when key is missing' do
project.set_full_path
- expect(rugged_config['gitlab.fullpath']).to eq project.full_path
+ expect(repository.full_path).to eq project.full_path
end
it 'updates full path in .git/config when key is present' do
project.set_full_path(gl_full_path: 'old/path')
- expect { project.set_full_path }.to change { rugged_config['gitlab.fullpath'] }.from('old/path').to(project.full_path)
+ expect { project.set_full_path }.to change { repository.full_path }.from('old/path').to(project.full_path)
end
it 'does not raise an error with an empty repository' do
@@ -8436,10 +8437,6 @@ RSpec.describe Project, factory_default: :keep do
export_job.finish
end
- def rugged_config
- rugged_repo(project.repository).config
- end
-
def create_pipeline(project, status = 'success')
create(:ci_pipeline, project: project,
sha: project.commit.sha,
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index 51351c9fdd1..62f50dc2874 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe RemoteMirror, :mailer do
- include GitHelpers
-
before do
stub_feature_flags(remote_mirror_no_delay: false)
end
@@ -96,16 +94,6 @@ RSpec.describe RemoteMirror, :mailer do
expect(mirror.url).to eq('http://foo:bar@test.com')
expect(mirror.credentials).to eq({ user: 'foo', password: 'bar' })
end
-
- it 'does not update the repository config if credentials changed' do
- mirror = create_mirror(url: 'http://foo:bar@test.com')
- repo = mirror.project.repository
- old_config = rugged_repo(repo).config
-
- mirror.update_attribute(:url, 'http://foo:baz@test.com')
-
- expect(rugged_repo(repo).config.to_hash).to eq(old_config.to_hash)
- end
end
end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 3ef859376a4..c513baea517 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -4,7 +4,6 @@ require 'spec_helper'
RSpec.describe GroupPolicy do
include_context 'GroupPolicy context'
- using RSpec::Parameterized::TableSyntax
context 'public group with no user' do
let(:group) { create(:group, :public, :crm_enabled) }
@@ -1230,30 +1229,4 @@ RSpec.describe GroupPolicy do
it { is_expected.to be_disallowed(:admin_crm_contact) }
it { is_expected.to be_disallowed(:admin_crm_organization) }
end
-
- describe 'maintain_namespace' do
- context 'with non-admin roles' do
- where(:role, :allowed) do
- :guest | false
- :reporter | false
- :developer | false
- :maintainer | true
- :owner | true
- end
-
- with_them do
- let(:current_user) { public_send(role) }
-
- it do
- expect(subject.allowed?(:maintain_namespace)).to eq allowed
- end
- end
- end
-
- context 'as an admin', :enable_admin_mode do
- let(:current_user) { admin }
-
- it { is_expected.to be_allowed(:maintain_namespace) }
- end
- end
end
diff --git a/spec/policies/namespaces/user_namespace_policy_spec.rb b/spec/policies/namespaces/user_namespace_policy_spec.rb
index e8a3c9b828d..22c3f6a6d67 100644
--- a/spec/policies/namespaces/user_namespace_policy_spec.rb
+++ b/spec/policies/namespaces/user_namespace_policy_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe Namespaces::UserNamespacePolicy do
let_it_be(:admin) { create(:admin) }
let_it_be(:namespace) { create(:user_namespace, owner: owner) }
- let(:owner_permissions) { [:owner_access, :create_projects, :admin_namespace, :read_namespace, :read_statistics, :transfer_projects, :admin_package, :maintain_namespace] }
+ let(:owner_permissions) { [:owner_access, :create_projects, :admin_namespace, :read_namespace, :read_statistics, :transfer_projects, :admin_package] }
subject { described_class.new(current_user, namespace) }
diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb
index b6cb790bb71..260f7cbc226 100644
--- a/spec/requests/api/api_spec.rb
+++ b/spec/requests/api/api_spec.rb
@@ -262,4 +262,54 @@ RSpec.describe API::API do
end
end
end
+
+ describe 'content security policy header' do
+ let_it_be(:user) { create(:user) }
+
+ let(:csp) { nil }
+ let(:report_only) { false }
+
+ subject { get api("/users/#{user.id}", user) }
+
+ before do
+ allow(Rails.application.config).to receive(:content_security_policy).and_return(csp)
+ allow(Rails.application.config).to receive(:content_security_policy_report_only).and_return(report_only)
+ end
+
+ context 'when CSP is not configured globally' do
+ it 'does not set the CSP header' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['Content-Security-Policy']).to be_nil
+ end
+ end
+
+ context 'when CSP is configured globally' do
+ let(:csp) do
+ ActionDispatch::ContentSecurityPolicy.new do |p|
+ p.default_src :self
+ end
+ end
+
+ it 'sets a stricter CSP header' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['Content-Security-Policy']).to eq("default-src 'none'")
+ end
+
+ context 'when report_only is true' do
+ let(:report_only) { true }
+
+ it 'does not set any CSP header' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['Content-Security-Policy']).to be_nil
+ expect(response.headers['Content-Security-Policy-Report-Only']).to be_nil
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb
index cb351635081..a795b49c44e 100644
--- a/spec/requests/api/invitations_spec.rb
+++ b/spec/requests/api/invitations_spec.rb
@@ -447,7 +447,7 @@ RSpec.describe API::Invitations do
emails = 'email3@example.com,email4@example.com,email5@example.com,email6@example.com,email7@example.com'
- unresolved_n_plus_ones = 32 # currently there are 8 queries added per email
+ unresolved_n_plus_ones = 36 # currently there are 9 queries added per email
expect do
post invitations_url(group, maintainer), params: { email: emails, access_level: Member::DEVELOPER }
diff --git a/spec/services/members/groups/creator_service_spec.rb b/spec/services/members/groups/creator_service_spec.rb
index 4130fbd44fa..fced7195046 100644
--- a/spec/services/members/groups/creator_service_spec.rb
+++ b/spec/services/members/groups/creator_service_spec.rb
@@ -27,7 +27,10 @@ RSpec.describe Members::Groups::CreatorService do
context 'authorized projects update' do
it 'schedules a single project authorization update job when called multiple times' do
- expect(AuthorizedProjectsWorker).to receive(:bulk_perform_and_wait).once
+ # this is inline with the overridden behaviour in stubbed_member.rb
+ worker_instance = AuthorizedProjectsWorker.new
+ expect(AuthorizedProjectsWorker).to receive(:new).once.and_return(worker_instance)
+ expect(worker_instance).to receive(:perform).with(user.id)
1.upto(3) do
described_class.add_member(source, user, :maintainer)
diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb
index 9dc15131bc5..edf4bbe0f7f 100644
--- a/spec/services/projects/after_rename_service_spec.rb
+++ b/spec/services/projects/after_rename_service_spec.rb
@@ -3,7 +3,6 @@
require 'spec_helper'
RSpec.describe Projects::AfterRenameService do
- let(:rugged_config) { rugged_repo(project.repository).config }
let(:legacy_storage) { Storage::LegacyProject.new(project) }
let(:hashed_storage) { Storage::Hashed.new(project) }
let!(:path_before_rename) { project.path }
@@ -71,10 +70,10 @@ RSpec.describe Projects::AfterRenameService do
end
end
- it 'updates project full path in .git/config' do
+ it 'updates project full path in gitaly' do
service_execute
- expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
+ expect(project.repository.full_path).to eq(project.full_path)
end
it 'updates storage location' do
@@ -173,10 +172,10 @@ RSpec.describe Projects::AfterRenameService do
end
end
- it 'updates project full path in .git/config' do
+ it 'updates project full path in gitaly' do
service_execute
- expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
+ expect(project.repository.full_path).to eq(project.full_path)
end
it 'updates storage location' do
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 59dee209ff9..9486f69fa01 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -4,7 +4,6 @@ require 'spec_helper'
RSpec.describe Projects::CreateService, '#execute' do
include ExternalAuthorizationServiceHelpers
- include GitHelpers
let(:user) { create :user }
let(:project_name) { 'GitLab' }
@@ -769,11 +768,10 @@ RSpec.describe Projects::CreateService, '#execute' do
create_project(user, opts)
end
- it 'writes project full path to .git/config' do
+ it 'writes project full path to gitaly' do
project = create_project(user, opts)
- rugged = rugged_repo(project.repository)
- expect(rugged.config['gitlab.fullpath']).to eq project.full_path
+ expect(project.repository.full_path).to eq project.full_path
end
it 'triggers PostCreationWorker' do
diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
index d0064873972..65da1976dc2 100644
--- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb
@@ -68,12 +68,10 @@ RSpec.describe Projects::HashedStorage::MigrateRepositoryService do
service.execute
end
- it 'writes project full path to .git/config' do
+ it 'writes project full path to gitaly' do
service.execute
- rugged_config = rugged_repo(project.repository).config['gitlab.fullpath']
-
- expect(rugged_config).to eq project.full_path
+ expect(project.repository.full_path).to eq project.full_path
end
end
diff --git a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
index 23e776b72bc..385c03e6308 100644
--- a/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
+++ b/spec/services/projects/hashed_storage/rollback_repository_service_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab_redis_shared_state do
- include GitHelpers
-
let(:gitlab_shell) { Gitlab::Shell.new }
let(:project) { create(:project, :repository, :wiki_repo, :design_repo, storage_version: ::Project::HASHED_STORAGE_FEATURES[:repository]) }
let(:legacy_storage) { Storage::LegacyProject.new(project) }
@@ -68,12 +66,10 @@ RSpec.describe Projects::HashedStorage::RollbackRepositoryService, :clean_gitlab
service.execute
end
- it 'writes project full path to .git/config' do
+ it 'writes project full path to gitaly' do
service.execute
- rugged_config = rugged_repo(project.repository).config['gitlab.fullpath']
-
- expect(rugged_config).to eq project.full_path
+ expect(project.repository.full_path).to eq project.full_path
end
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 1edc12bbe37..8f505c31c5a 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe Projects::TransferService do
- include GitHelpers
-
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
let_it_be(:group_integration) { create(:integrations_slack, :group, group: group, webhook: 'http://group.slack.com') }
@@ -202,10 +200,10 @@ RSpec.describe Projects::TransferService do
expect(project.disk_path).to start_with(group.path)
end
- it 'updates project full path in .git/config' do
+ it 'updates project full path in gitaly' do
execute_transfer
- expect(rugged_config['gitlab.fullpath']).to eq "#{group.full_path}/#{project.path}"
+ expect(project.repository.full_path).to eq "#{group.full_path}/#{project.path}"
end
it 'updates storage location' do
@@ -296,10 +294,10 @@ RSpec.describe Projects::TransferService do
expect(original_path).to eq current_path
end
- it 'rolls back project full path in .git/config' do
+ it 'rolls back project full path in gitaly' do
attempt_project_transfer
- expect(rugged_config['gitlab.fullpath']).to eq project.full_path
+ expect(project.repository.full_path).to eq project.full_path
end
it "doesn't send move notifications" do
@@ -770,10 +768,6 @@ RSpec.describe Projects::TransferService do
end
end
- def rugged_config
- rugged_repo(project.repository).config
- end
-
def project_namespace_in_sync(group)
project.reload
expect(project.namespace).to eq(group)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 47cd78873f8..921d6503099 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -208,6 +208,7 @@ RSpec.configure do |config|
include StubFeatureFlags
include StubSnowplow
+ include StubMember
if ENV['CI'] || ENV['RETRIES']
# This includes the first try, i.e. tests will be run 4 times before failing.
diff --git a/spec/support/helpers/stub_member.rb b/spec/support/helpers/stub_member.rb
new file mode 100644
index 00000000000..bcd0b675041
--- /dev/null
+++ b/spec/support/helpers/stub_member.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module StubMember
+ def self.included(base)
+ Member.prepend(StubbedMember::Member)
+ ProjectMember.prepend(StubbedMember::ProjectMember)
+ end
+end
diff --git a/spec/support/helpers/stubbed_member.rb b/spec/support/helpers/stubbed_member.rb
new file mode 100644
index 00000000000..27420c9b709
--- /dev/null
+++ b/spec/support/helpers/stubbed_member.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+# Extend the ProjectMember & GroupMember class with the ability to
+# to run project_authorizations refresh jobs inline.
+
+# This is needed so that calls like `group.add_member(user, access_level)` or `create(:project_member)`
+# in the specs can be run without including `:sidekiq_inline` trait.
+module StubbedMember
+ extend ActiveSupport::Concern
+
+ module Member
+ private
+
+ def refresh_member_authorized_projects(blocking:)
+ return super unless blocking
+
+ AuthorizedProjectsWorker.new.perform(user_id)
+ end
+ end
+
+ module ProjectMember
+ private
+
+ def blocking_project_authorizations_refresh
+ AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker.new.perform(project.id, user.id)
+ end
+ end
+end
diff --git a/spec/support/matchers/event_store.rb b/spec/support/matchers/event_store.rb
index 14f6a42d7f4..4ecb924b3ed 100644
--- a/spec/support/matchers/event_store.rb
+++ b/spec/support/matchers/event_store.rb
@@ -23,8 +23,8 @@ RSpec::Matchers.define :publish_event do |expected_event_class|
def match_data?(actual, expected)
values_match?(actual.keys, expected.keys) &&
- actual.keys.each do |key|
- values_match?(actual[key], expected[key])
+ actual.keys.all? do |key|
+ values_match?(expected[key], actual[key])
end
end
diff --git a/spec/tooling/danger/customer_success_spec.rb b/spec/tooling/danger/customer_success_spec.rb
new file mode 100644
index 00000000000..798905212f1
--- /dev/null
+++ b/spec/tooling/danger/customer_success_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+require 'rspec-parameterized'
+require 'gitlab-dangerfiles'
+require 'gitlab/dangerfiles/spec_helper'
+require_relative '../../../tooling/danger/customer_success'
+
+RSpec.describe Tooling::Danger::CustomerSuccess do
+ include_context "with dangerfile"
+
+ let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) }
+ let(:customer_success) { fake_danger.new(helper: fake_helper) }
+
+ describe 'customer success danger' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ 'with data category changes to Ops and no Customer Success::Impact Check label' => {
+ modified_files: %w(config/metrics/20210216182127_user_secret_detection_jobs.yml app/models/user.rb),
+ changed_lines: ['-data_category: cat1', '+data_category: operational'],
+ customer_labeled: false,
+ impacted: true,
+ impacted_files: %w(config/metrics/20210216182127_user_secret_detection_jobs.yml)
+ },
+ 'with data category changes and Customer Success::Impact Check label' => {
+ modified_files: %w(config/metrics/20210216182127_user_secret_detection_jobs.yml),
+ changed_lines: ['-data_category: cat1', '+data_category: operational'],
+ customer_labeled: true,
+ impacted: false,
+ impacted_files: %w(config/metrics/20210216182127_user_secret_detection_jobs.yml)
+ },
+ 'with metric file changes and no data category changes' => {
+ modified_files: %w(config/metrics/20210216182127_user_secret_detection_jobs.yml),
+ changed_lines: ['-product_stage: growth'],
+ customer_labeled: false,
+ impacted: false,
+ impacted_files: []
+ },
+ 'with data category changes from Ops' => {
+ modified_files: %w(config/metrics/20210216182127_user_secret_detection_jobs.yml app/models/user.rb),
+ changed_lines: ['-data_category: operational', '+data_category: cat2'],
+ customer_labeled: false,
+ impacted: true,
+ impacted_files: %w(config/metrics/20210216182127_user_secret_detection_jobs.yml)
+ },
+ 'with data category removed' => {
+ modified_files: %w(config/metrics/20210216182127_user_secret_detection_jobs.yml app/models/user.rb),
+ changed_lines: ['-data_category: operational'],
+ customer_labeled: false,
+ impacted: true,
+ impacted_files: %w(config/metrics/20210216182127_user_secret_detection_jobs.yml)
+ },
+ 'with data category added' => {
+ modified_files: %w(config/metrics/20210216182127_user_secret_detection_jobs.yml app/models/user.rb),
+ changed_lines: ['+data_category: operational'],
+ customer_labeled: false,
+ impacted: true,
+ impacted_files: %w(config/metrics/20210216182127_user_secret_detection_jobs.yml)
+ },
+ 'with data category in uppercase' => {
+ modified_files: %w(config/metrics/20210216182127_user_secret_detection_jobs.yml app/models/user.rb),
+ changed_lines: ['+data_category: Operational'],
+ customer_labeled: false,
+ impacted: true,
+ impacted_files: %w(config/metrics/20210216182127_user_secret_detection_jobs.yml)
+ }
+ }
+ end
+
+ with_them do
+ before do
+ allow(fake_helper).to receive(:modified_files).and_return(modified_files)
+ allow(fake_helper).to receive(:changed_lines).and_return(changed_lines)
+ allow(fake_helper).to receive(:has_scoped_label_with_scope?).and_return(customer_labeled)
+ allow(fake_helper).to receive(:markdown_list).with(impacted_files)
+ .and_return(impacted_files.map { |item| "* `#{item}`" }.join("\n"))
+ end
+
+ it 'generates correct message' do
+ expect(customer_success.build_message).to match_expected_message
+ end
+ end
+ end
+
+ def match_expected_message
+ return be_nil unless impacted
+
+ start_with(described_class::CHANGED_SCHEMA_MESSAGE).and(include(*impacted_files))
+ end
+end
diff --git a/spec/workers/concerns/waitable_worker_spec.rb b/spec/workers/concerns/waitable_worker_spec.rb
index f6d4cc4679d..bf156c3b8cb 100644
--- a/spec/workers/concerns/waitable_worker_spec.rb
+++ b/spec/workers/concerns/waitable_worker_spec.rb
@@ -30,19 +30,33 @@ RSpec.describe WaitableWorker do
describe '.bulk_perform_and_wait' do
context '1 job' do
- it 'inlines the job' do
- args_list = [[1]]
- expect(worker).to receive(:bulk_perform_inline).with(args_list).and_call_original
- expect(Gitlab::AppJsonLogger).to(
- receive(:info).with(a_hash_including('message' => 'running inline',
- 'class' => 'Gitlab::Foo::Bar::DummyWorker',
- 'job_status' => 'running',
- 'queue' => 'foo_bar_dummy'))
- .once)
-
- worker.bulk_perform_and_wait(args_list)
-
- expect(worker.counter).to eq(1)
+ it 'runs the jobs asynchronously' do
+ arguments = [[1]]
+
+ expect(worker).to receive(:bulk_perform_async).with(arguments)
+
+ worker.bulk_perform_and_wait(arguments)
+ end
+
+ context 'when the feature flag `always_async_project_authorizations_refresh` is turned off' do
+ before do
+ stub_feature_flags(always_async_project_authorizations_refresh: false)
+ end
+
+ it 'inlines the job' do
+ args_list = [[1]]
+ expect(worker).to receive(:bulk_perform_inline).with(args_list).and_call_original
+ expect(Gitlab::AppJsonLogger).to(
+ receive(:info).with(a_hash_including('message' => 'running inline',
+ 'class' => 'Gitlab::Foo::Bar::DummyWorker',
+ 'job_status' => 'running',
+ 'queue' => 'foo_bar_dummy'))
+ .once)
+
+ worker.bulk_perform_and_wait(args_list)
+
+ expect(worker.counter).to eq(1)
+ end
end
end