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-16 21:12:52 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-16 21:12:52 +0300
commit8a9790b0db723db32f8dff511ee032e5e8e3b583 (patch)
tree8173501b91ea0ada6a68d656786867b2abcc97f9 /spec
parent7212129029f4e7e68614066cc43802faba42c554 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/admin_variables_spec.rb34
-rw-r--r--spec/features/issues/user_creates_issue_spec.rb6
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb2
-rw-r--r--spec/frontend/ci_variable_list/components/ci_admin_variables_spec.js178
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js37
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js13
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_table_spec.js3
-rw-r--r--spec/frontend/ci_variable_list/mocks.js61
-rw-r--r--spec/frontend/ci_variable_list/utils_spec.js16
-rw-r--r--spec/graphql/types/work_item_type_spec.rb3
-rw-r--r--spec/models/event_spec.rb24
-rw-r--r--spec/requests/users/namespace_callouts_spec.rb57
-rw-r--r--spec/services/users/dismiss_namespace_callout_service_spec.rb24
-rw-r--r--spec/support/shared_examples/features/variable_list_shared_examples.rb10
-rw-r--r--spec/workers/pages/invalidate_domain_cache_worker_spec.rb10
15 files changed, 417 insertions, 61 deletions
diff --git a/spec/features/admin_variables_spec.rb b/spec/features/admin_variables_spec.rb
new file mode 100644
index 00000000000..174d4567520
--- /dev/null
+++ b/spec/features/admin_variables_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Instance variables', :js do
+ let(:admin) { create(:admin) }
+ let(:page_path) { ci_cd_admin_application_settings_path }
+
+ let_it_be(:variable) { create(:ci_instance_variable, key: 'test_key', value: 'test_value', masked: true) }
+
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ sign_in(admin)
+ gitlab_enable_admin_mode_sign_in(admin)
+ wait_for_requests
+ end
+
+ context 'with disabled ff `ci_variable_settings_graphql' do
+ before do
+ stub_feature_flags(ci_variable_settings_graphql: false)
+ visit page_path
+ end
+
+ it_behaves_like 'variable list', isAdmin: true
+ end
+
+ context 'with enabled ff `ci_variable_settings_graphql' do
+ before do
+ visit page_path
+ end
+
+ it_behaves_like 'variable list', isAdmin: true
+ end
+end
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index f2be85a4d0e..e29911e3263 100644
--- a/spec/features/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
@@ -188,7 +188,7 @@ RSpec.describe "User creates issue" do
end
it 'does not hide the milestone select' do
- expect(page).to have_selector('.qa-issuable-milestone-dropdown') # rubocop:disable QA/SelectorUsage
+ expect(page).to have_selector('[data-testid="issuable-milestone-dropdown"]')
end
end
@@ -204,7 +204,7 @@ RSpec.describe "User creates issue" do
end
it 'shows the milestone select' do
- expect(page).to have_selector('.qa-issuable-milestone-dropdown') # rubocop:disable QA/SelectorUsage
+ expect(page).to have_selector('[data-testid="issuable-milestone-dropdown"]')
end
it 'hides the incident help text' do
@@ -265,7 +265,7 @@ RSpec.describe "User creates issue" do
end
it 'shows the milestone select' do
- expect(page).to have_selector('.qa-issuable-milestone-dropdown') # rubocop:disable QA/SelectorUsage
+ expect(page).to have_selector('[data-testid="issuable-milestone-dropdown"]')
end
it 'hides the weight input' do
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index f5cafa2b2ec..13a4c1b5912 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -137,7 +137,7 @@ RSpec.describe 'File blob', :js do
context 'when ref switch' do
def switch_ref_to(ref_name)
- first('.qa-branches-select').click # rubocop:disable QA/SelectorUsage
+ first('[data-testid="branches-select"]').click
page.within '.project-refs-form' do
click_link ref_name
diff --git a/spec/frontend/ci_variable_list/components/ci_admin_variables_spec.js b/spec/frontend/ci_variable_list/components/ci_admin_variables_spec.js
new file mode 100644
index 00000000000..920ceaefb70
--- /dev/null
+++ b/spec/frontend/ci_variable_list/components/ci_admin_variables_spec.js
@@ -0,0 +1,178 @@
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlLoadingIcon, GlTable } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import createFlash from '~/flash';
+import { resolvers } from '~/ci_variable_list/graphql/resolvers';
+
+import ciAdminVariables from '~/ci_variable_list/components/ci_admin_variables.vue';
+import ciVariableSettings from '~/ci_variable_list/components/ci_variable_settings.vue';
+import ciVariableTable from '~/ci_variable_list/components/ci_variable_table.vue';
+import getAdminVariables from '~/ci_variable_list/graphql/queries/variables.query.graphql';
+
+import addAdminVariable from '~/ci_variable_list/graphql/mutations/admin_add_variable.mutation.graphql';
+import deleteAdminVariable from '~/ci_variable_list/graphql/mutations/admin_delete_variable.mutation.graphql';
+import updateAdminVariable from '~/ci_variable_list/graphql/mutations/admin_update_variable.mutation.graphql';
+
+import { genericMutationErrorText, variableFetchErrorText } from '~/ci_variable_list/constants';
+
+import { mockAdminVariables, newVariable } from '../mocks';
+
+jest.mock('~/flash');
+
+Vue.use(VueApollo);
+
+const mockProvide = {
+ endpoint: '/variables',
+};
+
+describe('Ci Admin Variable list', () => {
+ let wrapper;
+
+ let mockApollo;
+ let mockVariables;
+
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findCiTable = () => wrapper.findComponent(GlTable);
+ const findCiSettings = () => wrapper.findComponent(ciVariableSettings);
+
+ // eslint-disable-next-line consistent-return
+ const createComponentWithApollo = async ({ isLoading = false } = {}) => {
+ const handlers = [[getAdminVariables, mockVariables]];
+
+ mockApollo = createMockApollo(handlers, resolvers);
+
+ wrapper = shallowMount(ciAdminVariables, {
+ provide: mockProvide,
+ apolloProvider: mockApollo,
+ stubs: { ciVariableSettings, ciVariableTable },
+ });
+
+ if (!isLoading) {
+ return waitForPromises();
+ }
+ };
+
+ beforeEach(() => {
+ mockVariables = jest.fn();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('while queries are being fetch', () => {
+ beforeEach(() => {
+ createComponentWithApollo({ isLoading: true });
+ });
+
+ it('shows a loading icon', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findCiTable().exists()).toBe(false);
+ });
+ });
+
+ describe('when queries are resolved', () => {
+ describe('successfuly', () => {
+ beforeEach(async () => {
+ mockVariables.mockResolvedValue(mockAdminVariables);
+
+ await createComponentWithApollo();
+ });
+
+ it('passes down the expected environments as props', () => {
+ expect(findCiSettings().props('environments')).toEqual([]);
+ });
+
+ it('passes down the expected variables as props', () => {
+ expect(findCiSettings().props('variables')).toEqual(
+ mockAdminVariables.data.ciVariables.nodes,
+ );
+ });
+
+ it('createFlash was not called', () => {
+ expect(createFlash).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('with an error for variables', () => {
+ beforeEach(async () => {
+ mockVariables.mockRejectedValue();
+
+ await createComponentWithApollo();
+ });
+
+ it('calls createFlash with the expected error message', () => {
+ expect(createFlash).toHaveBeenCalledWith({ message: variableFetchErrorText });
+ });
+ });
+ });
+
+ describe('mutations', () => {
+ beforeEach(async () => {
+ mockVariables.mockResolvedValue(mockAdminVariables);
+
+ await createComponentWithApollo();
+ });
+ it.each`
+ actionName | mutation | event
+ ${'add'} | ${addAdminVariable} | ${'add-variable'}
+ ${'update'} | ${updateAdminVariable} | ${'update-variable'}
+ ${'delete'} | ${deleteAdminVariable} | ${'delete-variable'}
+ `(
+ 'calls the right mutation when user performs $actionName variable',
+ async ({ event, mutation }) => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
+ await findCiSettings().vm.$emit(event, newVariable);
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation,
+ variables: {
+ endpoint: mockProvide.endpoint,
+ variable: newVariable,
+ },
+ });
+ },
+ );
+
+ it.each`
+ actionName | event | mutationName
+ ${'add'} | ${'add-variable'} | ${'addAdminVariable'}
+ ${'update'} | ${'update-variable'} | ${'updateAdminVariable'}
+ ${'delete'} | ${'delete-variable'} | ${'deleteAdminVariable'}
+ `(
+ 'throws with the specific graphql error if present when user performs $actionName variable',
+ async ({ event, mutationName }) => {
+ const graphQLErrorMessage = 'There is a problem with this graphQL action';
+ jest
+ .spyOn(wrapper.vm.$apollo, 'mutate')
+ .mockResolvedValue({ data: { [mutationName]: { errors: [graphQLErrorMessage] } } });
+ await findCiSettings().vm.$emit(event, newVariable);
+ await nextTick();
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
+ expect(createFlash).toHaveBeenCalledWith({ message: graphQLErrorMessage });
+ },
+ );
+
+ it.each`
+ actionName | event
+ ${'add'} | ${'add-variable'}
+ ${'update'} | ${'update-variable'}
+ ${'delete'} | ${'delete-variable'}
+ `(
+ 'throws generic error when the mutation fails with no graphql errors and user performs $actionName variable',
+ async ({ event }) => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockImplementationOnce(() => {
+ throw new Error();
+ });
+ await findCiSettings().vm.$emit(event, newVariable);
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
+ expect(createFlash).toHaveBeenCalledWith({ message: genericMutationErrorText });
+ },
+ );
+ });
+});
diff --git a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
index 51b902d97dc..e5019e3261e 100644
--- a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
@@ -10,6 +10,7 @@ import {
EVENT_LABEL,
EVENT_ACTION,
ENVIRONMENT_SCOPE_LINK_TITLE,
+ instanceString,
} from '~/ci_variable_list/constants';
import { mockVariablesWithScopes } from '../mocks';
import ModalStub from '../stubs';
@@ -19,6 +20,7 @@ describe('Ci variable modal', () => {
let trackingSpy;
const maskableRegex = '^[a-zA-Z0-9_+=/@:.~-]{8,}$';
+ const mockVariables = mockVariablesWithScopes(instanceString);
const defaultProvide = {
awsLogoSvgPath: '/logo',
@@ -38,6 +40,7 @@ describe('Ci variable modal', () => {
environments: [],
mode: ADD_VARIABLE_ACTION,
selectedVariable: {},
+ variable: [],
};
const createComponent = ({ mountFn = shallowMountExtended, props = {}, provide = {} } = {}) => {
@@ -81,22 +84,22 @@ describe('Ci variable modal', () => {
});
it('shows the submit button as disabled ', () => {
- expect(findAddorUpdateButton().attributes('disabled')).toBeTruthy();
+ expect(findAddorUpdateButton().attributes('disabled')).toBe('true');
});
});
describe('when a key/value pair is present', () => {
beforeEach(() => {
- createComponent({ props: { selectedVariable: mockVariablesWithScopes[0] } });
+ createComponent({ props: { selectedVariable: mockVariables[0] } });
});
it('shows the submit button as enabled ', () => {
- expect(findAddorUpdateButton().attributes('disabled')).toBeFalsy();
+ expect(findAddorUpdateButton().attributes('disabled')).toBeUndefined();
});
});
describe('events', () => {
- const [currentVariable] = mockVariablesWithScopes;
+ const [currentVariable] = mockVariables;
beforeEach(() => {
createComponent({ props: { selectedVariable: currentVariable } });
@@ -123,9 +126,9 @@ describe('Ci variable modal', () => {
});
it('updates the protected value to true', () => {
- expect(
- findProtectedVariableCheckbox().attributes('data-is-protected-checked'),
- ).toBeTruthy();
+ expect(findProtectedVariableCheckbox().attributes('data-is-protected-checked')).toBe(
+ 'true',
+ );
});
});
@@ -151,7 +154,7 @@ describe('Ci variable modal', () => {
describe('Adding a new non-AWS variable', () => {
beforeEach(() => {
- const [variable] = mockVariablesWithScopes;
+ const [variable] = mockVariables;
createComponent({ mountFn: mountExtended, props: { selectedVariable: variable } });
});
@@ -164,7 +167,7 @@ describe('Ci variable modal', () => {
describe('Adding a new AWS variable', () => {
beforeEach(() => {
- const [variable] = mockVariablesWithScopes;
+ const [variable] = mockVariables;
const AWSKeyVariable = {
...variable,
key: AWS_ACCESS_KEY_ID,
@@ -183,7 +186,7 @@ describe('Ci variable modal', () => {
describe('Reference warning when adding a variable', () => {
describe('with a $ character', () => {
beforeEach(() => {
- const [variable] = mockVariablesWithScopes;
+ const [variable] = mockVariables;
const variableWithDollarSign = {
...variable,
value: 'valueWith$',
@@ -201,7 +204,7 @@ describe('Ci variable modal', () => {
describe('without a $ character', () => {
beforeEach(() => {
- const [variable] = mockVariablesWithScopes;
+ const [variable] = mockVariables;
createComponent({
mountFn: mountExtended,
props: { selectedVariable: variable },
@@ -215,7 +218,7 @@ describe('Ci variable modal', () => {
});
describe('Editing a variable', () => {
- const [variable] = mockVariablesWithScopes;
+ const [variable] = mockVariables;
beforeEach(() => {
createComponent({ props: { selectedVariable: variable, mode: EDIT_VARIABLE_ACTION } });
@@ -286,7 +289,7 @@ describe('Ci variable modal', () => {
describe('when the mask state is invalid', () => {
beforeEach(async () => {
- const [variable] = mockVariablesWithScopes;
+ const [variable] = mockVariables;
const invalidMaskVariable = {
...variable,
value: 'd:;',
@@ -301,7 +304,7 @@ describe('Ci variable modal', () => {
});
it('disables the submit button', () => {
- expect(findAddorUpdateButton().attributes('disabled')).toBeTruthy();
+ expect(findAddorUpdateButton().attributes('disabled')).toBe('disabled');
});
it('shows the correct error text', () => {
@@ -326,7 +329,7 @@ describe('Ci variable modal', () => {
${'unsupported|char'} | ${false} | ${0} | ${null}
`('Adding a new variable', ({ value, masked, eventSent, trackingErrorProperty }) => {
beforeEach(async () => {
- const [variable] = mockVariablesWithScopes;
+ const [variable] = mockVariables;
const invalidKeyVariable = {
...variable,
value: '',
@@ -359,7 +362,7 @@ describe('Ci variable modal', () => {
describe('when masked variable has acceptable value', () => {
beforeEach(() => {
- const [variable] = mockVariablesWithScopes;
+ const [variable] = mockVariables;
const validMaskandKeyVariable = {
...variable,
key: AWS_ACCESS_KEY_ID,
@@ -373,7 +376,7 @@ describe('Ci variable modal', () => {
});
it('does not disable the submit button', () => {
- expect(findAddorUpdateButton().attributes('disabled')).toBeFalsy();
+ expect(findAddorUpdateButton().attributes('disabled')).toBeUndefined();
});
});
});
diff --git a/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js
index fb79611229c..5c77ce71b41 100644
--- a/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js
@@ -3,8 +3,12 @@ import { shallowMount } from '@vue/test-utils';
import CiVariableSettings from '~/ci_variable_list/components/ci_variable_settings.vue';
import ciVariableModal from '~/ci_variable_list/components/ci_variable_modal.vue';
import ciVariableTable from '~/ci_variable_list/components/ci_variable_table.vue';
-import { ADD_VARIABLE_ACTION, EDIT_VARIABLE_ACTION } from '~/ci_variable_list/constants';
-import { createJoinedEnvironments, mapEnvironmentNames } from '~/ci_variable_list/utils';
+import {
+ ADD_VARIABLE_ACTION,
+ EDIT_VARIABLE_ACTION,
+ projectString,
+} from '~/ci_variable_list/constants';
+import { mapEnvironmentNames } from '~/ci_variable_list/utils';
import { mockEnvs, mockVariablesWithScopes, newVariable } from '../mocks';
@@ -15,7 +19,7 @@ describe('Ci variable table', () => {
areScopedVariablesAvailable: true,
environments: mapEnvironmentNames(mockEnvs),
isLoading: false,
- variables: mockVariablesWithScopes,
+ variables: mockVariablesWithScopes(projectString),
};
const findCiVariableTable = () => wrapper.findComponent(ciVariableTable);
@@ -51,7 +55,8 @@ describe('Ci variable table', () => {
expect(findCiVariableModal().props()).toEqual({
areScopedVariablesAvailable: defaultProps.areScopedVariablesAvailable,
- environments: createJoinedEnvironments(defaultProps.variables, defaultProps.environments),
+ environments: defaultProps.environments,
+ variables: defaultProps.variables,
mode: ADD_VARIABLE_ACTION,
selectedVariable: {},
});
diff --git a/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js
index b5b4881aa44..8a4c35173ec 100644
--- a/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js
@@ -1,5 +1,6 @@
import { mountExtended } from 'helpers/vue_test_utils_helper';
import CiVariableTable from '~/ci_variable_list/components/ci_variable_table.vue';
+import { projectString } from '~/ci_variable_list/constants';
import { mockVariables } from '../mocks';
describe('Ci variable table', () => {
@@ -7,7 +8,7 @@ describe('Ci variable table', () => {
const defaultProps = {
isLoading: false,
- variables: mockVariables,
+ variables: mockVariables(projectString),
};
const createComponent = ({ props = {} } = {}) => {
diff --git a/spec/frontend/ci_variable_list/mocks.js b/spec/frontend/ci_variable_list/mocks.js
index 1ba50c74152..07dc7a8c91f 100644
--- a/spec/frontend/ci_variable_list/mocks.js
+++ b/spec/frontend/ci_variable_list/mocks.js
@@ -1,42 +1,45 @@
-import { variableTypes } from '~/ci_variable_list/constants';
+import { variableTypes, instanceString } from '~/ci_variable_list/constants';
export const devName = 'dev';
export const prodName = 'prod';
-export const mockVariables = [
- {
- __typename: 'CiVariable',
- id: 1,
- key: 'my-var',
- masked: false,
- protected: true,
- value: 'env_val',
- variableType: variableTypes.variableType,
- },
- {
- __typename: 'CiVariable',
- id: 2,
- key: 'secret',
- masked: true,
- protected: false,
- value: 'the_secret_value',
- variableType: variableTypes.fileType,
- },
-];
+export const mockVariables = (kind) => {
+ return [
+ {
+ __typename: `Ci${kind}Variable`,
+ id: 1,
+ key: 'my-var',
+ masked: false,
+ protected: true,
+ value: 'env_val',
+ variableType: variableTypes.variableType,
+ },
+ {
+ __typename: `Ci${kind}Variable`,
+ id: 2,
+ key: 'secret',
+ masked: true,
+ protected: false,
+ value: 'the_secret_value',
+ variableType: variableTypes.fileType,
+ },
+ ];
+};
-export const mockVariablesWithScopes = mockVariables.map((variable) => {
- return { ...variable, environmentScope: '*' };
-});
+export const mockVariablesWithScopes = (kind) =>
+ mockVariables(kind).map((variable) => {
+ return { ...variable, environmentScope: '*' };
+ });
-const createDefaultVars = ({ withScope = true } = {}) => {
- let base = mockVariables;
+const createDefaultVars = ({ withScope = true, kind } = {}) => {
+ let base = mockVariables(kind);
if (withScope) {
- base = mockVariablesWithScopes;
+ base = mockVariablesWithScopes(kind);
}
return {
- __typename: 'CiVariableConnection',
+ __typename: `Ci${kind}VariableConnection`,
nodes: base,
};
};
@@ -101,7 +104,7 @@ export const mockGroupVariables = {
export const mockAdminVariables = {
data: {
- ciVariables: createDefaultVars({ withScope: false }),
+ ciVariables: createDefaultVars({ withScope: false, kind: instanceString }),
},
};
diff --git a/spec/frontend/ci_variable_list/utils_spec.js b/spec/frontend/ci_variable_list/utils_spec.js
index 1676e786515..081c399792f 100644
--- a/spec/frontend/ci_variable_list/utils_spec.js
+++ b/spec/frontend/ci_variable_list/utils_spec.js
@@ -7,12 +7,13 @@ import { allEnvironments } from '~/ci_variable_list/constants';
describe('utils', () => {
const environments = ['dev', 'prod'];
+ const newEnvironments = ['staging'];
describe('createJoinedEnvironments', () => {
it('returns only `environments` if `variables` argument is undefined', () => {
const variables = undefined;
- expect(createJoinedEnvironments(variables, environments)).toEqual(environments);
+ expect(createJoinedEnvironments(variables, environments, [])).toEqual(environments);
});
it('returns a list of environments and environment scopes taken from variables in alphabetical order', () => {
@@ -21,7 +22,7 @@ describe('utils', () => {
const variables = [{ environmentScope: envScope1 }, { environmentScope: envScope2 }];
- expect(createJoinedEnvironments(variables, environments)).toEqual([
+ expect(createJoinedEnvironments(variables, environments, [])).toEqual([
environments[0],
envScope1,
envScope2,
@@ -29,13 +30,22 @@ describe('utils', () => {
]);
});
+ it('returns combined list with new environments included', () => {
+ const variables = undefined;
+
+ expect(createJoinedEnvironments(variables, environments, newEnvironments)).toEqual([
+ ...environments,
+ ...newEnvironments,
+ ]);
+ });
+
it('removes duplicate environments', () => {
const envScope1 = environments[0];
const envScope2 = 'new2';
const variables = [{ environmentScope: envScope1 }, { environmentScope: envScope2 }];
- expect(createJoinedEnvironments(variables, environments)).toEqual([
+ expect(createJoinedEnvironments(variables, environments, [])).toEqual([
environments[0],
envScope2,
environments[1],
diff --git a/spec/graphql/types/work_item_type_spec.rb b/spec/graphql/types/work_item_type_spec.rb
index 153934c374c..228f9b6ec07 100644
--- a/spec/graphql/types/work_item_type_spec.rb
+++ b/spec/graphql/types/work_item_type_spec.rb
@@ -22,6 +22,9 @@ RSpec.describe GitlabSchema.types['WorkItem'] do
userPermissions
widgets
work_item_type
+ created_at
+ updated_at
+ closed_at
]
fields.each do |field_name|
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index e94e3ef84a4..9700852e567 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -264,6 +264,8 @@ RSpec.describe Event do
let(:project) { public_project }
let(:issue) { create(:issue, project: project, author: author, assignees: [assignee]) }
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee]) }
+ let(:work_item) { create(:work_item, project: project, author: author) }
+ let(:confidential_work_item) { create(:work_item, :confidential, project: project, author: author) }
let(:project_snippet) { create(:project_snippet, :public, project: project, author: author) }
let(:personal_snippet) { create(:personal_snippet, :public, author: author) }
let(:design) { create(:design, issue: issue, project: project) }
@@ -380,6 +382,28 @@ RSpec.describe Event do
end
end
+ context 'work item event' do
+ context 'for non confidential work item' do
+ let(:target) { work_item }
+
+ include_examples 'visibility examples' do
+ let(:visibility) { visible_to_all }
+ end
+
+ include_examples 'visible to assignee and author', true
+ end
+
+ context 'for confidential work item' do
+ let(:target) { confidential_work_item }
+
+ include_examples 'visibility examples' do
+ let(:visibility) { visible_to_none_except(:member, :admin) }
+ end
+
+ include_examples 'visible to author', true
+ end
+ end
+
context 'issue note event' do
context 'on non confidential issues' do
let(:target) { note_on_issue }
diff --git a/spec/requests/users/namespace_callouts_spec.rb b/spec/requests/users/namespace_callouts_spec.rb
new file mode 100644
index 00000000000..5a4e269eefb
--- /dev/null
+++ b/spec/requests/users/namespace_callouts_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Namespace callouts' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'POST /-/users/namespace_callouts' do
+ let(:params) { { feature_name: feature_name, namespace_id: user.namespace.id } }
+
+ subject { post namespace_callouts_path, params: params, headers: { 'ACCEPT' => 'application/json' } }
+
+ context 'with valid feature name and group' do
+ let(:feature_name) { Users::NamespaceCallout.feature_names.each_key.first }
+
+ context 'when callout entry does not exist' do
+ it 'creates a callout entry with dismissed state' do
+ expect { subject }.to change { Users::NamespaceCallout.count }.by(1)
+ end
+
+ it 'returns success' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when callout entry already exists' do
+ let!(:callout) do
+ create(:namespace_callout,
+ feature_name: Users::GroupCallout.feature_names.each_key.first,
+ user: user,
+ namespace: user.namespace)
+ end
+
+ it 'returns success', :aggregate_failures do
+ expect { subject }.not_to change { Users::NamespaceCallout.count }
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'with invalid feature name' do
+ let(:feature_name) { 'bogus_feature_name' }
+
+ it 'returns bad request' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+end
diff --git a/spec/services/users/dismiss_namespace_callout_service_spec.rb b/spec/services/users/dismiss_namespace_callout_service_spec.rb
new file mode 100644
index 00000000000..fbcdb66c9e8
--- /dev/null
+++ b/spec/services/users/dismiss_namespace_callout_service_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::DismissNamespaceCalloutService do
+ describe '#execute' do
+ let_it_be(:user) { create(:user) }
+
+ let(:params) { { feature_name: feature_name, namespace_id: user.namespace.id } }
+ let(:feature_name) { Users::NamespaceCallout.feature_names.each_key.first }
+
+ subject(:execute) do
+ described_class.new(
+ container: nil, current_user: user, params: params
+ ).execute
+ end
+
+ it_behaves_like 'dismissing user callout', Users::NamespaceCallout
+
+ it 'sets the namespace_id' do
+ expect(execute.namespace_id).to eq(user.namespace.id)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb
index c63faace6b2..9d81c0e9a3e 100644
--- a/spec/support/shared_examples/features/variable_list_shared_examples.rb
+++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'variable list' do
+RSpec.shared_examples 'variable list' do |is_admin|
it 'shows a list of variables' do
page.within('[data-testid="ci-variable-table"]') do
expect(find('.js-ci-variable-row:nth-child(1) td[data-label="Key"]').text).to eq(variable.key)
@@ -166,7 +166,7 @@ RSpec.shared_examples 'variable list' do
wait_for_requests
expect(find('.flash-container')).to be_present
- expect(find('[data-testid="alert-danger"]').text).to have_content('Variables key (key) has already been taken')
+ expect(find('[data-testid="alert-danger"]').text).to have_content('(key) has already been taken')
end
it 'prevents a variable to be added if no values are provided when a variable is set to masked' do
@@ -257,7 +257,11 @@ RSpec.shared_examples 'variable list' do
end
it 'shows a message regarding the changed default' do
- expect(page).to have_content 'Environment variables are configured by your administrator to be protected by default'
+ if is_admin
+ expect(page).to have_content 'Environment variables on this GitLab instance are configured to be protected by default'
+ else
+ expect(page).to have_content 'Environment variables are configured by your administrator to be protected by default'
+ end
end
end
diff --git a/spec/workers/pages/invalidate_domain_cache_worker_spec.rb b/spec/workers/pages/invalidate_domain_cache_worker_spec.rb
index 75d7c9f82b4..9272e26a34f 100644
--- a/spec/workers/pages/invalidate_domain_cache_worker_spec.rb
+++ b/spec/workers/pages/invalidate_domain_cache_worker_spec.rb
@@ -116,6 +116,16 @@ RSpec.describe Pages::InvalidateDomainCacheWorker do
{ type: :namespace, id: 2 }
]
+ it_behaves_like 'clears caches with',
+ event_class: Groups::GroupDeletedEvent,
+ event_data: {
+ group_id: 1,
+ root_namespace_id: 3
+ },
+ caches: [
+ { type: :namespace, id: 3 }
+ ]
+
context 'when namespace based cache keys are duplicated' do
# de-dups namespace cache keys
it_behaves_like 'clears caches with',