diff options
Diffstat (limited to 'spec')
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', |