diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-08 00:12:55 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-08 00:12:55 +0300 |
commit | 2cf4bdd0b060175c7058b395014b101fbe6214a3 (patch) | |
tree | f3aaeca09cc434632c8eb2c2998877c3f9a2e0d9 /spec/frontend | |
parent | 1bdc6c89c32a7380a81598629b9ad05ba9a2a94f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
9 files changed, 570 insertions, 140 deletions
diff --git a/spec/frontend/environments/environment_flux_resource_selector_spec.js b/spec/frontend/environments/environment_flux_resource_selector_spec.js index ba3375c731f..8dab8fdd96a 100644 --- a/spec/frontend/environments/environment_flux_resource_selector_spec.js +++ b/spec/frontend/environments/environment_flux_resource_selector_spec.js @@ -25,7 +25,7 @@ const DEFAULT_PROPS = { fluxResourcePath: '', }; -describe('~/environments/components/form.vue', () => { +describe('~/environments/components/flux_resource_selector.vue', () => { let wrapper; const kustomizationItem = { diff --git a/spec/frontend/environments/environment_form_spec.js b/spec/frontend/environments/environment_form_spec.js index 478ac8d6e0e..f3dfc7a72f2 100644 --- a/spec/frontend/environments/environment_form_spec.js +++ b/spec/frontend/environments/environment_form_spec.js @@ -1,11 +1,12 @@ -import { GlLoadingIcon, GlAlert } from '@gitlab/ui'; -import Vue from 'vue'; +import { GlLoadingIcon } from '@gitlab/ui'; +import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import waitForPromises from 'helpers/wait_for_promises'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import EnvironmentForm from '~/environments/components/environment_form.vue'; import getUserAuthorizedAgents from '~/environments/graphql/queries/user_authorized_agents.query.graphql'; import EnvironmentFluxResourceSelector from '~/environments/components/environment_flux_resource_selector.vue'; +import EnvironmentNamespaceSelector from '~/environments/components/environment_namespace_selector.vue'; import createMockApollo from '../__helpers__/mock_apollo_helper'; import { mockKasTunnelUrl } from './mock_data'; @@ -36,13 +37,16 @@ const configuration = { credentials: 'include', }; +const environmentWithAgentAndNamespace = { + ...DEFAULT_PROPS.environment, + clusterAgent: { id: '12', name: 'agent-2' }, + clusterAgentId: '2', + kubernetesNamespace: 'agent', +}; + describe('~/environments/components/form.vue', () => { let wrapper; - const getNamespacesQueryResult = jest - .fn() - .mockReturnValue([{ metadata: { name: 'default' } }, { metadata: { name: 'agent' } }]); - const createWrapper = (propsData = {}, options = {}) => mountExtended(EnvironmentForm, { provide: PROVIDE, @@ -53,7 +57,7 @@ describe('~/environments/components/form.vue', () => { }, }); - const createWrapperWithApollo = ({ propsData = {}, queryResult = null } = {}) => { + const createWrapperWithApollo = (propsData = {}) => { Vue.use(VueApollo); const requestHandlers = [ @@ -70,12 +74,6 @@ describe('~/environments/components/form.vue', () => { ], ]; - const mockResolvers = { - Query: { - k8sNamespaces: queryResult || getNamespacesQueryResult, - }, - }; - return mountExtended(EnvironmentForm, { provide: { ...PROVIDE, @@ -84,13 +82,12 @@ describe('~/environments/components/form.vue', () => { ...DEFAULT_PROPS, ...propsData, }, - apolloProvider: createMockApollo(requestHandlers, mockResolvers), + apolloProvider: createMockApollo(requestHandlers, []), }); }; const findAgentSelector = () => wrapper.findByTestId('agent-selector'); - const findNamespaceSelector = () => wrapper.findByTestId('namespace-selector'); - const findAlert = () => wrapper.findComponent(GlAlert); + const findNamespaceSelector = () => wrapper.findComponent(EnvironmentNamespaceSelector); const findFluxResourceSelector = () => wrapper.findComponent(EnvironmentFluxResourceSelector); const selectAgent = async () => { @@ -326,91 +323,15 @@ describe('~/environments/components/form.vue', () => { expect(findNamespaceSelector().exists()).toBe(true); }); - it('requests the kubernetes namespaces with the correct configuration', async () => { - await waitForPromises(); - - expect(getNamespacesQueryResult).toHaveBeenCalledWith( - {}, - { configuration }, - expect.anything(), - expect.anything(), - ); - }); - - it('sets the loading prop while fetching the list', async () => { - expect(findNamespaceSelector().props('loading')).toBe(true); - - await waitForPromises(); - - expect(findNamespaceSelector().props('loading')).toBe(false); - }); - - it('renders a list of available namespaces', async () => { - await waitForPromises(); - - expect(findNamespaceSelector().props('items')).toEqual([ - { text: 'default', value: 'default' }, - { text: 'agent', value: 'agent' }, - ]); - }); - - it('filters the namespaces list on user search', async () => { - await waitForPromises(); - await findNamespaceSelector().vm.$emit('search', 'default'); - - expect(findNamespaceSelector().props('items')).toEqual([ - { value: 'default', text: 'default' }, - ]); - }); - - it('updates namespace selector field with the name of selected namespace', async () => { - await waitForPromises(); - await findNamespaceSelector().vm.$emit('select', 'agent'); - - expect(findNamespaceSelector().props('toggleText')).toBe('agent'); - }); - it('emits changes to the kubernetesNamespace', async () => { await waitForPromises(); - await findNamespaceSelector().vm.$emit('select', 'agent'); + findNamespaceSelector().vm.$emit('change', 'agent'); + await nextTick(); expect(wrapper.emitted('change')[1]).toEqual([ { name: '', externalUrl: '', kubernetesNamespace: 'agent', fluxResourcePath: null }, ]); }); - - it('clears namespace selector when another agent was selected', async () => { - await waitForPromises(); - await findNamespaceSelector().vm.$emit('select', 'agent'); - - expect(findNamespaceSelector().props('toggleText')).toBe('agent'); - - await findAgentSelector().vm.$emit('select', '1'); - expect(findNamespaceSelector().props('toggleText')).toBe( - EnvironmentForm.i18n.namespaceHelpText, - ); - }); - }); - - describe('when cannot connect to the cluster', () => { - const error = new Error('Error from the cluster_client API'); - - beforeEach(async () => { - wrapper = createWrapperWithApollo({ - queryResult: jest.fn().mockRejectedValueOnce(error), - }); - - await selectAgent(); - await waitForPromises(); - }); - - it("doesn't render the namespace selector", () => { - expect(findNamespaceSelector().exists()).toBe(false); - }); - - it('renders an alert', () => { - expect(findAlert().text()).toBe('Error from the cluster_client API'); - }); }); }); @@ -431,16 +352,6 @@ describe('~/environments/components/form.vue', () => { it("doesn't render flux resource selector", () => { expect(findFluxResourceSelector().exists()).toBe(false); }); - - it('renders the flux resource selector when the namespace is selected', async () => { - await findNamespaceSelector().vm.$emit('select', 'agent'); - - expect(findFluxResourceSelector().props()).toEqual({ - namespace: 'agent', - fluxResourcePath: '', - configuration, - }); - }); }); }); @@ -451,9 +362,7 @@ describe('~/environments/components/form.vue', () => { clusterAgentId: '1', }; beforeEach(() => { - wrapper = createWrapperWithApollo({ - propsData: { environment: environmentWithAgent }, - }); + wrapper = createWrapperWithApollo({ environment: environmentWithAgent }); }); it('updates agent selector field with the name of the associated agent', () => { @@ -468,45 +377,46 @@ describe('~/environments/components/form.vue', () => { it('renders a list of available namespaces', async () => { await waitForPromises(); - expect(findNamespaceSelector().props('items')).toEqual([ - { text: 'default', value: 'default' }, - { text: 'agent', value: 'agent' }, - ]); + expect(findNamespaceSelector().exists()).toBe(true); }); }); describe('when environment has an associated kubernetes namespace', () => { - const environmentWithAgentAndNamespace = { - ...DEFAULT_PROPS.environment, - clusterAgent: { id: '1', name: 'agent-1' }, - clusterAgentId: '1', - kubernetesNamespace: 'default', - }; beforeEach(() => { - wrapper = createWrapperWithApollo({ - propsData: { environment: environmentWithAgentAndNamespace }, - }); + wrapper = createWrapperWithApollo({ environment: environmentWithAgentAndNamespace }); }); it('updates namespace selector with the name of the associated namespace', async () => { await waitForPromises(); - expect(findNamespaceSelector().props('toggleText')).toBe('default'); + expect(findNamespaceSelector().props('namespace')).toBe('agent'); + }); + + it('clears namespace selector when another agent was selected', async () => { + expect(findNamespaceSelector().props('namespace')).toBe('agent'); + + findAgentSelector().vm.$emit('select', '1'); + await nextTick(); + + expect(findNamespaceSelector().props('namespace')).toBe(null); + }); + + it('renders the flux resource selector when the namespace is selected', () => { + expect(findFluxResourceSelector().props()).toEqual({ + namespace: 'agent', + fluxResourcePath: '', + configuration, + }); }); }); describe('when environment has an associated flux resource', () => { const fluxResourcePath = 'path/to/flux/resource'; - const environmentWithAgentAndNamespace = { - ...DEFAULT_PROPS.environment, - clusterAgent: { id: '1', name: 'agent-1' }, - clusterAgentId: '1', - kubernetesNamespace: 'default', + const environmentWithFluxResource = { + ...environmentWithAgentAndNamespace, fluxResourcePath, }; beforeEach(() => { - wrapper = createWrapperWithApollo({ - propsData: { environment: environmentWithAgentAndNamespace }, - }); + wrapper = createWrapperWithApollo({ environment: environmentWithFluxResource }); }); it('provides flux resource path to the flux resource selector component', () => { diff --git a/spec/frontend/environments/environment_namespace_selector_spec.js b/spec/frontend/environments/environment_namespace_selector_spec.js new file mode 100644 index 00000000000..53e4f807751 --- /dev/null +++ b/spec/frontend/environments/environment_namespace_selector_spec.js @@ -0,0 +1,217 @@ +import { GlAlert, GlCollapsibleListbox, GlButton } from '@gitlab/ui'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import { shallowMount } from '@vue/test-utils'; +import waitForPromises from 'helpers/wait_for_promises'; +import EnvironmentNamespaceSelector from '~/environments/components/environment_namespace_selector.vue'; +import { stubComponent } from 'helpers/stub_component'; +import createMockApollo from '../__helpers__/mock_apollo_helper'; +import { mockKasTunnelUrl } from './mock_data'; + +const configuration = { + basePath: mockKasTunnelUrl.replace(/\/$/, ''), + headers: { + 'GitLab-Agent-Id': 2, + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + credentials: 'include', +}; + +const DEFAULT_PROPS = { + namespace: '', + configuration, +}; + +describe('~/environments/components/namespace_selector.vue', () => { + let wrapper; + + const getNamespacesQueryResult = jest + .fn() + .mockReturnValue([ + { metadata: { name: 'default' } }, + { metadata: { name: 'agent' } }, + { metadata: { name: 'test-agent' } }, + ]); + + const closeMock = jest.fn(); + + const createWrapper = ({ propsData = {}, queryResult = null } = {}) => { + Vue.use(VueApollo); + + const mockResolvers = { + Query: { + k8sNamespaces: queryResult || getNamespacesQueryResult, + }, + }; + + return shallowMount(EnvironmentNamespaceSelector, { + propsData: { + ...DEFAULT_PROPS, + ...propsData, + }, + stubs: { + GlCollapsibleListbox: stubComponent(GlCollapsibleListbox, { + template: `<div><slot name="footer"></slot></div>`, + methods: { + close: closeMock, + }, + }), + }, + apolloProvider: createMockApollo([], mockResolvers), + }); + }; + + const findNamespaceSelector = () => wrapper.findComponent(GlCollapsibleListbox); + const findAlert = () => wrapper.findComponent(GlAlert); + const findSelectButton = () => wrapper.findComponent(GlButton); + + const searchNamespace = async (searchTerm = 'test') => { + findNamespaceSelector().vm.$emit('search', searchTerm); + await nextTick(); + }; + + describe('default', () => { + beforeEach(() => { + wrapper = createWrapper(); + }); + + it('renders namespace selector', () => { + expect(findNamespaceSelector().exists()).toBe(true); + }); + + it('requests the namespaces', async () => { + await waitForPromises(); + + expect(getNamespacesQueryResult).toHaveBeenCalled(); + }); + + it('sets the loading prop while fetching the list', async () => { + expect(findNamespaceSelector().props('loading')).toBe(true); + + await waitForPromises(); + + expect(findNamespaceSelector().props('loading')).toBe(false); + }); + + it('renders a list of available namespaces', async () => { + await waitForPromises(); + + expect(findNamespaceSelector().props('items')).toMatchObject([ + { + text: 'default', + value: 'default', + }, + { + text: 'agent', + value: 'agent', + }, + { + text: 'test-agent', + value: 'test-agent', + }, + ]); + }); + + it('filters the namespaces list on user search', async () => { + await waitForPromises(); + await searchNamespace('agent'); + + expect(findNamespaceSelector().props('items')).toMatchObject([ + { + text: 'agent', + value: 'agent', + }, + { + text: 'test-agent', + value: 'test-agent', + }, + ]); + }); + + it('emits changes to the namespace', () => { + findNamespaceSelector().vm.$emit('select', 'agent'); + + expect(wrapper.emitted('change')).toEqual([['agent']]); + }); + }); + + describe('custom select button', () => { + beforeEach(async () => { + wrapper = createWrapper(); + await waitForPromises(); + }); + + it("doesn't render custom select button before searching", () => { + expect(findSelectButton().exists()).toBe(false); + }); + + it("doesn't render custom select button when the search is found in the namespaces list", async () => { + await searchNamespace('test-agent'); + expect(findSelectButton().exists()).toBe(false); + }); + + it('renders custom select button when the namespace searched for is not found in the namespaces list', async () => { + await searchNamespace(); + expect(findSelectButton().exists()).toBe(true); + }); + + it('emits custom filled namespace name to the `change` event', async () => { + await searchNamespace(); + findSelectButton().vm.$emit('click'); + + expect(wrapper.emitted('change')).toEqual([['test']]); + }); + + it('closes the listbox after the custom value for the namespace was selected', async () => { + await searchNamespace(); + findSelectButton().vm.$emit('click'); + + expect(closeMock).toHaveBeenCalled(); + }); + }); + + describe('when environment has an associated namespace', () => { + beforeEach(() => { + wrapper = createWrapper({ + propsData: { namespace: 'existing-namespace' }, + }); + }); + + it('updates namespace selector with the name of the associated namespace', () => { + expect(findNamespaceSelector().props('toggleText')).toBe('existing-namespace'); + }); + }); + + describe('on error', () => { + const error = new Error('Error from the cluster_client API'); + + beforeEach(async () => { + wrapper = createWrapper({ + queryResult: jest.fn().mockRejectedValueOnce(error), + }); + await waitForPromises(); + }); + + it('renders an alert with the error text', () => { + expect(findAlert().text()).toContain(error.message); + }); + + it('renders an empty namespace selector', () => { + expect(findNamespaceSelector().props('items')).toMatchObject([]); + }); + + it('renders custom select button when the user performs search', async () => { + await searchNamespace(); + + expect(findSelectButton().exists()).toBe(true); + }); + + it('emits custom filled namespace name to the `change` event', async () => { + await searchNamespace(); + findSelectButton().vm.$emit('click'); + + expect(wrapper.emitted('change')).toEqual([['test']]); + }); + }); +}); diff --git a/spec/frontend/environments/folder/environments_folder_app_spec.js b/spec/frontend/environments/folder/environments_folder_app_spec.js index 262e742ba5c..fbb252fb152 100644 --- a/spec/frontend/environments/folder/environments_folder_app_spec.js +++ b/spec/frontend/environments/folder/environments_folder_app_spec.js @@ -1,12 +1,21 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import { GlSkeletonLoader } from '@gitlab/ui'; +import { GlSkeletonLoader, GlTab } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import EnvironmentsFolderAppComponent from '~/environments/folder/environments_folder_app.vue'; import EnvironmentItem from '~/environments/components/new_environment_item.vue'; +import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue'; +import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue'; +import DeleteEnvironmentModal from '~/environments/components/delete_environment_modal.vue'; +import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { resolvedFolder } from '../graphql/mock_data'; +import { + resolvedFolder, + resolvedEnvironment, + resolvedEnvironmentToDelete, + resolvedEnvironmentToRollback, +} from '../graphql/mock_data'; Vue.use(VueApollo); @@ -20,6 +29,11 @@ describe('EnvironmentsFolderAppComponent', () => { const mockResolvers = { Query: { folder: environmentFolderMock, + environmentToDelete: jest.fn().mockReturnValue(resolvedEnvironmentToDelete), + environmentToRollback: jest.fn().mockReturnValue(resolvedEnvironment), + environmentToChangeCanary: jest.fn().mockReturnValue(resolvedEnvironment), + environmentToStop: jest.fn().mockReturnValue(resolvedEnvironment), + weight: jest.fn().mockReturnValue(1), }, }; @@ -47,6 +61,7 @@ describe('EnvironmentsFolderAppComponent', () => { propsData: { folderName: mockFolderName, folderPath: '/gitlab-org/test-project/-/environments/folder/dev', + scope: 'active', }, }); }; @@ -54,6 +69,7 @@ describe('EnvironmentsFolderAppComponent', () => { const findHeader = () => wrapper.findByTestId('folder-name'); const findEnvironmentItems = () => wrapper.findAllComponents(EnvironmentItem); const findSkeletonLoaders = () => wrapper.findAllComponents(GlSkeletonLoader); + const findTabs = () => wrapper.findAllComponents(GlTab); it('should render a header with the folder name', () => { createWrapper(); @@ -76,5 +92,32 @@ describe('EnvironmentsFolderAppComponent', () => { const items = findEnvironmentItems(); expect(items.length).toBe(resolvedFolder.environments.length); }); + + it('should render active and stopped tabs', () => { + const tabs = findTabs(); + expect(tabs.length).toBe(2); + }); + + [ + [StopEnvironmentModal, resolvedEnvironment], + [DeleteEnvironmentModal, resolvedEnvironmentToDelete], + [ConfirmRollbackModal, resolvedEnvironmentToRollback], + ].forEach(([Component, expectedEnvironment]) => + it(`should render ${Component.name} component`, () => { + const modal = wrapper.findComponent(Component); + + expect(modal.exists()).toBe(true); + expect(modal.props().environment).toEqual(expectedEnvironment); + expect(modal.props().graphql).toBe(true); + }), + ); + + it(`should render CanaryUpdateModal component`, () => { + const modal = wrapper.findComponent(CanaryUpdateModal); + + expect(modal.exists()).toBe(true); + expect(modal.props().environment).toEqual(resolvedEnvironment); + expect(modal.props().weight).toBe(1); + }); }); }); diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js index 7f5e46f51ad..efc63a80e89 100644 --- a/spec/frontend/environments/graphql/mock_data.js +++ b/spec/frontend/environments/graphql/mock_data.js @@ -930,3 +930,153 @@ export const fluxKustomizationsMock = [ ]; export const fluxResourcePathMock = 'path/to/flux/resource'; + +export const resolvedEnvironmentToDelete = { + __typename: 'LocalEnvironment', + id: 41, + name: 'review/hello', + deletePath: '/api/v4/projects/8/environments/41', +}; + +export const resolvedEnvironmentToRollback = { + __typename: 'LocalEnvironment', + id: 41, + name: 'review/hello', + lastDeployment: { + id: 78, + iid: 24, + sha: 'f3ba6dd84f8f891373e9b869135622b954852db1', + ref: { name: 'main', refPath: '/h5bp/html5-boilerplate/-/tree/main' }, + status: 'success', + createdAt: '2022-01-07T15:47:27.415Z', + deployedAt: '2022-01-07T15:47:32.450Z', + tierInYaml: 'staging', + tag: false, + isLast: true, + user: { + id: 1, + username: 'root', + name: 'Administrator', + state: 'active', + avatarUrl: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + webUrl: 'http://gck.test:3000/root', + showStatus: false, + path: '/root', + }, + deployable: { + id: 1014, + name: 'deploy-prod', + started: '2022-01-07T15:47:31.037Z', + complete: true, + archived: false, + buildPath: '/h5bp/html5-boilerplate/-/jobs/1014', + retryPath: '/h5bp/html5-boilerplate/-/jobs/1014/retry', + playable: false, + scheduled: false, + createdAt: '2022-01-07T15:47:27.404Z', + updatedAt: '2022-01-07T15:47:32.341Z', + status: { + action: { + buttonTitle: 'Retry this job', + icon: 'retry', + method: 'post', + path: '/h5bp/html5-boilerplate/-/jobs/1014/retry', + title: 'Retry', + }, + detailsPath: '/h5bp/html5-boilerplate/-/jobs/1014', + favicon: + '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png', + group: 'success', + hasDetails: true, + icon: 'status_success', + illustration: { + image: + '/assets/illustrations/skipped-job_empty-29a8a37d8a61d1b6f68cf3484f9024e53cd6eb95e28eae3554f8011a1146bf27.svg', + size: 'svg-430', + title: 'This job does not have a trace.', + }, + label: 'passed', + text: 'passed', + tooltip: 'passed', + }, + }, + commit: { + id: 'f3ba6dd84f8f891373e9b869135622b954852db1', + shortId: 'f3ba6dd8', + createdAt: '2022-01-07T15:47:26.000+00:00', + parentIds: ['3213b6ac17afab99be37d5d38f38c6c8407387cc'], + title: 'Update .gitlab-ci.yml file', + message: 'Update .gitlab-ci.yml file', + authorName: 'Administrator', + authorEmail: 'admin@example.com', + authoredDate: '2022-01-07T15:47:26.000+00:00', + committerName: 'Administrator', + committerEmail: 'admin@example.com', + committedDate: '2022-01-07T15:47:26.000+00:00', + trailers: {}, + webUrl: + 'http://gck.test:3000/h5bp/html5-boilerplate/-/commit/f3ba6dd84f8f891373e9b869135622b954852db1', + author: { + avatarUrl: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + id: 1, + name: 'Administrator', + path: '/root', + showStatus: false, + state: 'active', + username: 'root', + webUrl: 'http://gck.test:3000/root', + }, + authorGravatarUrl: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + commitUrl: + 'http://gck.test:3000/h5bp/html5-boilerplate/-/commit/f3ba6dd84f8f891373e9b869135622b954852db1', + commitPath: '/h5bp/html5-boilerplate/-/commit/f3ba6dd84f8f891373e9b869135622b954852db1', + }, + manualActions: [ + { + id: 1015, + name: 'deploy-staging', + started: null, + complete: false, + archived: false, + buildPath: '/h5bp/html5-boilerplate/-/jobs/1015', + playPath: '/h5bp/html5-boilerplate/-/jobs/1015/play', + playable: true, + scheduled: false, + createdAt: '2022-01-07T15:47:27.422Z', + updatedAt: '2022-01-07T15:47:28.557Z', + status: { + icon: 'status_manual', + text: 'manual', + label: 'manual play action', + group: 'manual', + tooltip: 'manual action', + hasDetails: true, + detailsPath: '/h5bp/html5-boilerplate/-/jobs/1015', + illustration: { + image: + '/assets/illustrations/manual_action-c55aee2c5f9ebe9f72751480af8bb307be1a6f35552f344cc6d1bf979d3422f6.svg', + size: 'svg-394', + title: 'This job requires a manual action', + content: + 'This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes.', + }, + favicon: + '/assets/ci_favicons/favicon_status_manual-829a0804612cef47d9efc1618dba38325483657c847dba0546c3b9f0295bb36c.png', + action: { + icon: 'play', + title: 'Play', + path: '/h5bp/html5-boilerplate/-/jobs/1015/play', + method: 'post', + buttonTitle: 'Run job', + }, + }, + }, + ], + scheduledActions: [], + cluster: null, + }, + retryUrl: '/h5bp/html5-boilerplate/-/jobs/1014/retry', +}; diff --git a/spec/frontend/environments/graphql/resolvers/base_spec.js b/spec/frontend/environments/graphql/resolvers/base_spec.js index e01cf18c40d..244c86fa679 100644 --- a/spec/frontend/environments/graphql/resolvers/base_spec.js +++ b/spec/frontend/environments/graphql/resolvers/base_spec.js @@ -147,10 +147,10 @@ describe('~/frontend/environments/graphql/resolvers', () => { describe('stopEnvironmentREST', () => { it('should post to the stop environment path', async () => { mock.onPost(ENDPOINT).reply(HTTP_STATUS_OK); - + const cache = { evict: jest.fn() }; const client = { writeQuery: jest.fn() }; const environment = { stopPath: ENDPOINT }; - await mockResolvers.Mutation.stopEnvironmentREST(null, { environment }, { client }); + await mockResolvers.Mutation.stopEnvironmentREST(null, { environment }, { client, cache }); expect(mock.history.post).toContainEqual( expect.objectContaining({ url: ENDPOINT, method: 'post' }), @@ -161,6 +161,7 @@ describe('~/frontend/environments/graphql/resolvers', () => { variables: { environment }, data: { isEnvironmentStopping: true }, }); + expect(cache.evict).toHaveBeenCalledWith({ fieldName: 'folder' }); }); it('should set is stopping to false if stop fails', async () => { mock.onPost(ENDPOINT).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR); @@ -183,27 +184,39 @@ describe('~/frontend/environments/graphql/resolvers', () => { describe('rollbackEnvironment', () => { it('should post to the retry environment path', async () => { mock.onPost(ENDPOINT).reply(HTTP_STATUS_OK); + const cache = { evict: jest.fn() }; - await mockResolvers.Mutation.rollbackEnvironment(null, { - environment: { retryUrl: ENDPOINT }, - }); + await mockResolvers.Mutation.rollbackEnvironment( + null, + { + environment: { retryUrl: ENDPOINT }, + }, + { cache }, + ); expect(mock.history.post).toContainEqual( expect.objectContaining({ url: ENDPOINT, method: 'post' }), ); + expect(cache.evict).toHaveBeenCalledWith({ fieldName: 'folder' }); }); }); describe('deleteEnvironment', () => { it('should DELETE to the delete environment path', async () => { mock.onDelete(ENDPOINT).reply(HTTP_STATUS_OK); + const cache = { evict: jest.fn() }; - await mockResolvers.Mutation.deleteEnvironment(null, { - environment: { deletePath: ENDPOINT }, - }); + await mockResolvers.Mutation.deleteEnvironment( + null, + { + environment: { deletePath: ENDPOINT }, + }, + { cache }, + ); expect(mock.history.delete).toContainEqual( expect.objectContaining({ url: ENDPOINT, method: 'delete' }), ); + expect(cache.evict).toHaveBeenCalledWith({ fieldName: 'folder' }); }); }); describe('cancelAutoStop', () => { diff --git a/spec/frontend/ide/init_gitlab_web_ide_spec.js b/spec/frontend/ide/init_gitlab_web_ide_spec.js index 6a5bedb0bbb..d7a16bec1c3 100644 --- a/spec/frontend/ide/init_gitlab_web_ide_spec.js +++ b/spec/frontend/ide/init_gitlab_web_ide_spec.js @@ -40,6 +40,9 @@ const TEST_EDITOR_FONT_SRC_URL = 'http://gitlab.test/assets/gitlab-mono/GitLabMo const TEST_EDITOR_FONT_FORMAT = 'woff2'; const TEST_EDITOR_FONT_FAMILY = 'GitLab Mono'; +const TEST_OAUTH_CLIENT_ID = 'oauth-client-id-123abc'; +const TEST_OAUTH_CALLBACK_URL = 'https://example.com/oauth_callback'; + describe('ide/init_gitlab_web_ide', () => { let resolveConfirm; @@ -231,4 +234,29 @@ describe('ide/init_gitlab_web_ide', () => { ); }); }); + + describe('when oauth info is in dataset', () => { + beforeEach(() => { + findRootElement().dataset.clientId = TEST_OAUTH_CLIENT_ID; + findRootElement().dataset.callbackUrl = TEST_OAUTH_CALLBACK_URL; + + createSubject(); + }); + + it('calls start with element', () => { + expect(start).toHaveBeenCalledTimes(1); + expect(start).toHaveBeenCalledWith( + findRootElement(), + expect.objectContaining({ + auth: { + type: 'oauth', + clientId: TEST_OAUTH_CLIENT_ID, + callbackUrl: TEST_OAUTH_CALLBACK_URL, + protectRefreshToken: true, + }, + httpHeaders: undefined, + }), + ); + }); + }); }); diff --git a/spec/frontend/ide/lib/gitlab_web_ide/get_oauth_config_spec.js b/spec/frontend/ide/lib/gitlab_web_ide/get_oauth_config_spec.js new file mode 100644 index 00000000000..3431068937f --- /dev/null +++ b/spec/frontend/ide/lib/gitlab_web_ide/get_oauth_config_spec.js @@ -0,0 +1,16 @@ +import { getOAuthConfig } from '~/ide/lib/gitlab_web_ide/get_oauth_config'; + +describe('~/ide/lib/gitlab_web_ide/get_oauth_config', () => { + it('returns undefined if no clientId found', () => { + expect(getOAuthConfig({})).toBeUndefined(); + }); + + it('returns auth config from dataset', () => { + expect(getOAuthConfig({ clientId: 'test-clientId', callbackUrl: 'test-callbackUrl' })).toEqual({ + type: 'oauth', + clientId: 'test-clientId', + callbackUrl: 'test-callbackUrl', + protectRefreshToken: true, + }); + }); +}); diff --git a/spec/frontend/ide/mount_oauth_callback_spec.js b/spec/frontend/ide/mount_oauth_callback_spec.js new file mode 100644 index 00000000000..6ac0b4e4615 --- /dev/null +++ b/spec/frontend/ide/mount_oauth_callback_spec.js @@ -0,0 +1,53 @@ +import { oauthCallback } from '@gitlab/web-ide'; +import { TEST_HOST } from 'helpers/test_constants'; +import { mountOAuthCallback } from '~/ide/mount_oauth_callback'; + +jest.mock('@gitlab/web-ide'); + +const TEST_USERNAME = 'gandalf.the.grey'; +const TEST_GITLAB_WEB_IDE_PUBLIC_PATH = 'test/webpack/assets/gitlab-web-ide/public/path'; + +const TEST_OAUTH_CLIENT_ID = 'oauth-client-id-123abc'; +const TEST_OAUTH_CALLBACK_URL = 'https://example.com/oauth_callback'; + +describe('~/ide/mount_oauth_callback', () => { + const createRootElement = () => { + const el = document.createElement('div'); + + el.id = 'ide'; + el.dataset.clientId = TEST_OAUTH_CLIENT_ID; + el.dataset.callbackUrl = TEST_OAUTH_CALLBACK_URL; + + document.body.append(el); + }; + + beforeEach(() => { + gon.current_username = TEST_USERNAME; + process.env.GITLAB_WEB_IDE_PUBLIC_PATH = TEST_GITLAB_WEB_IDE_PUBLIC_PATH; + + createRootElement(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('calls oauthCallback', () => { + expect(oauthCallback).not.toHaveBeenCalled(); + + mountOAuthCallback(); + + expect(oauthCallback).toHaveBeenCalledTimes(1); + expect(oauthCallback).toHaveBeenCalledWith({ + auth: { + type: 'oauth', + callbackUrl: TEST_OAUTH_CALLBACK_URL, + clientId: TEST_OAUTH_CLIENT_ID, + protectRefreshToken: true, + }, + gitlabUrl: TEST_HOST, + baseUrl: `${TEST_HOST}/${TEST_GITLAB_WEB_IDE_PUBLIC_PATH}`, + username: TEST_USERNAME, + }); + }); +}); |