diff options
Diffstat (limited to 'spec/frontend/import_entities/import_projects/store/actions_spec.js')
-rw-r--r-- | spec/frontend/import_entities/import_projects/store/actions_spec.js | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/spec/frontend/import_entities/import_projects/store/actions_spec.js b/spec/frontend/import_entities/import_projects/store/actions_spec.js new file mode 100644 index 00000000000..5d4e73a17a3 --- /dev/null +++ b/spec/frontend/import_entities/import_projects/store/actions_spec.js @@ -0,0 +1,398 @@ +import MockAdapter from 'axios-mock-adapter'; +import testAction from 'helpers/vuex_action_helper'; +import { TEST_HOST } from 'helpers/test_constants'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; +import axios from '~/lib/utils/axios_utils'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { + REQUEST_REPOS, + RECEIVE_REPOS_SUCCESS, + RECEIVE_REPOS_ERROR, + REQUEST_IMPORT, + RECEIVE_IMPORT_SUCCESS, + RECEIVE_IMPORT_ERROR, + RECEIVE_JOBS_SUCCESS, + REQUEST_NAMESPACES, + RECEIVE_NAMESPACES_SUCCESS, + RECEIVE_NAMESPACES_ERROR, + SET_PAGE, + SET_FILTER, +} from '~/import_entities/import_projects/store/mutation_types'; +import actionsFactory from '~/import_entities/import_projects/store/actions'; +import { getImportTarget } from '~/import_entities/import_projects/store/getters'; +import state from '~/import_entities/import_projects/store/state'; +import { STATUSES } from '~/import_entities/constants'; + +jest.mock('~/flash'); + +const MOCK_ENDPOINT = `${TEST_HOST}/endpoint.json`; +const endpoints = { + reposPath: MOCK_ENDPOINT, + importPath: MOCK_ENDPOINT, + jobsPath: MOCK_ENDPOINT, + namespacesPath: MOCK_ENDPOINT, +}; + +const { + clearJobsEtagPoll, + stopJobsPolling, + importAll, + fetchRepos, + fetchImport, + fetchJobs, + fetchNamespaces, + setFilter, +} = actionsFactory({ + endpoints, +}); + +describe('import_projects store actions', () => { + let localState; + const importRepoId = 1; + const otherImportRepoId = 2; + const defaultTargetNamespace = 'default'; + const sanitizedName = 'sanitizedName'; + const defaultImportTarget = { newName: sanitizedName, targetNamespace: defaultTargetNamespace }; + + beforeEach(() => { + localState = { + ...state(), + defaultTargetNamespace, + repositories: [ + { importSource: { id: importRepoId, sanitizedName }, importStatus: STATUSES.NONE }, + { + importSource: { id: otherImportRepoId, sanitizedName: 's2' }, + importStatus: STATUSES.NONE, + }, + { + importSource: { id: 3, sanitizedName: 's3', incompatible: true }, + importStatus: STATUSES.NONE, + }, + ], + provider: 'provider', + }; + + localState.getImportTarget = getImportTarget(localState); + }); + + describe('fetchRepos', () => { + let mock; + const payload = { imported_projects: [{}], provider_repos: [{}] }; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => mock.restore()); + + it('commits SET_PAGE, REQUEST_REPOS, RECEIVE_REPOS_SUCCESS mutations on a successful request', () => { + mock.onGet(MOCK_ENDPOINT).reply(200, payload); + + return testAction( + fetchRepos, + null, + localState, + [ + { type: SET_PAGE, payload: 1 }, + { type: REQUEST_REPOS }, + { + type: RECEIVE_REPOS_SUCCESS, + payload: convertObjectPropsToCamelCase(payload, { deep: true }), + }, + ], + [], + ); + }); + + it('commits SET_PAGE, REQUEST_REPOS, RECEIVE_REPOS_ERROR and SET_PAGE again mutations on an unsuccessful request', () => { + mock.onGet(MOCK_ENDPOINT).reply(500); + + return testAction( + fetchRepos, + null, + localState, + [ + { type: SET_PAGE, payload: 1 }, + { type: REQUEST_REPOS }, + { type: SET_PAGE, payload: 0 }, + { type: RECEIVE_REPOS_ERROR }, + ], + [], + ); + }); + + it('includes page in url query params', async () => { + let requestedUrl; + mock.onGet().reply(config => { + requestedUrl = config.url; + return [200, payload]; + }); + + const localStateWithPage = { ...localState, pageInfo: { page: 2 } }; + + await testAction(fetchRepos, null, localStateWithPage, expect.any(Array), expect.any(Array)); + + expect(requestedUrl).toBe(`${MOCK_ENDPOINT}?page=${localStateWithPage.pageInfo.page + 1}`); + }); + + it('correctly updates current page on an unsuccessful request', () => { + mock.onGet(MOCK_ENDPOINT).reply(500); + const CURRENT_PAGE = 5; + + return testAction( + fetchRepos, + null, + { ...localState, pageInfo: { page: CURRENT_PAGE } }, + expect.arrayContaining([ + { type: SET_PAGE, payload: CURRENT_PAGE + 1 }, + { type: SET_PAGE, payload: CURRENT_PAGE }, + ]), + [], + ); + }); + + describe('when rate limited', () => { + it('commits RECEIVE_REPOS_ERROR and shows rate limited error message', async () => { + mock.onGet(`${TEST_HOST}/endpoint.json?filter=filter`).reply(429); + + await testAction( + fetchRepos, + null, + { ...localState, filter: 'filter' }, + [ + { type: SET_PAGE, payload: 1 }, + { type: REQUEST_REPOS }, + { type: SET_PAGE, payload: 0 }, + { type: RECEIVE_REPOS_ERROR }, + ], + [], + ); + + expect(createFlash).toHaveBeenCalledWith('Provider rate limit exceeded. Try again later'); + }); + }); + + describe('when filtered', () => { + it('fetches repos with filter applied', () => { + mock.onGet(`${TEST_HOST}/endpoint.json?filter=filter`).reply(200, payload); + + return testAction( + fetchRepos, + null, + { ...localState, filter: 'filter' }, + [ + { type: SET_PAGE, payload: 1 }, + { type: REQUEST_REPOS }, + { + type: RECEIVE_REPOS_SUCCESS, + payload: convertObjectPropsToCamelCase(payload, { deep: true }), + }, + ], + [], + ); + }); + }); + }); + + describe('fetchImport', () => { + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => mock.restore()); + + it('commits REQUEST_IMPORT and REQUEST_IMPORT_SUCCESS mutations on a successful request', () => { + const importedProject = { name: 'imported/project' }; + mock.onPost(MOCK_ENDPOINT).reply(200, importedProject); + + return testAction( + fetchImport, + importRepoId, + localState, + [ + { + type: REQUEST_IMPORT, + payload: { repoId: importRepoId, importTarget: defaultImportTarget }, + }, + { + type: RECEIVE_IMPORT_SUCCESS, + payload: { + importedProject: convertObjectPropsToCamelCase(importedProject, { deep: true }), + repoId: importRepoId, + }, + }, + ], + [], + ); + }); + + it('commits REQUEST_IMPORT and RECEIVE_IMPORT_ERROR and shows generic error message on an unsuccessful request', async () => { + mock.onPost(MOCK_ENDPOINT).reply(500); + + await testAction( + fetchImport, + importRepoId, + localState, + [ + { + type: REQUEST_IMPORT, + payload: { repoId: importRepoId, importTarget: defaultImportTarget }, + }, + { type: RECEIVE_IMPORT_ERROR, payload: importRepoId }, + ], + [], + ); + + expect(createFlash).toHaveBeenCalledWith('Importing the project failed'); + }); + + it('commits REQUEST_IMPORT and RECEIVE_IMPORT_ERROR and shows detailed error message on an unsuccessful request with errors fields in response', async () => { + const ERROR_MESSAGE = 'dummy'; + mock.onPost(MOCK_ENDPOINT).reply(500, { errors: ERROR_MESSAGE }); + + await testAction( + fetchImport, + importRepoId, + localState, + [ + { + type: REQUEST_IMPORT, + payload: { repoId: importRepoId, importTarget: defaultImportTarget }, + }, + { type: RECEIVE_IMPORT_ERROR, payload: importRepoId }, + ], + [], + ); + + expect(createFlash).toHaveBeenCalledWith(`Importing the project failed: ${ERROR_MESSAGE}`); + }); + }); + + describe('fetchJobs', () => { + let mock; + const updatedProjects = [{ name: 'imported/project' }, { name: 'provider/repo' }]; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + stopJobsPolling(); + clearJobsEtagPoll(); + }); + + afterEach(() => mock.restore()); + + it('commits RECEIVE_JOBS_SUCCESS mutation on a successful request', async () => { + mock.onGet(MOCK_ENDPOINT).reply(200, updatedProjects); + + await testAction( + fetchJobs, + null, + localState, + [ + { + type: RECEIVE_JOBS_SUCCESS, + payload: convertObjectPropsToCamelCase(updatedProjects, { deep: true }), + }, + ], + [], + ); + }); + + describe('when filtered', () => { + beforeEach(() => { + localState.filter = 'filter'; + }); + + it('fetches realtime changes with filter applied', () => { + mock.onGet(`${TEST_HOST}/endpoint.json?filter=filter`).reply(200, updatedProjects); + + return testAction( + fetchJobs, + null, + localState, + [ + { + type: RECEIVE_JOBS_SUCCESS, + payload: convertObjectPropsToCamelCase(updatedProjects, { deep: true }), + }, + ], + [], + ); + }); + }); + }); + + describe('fetchNamespaces', () => { + let mock; + const namespaces = [{ full_name: 'test/ns1' }, { full_name: 'test_ns2' }]; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => mock.restore()); + + it('commits REQUEST_NAMESPACES and RECEIVE_NAMESPACES_SUCCESS on success', async () => { + mock.onGet(MOCK_ENDPOINT).reply(200, namespaces); + + await testAction( + fetchNamespaces, + null, + localState, + [ + { type: REQUEST_NAMESPACES }, + { + type: RECEIVE_NAMESPACES_SUCCESS, + payload: convertObjectPropsToCamelCase(namespaces, { deep: true }), + }, + ], + [], + ); + }); + + it('commits REQUEST_NAMESPACES and RECEIVE_NAMESPACES_ERROR and shows generic error message on an unsuccessful request', async () => { + mock.onGet(MOCK_ENDPOINT).reply(500); + + await testAction( + fetchNamespaces, + null, + localState, + [{ type: REQUEST_NAMESPACES }, { type: RECEIVE_NAMESPACES_ERROR }], + [], + ); + + expect(createFlash).toHaveBeenCalledWith('Requesting namespaces failed'); + }); + }); + + describe('importAll', () => { + it('dispatches multiple fetchImport actions', async () => { + await testAction( + importAll, + null, + localState, + [], + [ + { type: 'fetchImport', payload: importRepoId }, + { type: 'fetchImport', payload: otherImportRepoId }, + ], + ); + }); + }); + + describe('setFilter', () => { + it('dispatches sets the filter value and dispatches fetchRepos', async () => { + await testAction( + setFilter, + 'filteredRepo', + localState, + [{ type: SET_FILTER, payload: 'filteredRepo' }], + [{ type: 'fetchRepos' }], + ); + }); + }); +}); |