diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-05 18:17:47 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-05 18:17:47 +0000 |
commit | bc4cd6ffb93ae695f20ea54f4e4803d50c78a69e (patch) | |
tree | 2364d0f880af998b70356628f79cda64f13a11e7 /spec | |
parent | 6634288474d2a83cac7b4205f3ffc736cc6b36db (diff) | |
download | gitlab-ce-bc4cd6ffb93ae695f20ea54f4e4803d50c78a69e.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
22 files changed, 358 insertions, 368 deletions
diff --git a/spec/factories/organizations.rb b/spec/factories/organizations.rb new file mode 100644 index 00000000000..a6684a8f95f --- /dev/null +++ b/spec/factories/organizations.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :organization +end diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index e6e5a1f9894..6f155959adb 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -394,6 +394,24 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ providers: [mock_saml_config_with_upstream_two_factor_authn_contexts]) end + it 'displays the remember me checkbox' do + visit new_user_session_path + + expect(page).to have_field('remember_me_omniauth') + end + + context 'when remember me is not enabled' do + before do + stub_application_setting(remember_me_enabled: false) + end + + it 'does not display the remember me checkbox' do + visit new_user_session_path + + expect(page).not_to have_field('remember_me_omniauth') + end + end + context 'when authn_context is worth two factors' do let(:mock_saml_response) do File.read('spec/fixtures/authentication/saml_response.xml') @@ -444,6 +462,24 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ end describe 'without two-factor authentication' do + it 'displays the remember me checkbox' do + visit new_user_session_path + + expect(page).to have_content(_('Remember me')) + end + + context 'when remember me is not enabled' do + before do + stub_application_setting(remember_me_enabled: false) + end + + it 'does not display the remember me checkbox' do + visit new_user_session_path + + expect(page).not_to have_content(_('Remember me')) + end + end + context 'with correct username and password' do let(:user) { create(:user) } @@ -750,17 +786,37 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ allow(instance).to receive(:"user_#{provider}_omniauth_callback_path") .and_return("/users/auth/#{provider}/callback") end - - visit new_user_session_path end it 'correctly renders tabs and panes' do + visit new_user_session_path + ensure_tab_pane_correctness(['Main LDAP', 'Standard']) end it 'renders link to sign up path' do + visit new_user_session_path + expect(page.body).to have_link('Register now', href: new_user_registration_path) end + + it 'displays the remember me checkbox' do + visit new_user_session_path + + ensure_remember_me_in_tab(ldap_server_config['label']) + end + + context 'when remember me is not enabled' do + before do + stub_application_setting(remember_me_enabled: false) + end + + it 'does not display the remember me checkbox' do + visit new_user_session_path + + ensure_remember_me_not_in_tab(ldap_server_config['label']) + end + end end context 'when crowd is enabled' do @@ -775,13 +831,31 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ allow(instance).to receive(:user_crowd_omniauth_authorize_path) .and_return("/users/auth/crowd/callback") end - - visit new_user_session_path end it 'correctly renders tabs and panes' do + visit new_user_session_path + ensure_tab_pane_correctness(%w(Crowd Standard)) end + + it 'displays the remember me checkbox' do + visit new_user_session_path + + ensure_remember_me_in_tab(_('Crowd')) + end + + context 'when remember me is not enabled' do + before do + stub_application_setting(remember_me_enabled: false) + end + + it 'does not display the remember me checkbox' do + visit new_user_session_path + + ensure_remember_me_not_in_tab(_('Crowd')) + end + end end end diff --git a/spec/frontend/graphql_shared/utils_spec.js b/spec/frontend/graphql_shared/utils_spec.js index 35ae8de1b1f..f03856e5f75 100644 --- a/spec/frontend/graphql_shared/utils_spec.js +++ b/spec/frontend/graphql_shared/utils_spec.js @@ -3,6 +3,7 @@ import Visibility from 'visibilityjs'; import { isGid, getIdFromGraphQLId, + getTypeFromGraphQLId, convertToGraphQLId, convertToGraphQLIds, convertFromGraphQLIds, @@ -26,52 +27,30 @@ describe('isGid', () => { }); }); -describe('getIdFromGraphQLId', () => { - [ - { - input: '', - output: null, - }, - { - input: null, - output: null, - }, - { - input: 2, - output: 2, - }, - { - input: 'gid://', - output: null, - }, - { - input: 'gid://gitlab/', - output: null, - }, - { - input: 'gid://gitlab/Environments', - output: null, - }, - { - input: 'gid://gitlab/Environments/', - output: null, - }, - { - input: 'gid://gitlab/Environments/0', - output: 0, - }, - { - input: 'gid://gitlab/Environments/123', - output: 123, - }, - { - input: 'gid://gitlab/DesignManagement::Version/2', - output: 2, - }, - ].forEach(({ input, output }) => { - it(`getIdFromGraphQLId returns ${output} when passed ${input}`, () => { - expect(getIdFromGraphQLId(input)).toBe(output); - }); +describe.each` + input | id | type + ${''} | ${null} | ${null} + ${null} | ${null} | ${null} + ${0} | ${0} | ${null} + ${'0'} | ${0} | ${null} + ${2} | ${2} | ${null} + ${'2'} | ${2} | ${null} + ${'gid://'} | ${null} | ${null} + ${'gid://gitlab'} | ${null} | ${null} + ${'gid://gitlab/'} | ${null} | ${null} + ${'gid://gitlab/Environments'} | ${null} | ${'Environments'} + ${'gid://gitlab/Environments/'} | ${null} | ${'Environments'} + ${'gid://gitlab/Environments/0'} | ${0} | ${'Environments'} + ${'gid://gitlab/Environments/123'} | ${123} | ${'Environments'} + ${'gid://gitlab/Environments/123/test'} | ${123} | ${'Environments'} + ${'gid://gitlab/DesignManagement::Version/123'} | ${123} | ${'DesignManagement::Version'} +`('parses GraphQL ID `$input`', ({ input, id, type }) => { + it(`getIdFromGraphQLId returns ${id}`, () => { + expect(getIdFromGraphQLId(input)).toBe(id); + }); + + it(`getTypeFromGraphQLId returns ${type}`, () => { + expect(getTypeFromGraphQLId(input)).toBe(type); }); }); diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js index c6869573b2e..9a0cde15b24 100644 --- a/spec/frontend/issues/show/components/description_spec.js +++ b/spec/frontend/issues/show/components/description_spec.js @@ -8,7 +8,6 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { createAlert } from '~/alert'; import Description from '~/issues/show/components/description.vue'; import eventHub from '~/issues/show/event_hub'; -import workItemQuery from '~/work_items/graphql/work_item.query.graphql'; import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql'; import workItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql'; import TaskList from '~/task_list'; @@ -35,13 +34,6 @@ const $toast = { }; const issueDetailsResponse = getIssueDetailsResponse(); -const workItemQueryResponse = { - data: { - workItem: null, - }, -}; - -const queryHandler = jest.fn().mockResolvedValue(workItemQueryResponse); const workItemTypesQueryHandler = jest.fn().mockResolvedValue(projectWorkItemTypesQueryResponse); describe('Description component', () => { @@ -72,7 +64,6 @@ describe('Description component', () => { ...provide, }, apolloProvider: createMockApollo([ - [workItemQuery, queryHandler], [workItemTypesQuery, workItemTypesQueryHandler], [getIssueDetailsQuery, issueDetailsQueryHandler], [createWorkItemMutation, createWorkItemMutationHandler], diff --git a/spec/frontend/security_configuration/components/app_spec.js b/spec/frontend/security_configuration/components/app_spec.js index c4e6bef71eb..364fe733a41 100644 --- a/spec/frontend/security_configuration/components/app_spec.js +++ b/spec/frontend/security_configuration/components/app_spec.js @@ -11,7 +11,7 @@ import AutoDevopsEnabledAlert from '~/security_configuration/components/auto_dev import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from '~/security_configuration/components/constants'; import FeatureCard from '~/security_configuration/components/feature_card.vue'; import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue'; -import { complianceFeaturesMock, securityFeaturesMock, provideMock } from '../mock_data'; +import { securityFeaturesMock, provideMock } from '../mock_data'; const gitlabCiHistoryPath = 'test/historyPath'; const { vulnerabilityTrainingDocsPath, projectFullPath } = provideMock; @@ -29,7 +29,6 @@ describe('~/security_configuration/components/app', () => { wrapper = mountExtended(SecurityConfigurationApp, { propsData: { augmentedSecurityFeatures: securityFeaturesMock, - augmentedComplianceFeatures: complianceFeaturesMock, securityTrainingEnabled: true, ...propsData, }, @@ -72,12 +71,7 @@ describe('~/security_configuration/components/app', () => { text: i18n.configurationHistory, container: findByTestId('security-testing-tab'), }); - const findComplianceViewHistoryLink = () => - findLink({ - href: gitlabCiHistoryPath, - text: i18n.configurationHistory, - container: findByTestId('compliance-testing-tab'), - }); + const findAutoDevopsAlert = () => wrapper.findComponent(AutoDevopsAlert); const findAutoDevopsEnabledAlert = () => wrapper.findComponent(AutoDevopsEnabledAlert); const findVulnerabilityManagementTab = () => wrapper.findByTestId('vulnerability-management-tab'); @@ -94,7 +88,7 @@ describe('~/security_configuration/components/app', () => { }); describe('tabs', () => { - const expectedTabs = ['security-testing', 'compliance-testing', 'vulnerability-management']; + const expectedTabs = ['security-testing', 'vulnerability-management']; it('renders GlTab Component', () => { expect(findTab().exists()).toBe(true); @@ -123,9 +117,8 @@ describe('~/security_configuration/components/app', () => { it('renders right amount of feature cards for given props with correct props', () => { const cards = findFeatureCards(); - expect(cards).toHaveLength(2); + expect(cards).toHaveLength(1); expect(cards.at(0).props()).toEqual({ feature: securityFeaturesMock[0] }); - expect(cards.at(1).props()).toEqual({ feature: complianceFeaturesMock[0] }); }); it('renders a basic description', () => { @@ -137,7 +130,6 @@ describe('~/security_configuration/components/app', () => { }); it('should not show configuration History Link when gitlabCiPresent & gitlabCiHistoryPath are not defined', () => { - expect(findComplianceViewHistoryLink().exists()).toBe(false); expect(findSecurityViewHistoryLink().exists()).toBe(false); }); }); @@ -158,7 +150,7 @@ describe('~/security_configuration/components/app', () => { it('should show Alert with error Message', async () => { expect(findManageViaMRErrorAlert().exists()).toBe(false); - findFeatureCards().at(1).vm.$emit('error', errorMessage); + findFeatureCards().at(0).vm.$emit('error', errorMessage); await nextTick(); expect(findManageViaMRErrorAlert().exists()).toBe(true); @@ -166,7 +158,7 @@ describe('~/security_configuration/components/app', () => { }); it('should hide Alert when it is dismissed', async () => { - findFeatureCards().at(1).vm.$emit('error', errorMessage); + findFeatureCards().at(0).vm.$emit('error', errorMessage); await nextTick(); expect(findManageViaMRErrorAlert().exists()).toBe(true); @@ -257,7 +249,6 @@ describe('~/security_configuration/components/app', () => { createComponent({ augmentedSecurityFeatures: securityFeaturesMock, - augmentedComplianceFeatures: complianceFeaturesMock, autoDevopsEnabled: true, }); @@ -285,24 +276,6 @@ describe('~/security_configuration/components/app', () => { latestPipelinePath: 'test/path', }); }); - - it('should show latest pipeline info on the security tab with correct link when latestPipelinePath is defined', () => { - const latestPipelineInfoSecurity = findByTestId('latest-pipeline-info-security'); - - expect(latestPipelineInfoSecurity.text()).toMatchInterpolatedText( - i18n.latestPipelineDescription, - ); - expect(latestPipelineInfoSecurity.find('a').attributes('href')).toBe('test/path'); - }); - - it('should show latest pipeline info on the compliance tab with correct link when latestPipelinePath is defined', () => { - const latestPipelineInfoCompliance = findByTestId('latest-pipeline-info-compliance'); - - expect(latestPipelineInfoCompliance.text()).toMatchInterpolatedText( - i18n.latestPipelineDescription, - ); - expect(latestPipelineInfoCompliance.find('a').attributes('href')).toBe('test/path'); - }); }); describe('given gitlabCiPresent & gitlabCiHistoryPath props', () => { @@ -314,10 +287,8 @@ describe('~/security_configuration/components/app', () => { }); it('should show configuration History Link', () => { - expect(findComplianceViewHistoryLink().exists()).toBe(true); expect(findSecurityViewHistoryLink().exists()).toBe(true); - expect(findComplianceViewHistoryLink().attributes('href')).toBe('test/historyPath'); expect(findSecurityViewHistoryLink().attributes('href')).toBe('test/historyPath'); }); }); diff --git a/spec/frontend/security_configuration/mock_data.js b/spec/frontend/security_configuration/mock_data.js index 24ad8024b01..3d4f01d0da1 100644 --- a/spec/frontend/security_configuration/mock_data.js +++ b/spec/frontend/security_configuration/mock_data.js @@ -4,14 +4,8 @@ import { SAST_DESCRIPTION, SAST_HELP_PATH, SAST_CONFIG_HELP_PATH, - LICENSE_COMPLIANCE_NAME, - LICENSE_COMPLIANCE_DESCRIPTION, - LICENSE_COMPLIANCE_HELP_PATH, } from '~/security_configuration/components/constants'; -import { - REPORT_TYPE_LICENSE_COMPLIANCE, - REPORT_TYPE_SAST, -} from '~/vue_shared/security_reports/constants'; +import { REPORT_TYPE_SAST } from '~/vue_shared/security_reports/constants'; export const testProjectPath = 'foo/bar'; export const testProviderIds = [101, 102, 103]; @@ -128,16 +122,6 @@ export const securityFeaturesMock = [ }, ]; -export const complianceFeaturesMock = [ - { - name: LICENSE_COMPLIANCE_NAME, - description: LICENSE_COMPLIANCE_DESCRIPTION, - helpPath: LICENSE_COMPLIANCE_HELP_PATH, - type: REPORT_TYPE_LICENSE_COMPLIANCE, - configurationHelpPath: LICENSE_COMPLIANCE_HELP_PATH, - }, -]; - export const provideMock = { upgradePath: '/upgrade', autoDevopsHelpPagePath: '/autoDevopsHelpPagePath', diff --git a/spec/frontend/security_configuration/utils_spec.js b/spec/frontend/security_configuration/utils_spec.js index 241e69204d2..6e731e45da2 100644 --- a/spec/frontend/security_configuration/utils_spec.js +++ b/spec/frontend/security_configuration/utils_spec.js @@ -9,13 +9,6 @@ describe('augmentFeatures', () => { }, ]; - const mockComplianceFeatures = [ - { - name: 'LICENSE_COMPLIANCE', - type: 'LICENSE_COMPLIANCE', - }, - ]; - const mockFeaturesWithSecondary = [ { name: 'DAST', @@ -51,30 +44,25 @@ describe('augmentFeatures', () => { const expectedOutputDefault = { augmentedSecurityFeatures: mockSecurityFeatures, - augmentedComplianceFeatures: mockComplianceFeatures, }; const expectedOutputSecondary = { augmentedSecurityFeatures: mockSecurityFeatures, - augmentedComplianceFeatures: mockFeaturesWithSecondary, }; const expectedOutputCustomFeature = { augmentedSecurityFeatures: mockValidCustomFeature, - augmentedComplianceFeatures: mockComplianceFeatures, }; - describe('returns an object with augmentedSecurityFeatures and augmentedComplianceFeatures when', () => { + describe('returns an object with augmentedSecurityFeatures when', () => { it('given an empty array', () => { - expect(augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, [])).toEqual( - expectedOutputDefault, - ); + expect(augmentFeatures(mockSecurityFeatures, [])).toEqual(expectedOutputDefault); }); it('given an invalid populated array', () => { - expect( - augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, mockInvalidCustomFeature), - ).toEqual(expectedOutputDefault); + expect(augmentFeatures(mockSecurityFeatures, mockInvalidCustomFeature)).toEqual( + expectedOutputDefault, + ); }); it('features have secondary key', () => { @@ -84,21 +72,17 @@ describe('augmentFeatures', () => { }); it('given a valid populated array', () => { - expect( - augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, mockValidCustomFeature), - ).toEqual(expectedOutputCustomFeature); + expect(augmentFeatures(mockSecurityFeatures, mockValidCustomFeature)).toEqual( + expectedOutputCustomFeature, + ); }); }); describe('returns an object with camelcased keys', () => { it('given a customfeature in snakecase', () => { - expect( - augmentFeatures( - mockSecurityFeatures, - mockComplianceFeatures, - mockValidCustomFeatureSnakeCase, - ), - ).toEqual(expectedOutputCustomFeature); + expect(augmentFeatures(mockSecurityFeatures, mockValidCustomFeatureSnakeCase)).toEqual( + expectedOutputCustomFeature, + ); }); }); }); diff --git a/spec/frontend/work_items/components/work_item_actions_spec.js b/spec/frontend/work_items/components/work_item_actions_spec.js index 6e8f6defb17..0045abe50d0 100644 --- a/spec/frontend/work_items/components/work_item_actions_spec.js +++ b/spec/frontend/work_items/components/work_item_actions_spec.js @@ -18,10 +18,10 @@ import updateWorkItemNotificationsMutation from '~/work_items/graphql/update_wor import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql'; import convertWorkItemMutation from '~/work_items/graphql/work_item_convert.mutation.graphql'; import { - workItemResponseFactory, convertWorkItemMutationResponse, projectWorkItemTypesQueryResponse, convertWorkItemMutationErrorResponse, + workItemByIidResponseFactory, } from '../mock_data'; jest.mock('~/lib/utils/common_utils'); @@ -211,45 +211,45 @@ describe('WorkItemActions component', () => { describe('notifications action', () => { const errorMessage = 'Failed to subscribe'; + const id = 'gid://gitlab/WorkItem/1'; const notificationToggledOffMessage = 'Notifications turned off.'; const notificationToggledOnMessage = 'Notifications turned on.'; - const workItemQueryResponse = workItemResponseFactory({ canUpdate: true, canDelete: true }); const inputVariablesOff = { - id: workItemQueryResponse.data.workItem.id, + id, notificationsWidget: { subscribed: false, }, }; const inputVariablesOn = { - id: workItemQueryResponse.data.workItem.id, + id, notificationsWidget: { subscribed: true, }, }; - const notificationsOffExpectedResponse = workItemResponseFactory({ + const notificationsOffExpectedResponse = workItemByIidResponseFactory({ subscribed: false, }); const toggleNotificationsOffHandler = jest.fn().mockResolvedValue({ data: { workItemUpdate: { - workItem: notificationsOffExpectedResponse.data.workItem, + workItem: notificationsOffExpectedResponse.data.workspace.workItems.nodes[0], errors: [], }, }, }); - const notificationsOnExpectedResponse = workItemResponseFactory({ + const notificationsOnExpectedResponse = workItemByIidResponseFactory({ subscribed: true, }); const toggleNotificationsOnHandler = jest.fn().mockResolvedValue({ data: { workItemUpdate: { - workItem: notificationsOnExpectedResponse.data.workItem, + workItem: notificationsOnExpectedResponse.data.workspace.workItems.nodes[0], errors: [], }, }, diff --git a/spec/frontend/work_items/components/work_item_assignees_spec.js b/spec/frontend/work_items/components/work_item_assignees_spec.js index af97b3680f9..1e336a928a0 100644 --- a/spec/frontend/work_items/components/work_item_assignees_spec.js +++ b/spec/frontend/work_items/components/work_item_assignees_spec.js @@ -8,9 +8,7 @@ import { mockTracking } from 'helpers/tracking_helper'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql'; import currentUserQuery from '~/graphql_shared/queries/current_user.query.graphql'; -import { config } from '~/graphql_shared/issuable_client'; import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue'; -import workItemQuery from '~/work_items/graphql/work_item.query.graphql'; import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql'; import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue'; import { @@ -22,7 +20,6 @@ import { import { projectMembersResponseWithCurrentUser, mockAssignees, - workItemQueryResponse, currentUserResponse, currentUserNullResponse, projectMembersResponseWithoutCurrentUser, @@ -78,25 +75,11 @@ describe('WorkItemAssignees component', () => { canInviteMembers = false, canUpdate = true, } = {}) => { - const apolloProvider = createMockApollo( - [ - [userSearchQuery, searchQueryHandler], - [currentUserQuery, currentUserQueryHandler], - [updateWorkItemMutation, updateWorkItemMutationHandler], - ], - {}, - { - typePolicies: config.cacheConfig.typePolicies, - }, - ); - - apolloProvider.clients.defaultClient.writeQuery({ - query: workItemQuery, - variables: { - id: workItemId, - }, - data: workItemQueryResponse.data, - }); + const apolloProvider = createMockApollo([ + [userSearchQuery, searchQueryHandler], + [currentUserQuery, currentUserQueryHandler], + [updateWorkItemMutation, updateWorkItemMutationHandler], + ]); wrapper = mountExtended(WorkItemAssignees, { propsData: { diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js index 291dacfd844..630eb78d5c0 100644 --- a/spec/frontend/work_items/components/work_item_detail_spec.js +++ b/spec/frontend/work_items/components/work_item_detail_spec.js @@ -39,7 +39,7 @@ import updateWorkItemTaskMutation from '~/work_items/graphql/update_work_item_ta import { mockParent, workItemDatesSubscriptionResponse, - workItemByIidResponseFactory as workItemResponseFactory, + workItemByIidResponseFactory, workItemTitleSubscriptionResponse, workItemAssigneesSubscriptionResponse, workItemMilestoneSubscriptionResponse, @@ -52,8 +52,8 @@ describe('WorkItemDetail component', () => { Vue.use(VueApollo); - const workItemQueryResponse = workItemResponseFactory({ canUpdate: true, canDelete: true }); - const workItemQueryResponseWithoutParent = workItemResponseFactory({ + const workItemQueryResponse = workItemByIidResponseFactory({ canUpdate: true, canDelete: true }); + const workItemQueryResponseWithoutParent = workItemByIidResponseFactory({ parent: null, canUpdate: true, canDelete: true, @@ -221,7 +221,7 @@ describe('WorkItemDetail component', () => { describe('confidentiality', () => { const errorMessage = 'Mutation failed'; - const confidentialWorkItem = workItemResponseFactory({ + const confidentialWorkItem = workItemByIidResponseFactory({ confidential: true, }); const workItem = confidentialWorkItem.data.workspace.workItems.nodes[0]; @@ -398,7 +398,7 @@ describe('WorkItemDetail component', () => { describe('with parent', () => { beforeEach(() => { - const parentResponse = workItemResponseFactory(mockParent); + const parentResponse = workItemByIidResponseFactory(mockParent); createComponent({ handler: jest.fn().mockResolvedValue(parentResponse) }); return waitForPromises(); @@ -437,7 +437,7 @@ describe('WorkItemDetail component', () => { }, }, }; - const parentResponse = workItemResponseFactory(mockParentObjective); + const parentResponse = workItemByIidResponseFactory(mockParentObjective); createComponent({ handler: jest.fn().mockResolvedValue(parentResponse) }); await waitForPromises(); @@ -492,7 +492,7 @@ describe('WorkItemDetail component', () => { describe('when the assignees widget does not exist', () => { it('does not call the assignees subscription', async () => { - const response = workItemResponseFactory({ assigneesWidgetPresent: false }); + const response = workItemByIidResponseFactory({ assigneesWidgetPresent: false }); const handler = jest.fn().mockResolvedValue(response); createComponent({ handler }); await waitForPromises(); @@ -514,7 +514,7 @@ describe('WorkItemDetail component', () => { describe('when the due date widget does not exist', () => { it('does not call the dates subscription', async () => { - const response = workItemResponseFactory({ datesWidgetPresent: false }); + const response = workItemByIidResponseFactory({ datesWidgetPresent: false }); const handler = jest.fn().mockResolvedValue(response); createComponent({ handler }); await waitForPromises(); @@ -537,7 +537,7 @@ describe('WorkItemDetail component', () => { createComponent({ handler: jest .fn() - .mockResolvedValue(workItemResponseFactory({ assigneesWidgetPresent: false })), + .mockResolvedValue(workItemByIidResponseFactory({ assigneesWidgetPresent: false })), }); await waitForPromises(); @@ -551,7 +551,7 @@ describe('WorkItemDetail component', () => { ${'renders when widget is returned from API'} | ${true} | ${true} ${'does not render when widget is not returned from API'} | ${false} | ${false} `('$description', async ({ labelsWidgetPresent, exists }) => { - const response = workItemResponseFactory({ labelsWidgetPresent }); + const response = workItemByIidResponseFactory({ labelsWidgetPresent }); const handler = jest.fn().mockResolvedValue(response); createComponent({ handler }); await waitForPromises(); @@ -567,7 +567,7 @@ describe('WorkItemDetail component', () => { ${'when widget is not returned from API'} | ${false} | ${false} `('$description', ({ datesWidgetPresent, exists }) => { it(`${datesWidgetPresent ? 'renders' : 'does not render'} due date component`, async () => { - const response = workItemResponseFactory({ datesWidgetPresent }); + const response = workItemByIidResponseFactory({ datesWidgetPresent }); const handler = jest.fn().mockResolvedValue(response); createComponent({ handler }); await waitForPromises(); @@ -594,7 +594,7 @@ describe('WorkItemDetail component', () => { ${'renders when widget is returned from API'} | ${true} | ${true} ${'does not render when widget is not returned from API'} | ${false} | ${false} `('$description', async ({ milestoneWidgetPresent, exists }) => { - const response = workItemResponseFactory({ milestoneWidgetPresent }); + const response = workItemByIidResponseFactory({ milestoneWidgetPresent }); const handler = jest.fn().mockResolvedValue(response); createComponent({ handler }); await waitForPromises(); @@ -614,7 +614,7 @@ describe('WorkItemDetail component', () => { describe('when the assignees widget does not exist', () => { it('does not call the milestone subscription', async () => { - const response = workItemResponseFactory({ milestoneWidgetPresent: false }); + const response = workItemByIidResponseFactory({ milestoneWidgetPresent: false }); const handler = jest.fn().mockResolvedValue(response); createComponent({ handler }); await waitForPromises(); @@ -632,6 +632,13 @@ describe('WorkItemDetail component', () => { expect(successHandler).toHaveBeenCalledWith({ fullPath: 'group/project', iid: '1' }); }); + it('skips the work item query when there is no workItemIid', async () => { + createComponent({ workItemIid: null }); + await waitForPromises(); + + expect(successHandler).not.toHaveBeenCalled(); + }); + it('calls the work item query when isModal=true', async () => { createComponent({ isModal: true }); await waitForPromises(); @@ -648,7 +655,7 @@ describe('WorkItemDetail component', () => { }); describe('work item has children', () => { - const objectiveWorkItem = workItemResponseFactory({ + const objectiveWorkItem = workItemByIidResponseFactory({ workItemType: objectiveType, confidential: true, }); diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js index 2ab31908577..1c319844af3 100644 --- a/spec/frontend/work_items/mock_data.js +++ b/spec/frontend/work_items/mock_data.js @@ -684,87 +684,6 @@ export const createWorkItemMutationErrorResponse = { }, }; -export const createWorkItemFromTaskMutationResponse = { - data: { - workItemCreateFromTask: { - __typename: 'WorkItemCreateFromTaskPayload', - errors: [], - workItem: { - __typename: 'WorkItem', - description: 'New description', - id: 'gid://gitlab/WorkItem/1', - iid: '1', - title: 'Updated title', - state: 'OPEN', - confidential: false, - createdAt: '2022-08-03T12:41:54Z', - closedAt: null, - project: { - __typename: 'Project', - id: '1', - fullPath: 'test-project-path', - archived: false, - }, - workItemType: { - __typename: 'WorkItemType', - id: 'gid://gitlab/WorkItems::Type/5', - name: 'Task', - iconName: 'issue-type-task', - }, - userPermissions: { - deleteWorkItem: false, - updateWorkItem: false, - setWorkItemMetadata: false, - __typename: 'WorkItemPermissions', - }, - widgets: [ - { - __typename: 'WorkItemWidgetDescription', - type: 'DESCRIPTION', - description: 'New description', - descriptionHtml: '<p>New description</p>', - lastEditedAt: '2022-09-21T06:18:42Z', - lastEditedBy: { - name: 'Administrator', - webPath: '/root', - }, - }, - ], - }, - newWorkItem: { - __typename: 'WorkItem', - id: 'gid://gitlab/WorkItem/1000000', - iid: '100', - title: 'Updated title', - state: 'OPEN', - createdAt: '2022-08-03T12:41:54Z', - closedAt: null, - description: '', - confidential: false, - project: { - __typename: 'Project', - id: '1', - fullPath: 'test-project-path', - archived: false, - }, - workItemType: { - __typename: 'WorkItemType', - id: 'gid://gitlab/WorkItems::Type/5', - name: 'Task', - iconName: 'issue-type-task', - }, - userPermissions: { - deleteWorkItem: false, - updateWorkItem: false, - setWorkItemMetadata: false, - __typename: 'WorkItemPermissions', - }, - widgets: [], - }, - }, - }, -}; - export const deleteWorkItemResponse = { data: { workItemDelete: { errors: [], __typename: 'WorkItemDeletePayload' } }, }; @@ -1831,18 +1750,6 @@ export const projectMilestonesResponseWithNoMilestones = { }, }; -export const projectWorkItemResponse = { - data: { - workspace: { - id: 'gid://gitlab/Project/1', - workItems: { - nodes: [workItemQueryResponse.data.workItem], - }, - __typename: 'Project', - }, - }, -}; - export const mockWorkItemNotesResponse = { data: { workItem: { diff --git a/spec/frontend/work_items/router_spec.js b/spec/frontend/work_items/router_spec.js index 86e890ea809..b5d54a7c319 100644 --- a/spec/frontend/work_items/router_spec.js +++ b/spec/frontend/work_items/router_spec.js @@ -6,7 +6,7 @@ import { currentUserResponse, workItemAssigneesSubscriptionResponse, workItemDatesSubscriptionResponse, - workItemByIidResponseFactory as workItemResponseFactory, + workItemByIidResponseFactory, workItemTitleSubscriptionResponse, workItemLabelsSubscriptionResponse, workItemMilestoneSubscriptionResponse, @@ -32,7 +32,7 @@ describe('Work items router', () => { Vue.use(VueApollo); - const workItemQueryHandler = jest.fn().mockResolvedValue(workItemResponseFactory()); + const workItemQueryHandler = jest.fn().mockResolvedValue(workItemByIidResponseFactory()); const currentUserQueryHandler = jest.fn().mockResolvedValue(currentUserResponse); const datesSubscriptionHandler = jest.fn().mockResolvedValue(workItemDatesSubscriptionResponse); const titleSubscriptionHandler = jest.fn().mockResolvedValue(workItemTitleSubscriptionResponse); diff --git a/spec/helpers/sessions_helper_spec.rb b/spec/helpers/sessions_helper_spec.rb index b8290fa2337..5a46a20ce1a 100644 --- a/spec/helpers/sessions_helper_spec.rb +++ b/spec/helpers/sessions_helper_spec.rb @@ -87,4 +87,24 @@ RSpec.describe SessionsHelper do expect(subject).to eq('ma**@e******.com') end end + + describe '#remember_me_enabled?' do + subject { helper.remember_me_enabled? } + + context 'when application setting is enabled' do + before do + stub_application_setting(remember_me_enabled: true) + end + + it { is_expected.to be true } + end + + context 'when application setting is disabled' do + before do + stub_application_setting(remember_me_enabled: false) + end + + it { is_expected.to be false } + end + end end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index a35c9c5e526..e88c89c74dc 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -639,8 +639,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic create(:alert_management_alert, project: project, created_at: n.days.ago) end - stub_application_setting(self_monitoring_project: project) - for_defined_days_back do create(:product_analytics_event, project: project, se_category: 'epics', se_action: 'promote') end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 9f292b0a294..e0bfe41a3ae 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -27,12 +27,6 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do describe 'associations' do it do - is_expected.to belong_to(:self_monitoring_project).class_name('Project') - .with_foreign_key(:instance_administration_project_id) - .inverse_of(:application_setting) - end - - it do is_expected.to belong_to(:instance_group).class_name('Group') .with_foreign_key(:instance_administrators_group_id) .inverse_of(:application_setting) diff --git a/spec/models/integrations/prometheus_spec.rb b/spec/models/integrations/prometheus_spec.rb index aa248abd3bb..a533079f906 100644 --- a/spec/models/integrations/prometheus_spec.rb +++ b/spec/models/integrations/prometheus_spec.rb @@ -90,37 +90,6 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching, end end end - - context 'with self-monitoring project and internal Prometheus' do - before do - integration.api_url = 'http://localhost:9090' - - stub_application_setting(self_monitoring_project_id: project.id) - stub_config(prometheus: { enable: true, server_address: 'localhost:9090' }) - end - - it 'allows self-monitoring project to connect to internal Prometheus' do - aggregate_failures do - ['127.0.0.1', '192.168.2.3'].each do |url| - allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)]) - - expect(integration.can_query?).to be true - end - end - end - - it 'does not allow self-monitoring project to connect to other local URLs' do - integration.api_url = 'http://localhost:8000' - - aggregate_failures do - ['127.0.0.1', '192.168.2.3'].each do |url| - allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)]) - - expect(integration.can_query?).to be false - end - end - end - end end end @@ -218,23 +187,6 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching, it 'blocks local requests' do expect(integration.prometheus_client).to be_nil end - - context 'with self-monitoring project and internal Prometheus URL' do - before do - stub_application_setting(allow_local_requests_from_web_hooks_and_services: false) - stub_application_setting(self_monitoring_project_id: project.id) - - stub_config(prometheus: { - enable: true, - server_address: api_url - }) - end - - it 'allows local requests' do - expect(integration.prometheus_client).not_to be_nil - expect { integration.prometheus_client.ping }.not_to raise_error - end - end end context 'behind IAP' do diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb new file mode 100644 index 00000000000..9966a7132ce --- /dev/null +++ b/spec/models/organization_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# rubocop: disable Lint/EmptyBlock +# rubocop: disable RSpec/EmptyExampleGroup +RSpec.describe Organization, type: :model, feature_category: :cell do +end +# rubocop: enable RSpec/EmptyExampleGroup +# rubocop: enable Lint/EmptyBlock diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f25ff0884b9..b5b7a283e39 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -7551,24 +7551,6 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do end end - describe '#self_monitoring?' do - let_it_be(:project) { create(:project) } - - subject { project.self_monitoring? } - - context 'when the project is instance self-monitoring' do - before do - stub_application_setting(self_monitoring_project_id: project.id) - end - - it { is_expected.to be true } - end - - context 'when the project is not self-monitoring' do - it { is_expected.to be false } - end - end - describe '#add_export_job' do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 60583bc351d..f1d3b17fd6f 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2174,6 +2174,20 @@ RSpec.describe User, feature_category: :user_profile do end end + describe '#sign_in_with_codes_allowed?' do + let_it_be(:user) { create(:user, :two_factor_via_webauthn) } + + context 'when `webauthn_without_totp` disabled' do + before do + stub_feature_flags(webauthn_without_totp: false) + end + + it { expect(user.sign_in_with_codes_allowed?).to eq(false) } + end + + it { expect(user.sign_in_with_codes_allowed?).to eq(true) } + end + describe '#two_factor_otp_enabled?' do let_it_be(:user) { create(:user) } diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index 2b956bec469..48f59ba596b 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -463,16 +463,26 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do end context 'subresources' do - let(:user) { create(:user) } - let(:member_user) { create(:user) } + let_it_be_with_reload(:user) { create(:user) } + let_it_be_with_reload(:member_user) { create(:user) } + + let_it_be_with_reload(:group) { create(:group, :public) } + let_it_be_with_reload(:subgroup) { create(:group, parent: group) } + let_it_be(:private_subgroup) { create(:group, :private, parent: group, name: 'private_subgroup') } + let_it_be(:private_subgroup_with_direct_membership) { create(:group, :private, parent: group) } + let_it_be_with_reload(:subsubgroup) { create(:group, parent: subgroup) } + + let_it_be_with_reload(:group_project) { create(:project, :public, group: group) } + let_it_be_with_reload(:control_project) { create(:project, :private, group: subsubgroup) } + let_it_be_with_reload(:subsubproject) { create(:project, :public, group: subsubgroup) } - let(:group) { create(:group, :public) } - let(:subgroup) { create(:group, parent: group) } - let(:subsubgroup) { create(:group, parent: subgroup) } - let(:subsubproject) { create(:project, group: subsubgroup) } + let_it_be(:private_subgroup_project) do + create(:project, :private, group: private_subgroup, name: 'private_subgroup_project') + end - let(:group_project) { create(:project, :public, group: group) } - let(:control_project) { create(:project, group: subsubgroup) } + let_it_be(:private_subgroup_with_direct_membership_project) do + create(:project, :private, group: private_subgroup_with_direct_membership, name: 'private_subgroup_project') + end context 'with memberships' do before do @@ -481,14 +491,68 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do subsubproject.add_developer(member_user) group_project.add_developer(member_user) control_project.add_maintainer(user) + private_subgroup_with_direct_membership.add_developer(member_user) group.add_owner(user) @group_member = create(:group_member, :developer, group: group, user: member_user) end + let_it_be(:todo_in_public_group_project) do + create(:todo, :pending, + project: group_project, + user: member_user, + target: create(:issue, project: group_project) + ) + end + + let_it_be(:mr_in_public_group_project) do + create(:merge_request, source_project: group_project, assignees: [member_user]) + end + + let_it_be(:todo_in_private_subgroup_project) do + create(:todo, :pending, + project: private_subgroup_project, + user: member_user, + target: create(:issue, project: private_subgroup_project) + ) + end + + let_it_be(:mr_in_private_subgroup_project) do + create(:merge_request, source_project: private_subgroup_project, assignees: [member_user]) + end + + let_it_be(:todo_in_public_subsubgroup_project) do + create(:todo, :pending, + project: subsubproject, + user: member_user, + target: create(:issue, project: subsubproject) + ) + end + + let_it_be(:mr_in_public_subsubgroup_project) do + create(:merge_request, source_project: subsubproject, assignees: [member_user]) + end + + let_it_be(:todo_in_private_subgroup_with_direct_membership_project) do + create(:todo, :pending, + project: private_subgroup_with_direct_membership_project, + user: member_user, + target: create(:issue, project: private_subgroup_with_direct_membership_project) + ) + end + + let_it_be(:mr_in_private_subgroup_with_direct_membership_project) do + create(:merge_request, + source_project: private_subgroup_with_direct_membership_project, + assignees: [member_user] + ) + end + context 'with skipping of subresources' do + subject(:execute_service) { described_class.new(user).execute(@group_member, skip_subresources: true) } + before do - described_class.new(user).execute(@group_member, skip_subresources: true) + execute_service end it 'removes the group membership' do @@ -514,11 +578,35 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do it 'does not remove the user from the control project' do expect(control_project.members.map(&:user)).to include(user) end + + context 'todos', :sidekiq_inline do + it 'removes todos for which the user no longer has access' do + expect(member_user.todos).to include( + todo_in_public_group_project, + todo_in_public_subsubgroup_project, + todo_in_private_subgroup_with_direct_membership_project + ) + + expect(member_user.todos).not_to include(todo_in_private_subgroup_project) + end + end + + context 'issuables', :sidekiq_inline do + subject(:execute_service) do + described_class.new(user).execute(@group_member, skip_subresources: true, unassign_issuables: true) + end + + it 'removes assigned issuables, even in subresources' do + expect(member_user.assigned_merge_requests).to be_empty + end + end end context 'without skipping of subresources' do + subject(:execute_service) { described_class.new(user).execute(@group_member, skip_subresources: false) } + before do - described_class.new(user).execute(@group_member, skip_subresources: false) + execute_service end it 'removes the project membership' do @@ -544,6 +632,30 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do it 'does not remove the user from the control project' do expect(control_project.members.map(&:user)).to include(user) end + + context 'todos', :sidekiq_inline do + it 'removes todos for which the user no longer has access' do + expect(member_user.todos).to include( + todo_in_public_group_project, + todo_in_public_subsubgroup_project + ) + + expect(member_user.todos).not_to include( + todo_in_private_subgroup_project, + todo_in_private_subgroup_with_direct_membership_project + ) + end + end + + context 'issuables', :sidekiq_inline do + subject(:execute_service) do + described_class.new(user).execute(@group_member, skip_subresources: false, unassign_issuables: true) + end + + it 'removes assigned issuables' do + expect(member_user.assigned_merge_requests).to be_empty + end + end end end @@ -626,4 +738,13 @@ RSpec.describe Members::DestroyService, feature_category: :subgroups do expect(project.members.not_accepted_invitations_by_user(member_user)).to be_empty end end + + describe '#mark_as_recursive_call' do + it 'marks the instance as recursive' do + service = described_class.new(current_user) + service.mark_as_recursive_call + + expect(service.send(:recursive_call?)).to eq(true) + end + end end diff --git a/spec/support/helpers/user_login_helper.rb b/spec/support/helpers/user_login_helper.rb index 47e858cb68c..d8368a94ad7 100644 --- a/spec/support/helpers/user_login_helper.rb +++ b/spec/support/helpers/user_login_helper.rb @@ -30,4 +30,20 @@ module UserLoginHelper def ensure_one_active_pane expect(page).to have_selector('.tab-pane.active', count: 1) end + + def ensure_remember_me_in_tab(tab_name) + find_link(tab_name).click + + within '.tab-pane.active' do + expect(page).to have_content _('Remember me') + end + end + + def ensure_remember_me_not_in_tab(tab_name) + find_link(tab_name).click + + within '.tab-pane.active' do + expect(page).not_to have_content _('Remember me') + end + end end diff --git a/spec/tasks/gitlab/db_rake_spec.rb b/spec/tasks/gitlab/db_rake_spec.rb index fb45bc0864d..c2cc6d05630 100644 --- a/spec/tasks/gitlab/db_rake_spec.rb +++ b/spec/tasks/gitlab/db_rake_spec.rb @@ -961,19 +961,17 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout, feature_categor using RSpec::Parameterized::TableSyntax let(:task) { 'gitlab:db:active' } - let(:self_monitoring) { double('self_monitoring') } - where(:needs_migration, :self_monitoring_project, :project_count, :exit_status, :exit_code) do - true | nil | nil | 1 | false - false | :self_monitoring | 1 | 1 | false - false | nil | 0 | 1 | false - false | :self_monitoring | 2 | 0 | true + where(:needs_migration, :project_count, :exit_status, :exit_code) do + true | nil | 1 | false + false | 1 | 0 | true + false | 0 | 1 | false + false | 2 | 0 | true end with_them do it 'exits 0 or 1 depending on user modifications to the database' do allow_any_instance_of(ActiveRecord::MigrationContext).to receive(:needs_migration?).and_return(needs_migration) - allow_any_instance_of(ApplicationSetting).to receive(:self_monitoring_project).and_return(self_monitoring_project) allow(Project).to receive(:count).and_return(project_count) expect { run_rake_task(task) }.to raise_error do |error| |