diff options
Diffstat (limited to 'spec/frontend/import_entities')
-rw-r--r-- | spec/frontend/import_entities/components/group_dropdown_spec.js | 18 | ||||
-rw-r--r-- | spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js | 33 | ||||
-rw-r--r-- | spec/frontend/import_entities/import_groups/components/import_source_cell_spec.js | 27 | ||||
-rw-r--r-- | spec/frontend/import_entities/import_groups/components/import_table_spec.js | 235 | ||||
-rw-r--r-- | spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js | 96 | ||||
-rw-r--r-- | spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js | 442 | ||||
-rw-r--r-- | spec/frontend/import_entities/import_groups/graphql/fixtures.js | 42 | ||||
-rw-r--r-- | spec/frontend/import_entities/import_groups/graphql/services/local_storage_cache_spec.js | 61 | ||||
-rw-r--r-- | spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js | 64 | ||||
-rw-r--r-- | spec/frontend/import_entities/import_groups/services/status_poller_spec.js (renamed from spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js) | 9 |
10 files changed, 369 insertions, 658 deletions
diff --git a/spec/frontend/import_entities/components/group_dropdown_spec.js b/spec/frontend/import_entities/components/group_dropdown_spec.js index f7aa0e889ea..1c1e1e7ebd4 100644 --- a/spec/frontend/import_entities/components/group_dropdown_spec.js +++ b/spec/frontend/import_entities/components/group_dropdown_spec.js @@ -24,14 +24,21 @@ describe('Import entities group dropdown component', () => { }); it('passes namespaces from props to default slot', () => { - const namespaces = ['ns1', 'ns2']; + const namespaces = [ + { id: 1, fullPath: 'ns1' }, + { id: 2, fullPath: 'ns2' }, + ]; createComponent({ namespaces }); expect(namespacesTracker).toHaveBeenCalledWith({ namespaces }); }); it('filters namespaces based on user input', async () => { - const namespaces = ['match1', 'some unrelated', 'match2']; + const namespaces = [ + { id: 1, fullPath: 'match1' }, + { id: 2, fullPath: 'some unrelated' }, + { id: 3, fullPath: 'match2' }, + ]; createComponent({ namespaces }); namespacesTracker.mockReset(); @@ -39,6 +46,11 @@ describe('Import entities group dropdown component', () => { await nextTick(); - expect(namespacesTracker).toHaveBeenCalledWith({ namespaces: ['match1', 'match2'] }); + expect(namespacesTracker).toHaveBeenCalledWith({ + namespaces: [ + { id: 1, fullPath: 'match1' }, + { id: 3, fullPath: 'match2' }, + ], + }); }); }); diff --git a/spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js b/spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js index 60f0780fdb3..cd56f573011 100644 --- a/spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js +++ b/spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js @@ -1,8 +1,6 @@ import { GlButton, GlIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import { STATUSES } from '~/import_entities/constants'; import ImportActionsCell from '~/import_entities/import_groups/components/import_actions_cell.vue'; -import { generateFakeEntry } from '../graphql/fixtures'; describe('import actions cell', () => { let wrapper; @@ -10,7 +8,9 @@ describe('import actions cell', () => { const createComponent = (props) => { wrapper = shallowMount(ImportActionsCell, { propsData: { - groupPathRegex: /^[a-zA-Z]+$/, + isFinished: false, + isAvailableForImport: false, + isInvalid: false, ...props, }, }); @@ -20,10 +20,9 @@ describe('import actions cell', () => { wrapper.destroy(); }); - describe('when import status is NONE', () => { + describe('when group is available for import', () => { beforeEach(() => { - const group = generateFakeEntry({ id: 1, status: STATUSES.NONE }); - createComponent({ group }); + createComponent({ isAvailableForImport: true }); }); it('renders import button', () => { @@ -37,10 +36,9 @@ describe('import actions cell', () => { }); }); - describe('when import status is FINISHED', () => { + describe('when group is finished', () => { beforeEach(() => { - const group = generateFakeEntry({ id: 1, status: STATUSES.FINISHED }); - createComponent({ group }); + createComponent({ isAvailableForImport: true, isFinished: true }); }); it('renders re-import button', () => { @@ -58,29 +56,22 @@ describe('import actions cell', () => { }); }); - it('does not render import button when group import is in progress', () => { - const group = generateFakeEntry({ id: 1, status: STATUSES.STARTED }); - createComponent({ group }); + it('does not render import button when group is not available for import', () => { + createComponent({ isAvailableForImport: false }); const button = wrapper.findComponent(GlButton); expect(button.exists()).toBe(false); }); - it('renders import button as disabled when there are validation errors', () => { - const group = generateFakeEntry({ - id: 1, - status: STATUSES.NONE, - validation_errors: [{ field: 'new_name', message: 'something ' }], - }); - createComponent({ group }); + it('renders import button as disabled when group is invalid', () => { + createComponent({ isInvalid: true, isAvailableForImport: true }); const button = wrapper.findComponent(GlButton); expect(button.props().disabled).toBe(true); }); it('emits import-group event when import button is clicked', () => { - const group = generateFakeEntry({ id: 1, status: STATUSES.NONE }); - createComponent({ group }); + createComponent({ isAvailableForImport: true }); const button = wrapper.findComponent(GlButton); button.vm.$emit('click'); diff --git a/spec/frontend/import_entities/import_groups/components/import_source_cell_spec.js b/spec/frontend/import_entities/import_groups/components/import_source_cell_spec.js index 2a56efd1cbb..f2735d86493 100644 --- a/spec/frontend/import_entities/import_groups/components/import_source_cell_spec.js +++ b/spec/frontend/import_entities/import_groups/components/import_source_cell_spec.js @@ -4,6 +4,11 @@ import { STATUSES } from '~/import_entities/constants'; import ImportSourceCell from '~/import_entities/import_groups/components/import_source_cell.vue'; import { generateFakeEntry } from '../graphql/fixtures'; +const generateFakeTableEntry = ({ flags = {}, ...entry }) => ({ + ...generateFakeEntry(entry), + flags, +}); + describe('import source cell', () => { let wrapper; let group; @@ -23,14 +28,14 @@ describe('import source cell', () => { describe('when group status is NONE', () => { beforeEach(() => { - group = generateFakeEntry({ id: 1, status: STATUSES.NONE }); + group = generateFakeTableEntry({ id: 1, status: STATUSES.NONE }); createComponent({ group }); }); it('renders link to a group', () => { const link = wrapper.findComponent(GlLink); - expect(link.attributes().href).toBe(group.web_url); - expect(link.text()).toContain(group.full_path); + expect(link.attributes().href).toBe(group.webUrl); + expect(link.text()).toContain(group.fullPath); }); it('does not render last imported line', () => { @@ -40,20 +45,24 @@ describe('import source cell', () => { describe('when group status is FINISHED', () => { beforeEach(() => { - group = generateFakeEntry({ id: 1, status: STATUSES.FINISHED }); + group = generateFakeTableEntry({ + id: 1, + status: STATUSES.FINISHED, + flags: { + isFinished: true, + }, + }); createComponent({ group }); }); it('renders link to a group', () => { const link = wrapper.findComponent(GlLink); - expect(link.attributes().href).toBe(group.web_url); - expect(link.text()).toContain(group.full_path); + expect(link.attributes().href).toBe(group.webUrl); + expect(link.text()).toContain(group.fullPath); }); it('renders last imported line', () => { - expect(wrapper.text()).toMatchInterpolatedText( - 'fake_group_1 Last imported to root/last-group1', - ); + expect(wrapper.text()).toMatchInterpolatedText('fake_group_1 Last imported to root/group1'); }); }); }); diff --git a/spec/frontend/import_entities/import_groups/components/import_table_spec.js b/spec/frontend/import_entities/import_groups/components/import_table_spec.js index f43e545e049..6e3df21e30a 100644 --- a/spec/frontend/import_entities/import_groups/components/import_table_spec.js +++ b/spec/frontend/import_entities/import_groups/components/import_table_spec.js @@ -1,39 +1,30 @@ -import { - GlButton, - GlEmptyState, - GlLoadingIcon, - GlSearchBoxByClick, - GlDropdown, - GlDropdownItem, - GlTable, -} from '@gitlab/ui'; -import { mount, createLocalVue } from '@vue/test-utils'; -import { nextTick } from 'vue'; +import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; +import MockAdapter from 'axios-mock-adapter'; import createMockApollo from 'helpers/mock_apollo_helper'; -import stubChildren from 'helpers/stub_children'; -import { stubComponent } from 'helpers/stub_component'; import waitForPromises from 'helpers/wait_for_promises'; +import createFlash from '~/flash'; +import httpStatus from '~/lib/utils/http_status'; +import axios from '~/lib/utils/axios_utils'; import { STATUSES } from '~/import_entities/constants'; -import ImportActionsCell from '~/import_entities/import_groups/components/import_actions_cell.vue'; +import { i18n } from '~/import_entities/import_groups/constants'; import ImportTable from '~/import_entities/import_groups/components/import_table.vue'; -import ImportTargetCell from '~/import_entities/import_groups/components/import_target_cell.vue'; import importGroupsMutation from '~/import_entities/import_groups/graphql/mutations/import_groups.mutation.graphql'; -import setImportTargetMutation from '~/import_entities/import_groups/graphql/mutations/set_import_target.mutation.graphql'; import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; import { availableNamespacesFixture, generateFakeEntry } from '../graphql/fixtures'; -const localVue = createLocalVue(); -localVue.use(VueApollo); +jest.mock('~/flash'); +jest.mock('~/import_entities/import_groups/services/status_poller'); -const GlDropdownStub = stubComponent(GlDropdown, { - template: '<div><h1 ref="text"><slot name="button-content"></slot></h1><slot></slot></div>', -}); +Vue.use(VueApollo); describe('import table', () => { let wrapper; let apolloProvider; + let axiosMock; const SOURCE_URL = 'https://demo.host'; const FAKE_GROUP = generateFakeEntry({ id: 1, status: STATUSES.NONE }); @@ -44,76 +35,81 @@ describe('import table', () => { const FAKE_PAGE_INFO = { page: 1, perPage: 20, total: 40, totalPages: 2 }; const findImportSelectedButton = () => - wrapper.findAllComponents(GlButton).wrappers.find((w) => w.text() === 'Import selected'); - const findPaginationDropdown = () => wrapper.findComponent(GlDropdown); - const findPaginationDropdownText = () => findPaginationDropdown().find({ ref: 'text' }).text(); + wrapper.findAll('button').wrappers.find((w) => w.text() === 'Import selected'); + const findImportButtons = () => + wrapper.findAll('button').wrappers.filter((w) => w.text() === 'Import'); + const findPaginationDropdown = () => wrapper.find('[aria-label="Page size"]'); + const findPaginationDropdownText = () => findPaginationDropdown().find('button').text(); - // TODO: remove this ugly approach when - // issue: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1531 - const findTable = () => wrapper.vm.getTableRef(); + const selectRow = (idx) => + wrapper.findAll('tbody td input[type=checkbox]').at(idx).trigger('click'); - const createComponent = ({ bulkImportSourceGroups }) => { + const createComponent = ({ bulkImportSourceGroups, importGroups }) => { apolloProvider = createMockApollo([], { Query: { availableNamespaces: () => availableNamespacesFixture, bulkImportSourceGroups, }, Mutation: { - setTargetNamespace: jest.fn(), - setNewName: jest.fn(), - importGroup: jest.fn(), + importGroups, }, }); wrapper = mount(ImportTable, { propsData: { groupPathRegex: /.*/, + jobsPath: '/fake_job_path', sourceUrl: SOURCE_URL, - groupUrlErrorMessage: 'Please choose a group URL with no special characters or spaces.', - }, - stubs: { - ...stubChildren(ImportTable), - GlSprintf: false, - GlDropdown: GlDropdownStub, - GlTable: false, }, - localVue, apolloProvider, }); }; + beforeAll(() => { + gon.api_version = 'v4'; + }); + + beforeEach(() => { + axiosMock = new MockAdapter(axios); + axiosMock.onGet(/.*\/exists$/, () => []).reply(200); + }); + afterEach(() => { wrapper.destroy(); }); - it('renders loading icon while performing request', async () => { - createComponent({ - bulkImportSourceGroups: () => new Promise(() => {}), + describe('loading state', () => { + it('renders loading icon while performing request', async () => { + createComponent({ + bulkImportSourceGroups: () => new Promise(() => {}), + }); + await waitForPromises(); + + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); }); - await waitForPromises(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); - }); + it('does not renders loading icon when request is completed', async () => { + createComponent({ + bulkImportSourceGroups: () => [], + }); + await waitForPromises(); - it('does not renders loading icon when request is completed', async () => { - createComponent({ - bulkImportSourceGroups: () => [], + expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); }); - await waitForPromises(); - - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); }); - it('renders message about empty state when no groups are available for import', async () => { - createComponent({ - bulkImportSourceGroups: () => ({ - nodes: [], - pageInfo: FAKE_PAGE_INFO, - }), - }); - await waitForPromises(); + describe('empty state', () => { + it('renders message about empty state when no groups are available for import', async () => { + createComponent({ + bulkImportSourceGroups: () => ({ + nodes: [], + pageInfo: FAKE_PAGE_INFO, + }), + }); + await waitForPromises(); - expect(wrapper.find(GlEmptyState).props().title).toBe('You have no groups to import'); + expect(wrapper.find(GlEmptyState).props().title).toBe('You have no groups to import'); + }); }); it('renders import row for each group in response', async () => { @@ -140,40 +136,51 @@ describe('import table', () => { expect(wrapper.text()).not.toContain('Showing 1-0'); }); - describe('converts row events to mutation invocations', () => { - beforeEach(() => { - createComponent({ - bulkImportSourceGroups: () => ({ nodes: [FAKE_GROUP], pageInfo: FAKE_PAGE_INFO }), - }); - return waitForPromises(); + it('invokes importGroups mutation when row button is clicked', async () => { + createComponent({ + bulkImportSourceGroups: () => ({ nodes: [FAKE_GROUP], pageInfo: FAKE_PAGE_INFO }), }); - it.each` - event | payload | mutation | variables - ${'update-target-namespace'} | ${'new-namespace'} | ${setImportTargetMutation} | ${{ sourceGroupId: FAKE_GROUP.id, targetNamespace: 'new-namespace', newName: 'group1' }} - ${'update-new-name'} | ${'new-name'} | ${setImportTargetMutation} | ${{ sourceGroupId: FAKE_GROUP.id, targetNamespace: 'root', newName: 'new-name' }} - `('correctly maps $event to mutation', async ({ event, payload, mutation, variables }) => { - jest.spyOn(apolloProvider.defaultClient, 'mutate'); - wrapper.find(ImportTargetCell).vm.$emit(event, payload); - await waitForPromises(); - expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({ - mutation, - variables, - }); - }); + jest.spyOn(apolloProvider.defaultClient, 'mutate'); - it('invokes importGroups mutation when row button is clicked', async () => { - jest.spyOn(apolloProvider.defaultClient, 'mutate'); + await waitForPromises(); - wrapper.findComponent(ImportActionsCell).vm.$emit('import-group'); - await waitForPromises(); - expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({ - mutation: importGroupsMutation, - variables: { sourceGroupIds: [FAKE_GROUP.id] }, - }); + await findImportButtons()[0].trigger('click'); + expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({ + mutation: importGroupsMutation, + variables: { + importRequests: [ + { + newName: FAKE_GROUP.lastImportTarget.newName, + sourceGroupId: FAKE_GROUP.id, + targetNamespace: availableNamespacesFixture[0].fullPath, + }, + ], + }, }); }); + it('displays error if importing group fails', async () => { + createComponent({ + bulkImportSourceGroups: () => ({ nodes: [FAKE_GROUP], pageInfo: FAKE_PAGE_INFO }), + importGroups: () => { + throw new Error(); + }, + }); + + axiosMock.onPost('/import/bulk_imports.json').reply(httpStatus.BAD_REQUEST); + + await waitForPromises(); + await findImportButtons()[0].trigger('click'); + await waitForPromises(); + + expect(createFlash).toHaveBeenCalledWith( + expect.objectContaining({ + message: i18n.ERROR_IMPORT, + }), + ); + }); + describe('pagination', () => { const bulkImportSourceGroupsQueryMock = jest .fn() @@ -195,10 +202,10 @@ describe('import table', () => { }); it('updates page size when selected in Dropdown', async () => { - const otherOption = wrapper.findAllComponents(GlDropdownItem).at(1); + const otherOption = findPaginationDropdown().findAll('li p').at(1); expect(otherOption.text()).toMatchInterpolatedText('50 items per page'); - otherOption.vm.$emit('click'); + await otherOption.trigger('click'); await waitForPromises(); expect(findPaginationDropdownText()).toMatchInterpolatedText('50 items per page'); @@ -247,7 +254,11 @@ describe('import table', () => { return waitForPromises(); }); - const findFilterInput = () => wrapper.find(GlSearchBoxByClick); + const setFilter = (value) => { + const input = wrapper.find('input[placeholder="Filter by source group"]'); + input.setValue(value); + return input.trigger('keydown.enter'); + }; it('properly passes filter to graphql query when search box is submitted', async () => { createComponent({ @@ -256,7 +267,7 @@ describe('import table', () => { await waitForPromises(); const FILTER_VALUE = 'foo'; - findFilterInput().vm.$emit('submit', FILTER_VALUE); + await setFilter(FILTER_VALUE); await waitForPromises(); expect(bulkImportSourceGroupsQueryMock).toHaveBeenCalledWith( @@ -274,7 +285,7 @@ describe('import table', () => { await waitForPromises(); const FILTER_VALUE = 'foo'; - findFilterInput().vm.$emit('submit', FILTER_VALUE); + await setFilter(FILTER_VALUE); await waitForPromises(); expect(wrapper.text()).toContain('Showing 1-1 of 40 groups matching filter "foo" from'); @@ -282,12 +293,14 @@ describe('import table', () => { it('properly resets filter in graphql query when search box is cleared', async () => { const FILTER_VALUE = 'foo'; - findFilterInput().vm.$emit('submit', FILTER_VALUE); + await setFilter(FILTER_VALUE); await waitForPromises(); bulkImportSourceGroupsQueryMock.mockClear(); await apolloProvider.defaultClient.resetStore(); - findFilterInput().vm.$emit('clear'); + + await setFilter(''); + await waitForPromises(); expect(bulkImportSourceGroupsQueryMock).toHaveBeenCalledWith( @@ -320,8 +333,8 @@ describe('import table', () => { }), }); await waitForPromises(); - wrapper.find(GlTable).vm.$emit('row-selected', [FAKE_GROUPS[0]]); - await nextTick(); + + await selectRow(0); expect(findImportSelectedButton().props().disabled).toBe(false); }); @@ -337,7 +350,7 @@ describe('import table', () => { }); await waitForPromises(); - findTable().selectRow(0); + await selectRow(0); await nextTick(); expect(findImportSelectedButton().props().disabled).toBe(true); @@ -348,7 +361,6 @@ describe('import table', () => { generateFakeEntry({ id: 2, status: STATUSES.NONE, - validation_errors: [{ field: 'new_name', message: 'FAKE_VALIDATION_ERROR' }], }), ]; @@ -360,9 +372,9 @@ describe('import table', () => { }); await waitForPromises(); - // TODO: remove this ugly approach when - // issue: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1531 - findTable().selectRow(0); + await wrapper.find('tbody input[aria-label="New name"]').setValue(''); + jest.runOnlyPendingTimers(); + await selectRow(0); await nextTick(); expect(findImportSelectedButton().props().disabled).toBe(true); @@ -384,15 +396,28 @@ describe('import table', () => { jest.spyOn(apolloProvider.defaultClient, 'mutate'); await waitForPromises(); - findTable().selectRow(0); - findTable().selectRow(1); + await selectRow(0); + await selectRow(1); await nextTick(); - findImportSelectedButton().vm.$emit('click'); + await findImportSelectedButton().trigger('click'); expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({ mutation: importGroupsMutation, - variables: { sourceGroupIds: [NEW_GROUPS[0].id, NEW_GROUPS[1].id] }, + variables: { + importRequests: [ + { + targetNamespace: availableNamespacesFixture[0].fullPath, + newName: NEW_GROUPS[0].lastImportTarget.newName, + sourceGroupId: NEW_GROUPS[0].id, + }, + { + targetNamespace: availableNamespacesFixture[0].fullPath, + newName: NEW_GROUPS[1].lastImportTarget.newName, + sourceGroupId: NEW_GROUPS[1].id, + }, + ], + }, }); }); }); diff --git a/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js b/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js index be83a61841f..3c2367e22f5 100644 --- a/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js +++ b/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js @@ -3,20 +3,20 @@ import { shallowMount } from '@vue/test-utils'; import ImportGroupDropdown from '~/import_entities/components/group_dropdown.vue'; import { STATUSES } from '~/import_entities/constants'; import ImportTargetCell from '~/import_entities/import_groups/components/import_target_cell.vue'; -import { availableNamespacesFixture } from '../graphql/fixtures'; - -const getFakeGroup = (status) => ({ - web_url: 'https://fake.host/', - full_path: 'fake_group_1', - full_name: 'fake_name_1', - import_target: { - target_namespace: 'root', - new_name: 'group1', - }, - id: 1, - validation_errors: [], - progress: { status }, -}); +import { generateFakeEntry, availableNamespacesFixture } from '../graphql/fixtures'; + +const generateFakeTableEntry = ({ flags = {}, ...config }) => { + const entry = generateFakeEntry(config); + + return { + ...entry, + importTarget: { + targetNamespace: availableNamespacesFixture[0], + newName: entry.lastImportTarget.newName, + }, + flags, + }; +}; describe('import target cell', () => { let wrapper; @@ -31,7 +31,6 @@ describe('import target cell', () => { propsData: { availableNamespaces: availableNamespacesFixture, groupPathRegex: /.*/, - groupUrlErrorMessage: 'Please choose a group URL with no special characters or spaces.', ...props, }, }); @@ -44,11 +43,11 @@ describe('import target cell', () => { describe('events', () => { beforeEach(() => { - group = getFakeGroup(STATUSES.NONE); + group = generateFakeTableEntry({ id: 1, status: STATUSES.NONE }); createComponent({ group }); }); - it('invokes $event', () => { + it('emits update-new-name when input value is changed', () => { findNameInput().vm.$emit('input', 'demo'); expect(wrapper.emitted('update-new-name')).toBeDefined(); expect(wrapper.emitted('update-new-name')[0][0]).toBe('demo'); @@ -56,18 +55,23 @@ describe('import target cell', () => { it('emits update-target-namespace when dropdown option is clicked', () => { const dropdownItem = findNamespaceDropdown().findAllComponents(GlDropdownItem).at(2); - const dropdownItemText = dropdownItem.text(); dropdownItem.vm.$emit('click'); expect(wrapper.emitted('update-target-namespace')).toBeDefined(); - expect(wrapper.emitted('update-target-namespace')[0][0]).toBe(dropdownItemText); + expect(wrapper.emitted('update-target-namespace')[0][0]).toBe(availableNamespacesFixture[1]); }); }); describe('when entity status is NONE', () => { beforeEach(() => { - group = getFakeGroup(STATUSES.NONE); + group = generateFakeTableEntry({ + id: 1, + status: STATUSES.NONE, + flags: { + isAvailableForImport: true, + }, + }); createComponent({ group }); }); @@ -78,7 +82,7 @@ describe('import target cell', () => { it('renders only no parent option if available namespaces list is empty', () => { createComponent({ - group: getFakeGroup(STATUSES.NONE), + group: generateFakeTableEntry({ id: 1, status: STATUSES.NONE }), availableNamespaces: [], }); @@ -92,7 +96,7 @@ describe('import target cell', () => { it('renders both no parent option and available namespaces list when available namespaces list is not empty', () => { createComponent({ - group: getFakeGroup(STATUSES.NONE), + group: generateFakeTableEntry({ id: 1, status: STATUSES.NONE }), availableNamespaces: availableNamespacesFixture, }); @@ -104,9 +108,12 @@ describe('import target cell', () => { expect(rest).toHaveLength(availableNamespacesFixture.length); }); - describe('when entity status is SCHEDULING', () => { + describe('when entity is not available for import', () => { beforeEach(() => { - group = getFakeGroup(STATUSES.SCHEDULING); + group = generateFakeTableEntry({ + id: 1, + flags: { isAvailableForImport: false }, + }); createComponent({ group }); }); @@ -115,9 +122,9 @@ describe('import target cell', () => { }); }); - describe('when entity status is FINISHED', () => { + describe('when entity is available for import', () => { beforeEach(() => { - group = getFakeGroup(STATUSES.FINISHED); + group = generateFakeTableEntry({ id: 1, flags: { isAvailableForImport: true } }); createComponent({ group }); }); @@ -125,41 +132,4 @@ describe('import target cell', () => { expect(findNamespaceDropdown().attributes('disabled')).toBe(undefined); }); }); - - describe('validations', () => { - it('reports invalid group name when name is not matching regex', () => { - createComponent({ - group: { - ...getFakeGroup(STATUSES.NONE), - import_target: { - target_namespace: 'root', - new_name: 'very`bad`name', - }, - }, - groupPathRegex: /^[a-zA-Z]+$/, - }); - - expect(wrapper.text()).toContain( - 'Please choose a group URL with no special characters or spaces.', - ); - }); - - it('reports invalid group name if relevant validation error exists', async () => { - const FAKE_ERROR_MESSAGE = 'fake error'; - - createComponent({ - group: { - ...getFakeGroup(STATUSES.NONE), - validation_errors: [ - { - field: 'new_name', - message: FAKE_ERROR_MESSAGE, - }, - ], - }, - }); - - expect(wrapper.text()).toContain(FAKE_ERROR_MESSAGE); - }); - }); }); diff --git a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js index e1d65095888..f3447494578 100644 --- a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js +++ b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js @@ -2,32 +2,27 @@ import { InMemoryCache } from 'apollo-cache-inmemory'; import MockAdapter from 'axios-mock-adapter'; import { createMockClient } from 'mock-apollo-client'; import waitForPromises from 'helpers/wait_for_promises'; -import createFlash from '~/flash'; import { STATUSES } from '~/import_entities/constants'; import { clientTypenames, createResolvers, } from '~/import_entities/import_groups/graphql/client_factory'; -import addValidationErrorMutation from '~/import_entities/import_groups/graphql/mutations/add_validation_error.mutation.graphql'; +import { LocalStorageCache } from '~/import_entities/import_groups/graphql/services/local_storage_cache'; import importGroupsMutation from '~/import_entities/import_groups/graphql/mutations/import_groups.mutation.graphql'; -import removeValidationErrorMutation from '~/import_entities/import_groups/graphql/mutations/remove_validation_error.mutation.graphql'; -import setImportProgressMutation from '~/import_entities/import_groups/graphql/mutations/set_import_progress.mutation.graphql'; -import setImportTargetMutation from '~/import_entities/import_groups/graphql/mutations/set_import_target.mutation.graphql'; import updateImportStatusMutation from '~/import_entities/import_groups/graphql/mutations/update_import_status.mutation.graphql'; import availableNamespacesQuery from '~/import_entities/import_groups/graphql/queries/available_namespaces.query.graphql'; -import bulkImportSourceGroupQuery from '~/import_entities/import_groups/graphql/queries/bulk_import_source_group.query.graphql'; import bulkImportSourceGroupsQuery from '~/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql'; -import groupAndProjectQuery from '~/import_entities/import_groups/graphql/queries/group_and_project.query.graphql'; -import { StatusPoller } from '~/import_entities/import_groups/graphql/services/status_poller'; import axios from '~/lib/utils/axios_utils'; import httpStatus from '~/lib/utils/http_status'; import { statusEndpointFixture, availableNamespacesFixture } from './fixtures'; jest.mock('~/flash'); -jest.mock('~/import_entities/import_groups/graphql/services/status_poller', () => ({ - StatusPoller: jest.fn().mockImplementation(function mock() { - this.startPolling = jest.fn(); +jest.mock('~/import_entities/import_groups/graphql/services/local_storage_cache', () => ({ + LocalStorageCache: jest.fn().mockImplementation(function mock() { + this.get = jest.fn(); + this.set = jest.fn(); + this.updateStatusByJobId = jest.fn(); }), })); @@ -38,13 +33,6 @@ const FAKE_ENDPOINTS = { jobs: '/fake_jobs', }; -const FAKE_GROUP_AND_PROJECTS_QUERY_HANDLER = jest.fn().mockResolvedValue({ - data: { - existingGroup: null, - existingProject: null, - }, -}); - describe('Bulk import resolvers', () => { let axiosMockAdapter; let client; @@ -58,14 +46,28 @@ describe('Bulk import resolvers', () => { resolvers: createResolvers({ endpoints: FAKE_ENDPOINTS, ...extraResolverArgs }), }); - mockedClient.setRequestHandler(groupAndProjectQuery, FAKE_GROUP_AND_PROJECTS_QUERY_HANDLER); - return mockedClient; }; - beforeEach(() => { + let results; + beforeEach(async () => { axiosMockAdapter = new MockAdapter(axios); client = createClient(); + + axiosMockAdapter.onGet(FAKE_ENDPOINTS.status).reply(httpStatus.OK, statusEndpointFixture); + axiosMockAdapter.onGet(FAKE_ENDPOINTS.availableNamespaces).reply( + httpStatus.OK, + availableNamespacesFixture.map((ns) => ({ + id: ns.id, + full_path: ns.fullPath, + })), + ); + + client.watchQuery({ query: bulkImportSourceGroupsQuery }).subscribe(({ data }) => { + results = data.bulkImportSourceGroups.nodes; + }); + + return waitForPromises(); }); afterEach(() => { @@ -74,104 +76,41 @@ describe('Bulk import resolvers', () => { describe('queries', () => { describe('availableNamespaces', () => { - let results; - + let namespacesResults; beforeEach(async () => { - axiosMockAdapter - .onGet(FAKE_ENDPOINTS.availableNamespaces) - .reply(httpStatus.OK, availableNamespacesFixture); - const response = await client.query({ query: availableNamespacesQuery }); - results = response.data.availableNamespaces; + namespacesResults = response.data.availableNamespaces; }); it('mirrors REST endpoint response fields', () => { const extractRelevantFields = (obj) => ({ id: obj.id, full_path: obj.full_path }); - expect(results.map(extractRelevantFields)).toStrictEqual( + expect(namespacesResults.map(extractRelevantFields)).toStrictEqual( availableNamespacesFixture.map(extractRelevantFields), ); }); }); - describe('bulkImportSourceGroup', () => { - beforeEach(async () => { - axiosMockAdapter.onGet(FAKE_ENDPOINTS.status).reply(httpStatus.OK, statusEndpointFixture); - axiosMockAdapter - .onGet(FAKE_ENDPOINTS.availableNamespaces) - .reply(httpStatus.OK, availableNamespacesFixture); - - return client.query({ - query: bulkImportSourceGroupsQuery, - }); - }); - - it('returns group', async () => { - const { id } = statusEndpointFixture.importable_data[0]; - const { - data: { bulkImportSourceGroup: group }, - } = await client.query({ - query: bulkImportSourceGroupQuery, - variables: { id: id.toString() }, - }); - - expect(group).toMatchObject(statusEndpointFixture.importable_data[0]); - }); - }); - describe('bulkImportSourceGroups', () => { - let results; - - beforeEach(async () => { - axiosMockAdapter.onGet(FAKE_ENDPOINTS.status).reply(httpStatus.OK, statusEndpointFixture); - axiosMockAdapter - .onGet(FAKE_ENDPOINTS.availableNamespaces) - .reply(httpStatus.OK, availableNamespacesFixture); - }); - it('respects cached import state when provided by group manager', async () => { - const FAKE_JOB_ID = '1'; - const FAKE_STATUS = 'DEMO_STATUS'; - const FAKE_IMPORT_TARGET = { - new_name: 'test-name', - target_namespace: 'test-namespace', + const [localStorageCache] = LocalStorageCache.mock.instances; + const CACHED_DATA = { + progress: { + id: 'DEMO', + status: 'cached', + }, }; - const TARGET_INDEX = 0; + localStorageCache.get.mockReturnValueOnce(CACHED_DATA); - const clientWithMockedManager = createClient({ - GroupsManager: jest.fn().mockImplementation(() => ({ - getImportStateFromStorageByGroupId(groupId) { - if (groupId === statusEndpointFixture.importable_data[TARGET_INDEX].id) { - return { - jobId: FAKE_JOB_ID, - importState: { - status: FAKE_STATUS, - importTarget: FAKE_IMPORT_TARGET, - }, - }; - } - - return null; - }, - })), - }); - - const clientResponse = await clientWithMockedManager.query({ + const updatedResults = await client.query({ query: bulkImportSourceGroupsQuery, + fetchPolicy: 'no-cache', }); - const clientResults = clientResponse.data.bulkImportSourceGroups.nodes; - - expect(clientResults[TARGET_INDEX].import_target).toStrictEqual(FAKE_IMPORT_TARGET); - expect(clientResults[TARGET_INDEX].progress.status).toBe(FAKE_STATUS); - }); - - it('populates each result instance with empty import_target when there are no available namespaces', async () => { - axiosMockAdapter.onGet(FAKE_ENDPOINTS.availableNamespaces).reply(httpStatus.OK, []); - - const response = await client.query({ query: bulkImportSourceGroupsQuery }); - results = response.data.bulkImportSourceGroups.nodes; - expect(results.every((r) => r.import_target.target_namespace === '')).toBe(true); + expect(updatedResults.data.bulkImportSourceGroups.nodes[0].progress).toStrictEqual({ + __typename: clientTypenames.BulkImportProgress, + ...CACHED_DATA.progress, + }); }); describe('when called', () => { @@ -181,37 +120,23 @@ describe('Bulk import resolvers', () => { }); it('mirrors REST endpoint response fields', () => { - const MIRRORED_FIELDS = ['id', 'full_name', 'full_path', 'web_url']; + const MIRRORED_FIELDS = [ + { from: 'id', to: 'id' }, + { from: 'full_name', to: 'fullName' }, + { from: 'full_path', to: 'fullPath' }, + { from: 'web_url', to: 'webUrl' }, + ]; expect( results.every((r, idx) => MIRRORED_FIELDS.every( - (field) => r[field] === statusEndpointFixture.importable_data[idx][field], + (field) => r[field.to] === statusEndpointFixture.importable_data[idx][field.from], ), ), ).toBe(true); }); - it('populates each result instance with status default to none', () => { - expect(results.every((r) => r.progress.status === STATUSES.NONE)).toBe(true); - }); - - it('populates each result instance with import_target defaulted to first available namespace', () => { - expect( - results.every( - (r) => r.import_target.target_namespace === availableNamespacesFixture[0].full_path, - ), - ).toBe(true); - }); - - it('starts polling when request completes', async () => { - const [statusPoller] = StatusPoller.mock.instances; - expect(statusPoller.startPolling).toHaveBeenCalled(); - }); - - it('requests validation status when request completes', async () => { - expect(FAKE_GROUP_AND_PROJECTS_QUERY_HANDLER).not.toHaveBeenCalled(); - jest.runOnlyPendingTimers(); - expect(FAKE_GROUP_AND_PROJECTS_QUERY_HANDLER).toHaveBeenCalled(); + it('populates each result instance with empty status', () => { + expect(results.every((r) => r.progress === null)).toBe(true); }); }); @@ -223,6 +148,7 @@ describe('Bulk import resolvers', () => { `( 'properly passes GraphQL variable $variable as REST $queryParam query parameter', async ({ variable, queryParam, value }) => { + axiosMockAdapter.resetHistory(); await client.query({ query: bulkImportSourceGroupsQuery, variables: { [variable]: value }, @@ -237,275 +163,61 @@ describe('Bulk import resolvers', () => { }); describe('mutations', () => { - const GROUP_ID = 1; - beforeEach(() => { - client.writeQuery({ - query: bulkImportSourceGroupsQuery, - data: { - bulkImportSourceGroups: { - nodes: [ - { - __typename: clientTypenames.BulkImportSourceGroup, - id: GROUP_ID, - progress: { - id: `test-${GROUP_ID}`, - status: STATUSES.NONE, - }, - web_url: 'https://fake.host/1', - full_path: 'fake_group_1', - full_name: 'fake_name_1', - import_target: { - target_namespace: 'root', - new_name: 'group1', - }, - last_import_target: { - target_namespace: 'root', - new_name: 'group1', - }, - validation_errors: [], - }, - ], - pageInfo: { - page: 1, - perPage: 20, - total: 37, - totalPages: 2, - }, - }, - }, - }); + axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(httpStatus.OK, { id: 1 }); }); - describe('setImportTarget', () => { - it('updates group target namespace and name', async () => { - const NEW_TARGET_NAMESPACE = 'target'; - const NEW_NAME = 'new'; - - const { - data: { - setImportTarget: { - id: idInResponse, - import_target: { target_namespace: namespaceInResponse, new_name: newNameInResponse }, - }, - }, - } = await client.mutate({ - mutation: setImportTargetMutation, - variables: { - sourceGroupId: GROUP_ID, - targetNamespace: NEW_TARGET_NAMESPACE, - newName: NEW_NAME, - }, - }); - - expect(idInResponse).toBe(GROUP_ID); - expect(namespaceInResponse).toBe(NEW_TARGET_NAMESPACE); - expect(newNameInResponse).toBe(NEW_NAME); - }); - - it('invokes validation', async () => { - const NEW_TARGET_NAMESPACE = 'target'; - const NEW_NAME = 'new'; - + describe('importGroup', () => { + it('sets import status to CREATED when request completes', async () => { await client.mutate({ - mutation: setImportTargetMutation, + mutation: importGroupsMutation, variables: { - sourceGroupId: GROUP_ID, - targetNamespace: NEW_TARGET_NAMESPACE, - newName: NEW_NAME, + importRequests: [ + { + sourceGroupId: statusEndpointFixture.importable_data[0].id, + newName: 'test', + targetNamespace: 'root', + }, + ], }, }); - expect(FAKE_GROUP_AND_PROJECTS_QUERY_HANDLER).toHaveBeenCalledWith({ - fullPath: `${NEW_TARGET_NAMESPACE}/${NEW_NAME}`, - }); - }); - }); - - describe('importGroup', () => { - it('sets status to SCHEDULING when request initiates', async () => { - axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(() => new Promise(() => {})); - - client.mutate({ - mutation: importGroupsMutation, - variables: { sourceGroupIds: [GROUP_ID] }, - }); - await waitForPromises(); - - const { - bulkImportSourceGroups: { nodes: intermediateResults }, - } = client.readQuery({ - query: bulkImportSourceGroupsQuery, - }); - - expect(intermediateResults[0].progress.status).toBe(STATUSES.SCHEDULING); - }); - - describe('when request completes', () => { - let results; - - beforeEach(() => { - client - .watchQuery({ - query: bulkImportSourceGroupsQuery, - fetchPolicy: 'cache-only', - }) - .subscribe(({ data }) => { - results = data.bulkImportSourceGroups.nodes; - }); - }); - - it('sets import status to CREATED when request completes', async () => { - axiosMockAdapter.onPost(FAKE_ENDPOINTS.createBulkImport).reply(httpStatus.OK, { id: 1 }); - await client.mutate({ - mutation: importGroupsMutation, - variables: { sourceGroupIds: [GROUP_ID] }, - }); - await waitForPromises(); - - expect(results[0].progress.status).toBe(STATUSES.CREATED); - }); - - it('resets status to NONE if request fails', async () => { - axiosMockAdapter - .onPost(FAKE_ENDPOINTS.createBulkImport) - .reply(httpStatus.INTERNAL_SERVER_ERROR); - - client - .mutate({ - mutation: [importGroupsMutation], - variables: { sourceGroupIds: [GROUP_ID] }, - }) - .catch(() => {}); - await waitForPromises(); - - expect(results[0].progress.status).toBe(STATUSES.NONE); - }); - }); - - it('shows default error message when server error is not provided', async () => { - axiosMockAdapter - .onPost(FAKE_ENDPOINTS.createBulkImport) - .reply(httpStatus.INTERNAL_SERVER_ERROR); - - client - .mutate({ - mutation: importGroupsMutation, - variables: { sourceGroupIds: [GROUP_ID] }, - }) - .catch(() => {}); - await waitForPromises(); - - expect(createFlash).toHaveBeenCalledWith({ message: 'Importing the group failed' }); - }); - - it('shows provided error message when error is included in backend response', async () => { - const CUSTOM_MESSAGE = 'custom message'; - - axiosMockAdapter - .onPost(FAKE_ENDPOINTS.createBulkImport) - .reply(httpStatus.INTERNAL_SERVER_ERROR, { error: CUSTOM_MESSAGE }); - - client - .mutate({ - mutation: importGroupsMutation, - variables: { sourceGroupIds: [GROUP_ID] }, - }) - .catch(() => {}); - await waitForPromises(); - - expect(createFlash).toHaveBeenCalledWith({ message: CUSTOM_MESSAGE }); + await axios.waitForAll(); + expect(results[0].progress.status).toBe(STATUSES.CREATED); }); }); - it('setImportProgress updates group progress and sets import target', async () => { + it('updateImportStatus updates status', async () => { const NEW_STATUS = 'dummy'; - const FAKE_JOB_ID = 5; - const IMPORT_TARGET = { - __typename: 'ClientBulkImportTarget', - new_name: 'fake_name', - target_namespace: 'fake_target', - }; - const { - data: { - setImportProgress: { progress, last_import_target: lastImportTarget }, - }, - } = await client.mutate({ - mutation: setImportProgressMutation, + await client.mutate({ + mutation: importGroupsMutation, variables: { - sourceGroupId: GROUP_ID, - status: NEW_STATUS, - jobId: FAKE_JOB_ID, - importTarget: IMPORT_TARGET, + importRequests: [ + { + sourceGroupId: statusEndpointFixture.importable_data[0].id, + newName: 'test', + targetNamespace: 'root', + }, + ], }, }); + await axios.waitForAll(); + await waitForPromises(); - expect(lastImportTarget).toStrictEqual(IMPORT_TARGET); - - expect(progress).toStrictEqual({ - __typename: clientTypenames.BulkImportProgress, - id: FAKE_JOB_ID, - status: NEW_STATUS, - }); - }); + const { id } = results[0].progress; - it('updateImportStatus returns new status', async () => { - const NEW_STATUS = 'dummy'; - const FAKE_JOB_ID = 5; const { data: { updateImportStatus: statusInResponse }, } = await client.mutate({ mutation: updateImportStatusMutation, - variables: { id: FAKE_JOB_ID, status: NEW_STATUS }, + variables: { id, status: NEW_STATUS }, }); expect(statusInResponse).toStrictEqual({ __typename: clientTypenames.BulkImportProgress, - id: FAKE_JOB_ID, + id, status: NEW_STATUS, }); }); - - it('addValidationError adds error to group', async () => { - const FAKE_FIELD = 'some-field'; - const FAKE_MESSAGE = 'some-message'; - const { - data: { - addValidationError: { validation_errors: validationErrors }, - }, - } = await client.mutate({ - mutation: addValidationErrorMutation, - variables: { sourceGroupId: GROUP_ID, field: FAKE_FIELD, message: FAKE_MESSAGE }, - }); - - expect(validationErrors).toStrictEqual([ - { - __typename: clientTypenames.BulkImportValidationError, - field: FAKE_FIELD, - message: FAKE_MESSAGE, - }, - ]); - }); - - it('removeValidationError removes error from group', async () => { - const FAKE_FIELD = 'some-field'; - const FAKE_MESSAGE = 'some-message'; - - await client.mutate({ - mutation: addValidationErrorMutation, - variables: { sourceGroupId: GROUP_ID, field: FAKE_FIELD, message: FAKE_MESSAGE }, - }); - - const { - data: { - removeValidationError: { validation_errors: validationErrors }, - }, - } = await client.mutate({ - mutation: removeValidationErrorMutation, - variables: { sourceGroupId: GROUP_ID, field: FAKE_FIELD }, - }); - - expect(validationErrors).toStrictEqual([]); - }); }); }); diff --git a/spec/frontend/import_entities/import_groups/graphql/fixtures.js b/spec/frontend/import_entities/import_groups/graphql/fixtures.js index d1bd52693b6..5f6f9987a8f 100644 --- a/spec/frontend/import_entities/import_groups/graphql/fixtures.js +++ b/spec/frontend/import_entities/import_groups/graphql/fixtures.js @@ -1,24 +1,24 @@ +import { STATUSES } from '~/import_entities/constants'; import { clientTypenames } from '~/import_entities/import_groups/graphql/client_factory'; export const generateFakeEntry = ({ id, status, ...rest }) => ({ __typename: clientTypenames.BulkImportSourceGroup, - web_url: `https://fake.host/${id}`, - full_path: `fake_group_${id}`, - full_name: `fake_name_${id}`, - import_target: { - target_namespace: 'root', - new_name: `group${id}`, - }, - last_import_target: { - target_namespace: 'root', - new_name: `last-group${id}`, + webUrl: `https://fake.host/${id}`, + fullPath: `fake_group_${id}`, + fullName: `fake_name_${id}`, + lastImportTarget: { + id, + targetNamespace: 'root', + newName: `group${id}`, }, id, - progress: { - id: `test-${id}`, - status, - }, - validation_errors: [], + progress: + status === STATUSES.NONE || status === STATUSES.PENDING + ? null + : { + id, + status, + }, ...rest, }); @@ -51,9 +51,9 @@ export const statusEndpointFixture = { ], }; -export const availableNamespacesFixture = [ - { id: 24, full_path: 'Commit451' }, - { id: 22, full_path: 'gitlab-org' }, - { id: 23, full_path: 'gnuwget' }, - { id: 25, full_path: 'jashkenas' }, -]; +export const availableNamespacesFixture = Object.freeze([ + { id: 24, fullPath: 'Commit451' }, + { id: 22, fullPath: 'gitlab-org' }, + { id: 23, fullPath: 'gnuwget' }, + { id: 25, fullPath: 'jashkenas' }, +]); diff --git a/spec/frontend/import_entities/import_groups/graphql/services/local_storage_cache_spec.js b/spec/frontend/import_entities/import_groups/graphql/services/local_storage_cache_spec.js new file mode 100644 index 00000000000..b44a2767ad8 --- /dev/null +++ b/spec/frontend/import_entities/import_groups/graphql/services/local_storage_cache_spec.js @@ -0,0 +1,61 @@ +import { + KEY, + LocalStorageCache, +} from '~/import_entities/import_groups/graphql/services/local_storage_cache'; + +describe('Local storage cache', () => { + let cache; + let storage; + + beforeEach(() => { + storage = { + getItem: jest.fn(), + setItem: jest.fn(), + }; + + cache = new LocalStorageCache({ storage }); + }); + + describe('storage management', () => { + const IMPORT_URL = 'http://fake.url'; + + it('loads state from storage on creation', () => { + expect(storage.getItem).toHaveBeenCalledWith(KEY); + }); + + it('saves to storage when set is called', () => { + const STORAGE_CONTENT = { fake: 'content ' }; + cache.set(IMPORT_URL, STORAGE_CONTENT); + expect(storage.setItem).toHaveBeenCalledWith( + KEY, + JSON.stringify({ [IMPORT_URL]: STORAGE_CONTENT }), + ); + }); + + it('updates status by job id', () => { + const CHANGED_STATUS = 'changed'; + const JOB_ID = 2; + + cache.set(IMPORT_URL, { + progress: { + id: JOB_ID, + status: 'original', + }, + }); + + cache.updateStatusByJobId(JOB_ID, CHANGED_STATUS); + + expect(storage.setItem).toHaveBeenCalledWith( + KEY, + JSON.stringify({ + [IMPORT_URL]: { + progress: { + id: JOB_ID, + status: CHANGED_STATUS, + }, + }, + }), + ); + }); + }); +}); diff --git a/spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js b/spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js deleted file mode 100644 index f06babcb149..00000000000 --- a/spec/frontend/import_entities/import_groups/graphql/services/source_groups_manager_spec.js +++ /dev/null @@ -1,64 +0,0 @@ -import { - KEY, - SourceGroupsManager, -} from '~/import_entities/import_groups/graphql/services/source_groups_manager'; - -const FAKE_SOURCE_URL = 'http://demo.host'; - -describe('SourceGroupsManager', () => { - let manager; - let storage; - - beforeEach(() => { - storage = { - getItem: jest.fn(), - setItem: jest.fn(), - }; - - manager = new SourceGroupsManager({ storage, sourceUrl: FAKE_SOURCE_URL }); - }); - - describe('storage management', () => { - const IMPORT_ID = 1; - const IMPORT_TARGET = { new_name: 'demo', target_namespace: 'foo' }; - const STATUS = 'FAKE_STATUS'; - const FAKE_GROUP = { id: 1, import_target: IMPORT_TARGET, status: STATUS }; - - it('loads state from storage on creation', () => { - expect(storage.getItem).toHaveBeenCalledWith(KEY); - }); - - it('saves to storage when createImportState is called', () => { - const FAKE_STATUS = 'fake;'; - manager.createImportState(IMPORT_ID, { status: FAKE_STATUS, groups: [FAKE_GROUP] }); - const storedObject = JSON.parse(storage.setItem.mock.calls[0][1]); - expect(Object.values(storedObject)[0]).toStrictEqual({ - status: FAKE_STATUS, - groups: [ - { - id: FAKE_GROUP.id, - importTarget: IMPORT_TARGET, - }, - ], - }); - }); - - it('updates storage when previous state is available', () => { - const CHANGED_STATUS = 'changed'; - - manager.createImportState(IMPORT_ID, { status: STATUS, groups: [FAKE_GROUP] }); - - manager.updateImportProgress(IMPORT_ID, CHANGED_STATUS); - const storedObject = JSON.parse(storage.setItem.mock.calls[1][1]); - expect(Object.values(storedObject)[0]).toStrictEqual({ - status: CHANGED_STATUS, - groups: [ - { - id: FAKE_GROUP.id, - importTarget: IMPORT_TARGET, - }, - ], - }); - }); - }); -}); diff --git a/spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js b/spec/frontend/import_entities/import_groups/services/status_poller_spec.js index 9c47647c430..01f976562c6 100644 --- a/spec/frontend/import_entities/import_groups/graphql/services/status_poller_spec.js +++ b/spec/frontend/import_entities/import_groups/services/status_poller_spec.js @@ -2,19 +2,13 @@ import MockAdapter from 'axios-mock-adapter'; import Visibility from 'visibilityjs'; import createFlash from '~/flash'; import { STATUSES } from '~/import_entities/constants'; -import { StatusPoller } from '~/import_entities/import_groups/graphql/services/status_poller'; +import { StatusPoller } from '~/import_entities/import_groups/services/status_poller'; import axios from '~/lib/utils/axios_utils'; import Poll from '~/lib/utils/poll'; jest.mock('visibilityjs'); jest.mock('~/flash'); jest.mock('~/lib/utils/poll'); -jest.mock('~/import_entities/import_groups/graphql/services/source_groups_manager', () => ({ - SourceGroupsManager: jest.fn().mockImplementation(function mock() { - this.setImportStatus = jest.fn(); - this.findByImportId = jest.fn(); - }), -})); const FAKE_POLL_PATH = '/fake/poll/path'; @@ -81,6 +75,7 @@ describe('Bulk import status poller', () => { const [pollInstance] = Poll.mock.instances; poller.startPolling(); + await Promise.resolve(); expect(pollInstance.makeRequest).toHaveBeenCalled(); }); |