diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-07 21:10:22 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-07 21:10:22 +0300 |
commit | 4d18bba787186aeb5bf8a0463fd145fae48b3234 (patch) | |
tree | daa26f7daa8bf303ef0cfcc1bfe68d13eae74537 /spec | |
parent | 33c86930e0a657e1519082a9a00faae260a44882 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r-- | spec/frontend/boards/components/boards_selector_spec.js | 3 | ||||
-rw-r--r-- | spec/frontend/boards/components/config_toggle_spec.js | 20 | ||||
-rw-r--r-- | spec/frontend/boards/mock_data.js | 22 | ||||
-rw-r--r-- | spec/frontend/environments/graphql/mock_data.js | 7 | ||||
-rw-r--r-- | spec/frontend/environments/graphql/resolvers_spec.js | 76 | ||||
-rw-r--r-- | spec/frontend/environments/kubernetes_overview_spec.js | 10 | ||||
-rw-r--r-- | spec/frontend/environments/kubernetes_status_bar_spec.js | 142 | ||||
-rw-r--r-- | spec/frontend/environments/new_environment_item_spec.js | 6 | ||||
-rw-r--r-- | spec/frontend/group_settings/components/shared_runners_form_spec.js | 51 | ||||
-rw-r--r-- | spec/frontend/token_access/inbound_token_access_spec.js | 27 | ||||
-rw-r--r-- | spec/frontend/token_access/outbound_token_access_spec.js | 6 | ||||
-rw-r--r-- | spec/helpers/ci/runners_helper_spec.rb | 34 | ||||
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 23 | ||||
-rw-r--r-- | spec/models/project_team_spec.rb | 4 | ||||
-rw-r--r-- | spec/support/fast_quarantine.rb | 7 |
15 files changed, 379 insertions, 59 deletions
diff --git a/spec/frontend/boards/components/boards_selector_spec.js b/spec/frontend/boards/components/boards_selector_spec.js index 74d91eeaa26..50860c6e45b 100644 --- a/spec/frontend/boards/components/boards_selector_spec.js +++ b/spec/frontend/boards/components/boards_selector_spec.js @@ -180,8 +180,7 @@ describe('BoardsSelector', () => { it('shows only matching boards when filtering', async () => { const filterTerm = 'board1'; - const expectedCount = boards.filter((board) => board.node.name.includes(filterTerm)) - .length; + const expectedCount = boards.filter((board) => board.name.includes(filterTerm)).length; fillSearchBox(filterTerm); diff --git a/spec/frontend/boards/components/config_toggle_spec.js b/spec/frontend/boards/components/config_toggle_spec.js index 5330721451e..9f006ebf01d 100644 --- a/spec/frontend/boards/components/config_toggle_spec.js +++ b/spec/frontend/boards/components/config_toggle_spec.js @@ -2,6 +2,7 @@ import Vuex from 'vuex'; import Vue from 'vue'; import { shallowMount } from '@vue/test-utils'; import { GlButton } from '@gitlab/ui'; +import { __ } from '~/locale'; import ConfigToggle from '~/boards/components/config_toggle.vue'; import eventHub from '~/boards/eventhub'; import store from '~/boards/stores'; @@ -12,13 +13,14 @@ describe('ConfigToggle', () => { Vue.use(Vuex); - const createComponent = (provide = {}) => + const createComponent = (provide = {}, props = {}) => shallowMount(ConfigToggle, { store, provide: { canAdminList: true, ...provide, }, + propsData: props, }); const findButton = () => wrapper.findComponent(GlButton); @@ -52,4 +54,20 @@ describe('ConfigToggle', () => { label: 'edit_board', }); }); + + it.each` + boardHasScope + ${true} + ${false} + `('renders dot highlight and tooltip depending on boardHasScope prop', ({ boardHasScope }) => { + wrapper = createComponent({}, { boardHasScope }); + + expect(findButton().classes('dot-highlight')).toBe(boardHasScope); + + if (boardHasScope) { + expect(findButton().attributes('title')).toBe(__("This board's scope is reduced")); + } else { + expect(findButton().attributes('title')).toBe(''); + } + }); }); diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index 8235c3e4194..8f57a6eb7da 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -110,12 +110,10 @@ function boardGenerator(n) { const name = `board${id}`; return { - node: { - id, - name, - weight: 0, - __typename: 'Board', - }, + id, + name, + weight: 0, + __typename: 'Board', }; }); } @@ -127,7 +125,7 @@ export const mockSmallProjectAllBoardsResponse = { data: { project: { id: 'gid://gitlab/Project/114', - boards: { edges: boardGenerator(3) }, + boards: { nodes: boardGenerator(3) }, __typename: 'Project', }, }, @@ -137,7 +135,7 @@ export const mockEmptyProjectRecentBoardsResponse = { data: { project: { id: 'gid://gitlab/Project/114', - recentIssueBoards: { edges: [] }, + recentIssueBoards: { nodes: [] }, __typename: 'Project', }, }, @@ -147,7 +145,7 @@ export const mockGroupAllBoardsResponse = { data: { group: { id: 'gid://gitlab/Group/114', - boards: { edges: boards }, + boards: { nodes: boards }, __typename: 'Group', }, }, @@ -157,7 +155,7 @@ export const mockProjectAllBoardsResponse = { data: { project: { id: 'gid://gitlab/Project/1', - boards: { edges: boards }, + boards: { nodes: boards }, __typename: 'Project', }, }, @@ -167,7 +165,7 @@ export const mockGroupRecentBoardsResponse = { data: { group: { id: 'gid://gitlab/Group/114', - recentIssueBoards: { edges: recentIssueBoards }, + recentIssueBoards: { nodes: recentIssueBoards }, __typename: 'Group', }, }, @@ -177,7 +175,7 @@ export const mockProjectRecentBoardsResponse = { data: { project: { id: 'gid://gitlab/Project/1', - recentIssueBoards: { edges: recentIssueBoards }, + recentIssueBoards: { nodes: recentIssueBoards }, __typename: 'Project', }, }, diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js index c2eafa5f51e..0a0a82c12c1 100644 --- a/spec/frontend/environments/graphql/mock_data.js +++ b/spec/frontend/environments/graphql/mock_data.js @@ -914,3 +914,10 @@ export const k8sNamespacesMock = [ { metadata: { name: 'default' } }, { metadata: { name: 'agent' } }, ]; + +export const fluxKustomizationsMock = [ + { + status: 'True', + type: 'Ready', + }, +]; diff --git a/spec/frontend/environments/graphql/resolvers_spec.js b/spec/frontend/environments/graphql/resolvers_spec.js index be210ed619e..20e96d771f3 100644 --- a/spec/frontend/environments/graphql/resolvers_spec.js +++ b/spec/frontend/environments/graphql/resolvers_spec.js @@ -2,7 +2,11 @@ import MockAdapter from 'axios-mock-adapter'; import { CoreV1Api, AppsV1Api, BatchV1Api } from '@gitlab/cluster-client'; import { s__ } from '~/locale'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; +import { + HTTP_STATUS_INTERNAL_SERVER_ERROR, + HTTP_STATUS_OK, + HTTP_STATUS_UNAUTHORIZED, +} from '~/lib/utils/http_status'; import { resolvers } from '~/environments/graphql/resolvers'; import environmentToRollback from '~/environments/graphql/queries/environment_to_rollback.query.graphql'; import environmentToDelete from '~/environments/graphql/queries/environment_to_delete.query.graphql'; @@ -22,6 +26,7 @@ import { k8sPodsMock, k8sServicesMock, k8sNamespacesMock, + fluxKustomizationsMock, } from './mock_data'; const ENDPOINT = `${TEST_HOST}/environments`; @@ -39,6 +44,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { }, }; const namespace = 'default'; + const environmentName = 'my-environment'; beforeEach(() => { mockResolvers = resolvers(ENDPOINT); @@ -365,6 +371,74 @@ describe('~/frontend/environments/graphql/resolvers', () => { }, ); }); + describe('fluxKustomizationStatus', () => { + const endpoint = `${configuration.basePath}/apis/kustomize.toolkit.fluxcd.io/v1beta1/namespaces/${namespace}/kustomizations/${environmentName}`; + + it('should request Flux Kustomizations via the Kubernetes API', async () => { + mock + .onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers }) + .reply(HTTP_STATUS_OK, { + status: { conditions: fluxKustomizationsMock }, + }); + + const fluxKustomizationStatus = await mockResolvers.Query.fluxKustomizationStatus(null, { + configuration, + namespace, + environmentName, + }); + + expect(fluxKustomizationStatus).toEqual(fluxKustomizationsMock); + }); + it('should throw an error if the API call fails', async () => { + const apiError = 'Invalid credentials'; + mock + .onGet(endpoint, { withCredentials: true, headers: configuration.base }) + .reply(HTTP_STATUS_UNAUTHORIZED, { message: apiError }); + + const fluxKustomizationsError = mockResolvers.Query.fluxKustomizationStatus(null, { + configuration, + namespace, + environmentName, + }); + + await expect(fluxKustomizationsError).rejects.toThrow(apiError); + }); + }); + + describe('fluxHelmReleaseStatus', () => { + const endpoint = `${configuration.basePath}/apis/helm.toolkit.fluxcd.io/v2beta1/namespaces/${namespace}/helmreleases/${environmentName}`; + + it('should request Flux Helm Releases via the Kubernetes API', async () => { + mock + .onGet(endpoint, { withCredentials: true, headers: configuration.baseOptions.headers }) + .reply(HTTP_STATUS_OK, { + status: { conditions: fluxKustomizationsMock }, + }); + + const fluxHelmReleaseStatus = await mockResolvers.Query.fluxHelmReleaseStatus(null, { + configuration, + namespace, + environmentName, + }); + + expect(fluxHelmReleaseStatus).toEqual(fluxKustomizationsMock); + }); + it('should throw an error if the API call fails', async () => { + const apiError = 'Invalid credentials'; + mock + .onGet(endpoint, { withCredentials: true, headers: configuration.base }) + .reply(HTTP_STATUS_UNAUTHORIZED, { message: apiError }); + + const fluxHelmReleasesError = mockResolvers.Query.fluxHelmReleaseStatus(null, { + configuration, + namespace, + environmentName, + }); + + await expect(fluxHelmReleasesError).rejects.toThrow(apiError); + }); + }); + describe('stopEnvironmentREST', () => { it('should post to the stop environment path', async () => { mock.onPost(ENDPOINT).reply(HTTP_STATUS_OK); diff --git a/spec/frontend/environments/kubernetes_overview_spec.js b/spec/frontend/environments/kubernetes_overview_spec.js index 1c7ace00f48..b088fce7e9e 100644 --- a/spec/frontend/environments/kubernetes_overview_spec.js +++ b/spec/frontend/environments/kubernetes_overview_spec.js @@ -6,12 +6,13 @@ import KubernetesAgentInfo from '~/environments/components/kubernetes_agent_info import KubernetesPods from '~/environments/components/kubernetes_pods.vue'; import KubernetesTabs from '~/environments/components/kubernetes_tabs.vue'; import KubernetesStatusBar from '~/environments/components/kubernetes_status_bar.vue'; -import { agent, kubernetesNamespace } from './graphql/mock_data'; +import { agent, kubernetesNamespace, resolvedEnvironment } from './graphql/mock_data'; import { mockKasTunnelUrl } from './mock_data'; const propsData = { clusterAgent: agent, namespace: kubernetesNamespace, + environmentName: resolvedEnvironment.name, }; const provide = { @@ -110,7 +111,12 @@ describe('~/environments/components/kubernetes_overview.vue', () => { }); it('renders kubernetes status bar', () => { - expect(findKubernetesStatusBar().exists()).toBe(true); + expect(findKubernetesStatusBar().props()).toEqual({ + clusterHealthStatus: 'success', + configuration, + namespace: kubernetesNamespace, + environmentName: resolvedEnvironment.name, + }); }); }); diff --git a/spec/frontend/environments/kubernetes_status_bar_spec.js b/spec/frontend/environments/kubernetes_status_bar_spec.js index 2ebb30e2766..ad32818232a 100644 --- a/spec/frontend/environments/kubernetes_status_bar_spec.js +++ b/spec/frontend/environments/kubernetes_status_bar_spec.js @@ -1,20 +1,58 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlLoadingIcon, GlBadge } from '@gitlab/ui'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { GlLoadingIcon } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import KubernetesStatusBar from '~/environments/components/kubernetes_status_bar.vue'; import { CLUSTER_STATUS_HEALTHY_TEXT, CLUSTER_STATUS_UNHEALTHY_TEXT, + SYNC_STATUS_BADGES, } from '~/environments/constants'; +import waitForPromises from 'helpers/wait_for_promises'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { s__ } from '~/locale'; +import { mockKasTunnelUrl } from './mock_data'; + +Vue.use(VueApollo); + +const configuration = { + basePath: mockKasTunnelUrl.replace(/\/$/, ''), + baseOptions: { + headers: { 'GitLab-Agent-Id': '1' }, + withCredentials: true, + }, +}; +const environmentName = 'environment_name'; describe('~/environments/components/kubernetes_status_bar.vue', () => { let wrapper; const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); - const findHealthBadge = () => wrapper.findComponent(GlBadge); + const findHealthBadge = () => wrapper.findByTestId('health-badge'); + const findSyncBadge = () => wrapper.findByTestId('sync-badge'); + + const fluxKustomizationStatusQuery = jest.fn().mockReturnValue([]); + const fluxHelmReleaseStatusQuery = jest.fn().mockReturnValue([]); + + const createApolloProvider = () => { + const mockResolvers = { + Query: { + fluxKustomizationStatus: fluxKustomizationStatusQuery, + fluxHelmReleaseStatus: fluxHelmReleaseStatusQuery, + }, + }; + + return createMockApollo([], mockResolvers); + }; - const createWrapper = ({ clusterHealthStatus = '' } = {}) => { - wrapper = shallowMount(KubernetesStatusBar, { - propsData: { clusterHealthStatus }, + const createWrapper = ({ + apolloProvider = createApolloProvider(), + clusterHealthStatus = '', + namespace = '', + } = {}) => { + wrapper = shallowMountExtended(KubernetesStatusBar, { + propsData: { clusterHealthStatus, configuration, environmentName, namespace }, + apolloProvider, }); }; @@ -39,4 +77,96 @@ describe('~/environments/components/kubernetes_status_bar.vue', () => { }, ); }); + + describe('sync badge', () => { + describe('when no namespace is provided', () => { + beforeEach(() => { + createWrapper(); + }); + + it("doesn't request Kustomizations and HelmReleases", () => { + expect(fluxKustomizationStatusQuery).not.toHaveBeenCalled(); + expect(fluxHelmReleaseStatusQuery).not.toHaveBeenCalled(); + }); + + it('renders sync status as Unavailable', () => { + expect(findSyncBadge().text()).toBe(s__('Deployment|Unavailable')); + }); + }); + + describe('when namespace is provided', () => { + describe('with no Flux resources found', () => { + beforeEach(() => { + createWrapper({ namespace: 'my-namespace' }); + }); + + it('requests Kustomizations', () => { + expect(fluxKustomizationStatusQuery).toHaveBeenCalled(); + }); + + it('requests HelmReleases when there were no Kustomizations found', async () => { + await waitForPromises(); + + expect(fluxHelmReleaseStatusQuery).toHaveBeenCalled(); + }); + + it('renders sync status as Unavailable when no Kustomizations and HelmReleases found', async () => { + await waitForPromises(); + + expect(findSyncBadge().text()).toBe(s__('Deployment|Unavailable')); + }); + }); + + describe('with Flux Kustomizations available', () => { + const createApolloProviderWithKustomizations = ({ + result = { status: 'True', type: 'Ready' }, + } = {}) => { + const mockResolvers = { + Query: { + fluxKustomizationStatus: jest.fn().mockReturnValue([result]), + fluxHelmReleaseStatus: fluxHelmReleaseStatusQuery, + }, + }; + + return createMockApollo([], mockResolvers); + }; + + it("doesn't request HelmReleases when the Kustomizations were found", async () => { + createWrapper({ + apolloProvider: createApolloProviderWithKustomizations(), + namespace: 'my-namespace', + }); + await waitForPromises(); + + expect(fluxHelmReleaseStatusQuery).not.toHaveBeenCalled(); + }); + + it.each` + status | type | badgeType + ${'True'} | ${'Stalled'} | ${'stalled'} + ${'True'} | ${'Reconciling'} | ${'reconciling'} + ${'True'} | ${'Ready'} | ${'reconciled'} + ${'False'} | ${'Ready'} | ${'failed'} + ${'True'} | ${'Unknown'} | ${'unknown'} + `( + 'renders $badgeType when status is $status and type is $type', + async ({ status, type, badgeType }) => { + createWrapper({ + apolloProvider: createApolloProviderWithKustomizations({ result: { status, type } }), + namespace: 'my-namespace', + }); + await waitForPromises(); + + const badge = SYNC_STATUS_BADGES[badgeType]; + + expect(findSyncBadge().text()).toBe(badge.text); + expect(findSyncBadge().props()).toMatchObject({ + icon: badge.icon, + variant: badge.variant, + }); + }, + ); + }); + }); + }); }); diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js index 387bc31c9aa..2f319855bca 100644 --- a/spec/frontend/environments/new_environment_item_spec.js +++ b/spec/frontend/environments/new_environment_item_spec.js @@ -534,7 +534,7 @@ describe('~/environments/components/new_environment_item.vue', () => { }); describe('kubernetes overview', () => { - it('should request agent data when the environment is visible if the feature flag is enabled', async () => { + it('should request agent data when the environment is visible', async () => { wrapper = createWrapper({ propsData: { environment: resolvedEnvironment }, apolloProvider: createApolloProvider(agent), @@ -578,6 +578,7 @@ describe('~/environments/components/new_environment_item.vue', () => { expect(findKubernetesOverview().props()).toMatchObject({ clusterAgent: agent, + environmentName: resolvedEnvironment.name, }); }); @@ -595,8 +596,9 @@ describe('~/environments/components/new_environment_item.vue', () => { await expandCollapsedSection(); await waitForPromises(); - expect(findKubernetesOverview().props()).toMatchObject({ + expect(findKubernetesOverview().props()).toEqual({ clusterAgent: agent, + environmentName: resolvedEnvironment.name, namespace: 'default', }); }); diff --git a/spec/frontend/group_settings/components/shared_runners_form_spec.js b/spec/frontend/group_settings/components/shared_runners_form_spec.js index 5daa21fd618..b39b9a62661 100644 --- a/spec/frontend/group_settings/components/shared_runners_form_spec.js +++ b/spec/frontend/group_settings/components/shared_runners_form_spec.js @@ -1,5 +1,6 @@ -import { GlAlert } from '@gitlab/ui'; +import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui'; import { nextTick } from 'vue'; +import { s__, sprintf } from '~/locale'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; @@ -17,6 +18,9 @@ const RUNNER_ENABLED_VALUE = 'enabled'; const RUNNER_DISABLED_VALUE = 'disabled_and_unoverridable'; const RUNNER_ALLOW_OVERRIDE_VALUE = 'disabled_and_overridable'; +const mockParentName = 'My group'; +const mockParentSettingsPath = '/groups/my-group/-/settings/ci_cd'; + describe('group_settings/components/shared_runners_form', () => { let wrapper; @@ -27,20 +31,19 @@ describe('group_settings/components/shared_runners_form', () => { groupName: GROUP_NAME, groupIsEmpty: false, sharedRunnersSetting: RUNNER_ENABLED_VALUE, - parentSharedRunnersSetting: null, + runnerEnabledValue: RUNNER_ENABLED_VALUE, runnerDisabledValue: RUNNER_DISABLED_VALUE, runnerAllowOverrideValue: RUNNER_ALLOW_OVERRIDE_VALUE, ...provide, }, + stubs: { + GlSprintf, + }, }); }; - const findAlert = (variant) => - wrapper - .findAllComponents(GlAlert) - .filter((w) => w.props('variant') === variant) - .at(0); + const findAlert = () => wrapper.findComponent(GlAlert); const findSharedRunnersToggle = () => wrapper.findByTestId('shared-runners-toggle'); const findOverrideToggle = () => wrapper.findByTestId('override-runners-toggle'); const getSharedRunnersSetting = () => { @@ -86,17 +89,37 @@ describe('group_settings/components/shared_runners_form', () => { }); }); - describe('When parent group disabled shared runners', () => { - it('toggles are disabled', () => { + describe.each` + provide | case | isParentLinkExpected + ${{ parentName: mockParentName, parentSettingsPath: mockParentSettingsPath }} | ${'can configure parent'} | ${true} + ${{}} | ${'cannot configure parent'} | ${false} + `('When parent group disabled shared runners and $case', ({ provide, isParentLinkExpected }) => { + beforeEach(() => { createComponent({ sharedRunnersSetting: RUNNER_DISABLED_VALUE, parentSharedRunnersSetting: RUNNER_DISABLED_VALUE, + ...provide, }); - - expect(findSharedRunnersToggle().props('disabled')).toBe(true); - expect(findOverrideToggle().props('disabled')).toBe(true); - expect(findAlert('warning').exists()).toBe(true); }); + + it.each([findSharedRunnersToggle, findOverrideToggle])( + 'toggle %# is disabled', + (findToggle) => { + expect(findToggle().props('disabled')).toBe(true); + expect(findToggle().text()).toContain(s__('Runners|Shared runners are disabled.')); + + if (isParentLinkExpected) { + expect(findToggle().text()).toContain( + sprintf(s__('Runners|Go to %{groupLink} to enable them.'), { + groupLink: mockParentName, + }), + ); + const link = findToggle().findComponent(GlLink); + expect(link.text()).toBe(mockParentName); + expect(link.attributes('href')).toBe(mockParentSettingsPath); + } + }, + ); }); describe('loading state', () => { @@ -240,7 +263,7 @@ describe('group_settings/components/shared_runners_form', () => { }); it('error should be shown', () => { - expect(findAlert('danger').text()).toBe(message); + expect(findAlert().text()).toBe(message); }); }); }); diff --git a/spec/frontend/token_access/inbound_token_access_spec.js b/spec/frontend/token_access/inbound_token_access_spec.js index 1ca58053e68..d82d65e3549 100644 --- a/spec/frontend/token_access/inbound_token_access_spec.js +++ b/spec/frontend/token_access/inbound_token_access_spec.js @@ -21,6 +21,7 @@ import { } from './mock_data'; const projectPath = 'root/my-repo'; +const testProjectPath = 'root/test'; const message = 'An error occurred'; const error = new Error(message); @@ -53,10 +54,11 @@ describe('TokenAccess component', () => { const findToggle = () => wrapper.findComponent(GlToggle); const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); - const findAddProjectBtn = () => wrapper.findByRole('button', { name: 'Add project' }); + const findAddProjectBtn = () => wrapper.findByTestId('add-project-btn'); const findCancelBtn = () => wrapper.findByRole('button', { name: 'Cancel' }); const findProjectInput = () => wrapper.findComponent(GlFormInput); const findRemoveProjectBtn = () => wrapper.findByRole('button', { name: 'Remove access' }); + const findToggleFormBtn = () => wrapper.findByTestId('toggle-form-btn'); const findTokenDisabledAlert = () => wrapper.findComponent(GlAlert); const createMockApolloProvider = (requestHandlers) => { @@ -69,11 +71,6 @@ describe('TokenAccess component', () => { fullPath: projectPath, }, apolloProvider: createMockApolloProvider(requestHandlers), - data() { - return { - targetProjectPath: 'root/test', - }; - }, }); }; @@ -222,11 +219,13 @@ describe('TokenAccess component', () => { await waitForPromises(); + await findToggleFormBtn().trigger('click'); + await findProjectInput().vm.$emit('input', testProjectPath); findAddProjectBtn().trigger('click'); expect(inboundAddProjectSuccessResponseHandler).toHaveBeenCalledWith({ projectPath, - targetProjectPath: 'root/test', + targetProjectPath: testProjectPath, }); }); @@ -242,6 +241,8 @@ describe('TokenAccess component', () => { await waitForPromises(); + await findToggleFormBtn().trigger('click'); + await findProjectInput().vm.$emit('input', testProjectPath); findAddProjectBtn().trigger('click'); await waitForPromises(); @@ -249,7 +250,7 @@ describe('TokenAccess component', () => { expect(createAlert).toHaveBeenCalledWith({ message }); }); - it('clicking cancel clears target path', async () => { + it('clicking cancel hides the form and clears the target path', async () => { createComponent( [ [inboundGetCIJobTokenScopeQuery, inboundJobTokenScopeEnabledResponseHandler], @@ -260,10 +261,18 @@ describe('TokenAccess component', () => { await waitForPromises(); - expect(findProjectInput().element.value).toBe('root/test'); + await findToggleFormBtn().trigger('click'); + + expect(findProjectInput().exists()).toBe(true); + + await findProjectInput().vm.$emit('input', testProjectPath); await findCancelBtn().trigger('click'); + expect(findProjectInput().exists()).toBe(false); + + await findToggleFormBtn().trigger('click'); + expect(findProjectInput().element.value).toBe(''); }); }); diff --git a/spec/frontend/token_access/outbound_token_access_spec.js b/spec/frontend/token_access/outbound_token_access_spec.js index f9eb201eb5c..c5224d5d942 100644 --- a/spec/frontend/token_access/outbound_token_access_spec.js +++ b/spec/frontend/token_access/outbound_token_access_spec.js @@ -38,9 +38,9 @@ describe('TokenAccess component', () => { const findToggle = () => wrapper.findComponent(GlToggle); const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findAddProjectBtn = () => wrapper.findByRole('button', { name: 'Add project' }); const findRemoveProjectBtn = () => wrapper.findByRole('button', { name: 'Remove access' }); const findDeprecationAlert = () => wrapper.findByTestId('deprecation-alert'); - const findProjectPathInput = () => wrapper.findByTestId('project-path-input'); const createMockApolloProvider = (requestHandlers) => { return createMockApollo(requestHandlers); @@ -247,7 +247,7 @@ describe('TokenAccess component', () => { }); describe('adding a new project', () => { - it('disables the input to add new projects', async () => { + it('disables the button for adding new projects', async () => { createComponent( [ [getCIJobTokenScopeQuery, disabledJobTokenScopeHandler], @@ -260,7 +260,7 @@ describe('TokenAccess component', () => { await waitForPromises(); - expect(findProjectPathInput().attributes('disabled')).toBe('disabled'); + expect(findAddProjectBtn().attributes('disabled')).toBe('disabled'); }); }); }); diff --git a/spec/helpers/ci/runners_helper_spec.rb b/spec/helpers/ci/runners_helper_spec.rb index dc593228388..4f47d7c936f 100644 --- a/spec/helpers/ci/runners_helper_spec.rb +++ b/spec/helpers/ci/runners_helper_spec.rb @@ -115,12 +115,19 @@ RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do } end + before do + allow(helper).to receive(:can?).with(user, :admin_group, parent).and_return(true) + end + it 'returns group data for top level group' do result = { group_id: parent.id, group_name: parent.name, group_is_empty: 'false', shared_runners_setting: Namespace::SR_ENABLED, + + parent_name: nil, + parent_settings_path: nil, parent_shared_runners_setting: nil }.merge(runner_constants) @@ -133,7 +140,27 @@ RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do group_name: group.name, group_is_empty: 'true', shared_runners_setting: Namespace::SR_DISABLED_AND_UNOVERRIDABLE, - parent_shared_runners_setting: Namespace::SR_ENABLED + + parent_shared_runners_setting: Namespace::SR_ENABLED, + parent_name: parent.name, + parent_settings_path: group_settings_ci_cd_path(group.parent, anchor: 'js-runner-settings') + }.merge(runner_constants) + + expect(helper.group_shared_runners_settings_data(group)).to eq result + end + + it 'returns groups data for child group with no access to parent' do + allow(helper).to receive(:can?).with(user, :admin_group, parent).and_return(false) + + result = { + group_id: group.id, + group_name: group.name, + group_is_empty: 'true', + shared_runners_setting: Namespace::SR_DISABLED_AND_UNOVERRIDABLE, + + parent_shared_runners_setting: Namespace::SR_ENABLED, + parent_name: nil, + parent_settings_path: nil }.merge(runner_constants) expect(helper.group_shared_runners_settings_data(group)).to eq result @@ -145,7 +172,10 @@ RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do group_name: group_with_project.name, group_is_empty: 'false', shared_runners_setting: Namespace::SR_ENABLED, - parent_shared_runners_setting: Namespace::SR_ENABLED + + parent_shared_runners_setting: Namespace::SR_ENABLED, + parent_name: parent.name, + parent_settings_path: group_settings_ci_cd_path(group.parent, anchor: 'js-runner-settings') }.merge(runner_constants) expect(helper.group_shared_runners_settings_data(group_with_project)).to eq result diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 1de19faf063..c4b8f4f0145 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -279,6 +279,29 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category: end end + describe '.for_status' do + subject { described_class.for_status(status) } + + let_it_be(:pipeline1) { create(:ci_pipeline, name: 'Build pipeline', status: :created) } + let_it_be(:pipeline2) { create(:ci_pipeline, name: 'Chatops pipeline', status: :failed) } + + context 'when status exists' do + let(:status) { :created } + + it 'performs exact compare' do + is_expected.to contain_exactly(pipeline1) + end + end + + context 'when status does not exist' do + let(:status) { :pending } + + it 'returns empty' do + is_expected.to be_empty + end + end + end + describe '.created_after' do let_it_be(:old_pipeline) { create(:ci_pipeline, created_at: 1.week.ago) } let_it_be(:pipeline) { create(:ci_pipeline) } diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index a6766035c1e..e557990c7e9 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -180,8 +180,8 @@ RSpec.describe ProjectTeam, feature_category: :groups_and_projects do subject(:import) { target_project.team.import(source_project, current_user) } - it 'matches the imported members', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/419394' do - is_expected.to match(imported_members) + it 'matches the imported members' do + is_expected.to match_array(imported_members) end it 'target project includes source member with the same access' do diff --git a/spec/support/fast_quarantine.rb b/spec/support/fast_quarantine.rb index b5ed1a2aa96..9732a287cb2 100644 --- a/spec/support/fast_quarantine.rb +++ b/spec/support/fast_quarantine.rb @@ -7,10 +7,9 @@ return if ENV['CI_MERGE_REQUEST_LABELS'].to_s.include?('pipeline:run-flaky-tests require_relative '../../tooling/lib/tooling/fast_quarantine' RSpec.configure do |config| - fast_quarantine_local_path = ENV.fetch('RSPEC_FAST_QUARANTINE_LOCAL_PATH', 'rspec/fast_quarantine-gitlab.txt') fast_quarantine_path = ENV.fetch( 'RSPEC_FAST_QUARANTINE_PATH', - File.expand_path("../../#{fast_quarantine_local_path}", __dir__) + File.expand_path("../../rspec/fast_quarantine-gitlab.txt", __dir__) ) fast_quarantine = Tooling::FastQuarantine.new(fast_quarantine_path: fast_quarantine_path) skipped_examples = [] @@ -28,10 +27,12 @@ RSpec.configure do |config| next if skipped_examples.empty? skipped_tests_report_path = ENV.fetch( - 'SKIPPED_TESTS_REPORT_PATH', + 'RSPEC_SKIPPED_TESTS_REPORT_PATH', File.expand_path("../../rspec/flaky/skipped_tests.txt", __dir__) ) + next warn("#{skipped_tests_report_path} doesn't exist!") unless File.exist?(skipped_tests_report_path.to_s) + File.write(skipped_tests_report_path, "#{ENV.fetch('CI_JOB_URL', 'local-run')}\n#{skipped_examples.join("\n")}\n\n") end end |