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-06-07 18:08:12 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-06-07 18:08:12 +0300
commit7bbc731c75d0b8bf7c74ba77d521266d2ed0a1fc (patch)
tree4cab2383639b839613ffc4ef457e2a594f61aaa3 /spec
parentedb317e9fe43c62229805fae529c550467ee5dc5 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb2
-rw-r--r--spec/frontend/access_tokens/components/new_access_token_app_spec.js25
-rw-r--r--spec/frontend/runner/components/registration/registration_dropdown_spec.js11
-rw-r--r--spec/frontend/terraform/components/states_table_spec.js40
-rw-r--r--spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js85
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js244
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js24
-rw-r--r--spec/graphql/types/packages/cleanup/keep_duplicated_package_files_enum_spec.rb15
-rw-r--r--spec/graphql/types/packages/cleanup/policy_type_spec.rb26
-rw-r--r--spec/graphql/types/project_type_spec.rb8
-rw-r--r--spec/graphql/types/terraform/state_type_spec.rb3
-rw-r--r--spec/lib/gitlab/tracking/standard_context_spec.rb28
-rw-r--r--spec/models/packages/cleanup/policy_spec.rb2
-rw-r--r--spec/policies/project_policy_spec.rb30
-rw-r--r--spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb109
-rw-r--r--spec/requests/api/graphql/project/packages_cleanup_policy_spec.rb79
-rw-r--r--spec/services/packages/cleanup/update_policy_service_spec.rb105
-rw-r--r--spec/support/helpers/next_instance_of.rb2
-rw-r--r--spec/support/shared_examples/features/runners_shared_examples.rb4
-rw-r--r--spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb2
20 files changed, 651 insertions, 193 deletions
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index 01e2571ff3e..247a5d3cd6c 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do
end
def created_personal_access_token
- find("[data-testid='new-access-token'] input").value
+ find_field('new-access-token').value
end
def feed_token_description
diff --git a/spec/frontend/access_tokens/components/new_access_token_app_spec.js b/spec/frontend/access_tokens/components/new_access_token_app_spec.js
index b750a955fb2..25b3eba6587 100644
--- a/spec/frontend/access_tokens/components/new_access_token_app_spec.js
+++ b/spec/frontend/access_tokens/components/new_access_token_app_spec.js
@@ -1,7 +1,7 @@
import { GlAlert } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import NewAccessTokenApp from '~/access_tokens/components/new_access_token_app.vue';
import { createAlert, VARIANT_INFO } from '~/flash';
import { __, sprintf } from '~/locale';
@@ -16,7 +16,7 @@ describe('~/access_tokens/components/new_access_token_app', () => {
const accessTokenType = 'personal access token';
const createComponent = (provide = { accessTokenType }) => {
- wrapper = shallowMount(NewAccessTokenApp, {
+ wrapper = mountExtended(NewAccessTokenApp, {
provide,
});
};
@@ -64,17 +64,26 @@ describe('~/access_tokens/components/new_access_token_app', () => {
sprintf(__('Copy %{accessTokenType}'), { accessTokenType }),
);
expect(InputCopyToggleVisibilityComponent.props('initialVisibility')).toBe(true);
- expect(InputCopyToggleVisibilityComponent.props('inputClass')).toBe(
- 'qa-created-access-token',
- );
- expect(InputCopyToggleVisibilityComponent.props('qaSelector')).toBe(
- 'created_access_token_field',
- );
expect(InputCopyToggleVisibilityComponent.attributes('label')).toBe(
sprintf(__('Your new %{accessTokenType}'), { accessTokenType }),
);
});
+ it('input field should contain QA-related selectors', async () => {
+ const newToken = '12345';
+ await triggerSuccess(newToken);
+
+ expect(wrapper.findComponent(GlAlert).exists()).toBe(false);
+
+ const inputAttributes = wrapper
+ .findByLabelText(sprintf(__('Your new %{accessTokenType}'), { accessTokenType }))
+ .attributes();
+ expect(inputAttributes).toMatchObject({
+ class: expect.stringContaining('qa-created-access-token'),
+ 'data-qa-selector': 'created_access_token_field',
+ });
+ });
+
it('should render an info alert', async () => {
await triggerSuccess();
diff --git a/spec/frontend/runner/components/registration/registration_dropdown_spec.js b/spec/frontend/runner/components/registration/registration_dropdown_spec.js
index 81c2788f084..e27963161d8 100644
--- a/spec/frontend/runner/components/registration/registration_dropdown_spec.js
+++ b/spec/frontend/runner/components/registration/registration_dropdown_spec.js
@@ -1,4 +1,4 @@
-import { GlDropdown, GlDropdownItem, GlDropdownForm } from '@gitlab/ui';
+import { GlModal, GlDropdown, GlDropdownItem, GlDropdownForm } from '@gitlab/ui';
import { mount, shallowMount, createWrapper } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
@@ -24,6 +24,8 @@ import {
const mockToken = '0123456789';
const maskToken = '**********';
+Vue.use(VueApollo);
+
describe('RegistrationDropdown', () => {
let wrapper;
@@ -32,9 +34,10 @@ describe('RegistrationDropdown', () => {
const findRegistrationInstructionsDropdownItem = () => wrapper.findComponent(GlDropdownItem);
const findTokenDropdownItem = () => wrapper.findComponent(GlDropdownForm);
const findRegistrationToken = () => wrapper.findComponent(RegistrationToken);
- const findRegistrationTokenInput = () => wrapper.findByTestId('token-value').find('input');
+ const findRegistrationTokenInput = () => wrapper.find('[name=token-value]');
const findTokenResetDropdownItem = () =>
wrapper.findComponent(RegistrationTokenResetDropdownItem);
+ const findModal = () => wrapper.findComponent(GlModal);
const findModalContent = () =>
createWrapper(document.body)
.find('[data-testid="runner-instructions-modal"]')
@@ -43,6 +46,8 @@ describe('RegistrationDropdown', () => {
const openModal = async () => {
await findRegistrationInstructionsDropdownItem().trigger('click');
+ findModal().vm.$emit('shown');
+
await waitForPromises();
};
@@ -60,8 +65,6 @@ describe('RegistrationDropdown', () => {
};
const createComponentWithModal = () => {
- Vue.use(VueApollo);
-
const requestHandlers = [
[getRunnerPlatformsQuery, jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms)],
[getRunnerSetupInstructionsQuery, jest.fn().mockResolvedValue(mockGraphqlInstructions)],
diff --git a/spec/frontend/terraform/components/states_table_spec.js b/spec/frontend/terraform/components/states_table_spec.js
index fa9c8320b4f..3821b7beb5f 100644
--- a/spec/frontend/terraform/components/states_table_spec.js
+++ b/spec/frontend/terraform/components/states_table_spec.js
@@ -2,6 +2,8 @@ import { GlBadge, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { useFakeDate } from 'helpers/fake_date';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import StatesTable from '~/terraform/components/states_table.vue';
import StateActions from '~/terraform/components/states_table_actions.vue';
@@ -104,11 +106,30 @@ describe('StatesTable', () => {
updatedAt: '2020-10-10T00:00:00Z',
latestVersion: null,
},
+ {
+ _showDetails: false,
+ errorMessages: [],
+ name: 'state-6',
+ loadingLock: false,
+ loadingRemove: false,
+ lockedAt: null,
+ lockedByUser: null,
+ updatedAt: '2020-10-10T00:00:00Z',
+ deletedAt: '2022-02-02T00:00:00Z',
+ latestVersion: null,
+ },
],
};
const createComponent = async (propsData = defaultProps) => {
- wrapper = mount(StatesTable, { propsData });
+ wrapper = extendedWrapper(
+ mount(StatesTable, {
+ propsData,
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ }),
+ );
await nextTick();
};
@@ -124,27 +145,28 @@ describe('StatesTable', () => {
});
it.each`
- name | toolTipText | locked | loading | lineNumber
+ name | toolTipText | hasBadge | loading | lineNumber
${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${false} | ${0}
${'state-2'} | ${'Locking state'} | ${false} | ${true} | ${1}
${'state-3'} | ${'Unlocking state'} | ${false} | ${true} | ${2}
${'state-4'} | ${'Locked by Unknown User 5 days ago'} | ${true} | ${false} | ${3}
${'state-5'} | ${'Removing'} | ${false} | ${true} | ${4}
+ ${'state-6'} | ${'Deletion in progress'} | ${true} | ${false} | ${5}
`(
'displays the name and locked information "$name" for line "$lineNumber"',
- ({ name, toolTipText, locked, loading, lineNumber }) => {
+ ({ name, toolTipText, hasBadge, loading, lineNumber }) => {
const states = wrapper.findAll('[data-testid="terraform-states-table-name"]');
-
const state = states.at(lineNumber);
- const toolTip = state.find(GlTooltip);
expect(state.text()).toContain(name);
- expect(state.find(GlBadge).exists()).toBe(locked);
+ expect(state.find(GlBadge).exists()).toBe(hasBadge);
expect(state.find(GlLoadingIcon).exists()).toBe(loading);
- expect(toolTip.exists()).toBe(locked);
- if (locked) {
- expect(toolTip.text()).toMatchInterpolatedText(toolTipText);
+ if (hasBadge) {
+ const badge = wrapper.findByTestId(`state-badge-${name}`);
+
+ expect(getBinding(badge.element, 'gl-tooltip')).toBeDefined();
+ expect(badge.attributes('title')).toMatchInterpolatedText(toolTipText);
}
},
);
diff --git a/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js b/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js
index f42061d5982..e1da8b690af 100644
--- a/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js
+++ b/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js
@@ -66,7 +66,7 @@ describe('InputCopyToggleVisibility', () => {
});
it('displays value as hidden', () => {
- expect(findFormInputGroup().props('value')).toBe('********************');
+ expect(findFormInput().element.value).toBe('********************');
});
it('saves actual value to clipboard when manually copied', () => {
@@ -107,7 +107,7 @@ describe('InputCopyToggleVisibility', () => {
});
it('displays value', () => {
- expect(findFormInputGroup().props('value')).toBe(valueProp);
+ expect(findFormInput().element.value).toBe(valueProp);
});
it('renders a hide button', () => {
@@ -159,25 +159,52 @@ describe('InputCopyToggleVisibility', () => {
});
it('displays value as hidden with 20 asterisks', () => {
- expect(findFormInputGroup().props('value')).toBe('********************');
+ expect(findFormInput().element.value).toBe('********************');
});
});
describe('when `initialVisibility` prop is `true`', () => {
+ const label = 'My label';
+
beforeEach(() => {
createComponent({
propsData: {
value: valueProp,
initialVisibility: true,
+ label,
+ 'label-for': 'my-input',
+ formInputGroupProps: {
+ id: 'my-input',
+ },
},
});
});
it('displays value', () => {
- expect(findFormInputGroup().props('value')).toBe(valueProp);
+ expect(findFormInput().element.value).toBe(valueProp);
});
itDoesNotModifyCopyEvent();
+
+ describe('when input is clicked', () => {
+ it('selects input value', async () => {
+ const mockSelect = jest.fn();
+ wrapper.vm.$refs.input.$el.select = mockSelect;
+ await wrapper.findByLabelText(label).trigger('click');
+
+ expect(mockSelect).toHaveBeenCalled();
+ });
+ });
+
+ describe('when label is clicked', () => {
+ it('selects input value', async () => {
+ const mockSelect = jest.fn();
+ wrapper.vm.$refs.input.$el.select = mockSelect;
+ await wrapper.find('label').trigger('click');
+
+ expect(mockSelect).toHaveBeenCalled();
+ });
+ });
});
describe('when `showToggleVisibilityButton` is `false`', () => {
@@ -196,7 +223,7 @@ describe('InputCopyToggleVisibility', () => {
});
it('displays value', () => {
- expect(findFormInputGroup().props('value')).toBe(valueProp);
+ expect(findFormInput().element.value).toBe(valueProp);
});
itDoesNotModifyCopyEvent();
@@ -216,16 +243,30 @@ describe('InputCopyToggleVisibility', () => {
});
});
- it('passes `formInputGroupProps` prop to `GlFormInputGroup`', () => {
+ it('passes `formInputGroupProps` prop only to the input', () => {
createComponent({
propsData: {
formInputGroupProps: {
- label: 'Foo bar',
+ name: 'Foo bar',
+ 'data-qa-selector': 'Foo bar',
+ class: 'Foo bar',
+ id: 'Foo bar',
},
},
});
- expect(findFormInputGroup().props('label')).toBe('Foo bar');
+ expect(findFormInput().attributes()).toMatchObject({
+ name: 'Foo bar',
+ 'data-qa-selector': 'Foo bar',
+ class: expect.stringContaining('Foo bar'),
+ id: 'Foo bar',
+ });
+
+ const attributesInputGroup = findFormInputGroup().attributes();
+ expect(attributesInputGroup.name).toBeUndefined();
+ expect(attributesInputGroup['data-qa-selector']).toBeUndefined();
+ expect(attributesInputGroup.class).not.toContain('Foo bar');
+ expect(attributesInputGroup.id).toBeUndefined();
});
it('passes `copyButtonTitle` prop to `ClipboardButton`', () => {
@@ -248,32 +289,4 @@ describe('InputCopyToggleVisibility', () => {
expect(wrapper.findByText(description).exists()).toBe(true);
});
-
- it('passes `inputClass` prop to `GlFormInputGroup`', () => {
- createComponent();
- expect(findFormInputGroup().props('inputClass')).toBe('gl-font-monospace! gl-cursor-default!');
- wrapper.destroy();
-
- createComponent({
- propsData: {
- inputClass: 'Foo bar',
- },
- });
- expect(findFormInputGroup().props('inputClass')).toBe(
- 'gl-font-monospace! gl-cursor-default! Foo bar',
- );
- });
-
- it('passes `qaSelector` prop as an `data-qa-selector` attribute to `GlFormInputGroup`', () => {
- createComponent();
- expect(findFormInputGroup().attributes('data-qa-selector')).toBeUndefined();
- wrapper.destroy();
-
- createComponent({
- propsData: {
- qaSelector: 'Foo bar',
- },
- });
- expect(findFormInputGroup().attributes('data-qa-selector')).toBe('Foo bar');
- });
});
diff --git a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js
index 639ca9e7559..7173abe1316 100644
--- a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js
+++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js
@@ -48,13 +48,12 @@ describe('RunnerInstructionsModal component', () => {
const findModal = () => wrapper.findComponent(GlModal);
const findPlatformButtonGroup = () => wrapper.findByTestId('platform-buttons');
const findPlatformButtons = () => findPlatformButtonGroup().findAllComponents(GlButton);
- const findOsxPlatformButton = () => wrapper.find({ ref: 'osx' });
const findArchitectureDropdownItems = () => wrapper.findAllByTestId('architecture-dropdown-item');
const findBinaryDownloadButton = () => wrapper.findByTestId('binary-download-button');
const findBinaryInstructions = () => wrapper.findByTestId('binary-instructions');
const findRegisterCommand = () => wrapper.findByTestId('register-command');
- const createComponent = ({ props, ...options } = {}) => {
+ const createComponent = ({ props, shown = true, ...options } = {}) => {
const requestHandlers = [
[getRunnerPlatformsQuery, runnerPlatformsHandler],
[getRunnerSetupInstructionsQuery, runnerSetupInstructionsHandler],
@@ -73,184 +72,202 @@ describe('RunnerInstructionsModal component', () => {
...options,
}),
);
+
+ // trigger open modal
+ if (shown) {
+ findModal().vm.$emit('shown');
+ }
};
beforeEach(async () => {
runnerPlatformsHandler = jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms);
runnerSetupInstructionsHandler = jest.fn().mockResolvedValue(mockGraphqlInstructions);
-
- createComponent();
- await waitForPromises();
});
afterEach(() => {
wrapper.destroy();
});
- it('should not show alert', () => {
- expect(findAlert().exists()).toBe(false);
- });
-
- it('should contain a number of platforms buttons', () => {
- expect(runnerPlatformsHandler).toHaveBeenCalledWith({});
+ describe('when the modal is shown', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
+ });
- const buttons = findPlatformButtons();
+ it('should not show alert', async () => {
+ expect(findAlert().exists()).toBe(false);
+ });
- expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length);
- });
+ it('should contain a number of platforms buttons', () => {
+ expect(runnerPlatformsHandler).toHaveBeenCalledWith({});
- it('should contain a number of dropdown items for the architecture options', () => {
- expect(findArchitectureDropdownItems()).toHaveLength(
- mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length,
- );
- });
+ const buttons = findPlatformButtons();
- describe('should display default instructions', () => {
- const { installInstructions, registerInstructions } = mockGraphqlInstructions.data.runnerSetup;
+ expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length);
+ });
- it('runner instructions are requested', () => {
- expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({
- platform: 'linux',
- architecture: 'amd64',
- });
+ it('should contain a number of dropdown items for the architecture options', () => {
+ expect(findArchitectureDropdownItems()).toHaveLength(
+ mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length,
+ );
});
- it('binary instructions are shown', async () => {
- await waitForPromises();
- const instructions = findBinaryInstructions().text();
+ describe('should display default instructions', () => {
+ const {
+ installInstructions,
+ registerInstructions,
+ } = mockGraphqlInstructions.data.runnerSetup;
- expect(instructions).toBe(installInstructions);
- });
+ it('runner instructions are requested', () => {
+ expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({
+ platform: 'linux',
+ architecture: 'amd64',
+ });
+ });
- it('register command is shown with a replaced token', async () => {
- await waitForPromises();
- const instructions = findRegisterCommand().text();
+ it('binary instructions are shown', async () => {
+ const instructions = findBinaryInstructions().text();
- expect(instructions).toBe(
- 'sudo gitlab-runner register --url http://gdk.test:3000/ --registration-token MY_TOKEN',
- );
- });
+ expect(instructions).toBe(installInstructions);
+ });
- describe('when a register token is not shown', () => {
- beforeEach(async () => {
- createComponent({ props: { registrationToken: undefined } });
- await waitForPromises();
+ it('register command is shown with a replaced token', async () => {
+ const command = findRegisterCommand().text();
+
+ expect(command).toBe(
+ 'sudo gitlab-runner register --url http://gdk.test:3000/ --registration-token MY_TOKEN',
+ );
});
- it('register command is shown without a defined registration token', () => {
- const instructions = findRegisterCommand().text();
+ describe('when a register token is not shown', () => {
+ beforeEach(async () => {
+ createComponent({ props: { registrationToken: undefined } });
+ await waitForPromises();
+ });
+
+ it('register command is shown without a defined registration token', () => {
+ const instructions = findRegisterCommand().text();
- expect(instructions).toBe(registerInstructions);
+ expect(instructions).toBe(registerInstructions);
+ });
});
- });
- describe('when the modal is shown', () => {
- it('sets the focus on the selected platform', () => {
- findPlatformButtons().at(0).element.focus = jest.fn();
+ describe('when providing a defaultPlatformName', () => {
+ beforeEach(async () => {
+ createComponent({ props: { defaultPlatformName: 'osx' } });
+ await waitForPromises();
+ });
+
+ it('runner instructions for the default selected platform are requested', () => {
+ expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({
+ platform: 'osx',
+ architecture: 'amd64',
+ });
+ });
+
+ it('sets the focus on the default selected platform', () => {
+ const findOsxPlatformButton = () => wrapper.find({ ref: 'osx' });
- findModal().vm.$emit('shown');
+ findOsxPlatformButton().element.focus = jest.fn();
- expect(findPlatformButtons().at(0).element.focus).toHaveBeenCalled();
+ findModal().vm.$emit('shown');
+
+ expect(findOsxPlatformButton().element.focus).toHaveBeenCalled();
+ });
});
});
- describe('when providing a defaultPlatformName', () => {
+ describe('after a platform and architecture are selected', () => {
+ const windowsIndex = 2;
+ const { installInstructions } = mockGraphqlInstructionsWindows.data.runnerSetup;
+
beforeEach(async () => {
- createComponent({ props: { defaultPlatformName: 'osx' } });
+ runnerSetupInstructionsHandler.mockResolvedValue(mockGraphqlInstructionsWindows);
+
+ findPlatformButtons().at(windowsIndex).vm.$emit('click');
await waitForPromises();
});
- it('runner instructions for the default selected platform are requested', () => {
- expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({
- platform: 'osx',
+ it('runner instructions are requested', () => {
+ expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({
+ platform: 'windows',
architecture: 'amd64',
});
});
- it('sets the focus on the default selected platform', () => {
- findOsxPlatformButton().element.focus = jest.fn();
+ it('architecture download link is updated', () => {
+ const architectures =
+ mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[windowsIndex].architectures.nodes;
- findModal().vm.$emit('shown');
-
- expect(findOsxPlatformButton().element.focus).toHaveBeenCalled();
+ expect(findBinaryDownloadButton().attributes('href')).toBe(
+ architectures[0].downloadLocation,
+ );
});
- });
- });
-
- describe('after a platform and architecture are selected', () => {
- const windowsIndex = 2;
- const { installInstructions } = mockGraphqlInstructionsWindows.data.runnerSetup;
- beforeEach(async () => {
- runnerSetupInstructionsHandler.mockResolvedValue(mockGraphqlInstructionsWindows);
+ it('other binary instructions are shown', () => {
+ const instructions = findBinaryInstructions().text();
- findPlatformButtons().at(windowsIndex).vm.$emit('click');
- await waitForPromises();
- });
-
- it('runner instructions are requested', () => {
- expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({
- platform: 'windows',
- architecture: 'amd64',
+ expect(instructions).toBe(installInstructions);
});
- });
- it('architecture download link is updated', () => {
- const architectures =
- mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[windowsIndex].architectures.nodes;
+ it('register command is shown', () => {
+ const command = findRegisterCommand().text();
- expect(findBinaryDownloadButton().attributes('href')).toBe(architectures[0].downloadLocation);
- });
+ expect(command).toBe(
+ './gitlab-runner.exe register --url http://gdk.test:3000/ --registration-token MY_TOKEN',
+ );
+ });
- it('other binary instructions are shown', () => {
- const instructions = findBinaryInstructions().text();
+ it('runner instructions are requested with another architecture', async () => {
+ findArchitectureDropdownItems().at(1).vm.$emit('click');
+ await waitForPromises();
- expect(instructions).toBe(installInstructions);
+ expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({
+ platform: 'windows',
+ architecture: '386',
+ });
+ });
});
- it('register command is shown', () => {
- const command = findRegisterCommand().text();
+ describe('when the modal resizes', () => {
+ it('to an xs viewport', async () => {
+ MockResizeObserver.mockResize('xs');
+ await nextTick();
- expect(command).toBe(
- './gitlab-runner.exe register --url http://gdk.test:3000/ --registration-token MY_TOKEN',
- );
- });
+ expect(findPlatformButtonGroup().attributes('vertical')).toBeTruthy();
+ });
- it('runner instructions are requested with another architecture', async () => {
- findArchitectureDropdownItems().at(1).vm.$emit('click');
- await waitForPromises();
+ it('to a non-xs viewport', async () => {
+ MockResizeObserver.mockResize('sm');
+ await nextTick();
- expect(runnerSetupInstructionsHandler).toHaveBeenLastCalledWith({
- platform: 'windows',
- architecture: '386',
+ expect(findPlatformButtonGroup().props('vertical')).toBeFalsy();
});
});
});
- describe('when the modal resizes', () => {
- it('to an xs viewport', async () => {
- MockResizeObserver.mockResize('xs');
- await nextTick();
-
- expect(findPlatformButtonGroup().attributes('vertical')).toBeTruthy();
+ describe('when the modal is not shown', () => {
+ beforeEach(async () => {
+ createComponent({ shown: false });
+ await waitForPromises();
});
- it('to a non-xs viewport', async () => {
- MockResizeObserver.mockResize('sm');
- await nextTick();
-
- expect(findPlatformButtonGroup().props('vertical')).toBeFalsy();
+ it('does not fetch instructions', () => {
+ expect(runnerPlatformsHandler).not.toHaveBeenCalled();
+ expect(runnerSetupInstructionsHandler).not.toHaveBeenCalled();
});
});
describe('when apollo is loading', () => {
- it('should show a skeleton loader', async () => {
+ beforeEach(() => {
createComponent();
+ });
+
+ it('should show a skeleton loader', async () => {
expect(findSkeletonLoader().exists()).toBe(true);
expect(findGlLoadingIcon().exists()).toBe(false);
- await nextTick();
- jest.runOnlyPendingTimers();
+ // wait on fetch of both `platforms` and `instructions`
await nextTick();
await nextTick();
@@ -258,7 +275,6 @@ describe('RunnerInstructionsModal component', () => {
});
it('once loaded, should not show a loading state', async () => {
- createComponent();
await waitForPromises();
expect(findSkeletonLoader().exists()).toBe(false);
@@ -271,7 +287,6 @@ describe('RunnerInstructionsModal component', () => {
runnerSetupInstructionsHandler.mockRejectedValue();
createComponent();
-
await waitForPromises();
});
@@ -303,6 +318,7 @@ describe('RunnerInstructionsModal component', () => {
mockShow = jest.fn();
createComponent({
+ shown: false,
stubs: {
GlModal: getGlModalStub({ show: mockShow }),
},
diff --git a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js
index 9a95a838291..986d76d2b95 100644
--- a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js
+++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js
@@ -1,6 +1,5 @@
-import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
@@ -11,7 +10,11 @@ describe('RunnerInstructions component', () => {
const findModal = () => wrapper.findComponent(RunnerInstructionsModal);
const createComponent = () => {
- wrapper = extendedWrapper(shallowMount(RunnerInstructions));
+ wrapper = shallowMountExtended(RunnerInstructions, {
+ directives: {
+ GlModal: createMockDirective(),
+ },
+ });
};
beforeEach(() => {
@@ -23,19 +26,12 @@ describe('RunnerInstructions component', () => {
});
it('should show the "Show runner installation instructions" button', () => {
- expect(findModalButton().exists()).toBe(true);
expect(findModalButton().text()).toBe('Show runner installation instructions');
});
- it('should not render the modal once mounted', () => {
- expect(findModal().exists()).toBe(false);
- });
-
- it('should render the modal once clicked', async () => {
- findModalButton().vm.$emit('click');
-
- await nextTick();
+ it('should render the modal', () => {
+ const modalId = getBinding(findModal().element, 'gl-modal');
- expect(findModal().exists()).toBe(true);
+ expect(findModalButton().attributes('modal-id')).toBe(modalId);
});
});
diff --git a/spec/graphql/types/packages/cleanup/keep_duplicated_package_files_enum_spec.rb b/spec/graphql/types/packages/cleanup/keep_duplicated_package_files_enum_spec.rb
new file mode 100644
index 00000000000..d7f24a9edfd
--- /dev/null
+++ b/spec/graphql/types/packages/cleanup/keep_duplicated_package_files_enum_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['PackagesCleanupKeepDuplicatedPackageFilesEnum'] do
+ it 'exposes all options' do
+ expect(described_class.values.keys)
+ .to contain_exactly(*Types::Packages::Cleanup::KeepDuplicatedPackageFilesEnum::OPTIONS_MAPPING.values)
+ end
+
+ it 'uses all possible options from model' do
+ all_options = Packages::Cleanup::Policy::KEEP_N_DUPLICATED_PACKAGE_FILES_VALUES
+ expect(described_class::OPTIONS_MAPPING.keys).to contain_exactly(*all_options)
+ end
+end
diff --git a/spec/graphql/types/packages/cleanup/policy_type_spec.rb b/spec/graphql/types/packages/cleanup/policy_type_spec.rb
new file mode 100644
index 00000000000..f48651ed832
--- /dev/null
+++ b/spec/graphql/types/packages/cleanup/policy_type_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['PackagesCleanupPolicy'] do
+ specify { expect(described_class.graphql_name).to eq('PackagesCleanupPolicy') }
+
+ specify do
+ expect(described_class.description)
+ .to eq('A packages cleanup policy designed to keep only packages and packages assets that matter most')
+ end
+
+ specify { expect(described_class).to require_graphql_authorizations(:admin_package) }
+
+ describe 'keep_n_duplicated_package_files' do
+ subject { described_class.fields['keepNDuplicatedPackageFiles'] }
+
+ it { is_expected.to have_non_null_graphql_type(Types::Packages::Cleanup::KeepDuplicatedPackageFilesEnum) }
+ end
+
+ describe 'next_run_at' do
+ subject { described_class.fields['nextRunAt'] }
+
+ it { is_expected.to have_nullable_graphql_type(Types::TimeType) }
+ end
+end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 23deef73734..2e994bf7820 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe GitlabSchema.types['Project'] do
pipeline_analytics squash_read_only sast_ci_configuration
cluster_agent cluster_agents agent_configurations
ci_template timelogs merge_commit_template squash_commit_template work_item_types
- recent_issue_boards ci_config_path_or_default
+ recent_issue_boards ci_config_path_or_default packages_cleanup_policy
]
expect(described_class).to include_graphql_fields(*expected_fields)
@@ -421,6 +421,12 @@ RSpec.describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_type(Types::ContainerExpirationPolicyType) }
end
+ describe 'packages cleanup policy field' do
+ subject { described_class.fields['packagesCleanupPolicy'] }
+
+ it { is_expected.to have_graphql_type(Types::Packages::Cleanup::PolicyType) }
+ end
+
describe 'terraform state field' do
subject { described_class.fields['terraformState'] }
diff --git a/spec/graphql/types/terraform/state_type_spec.rb b/spec/graphql/types/terraform/state_type_spec.rb
index 9f65bb926d7..5098adbf45c 100644
--- a/spec/graphql/types/terraform/state_type_spec.rb
+++ b/spec/graphql/types/terraform/state_type_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['TerraformState'] do
it { expect(described_class).to require_graphql_authorizations(:read_terraform_state) }
describe 'fields' do
- let(:fields) { %i[id name locked_by_user locked_at latest_version created_at updated_at] }
+ let(:fields) { %i[id name locked_by_user locked_at latest_version created_at updated_at deleted_at] }
it { expect(described_class).to have_graphql_fields(fields) }
@@ -17,6 +17,7 @@ RSpec.describe GitlabSchema.types['TerraformState'] do
it { expect(described_class.fields['lockedAt'].type).not_to be_non_null }
it { expect(described_class.fields['createdAt'].type).to be_non_null }
it { expect(described_class.fields['updatedAt'].type).to be_non_null }
+ it { expect(described_class.fields['deletedAt'].type).not_to be_non_null }
it { expect(described_class.fields['latestVersion'].type).not_to be_non_null }
it { expect(described_class.fields['latestVersion'].complexity).to eq(3) }
diff --git a/spec/lib/gitlab/tracking/standard_context_spec.rb b/spec/lib/gitlab/tracking/standard_context_spec.rb
index c88b0af30f6..508b33949a8 100644
--- a/spec/lib/gitlab/tracking/standard_context_spec.rb
+++ b/spec/lib/gitlab/tracking/standard_context_spec.rb
@@ -92,6 +92,34 @@ RSpec.describe Gitlab::Tracking::StandardContext do
end
end
+ context 'with incorrect argument type' do
+ context 'when standard_context_type_check FF is disabled' do
+ before do
+ stub_feature_flags(standard_context_type_check: false)
+ end
+
+ subject { described_class.new(project: create(:group)) }
+
+ it 'does not call `track_and_raise_for_dev_exception`' do
+ expect(Gitlab::ErrorTracking).not_to receive(:track_and_raise_for_dev_exception)
+ snowplow_context
+ end
+ end
+
+ context 'when standard_context_type_check FF is enabled' do
+ before do
+ stub_feature_flags(standard_context_type_check: true)
+ end
+
+ subject { described_class.new(project: create(:group)) }
+
+ it 'does call `track_and_raise_for_dev_exception`' do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
+ snowplow_context
+ end
+ end
+ end
+
it 'contains user id' do
expect(snowplow_context.to_json[:data].keys).to include(:user_id)
end
diff --git a/spec/models/packages/cleanup/policy_spec.rb b/spec/models/packages/cleanup/policy_spec.rb
index 972071aa0ad..c08ae4aa7e7 100644
--- a/spec/models/packages/cleanup/policy_spec.rb
+++ b/spec/models/packages/cleanup/policy_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Packages::Cleanup::Policy, type: :model do
is_expected
.to validate_inclusion_of(:keep_n_duplicated_package_files)
.in_array(described_class::KEEP_N_DUPLICATED_PACKAGE_FILES_VALUES)
- .with_message('keep_n_duplicated_package_files is invalid')
+ .with_message('is invalid')
end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index d9316344474..23e4641e0d5 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -1356,6 +1356,36 @@ RSpec.describe ProjectPolicy do
end
end
+ describe 'admin_package' do
+ context 'with admin' do
+ let(:current_user) { admin }
+
+ context 'when admin mode enabled', :enable_admin_mode do
+ it { is_expected.to be_allowed(:admin_package) }
+ end
+
+ context 'when admin mode disabled' do
+ it { is_expected.to be_disallowed(:admin_package) }
+ end
+ end
+
+ %i[owner maintainer].each do |role|
+ context "with #{role}" do
+ let(:current_user) { public_send(role) }
+
+ it { is_expected.to be_allowed(:admin_package) }
+ end
+ end
+
+ %i[developer reporter guest non_member anonymous].each do |role|
+ context "with #{role}" do
+ let(:current_user) { public_send(role) }
+
+ it { is_expected.to be_disallowed(:admin_package) }
+ end
+ end
+ end
+
describe 'read_feature_flag' do
subject { described_class.new(current_user, project) }
diff --git a/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb b/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb
new file mode 100644
index 00000000000..7e00f3ca53a
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/packages/cleanup/policy/update_spec.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Updating the packages cleanup policy' do
+ include GraphqlHelpers
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:project, reload: true) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ let(:params) do
+ {
+ project_path: project.full_path,
+ keep_n_duplicated_package_files: 'TWENTY_PACKAGE_FILES'
+ }
+ end
+
+ let(:mutation) do
+ graphql_mutation(:update_packages_cleanup_policy, params,
+ <<~QUERY
+ packagesCleanupPolicy {
+ keepNDuplicatedPackageFiles
+ nextRunAt
+ }
+ errors
+ QUERY
+ )
+ end
+
+ let(:mutation_response) { graphql_mutation_response(:update_packages_cleanup_policy) }
+ let(:packages_cleanup_policy_response) { mutation_response['packagesCleanupPolicy'] }
+
+ shared_examples 'accepting the mutation request and updates the existing policy' do
+ it 'returns the updated packages cleanup policy' do
+ expect { subject }.not_to change { ::Packages::Cleanup::Policy.count }
+
+ expect(project.packages_cleanup_policy.keep_n_duplicated_package_files).to eq('20')
+ expect_graphql_errors_to_be_empty
+ expect(packages_cleanup_policy_response['keepNDuplicatedPackageFiles'])
+ .to eq(params[:keep_n_duplicated_package_files])
+ expect(packages_cleanup_policy_response['nextRunAt']).not_to eq(nil)
+ end
+ end
+
+ shared_examples 'accepting the mutation request and creates a policy' do
+ it 'returns the created packages cleanup policy' do
+ expect { subject }.to change { ::Packages::Cleanup::Policy.count }.by(1)
+
+ expect(project.packages_cleanup_policy.keep_n_duplicated_package_files).to eq('20')
+ expect_graphql_errors_to_be_empty
+ expect(packages_cleanup_policy_response['keepNDuplicatedPackageFiles'])
+ .to eq(params[:keep_n_duplicated_package_files])
+ expect(packages_cleanup_policy_response['nextRunAt']).not_to eq(nil)
+ end
+ end
+
+ shared_examples 'denying the mutation request' do
+ it 'returns an error' do
+ expect { subject }.not_to change { ::Packages::Cleanup::Policy.count }
+
+ expect(project.packages_cleanup_policy.keep_n_duplicated_package_files).not_to eq('20')
+ expect(mutation_response).to be_nil
+ expect_graphql_errors_to_include(/you don't have permission to perform this action/)
+ end
+ end
+
+ describe 'post graphql mutation' do
+ subject { post_graphql_mutation(mutation, current_user: user) }
+
+ context 'with existing packages cleanup policy' do
+ let_it_be(:project_packages_cleanup_policy) { create(:packages_cleanup_policy, project: project) }
+
+ where(:user_role, :shared_examples_name) do
+ :maintainer | 'accepting the mutation request and updates the existing policy'
+ :developer | 'denying the mutation request'
+ :reporter | 'denying the mutation request'
+ :guest | 'denying the mutation request'
+ :anonymous | 'denying the mutation request'
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", user) unless user_role == :anonymous
+ end
+
+ it_behaves_like params[:shared_examples_name]
+ end
+ end
+
+ context 'without existing packages cleanup policy' do
+ where(:user_role, :shared_examples_name) do
+ :maintainer | 'accepting the mutation request and creates a policy'
+ :developer | 'denying the mutation request'
+ :reporter | 'denying the mutation request'
+ :guest | 'denying the mutation request'
+ :anonymous | 'denying the mutation request'
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", user) unless user_role == :anonymous
+ end
+
+ it_behaves_like params[:shared_examples_name]
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/packages_cleanup_policy_spec.rb b/spec/requests/api/graphql/project/packages_cleanup_policy_spec.rb
new file mode 100644
index 00000000000..a025c57d4b8
--- /dev/null
+++ b/spec/requests/api/graphql/project/packages_cleanup_policy_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe 'getting the packages cleanup policy linked to a project' do
+ using RSpec::Parameterized::TableSyntax
+ include GraphqlHelpers
+
+ let_it_be_with_reload(:project) { create(:project) }
+ let_it_be(:current_user) { project.first_owner }
+
+ let(:fields) do
+ <<~QUERY
+ #{all_graphql_fields_for('packages_cleanup_policy'.classify)}
+ QUERY
+ end
+
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('packagesCleanupPolicy', {}, fields)
+ )
+ end
+
+ subject { post_graphql(query, current_user: current_user) }
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ subject
+ end
+ end
+
+ context 'with an existing policy' do
+ let_it_be(:policy) { create(:packages_cleanup_policy, project: project) }
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ subject
+ end
+ end
+ end
+
+ context 'with different permissions' do
+ let_it_be(:current_user) { create(:user) }
+
+ let(:packages_cleanup_policy_response) { graphql_data_at('project', 'packagesCleanupPolicy') }
+
+ where(:visibility, :role, :policy_visible) do
+ :private | :maintainer | true
+ :private | :developer | false
+ :private | :reporter | false
+ :private | :guest | false
+ :private | :anonymous | false
+ :public | :maintainer | true
+ :public | :developer | false
+ :public | :reporter | false
+ :public | :guest | false
+ :public | :anonymous | false
+ end
+
+ with_them do
+ before do
+ project.update!(visibility: visibility.to_s)
+ project.add_user(current_user, role) unless role == :anonymous
+ end
+
+ it 'return the proper response' do
+ subject
+
+ if policy_visible
+ expect(packages_cleanup_policy_response)
+ .to eq('keepNDuplicatedPackageFiles' => 'ALL_PACKAGE_FILES', 'nextRunAt' => nil)
+ else
+ expect(packages_cleanup_policy_response).to be_blank
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/cleanup/update_policy_service_spec.rb b/spec/services/packages/cleanup/update_policy_service_spec.rb
new file mode 100644
index 00000000000..a11fbb766f5
--- /dev/null
+++ b/spec/services/packages/cleanup/update_policy_service_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Packages::Cleanup::UpdatePolicyService do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be_with_reload(:project) { create(:project) }
+ let_it_be(:current_user) { create(:user) }
+
+ let(:params) { { keep_n_duplicated_package_files: 50 } }
+
+ describe '#execute' do
+ subject { described_class.new(project: project, current_user: current_user, params: params).execute }
+
+ shared_examples 'creating the policy' do
+ it 'creates a new one' do
+ expect { subject }.to change { ::Packages::Cleanup::Policy.count }.from(0).to(1)
+
+ expect(subject.payload[:packages_cleanup_policy]).to be_present
+ expect(subject.success?).to be_truthy
+ expect(project.packages_cleanup_policy).to be_persisted
+ expect(project.packages_cleanup_policy.keep_n_duplicated_package_files).to eq('50')
+ end
+
+ context 'with invalid parameters' do
+ let(:params) { { keep_n_duplicated_package_files: 100 } }
+
+ it 'does not create one' do
+ expect { subject }.not_to change { ::Packages::Cleanup::Policy.count }
+
+ expect(subject.status).to eq(:error)
+ expect(subject.message).to eq('Keep n duplicated package files is invalid')
+ end
+ end
+ end
+
+ shared_examples 'updating the policy' do
+ it 'updates the existing one' do
+ expect { subject }.not_to change { ::Packages::Cleanup::Policy.count }
+
+ expect(subject.payload[:packages_cleanup_policy]).to be_present
+ expect(subject.success?).to be_truthy
+ expect(project.packages_cleanup_policy.keep_n_duplicated_package_files).to eq('50')
+ end
+
+ context 'with invalid parameters' do
+ let(:params) { { keep_n_duplicated_package_files: 100 } }
+
+ it 'does not update one' do
+ expect { subject }.not_to change { policy.keep_n_duplicated_package_files }
+
+ expect(subject.status).to eq(:error)
+ expect(subject.message).to eq('Keep n duplicated package files is invalid')
+ end
+ end
+ end
+
+ shared_examples 'denying access' do
+ it 'returns an error' do
+ subject
+
+ expect(subject.message).to eq('Access denied')
+ expect(subject.status).to eq(:error)
+ end
+ end
+
+ context 'with existing container expiration policy' do
+ let_it_be(:policy) { create(:packages_cleanup_policy, project: project) }
+
+ where(:user_role, :shared_examples_name) do
+ :maintainer | 'updating the policy'
+ :developer | 'denying access'
+ :reporter | 'denying access'
+ :guest | 'denying access'
+ :anonymous | 'denying access'
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", current_user) unless user_role == :anonymous
+ end
+
+ it_behaves_like params[:shared_examples_name]
+ end
+ end
+
+ context 'without existing container expiration policy' do
+ where(:user_role, :shared_examples_name) do
+ :maintainer | 'creating the policy'
+ :developer | 'denying access'
+ :reporter | 'denying access'
+ :guest | 'denying access'
+ :anonymous | 'denying access'
+ end
+
+ with_them do
+ before do
+ project.send("add_#{user_role}", current_user) unless user_role == :anonymous
+ end
+
+ it_behaves_like params[:shared_examples_name]
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/next_instance_of.rb b/spec/support/helpers/next_instance_of.rb
index 461d411a5ce..3c88715615d 100644
--- a/spec/support/helpers/next_instance_of.rb
+++ b/spec/support/helpers/next_instance_of.rb
@@ -22,7 +22,7 @@ module NextInstanceOf
def stub_new(target, number, ordered = false, *new_args, &blk)
receive_new = receive(:new)
receive_new.ordered if ordered
- receive_new.with(*new_args) if new_args.any?
+ receive_new.with(*new_args) if new_args.present?
if number.is_a?(Range)
receive_new.at_least(number.begin).times if number.begin
diff --git a/spec/support/shared_examples/features/runners_shared_examples.rb b/spec/support/shared_examples/features/runners_shared_examples.rb
index d9460c7b8f1..1f5d6ed5586 100644
--- a/spec/support/shared_examples/features/runners_shared_examples.rb
+++ b/spec/support/shared_examples/features/runners_shared_examples.rb
@@ -35,11 +35,11 @@ RSpec.shared_examples 'shows and resets runner registration token' do
it 'has a registration token' do
click_on 'Click to reveal'
- expect(page.find('[data-testid="token-value"] input').value).to have_content(registration_token)
+ expect(page.find_field('token-value').value).to have_content(registration_token)
end
describe 'reset registration token' do
- let!(:old_registration_token) { find('[data-testid="token-value"] input').value }
+ let!(:old_registration_token) { find_field('token-value').value }
before do
click_on 'Reset registration token'
diff --git a/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb b/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
index be7f7ef5c8c..785cba24f9d 100644
--- a/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
+++ b/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Ci::ResourceGroups::AssignResourceFromResourceGroupWorker do
context 'when resource group exists' do
it 'executes AssignResourceFromResourceGroupService' do
- expect_next_instances_of(Ci::ResourceGroups::AssignResourceFromResourceGroupService, 2, resource_group.project, nil) do |service|
+ expect_next_instances_of(Ci::ResourceGroups::AssignResourceFromResourceGroupService, 2, false, resource_group.project, nil) do |service|
expect(service).to receive(:execute).with(resource_group)
end