From 6be39f3140392952f42f3e729170a85dd434bc09 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Fri, 21 May 2021 15:06:05 +0800 Subject: [PATCH 01/36] refactor: replace object with typescript FormService --- .../forms/services/form-api.client.factory.js | 135 ++++--------- src/public/services/FormService.ts | 185 ++++++++++++++++++ 2 files changed, 226 insertions(+), 94 deletions(-) create mode 100644 src/public/services/FormService.ts diff --git a/src/public/modules/forms/services/form-api.client.factory.js b/src/public/modules/forms/services/form-api.client.factory.js index c7e223b54b..48bea61d3a 100644 --- a/src/public/modules/forms/services/form-api.client.factory.js +++ b/src/public/modules/forms/services/form-api.client.factory.js @@ -1,12 +1,11 @@ 'use strict' - const { get } = require('lodash') const Form = require('../viewmodels/Form.class') - +const FormService = require('../../../services/FormService') // Forms service used for communicating with the forms REST endpoints angular .module('forms') - .factory('FormApi', ['$resource', 'FormErrorService', 'FormFields', FormApi]) + .factory('FormApi', ['FormErrorService', 'FormFields', FormApi]) // Helper function for getting formID from path starting with /:formId/. // If form ID is not found, returns an empty string. @@ -25,7 +24,7 @@ const extractFormId = (path) => { * Service for making API calls to /:formId/:accessMode endpoint, which is used * for all CRUD operations for forms. */ -function FormApi($resource, FormErrorService, FormFields) { +function FormApi(FormErrorService, FormFields) { /** * Used to generate interceptors for all API calls. These interceptors have the * following responsibilities: @@ -53,101 +52,49 @@ function FormApi($resource, FormErrorService, FormFields) { // all the form data; for PUT requests, it returns a $resource instance with // all the form data at the top level. We need to ensure that the postprocessing // is done in both cases. - if (get(response, 'resource.form.form_fields')) { - FormFields.injectMyInfoIntoForm(response.resource.form) + if (get(response, 'data.form.form_fields')) { + FormFields.injectMyInfoIntoForm(response.data.form) // Convert plain form object to smart Form instance - response.resource.form = new Form(response.resource.form) - } else if (get(response, 'resource.form_fields')) { - FormFields.injectMyInfoIntoForm(response.resource) - response.resource = new Form(response.resource) + response.data.form = new Form(response.data.form) + } else if (get(response, 'data.form_fields')) { + FormFields.injectMyInfoIntoForm(response.data) + response.data = new Form(response.data) } - return response.resource + return response }, } - if (redirectOnError) { - interceptor.responseError = (response) => { - return FormErrorService.redirect({ - response, - targetState: errorTargetState, - targetFormId: extractFormId(get(response, 'config.url')), - }) - } - } + interceptor.responseError = redirectOnError + ? (response) => { + return FormErrorService.redirect({ + response, + targetState: errorTargetState, + targetFormId: extractFormId(get(response, 'config.url')), + }) + } + : null return interceptor } - // accessMode is either adminForm or publicForm - let resourceUrl = '/:formId/:accessMode' - const V3_PUBLICFORM_URL = '/api/v3/forms/:formId' - - return $resource( - resourceUrl, - // default to admin for access mode, since that applies to most methods - { accessMode: 'adminform' }, - { - query: { - url: '/api/v3/admin/forms', - method: 'GET', - isArray: true, - headers: { 'If-Modified-Since': '0' }, - interceptor: getInterceptor(true, 'listForms'), - // disable IE ajax request caching (so new forms show on dashboard) - }, - getAdmin: { - method: 'GET', - headers: { 'If-Modified-Since': '0' }, - interceptor: getInterceptor(true, 'viewForm'), - // disable IE ajax request caching (so new fields show on build panel) - }, - getPublic: { - url: V3_PUBLICFORM_URL, - method: 'GET', - // disable IE ajax request caching (so new fields show on build panel) - headers: { 'If-Modified-Since': '0' }, - interceptor: getInterceptor(true), - }, - update: { - method: 'PUT', - interceptor: getInterceptor(false), - }, - save: { - url: '/api/v3/admin/forms/:formId/duplicate', - method: 'POST', - interceptor: getInterceptor(false), - }, - delete: { - url: '/api/v3/admin/forms/:formId', - method: 'DELETE', - interceptor: getInterceptor(false), - }, - // create is called without formId, so the endpoint is just /adminform - create: { - url: '/api/v3/admin/forms', - method: 'POST', - interceptor: getInterceptor(true, 'listForms'), - }, - // Used for viewing templates with use-template or examples listing. Any logged in officer is authorized. - template: { - url: resourceUrl + '/template', - method: 'GET', - interceptor: getInterceptor(true, 'templateForm'), - }, - // Used for previewing the form from the form admin page. Must be a viewer, collaborator or admin. - preview: { - url: '/api/v3/admin/forms/:formId/preview', - method: 'GET', - interceptor: getInterceptor(true, 'previewForm'), - }, - useTemplate: { - url: resourceUrl + '/copy', - method: 'POST', - interceptor: getInterceptor(true, 'useTemplate'), - }, - transferOwner: { - url: '/api/v3/admin/forms/:formId/collaborators/transfer-owner', - method: 'POST', - interceptor: getInterceptor(false), - }, - }, - ) + return { + query: () => FormService.queryForm(getInterceptor(true, 'listForms')), + getAdmin: (formId) => + FormService.getAdminForm(formId, getInterceptor(true, 'viewForm')), + getPublic: (formId) => + FormService.getPublicForm(formId, getInterceptor(true)), + update: (formId, update) => + FormService.updateForm(formId, update, getInterceptor(false)), + save: (formId, formToSave) => + FormService.saveForm(formId, formToSave, getInterceptor(false)), + delete: (formId) => FormService.deleteForm(formId, getInterceptor(false)), + create: (newForm) => + FormService.createForm(newForm, getInterceptor(true, 'listForms')), + template: (formId) => + FormService.queryTemplate(formId, getInterceptor(true, 'templateForm')), + preview: (formId) => + FormService.previewForm(formId, getInterceptor(true, 'previewForm')), + useTemplate: (formId) => + FormService.useTemplate(formId, getInterceptor(true, 'useTemplate')), + transferOwner: (formId) => + FormService.transferOwner(formId, getInterceptor(false)), + } } diff --git a/src/public/services/FormService.ts b/src/public/services/FormService.ts new file mode 100644 index 0000000000..9ac5d35104 --- /dev/null +++ b/src/public/services/FormService.ts @@ -0,0 +1,185 @@ +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' + +import { FormUpdateParams } from 'src/app/modules/form/admin-form/admin-form.types' +import { PublicFormViewDto } from 'src/app/modules/form/public-form/public-form.types' +import { + FormMetaView, + IFormSchema, + IPopulatedForm, + PublicForm, +} from 'src/types' +import { FormFieldDto } from 'src/types/api' + +// endpoints exported for testing +export const ADMIN_FORM_ENDPOINT = '/api/v3/admin/forms' +export const PUBLIC_FORM_ENDPOINT = '/api/v3/forms' + +type Interceptor = { + response: (response: AxiosResponse) => AxiosResponse | Promise + request: ( + config: AxiosRequestConfig, + ) => AxiosRequestConfig | Promise + responseError: ((response: AxiosResponse) => unknown) | null +} + +export const queryForm = async ( + interceptor: Interceptor, +): Promise => { + const instance = axios.create() + _addInterceptor(instance, interceptor) + return instance + .get(`${ADMIN_FORM_ENDPOINT}`, { + headers: { 'If-Modified-Since': '0' }, + }) + .then(({ data }) => data) +} + +export const getAdminForm = async ( + formId: string, + interceptor: Interceptor, + accessMode = 'adminform', +): Promise<{ form: IPopulatedForm }> => { + const instance = axios.create() + _addInterceptor(instance, interceptor) + // disable IE ajax request caching (so new forms show on dashboard) + return instance + .get<{ form: IPopulatedForm }>(`/${formId}/${accessMode}`, { + headers: { 'If-Modified-Since': '0' }, + }) + .then(({ data }) => data) +} + +export const getPublicForm = async ( + formId: string, + interceptor: Interceptor, +): Promise => { + const instance = axios.create() + _addInterceptor(instance, interceptor) + // disable IE ajax request caching (so new forms show on dashboard) + return instance + .get(`${PUBLIC_FORM_ENDPOINT}/${formId}`, { + headers: { 'If-Modified-Since': '0' }, + }) + .then(({ data }) => data) +} + +export const updateForm = async ( + formId: string, + update: { form: FormUpdateParams }, + interceptor: Interceptor, + accessMode = 'adminform', +): Promise => { + const instance = axios.create() + _addInterceptor(instance, interceptor) + return instance + .put(`${formId}/${accessMode}`, update) + .then(({ data }) => data) +} + +export const saveForm = async ( + formId: string, + formToSave: FormFieldDto[], + interceptor: Interceptor, +): Promise => { + const instance = axios.create() + _addInterceptor(instance, interceptor) + return instance + .post( + `${ADMIN_FORM_ENDPOINT}/${formId}/duplicate`, + formToSave, + ) + .then(({ data }) => data) +} + +export const deleteForm = async ( + formId: string, + interceptor: Interceptor, +): Promise => { + const instance = axios.create() + _addInterceptor(instance, interceptor) + return instance.delete(`${ADMIN_FORM_ENDPOINT}/${formId}`) +} + +// createForm is called without formId, so the endpoint is just /adminform +export const createForm = async ( + newForm: { form: FormFieldDto[] }, + interceptor: Interceptor, +): Promise => { + const instance = axios.create() + _addInterceptor(instance, interceptor) + return instance + .post(`${ADMIN_FORM_ENDPOINT}`, newForm) + .then(({ data }) => data) +} + +// Used for viewing templates with use-template or examples listing. Any logged in officer is authorized. +export const queryTemplate = async ( + formId: string, + interceptor: Interceptor, + accessMode = 'adminform', +): Promise<{ form: PublicForm }> => { + const instance = axios.create() + _addInterceptor(instance, interceptor) + return instance + .get<{ form: PublicForm }>( + `${ADMIN_FORM_ENDPOINT}/${formId}/${accessMode}/template`, + ) + .then(({ data }) => data) +} + +// Used for previewing the form from the form admin page. Must be a viewer, collaborator or admin. +export const previewForm = async ( + formId: string, + interceptor: Interceptor, +): Promise<{ form: PublicForm }> => { + const instance = axios.create() + _addInterceptor(instance, interceptor) + return instance + .get<{ form: PublicForm }>(`${ADMIN_FORM_ENDPOINT}/${formId}/preview`) + .then(({ data }) => data) +} + +export const useTemplate = async ( + formId: string, + interceptor: Interceptor, + accessMode = 'adminform', +): Promise => { + const instance = axios.create() + _addInterceptor(instance, interceptor) + return instance + .post(`${ADMIN_FORM_ENDPOINT}/${formId}/${accessMode}/copy`) + .then(({ data }) => data) +} + +export const transferOwner = async ( + formId: string, + interceptor: Interceptor, +): Promise<{ form: IPopulatedForm }> => { + const instance = axios.create() + _addInterceptor(instance, interceptor) + return instance + .post<{ form: IPopulatedForm }>( + `${ADMIN_FORM_ENDPOINT}/${formId}/collaborators/transfer-owner`, + ) + .then(({ data }) => data) +} + +/** + * Adds both a response and request interceptor to the axios instance + * @param instance Axios Instance created for a specific API call + * @param interceptor Interceptor for API call + */ +const _addInterceptor = (instance: AxiosInstance, interceptor: Interceptor) => { + const responseError = interceptor.responseError + if (responseError) { + instance.interceptors.response.use( + (response) => interceptor.response(response), + (response) => responseError(response), + ) + } else { + instance.interceptors.response.use((response) => + interceptor.response(response), + ) + } + instance.interceptors.request.use((config) => interceptor.request(config)) +} From aa0198ebcc24e66155fdd50f5a721bd2c374af88 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Fri, 21 May 2021 15:06:43 +0800 Subject: [PATCH 02/36] test: add test for FormService --- .../services/__tests__/FormService.test.ts | 557 ++++++++++++++++++ 1 file changed, 557 insertions(+) create mode 100644 src/public/services/__tests__/FormService.test.ts diff --git a/src/public/services/__tests__/FormService.test.ts b/src/public/services/__tests__/FormService.test.ts new file mode 100644 index 0000000000..56d5fba67b --- /dev/null +++ b/src/public/services/__tests__/FormService.test.ts @@ -0,0 +1,557 @@ +import { AxiosRequestConfig, AxiosResponse } from 'axios' +import { ObjectId } from 'bson' +import { StatusCodes } from 'http-status-codes' +import mockAxios from 'jest-mock-axios' + +import { FormUpdateParams } from 'src/app/modules/form/admin-form/admin-form.types' +import { IFieldSchema, IPopulatedUser, IYesNoFieldSchema } from 'src/types' +import { + FormMetaView, + IPopulatedForm, + PublicForm, + ResponseMode, +} from 'src/types/form' + +import { + ADMIN_FORM_ENDPOINT, + createForm, + deleteForm, + getAdminForm, + getPublicForm, + previewForm, + PUBLIC_FORM_ENDPOINT, + queryForm, + queryTemplate, + saveForm, + transferOwner, + updateForm, + useTemplate, +} from '../FormService' + +jest.mock('axios', () => mockAxios) + +const MOCK_USER = { + _id: new ObjectId(), +} as IPopulatedUser + +describe('FormService', () => { + afterEach(() => mockAxios.reset()) + const MOCK_INTERCEPTOR = { + response: (response: AxiosResponse) => response, + request: (config: AxiosRequestConfig) => config, + responseError: null, + } + describe('queryForm', () => { + it('should successfully return all available forms if GET request succeeds', async () => { + // Arrange + const expected: FormMetaView[] = [_generateMockDashboardViewForm()] + + // Act + const actualPromise = queryForm(MOCK_INTERCEPTOR) + mockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(mockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`, { + headers: { 'If-Modified-Since': '0' }, + }) + }) + + it('should successfully return empty array if GET request succeeds and there are no forms', async () => { + // Arrange + const expected: FormMetaView[] = [] + + // Act + const actualPromise = queryForm(MOCK_INTERCEPTOR) + mockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(mockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`, { + headers: { 'If-Modified-Since': '0' }, + }) + }) + + it('should reject with error message if GET request fails', async () => { + // Arrange + const expected = new Error('error') + + // Act + const actualPromise = queryForm(MOCK_INTERCEPTOR) + mockAxios.mockError(expected) + + //Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(mockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`, { + headers: { 'If-Modified-Since': '0' }, + }) + }) + }) + + describe('getAdminForm', () => { + it('should return admin form if GET request succeeds', async () => { + // Arrange + const expected = _generateMockFullForm() + const accessMode = 'adminform' + + // Act + const actualPromise = getAdminForm( + expected._id, + MOCK_INTERCEPTOR, + accessMode, + ) + mockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(mockAxios.get).toHaveBeenCalledWith( + `/${expected._id}/${accessMode}`, + { + headers: { 'If-Modified-Since': '0' }, + }, + ) + }) + + it('should reject with error message when GET request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM = _generateMockFullForm() + const accessMode = 'adminform' + + // Act + const actualPromise = getAdminForm( + MOCK_FORM._id, + MOCK_INTERCEPTOR, + accessMode, + ) + mockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(mockAxios.get).toHaveBeenCalledWith( + `/${MOCK_FORM._id}/${accessMode}`, + { + headers: { 'If-Modified-Since': '0' }, + }, + ) + }) + }) + + describe('getPublicForm', () => { + it('should return public form if GET request succeeds', async () => { + // Arrange + const MOCK_FORM_ID = new ObjectId().toHexString() + const expected = { + form: { _id: MOCK_FORM_ID, form_fields: [] }, + spcpSession: { username: 'username' }, + isIntranetUser: false, + myInfoError: true, + } + + // Act + const actualPromise = getPublicForm(MOCK_FORM_ID, MOCK_INTERCEPTOR) + mockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(mockAxios.get).toHaveBeenCalledWith( + `${PUBLIC_FORM_ENDPOINT}/${MOCK_FORM_ID}`, + { + headers: { 'If-Modified-Since': '0' }, + }, + ) + }) + + it('should reject with error message if GET request fails', async () => { + // Arrange + const MOCK_FORM_ID = new ObjectId().toHexString() + const expected = new Error('error') + + // Act + const actualPromise = getPublicForm(MOCK_FORM_ID, MOCK_INTERCEPTOR) + mockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(mockAxios.get).toHaveBeenCalledWith( + `${PUBLIC_FORM_ENDPOINT}/${MOCK_FORM_ID}`, + { + headers: { 'If-Modified-Since': '0' }, + }, + ) + }) + }) + + describe('updateForm', () => { + it('should return updated form if PUT request succeeds', async () => { + // Arrange + const expected = [_generateMockField()] + const MOCK_FORM_ID = new ObjectId().toHexString() + const update = { + form: { + editFormField: { + action: { name: 'REORDER' }, + field: expected[0], + }, + } as FormUpdateParams, + } + const accessMode = 'adminform' + + // Act + const actualPromise = updateForm( + MOCK_FORM_ID, + update, + MOCK_INTERCEPTOR, + accessMode, + ) + mockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(mockAxios.put).toHaveBeenCalledWith( + `${MOCK_FORM_ID}/${accessMode}`, + update, + ) + }) + + it('should reject with error message if PUT request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = new ObjectId().toHexString() + const update = { + form: { + editFormField: { + action: { name: 'REORDER' }, + field: _generateMockField(), + }, + } as FormUpdateParams, + } + const accessMode = 'adminform' + + // Act + const actualPromise = updateForm(MOCK_FORM_ID, update, MOCK_INTERCEPTOR) + mockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(mockAxios.put).toHaveBeenCalledWith( + `${MOCK_FORM_ID}/${accessMode}`, + update, + ) + }) + + describe('saveForm', () => { + it('should return saved form if POST request succeeds', async () => { + // Arrange + const expected = _generateMockDashboardViewForm() + const MOCK_FORM_ID = expected._id + const MOCK_FIELDS = [_generateMockField()] + + // Act + const actualPromise = saveForm( + MOCK_FORM_ID, + MOCK_FIELDS, + MOCK_INTERCEPTOR, + ) + mockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(mockAxios.post).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/duplicate`, + MOCK_FIELDS, + ) + }) + + it('should reject with error message if POST request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = new ObjectId().toHexString() + const MOCK_FIELDS = [_generateMockField()] + + // Act + const actualPromise = saveForm( + MOCK_FORM_ID, + MOCK_FIELDS, + MOCK_INTERCEPTOR, + ) + mockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(mockAxios.post).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/duplicate`, + MOCK_FIELDS, + ) + }) + }) + }) + + describe('deleteForm', () => { + it('should successfully call delete endpoint', async () => { + // Arrange + const MOCK_FORM_ID = new ObjectId().toHexString() + + // Act + const actualPromise = deleteForm(MOCK_FORM_ID, MOCK_INTERCEPTOR) + mockAxios.mockResponse({ + status: StatusCodes.OK, + data: { message: 'Form has been archived' }, + }) + await actualPromise + + // Assert + expect(mockAxios.delete).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}`, + ) + }) + + it('should reject with error message if DELETE request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = new ObjectId().toHexString() + + // Act + const actualPromise = deleteForm(MOCK_FORM_ID, MOCK_INTERCEPTOR) + mockAxios.mockError(expected) + + await expect(actualPromise).rejects.toEqual(expected) + // Assert + expect(mockAxios.delete).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}`, + ) + }) + }) + + describe('createForm', () => { + it('should return created form if POST request succeeds', async () => { + // Arrange + const expected = { form_fields: [_generateMockField()] } + const MOCK_FORM_PARAMS = { form: [_generateMockField()] } + + // Act + const actualPromise = createForm(MOCK_FORM_PARAMS, MOCK_INTERCEPTOR) + mockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(mockAxios.post).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}`, + MOCK_FORM_PARAMS, + ) + }) + + it('should reject with error message if POST request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_PARAMS = { form: [_generateMockField()] } + + // Act + const actualPromise = createForm(MOCK_FORM_PARAMS, MOCK_INTERCEPTOR) + mockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(mockAxios.post).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}`, + MOCK_FORM_PARAMS, + ) + }) + }) + + describe('queryTemplate', () => { + it('should return template if GET request succeeds', async () => { + // Arrange + const expected = _generateMockPublicForm() + const MOCK_FORM_ID = new ObjectId().toHexString() + const accessMode = 'adminform' + + // Act + const actualPromise = queryTemplate( + MOCK_FORM_ID, + MOCK_INTERCEPTOR, + accessMode, + ) + mockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(mockAxios.get).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/${accessMode}/template`, + ) + }) + + it('should reject with error message if GET request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = new ObjectId().toHexString() + const accessMode = 'adminform' + + // Act + const actualPromise = queryTemplate( + MOCK_FORM_ID, + MOCK_INTERCEPTOR, + accessMode, + ) + mockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(mockAxios.get).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/${accessMode}/template`, + ) + }) + }) + + describe('previewForm', () => { + it('should return public form if GET request succeeds', async () => { + // Arrange + const expected = _generateMockPublicForm() + const MOCK_FORM_ID = new ObjectId().toHexString() + + // Act + const actualPromise = previewForm(MOCK_FORM_ID, MOCK_INTERCEPTOR) + mockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(mockAxios.get).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/preview`, + ) + }) + + it('should reject with error message if GET request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = new ObjectId().toHexString() + + // Act + const actualPromise = previewForm(MOCK_FORM_ID, MOCK_INTERCEPTOR) + mockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(mockAxios.get).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/preview`, + ) + }) + }) + + describe('useTemplate', () => { + it('should return template if POST request succeeds', async () => { + // Arrange + const expected = _generateMockDashboardViewForm() + const MOCK_FORM_ID = new ObjectId().toHexString() + const accessMode = 'adminform' + + // Act + const actualPromise = useTemplate( + MOCK_FORM_ID, + MOCK_INTERCEPTOR, + accessMode, + ) + mockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(mockAxios.post).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/${accessMode}/copy`, + ) + }) + + it('should reject with error message if POST request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = new ObjectId().toHexString() + const accessMode = 'adminform' + + // Act + const actualPromise = useTemplate( + MOCK_FORM_ID, + MOCK_INTERCEPTOR, + accessMode, + ) + mockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(mockAxios.post).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/${accessMode}/copy`, + ) + }) + }) + + describe('transferOwner', () => { + it('should return updated form if POST request succeeds', async () => { + // Arrange + const expected = _generateMockFullForm() + const MOCK_FORM_ID = new ObjectId().toHexString() + + // Act + const actualPromise = transferOwner(MOCK_FORM_ID, MOCK_INTERCEPTOR) + mockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(mockAxios.post).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/collaborators/transfer-owner`, + ) + }) + + it('should reject with error message if POST request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = new ObjectId().toHexString() + + // Act + const actualPromise = transferOwner(MOCK_FORM_ID, MOCK_INTERCEPTOR) + mockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(mockAxios.post).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/collaborators/transfer-owner`, + ) + }) + }) +}) + +// Utils +const _generateMockDashboardViewForm = (): FormMetaView => { + return { + title: 'title', + lastModified: new Date(), + _id: new ObjectId(), + responseMode: ResponseMode.Email, + admin: MOCK_USER, + } +} + +const _generateMockFullForm = (): IPopulatedForm => { + return { + _id: new ObjectId(), + } as IPopulatedForm +} + +const _generateMockField = (): IFieldSchema => { + return {} as IYesNoFieldSchema +} + +const _generateMockPublicForm = (): PublicForm => { + return ({ + _id: new ObjectId(), + title: 'mock preview title', + admin: MOCK_USER, + } as unknown) as PublicForm +} From d70036f3713d727ca6e70797fa34a9e9b1ec9264 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Fri, 21 May 2021 15:08:48 +0800 Subject: [PATCH 03/36] refactor: edit calls to fit new function signature and remove angularjs --- .../admin-form.client.controller.js | 4 +- .../collaborator-modal.client.controller.js | 9 ++--- .../create-form-modal.client.controller.js | 39 ++++++++----------- .../delete-form-modal.client.controller.js | 4 +- .../list-forms.client.controller.js | 10 +++-- .../forms/config/forms.client.routes.js | 6 +-- .../services/form-factory.client.service.js | 6 +-- 7 files changed, 35 insertions(+), 43 deletions(-) diff --git a/src/public/modules/forms/admin/controllers/admin-form.client.controller.js b/src/public/modules/forms/admin/controllers/admin-form.client.controller.js index 07f57002c9..3c47b6ce01 100644 --- a/src/public/modules/forms/admin/controllers/admin-form.client.controller.js +++ b/src/public/modules/forms/admin/controllers/admin-form.client.controller.js @@ -295,8 +295,8 @@ function AdminFormController( .catch(handleUpdateError) } default: - return FormApi.update({ formId: $scope.myform._id }, { form: update }) - .$promise.then((savedForm) => { + return FormApi.update($scope.myform._id, { form: update }) + .then((savedForm) => { // Updating this form updates lastModified // and also updates myform if a formToUse is passed in $scope.myform = savedForm diff --git a/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js b/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js index 797c0419f4..dfad59548b 100644 --- a/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js +++ b/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js @@ -73,11 +73,10 @@ function CollaboratorModalController( return } - FormApi.transferOwner( - { formId: $scope.myform._id }, - { email: $scope.transferOwnerEmail }, - ) - .$promise.then((res) => { + FormApi.transferOwner($scope.myform._id, { + email: $scope.transferOwnerEmail, + }) + .then((res) => { $scope.myform = res.form externalScope.refreshFormDataFromCollab($scope.myform) Toastr.success('Form ownership transferred. You are now an Editor.') diff --git a/src/public/modules/forms/admin/controllers/create-form-modal.client.controller.js b/src/public/modules/forms/admin/controllers/create-form-modal.client.controller.js index 63f9ac5888..42935e16ca 100644 --- a/src/public/modules/forms/admin/controllers/create-form-modal.client.controller.js +++ b/src/public/modules/forms/admin/controllers/create-form-modal.client.controller.js @@ -259,7 +259,7 @@ function CreateFormModalController( formMode, formParams, FormToDuplicate._id, - ).$promise.then((data) => { + ).then((data) => { vm.closeCreateModal() externalScope.onDuplicateSuccess(data) }, handleCreateFormError) @@ -267,37 +267,30 @@ function CreateFormModalController( } case 'useTemplate': { const { form } = externalScope - FormFactory.generateForm( - formMode, - formParams, - form._id, - ).$promise.then((data) => { - vm.closeCreateModal() - vm.goToWithId('viewForm', data._id + '') - GTag.examplesClickCreateNewForm(form) - }, handleCreateFormError) - break - } - case 'createFromTemplate': { - // Create new form from template selected - const newForm = Object.assign({}, vm.template, formParams) - FormFactory.generateForm('create', newForm).$promise.then( + FormFactory.generateForm(formMode, formParams, form._id).then( (data) => { vm.closeCreateModal() vm.goToWithId('viewForm', data._id + '') + GTag.examplesClickCreateNewForm(form) }, handleCreateFormError, ) break } + case 'createFromTemplate': { + // Create new form from template selected + const newForm = Object.assign({}, vm.template, formParams) + FormFactory.generateForm('create', newForm).then((data) => { + vm.closeCreateModal() + vm.goToWithId('viewForm', data._id + '') + }, handleCreateFormError) + break + } case 'create': // Create form - FormFactory.generateForm(formMode, formParams).$promise.then( - (data) => { - vm.closeCreateModal() - vm.goToWithId('viewForm', data._id + '') - }, - handleCreateFormError, - ) + FormFactory.generateForm(formMode, formParams).then((data) => { + vm.closeCreateModal() + vm.goToWithId('viewForm', data._id + '') + }, handleCreateFormError) break } } diff --git a/src/public/modules/forms/admin/controllers/delete-form-modal.client.controller.js b/src/public/modules/forms/admin/controllers/delete-form-modal.client.controller.js index 3b1b47788d..a46ae17f83 100644 --- a/src/public/modules/forms/admin/controllers/delete-form-modal.client.controller.js +++ b/src/public/modules/forms/admin/controllers/delete-form-modal.client.controller.js @@ -28,9 +28,7 @@ function DeleteFormModalController($uibModalInstance, externalScope, FormApi) { }`, ) } - FormApi.delete({ - formId: vm.myforms[formIndex]._id, - }).$promise.then( + FormApi.delete(vm.myforms[formIndex]._id).then( function () { vm.myforms.splice(formIndex, 1) vm.cancel() diff --git a/src/public/modules/forms/admin/controllers/list-forms.client.controller.js b/src/public/modules/forms/admin/controllers/list-forms.client.controller.js index 5ad9d65bac..9a1c0e992d 100644 --- a/src/public/modules/forms/admin/controllers/list-forms.client.controller.js +++ b/src/public/modules/forms/admin/controllers/list-forms.client.controller.js @@ -16,6 +16,7 @@ angular '$timeout', '$window', 'Toastr', + '$q', ListFormsController, ]) @@ -29,6 +30,7 @@ function ListFormsController( $timeout, $window, Toastr, + $q, ) { const vm = this @@ -74,7 +76,7 @@ function ListFormsController( // Massage user email into a name turnEmailToName() - FormApi.query(function (_forms) { + $q.when(FormApi.query()).then((_forms) => { vm.myforms = _forms }) } @@ -192,9 +194,9 @@ function ListFormsController( resolve: { FormToDuplicate: () => { // Retrieve the form so that we can populate the modal with any existing email recipients - return FormApi.preview({ - formId: vm.myforms[formIndex]._id, - }).$promise.then((res) => res.form) + return FormApi.preview(vm.myforms[formIndex]._id).then( + (res) => res.form, + ) }, createFormModalOptions: () => ({ mode: 'duplicate' }), externalScope: () => ({ diff --git a/src/public/modules/forms/config/forms.client.routes.js b/src/public/modules/forms/config/forms.client.routes.js index 2a58b6a757..6692d33465 100644 --- a/src/public/modules/forms/config/forms.client.routes.js +++ b/src/public/modules/forms/config/forms.client.routes.js @@ -22,7 +22,7 @@ angular.module('forms').config([ 'FormApi', '$transition$', function (FormApi, $transition$) { - return FormApi.getPublic($transition$.params()).$promise + return FormApi.getPublic($transition$.params().formId) }, ], }, @@ -37,7 +37,7 @@ angular.module('forms').config([ 'FormApi', '$transition$', function (FormApi, $transition$) { - return FormApi.preview($transition$.params()).$promise.then( + return FormApi.preview($transition$.params().formId).then( (FormData) => { FormData.isTemplate = true FormData.isPreview = true @@ -114,7 +114,7 @@ angular.module('forms').config([ 'FormApi', '$transition$', function (FormApi, $transition$) { - return FormApi.getAdmin($transition$.params()).$promise + return FormApi.getAdmin($transition$.params().formId) }, ], }, diff --git a/src/public/modules/forms/services/form-factory.client.service.js b/src/public/modules/forms/services/form-factory.client.service.js index 235e9cf345..407d3d786e 100644 --- a/src/public/modules/forms/services/form-factory.client.service.js +++ b/src/public/modules/forms/services/form-factory.client.service.js @@ -15,11 +15,11 @@ function FormFactory(FormApi) { function generateForm(mode, params, formId) { switch (mode) { case 'create': - return FormApi.create({}, { form: params }) + return FormApi.create({ form: params }) case 'duplicate': - return FormApi.save({ formId }, params) + return FormApi.save(formId, params) case 'useTemplate': - return FormApi.useTemplate({ formId }, params) + return FormApi.useTemplate(formId, params) default: throw new Error('Unsupported mode of form generation.') } From caed806204c700c2292375739f159f92dec335ee Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Fri, 21 May 2021 17:33:10 +0800 Subject: [PATCH 04/36] rfix: add missing parameter in transferOwner and cfix reading error message in collaborators client controller --- .../collaborator-modal.client.controller.js | 2 +- .../forms/services/form-api.client.factory.js | 4 ++-- src/public/services/FormService.ts | 2 ++ .../services/__tests__/FormService.test.ts | 17 ++++++++++++++--- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js b/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js index dfad59548b..f8ad7aae3c 100644 --- a/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js +++ b/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js @@ -82,7 +82,7 @@ function CollaboratorModalController( Toastr.success('Form ownership transferred. You are now an Editor.') }) .catch((err) => { - Toastr.error(err.data.message) + Toastr.error(err.response.data.message) return }) .finally(() => { diff --git a/src/public/modules/forms/services/form-api.client.factory.js b/src/public/modules/forms/services/form-api.client.factory.js index 48bea61d3a..81219a4246 100644 --- a/src/public/modules/forms/services/form-api.client.factory.js +++ b/src/public/modules/forms/services/form-api.client.factory.js @@ -94,7 +94,7 @@ function FormApi(FormErrorService, FormFields) { FormService.previewForm(formId, getInterceptor(true, 'previewForm')), useTemplate: (formId) => FormService.useTemplate(formId, getInterceptor(true, 'useTemplate')), - transferOwner: (formId) => - FormService.transferOwner(formId, getInterceptor(false)), + transferOwner: (formId, newOwner) => + FormService.transferOwner(formId, newOwner, getInterceptor(false)), } } diff --git a/src/public/services/FormService.ts b/src/public/services/FormService.ts index 9ac5d35104..cd0bbc2a7c 100644 --- a/src/public/services/FormService.ts +++ b/src/public/services/FormService.ts @@ -153,6 +153,7 @@ export const useTemplate = async ( export const transferOwner = async ( formId: string, + newOwner: { email: string }, interceptor: Interceptor, ): Promise<{ form: IPopulatedForm }> => { const instance = axios.create() @@ -160,6 +161,7 @@ export const transferOwner = async ( return instance .post<{ form: IPopulatedForm }>( `${ADMIN_FORM_ENDPOINT}/${formId}/collaborators/transfer-owner`, + newOwner, ) .then(({ data }) => data) } diff --git a/src/public/services/__tests__/FormService.test.ts b/src/public/services/__tests__/FormService.test.ts index 56d5fba67b..2e55db2e89 100644 --- a/src/public/services/__tests__/FormService.test.ts +++ b/src/public/services/__tests__/FormService.test.ts @@ -496,9 +496,13 @@ describe('FormService', () => { // Arrange const expected = _generateMockFullForm() const MOCK_FORM_ID = new ObjectId().toHexString() - + const MOCK_NEW_OWNER = { email: 'test@open.gov.sg' } // Act - const actualPromise = transferOwner(MOCK_FORM_ID, MOCK_INTERCEPTOR) + const actualPromise = transferOwner( + MOCK_FORM_ID, + MOCK_NEW_OWNER, + MOCK_INTERCEPTOR, + ) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -506,6 +510,7 @@ describe('FormService', () => { expect(actual).toEqual(expected) expect(mockAxios.post).toHaveBeenCalledWith( `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/collaborators/transfer-owner`, + MOCK_NEW_OWNER, ) }) @@ -513,15 +518,21 @@ describe('FormService', () => { // Arrange const expected = new Error('error') const MOCK_FORM_ID = new ObjectId().toHexString() + const MOCK_NEW_OWNER = { email: 'test@open.gov.sg' } // Act - const actualPromise = transferOwner(MOCK_FORM_ID, MOCK_INTERCEPTOR) + const actualPromise = transferOwner( + MOCK_FORM_ID, + MOCK_NEW_OWNER, + MOCK_INTERCEPTOR, + ) mockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) expect(mockAxios.post).toHaveBeenCalledWith( `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/collaborators/transfer-owner`, + MOCK_NEW_OWNER, ) }) }) From f18448f8cff8a238e9fd1a428028e8a0fd26735d Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Tue, 25 May 2021 09:48:29 +0800 Subject: [PATCH 05/36] refactor: add .when to all FormApi calls --- .../admin-form.client.controller.js | 3 ++- .../collaborator-modal.client.controller.js | 8 ++++--- .../delete-form-modal.client.controller.js | 10 +++++++-- .../list-forms.client.controller.js | 6 ++--- .../forms/config/forms.client.routes.js | 22 ++++++++++--------- .../examples-card.client.directive.js | 12 +++++++--- 6 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/public/modules/forms/admin/controllers/admin-form.client.controller.js b/src/public/modules/forms/admin/controllers/admin-form.client.controller.js index 3c47b6ce01..f35a905e59 100644 --- a/src/public/modules/forms/admin/controllers/admin-form.client.controller.js +++ b/src/public/modules/forms/admin/controllers/admin-form.client.controller.js @@ -295,7 +295,8 @@ function AdminFormController( .catch(handleUpdateError) } default: - return FormApi.update($scope.myform._id, { form: update }) + return $q + .when(FormApi.update($scope.myform._id, { form: update })) .then((savedForm) => { // Updating this form updates lastModified // and also updates myform if a formToUse is passed in diff --git a/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js b/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js index f8ad7aae3c..8d42013c8f 100644 --- a/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js +++ b/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js @@ -73,9 +73,11 @@ function CollaboratorModalController( return } - FormApi.transferOwner($scope.myform._id, { - email: $scope.transferOwnerEmail, - }) + $q.when( + FormApi.transferOwner($scope.myform._id, { + email: $scope.transferOwnerEmail, + }), + ) .then((res) => { $scope.myform = res.form externalScope.refreshFormDataFromCollab($scope.myform) diff --git a/src/public/modules/forms/admin/controllers/delete-form-modal.client.controller.js b/src/public/modules/forms/admin/controllers/delete-form-modal.client.controller.js index a46ae17f83..1ad0fb1b4f 100644 --- a/src/public/modules/forms/admin/controllers/delete-form-modal.client.controller.js +++ b/src/public/modules/forms/admin/controllers/delete-form-modal.client.controller.js @@ -6,10 +6,16 @@ angular '$uibModalInstance', 'externalScope', 'FormApi', + '$q', DeleteFormModalController, ]) -function DeleteFormModalController($uibModalInstance, externalScope, FormApi) { +function DeleteFormModalController( + $uibModalInstance, + externalScope, + FormApi, + $q, +) { const vm = this vm.cancel = function () { @@ -28,7 +34,7 @@ function DeleteFormModalController($uibModalInstance, externalScope, FormApi) { }`, ) } - FormApi.delete(vm.myforms[formIndex]._id).then( + $q.when(FormApi.delete(vm.myforms[formIndex]._id)).then( function () { vm.myforms.splice(formIndex, 1) vm.cancel() diff --git a/src/public/modules/forms/admin/controllers/list-forms.client.controller.js b/src/public/modules/forms/admin/controllers/list-forms.client.controller.js index 9a1c0e992d..91b53f58cf 100644 --- a/src/public/modules/forms/admin/controllers/list-forms.client.controller.js +++ b/src/public/modules/forms/admin/controllers/list-forms.client.controller.js @@ -194,9 +194,9 @@ function ListFormsController( resolve: { FormToDuplicate: () => { // Retrieve the form so that we can populate the modal with any existing email recipients - return FormApi.preview(vm.myforms[formIndex]._id).then( - (res) => res.form, - ) + return $q + .when(FormApi.preview(vm.myforms[formIndex]._id)) + .then((res) => res.form) }, createFormModalOptions: () => ({ mode: 'duplicate' }), externalScope: () => ({ diff --git a/src/public/modules/forms/config/forms.client.routes.js b/src/public/modules/forms/config/forms.client.routes.js index 6692d33465..b34382faae 100644 --- a/src/public/modules/forms/config/forms.client.routes.js +++ b/src/public/modules/forms/config/forms.client.routes.js @@ -36,14 +36,15 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - function (FormApi, $transition$) { - return FormApi.preview($transition$.params().formId).then( - (FormData) => { + '$q', + function (FormApi, $transition$, $q) { + return $q + .when(FormApi.preview($transition$.params().formId)) + .then((FormData) => { FormData.isTemplate = true FormData.isPreview = true return FormData - }, - ) + }) }, ], }, @@ -57,13 +58,14 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - function (FormApi, $transition$) { - return FormApi.template($transition$.params()).$promise.then( - (FormData) => { + '$q', + function (FormApi, $transition$, $q) { + return $q + .when(FormApi.template($transition$.params())) + .then((FormData) => { FormData.isTemplate = true return FormData - }, - ) + }) }, ], }, diff --git a/src/public/modules/users/controllers/examples-card.client.directive.js b/src/public/modules/users/controllers/examples-card.client.directive.js index 2331969a89..e9d931a48e 100644 --- a/src/public/modules/users/controllers/examples-card.client.directive.js +++ b/src/public/modules/users/controllers/examples-card.client.directive.js @@ -23,6 +23,7 @@ function examplesCard() { 'Auth', '$location', 'Toastr', + '$q', examplesCardController, ], } @@ -38,6 +39,7 @@ function examplesCardController( Auth, $location, Toastr, + $q, ) { $scope.user = Auth.getUser() @@ -98,9 +100,13 @@ function examplesCardController( controllerAs: 'vm', resolve: { FormToDuplicate: () => { - return FormApi.template({ - formId: $scope.form._id, - }).$promise.then((res) => res.form) + return $q + .when( + FormApi.template({ + formId: $scope.form._id, + }), + ) + .then((res) => res.form) }, createFormModalOptions: () => ({ mode: 'useTemplate' }), externalScope: () => ({ From ce9f9c1101929ec79d02305d61641a4214c659e5 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Tue, 25 May 2021 09:49:50 +0800 Subject: [PATCH 06/36] refactor: extract interceptors from FormService and replace original interceptors --- .../forms/services/form-api.client.factory.js | 175 ++++++++++++------ src/public/services/FormService.ts | 65 +------ 2 files changed, 126 insertions(+), 114 deletions(-) diff --git a/src/public/modules/forms/services/form-api.client.factory.js b/src/public/modules/forms/services/form-api.client.factory.js index 81219a4246..44e35c85a8 100644 --- a/src/public/modules/forms/services/form-api.client.factory.js +++ b/src/public/modules/forms/services/form-api.client.factory.js @@ -26,75 +26,140 @@ const extractFormId = (path) => { */ function FormApi(FormErrorService, FormFields) { /** - * Used to generate interceptors for all API calls. These interceptors have the - * following responsibilities: - * 1) Stripping MyInfo data when saving forms to the database. - * 2) Injecting MyInfo data when forms are received from the database. - * 3) Converting plain form objects to smart Form class instances before returning + * Handles data passed into API calls. In charge of stripping of + * MyInfo data when saving forms to the database. + * @param {*} input API service data input + * @returns Transformed input + */ + const handleInput = (input) => { + if (get(input, 'form')) { + FormFields.removeMyInfoFromForm(input) + } + return input + } + + /** + * Handles data returned from API calls. Responsibilities include: + * 1) Injecting MyInfo data when forms are received from the database. + * 2) Converting plain form objects to smart Form class instances before returning * them to controllers. - * 4) Redirecting users using FormErrorService if redirectOnError is true. - * @param {boolean} redirectOnError Whether to use FormErrorService to redirect - * to errorTargetState when an error is encountered. - * @param {string} [errorTargetState] If redirectOnError is true, this is the - * redirect state which will be passed to FormErrorService + * @param {*} data Data returned from API call + * @returns Transformed data + */ + const handleResponse = (data) => { + // The backend returns different shapes for different request types. For GET + // requests, it returns a $resource instance with a form attribute containing + // all the form data; for PUT requests, it returns a $resource instance with + // all the form data at the top level. We need to ensure that the postprocessing + // is done in both cases. + if (get(data, 'form.form_fields')) { + FormFields.injectMyInfoIntoForm(data.form) + // Convert plain form object to smart Form instance + data.form = new Form(data.form) + } else if (get(data, 'form_fields')) { + FormFields.injectMyInfoIntoForm(data) + data = new Form(data) + } + return data + } + + /** + * Handles error returned from API calls. In charge of redirecting users using FormErrorService + * if redirectOnError is true. + * @param {Error} err Error returned from API calls + * @param {{redirectOnError: boolean, errorTargetState: string }} errorParams redirectOnError specifies + * whether to use FormErrorService to redirect to errorTargetState when an error is encountered. + * If redirectOnError is true, this is the redirect state which will be passed to FormErrorService */ - const getInterceptor = (redirectOnError, errorTargetState) => { - const interceptor = { - request: (config) => { - if (get(config, 'data.form')) { - FormFields.removeMyInfoFromForm(config.data.form) - } - return config - }, - response: (response) => { - // The backend returns different shapes for different request types. For GET - // requests, it returns a $resource instance with a form attribute containing - // all the form data; for PUT requests, it returns a $resource instance with - // all the form data at the top level. We need to ensure that the postprocessing - // is done in both cases. - if (get(response, 'data.form.form_fields')) { - FormFields.injectMyInfoIntoForm(response.data.form) - // Convert plain form object to smart Form instance - response.data.form = new Form(response.data.form) - } else if (get(response, 'data.form_fields')) { - FormFields.injectMyInfoIntoForm(response.data) - response.data = new Form(response.data) - } - return response - }, + const handleError = (err, errorParams) => { + const { redirectOnError, errorTargetState } = errorParams + if (redirectOnError) { + FormErrorService.redirect({ + response: err.response, + targetState: errorTargetState, + targetFormId: extractFormId(get(err.response, 'config.url')), + }) + } else { + throw err // just pass error on } - interceptor.responseError = redirectOnError - ? (response) => { - return FormErrorService.redirect({ - response, - targetState: errorTargetState, - targetFormId: extractFormId(get(response, 'config.url')), - }) - } - : null - return interceptor + } + + const generateService = (service, errorParams, ...inputs) => { + return service(...inputs.map((input) => handleInput(input))) + .then((data) => { + return handleResponse(data) + }) + .catch((err) => handleError(err, errorParams)) } return { - query: () => FormService.queryForm(getInterceptor(true, 'listForms')), + query: () => + generateService(FormService.queryForm, { + redirectOnError: true, + errorTargetState: 'listForms', + }), getAdmin: (formId) => - FormService.getAdminForm(formId, getInterceptor(true, 'viewForm')), + generateService( + FormService.getAdminForm, + { redirectOnError: true, errorTargetState: 'viewForm' }, + formId, + ), getPublic: (formId) => - FormService.getPublicForm(formId, getInterceptor(true)), + generateService( + FormService.getPublicForm, + { redirectOnError: true }, + formId, + ), update: (formId, update) => - FormService.updateForm(formId, update, getInterceptor(false)), + generateService( + FormService.updateForm, + { redirectOnError: false }, + formId, + update, + ), save: (formId, formToSave) => - FormService.saveForm(formId, formToSave, getInterceptor(false)), - delete: (formId) => FormService.deleteForm(formId, getInterceptor(false)), + generateService( + FormService.saveForm, + { redirectOnError: false }, + formId, + formToSave, + ), + delete: (formId) => + generateService( + FormService.deleteForm, + { redirectOnError: false }, + formId, + ), create: (newForm) => - FormService.createForm(newForm, getInterceptor(true, 'listForms')), + generateService( + FormService.createForm, + { redirectOnError: true, errorTargetState: 'listForms' }, + newForm, + ), template: (formId) => - FormService.queryTemplate(formId, getInterceptor(true, 'templateForm')), + generateService( + FormService.queryTemplate, + { redirectOnError: true, errorTargetState: 'templateForm' }, + formId, + ), preview: (formId) => - FormService.previewForm(formId, getInterceptor(true, 'previewForm')), + generateService( + FormService.previewForm, + { redirectOnError: true, errorTargetState: 'previewForm' }, + formId, + ), useTemplate: (formId) => - FormService.useTemplate(formId, getInterceptor(true, 'useTemplate')), + generateService( + FormService.useTemplate, + { redirectOnError: true, errorTargetState: 'useTemplate' }, + formId, + ), transferOwner: (formId, newOwner) => - FormService.transferOwner(formId, newOwner, getInterceptor(false)), + generateService( + FormService.transferOwner, + { redirectOnError: false }, + formId, + newOwner, + ), } } diff --git a/src/public/services/FormService.ts b/src/public/services/FormService.ts index cd0bbc2a7c..d0e4cbef6c 100644 --- a/src/public/services/FormService.ts +++ b/src/public/services/FormService.ts @@ -1,4 +1,4 @@ -import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' +import axios from 'axios' import { FormUpdateParams } from 'src/app/modules/form/admin-form/admin-form.types' import { PublicFormViewDto } from 'src/app/modules/form/public-form/public-form.types' @@ -14,19 +14,8 @@ import { FormFieldDto } from 'src/types/api' export const ADMIN_FORM_ENDPOINT = '/api/v3/admin/forms' export const PUBLIC_FORM_ENDPOINT = '/api/v3/forms' -type Interceptor = { - response: (response: AxiosResponse) => AxiosResponse | Promise - request: ( - config: AxiosRequestConfig, - ) => AxiosRequestConfig | Promise - responseError: ((response: AxiosResponse) => unknown) | null -} - -export const queryForm = async ( - interceptor: Interceptor, -): Promise => { +export const queryForm = async (): Promise => { const instance = axios.create() - _addInterceptor(instance, interceptor) return instance .get(`${ADMIN_FORM_ENDPOINT}`, { headers: { 'If-Modified-Since': '0' }, @@ -36,11 +25,9 @@ export const queryForm = async ( export const getAdminForm = async ( formId: string, - interceptor: Interceptor, accessMode = 'adminform', ): Promise<{ form: IPopulatedForm }> => { const instance = axios.create() - _addInterceptor(instance, interceptor) // disable IE ajax request caching (so new forms show on dashboard) return instance .get<{ form: IPopulatedForm }>(`/${formId}/${accessMode}`, { @@ -51,10 +38,8 @@ export const getAdminForm = async ( export const getPublicForm = async ( formId: string, - interceptor: Interceptor, ): Promise => { const instance = axios.create() - _addInterceptor(instance, interceptor) // disable IE ajax request caching (so new forms show on dashboard) return instance .get(`${PUBLIC_FORM_ENDPOINT}/${formId}`, { @@ -66,11 +51,9 @@ export const getPublicForm = async ( export const updateForm = async ( formId: string, update: { form: FormUpdateParams }, - interceptor: Interceptor, accessMode = 'adminform', ): Promise => { const instance = axios.create() - _addInterceptor(instance, interceptor) return instance .put(`${formId}/${accessMode}`, update) .then(({ data }) => data) @@ -79,10 +62,8 @@ export const updateForm = async ( export const saveForm = async ( formId: string, formToSave: FormFieldDto[], - interceptor: Interceptor, ): Promise => { const instance = axios.create() - _addInterceptor(instance, interceptor) return instance .post( `${ADMIN_FORM_ENDPOINT}/${formId}/duplicate`, @@ -91,22 +72,16 @@ export const saveForm = async ( .then(({ data }) => data) } -export const deleteForm = async ( - formId: string, - interceptor: Interceptor, -): Promise => { +export const deleteForm = async (formId: string): Promise => { const instance = axios.create() - _addInterceptor(instance, interceptor) return instance.delete(`${ADMIN_FORM_ENDPOINT}/${formId}`) } // createForm is called without formId, so the endpoint is just /adminform -export const createForm = async ( - newForm: { form: FormFieldDto[] }, - interceptor: Interceptor, -): Promise => { +export const createForm = async (newForm: { + form: FormFieldDto[] +}): Promise => { const instance = axios.create() - _addInterceptor(instance, interceptor) return instance .post(`${ADMIN_FORM_ENDPOINT}`, newForm) .then(({ data }) => data) @@ -115,11 +90,9 @@ export const createForm = async ( // Used for viewing templates with use-template or examples listing. Any logged in officer is authorized. export const queryTemplate = async ( formId: string, - interceptor: Interceptor, accessMode = 'adminform', ): Promise<{ form: PublicForm }> => { const instance = axios.create() - _addInterceptor(instance, interceptor) return instance .get<{ form: PublicForm }>( `${ADMIN_FORM_ENDPOINT}/${formId}/${accessMode}/template`, @@ -130,10 +103,8 @@ export const queryTemplate = async ( // Used for previewing the form from the form admin page. Must be a viewer, collaborator or admin. export const previewForm = async ( formId: string, - interceptor: Interceptor, ): Promise<{ form: PublicForm }> => { const instance = axios.create() - _addInterceptor(instance, interceptor) return instance .get<{ form: PublicForm }>(`${ADMIN_FORM_ENDPOINT}/${formId}/preview`) .then(({ data }) => data) @@ -141,11 +112,9 @@ export const previewForm = async ( export const useTemplate = async ( formId: string, - interceptor: Interceptor, accessMode = 'adminform', ): Promise => { const instance = axios.create() - _addInterceptor(instance, interceptor) return instance .post(`${ADMIN_FORM_ENDPOINT}/${formId}/${accessMode}/copy`) .then(({ data }) => data) @@ -154,10 +123,8 @@ export const useTemplate = async ( export const transferOwner = async ( formId: string, newOwner: { email: string }, - interceptor: Interceptor, ): Promise<{ form: IPopulatedForm }> => { const instance = axios.create() - _addInterceptor(instance, interceptor) return instance .post<{ form: IPopulatedForm }>( `${ADMIN_FORM_ENDPOINT}/${formId}/collaborators/transfer-owner`, @@ -165,23 +132,3 @@ export const transferOwner = async ( ) .then(({ data }) => data) } - -/** - * Adds both a response and request interceptor to the axios instance - * @param instance Axios Instance created for a specific API call - * @param interceptor Interceptor for API call - */ -const _addInterceptor = (instance: AxiosInstance, interceptor: Interceptor) => { - const responseError = interceptor.responseError - if (responseError) { - instance.interceptors.response.use( - (response) => interceptor.response(response), - (response) => responseError(response), - ) - } else { - instance.interceptors.response.use((response) => - interceptor.response(response), - ) - } - instance.interceptors.request.use((config) => interceptor.request(config)) -} From 3dc5a0bfa9bbc278cc7deed565cb381c2cc81135 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Tue, 25 May 2021 10:04:20 +0800 Subject: [PATCH 07/36] test: remove interceptor from FormService tests --- .../services/__tests__/FormService.test.ts | 97 +++++-------------- 1 file changed, 23 insertions(+), 74 deletions(-) diff --git a/src/public/services/__tests__/FormService.test.ts b/src/public/services/__tests__/FormService.test.ts index 2e55db2e89..c0c48a6f2e 100644 --- a/src/public/services/__tests__/FormService.test.ts +++ b/src/public/services/__tests__/FormService.test.ts @@ -1,4 +1,3 @@ -import { AxiosRequestConfig, AxiosResponse } from 'axios' import { ObjectId } from 'bson' import { StatusCodes } from 'http-status-codes' import mockAxios from 'jest-mock-axios' @@ -36,18 +35,13 @@ const MOCK_USER = { describe('FormService', () => { afterEach(() => mockAxios.reset()) - const MOCK_INTERCEPTOR = { - response: (response: AxiosResponse) => response, - request: (config: AxiosRequestConfig) => config, - responseError: null, - } describe('queryForm', () => { it('should successfully return all available forms if GET request succeeds', async () => { // Arrange const expected: FormMetaView[] = [_generateMockDashboardViewForm()] // Act - const actualPromise = queryForm(MOCK_INTERCEPTOR) + const actualPromise = queryForm() mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -63,7 +57,7 @@ describe('FormService', () => { const expected: FormMetaView[] = [] // Act - const actualPromise = queryForm(MOCK_INTERCEPTOR) + const actualPromise = queryForm() mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -79,7 +73,7 @@ describe('FormService', () => { const expected = new Error('error') // Act - const actualPromise = queryForm(MOCK_INTERCEPTOR) + const actualPromise = queryForm() mockAxios.mockError(expected) //Assert @@ -97,11 +91,7 @@ describe('FormService', () => { const accessMode = 'adminform' // Act - const actualPromise = getAdminForm( - expected._id, - MOCK_INTERCEPTOR, - accessMode, - ) + const actualPromise = getAdminForm(expected._id, accessMode) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -122,11 +112,7 @@ describe('FormService', () => { const accessMode = 'adminform' // Act - const actualPromise = getAdminForm( - MOCK_FORM._id, - MOCK_INTERCEPTOR, - accessMode, - ) + const actualPromise = getAdminForm(MOCK_FORM._id, accessMode) mockAxios.mockError(expected) // Assert @@ -152,7 +138,7 @@ describe('FormService', () => { } // Act - const actualPromise = getPublicForm(MOCK_FORM_ID, MOCK_INTERCEPTOR) + const actualPromise = getPublicForm(MOCK_FORM_ID) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -172,7 +158,7 @@ describe('FormService', () => { const expected = new Error('error') // Act - const actualPromise = getPublicForm(MOCK_FORM_ID, MOCK_INTERCEPTOR) + const actualPromise = getPublicForm(MOCK_FORM_ID) mockAxios.mockError(expected) // Assert @@ -202,12 +188,7 @@ describe('FormService', () => { const accessMode = 'adminform' // Act - const actualPromise = updateForm( - MOCK_FORM_ID, - update, - MOCK_INTERCEPTOR, - accessMode, - ) + const actualPromise = updateForm(MOCK_FORM_ID, update, accessMode) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -234,7 +215,7 @@ describe('FormService', () => { const accessMode = 'adminform' // Act - const actualPromise = updateForm(MOCK_FORM_ID, update, MOCK_INTERCEPTOR) + const actualPromise = updateForm(MOCK_FORM_ID, update) mockAxios.mockError(expected) // Assert @@ -253,11 +234,7 @@ describe('FormService', () => { const MOCK_FIELDS = [_generateMockField()] // Act - const actualPromise = saveForm( - MOCK_FORM_ID, - MOCK_FIELDS, - MOCK_INTERCEPTOR, - ) + const actualPromise = saveForm(MOCK_FORM_ID, MOCK_FIELDS) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -276,11 +253,7 @@ describe('FormService', () => { const MOCK_FIELDS = [_generateMockField()] // Act - const actualPromise = saveForm( - MOCK_FORM_ID, - MOCK_FIELDS, - MOCK_INTERCEPTOR, - ) + const actualPromise = saveForm(MOCK_FORM_ID, MOCK_FIELDS) mockAxios.mockError(expected) // Assert @@ -299,7 +272,7 @@ describe('FormService', () => { const MOCK_FORM_ID = new ObjectId().toHexString() // Act - const actualPromise = deleteForm(MOCK_FORM_ID, MOCK_INTERCEPTOR) + const actualPromise = deleteForm(MOCK_FORM_ID) mockAxios.mockResponse({ status: StatusCodes.OK, data: { message: 'Form has been archived' }, @@ -318,7 +291,7 @@ describe('FormService', () => { const MOCK_FORM_ID = new ObjectId().toHexString() // Act - const actualPromise = deleteForm(MOCK_FORM_ID, MOCK_INTERCEPTOR) + const actualPromise = deleteForm(MOCK_FORM_ID) mockAxios.mockError(expected) await expect(actualPromise).rejects.toEqual(expected) @@ -336,7 +309,7 @@ describe('FormService', () => { const MOCK_FORM_PARAMS = { form: [_generateMockField()] } // Act - const actualPromise = createForm(MOCK_FORM_PARAMS, MOCK_INTERCEPTOR) + const actualPromise = createForm(MOCK_FORM_PARAMS) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -354,7 +327,7 @@ describe('FormService', () => { const MOCK_FORM_PARAMS = { form: [_generateMockField()] } // Act - const actualPromise = createForm(MOCK_FORM_PARAMS, MOCK_INTERCEPTOR) + const actualPromise = createForm(MOCK_FORM_PARAMS) mockAxios.mockError(expected) // Assert @@ -374,11 +347,7 @@ describe('FormService', () => { const accessMode = 'adminform' // Act - const actualPromise = queryTemplate( - MOCK_FORM_ID, - MOCK_INTERCEPTOR, - accessMode, - ) + const actualPromise = queryTemplate(MOCK_FORM_ID, accessMode) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -396,11 +365,7 @@ describe('FormService', () => { const accessMode = 'adminform' // Act - const actualPromise = queryTemplate( - MOCK_FORM_ID, - MOCK_INTERCEPTOR, - accessMode, - ) + const actualPromise = queryTemplate(MOCK_FORM_ID, accessMode) mockAxios.mockError(expected) // Assert @@ -418,7 +383,7 @@ describe('FormService', () => { const MOCK_FORM_ID = new ObjectId().toHexString() // Act - const actualPromise = previewForm(MOCK_FORM_ID, MOCK_INTERCEPTOR) + const actualPromise = previewForm(MOCK_FORM_ID) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -435,7 +400,7 @@ describe('FormService', () => { const MOCK_FORM_ID = new ObjectId().toHexString() // Act - const actualPromise = previewForm(MOCK_FORM_ID, MOCK_INTERCEPTOR) + const actualPromise = previewForm(MOCK_FORM_ID) mockAxios.mockError(expected) // Assert @@ -454,11 +419,7 @@ describe('FormService', () => { const accessMode = 'adminform' // Act - const actualPromise = useTemplate( - MOCK_FORM_ID, - MOCK_INTERCEPTOR, - accessMode, - ) + const actualPromise = useTemplate(MOCK_FORM_ID, accessMode) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -476,11 +437,7 @@ describe('FormService', () => { const accessMode = 'adminform' // Act - const actualPromise = useTemplate( - MOCK_FORM_ID, - MOCK_INTERCEPTOR, - accessMode, - ) + const actualPromise = useTemplate(MOCK_FORM_ID, accessMode) mockAxios.mockError(expected) // Assert @@ -498,11 +455,7 @@ describe('FormService', () => { const MOCK_FORM_ID = new ObjectId().toHexString() const MOCK_NEW_OWNER = { email: 'test@open.gov.sg' } // Act - const actualPromise = transferOwner( - MOCK_FORM_ID, - MOCK_NEW_OWNER, - MOCK_INTERCEPTOR, - ) + const actualPromise = transferOwner(MOCK_FORM_ID, MOCK_NEW_OWNER) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -521,11 +474,7 @@ describe('FormService', () => { const MOCK_NEW_OWNER = { email: 'test@open.gov.sg' } // Act - const actualPromise = transferOwner( - MOCK_FORM_ID, - MOCK_NEW_OWNER, - MOCK_INTERCEPTOR, - ) + const actualPromise = transferOwner(MOCK_FORM_ID, MOCK_NEW_OWNER) mockAxios.mockError(expected) // Assert From 93495731250cc7dcc9a5d6534e4504fc14692e4e Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Tue, 25 May 2021 12:00:03 +0800 Subject: [PATCH 08/36] refactor: add .when to FormApi calls --- .../create-form-modal.client.controller.js | 47 +++++++++++-------- .../forms/config/forms.client.routes.js | 10 ++-- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/public/modules/forms/admin/controllers/create-form-modal.client.controller.js b/src/public/modules/forms/admin/controllers/create-form-modal.client.controller.js index 42935e16ca..a82b2e615c 100644 --- a/src/public/modules/forms/admin/controllers/create-form-modal.client.controller.js +++ b/src/public/modules/forms/admin/controllers/create-form-modal.client.controller.js @@ -43,6 +43,7 @@ angular 'FormSgSdk', 'externalScope', 'MailTo', + '$q', CreateFormModalController, ]) @@ -61,6 +62,7 @@ function CreateFormModalController( FormSgSdk, externalScope, MailTo, + $q, ) { const vm = this @@ -255,10 +257,12 @@ function CreateFormModalController( const formMode = vm.mode switch (formMode) { case 'duplicate': { - FormFactory.generateForm( - formMode, - formParams, - FormToDuplicate._id, + $q.when( + FormFactory.generateForm( + formMode, + formParams, + FormToDuplicate._id, + ), ).then((data) => { vm.closeCreateModal() externalScope.onDuplicateSuccess(data) @@ -267,30 +271,35 @@ function CreateFormModalController( } case 'useTemplate': { const { form } = externalScope - FormFactory.generateForm(formMode, formParams, form._id).then( + $q.when( + FormFactory.generateForm(formMode, formParams, form._id), + ).then((data) => { + vm.closeCreateModal() + vm.goToWithId('viewForm', data._id + '') + GTag.examplesClickCreateNewForm(form) + }, handleCreateFormError) + break + } + case 'createFromTemplate': { + // Create new form from template selected + const newForm = Object.assign({}, vm.template, formParams) + $q.when(FormFactory.generateForm('create', newForm)).then( (data) => { vm.closeCreateModal() vm.goToWithId('viewForm', data._id + '') - GTag.examplesClickCreateNewForm(form) }, handleCreateFormError, ) break } - case 'createFromTemplate': { - // Create new form from template selected - const newForm = Object.assign({}, vm.template, formParams) - FormFactory.generateForm('create', newForm).then((data) => { - vm.closeCreateModal() - vm.goToWithId('viewForm', data._id + '') - }, handleCreateFormError) - break - } case 'create': // Create form - FormFactory.generateForm(formMode, formParams).then((data) => { - vm.closeCreateModal() - vm.goToWithId('viewForm', data._id + '') - }, handleCreateFormError) + $q.when(FormFactory.generateForm(formMode, formParams)).then( + (data) => { + vm.closeCreateModal() + vm.goToWithId('viewForm', data._id + '') + }, + handleCreateFormError, + ) break } } diff --git a/src/public/modules/forms/config/forms.client.routes.js b/src/public/modules/forms/config/forms.client.routes.js index b34382faae..5f3672da63 100644 --- a/src/public/modules/forms/config/forms.client.routes.js +++ b/src/public/modules/forms/config/forms.client.routes.js @@ -21,8 +21,9 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - function (FormApi, $transition$) { - return FormApi.getPublic($transition$.params().formId) + '$q', + function (FormApi, $transition$, $q) { + return $q.when(FormApi.getPublic($transition$.params().formId)) }, ], }, @@ -115,8 +116,9 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - function (FormApi, $transition$) { - return FormApi.getAdmin($transition$.params().formId) + '$q', + function (FormApi, $transition$, $q) { + return $q.when(FormApi.getAdmin($transition$.params().formId)) }, ], }, From 958a378ab65160cf67ab7eb2e34340fb41343780 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Thu, 27 May 2021 15:15:06 +0800 Subject: [PATCH 09/36] docs: add documentation and reaname some functions --- .../admin-form.client.controller.js | 1 + .../forms/services/form-api.client.factory.js | 12 +- src/public/services/FormService.ts | 136 ++++++++++++------ .../services/__tests__/FormService.test.ts | 110 +++++++------- 4 files changed, 157 insertions(+), 102 deletions(-) diff --git a/src/public/modules/forms/admin/controllers/admin-form.client.controller.js b/src/public/modules/forms/admin/controllers/admin-form.client.controller.js index f35a905e59..42ab2d2743 100644 --- a/src/public/modules/forms/admin/controllers/admin-form.client.controller.js +++ b/src/public/modules/forms/admin/controllers/admin-form.client.controller.js @@ -295,6 +295,7 @@ function AdminFormController( .catch(handleUpdateError) } default: + // This block should not be reached. All updateForm calls should have an update type. return $q .when(FormApi.update($scope.myform._id, { form: update })) .then((savedForm) => { diff --git a/src/public/modules/forms/services/form-api.client.factory.js b/src/public/modules/forms/services/form-api.client.factory.js index 44e35c85a8..c3f988f732 100644 --- a/src/public/modules/forms/services/form-api.client.factory.js +++ b/src/public/modules/forms/services/form-api.client.factory.js @@ -48,8 +48,8 @@ function FormApi(FormErrorService, FormFields) { */ const handleResponse = (data) => { // The backend returns different shapes for different request types. For GET - // requests, it returns a $resource instance with a form attribute containing - // all the form data; for PUT requests, it returns a $resource instance with + // requests, it returns an object with a form attribute containing + // all the form data; for PUT requests, it returns an object with // all the form data at the top level. We need to ensure that the postprocessing // is done in both cases. if (get(data, 'form.form_fields')) { @@ -94,19 +94,19 @@ function FormApi(FormErrorService, FormFields) { return { query: () => - generateService(FormService.queryForm, { + generateService(FormService.getDashboardViews, { redirectOnError: true, errorTargetState: 'listForms', }), getAdmin: (formId) => generateService( - FormService.getAdminForm, + FormService.getAdminFormView, { redirectOnError: true, errorTargetState: 'viewForm' }, formId, ), getPublic: (formId) => generateService( - FormService.getPublicForm, + FormService.getPublicFormView, { redirectOnError: true }, formId, ), @@ -119,7 +119,7 @@ function FormApi(FormErrorService, FormFields) { ), save: (formId, formToSave) => generateService( - FormService.saveForm, + FormService.duplicateForm, { redirectOnError: false }, formId, formToSave, diff --git a/src/public/services/FormService.ts b/src/public/services/FormService.ts index d0e4cbef6c..4198666672 100644 --- a/src/public/services/FormService.ts +++ b/src/public/services/FormService.ts @@ -1,131 +1,173 @@ import axios from 'axios' -import { FormUpdateParams } from 'src/app/modules/form/admin-form/admin-form.types' +import { + DuplicateFormBody, + FormUpdateParams, +} from 'src/app/modules/form/admin-form/admin-form.types' import { PublicFormViewDto } from 'src/app/modules/form/public-form/public-form.types' import { FormMetaView, + IForm, IFormSchema, IPopulatedForm, PublicForm, } from 'src/types' -import { FormFieldDto } from 'src/types/api' // endpoints exported for testing export const ADMIN_FORM_ENDPOINT = '/api/v3/admin/forms' export const PUBLIC_FORM_ENDPOINT = '/api/v3/forms' -export const queryForm = async (): Promise => { - const instance = axios.create() - return instance +/** + * Gets metadata for all forms in dashboard view i.e. forms which user + * owns or collaborates on + * @returns Metadata required for forms on dashboard view + */ +export const getDashboardViews = async (): Promise => { + return axios .get(`${ADMIN_FORM_ENDPOINT}`, { headers: { 'If-Modified-Since': '0' }, }) .then(({ data }) => data) } -export const getAdminForm = async ( +/** + * Gets admin view of form. + * @param formId formId of form in question + * @returns Admin view of form + */ +export const getAdminFormView = async ( formId: string, - accessMode = 'adminform', ): Promise<{ form: IPopulatedForm }> => { - const instance = axios.create() // disable IE ajax request caching (so new forms show on dashboard) - return instance - .get<{ form: IPopulatedForm }>(`/${formId}/${accessMode}`, { + return axios + .get<{ form: IPopulatedForm }>(`/${formId}/adminform`, { headers: { 'If-Modified-Since': '0' }, }) .then(({ data }) => data) } -export const getPublicForm = async ( +/** + * Gets public view of form, along with any + * identify information obtained from Singpass/Corppass/MyInfo. + * @param formId FormId of form in question + * @returns Public view of form, with additional identify information + */ +export const getPublicFormView = async ( formId: string, ): Promise => { - const instance = axios.create() // disable IE ajax request caching (so new forms show on dashboard) - return instance + return axios .get(`${PUBLIC_FORM_ENDPOINT}/${formId}`, { headers: { 'If-Modified-Since': '0' }, }) .then(({ data }) => data) } +/** + * Updates FormUpdateParams attribute(s) in the corresponding form. + * @deprecated This function should no longer be called + * @param formId formId of form in question + * @returns Updated form + */ export const updateForm = async ( formId: string, update: { form: FormUpdateParams }, - accessMode = 'adminform', ): Promise => { - const instance = axios.create() - return instance - .put(`${formId}/${accessMode}`, update) + return axios + .put(`${formId}/adminform`, update) .then(({ data }) => data) } -export const saveForm = async ( +/** + * Duplicates the form + * @param formId formId of form to be duplicated + * @param duplicateFormBody Title, response mode and relevant information for new form + * @returns Metadata of duplicated form for dashboard view + */ +export const duplicateForm = async ( formId: string, - formToSave: FormFieldDto[], + duplicateFormBody: DuplicateFormBody, ): Promise => { - const instance = axios.create() - return instance + return axios .post( `${ADMIN_FORM_ENDPOINT}/${formId}/duplicate`, - formToSave, + duplicateFormBody, ) .then(({ data }) => data) } +/** + * Deletes the form with the corresponding formId + * @param formId formId of form to delete + */ export const deleteForm = async (formId: string): Promise => { - const instance = axios.create() - return instance.delete(`${ADMIN_FORM_ENDPOINT}/${formId}`) + return axios.delete(`${ADMIN_FORM_ENDPOINT}/${formId}`) } -// createForm is called without formId, so the endpoint is just /adminform -export const createForm = async (newForm: { - form: FormFieldDto[] -}): Promise => { - const instance = axios.create() - return instance +/** + * Creates a new form. This function is called without formId, so the endpoint is just /adminform. + * @param newForm Form fields to newly created form + * @returns Newly created form. + */ +export const createForm = async ( + newForm: Omit, +): Promise => { + return axios .post(`${ADMIN_FORM_ENDPOINT}`, newForm) .then(({ data }) => data) } -// Used for viewing templates with use-template or examples listing. Any logged in officer is authorized. +/** + * Only used by examples page. + * Queries templates with use-template or examples listings. Any logged in officer is authorized. + * @param formId formId of template in question + * @returns Public view of a template + */ export const queryTemplate = async ( formId: string, - accessMode = 'adminform', ): Promise<{ form: PublicForm }> => { - const instance = axios.create() - return instance + return axios .get<{ form: PublicForm }>( - `${ADMIN_FORM_ENDPOINT}/${formId}/${accessMode}/template`, + `${ADMIN_FORM_ENDPOINT}/${formId}/adminform/template`, ) .then(({ data }) => data) } -// Used for previewing the form from the form admin page. Must be a viewer, collaborator or admin. +/** + * Gets the public view of a form. Used for previewing the form from the form admin page. + * Must be a viewer, collaborator or admin. + * @param formId formId of template in question + * @returns Public view of a form + */ export const previewForm = async ( formId: string, ): Promise<{ form: PublicForm }> => { - const instance = axios.create() - return instance + return axios .get<{ form: PublicForm }>(`${ADMIN_FORM_ENDPOINT}/${formId}/preview`) .then(({ data }) => data) } -export const useTemplate = async ( - formId: string, - accessMode = 'adminform', -): Promise => { - const instance = axios.create() - return instance - .post(`${ADMIN_FORM_ENDPOINT}/${formId}/${accessMode}/copy`) +/** + * Used to create a new form from an existing template. + * @param formId formId of template to base the new form on + * @returns Metadata for newly created form in dashboard view + */ +export const useTemplate = async (formId: string): Promise => { + return axios + .post(`${ADMIN_FORM_ENDPOINT}/${formId}/adminform/copy`) .then(({ data }) => data) } +/** + * Transfers ownership of form to another user with the given email. + * @param newOwner Object with email of the new owner. + * @returns Updated form with new ownership. + */ export const transferOwner = async ( formId: string, newOwner: { email: string }, ): Promise<{ form: IPopulatedForm }> => { - const instance = axios.create() - return instance + return axios .post<{ form: IPopulatedForm }>( `${ADMIN_FORM_ENDPOINT}/${formId}/collaborators/transfer-owner`, newOwner, diff --git a/src/public/services/__tests__/FormService.test.ts b/src/public/services/__tests__/FormService.test.ts index c0c48a6f2e..1c9770e6ad 100644 --- a/src/public/services/__tests__/FormService.test.ts +++ b/src/public/services/__tests__/FormService.test.ts @@ -2,7 +2,10 @@ import { ObjectId } from 'bson' import { StatusCodes } from 'http-status-codes' import mockAxios from 'jest-mock-axios' -import { FormUpdateParams } from 'src/app/modules/form/admin-form/admin-form.types' +import { + DuplicateFormBody, + FormUpdateParams, +} from 'src/app/modules/form/admin-form/admin-form.types' import { IFieldSchema, IPopulatedUser, IYesNoFieldSchema } from 'src/types' import { FormMetaView, @@ -15,13 +18,13 @@ import { ADMIN_FORM_ENDPOINT, createForm, deleteForm, - getAdminForm, - getPublicForm, + duplicateForm, + getAdminFormView, + getDashboardViews, + getPublicFormView, previewForm, PUBLIC_FORM_ENDPOINT, - queryForm, queryTemplate, - saveForm, transferOwner, updateForm, useTemplate, @@ -35,13 +38,13 @@ const MOCK_USER = { describe('FormService', () => { afterEach(() => mockAxios.reset()) - describe('queryForm', () => { + describe('getDashboardViews', () => { it('should successfully return all available forms if GET request succeeds', async () => { // Arrange const expected: FormMetaView[] = [_generateMockDashboardViewForm()] // Act - const actualPromise = queryForm() + const actualPromise = getDashboardViews() mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -57,7 +60,7 @@ describe('FormService', () => { const expected: FormMetaView[] = [] // Act - const actualPromise = queryForm() + const actualPromise = getDashboardViews() mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -73,7 +76,7 @@ describe('FormService', () => { const expected = new Error('error') // Act - const actualPromise = queryForm() + const actualPromise = getDashboardViews() mockAxios.mockError(expected) //Assert @@ -84,41 +87,36 @@ describe('FormService', () => { }) }) - describe('getAdminForm', () => { + describe('getAdminFormView', () => { it('should return admin form if GET request succeeds', async () => { // Arrange const expected = _generateMockFullForm() - const accessMode = 'adminform' // Act - const actualPromise = getAdminForm(expected._id, accessMode) + const actualPromise = getAdminFormView(expected._id) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise // Assert expect(actual).toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith( - `/${expected._id}/${accessMode}`, - { - headers: { 'If-Modified-Since': '0' }, - }, - ) + expect(mockAxios.get).toHaveBeenCalledWith(`/${expected._id}/adminform`, { + headers: { 'If-Modified-Since': '0' }, + }) }) it('should reject with error message when GET request fails', async () => { // Arrange const expected = new Error('error') const MOCK_FORM = _generateMockFullForm() - const accessMode = 'adminform' // Act - const actualPromise = getAdminForm(MOCK_FORM._id, accessMode) + const actualPromise = getAdminFormView(MOCK_FORM._id) mockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) expect(mockAxios.get).toHaveBeenCalledWith( - `/${MOCK_FORM._id}/${accessMode}`, + `/${MOCK_FORM._id}/adminform`, { headers: { 'If-Modified-Since': '0' }, }, @@ -126,7 +124,7 @@ describe('FormService', () => { }) }) - describe('getPublicForm', () => { + describe('getPublicFormView', () => { it('should return public form if GET request succeeds', async () => { // Arrange const MOCK_FORM_ID = new ObjectId().toHexString() @@ -138,7 +136,7 @@ describe('FormService', () => { } // Act - const actualPromise = getPublicForm(MOCK_FORM_ID) + const actualPromise = getPublicFormView(MOCK_FORM_ID) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -158,7 +156,7 @@ describe('FormService', () => { const expected = new Error('error') // Act - const actualPromise = getPublicForm(MOCK_FORM_ID) + const actualPromise = getPublicFormView(MOCK_FORM_ID) mockAxios.mockError(expected) // Assert @@ -185,17 +183,16 @@ describe('FormService', () => { }, } as FormUpdateParams, } - const accessMode = 'adminform' // Act - const actualPromise = updateForm(MOCK_FORM_ID, update, accessMode) + const actualPromise = updateForm(MOCK_FORM_ID, update) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise // Assert expect(actual).toEqual(expected) expect(mockAxios.put).toHaveBeenCalledWith( - `${MOCK_FORM_ID}/${accessMode}`, + `${MOCK_FORM_ID}/adminform`, update, ) }) @@ -226,15 +223,22 @@ describe('FormService', () => { ) }) - describe('saveForm', () => { + describe('duplicateForm', () => { it('should return saved form if POST request succeeds', async () => { // Arrange const expected = _generateMockDashboardViewForm() const MOCK_FORM_ID = expected._id - const MOCK_FIELDS = [_generateMockField()] + const MOCK_DUPLICATE_FORM_BODY = { + title: 'title', + responseMode: ResponseMode.Email, + emails: 'test@example.com', + } as DuplicateFormBody // Act - const actualPromise = saveForm(MOCK_FORM_ID, MOCK_FIELDS) + const actualPromise = duplicateForm( + MOCK_FORM_ID, + MOCK_DUPLICATE_FORM_BODY, + ) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -242,7 +246,7 @@ describe('FormService', () => { expect(actual).toEqual(expected) expect(mockAxios.post).toHaveBeenCalledWith( `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/duplicate`, - MOCK_FIELDS, + MOCK_DUPLICATE_FORM_BODY, ) }) @@ -250,17 +254,24 @@ describe('FormService', () => { // Arrange const expected = new Error('error') const MOCK_FORM_ID = new ObjectId().toHexString() - const MOCK_FIELDS = [_generateMockField()] + const MOCK_DUPLICATE_FORM_BODY = { + title: 'title', + responseMode: ResponseMode.Email, + emails: 'test@example.com', + } as DuplicateFormBody // Act - const actualPromise = saveForm(MOCK_FORM_ID, MOCK_FIELDS) + const actualPromise = duplicateForm( + MOCK_FORM_ID, + MOCK_DUPLICATE_FORM_BODY, + ) mockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) expect(mockAxios.post).toHaveBeenCalledWith( `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/duplicate`, - MOCK_FIELDS, + MOCK_DUPLICATE_FORM_BODY, ) }) }) @@ -306,8 +317,10 @@ describe('FormService', () => { it('should return created form if POST request succeeds', async () => { // Arrange const expected = { form_fields: [_generateMockField()] } - const MOCK_FORM_PARAMS = { form: [_generateMockField()] } - + const MOCK_FORM_PARAMS = { + title: 'title', + responseMode: ResponseMode.Email, + } // Act const actualPromise = createForm(MOCK_FORM_PARAMS) mockAxios.mockResponse({ data: expected }) @@ -324,7 +337,10 @@ describe('FormService', () => { it('should reject with error message if POST request fails', async () => { // Arrange const expected = new Error('error') - const MOCK_FORM_PARAMS = { form: [_generateMockField()] } + const MOCK_FORM_PARAMS = { + title: 'title', + responseMode: ResponseMode.Email, + } // Act const actualPromise = createForm(MOCK_FORM_PARAMS) @@ -344,17 +360,16 @@ describe('FormService', () => { // Arrange const expected = _generateMockPublicForm() const MOCK_FORM_ID = new ObjectId().toHexString() - const accessMode = 'adminform' // Act - const actualPromise = queryTemplate(MOCK_FORM_ID, accessMode) + const actualPromise = queryTemplate(MOCK_FORM_ID) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise // Assert expect(actual).toEqual(expected) expect(mockAxios.get).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/${accessMode}/template`, + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/template`, ) }) @@ -362,16 +377,15 @@ describe('FormService', () => { // Arrange const expected = new Error('error') const MOCK_FORM_ID = new ObjectId().toHexString() - const accessMode = 'adminform' // Act - const actualPromise = queryTemplate(MOCK_FORM_ID, accessMode) + const actualPromise = queryTemplate(MOCK_FORM_ID) mockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) expect(mockAxios.get).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/${accessMode}/template`, + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/template`, ) }) }) @@ -416,17 +430,16 @@ describe('FormService', () => { // Arrange const expected = _generateMockDashboardViewForm() const MOCK_FORM_ID = new ObjectId().toHexString() - const accessMode = 'adminform' // Act - const actualPromise = useTemplate(MOCK_FORM_ID, accessMode) + const actualPromise = useTemplate(MOCK_FORM_ID) mockAxios.mockResponse({ data: expected }) const actual = await actualPromise // Assert expect(actual).toEqual(expected) expect(mockAxios.post).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/${accessMode}/copy`, + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/copy`, ) }) @@ -434,16 +447,15 @@ describe('FormService', () => { // Arrange const expected = new Error('error') const MOCK_FORM_ID = new ObjectId().toHexString() - const accessMode = 'adminform' // Act - const actualPromise = useTemplate(MOCK_FORM_ID, accessMode) + const actualPromise = useTemplate(MOCK_FORM_ID) mockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) expect(mockAxios.post).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/${accessMode}/copy`, + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/copy`, ) }) }) From 998b8108e4483a786d5450e8b7a8e88ddd489268 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Fri, 21 May 2021 15:08:48 +0800 Subject: [PATCH 10/36] refactor: edit calls to fit new function signature and remove angularjs --- .../list-forms.client.controller.js | 6 +++--- .../forms/config/forms.client.routes.js | 21 ++++++++----------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/public/modules/forms/admin/controllers/list-forms.client.controller.js b/src/public/modules/forms/admin/controllers/list-forms.client.controller.js index 91b53f58cf..9a1c0e992d 100644 --- a/src/public/modules/forms/admin/controllers/list-forms.client.controller.js +++ b/src/public/modules/forms/admin/controllers/list-forms.client.controller.js @@ -194,9 +194,9 @@ function ListFormsController( resolve: { FormToDuplicate: () => { // Retrieve the form so that we can populate the modal with any existing email recipients - return $q - .when(FormApi.preview(vm.myforms[formIndex]._id)) - .then((res) => res.form) + return FormApi.preview(vm.myforms[formIndex]._id).then( + (res) => res.form, + ) }, createFormModalOptions: () => ({ mode: 'duplicate' }), externalScope: () => ({ diff --git a/src/public/modules/forms/config/forms.client.routes.js b/src/public/modules/forms/config/forms.client.routes.js index 5f3672da63..eb9e4a70a9 100644 --- a/src/public/modules/forms/config/forms.client.routes.js +++ b/src/public/modules/forms/config/forms.client.routes.js @@ -21,9 +21,8 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - '$q', - function (FormApi, $transition$, $q) { - return $q.when(FormApi.getPublic($transition$.params().formId)) + function (FormApi, $transition$) { + return FormApi.getPublic($transition$.params().formId) }, ], }, @@ -37,15 +36,14 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - '$q', - function (FormApi, $transition$, $q) { - return $q - .when(FormApi.preview($transition$.params().formId)) - .then((FormData) => { + function (FormApi, $transition$) { + return FormApi.preview($transition$.params().formId).then( + (FormData) => { FormData.isTemplate = true FormData.isPreview = true return FormData - }) + }, + ) }, ], }, @@ -116,9 +114,8 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - '$q', - function (FormApi, $transition$, $q) { - return $q.when(FormApi.getAdmin($transition$.params().formId)) + function (FormApi, $transition$) { + return FormApi.getAdmin($transition$.params().formId) }, ], }, From e990310aa35f00e10fd802d8495ed416d3575583 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Tue, 25 May 2021 09:48:29 +0800 Subject: [PATCH 11/36] refactor: add .when to all FormApi calls --- .../admin/controllers/list-forms.client.controller.js | 6 +++--- .../modules/forms/config/forms.client.routes.js | 11 ++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/public/modules/forms/admin/controllers/list-forms.client.controller.js b/src/public/modules/forms/admin/controllers/list-forms.client.controller.js index 9a1c0e992d..91b53f58cf 100644 --- a/src/public/modules/forms/admin/controllers/list-forms.client.controller.js +++ b/src/public/modules/forms/admin/controllers/list-forms.client.controller.js @@ -194,9 +194,9 @@ function ListFormsController( resolve: { FormToDuplicate: () => { // Retrieve the form so that we can populate the modal with any existing email recipients - return FormApi.preview(vm.myforms[formIndex]._id).then( - (res) => res.form, - ) + return $q + .when(FormApi.preview(vm.myforms[formIndex]._id)) + .then((res) => res.form) }, createFormModalOptions: () => ({ mode: 'duplicate' }), externalScope: () => ({ diff --git a/src/public/modules/forms/config/forms.client.routes.js b/src/public/modules/forms/config/forms.client.routes.js index eb9e4a70a9..b34382faae 100644 --- a/src/public/modules/forms/config/forms.client.routes.js +++ b/src/public/modules/forms/config/forms.client.routes.js @@ -36,14 +36,15 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - function (FormApi, $transition$) { - return FormApi.preview($transition$.params().formId).then( - (FormData) => { + '$q', + function (FormApi, $transition$, $q) { + return $q + .when(FormApi.preview($transition$.params().formId)) + .then((FormData) => { FormData.isTemplate = true FormData.isPreview = true return FormData - }, - ) + }) }, ], }, From f552dc1e514bab9c6846fe209b217e92703d6aec Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Tue, 25 May 2021 12:00:03 +0800 Subject: [PATCH 12/36] refactor: add .when to FormApi calls --- src/public/modules/forms/config/forms.client.routes.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/public/modules/forms/config/forms.client.routes.js b/src/public/modules/forms/config/forms.client.routes.js index b34382faae..5f3672da63 100644 --- a/src/public/modules/forms/config/forms.client.routes.js +++ b/src/public/modules/forms/config/forms.client.routes.js @@ -21,8 +21,9 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - function (FormApi, $transition$) { - return FormApi.getPublic($transition$.params().formId) + '$q', + function (FormApi, $transition$, $q) { + return $q.when(FormApi.getPublic($transition$.params().formId)) }, ], }, @@ -115,8 +116,9 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - function (FormApi, $transition$) { - return FormApi.getAdmin($transition$.params().formId) + '$q', + function (FormApi, $transition$, $q) { + return $q.when(FormApi.getAdmin($transition$.params().formId)) }, ], }, From 664d2c17f1f815491f117682d8aa93002e6b1969 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Thu, 27 May 2021 15:33:47 +0800 Subject: [PATCH 13/36] refactor: rename getDashboardViews to getDashboardView --- src/public/services/FormService.ts | 2 +- src/public/services/__tests__/FormService.test.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/public/services/FormService.ts b/src/public/services/FormService.ts index 4198666672..c428519d14 100644 --- a/src/public/services/FormService.ts +++ b/src/public/services/FormService.ts @@ -22,7 +22,7 @@ export const PUBLIC_FORM_ENDPOINT = '/api/v3/forms' * owns or collaborates on * @returns Metadata required for forms on dashboard view */ -export const getDashboardViews = async (): Promise => { +export const getDashboardView = async (): Promise => { return axios .get(`${ADMIN_FORM_ENDPOINT}`, { headers: { 'If-Modified-Since': '0' }, diff --git a/src/public/services/__tests__/FormService.test.ts b/src/public/services/__tests__/FormService.test.ts index 1c9770e6ad..65a4ba513d 100644 --- a/src/public/services/__tests__/FormService.test.ts +++ b/src/public/services/__tests__/FormService.test.ts @@ -20,7 +20,7 @@ import { deleteForm, duplicateForm, getAdminFormView, - getDashboardViews, + getDashboardView, getPublicFormView, previewForm, PUBLIC_FORM_ENDPOINT, @@ -38,13 +38,13 @@ const MOCK_USER = { describe('FormService', () => { afterEach(() => mockAxios.reset()) - describe('getDashboardViews', () => { + describe('getDashboardView', () => { it('should successfully return all available forms if GET request succeeds', async () => { // Arrange const expected: FormMetaView[] = [_generateMockDashboardViewForm()] // Act - const actualPromise = getDashboardViews() + const actualPromise = getDashboardView() mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -60,7 +60,7 @@ describe('FormService', () => { const expected: FormMetaView[] = [] // Act - const actualPromise = getDashboardViews() + const actualPromise = getDashboardView() mockAxios.mockResponse({ data: expected }) const actual = await actualPromise @@ -76,7 +76,7 @@ describe('FormService', () => { const expected = new Error('error') // Act - const actualPromise = getDashboardViews() + const actualPromise = getDashboardView() mockAxios.mockError(expected) //Assert From 6174d5b0aeda68f83d27cee74117771b9ba46c94 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Thu, 27 May 2021 16:42:59 +0800 Subject: [PATCH 14/36] fix: rename getDashboardViews in form-api.client.factory.js --- src/public/modules/forms/services/form-api.client.factory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/modules/forms/services/form-api.client.factory.js b/src/public/modules/forms/services/form-api.client.factory.js index c3f988f732..5646f355ea 100644 --- a/src/public/modules/forms/services/form-api.client.factory.js +++ b/src/public/modules/forms/services/form-api.client.factory.js @@ -94,7 +94,7 @@ function FormApi(FormErrorService, FormFields) { return { query: () => - generateService(FormService.getDashboardViews, { + generateService(FormService.getDashboardView, { redirectOnError: true, errorTargetState: 'listForms', }), From 0785673df6230fdb5272ce2eec910e0ba3d0e5d1 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Tue, 1 Jun 2021 11:48:42 +0800 Subject: [PATCH 15/36] refactor: remove headers for IE11 support --- src/public/services/FormService.ts | 12 ++------ .../services/__tests__/FormService.test.ts | 29 ++++--------------- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/src/public/services/FormService.ts b/src/public/services/FormService.ts index c428519d14..fcaa74ac93 100644 --- a/src/public/services/FormService.ts +++ b/src/public/services/FormService.ts @@ -24,9 +24,7 @@ export const PUBLIC_FORM_ENDPOINT = '/api/v3/forms' */ export const getDashboardView = async (): Promise => { return axios - .get(`${ADMIN_FORM_ENDPOINT}`, { - headers: { 'If-Modified-Since': '0' }, - }) + .get(`${ADMIN_FORM_ENDPOINT}`) .then(({ data }) => data) } @@ -40,9 +38,7 @@ export const getAdminFormView = async ( ): Promise<{ form: IPopulatedForm }> => { // disable IE ajax request caching (so new forms show on dashboard) return axios - .get<{ form: IPopulatedForm }>(`/${formId}/adminform`, { - headers: { 'If-Modified-Since': '0' }, - }) + .get<{ form: IPopulatedForm }>(`/${formId}/adminform`) .then(({ data }) => data) } @@ -57,9 +53,7 @@ export const getPublicFormView = async ( ): Promise => { // disable IE ajax request caching (so new forms show on dashboard) return axios - .get(`${PUBLIC_FORM_ENDPOINT}/${formId}`, { - headers: { 'If-Modified-Since': '0' }, - }) + .get(`${PUBLIC_FORM_ENDPOINT}/${formId}`) .then(({ data }) => data) } diff --git a/src/public/services/__tests__/FormService.test.ts b/src/public/services/__tests__/FormService.test.ts index 65a4ba513d..3964e411e0 100644 --- a/src/public/services/__tests__/FormService.test.ts +++ b/src/public/services/__tests__/FormService.test.ts @@ -50,9 +50,7 @@ describe('FormService', () => { // Assert expect(actual).toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`, { - headers: { 'If-Modified-Since': '0' }, - }) + expect(mockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`) }) it('should successfully return empty array if GET request succeeds and there are no forms', async () => { @@ -66,9 +64,7 @@ describe('FormService', () => { // Assert expect(actual).toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`, { - headers: { 'If-Modified-Since': '0' }, - }) + expect(mockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`) }) it('should reject with error message if GET request fails', async () => { @@ -81,9 +77,7 @@ describe('FormService', () => { //Assert await expect(actualPromise).rejects.toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`, { - headers: { 'If-Modified-Since': '0' }, - }) + expect(mockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`) }) }) @@ -99,9 +93,7 @@ describe('FormService', () => { // Assert expect(actual).toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith(`/${expected._id}/adminform`, { - headers: { 'If-Modified-Since': '0' }, - }) + expect(mockAxios.get).toHaveBeenCalledWith(`/${expected._id}/adminform`) }) it('should reject with error message when GET request fails', async () => { @@ -115,12 +107,7 @@ describe('FormService', () => { // Assert await expect(actualPromise).rejects.toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith( - `/${MOCK_FORM._id}/adminform`, - { - headers: { 'If-Modified-Since': '0' }, - }, - ) + expect(mockAxios.get).toHaveBeenCalledWith(`/${MOCK_FORM._id}/adminform`) }) }) @@ -144,9 +131,6 @@ describe('FormService', () => { expect(actual).toEqual(expected) expect(mockAxios.get).toHaveBeenCalledWith( `${PUBLIC_FORM_ENDPOINT}/${MOCK_FORM_ID}`, - { - headers: { 'If-Modified-Since': '0' }, - }, ) }) @@ -163,9 +147,6 @@ describe('FormService', () => { await expect(actualPromise).rejects.toEqual(expected) expect(mockAxios.get).toHaveBeenCalledWith( `${PUBLIC_FORM_ENDPOINT}/${MOCK_FORM_ID}`, - { - headers: { 'If-Modified-Since': '0' }, - }, ) }) }) From ff083f9f9824b3426398f9b0e4318a38ffa0facd Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 2 Jun 2021 10:45:25 +0800 Subject: [PATCH 16/36] refactor: split FormService into different services --- src/public/services/AdminViewFormService.ts | 43 +++++ src/public/services/CreateFormService.ts | 50 ++++++ src/public/services/ExamplesService.ts | 36 ++++ src/public/services/FormService.ts | 170 ------------------ src/public/services/PublicFormService.ts | 16 ++ ...minFormService.ts => UpdateFormService.ts} | 40 ++++- 6 files changed, 183 insertions(+), 172 deletions(-) create mode 100644 src/public/services/AdminViewFormService.ts create mode 100644 src/public/services/CreateFormService.ts delete mode 100644 src/public/services/FormService.ts rename src/public/services/{AdminFormService.ts => UpdateFormService.ts} (85%) diff --git a/src/public/services/AdminViewFormService.ts b/src/public/services/AdminViewFormService.ts new file mode 100644 index 0000000000..ce62f4a309 --- /dev/null +++ b/src/public/services/AdminViewFormService.ts @@ -0,0 +1,43 @@ +import axios from 'axios' + +import { FormMetaView, PublicForm } from 'src/types' +import { FormViewDto } from 'src/types/api' + +// endpoint exported for testing +export const ADMIN_FORM_ENDPOINT = '/api/v3/admin/forms' + +/** + * Gets metadata for all forms in dashboard view i.e. forms which user + * owns or collaborates on + * @returns Metadata required for forms on dashboard view + */ +export const getDashboardView = async (): Promise => { + return axios + .get(`${ADMIN_FORM_ENDPOINT}`) + .then(({ data }) => data) +} + +/** + * Gets admin view of form. + * @param formId formId of form in question + * @returns Admin view of form + */ +export const getAdminFormView = async ( + formId: string, +): Promise => { + return axios.get(`/${formId}/adminform`).then(({ data }) => data) +} + +/** + * Gets the public view of a form. Used for previewing the form from the form admin page. + * Must be a viewer, collaborator or admin. + * @param formId formId of form in question + * @returns Public view of a form + */ +export const previewForm = async ( + formId: string, +): Promise<{ form: PublicForm }> => { + return axios + .get<{ form: PublicForm }>(`${ADMIN_FORM_ENDPOINT}/${formId}/preview`) + .then(({ data }) => data) +} diff --git a/src/public/services/CreateFormService.ts b/src/public/services/CreateFormService.ts new file mode 100644 index 0000000000..46ce16326b --- /dev/null +++ b/src/public/services/CreateFormService.ts @@ -0,0 +1,50 @@ +import axios from 'axios' + +import { DuplicateFormBody } from 'src/app/modules/form/admin-form/admin-form.types' +import { FormMetaView, IForm, IFormSchema } from 'src/types' + +// endpoints exported for testing +export const ADMIN_FORM_ENDPOINT = '/api/v3/admin/forms' + +/** + * Duplicates the form + * @param formId formId of form to be duplicated + * @param duplicateFormBody Title, response mode and relevant information for new form + * @returns Metadata of duplicated form for dashboard view + */ +export const duplicateForm = async ( + formId: string, + duplicateFormBody: DuplicateFormBody, +): Promise => { + return axios + .post( + `${ADMIN_FORM_ENDPOINT}/${formId}/duplicate`, + duplicateFormBody, + ) + .then(({ data }) => data) +} + +/** + * Creates a new form. This function is called without formId, so the endpoint is just /adminform. + * @param newForm Form fields to newly created form + * @returns Newly created form. + */ +export const createForm = async ( + newForm: Omit, +): Promise => { + return axios + .post(`${ADMIN_FORM_ENDPOINT}`, { form: newForm }) + .then(({ data }) => data) +} + +/** + * Deletes the form with the corresponding formId + * @param formId formId of form to delete + */ +export const deleteForm = async ( + formId: string, +): Promise<{ message: string }> => { + return axios + .delete(`${ADMIN_FORM_ENDPOINT}/${formId}`) + .then(({ data }) => data) +} diff --git a/src/public/services/ExamplesService.ts b/src/public/services/ExamplesService.ts index 0575cdc711..5d220a2a06 100644 --- a/src/public/services/ExamplesService.ts +++ b/src/public/services/ExamplesService.ts @@ -1,5 +1,8 @@ import axios from 'axios' +import { DuplicateFormBody } from 'src/app/modules/form/admin-form/admin-form.types' +import { FormMetaView, PublicForm } from 'src/types' + import { ExampleFormsQueryDto, ExampleFormsResult, @@ -7,6 +10,7 @@ import { } from '../../types/api' export const EXAMPLES_ENDPOINT = '/examples' +export const ADMIN_FORM_ENDPOINT = '/api/v3/admin/forms' /** * Gets example forms that matches the specified parameters for listing @@ -40,3 +44,35 @@ export const getSingleExampleForm = ( }) .then(({ data }) => data) } + +/** + * Used to create a new form from an existing template. + * @param formId formId of template to base the new form on + * @returns Metadata for newly created form in dashboard view + */ +export const useTemplate = async ( + formId: string, + overrideParams: DuplicateFormBody, +): Promise => { + return axios + .post( + `${ADMIN_FORM_ENDPOINT}/${formId}/adminform/copy`, + overrideParams, + ) + .then(({ data }) => data) +} + +/** + * Queries templates with use-template or examples listings. Any logged in officer is authorized. + * @param formId formId of template in question + * @returns Public view of a template + */ +export const queryTemplate = async ( + formId: string, +): Promise<{ form: PublicForm }> => { + return axios + .get<{ form: PublicForm }>( + `${ADMIN_FORM_ENDPOINT}/${formId}/adminform/template`, + ) + .then(({ data }) => data) +} diff --git a/src/public/services/FormService.ts b/src/public/services/FormService.ts deleted file mode 100644 index fcaa74ac93..0000000000 --- a/src/public/services/FormService.ts +++ /dev/null @@ -1,170 +0,0 @@ -import axios from 'axios' - -import { - DuplicateFormBody, - FormUpdateParams, -} from 'src/app/modules/form/admin-form/admin-form.types' -import { PublicFormViewDto } from 'src/app/modules/form/public-form/public-form.types' -import { - FormMetaView, - IForm, - IFormSchema, - IPopulatedForm, - PublicForm, -} from 'src/types' - -// endpoints exported for testing -export const ADMIN_FORM_ENDPOINT = '/api/v3/admin/forms' -export const PUBLIC_FORM_ENDPOINT = '/api/v3/forms' - -/** - * Gets metadata for all forms in dashboard view i.e. forms which user - * owns or collaborates on - * @returns Metadata required for forms on dashboard view - */ -export const getDashboardView = async (): Promise => { - return axios - .get(`${ADMIN_FORM_ENDPOINT}`) - .then(({ data }) => data) -} - -/** - * Gets admin view of form. - * @param formId formId of form in question - * @returns Admin view of form - */ -export const getAdminFormView = async ( - formId: string, -): Promise<{ form: IPopulatedForm }> => { - // disable IE ajax request caching (so new forms show on dashboard) - return axios - .get<{ form: IPopulatedForm }>(`/${formId}/adminform`) - .then(({ data }) => data) -} - -/** - * Gets public view of form, along with any - * identify information obtained from Singpass/Corppass/MyInfo. - * @param formId FormId of form in question - * @returns Public view of form, with additional identify information - */ -export const getPublicFormView = async ( - formId: string, -): Promise => { - // disable IE ajax request caching (so new forms show on dashboard) - return axios - .get(`${PUBLIC_FORM_ENDPOINT}/${formId}`) - .then(({ data }) => data) -} - -/** - * Updates FormUpdateParams attribute(s) in the corresponding form. - * @deprecated This function should no longer be called - * @param formId formId of form in question - * @returns Updated form - */ -export const updateForm = async ( - formId: string, - update: { form: FormUpdateParams }, -): Promise => { - return axios - .put(`${formId}/adminform`, update) - .then(({ data }) => data) -} - -/** - * Duplicates the form - * @param formId formId of form to be duplicated - * @param duplicateFormBody Title, response mode and relevant information for new form - * @returns Metadata of duplicated form for dashboard view - */ -export const duplicateForm = async ( - formId: string, - duplicateFormBody: DuplicateFormBody, -): Promise => { - return axios - .post( - `${ADMIN_FORM_ENDPOINT}/${formId}/duplicate`, - duplicateFormBody, - ) - .then(({ data }) => data) -} - -/** - * Deletes the form with the corresponding formId - * @param formId formId of form to delete - */ -export const deleteForm = async (formId: string): Promise => { - return axios.delete(`${ADMIN_FORM_ENDPOINT}/${formId}`) -} - -/** - * Creates a new form. This function is called without formId, so the endpoint is just /adminform. - * @param newForm Form fields to newly created form - * @returns Newly created form. - */ -export const createForm = async ( - newForm: Omit, -): Promise => { - return axios - .post(`${ADMIN_FORM_ENDPOINT}`, newForm) - .then(({ data }) => data) -} - -/** - * Only used by examples page. - * Queries templates with use-template or examples listings. Any logged in officer is authorized. - * @param formId formId of template in question - * @returns Public view of a template - */ -export const queryTemplate = async ( - formId: string, -): Promise<{ form: PublicForm }> => { - return axios - .get<{ form: PublicForm }>( - `${ADMIN_FORM_ENDPOINT}/${formId}/adminform/template`, - ) - .then(({ data }) => data) -} - -/** - * Gets the public view of a form. Used for previewing the form from the form admin page. - * Must be a viewer, collaborator or admin. - * @param formId formId of template in question - * @returns Public view of a form - */ -export const previewForm = async ( - formId: string, -): Promise<{ form: PublicForm }> => { - return axios - .get<{ form: PublicForm }>(`${ADMIN_FORM_ENDPOINT}/${formId}/preview`) - .then(({ data }) => data) -} - -/** - * Used to create a new form from an existing template. - * @param formId formId of template to base the new form on - * @returns Metadata for newly created form in dashboard view - */ -export const useTemplate = async (formId: string): Promise => { - return axios - .post(`${ADMIN_FORM_ENDPOINT}/${formId}/adminform/copy`) - .then(({ data }) => data) -} - -/** - * Transfers ownership of form to another user with the given email. - * @param newOwner Object with email of the new owner. - * @returns Updated form with new ownership. - */ -export const transferOwner = async ( - formId: string, - newOwner: { email: string }, -): Promise<{ form: IPopulatedForm }> => { - return axios - .post<{ form: IPopulatedForm }>( - `${ADMIN_FORM_ENDPOINT}/${formId}/collaborators/transfer-owner`, - newOwner, - ) - .then(({ data }) => data) -} diff --git a/src/public/services/PublicFormService.ts b/src/public/services/PublicFormService.ts index cba1f12c96..6b0a92cded 100644 --- a/src/public/services/PublicFormService.ts +++ b/src/public/services/PublicFormService.ts @@ -1,5 +1,7 @@ import axios from 'axios' +import { PublicFormViewDto } from 'src/app/modules/form/public-form/public-form.types' + import { EmailSubmissionDto, EncryptSubmissionDto, @@ -76,3 +78,17 @@ export const submitStorageModeForm = async ({ ) .then(({ data }) => data) } + +/** + * Gets public view of form, along with any + * identify information obtained from Singpass/Corppass/MyInfo. + * @param formId FormId of form in question + * @returns Public view of form, with additional identify information + */ +export const getPublicFormView = async ( + formId: string, +): Promise => { + return axios + .get(`${PUBLIC_FORMS_ENDPOINT}/${formId}`) + .then(({ data }) => data) +} diff --git a/src/public/services/AdminFormService.ts b/src/public/services/UpdateFormService.ts similarity index 85% rename from src/public/services/AdminFormService.ts rename to src/public/services/UpdateFormService.ts index 51d92934ae..457381abed 100644 --- a/src/public/services/AdminFormService.ts +++ b/src/public/services/UpdateFormService.ts @@ -1,6 +1,8 @@ import axios from 'axios' -import { FormSettings, LogicDto } from '../../types' +import { FormUpdateParams } from 'src/app/modules/form/admin-form/admin-form.types' + +import { FormSettings, IPopulatedForm, LogicDto } from '../../types' import { EmailSubmissionDto, EncryptSubmissionDto, @@ -8,6 +10,7 @@ import { FieldCreateDto, FieldUpdateDto, FormFieldDto, + FormViewDto, PermissionsUpdateDto, SettingsUpdateDto, StartPageUpdateDto, @@ -15,7 +18,8 @@ import { } from '../../types/api' import { createEmailSubmissionFormData } from '../utils/submission' -const ADMIN_FORM_ENDPOINT = '/api/v3/admin/forms' +// endpoint exported for testing +export const ADMIN_FORM_ENDPOINT = '/api/v3/admin/forms' export const updateFormSettings = async ( formId: string, @@ -254,3 +258,35 @@ export const submitStorageModeFormPreview = async ({ ) .then(({ data }) => data) } + +/** + * Updates FormUpdateParams attribute(s) in the corresponding form. + * @deprecated This function should no longer be called + * @param formId formId of form in question + * @returns Updated form + */ +export const updateForm = async ( + formId: string, + update: FormUpdateParams, +): Promise => { + return axios + .put(`${formId}/adminform`, { form: update }) + .then(({ data }) => data) +} + +/** + * Transfers ownership of form to another user with the given email. + * @param newOwnerEmail Email of new owner + * @returns Updated form with new ownership. + */ +export const transferOwner = async ( + formId: string, + newOwnerEmail: string, +): Promise => { + return axios + .post( + `${ADMIN_FORM_ENDPOINT}/${formId}/collaborators/transfer-owner`, + { email: newOwnerEmail }, + ) + .then(({ data }) => data) +} From a298378b1345d0ce32f102b1ce2ec6cf9f8c36a7 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 2 Jun 2021 10:47:51 +0800 Subject: [PATCH 17/36] split test into relevant files --- .../__tests__/AdminViewFormService.test.ts | 152 ++++++ .../__tests__/CreateFormService.test.ts | 158 ++++++ .../__tests__/ExamplesService.test.ts | 108 ++++ .../services/__tests__/FormService.test.ts | 510 ------------------ .../__tests__/PublicFormService.test.ts | 41 ++ ...vice.test.ts => UpdateFormService.test.ts} | 102 +++- 6 files changed, 558 insertions(+), 513 deletions(-) create mode 100644 src/public/services/__tests__/AdminViewFormService.test.ts create mode 100644 src/public/services/__tests__/CreateFormService.test.ts delete mode 100644 src/public/services/__tests__/FormService.test.ts rename src/public/services/__tests__/{AdminFormService.test.ts => UpdateFormService.test.ts} (62%) diff --git a/src/public/services/__tests__/AdminViewFormService.test.ts b/src/public/services/__tests__/AdminViewFormService.test.ts new file mode 100644 index 0000000000..e337e9ab28 --- /dev/null +++ b/src/public/services/__tests__/AdminViewFormService.test.ts @@ -0,0 +1,152 @@ +import MockAxios from 'jest-mock-axios' + +import { IPopulatedUser } from 'src/types' +import { + FormMetaView, + IPopulatedForm, + PublicForm, + ResponseMode, +} from 'src/types/form' + +import { + ADMIN_FORM_ENDPOINT, + getAdminFormView, + getDashboardView, + previewForm, +} from '../AdminViewFormService' + +jest.mock('axios', () => MockAxios) + +const MOCK_USER = { + _id: 'mock-user-id', +} as IPopulatedUser + +describe('AdminViewFormService', () => { + afterEach(() => MockAxios.reset()) + describe('getDashboardView', () => { + it('should successfully return all available forms if GET request succeeds', async () => { + // Arrange + const expected: FormMetaView[] = [ + { + title: 'title', + lastModified: new Date(), + _id: 'mock-form-id', + responseMode: ResponseMode.Email, + admin: MOCK_USER, + }, + ] + + // Act + const actualPromise = getDashboardView() + MockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(MockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`) + }) + + it('should successfully return empty array if GET request succeeds and there are no forms', async () => { + // Arrange + const expected: FormMetaView[] = [] + + // Act + const actualPromise = getDashboardView() + MockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(MockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`) + }) + + it('should reject with error message if GET request fails', async () => { + // Arrange + const expected = new Error('error') + + // Act + const actualPromise = getDashboardView() + MockAxios.mockError(expected) + + //Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(MockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`) + }) + }) + + describe('getAdminFormView', () => { + it('should return admin form if GET request succeeds', async () => { + // Arrange + const expected = _generateMockFullForm() + + // Act + const actualPromise = getAdminFormView(expected._id) + MockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(MockAxios.get).toHaveBeenCalledWith(`/${expected._id}/adminform`) + }) + + it('should reject with error message when GET request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM = _generateMockFullForm() + + // Act + const actualPromise = getAdminFormView(MOCK_FORM._id) + MockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(MockAxios.get).toHaveBeenCalledWith(`/${MOCK_FORM._id}/adminform`) + }) + }) + + describe('previewForm', () => { + it('should return public form if GET request succeeds', async () => { + // Arrange + const MOCK_FORM_ID = 'mock-form-id' + const expected = ({ + _id: MOCK_FORM_ID, + title: 'mock preview title', + admin: MOCK_USER, + } as unknown) as PublicForm + + // Act + const actualPromise = previewForm(MOCK_FORM_ID) + MockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(MockAxios.get).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/preview`, + ) + }) + + it('should reject with error message if GET request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = 'mock-form-id' + + // Act + const actualPromise = previewForm(MOCK_FORM_ID) + MockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(MockAxios.get).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/preview`, + ) + }) + }) +}) + +// Utils +const _generateMockFullForm = (): IPopulatedForm => { + return { + _id: 'mock-form-id', + } as IPopulatedForm +} diff --git a/src/public/services/__tests__/CreateFormService.test.ts b/src/public/services/__tests__/CreateFormService.test.ts new file mode 100644 index 0000000000..ea9733f26e --- /dev/null +++ b/src/public/services/__tests__/CreateFormService.test.ts @@ -0,0 +1,158 @@ +import { ObjectId } from 'bson' +import { StatusCodes } from 'http-status-codes' +import MockAxios from 'jest-mock-axios' + +import { DuplicateFormBody } from 'src/app/modules/form/admin-form/admin-form.types' +import { IPopulatedUser, IYesNoFieldSchema } from 'src/types' +import { ResponseMode } from 'src/types/form' + +import { + ADMIN_FORM_ENDPOINT, + createForm, + deleteForm, + duplicateForm, +} from '../CreateFormService' + +jest.mock('axios', () => MockAxios) + +const MOCK_USER = { + _id: new ObjectId(), +} as IPopulatedUser + +describe('CreateFormService', () => { + afterEach(() => MockAxios.reset()) + describe('duplicateForm', () => { + it('should return saved form if POST request succeeds', async () => { + // Arrange + const expected = { + title: 'title', + lastModified: new Date(), + _id: new ObjectId().toHexString(), + responseMode: ResponseMode.Email, + admin: MOCK_USER, + } + const MOCK_FORM_ID = expected._id + const MOCK_DUPLICATE_FORM_BODY = _generateDuplicateFormBody() + + // Act + const actualPromise = duplicateForm( + MOCK_FORM_ID, + MOCK_DUPLICATE_FORM_BODY, + ) + MockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(MockAxios.post).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/duplicate`, + MOCK_DUPLICATE_FORM_BODY, + ) + }) + + it('should reject with error message if POST request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = new ObjectId().toHexString() + const MOCK_DUPLICATE_FORM_BODY = _generateDuplicateFormBody() + + // Act + const actualPromise = duplicateForm( + MOCK_FORM_ID, + MOCK_DUPLICATE_FORM_BODY, + ) + MockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(MockAxios.post).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/duplicate`, + MOCK_DUPLICATE_FORM_BODY, + ) + }) + }) + + describe('deleteForm', () => { + it('should successfully call delete endpoint', async () => { + // Arrange + const MOCK_FORM_ID = new ObjectId().toHexString() + + // Act + const actualPromise = deleteForm(MOCK_FORM_ID) + MockAxios.mockResponse({ + status: StatusCodes.OK, + data: { message: 'Form has been archived' }, + }) + await actualPromise + + // Assert + expect(MockAxios.delete).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}`, + ) + }) + + it('should reject with error message if DELETE request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = new ObjectId().toHexString() + + // Act + const actualPromise = deleteForm(MOCK_FORM_ID) + MockAxios.mockError(expected) + + await expect(actualPromise).rejects.toEqual(expected) + // Assert + expect(MockAxios.delete).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}`, + ) + }) + }) + + describe('createForm', () => { + it('should return created form if POST request succeeds', async () => { + // Arrange + const expected = { form_fields: [{} as IYesNoFieldSchema] } + const MOCK_FORM_PARAMS = { + title: 'title', + responseMode: ResponseMode.Email, + } + // Act + const actualPromise = createForm(MOCK_FORM_PARAMS) + MockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(MockAxios.post).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`, { + form: MOCK_FORM_PARAMS, + }) + }) + + it('should reject with error message if POST request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_PARAMS = { + title: 'title', + responseMode: ResponseMode.Email, + } + + // Act + const actualPromise = createForm(MOCK_FORM_PARAMS) + MockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(MockAxios.post).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`, { + form: MOCK_FORM_PARAMS, + }) + }) + }) +}) + +const _generateDuplicateFormBody = (): DuplicateFormBody => { + return { + title: 'title', + responseMode: ResponseMode.Email, + emails: 'test@example.com', + } as DuplicateFormBody +} diff --git a/src/public/services/__tests__/ExamplesService.test.ts b/src/public/services/__tests__/ExamplesService.test.ts index b50f0edcc5..3dca87c827 100644 --- a/src/public/services/__tests__/ExamplesService.test.ts +++ b/src/public/services/__tests__/ExamplesService.test.ts @@ -1,5 +1,8 @@ import MockAxios from 'jest-mock-axios' +import { DuplicateFormBody } from 'src/app/modules/form/admin-form/admin-form.types' +import { IPopulatedUser, PublicForm, ResponseMode } from 'src/types' + import * as ExamplesService from '../ExamplesService' jest.mock('axios', () => MockAxios) @@ -88,4 +91,109 @@ describe('ExamplesService', () => { }) }) }) + + describe('useTemplate', () => { + it('should return template if POST request succeeds', async () => { + // Arrange + const MOCK_USER = { + _id: 'mock-user-id', + } as IPopulatedUser + const MOCK_FORM_ID = 'mock-form-id' + const expected = { + title: 'title', + lastModified: new Date(), + _id: MOCK_FORM_ID, + responseMode: ResponseMode.Email, + admin: MOCK_USER, + } + const MOCK_DUPLICATE_FORM_BODY = _generateDuplicateFormBody() + + // Act + const actualPromise = ExamplesService.useTemplate( + MOCK_FORM_ID, + MOCK_DUPLICATE_FORM_BODY, + ) + MockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(MockAxios.post).toHaveBeenCalledWith( + `${ExamplesService.ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/copy`, + MOCK_DUPLICATE_FORM_BODY, + ) + }) + + it('should reject with error message if POST request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = 'mock-form-id' + const MOCK_DUPLICATE_FORM_BODY = _generateDuplicateFormBody() + + // Act + const actualPromise = ExamplesService.useTemplate( + MOCK_FORM_ID, + MOCK_DUPLICATE_FORM_BODY, + ) + MockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(MockAxios.post).toHaveBeenCalledWith( + `${ExamplesService.ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/copy`, + MOCK_DUPLICATE_FORM_BODY, + ) + }) + }) + + describe('queryTemplate', () => { + it('should return template if GET request succeeds', async () => { + // Arrange + const MOCK_USER = { + _id: 'mock-user-id', + } as IPopulatedUser + const MOCK_FORM_ID = 'mock-form-id' + const expected = ({ + _id: MOCK_FORM_ID, + title: 'mock preview title', + admin: MOCK_USER, + } as unknown) as PublicForm + + // Act + const actualPromise = ExamplesService.queryTemplate(MOCK_FORM_ID) + MockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(MockAxios.get).toHaveBeenCalledWith( + `${ExamplesService.ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/template`, + ) + }) + + it('should reject with error message if GET request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = 'mock-form-id' + + // Act + const actualPromise = ExamplesService.queryTemplate(MOCK_FORM_ID) + MockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(MockAxios.get).toHaveBeenCalledWith( + `${ExamplesService.ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/template`, + ) + }) + }) }) + +// Utils +const _generateDuplicateFormBody = (): DuplicateFormBody => { + return { + title: 'title', + responseMode: ResponseMode.Email, + emails: 'test@example.com', + } as DuplicateFormBody +} diff --git a/src/public/services/__tests__/FormService.test.ts b/src/public/services/__tests__/FormService.test.ts deleted file mode 100644 index 3964e411e0..0000000000 --- a/src/public/services/__tests__/FormService.test.ts +++ /dev/null @@ -1,510 +0,0 @@ -import { ObjectId } from 'bson' -import { StatusCodes } from 'http-status-codes' -import mockAxios from 'jest-mock-axios' - -import { - DuplicateFormBody, - FormUpdateParams, -} from 'src/app/modules/form/admin-form/admin-form.types' -import { IFieldSchema, IPopulatedUser, IYesNoFieldSchema } from 'src/types' -import { - FormMetaView, - IPopulatedForm, - PublicForm, - ResponseMode, -} from 'src/types/form' - -import { - ADMIN_FORM_ENDPOINT, - createForm, - deleteForm, - duplicateForm, - getAdminFormView, - getDashboardView, - getPublicFormView, - previewForm, - PUBLIC_FORM_ENDPOINT, - queryTemplate, - transferOwner, - updateForm, - useTemplate, -} from '../FormService' - -jest.mock('axios', () => mockAxios) - -const MOCK_USER = { - _id: new ObjectId(), -} as IPopulatedUser - -describe('FormService', () => { - afterEach(() => mockAxios.reset()) - describe('getDashboardView', () => { - it('should successfully return all available forms if GET request succeeds', async () => { - // Arrange - const expected: FormMetaView[] = [_generateMockDashboardViewForm()] - - // Act - const actualPromise = getDashboardView() - mockAxios.mockResponse({ data: expected }) - const actual = await actualPromise - - // Assert - expect(actual).toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`) - }) - - it('should successfully return empty array if GET request succeeds and there are no forms', async () => { - // Arrange - const expected: FormMetaView[] = [] - - // Act - const actualPromise = getDashboardView() - mockAxios.mockResponse({ data: expected }) - const actual = await actualPromise - - // Assert - expect(actual).toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`) - }) - - it('should reject with error message if GET request fails', async () => { - // Arrange - const expected = new Error('error') - - // Act - const actualPromise = getDashboardView() - mockAxios.mockError(expected) - - //Assert - await expect(actualPromise).rejects.toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith(`${ADMIN_FORM_ENDPOINT}`) - }) - }) - - describe('getAdminFormView', () => { - it('should return admin form if GET request succeeds', async () => { - // Arrange - const expected = _generateMockFullForm() - - // Act - const actualPromise = getAdminFormView(expected._id) - mockAxios.mockResponse({ data: expected }) - const actual = await actualPromise - - // Assert - expect(actual).toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith(`/${expected._id}/adminform`) - }) - - it('should reject with error message when GET request fails', async () => { - // Arrange - const expected = new Error('error') - const MOCK_FORM = _generateMockFullForm() - - // Act - const actualPromise = getAdminFormView(MOCK_FORM._id) - mockAxios.mockError(expected) - - // Assert - await expect(actualPromise).rejects.toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith(`/${MOCK_FORM._id}/adminform`) - }) - }) - - describe('getPublicFormView', () => { - it('should return public form if GET request succeeds', async () => { - // Arrange - const MOCK_FORM_ID = new ObjectId().toHexString() - const expected = { - form: { _id: MOCK_FORM_ID, form_fields: [] }, - spcpSession: { username: 'username' }, - isIntranetUser: false, - myInfoError: true, - } - - // Act - const actualPromise = getPublicFormView(MOCK_FORM_ID) - mockAxios.mockResponse({ data: expected }) - const actual = await actualPromise - - // Assert - expect(actual).toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith( - `${PUBLIC_FORM_ENDPOINT}/${MOCK_FORM_ID}`, - ) - }) - - it('should reject with error message if GET request fails', async () => { - // Arrange - const MOCK_FORM_ID = new ObjectId().toHexString() - const expected = new Error('error') - - // Act - const actualPromise = getPublicFormView(MOCK_FORM_ID) - mockAxios.mockError(expected) - - // Assert - await expect(actualPromise).rejects.toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith( - `${PUBLIC_FORM_ENDPOINT}/${MOCK_FORM_ID}`, - ) - }) - }) - - describe('updateForm', () => { - it('should return updated form if PUT request succeeds', async () => { - // Arrange - const expected = [_generateMockField()] - const MOCK_FORM_ID = new ObjectId().toHexString() - const update = { - form: { - editFormField: { - action: { name: 'REORDER' }, - field: expected[0], - }, - } as FormUpdateParams, - } - - // Act - const actualPromise = updateForm(MOCK_FORM_ID, update) - mockAxios.mockResponse({ data: expected }) - const actual = await actualPromise - - // Assert - expect(actual).toEqual(expected) - expect(mockAxios.put).toHaveBeenCalledWith( - `${MOCK_FORM_ID}/adminform`, - update, - ) - }) - - it('should reject with error message if PUT request fails', async () => { - // Arrange - const expected = new Error('error') - const MOCK_FORM_ID = new ObjectId().toHexString() - const update = { - form: { - editFormField: { - action: { name: 'REORDER' }, - field: _generateMockField(), - }, - } as FormUpdateParams, - } - const accessMode = 'adminform' - - // Act - const actualPromise = updateForm(MOCK_FORM_ID, update) - mockAxios.mockError(expected) - - // Assert - await expect(actualPromise).rejects.toEqual(expected) - expect(mockAxios.put).toHaveBeenCalledWith( - `${MOCK_FORM_ID}/${accessMode}`, - update, - ) - }) - - describe('duplicateForm', () => { - it('should return saved form if POST request succeeds', async () => { - // Arrange - const expected = _generateMockDashboardViewForm() - const MOCK_FORM_ID = expected._id - const MOCK_DUPLICATE_FORM_BODY = { - title: 'title', - responseMode: ResponseMode.Email, - emails: 'test@example.com', - } as DuplicateFormBody - - // Act - const actualPromise = duplicateForm( - MOCK_FORM_ID, - MOCK_DUPLICATE_FORM_BODY, - ) - mockAxios.mockResponse({ data: expected }) - const actual = await actualPromise - - // Assert - expect(actual).toEqual(expected) - expect(mockAxios.post).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/duplicate`, - MOCK_DUPLICATE_FORM_BODY, - ) - }) - - it('should reject with error message if POST request fails', async () => { - // Arrange - const expected = new Error('error') - const MOCK_FORM_ID = new ObjectId().toHexString() - const MOCK_DUPLICATE_FORM_BODY = { - title: 'title', - responseMode: ResponseMode.Email, - emails: 'test@example.com', - } as DuplicateFormBody - - // Act - const actualPromise = duplicateForm( - MOCK_FORM_ID, - MOCK_DUPLICATE_FORM_BODY, - ) - mockAxios.mockError(expected) - - // Assert - await expect(actualPromise).rejects.toEqual(expected) - expect(mockAxios.post).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/duplicate`, - MOCK_DUPLICATE_FORM_BODY, - ) - }) - }) - }) - - describe('deleteForm', () => { - it('should successfully call delete endpoint', async () => { - // Arrange - const MOCK_FORM_ID = new ObjectId().toHexString() - - // Act - const actualPromise = deleteForm(MOCK_FORM_ID) - mockAxios.mockResponse({ - status: StatusCodes.OK, - data: { message: 'Form has been archived' }, - }) - await actualPromise - - // Assert - expect(mockAxios.delete).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}`, - ) - }) - - it('should reject with error message if DELETE request fails', async () => { - // Arrange - const expected = new Error('error') - const MOCK_FORM_ID = new ObjectId().toHexString() - - // Act - const actualPromise = deleteForm(MOCK_FORM_ID) - mockAxios.mockError(expected) - - await expect(actualPromise).rejects.toEqual(expected) - // Assert - expect(mockAxios.delete).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}`, - ) - }) - }) - - describe('createForm', () => { - it('should return created form if POST request succeeds', async () => { - // Arrange - const expected = { form_fields: [_generateMockField()] } - const MOCK_FORM_PARAMS = { - title: 'title', - responseMode: ResponseMode.Email, - } - // Act - const actualPromise = createForm(MOCK_FORM_PARAMS) - mockAxios.mockResponse({ data: expected }) - const actual = await actualPromise - - // Assert - expect(actual).toEqual(expected) - expect(mockAxios.post).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}`, - MOCK_FORM_PARAMS, - ) - }) - - it('should reject with error message if POST request fails', async () => { - // Arrange - const expected = new Error('error') - const MOCK_FORM_PARAMS = { - title: 'title', - responseMode: ResponseMode.Email, - } - - // Act - const actualPromise = createForm(MOCK_FORM_PARAMS) - mockAxios.mockError(expected) - - // Assert - await expect(actualPromise).rejects.toEqual(expected) - expect(mockAxios.post).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}`, - MOCK_FORM_PARAMS, - ) - }) - }) - - describe('queryTemplate', () => { - it('should return template if GET request succeeds', async () => { - // Arrange - const expected = _generateMockPublicForm() - const MOCK_FORM_ID = new ObjectId().toHexString() - - // Act - const actualPromise = queryTemplate(MOCK_FORM_ID) - mockAxios.mockResponse({ data: expected }) - const actual = await actualPromise - - // Assert - expect(actual).toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/template`, - ) - }) - - it('should reject with error message if GET request fails', async () => { - // Arrange - const expected = new Error('error') - const MOCK_FORM_ID = new ObjectId().toHexString() - - // Act - const actualPromise = queryTemplate(MOCK_FORM_ID) - mockAxios.mockError(expected) - - // Assert - await expect(actualPromise).rejects.toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/template`, - ) - }) - }) - - describe('previewForm', () => { - it('should return public form if GET request succeeds', async () => { - // Arrange - const expected = _generateMockPublicForm() - const MOCK_FORM_ID = new ObjectId().toHexString() - - // Act - const actualPromise = previewForm(MOCK_FORM_ID) - mockAxios.mockResponse({ data: expected }) - const actual = await actualPromise - - // Assert - expect(actual).toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/preview`, - ) - }) - - it('should reject with error message if GET request fails', async () => { - // Arrange - const expected = new Error('error') - const MOCK_FORM_ID = new ObjectId().toHexString() - - // Act - const actualPromise = previewForm(MOCK_FORM_ID) - mockAxios.mockError(expected) - - // Assert - await expect(actualPromise).rejects.toEqual(expected) - expect(mockAxios.get).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/preview`, - ) - }) - }) - - describe('useTemplate', () => { - it('should return template if POST request succeeds', async () => { - // Arrange - const expected = _generateMockDashboardViewForm() - const MOCK_FORM_ID = new ObjectId().toHexString() - - // Act - const actualPromise = useTemplate(MOCK_FORM_ID) - mockAxios.mockResponse({ data: expected }) - const actual = await actualPromise - - // Assert - expect(actual).toEqual(expected) - expect(mockAxios.post).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/copy`, - ) - }) - - it('should reject with error message if POST request fails', async () => { - // Arrange - const expected = new Error('error') - const MOCK_FORM_ID = new ObjectId().toHexString() - - // Act - const actualPromise = useTemplate(MOCK_FORM_ID) - mockAxios.mockError(expected) - - // Assert - await expect(actualPromise).rejects.toEqual(expected) - expect(mockAxios.post).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/copy`, - ) - }) - }) - - describe('transferOwner', () => { - it('should return updated form if POST request succeeds', async () => { - // Arrange - const expected = _generateMockFullForm() - const MOCK_FORM_ID = new ObjectId().toHexString() - const MOCK_NEW_OWNER = { email: 'test@open.gov.sg' } - // Act - const actualPromise = transferOwner(MOCK_FORM_ID, MOCK_NEW_OWNER) - mockAxios.mockResponse({ data: expected }) - const actual = await actualPromise - - // Assert - expect(actual).toEqual(expected) - expect(mockAxios.post).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/collaborators/transfer-owner`, - MOCK_NEW_OWNER, - ) - }) - - it('should reject with error message if POST request fails', async () => { - // Arrange - const expected = new Error('error') - const MOCK_FORM_ID = new ObjectId().toHexString() - const MOCK_NEW_OWNER = { email: 'test@open.gov.sg' } - - // Act - const actualPromise = transferOwner(MOCK_FORM_ID, MOCK_NEW_OWNER) - mockAxios.mockError(expected) - - // Assert - await expect(actualPromise).rejects.toEqual(expected) - expect(mockAxios.post).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/collaborators/transfer-owner`, - MOCK_NEW_OWNER, - ) - }) - }) -}) - -// Utils -const _generateMockDashboardViewForm = (): FormMetaView => { - return { - title: 'title', - lastModified: new Date(), - _id: new ObjectId(), - responseMode: ResponseMode.Email, - admin: MOCK_USER, - } -} - -const _generateMockFullForm = (): IPopulatedForm => { - return { - _id: new ObjectId(), - } as IPopulatedForm -} - -const _generateMockField = (): IFieldSchema => { - return {} as IYesNoFieldSchema -} - -const _generateMockPublicForm = (): PublicForm => { - return ({ - _id: new ObjectId(), - title: 'mock preview title', - admin: MOCK_USER, - } as unknown) as PublicForm -} diff --git a/src/public/services/__tests__/PublicFormService.test.ts b/src/public/services/__tests__/PublicFormService.test.ts index 559ebd30a2..8f5bcd798e 100644 --- a/src/public/services/__tests__/PublicFormService.test.ts +++ b/src/public/services/__tests__/PublicFormService.test.ts @@ -10,6 +10,7 @@ import { import * as SubmissionUtil from '../../utils/submission' import { + getPublicFormView, submitEmailModeForm, submitStorageModeForm, } from '../PublicFormService' @@ -155,4 +156,44 @@ describe('PublicFormService', () => { ) }) }) + + describe('getPublicFormView', () => { + it('should return public form if GET request succeeds', async () => { + // Arrange + const MOCK_FORM_ID = 'mock-form-id' + const expected = { + form: { _id: MOCK_FORM_ID, form_fields: [] }, + spcpSession: { username: 'username' }, + isIntranetUser: false, + myInfoError: true, + } + + // Act + const actualPromise = getPublicFormView(MOCK_FORM_ID) + MockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(MockAxios.get).toHaveBeenCalledWith( + `/api/v3/forms/${MOCK_FORM_ID}`, + ) + }) + + it('should reject with error message if GET request fails', async () => { + // Arrange + const MOCK_FORM_ID = 'mock-form-id' + const expected = new Error('error') + + // Act + const actualPromise = getPublicFormView(MOCK_FORM_ID) + MockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(MockAxios.get).toHaveBeenCalledWith( + `/api/v3/forms/${MOCK_FORM_ID}`, + ) + }) + }) }) diff --git a/src/public/services/__tests__/AdminFormService.test.ts b/src/public/services/__tests__/UpdateFormService.test.ts similarity index 62% rename from src/public/services/__tests__/AdminFormService.test.ts rename to src/public/services/__tests__/UpdateFormService.test.ts index f8e80438fb..2a8518b730 100644 --- a/src/public/services/__tests__/AdminFormService.test.ts +++ b/src/public/services/__tests__/UpdateFormService.test.ts @@ -1,7 +1,9 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ +import { ObjectId } from 'bson' import MockAxios from 'jest-mock-axios' -import { BasicField } from 'src/types' +import { FormUpdateParams } from 'src/app/modules/form/admin-form/admin-form.types' +import { BasicField, IPopulatedForm, IYesNoFieldSchema } from 'src/types' import { EmailSubmissionDto, EncryptSubmissionDto, @@ -10,13 +12,16 @@ import { import * as SubmissionUtil from '../../utils/submission' import { + ADMIN_FORM_ENDPOINT, submitEmailModeFormPreview, submitStorageModeFormPreview, -} from '../AdminFormService' + transferOwner, + updateForm, +} from '../UpdateFormService' jest.mock('axios', () => MockAxios) -describe('AdminFormService', () => { +describe('UpdateFormService', () => { describe('submitEmailModeFormPreview', () => { const MOCK_FORM_ID = 'mock–form-id' const MOCK_RESPONSE: SubmissionResponseDto = { @@ -155,4 +160,95 @@ describe('AdminFormService', () => { ) }) }) + + describe('updateForm', () => { + it('should return updated form if PUT request succeeds', async () => { + // Arrange + const expected = [{} as IYesNoFieldSchema] + const MOCK_FORM_ID = new ObjectId().toHexString() + const update = { + editFormField: { + action: { name: 'REORDER' }, + field: expected[0], + }, + } as FormUpdateParams + + // Act + const actualPromise = updateForm(MOCK_FORM_ID, update) + MockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect(MockAxios.put).toHaveBeenCalledWith(`${MOCK_FORM_ID}/adminform`, { + form: update, + }) + }) + + it('should reject with error message if PUT request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = new ObjectId().toHexString() + const update = { + editFormField: { + action: { name: 'REORDER' }, + field: {} as IYesNoFieldSchema, + }, + } as FormUpdateParams + + // Act + const actualPromise = updateForm(MOCK_FORM_ID, update) + MockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect(MockAxios.put).toHaveBeenCalledWith(`${MOCK_FORM_ID}/adminform`, { + form: update, + }) + }) + }) + + describe('transferOwner', () => { + it('should return updated form if POST request succeeds', async () => { + // Arrange + const MOCK_FORM_ID = 'mock-form-id' + const expected = { + _id: MOCK_FORM_ID, + } as IPopulatedForm + const MOCK_NEW_OWNER = 'test@open.gov.sg' + // Act + const actualPromise = transferOwner(MOCK_FORM_ID, MOCK_NEW_OWNER) + MockAxios.mockResponse({ data: expected }) + const actual = await actualPromise + + // Assert + expect(actual).toEqual(expected) + expect( + MockAxios.post, + ).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/collaborators/transfer-owner`, + { email: MOCK_NEW_OWNER }, + ) + }) + + it('should reject with error message if POST request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = 'mock-form-id' + const MOCK_NEW_OWNER = 'test@open.gov.sg' + + // Act + const actualPromise = transferOwner(MOCK_FORM_ID, MOCK_NEW_OWNER) + MockAxios.mockError(expected) + + // Assert + await expect(actualPromise).rejects.toEqual(expected) + expect( + MockAxios.post, + ).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/collaborators/transfer-owner`, + { email: MOCK_NEW_OWNER }, + ) + }) + }) }) From 573858d97a60fa49bc54c48ae9376e91b622c153 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 2 Jun 2021 11:03:45 +0800 Subject: [PATCH 18/36] refactor: update other files after rename of AdminFormService --- .../components/edit-logic.client.component.js | 4 ++-- .../admin-form.client.controller.js | 20 ++++++++++--------- .../collaborator-modal.client.controller.js | 7 +++++-- .../edit-logic-modal.client.controller.js | 6 +++--- .../base/directives/submit-form.directive.js | 6 +++--- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/public/modules/forms/admin/components/edit-logic.client.component.js b/src/public/modules/forms/admin/components/edit-logic.client.component.js index 78f5a88e7f..0cdea8675b 100644 --- a/src/public/modules/forms/admin/components/edit-logic.client.component.js +++ b/src/public/modules/forms/admin/components/edit-logic.client.component.js @@ -1,7 +1,7 @@ 'use strict' const { LogicType } = require('../../../../../types') -const AdminFormService = require('../../../../services/AdminFormService') +const UpdateFormService = require('../../../../services/UpdateFormService') angular.module('forms').component('editLogicComponent', { templateUrl: 'modules/forms/admin/componentViews/edit-logic.client.view.html', @@ -82,7 +82,7 @@ function editLogicComponentController($uibModal, FormFields, Toastr, $q) { vm.deleteLogic = function (logicIndex) { const logicIdToDelete = vm.myform.form_logics[logicIndex]._id - $q.when(AdminFormService.deleteFormLogic(vm.myform._id, logicIdToDelete)) + $q.when(UpdateFormService.deleteFormLogic(vm.myform._id, logicIdToDelete)) .then(() => { vm.myform.form_logics.splice(logicIndex, 1) }) diff --git a/src/public/modules/forms/admin/controllers/admin-form.client.controller.js b/src/public/modules/forms/admin/controllers/admin-form.client.controller.js index 42ab2d2743..e3ff05c376 100644 --- a/src/public/modules/forms/admin/controllers/admin-form.client.controller.js +++ b/src/public/modules/forms/admin/controllers/admin-form.client.controller.js @@ -3,7 +3,7 @@ const { StatusCodes } = require('http-status-codes') const get = require('lodash/get') const { LogicType } = require('../../../../../types') -const AdminFormService = require('../../../../services/AdminFormService') +const UpdateFormService = require('../../../../services/UpdateFormService') const FieldFactory = require('../../helpers/field-factory') const { UPDATE_FORM_TYPES } = require('../constants/update-form-types') @@ -191,7 +191,9 @@ function AdminFormController( case UPDATE_FORM_TYPES.CreateField: { const { body } = update return $q - .when(AdminFormService.createSingleFormField($scope.myform._id, body)) + .when( + UpdateFormService.createSingleFormField($scope.myform._id, body), + ) .then((updatedFormField) => { // !!! Convert retrieved form field objects into their class counterparts. const updatedFieldClass = @@ -210,7 +212,7 @@ function AdminFormController( const { fieldId } = update return $q .when( - AdminFormService.deleteSingleFormField($scope.myform._id, fieldId), + UpdateFormService.deleteSingleFormField($scope.myform._id, fieldId), ) .then(() => { // Success, remove deleted form field @@ -225,7 +227,7 @@ function AdminFormController( const { fieldId, body } = update return $q .when( - AdminFormService.updateSingleFormField( + UpdateFormService.updateSingleFormField( $scope.myform._id, fieldId, body, @@ -253,7 +255,7 @@ function AdminFormController( const { fieldId } = update return $q .when( - AdminFormService.duplicateSingleFormField( + UpdateFormService.duplicateSingleFormField( $scope.myform._id, fieldId, ), @@ -277,7 +279,7 @@ function AdminFormController( return $q .when( - AdminFormService.reorderSingleFormField( + UpdateFormService.reorderSingleFormField( $scope.myform._id, fieldId, newPosition, @@ -309,7 +311,7 @@ function AdminFormController( $scope.updateFormEndPage = (newEndPage) => { return $q - .when(AdminFormService.updateFormEndPage($scope.myform._id, newEndPage)) + .when(UpdateFormService.updateFormEndPage($scope.myform._id, newEndPage)) .then((updatedEndPage) => { $scope.myform.endPage = updatedEndPage }) @@ -319,7 +321,7 @@ function AdminFormController( $scope.updateFormStartPage = (newStartPage) => { return $q .when( - AdminFormService.updateFormStartPage($scope.myform._id, newStartPage), + UpdateFormService.updateFormStartPage($scope.myform._id, newStartPage), ) .then((updatedStartPage) => { $scope.myform.startPage = updatedStartPage @@ -335,7 +337,7 @@ function AdminFormController( $scope.updateFormSettings = (settingsToUpdate) => { return $q .when( - AdminFormService.updateFormSettings( + UpdateFormService.updateFormSettings( $scope.myform._id, settingsToUpdate, ), diff --git a/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js b/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js index 8d42013c8f..a986d616f2 100644 --- a/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js +++ b/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js @@ -2,7 +2,7 @@ const { get } = require('lodash') const { StatusCodes } = require('http-status-codes') -const AdminFormService = require('../../../../services/AdminFormService') +const UpdateFormService = require('../../../../services/UpdateFormService') angular .module('forms') @@ -99,7 +99,10 @@ function CollaboratorModalController( $scope.updatePermissionList = (permissionList) => { return $q .when( - AdminFormService.updateCollaborators($scope.myform._id, permissionList), + UpdateFormService.updateCollaborators( + $scope.myform._id, + permissionList, + ), ) .then((updatedCollaborators) => { $scope.myform.permissionList = updatedCollaborators diff --git a/src/public/modules/forms/admin/controllers/edit-logic-modal.client.controller.js b/src/public/modules/forms/admin/controllers/edit-logic-modal.client.controller.js index 19f88c5561..c6d2d5c164 100644 --- a/src/public/modules/forms/admin/controllers/edit-logic-modal.client.controller.js +++ b/src/public/modules/forms/admin/controllers/edit-logic-modal.client.controller.js @@ -3,7 +3,7 @@ const { range } = require('lodash') const { LogicType } = require('../../../../../types') const FormLogic = require('../../services/form-logic/form-logic.client.service') -const AdminFormService = require('../../../../services/AdminFormService') +const UpdateFormService = require('../../../../services/UpdateFormService') angular .module('forms') @@ -272,7 +272,7 @@ function EditLogicModalController( } vm.createNewLogic = function (newLogic) { - $q.when(AdminFormService.createFormLogic(vm.myform._id, newLogic)) + $q.when(UpdateFormService.createFormLogic(vm.myform._id, newLogic)) .then((createdLogic) => { vm.formLogics.push(createdLogic) externalScope.myform.form_logics.push(createdLogic) // update global myform @@ -290,7 +290,7 @@ function EditLogicModalController( vm.updateExistingLogic = function (logicIndex, updatedLogic) { const logicIdToUpdate = vm.formLogics[logicIndex]._id $q.when( - AdminFormService.updateFormLogic( + UpdateFormService.updateFormLogic( vm.myform._id, logicIdToUpdate, updatedLogic, diff --git a/src/public/modules/forms/base/directives/submit-form.directive.js b/src/public/modules/forms/base/directives/submit-form.directive.js index 1527f177c0..fd686c819e 100644 --- a/src/public/modules/forms/base/directives/submit-form.directive.js +++ b/src/public/modules/forms/base/directives/submit-form.directive.js @@ -5,7 +5,7 @@ const get = require('lodash/get') const FieldVerificationService = require('../../../../services/FieldVerificationService') const PublicFormAuthService = require('../../../../services/PublicFormAuthService') const PublicFormService = require('../../../../services/PublicFormService') -const AdminFormService = require('../../../../services/AdminFormService') +const UpdateFormService = require('../../../../services/UpdateFormService') const { getVisibleFieldIds, getLogicUnitPreventingSubmit, @@ -324,7 +324,7 @@ function submitFormDirective( const content = { responses: submissionContent.responses } const submitFn = form.isPreview - ? AdminFormService.submitEmailModeFormPreview + ? UpdateFormService.submitEmailModeFormPreview : PublicFormService.submitEmailModeForm return $q @@ -341,7 +341,7 @@ function submitFormDirective( } case responseModeEnum.ENCRYPT: { const submitFn = form.isPreview - ? AdminFormService.submitStorageModeFormPreview + ? UpdateFormService.submitStorageModeFormPreview : PublicFormService.submitStorageModeForm return $q From 7f91895b838932c57aeae02b902b632f6629b252 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 2 Jun 2021 12:16:30 +0800 Subject: [PATCH 19/36] refactor: change services and rename formapi functions --- .../admin-form.client.controller.js | 2 +- .../collaborator-modal.client.controller.js | 6 +-- .../delete-form-modal.client.controller.js | 2 +- .../list-forms.client.controller.js | 4 +- .../forms/config/forms.client.routes.js | 8 +-- .../forms/services/form-api.client.factory.js | 50 +++++++++++-------- .../services/form-factory.client.service.js | 4 +- .../examples-card.client.directive.js | 6 +-- 8 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/public/modules/forms/admin/controllers/admin-form.client.controller.js b/src/public/modules/forms/admin/controllers/admin-form.client.controller.js index e3ff05c376..51f148f7b9 100644 --- a/src/public/modules/forms/admin/controllers/admin-form.client.controller.js +++ b/src/public/modules/forms/admin/controllers/admin-form.client.controller.js @@ -299,7 +299,7 @@ function AdminFormController( default: // This block should not be reached. All updateForm calls should have an update type. return $q - .when(FormApi.update($scope.myform._id, { form: update })) + .when(FormApi.updateForm($scope.myform._id, update)) .then((savedForm) => { // Updating this form updates lastModified // and also updates myform if a formToUse is passed in diff --git a/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js b/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js index a986d616f2..a145457e17 100644 --- a/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js +++ b/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js @@ -73,11 +73,7 @@ function CollaboratorModalController( return } - $q.when( - FormApi.transferOwner($scope.myform._id, { - email: $scope.transferOwnerEmail, - }), - ) + $q.when(FormApi.transferOwner($scope.myform._id, $scope.transferOwnerEmail)) .then((res) => { $scope.myform = res.form externalScope.refreshFormDataFromCollab($scope.myform) diff --git a/src/public/modules/forms/admin/controllers/delete-form-modal.client.controller.js b/src/public/modules/forms/admin/controllers/delete-form-modal.client.controller.js index 1ad0fb1b4f..b737747ae6 100644 --- a/src/public/modules/forms/admin/controllers/delete-form-modal.client.controller.js +++ b/src/public/modules/forms/admin/controllers/delete-form-modal.client.controller.js @@ -34,7 +34,7 @@ function DeleteFormModalController( }`, ) } - $q.when(FormApi.delete(vm.myforms[formIndex]._id)).then( + $q.when(FormApi.deleteForm(vm.myforms[formIndex]._id)).then( function () { vm.myforms.splice(formIndex, 1) vm.cancel() diff --git a/src/public/modules/forms/admin/controllers/list-forms.client.controller.js b/src/public/modules/forms/admin/controllers/list-forms.client.controller.js index 91b53f58cf..f9ed175d8f 100644 --- a/src/public/modules/forms/admin/controllers/list-forms.client.controller.js +++ b/src/public/modules/forms/admin/controllers/list-forms.client.controller.js @@ -76,7 +76,7 @@ function ListFormsController( // Massage user email into a name turnEmailToName() - $q.when(FormApi.query()).then((_forms) => { + $q.when(FormApi.getDashboardView()).then((_forms) => { vm.myforms = _forms }) } @@ -195,7 +195,7 @@ function ListFormsController( FormToDuplicate: () => { // Retrieve the form so that we can populate the modal with any existing email recipients return $q - .when(FormApi.preview(vm.myforms[formIndex]._id)) + .when(FormApi.previewForm(vm.myforms[formIndex]._id)) .then((res) => res.form) }, createFormModalOptions: () => ({ mode: 'duplicate' }), diff --git a/src/public/modules/forms/config/forms.client.routes.js b/src/public/modules/forms/config/forms.client.routes.js index 5f3672da63..38f731dfdd 100644 --- a/src/public/modules/forms/config/forms.client.routes.js +++ b/src/public/modules/forms/config/forms.client.routes.js @@ -23,7 +23,9 @@ angular.module('forms').config([ '$transition$', '$q', function (FormApi, $transition$, $q) { - return $q.when(FormApi.getPublic($transition$.params().formId)) + return $q.when( + FormApi.getPublicForm($transition$.params().formId), + ) }, ], }, @@ -40,7 +42,7 @@ angular.module('forms').config([ '$q', function (FormApi, $transition$, $q) { return $q - .when(FormApi.preview($transition$.params().formId)) + .when(FormApi.previewForm($transition$.params().formId)) .then((FormData) => { FormData.isTemplate = true FormData.isPreview = true @@ -118,7 +120,7 @@ angular.module('forms').config([ '$transition$', '$q', function (FormApi, $transition$, $q) { - return $q.when(FormApi.getAdmin($transition$.params().formId)) + return $q.when(FormApi.getAdminForm($transition$.params().formId)) }, ], }, diff --git a/src/public/modules/forms/services/form-api.client.factory.js b/src/public/modules/forms/services/form-api.client.factory.js index 5646f355ea..43df48f181 100644 --- a/src/public/modules/forms/services/form-api.client.factory.js +++ b/src/public/modules/forms/services/form-api.client.factory.js @@ -1,7 +1,12 @@ 'use strict' const { get } = require('lodash') const Form = require('../viewmodels/Form.class') -const FormService = require('../../../services/FormService') +const CreateFormService = require('../../../services/CreateFormService') +const UpdateFormService = require('../../../services/UpdateFormService') +const ExamplesService = require('../../../services/ExamplesService') +const AdminViewFormService = require('../../../services/AdminViewFormService') +const PublicFormService = require('../../../services/PublicFormService') + // Forms service used for communicating with the forms REST endpoints angular .module('forms') @@ -93,70 +98,71 @@ function FormApi(FormErrorService, FormFields) { } return { - query: () => - generateService(FormService.getDashboardView, { + getDashboardView: () => + generateService(AdminViewFormService.getDashboardView, { redirectOnError: true, errorTargetState: 'listForms', }), - getAdmin: (formId) => + getAdminForm: (formId) => generateService( - FormService.getAdminFormView, + AdminViewFormService.getAdminFormView, { redirectOnError: true, errorTargetState: 'viewForm' }, formId, ), - getPublic: (formId) => + getPublicForm: (formId) => generateService( - FormService.getPublicFormView, + PublicFormService.getPublicFormView, { redirectOnError: true }, formId, ), - update: (formId, update) => + updateForm: (formId, update) => generateService( - FormService.updateForm, + UpdateFormService.updateForm, { redirectOnError: false }, formId, update, ), - save: (formId, formToSave) => + duplicateForm: (formId, formToSave) => generateService( - FormService.duplicateForm, + CreateFormService.duplicateForm, { redirectOnError: false }, formId, formToSave, ), - delete: (formId) => + deleteForm: (formId) => generateService( - FormService.deleteForm, + CreateFormService.deleteForm, { redirectOnError: false }, formId, ), - create: (newForm) => + createForm: (newForm) => generateService( - FormService.createForm, + CreateFormService.createForm, { redirectOnError: true, errorTargetState: 'listForms' }, newForm, ), - template: (formId) => + queryTemplate: (formId) => generateService( - FormService.queryTemplate, + ExamplesService.queryTemplate, { redirectOnError: true, errorTargetState: 'templateForm' }, formId, ), - preview: (formId) => + previewForm: (formId) => generateService( - FormService.previewForm, + AdminViewFormService.previewForm, { redirectOnError: true, errorTargetState: 'previewForm' }, formId, ), - useTemplate: (formId) => + useTemplate: (formId, overrideParams) => generateService( - FormService.useTemplate, + ExamplesService.useTemplate, { redirectOnError: true, errorTargetState: 'useTemplate' }, formId, + overrideParams, ), transferOwner: (formId, newOwner) => generateService( - FormService.transferOwner, + UpdateFormService.transferOwner, { redirectOnError: false }, formId, newOwner, diff --git a/src/public/modules/forms/services/form-factory.client.service.js b/src/public/modules/forms/services/form-factory.client.service.js index 407d3d786e..79ba86aae2 100644 --- a/src/public/modules/forms/services/form-factory.client.service.js +++ b/src/public/modules/forms/services/form-factory.client.service.js @@ -15,9 +15,9 @@ function FormFactory(FormApi) { function generateForm(mode, params, formId) { switch (mode) { case 'create': - return FormApi.create({ form: params }) + return FormApi.createForm(params) case 'duplicate': - return FormApi.save(formId, params) + return FormApi.duplicateForm(formId, params) case 'useTemplate': return FormApi.useTemplate(formId, params) default: diff --git a/src/public/modules/users/controllers/examples-card.client.directive.js b/src/public/modules/users/controllers/examples-card.client.directive.js index e9d931a48e..d0f9faf37a 100644 --- a/src/public/modules/users/controllers/examples-card.client.directive.js +++ b/src/public/modules/users/controllers/examples-card.client.directive.js @@ -101,11 +101,7 @@ function examplesCardController( resolve: { FormToDuplicate: () => { return $q - .when( - FormApi.template({ - formId: $scope.form._id, - }), - ) + .when(FormApi.queryTemplate($scope.form._id)) .then((res) => res.form) }, createFormModalOptions: () => ({ mode: 'useTemplate' }), From ab47c52293c52fb7d064c98bc69e9478e2eed7fc Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 2 Jun 2021 12:17:49 +0800 Subject: [PATCH 20/36] refactor: remove outdated comment --- src/public/services/ExamplesService.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/public/services/ExamplesService.ts b/src/public/services/ExamplesService.ts index 5d220a2a06..40299b85e7 100644 --- a/src/public/services/ExamplesService.ts +++ b/src/public/services/ExamplesService.ts @@ -24,7 +24,6 @@ export const getExampleForms = ( return axios .get(EXAMPLES_ENDPOINT, { params: exampleFormsSearchParams, - // disable IE ajax request caching (so search requests don't get cached) headers: { 'If-Modified-Since': '0' }, }) .then(({ data }) => data) @@ -39,7 +38,6 @@ export const getSingleExampleForm = ( ): Promise => { return axios .get(`${EXAMPLES_ENDPOINT}/${formId}`, { - // disable IE ajax request caching (so search requests don't get cached) headers: { 'If-Modified-Since': '0' }, }) .then(({ data }) => data) From 93c400341e0d02ff07ef0500c7b7672fa3ccb738 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 2 Jun 2021 12:21:07 +0800 Subject: [PATCH 21/36] refactor: remove unnecessary --- .../modules/forms/config/forms.client.routes.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/public/modules/forms/config/forms.client.routes.js b/src/public/modules/forms/config/forms.client.routes.js index 38f731dfdd..7fab4a7380 100644 --- a/src/public/modules/forms/config/forms.client.routes.js +++ b/src/public/modules/forms/config/forms.client.routes.js @@ -21,11 +21,8 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - '$q', - function (FormApi, $transition$, $q) { - return $q.when( - FormApi.getPublicForm($transition$.params().formId), - ) + function (FormApi, $transition$) { + return FormApi.getPublicForm($transition$.params().formId) }, ], }, @@ -118,9 +115,8 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - '$q', - function (FormApi, $transition$, $q) { - return $q.when(FormApi.getAdminForm($transition$.params().formId)) + function (FormApi, $transition$) { + return FormApi.getAdminForm($transition$.params().formId) }, ], }, From 64b069ae284b00e4b1ab3e5de354823e1c1d06b8 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 2 Jun 2021 15:58:37 +0800 Subject: [PATCH 22/36] docs: update jsdocs regarding AdminFormService --- .../admin/controllers/collaborator-modal.client.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js b/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js index a145457e17..a9ff67bcde 100644 --- a/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js +++ b/src/public/modules/forms/admin/controllers/collaborator-modal.client.controller.js @@ -89,7 +89,7 @@ function CollaboratorModalController( } /** - * Calls AdminFormService to update the permission list (collaborators) of a form + * Calls UpdateFormService to update the permission list (collaborators) of a form * @param {Array} permissionList - New permission list for the form */ $scope.updatePermissionList = (permissionList) => { From c2dcf395a8eeffa5cdbc92997ec36fefa53d5731 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 2 Jun 2021 15:59:08 +0800 Subject: [PATCH 23/36] refactor: remove unnecessary q.when --- .../forms/config/forms.client.routes.js | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/public/modules/forms/config/forms.client.routes.js b/src/public/modules/forms/config/forms.client.routes.js index 7fab4a7380..c76a3aab98 100644 --- a/src/public/modules/forms/config/forms.client.routes.js +++ b/src/public/modules/forms/config/forms.client.routes.js @@ -36,15 +36,14 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - '$q', - function (FormApi, $transition$, $q) { - return $q - .when(FormApi.previewForm($transition$.params().formId)) - .then((FormData) => { + function (FormApi, $transition$) { + return FormApi.previewForm($transition$.params().formId).then( + (FormData) => { FormData.isTemplate = true FormData.isPreview = true return FormData - }) + }, + ) }, ], }, @@ -58,14 +57,13 @@ angular.module('forms').config([ FormData: [ 'FormApi', '$transition$', - '$q', - function (FormApi, $transition$, $q) { - return $q - .when(FormApi.template($transition$.params())) - .then((FormData) => { + function (FormApi, $transition$) { + return FormApi.template($transition$.params()).then( + (FormData) => { FormData.isTemplate = true return FormData - }) + }, + ) }, ], }, @@ -81,17 +79,15 @@ angular.module('forms').config([ // If the user is logged in, this field will contain the form data of the provided formId, // otherwise it will only contain the formId itself. FormData: [ - '$q', 'Auth', 'FormErrorService', '$stateParams', - function ($q, Auth, FormErrorService, $stateParams) { + function (Auth, FormErrorService, $stateParams) { if (!Auth.getUser()) { return $stateParams.formId } - return $q - .when(ExamplesService.getSingleExampleForm($stateParams.formId)) + return ExamplesService.getSingleExampleForm($stateParams.formId) .then(function (response) { response.form.isTemplate = true return response.form From d24f569c4fa1cec0c66a842967552a71cfdabecb Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 2 Jun 2021 16:00:07 +0800 Subject: [PATCH 24/36] refactor: change from absolute to relative import in AdminViewFormService --- src/public/services/AdminViewFormService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/public/services/AdminViewFormService.ts b/src/public/services/AdminViewFormService.ts index ce62f4a309..35f8abefb9 100644 --- a/src/public/services/AdminViewFormService.ts +++ b/src/public/services/AdminViewFormService.ts @@ -1,7 +1,7 @@ import axios from 'axios' -import { FormMetaView, PublicForm } from 'src/types' -import { FormViewDto } from 'src/types/api' +import { FormViewDto } from '../..//types/api' +import { FormMetaView, PublicForm } from '../../types' // endpoint exported for testing export const ADMIN_FORM_ENDPOINT = '/api/v3/admin/forms' From 984b9d66d8b4682ea190e005b158252175bf8f40 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 2 Jun 2021 16:00:57 +0800 Subject: [PATCH 25/36] refactor: shift deleteForm to UpdateFormService --- src/public/services/CreateFormService.ts | 12 ------ src/public/services/UpdateFormService.ts | 12 ++++++ .../__tests__/CreateFormService.test.ts | 38 ------------------- .../__tests__/UpdateFormService.test.ts | 38 +++++++++++++++++++ 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/public/services/CreateFormService.ts b/src/public/services/CreateFormService.ts index 46ce16326b..4fc5094a67 100644 --- a/src/public/services/CreateFormService.ts +++ b/src/public/services/CreateFormService.ts @@ -36,15 +36,3 @@ export const createForm = async ( .post(`${ADMIN_FORM_ENDPOINT}`, { form: newForm }) .then(({ data }) => data) } - -/** - * Deletes the form with the corresponding formId - * @param formId formId of form to delete - */ -export const deleteForm = async ( - formId: string, -): Promise<{ message: string }> => { - return axios - .delete(`${ADMIN_FORM_ENDPOINT}/${formId}`) - .then(({ data }) => data) -} diff --git a/src/public/services/UpdateFormService.ts b/src/public/services/UpdateFormService.ts index 457381abed..dd141e6ca5 100644 --- a/src/public/services/UpdateFormService.ts +++ b/src/public/services/UpdateFormService.ts @@ -259,6 +259,18 @@ export const submitStorageModeFormPreview = async ({ .then(({ data }) => data) } +/** + * Deletes the form with the corresponding formId + * @param formId formId of form to delete + */ +export const deleteForm = async ( + formId: string, +): Promise<{ message: string }> => { + return axios + .delete(`${ADMIN_FORM_ENDPOINT}/${formId}`) + .then(({ data }) => data) +} + /** * Updates FormUpdateParams attribute(s) in the corresponding form. * @deprecated This function should no longer be called diff --git a/src/public/services/__tests__/CreateFormService.test.ts b/src/public/services/__tests__/CreateFormService.test.ts index ea9733f26e..313e54c969 100644 --- a/src/public/services/__tests__/CreateFormService.test.ts +++ b/src/public/services/__tests__/CreateFormService.test.ts @@ -1,5 +1,4 @@ import { ObjectId } from 'bson' -import { StatusCodes } from 'http-status-codes' import MockAxios from 'jest-mock-axios' import { DuplicateFormBody } from 'src/app/modules/form/admin-form/admin-form.types' @@ -9,7 +8,6 @@ import { ResponseMode } from 'src/types/form' import { ADMIN_FORM_ENDPOINT, createForm, - deleteForm, duplicateForm, } from '../CreateFormService' @@ -72,42 +70,6 @@ describe('CreateFormService', () => { }) }) - describe('deleteForm', () => { - it('should successfully call delete endpoint', async () => { - // Arrange - const MOCK_FORM_ID = new ObjectId().toHexString() - - // Act - const actualPromise = deleteForm(MOCK_FORM_ID) - MockAxios.mockResponse({ - status: StatusCodes.OK, - data: { message: 'Form has been archived' }, - }) - await actualPromise - - // Assert - expect(MockAxios.delete).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}`, - ) - }) - - it('should reject with error message if DELETE request fails', async () => { - // Arrange - const expected = new Error('error') - const MOCK_FORM_ID = new ObjectId().toHexString() - - // Act - const actualPromise = deleteForm(MOCK_FORM_ID) - MockAxios.mockError(expected) - - await expect(actualPromise).rejects.toEqual(expected) - // Assert - expect(MockAxios.delete).toHaveBeenCalledWith( - `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}`, - ) - }) - }) - describe('createForm', () => { it('should return created form if POST request succeeds', async () => { // Arrange diff --git a/src/public/services/__tests__/UpdateFormService.test.ts b/src/public/services/__tests__/UpdateFormService.test.ts index 2a8518b730..9c00552ad0 100644 --- a/src/public/services/__tests__/UpdateFormService.test.ts +++ b/src/public/services/__tests__/UpdateFormService.test.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { ObjectId } from 'bson' +import { StatusCodes } from 'http-status-codes' import MockAxios from 'jest-mock-axios' import { FormUpdateParams } from 'src/app/modules/form/admin-form/admin-form.types' @@ -13,6 +14,7 @@ import { import * as SubmissionUtil from '../../utils/submission' import { ADMIN_FORM_ENDPOINT, + deleteForm, submitEmailModeFormPreview, submitStorageModeFormPreview, transferOwner, @@ -161,6 +163,42 @@ describe('UpdateFormService', () => { }) }) + describe('deleteForm', () => { + it('should successfully call delete endpoint', async () => { + // Arrange + const MOCK_FORM_ID = new ObjectId().toHexString() + + // Act + const actualPromise = deleteForm(MOCK_FORM_ID) + MockAxios.mockResponse({ + status: StatusCodes.OK, + data: { message: 'Form has been archived' }, + }) + await actualPromise + + // Assert + expect(MockAxios.delete).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}`, + ) + }) + + it('should reject with error message if DELETE request fails', async () => { + // Arrange + const expected = new Error('error') + const MOCK_FORM_ID = new ObjectId().toHexString() + + // Act + const actualPromise = deleteForm(MOCK_FORM_ID) + MockAxios.mockError(expected) + + await expect(actualPromise).rejects.toEqual(expected) + // Assert + expect(MockAxios.delete).toHaveBeenCalledWith( + `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}`, + ) + }) + }) + describe('updateForm', () => { it('should return updated form if PUT request succeeds', async () => { // Arrange From 2f8880a17d60a2c2cd36f67fe5c8d399f0728cd8 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 2 Jun 2021 16:36:18 +0800 Subject: [PATCH 26/36] fix: change service deleteForm --- src/public/modules/forms/services/form-api.client.factory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/modules/forms/services/form-api.client.factory.js b/src/public/modules/forms/services/form-api.client.factory.js index 43df48f181..3d9affa3ca 100644 --- a/src/public/modules/forms/services/form-api.client.factory.js +++ b/src/public/modules/forms/services/form-api.client.factory.js @@ -131,7 +131,7 @@ function FormApi(FormErrorService, FormFields) { ), deleteForm: (formId) => generateService( - CreateFormService.deleteForm, + UpdateFormService.deleteForm, { redirectOnError: false }, formId, ), From f08d3d6585092eca1944f9d01150be1c4c26598d Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 2 Jun 2021 17:26:19 +0800 Subject: [PATCH 27/36] refactor: make tcalls to transformations explicit --- .../forms/services/form-api.client.factory.js | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/public/modules/forms/services/form-api.client.factory.js b/src/public/modules/forms/services/form-api.client.factory.js index 3d9affa3ca..c26de14407 100644 --- a/src/public/modules/forms/services/form-api.client.factory.js +++ b/src/public/modules/forms/services/form-api.client.factory.js @@ -36,10 +36,8 @@ function FormApi(FormErrorService, FormFields) { * @param {*} input API service data input * @returns Transformed input */ - const handleInput = (input) => { - if (get(input, 'form')) { - FormFields.removeMyInfoFromForm(input) - } + const stripMyInfo = (input) => { + FormFields.removeMyInfoFromForm(input) return input } @@ -51,7 +49,7 @@ function FormApi(FormErrorService, FormFields) { * @param {*} data Data returned from API call * @returns Transformed data */ - const handleResponse = (data) => { + const injectMyInfo = (data) => { // The backend returns different shapes for different request types. For GET // requests, it returns an object with a form attribute containing // all the form data; for PUT requests, it returns an object with @@ -89,73 +87,100 @@ function FormApi(FormErrorService, FormFields) { } } - const generateService = (service, errorParams, ...inputs) => { - return service(...inputs.map((input) => handleInput(input))) + const generateService = ( + service, + handleInput, + handleResponse, + errorParams, + ...inputs + ) => { + const input = handleInput + ? inputs.map((input) => handleInput(input)) + : inputs + return service(...input) .then((data) => { - return handleResponse(data) + return handleResponse ? handleResponse(data) : data }) .catch((err) => handleError(err, errorParams)) } return { getDashboardView: () => - generateService(AdminViewFormService.getDashboardView, { + generateService(AdminViewFormService.getDashboardView, null, null, { redirectOnError: true, errorTargetState: 'listForms', }), getAdminForm: (formId) => generateService( AdminViewFormService.getAdminFormView, + null, + injectMyInfo, { redirectOnError: true, errorTargetState: 'viewForm' }, formId, ), getPublicForm: (formId) => generateService( PublicFormService.getPublicFormView, + null, + injectMyInfo, { redirectOnError: true }, formId, ), updateForm: (formId, update) => generateService( UpdateFormService.updateForm, + stripMyInfo, + injectMyInfo, { redirectOnError: false }, formId, update, ), - duplicateForm: (formId, formToSave) => + duplicateForm: (formId, duplicateFormBody) => generateService( CreateFormService.duplicateForm, + null, + injectMyInfo, { redirectOnError: false }, formId, - formToSave, + duplicateFormBody, ), deleteForm: (formId) => generateService( UpdateFormService.deleteForm, + null, + null, { redirectOnError: false }, formId, ), createForm: (newForm) => generateService( CreateFormService.createForm, + stripMyInfo, + injectMyInfo, { redirectOnError: true, errorTargetState: 'listForms' }, newForm, ), queryTemplate: (formId) => generateService( ExamplesService.queryTemplate, + null, + injectMyInfo, { redirectOnError: true, errorTargetState: 'templateForm' }, formId, ), previewForm: (formId) => generateService( AdminViewFormService.previewForm, + null, + injectMyInfo, { redirectOnError: true, errorTargetState: 'previewForm' }, formId, ), useTemplate: (formId, overrideParams) => generateService( ExamplesService.useTemplate, + null, + null, { redirectOnError: true, errorTargetState: 'useTemplate' }, formId, overrideParams, @@ -163,6 +188,8 @@ function FormApi(FormErrorService, FormFields) { transferOwner: (formId, newOwner) => generateService( UpdateFormService.transferOwner, + null, + injectMyInfo, { redirectOnError: false }, formId, newOwner, From 4b7a009defe56b2a5f8441a1f56619d212c30395 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 2 Jun 2021 17:57:32 +0800 Subject: [PATCH 28/36] test: edit tests to improve readability --- .../__tests__/AdminViewFormService.test.ts | 27 ++++++++----------- .../__tests__/CreateFormService.test.ts | 19 +++++-------- .../__tests__/ExamplesService.test.ts | 14 +++++----- .../__tests__/PublicFormService.test.ts | 7 +++-- .../__tests__/UpdateFormService.test.ts | 26 +++++++++--------- 5 files changed, 39 insertions(+), 54 deletions(-) diff --git a/src/public/services/__tests__/AdminViewFormService.test.ts b/src/public/services/__tests__/AdminViewFormService.test.ts index e337e9ab28..4a2e0e9d51 100644 --- a/src/public/services/__tests__/AdminViewFormService.test.ts +++ b/src/public/services/__tests__/AdminViewFormService.test.ts @@ -35,11 +35,9 @@ describe('AdminViewFormService', () => { admin: MOCK_USER, }, ] - + MockAxios.get.mockResolvedValueOnce({ data: expected }) // Act - const actualPromise = getDashboardView() - MockAxios.mockResponse({ data: expected }) - const actual = await actualPromise + const actual = await getDashboardView() // Assert expect(actual).toEqual(expected) @@ -49,11 +47,10 @@ describe('AdminViewFormService', () => { it('should successfully return empty array if GET request succeeds and there are no forms', async () => { // Arrange const expected: FormMetaView[] = [] + MockAxios.get.mockResolvedValueOnce({ data: expected }) // Act - const actualPromise = getDashboardView() - MockAxios.mockResponse({ data: expected }) - const actual = await actualPromise + const actual = await getDashboardView() // Assert expect(actual).toEqual(expected) @@ -63,10 +60,10 @@ describe('AdminViewFormService', () => { it('should reject with error message if GET request fails', async () => { // Arrange const expected = new Error('error') + MockAxios.get.mockRejectedValueOnce(expected) // Act const actualPromise = getDashboardView() - MockAxios.mockError(expected) //Assert await expect(actualPromise).rejects.toEqual(expected) @@ -78,11 +75,10 @@ describe('AdminViewFormService', () => { it('should return admin form if GET request succeeds', async () => { // Arrange const expected = _generateMockFullForm() + MockAxios.get.mockResolvedValueOnce({ data: expected }) // Act - const actualPromise = getAdminFormView(expected._id) - MockAxios.mockResponse({ data: expected }) - const actual = await actualPromise + const actual = await getAdminFormView(expected._id) // Assert expect(actual).toEqual(expected) @@ -93,10 +89,10 @@ describe('AdminViewFormService', () => { // Arrange const expected = new Error('error') const MOCK_FORM = _generateMockFullForm() + MockAxios.get.mockRejectedValueOnce(expected) // Act const actualPromise = getAdminFormView(MOCK_FORM._id) - MockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) @@ -113,11 +109,10 @@ describe('AdminViewFormService', () => { title: 'mock preview title', admin: MOCK_USER, } as unknown) as PublicForm + MockAxios.get.mockResolvedValueOnce({ data: expected }) // Act - const actualPromise = previewForm(MOCK_FORM_ID) - MockAxios.mockResponse({ data: expected }) - const actual = await actualPromise + const actual = await previewForm(MOCK_FORM_ID) // Assert expect(actual).toEqual(expected) @@ -130,10 +125,10 @@ describe('AdminViewFormService', () => { // Arrange const expected = new Error('error') const MOCK_FORM_ID = 'mock-form-id' + MockAxios.get.mockRejectedValueOnce(expected) // Act const actualPromise = previewForm(MOCK_FORM_ID) - MockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) diff --git a/src/public/services/__tests__/CreateFormService.test.ts b/src/public/services/__tests__/CreateFormService.test.ts index 313e54c969..5bbd1a2386 100644 --- a/src/public/services/__tests__/CreateFormService.test.ts +++ b/src/public/services/__tests__/CreateFormService.test.ts @@ -31,14 +31,10 @@ describe('CreateFormService', () => { } const MOCK_FORM_ID = expected._id const MOCK_DUPLICATE_FORM_BODY = _generateDuplicateFormBody() + MockAxios.post.mockResolvedValueOnce({ data: expected }) // Act - const actualPromise = duplicateForm( - MOCK_FORM_ID, - MOCK_DUPLICATE_FORM_BODY, - ) - MockAxios.mockResponse({ data: expected }) - const actual = await actualPromise + const actual = await duplicateForm(MOCK_FORM_ID, MOCK_DUPLICATE_FORM_BODY) // Assert expect(actual).toEqual(expected) @@ -53,13 +49,13 @@ describe('CreateFormService', () => { const expected = new Error('error') const MOCK_FORM_ID = new ObjectId().toHexString() const MOCK_DUPLICATE_FORM_BODY = _generateDuplicateFormBody() + MockAxios.post.mockRejectedValueOnce(expected) // Act const actualPromise = duplicateForm( MOCK_FORM_ID, MOCK_DUPLICATE_FORM_BODY, ) - MockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) @@ -78,10 +74,10 @@ describe('CreateFormService', () => { title: 'title', responseMode: ResponseMode.Email, } + MockAxios.post.mockResolvedValueOnce({ data: expected }) + // Act - const actualPromise = createForm(MOCK_FORM_PARAMS) - MockAxios.mockResponse({ data: expected }) - const actual = await actualPromise + const actual = await createForm(MOCK_FORM_PARAMS) // Assert expect(actual).toEqual(expected) @@ -97,10 +93,9 @@ describe('CreateFormService', () => { title: 'title', responseMode: ResponseMode.Email, } - + MockAxios.post.mockRejectedValueOnce(expected) // Act const actualPromise = createForm(MOCK_FORM_PARAMS) - MockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) diff --git a/src/public/services/__tests__/ExamplesService.test.ts b/src/public/services/__tests__/ExamplesService.test.ts index 3dca87c827..d26e3abbf6 100644 --- a/src/public/services/__tests__/ExamplesService.test.ts +++ b/src/public/services/__tests__/ExamplesService.test.ts @@ -107,14 +107,13 @@ describe('ExamplesService', () => { admin: MOCK_USER, } const MOCK_DUPLICATE_FORM_BODY = _generateDuplicateFormBody() + MockAxios.post.mockResolvedValueOnce({ data: expected }) // Act - const actualPromise = ExamplesService.useTemplate( + const actual = await ExamplesService.useTemplate( MOCK_FORM_ID, MOCK_DUPLICATE_FORM_BODY, ) - MockAxios.mockResponse({ data: expected }) - const actual = await actualPromise // Assert expect(actual).toEqual(expected) @@ -129,13 +128,13 @@ describe('ExamplesService', () => { const expected = new Error('error') const MOCK_FORM_ID = 'mock-form-id' const MOCK_DUPLICATE_FORM_BODY = _generateDuplicateFormBody() + MockAxios.post.mockRejectedValueOnce(expected) // Act const actualPromise = ExamplesService.useTemplate( MOCK_FORM_ID, MOCK_DUPLICATE_FORM_BODY, ) - MockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) @@ -158,11 +157,10 @@ describe('ExamplesService', () => { title: 'mock preview title', admin: MOCK_USER, } as unknown) as PublicForm + MockAxios.get.mockResolvedValueOnce({ data: expected }) // Act - const actualPromise = ExamplesService.queryTemplate(MOCK_FORM_ID) - MockAxios.mockResponse({ data: expected }) - const actual = await actualPromise + const actual = await ExamplesService.queryTemplate(MOCK_FORM_ID) // Assert expect(actual).toEqual(expected) @@ -175,10 +173,10 @@ describe('ExamplesService', () => { // Arrange const expected = new Error('error') const MOCK_FORM_ID = 'mock-form-id' + MockAxios.get.mockRejectedValueOnce(expected) // Act const actualPromise = ExamplesService.queryTemplate(MOCK_FORM_ID) - MockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) diff --git a/src/public/services/__tests__/PublicFormService.test.ts b/src/public/services/__tests__/PublicFormService.test.ts index 8f5bcd798e..6d24283735 100644 --- a/src/public/services/__tests__/PublicFormService.test.ts +++ b/src/public/services/__tests__/PublicFormService.test.ts @@ -167,11 +167,10 @@ describe('PublicFormService', () => { isIntranetUser: false, myInfoError: true, } + MockAxios.get.mockResolvedValueOnce({ data: expected }) // Act - const actualPromise = getPublicFormView(MOCK_FORM_ID) - MockAxios.mockResponse({ data: expected }) - const actual = await actualPromise + const actual = await getPublicFormView(MOCK_FORM_ID) // Assert expect(actual).toEqual(expected) @@ -184,10 +183,10 @@ describe('PublicFormService', () => { // Arrange const MOCK_FORM_ID = 'mock-form-id' const expected = new Error('error') + MockAxios.get.mockRejectedValueOnce(expected) // Act const actualPromise = getPublicFormView(MOCK_FORM_ID) - MockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) diff --git a/src/public/services/__tests__/UpdateFormService.test.ts b/src/public/services/__tests__/UpdateFormService.test.ts index 9c00552ad0..20bc1beed2 100644 --- a/src/public/services/__tests__/UpdateFormService.test.ts +++ b/src/public/services/__tests__/UpdateFormService.test.ts @@ -167,14 +167,13 @@ describe('UpdateFormService', () => { it('should successfully call delete endpoint', async () => { // Arrange const MOCK_FORM_ID = new ObjectId().toHexString() - - // Act - const actualPromise = deleteForm(MOCK_FORM_ID) - MockAxios.mockResponse({ + MockAxios.delete.mockResolvedValueOnce({ status: StatusCodes.OK, data: { message: 'Form has been archived' }, }) - await actualPromise + + // Act + await deleteForm(MOCK_FORM_ID) // Assert expect(MockAxios.delete).toHaveBeenCalledWith( @@ -186,10 +185,10 @@ describe('UpdateFormService', () => { // Arrange const expected = new Error('error') const MOCK_FORM_ID = new ObjectId().toHexString() + MockAxios.delete.mockRejectedValueOnce(expected) // Act const actualPromise = deleteForm(MOCK_FORM_ID) - MockAxios.mockError(expected) await expect(actualPromise).rejects.toEqual(expected) // Assert @@ -210,11 +209,10 @@ describe('UpdateFormService', () => { field: expected[0], }, } as FormUpdateParams + MockAxios.put.mockResolvedValueOnce({ data: expected }) // Act - const actualPromise = updateForm(MOCK_FORM_ID, update) - MockAxios.mockResponse({ data: expected }) - const actual = await actualPromise + const actual = await updateForm(MOCK_FORM_ID, update) // Assert expect(actual).toEqual(expected) @@ -233,10 +231,10 @@ describe('UpdateFormService', () => { field: {} as IYesNoFieldSchema, }, } as FormUpdateParams + MockAxios.put.mockRejectedValueOnce(expected) // Act const actualPromise = updateForm(MOCK_FORM_ID, update) - MockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) @@ -254,10 +252,10 @@ describe('UpdateFormService', () => { _id: MOCK_FORM_ID, } as IPopulatedForm const MOCK_NEW_OWNER = 'test@open.gov.sg' + MockAxios.post.mockResolvedValueOnce({ data: expected }) + // Act - const actualPromise = transferOwner(MOCK_FORM_ID, MOCK_NEW_OWNER) - MockAxios.mockResponse({ data: expected }) - const actual = await actualPromise + const actual = await transferOwner(MOCK_FORM_ID, MOCK_NEW_OWNER) // Assert expect(actual).toEqual(expected) @@ -274,10 +272,10 @@ describe('UpdateFormService', () => { const expected = new Error('error') const MOCK_FORM_ID = 'mock-form-id' const MOCK_NEW_OWNER = 'test@open.gov.sg' + MockAxios.post.mockRejectedValueOnce(expected) // Act const actualPromise = transferOwner(MOCK_FORM_ID, MOCK_NEW_OWNER) - MockAxios.mockError(expected) // Assert await expect(actualPromise).rejects.toEqual(expected) From 6f8293bf0b36f0438ed45858a665da5d1581ce43 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Fri, 4 Jun 2021 13:21:43 +0800 Subject: [PATCH 29/36] refactor: update imports --- src/public/services/CreateFormService.ts | 4 ++-- src/public/services/ExamplesService.ts | 5 ++--- src/public/services/PublicFormService.ts | 3 +-- src/public/services/UpdateFormService.ts | 3 +-- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/public/services/CreateFormService.ts b/src/public/services/CreateFormService.ts index 4fc5094a67..13482e85d5 100644 --- a/src/public/services/CreateFormService.ts +++ b/src/public/services/CreateFormService.ts @@ -1,7 +1,7 @@ import axios from 'axios' -import { DuplicateFormBody } from 'src/app/modules/form/admin-form/admin-form.types' -import { FormMetaView, IForm, IFormSchema } from 'src/types' +import { FormMetaView, IForm, IFormSchema } from '../../types' +import { DuplicateFormBody } from '../../types/api' // endpoints exported for testing export const ADMIN_FORM_ENDPOINT = '/api/v3/admin/forms' diff --git a/src/public/services/ExamplesService.ts b/src/public/services/ExamplesService.ts index 40299b85e7..e29d16998e 100644 --- a/src/public/services/ExamplesService.ts +++ b/src/public/services/ExamplesService.ts @@ -1,9 +1,8 @@ import axios from 'axios' -import { DuplicateFormBody } from 'src/app/modules/form/admin-form/admin-form.types' -import { FormMetaView, PublicForm } from 'src/types' - +import { FormMetaView, PublicForm } from '../../types' import { + DuplicateFormBody, ExampleFormsQueryDto, ExampleFormsResult, ExampleSingleFormResult, diff --git a/src/public/services/PublicFormService.ts b/src/public/services/PublicFormService.ts index 6b0a92cded..ea2a59f69a 100644 --- a/src/public/services/PublicFormService.ts +++ b/src/public/services/PublicFormService.ts @@ -1,10 +1,9 @@ import axios from 'axios' -import { PublicFormViewDto } from 'src/app/modules/form/public-form/public-form.types' - import { EmailSubmissionDto, EncryptSubmissionDto, + PublicFormViewDto, SubmissionResponseDto, } from '../../types/api' import { createEmailSubmissionFormData } from '../utils/submission' diff --git a/src/public/services/UpdateFormService.ts b/src/public/services/UpdateFormService.ts index dd141e6ca5..7fd6990fda 100644 --- a/src/public/services/UpdateFormService.ts +++ b/src/public/services/UpdateFormService.ts @@ -1,7 +1,5 @@ import axios from 'axios' -import { FormUpdateParams } from 'src/app/modules/form/admin-form/admin-form.types' - import { FormSettings, IPopulatedForm, LogicDto } from '../../types' import { EmailSubmissionDto, @@ -10,6 +8,7 @@ import { FieldCreateDto, FieldUpdateDto, FormFieldDto, + FormUpdateParams, FormViewDto, PermissionsUpdateDto, SettingsUpdateDto, From b2994ff2bd627dca3c4b833fb44e5b67dfcb849d Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Mon, 7 Jun 2021 12:40:03 +0800 Subject: [PATCH 30/36] test: fix imports in tests --- src/public/services/__tests__/CreateFormService.test.ts | 7 +++---- src/public/services/__tests__/ExamplesService.test.ts | 5 ++--- src/public/services/__tests__/UpdateFormService.test.ts | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/public/services/__tests__/CreateFormService.test.ts b/src/public/services/__tests__/CreateFormService.test.ts index 5bbd1a2386..56a0380615 100644 --- a/src/public/services/__tests__/CreateFormService.test.ts +++ b/src/public/services/__tests__/CreateFormService.test.ts @@ -1,10 +1,9 @@ import { ObjectId } from 'bson' import MockAxios from 'jest-mock-axios' -import { DuplicateFormBody } from 'src/app/modules/form/admin-form/admin-form.types' -import { IPopulatedUser, IYesNoFieldSchema } from 'src/types' -import { ResponseMode } from 'src/types/form' - +import { IPopulatedUser, IYesNoFieldSchema } from '../../../types' +import { DuplicateFormBody } from '../../../types/api' +import { ResponseMode } from '../../../types/form' import { ADMIN_FORM_ENDPOINT, createForm, diff --git a/src/public/services/__tests__/ExamplesService.test.ts b/src/public/services/__tests__/ExamplesService.test.ts index d26e3abbf6..ad8134f596 100644 --- a/src/public/services/__tests__/ExamplesService.test.ts +++ b/src/public/services/__tests__/ExamplesService.test.ts @@ -1,8 +1,7 @@ import MockAxios from 'jest-mock-axios' -import { DuplicateFormBody } from 'src/app/modules/form/admin-form/admin-form.types' -import { IPopulatedUser, PublicForm, ResponseMode } from 'src/types' - +import { IPopulatedUser, PublicForm, ResponseMode } from '../../../types' +import { DuplicateFormBody } from '../../../types/api' import * as ExamplesService from '../ExamplesService' jest.mock('axios', () => MockAxios) diff --git a/src/public/services/__tests__/UpdateFormService.test.ts b/src/public/services/__tests__/UpdateFormService.test.ts index 20bc1beed2..dcca0e0334 100644 --- a/src/public/services/__tests__/UpdateFormService.test.ts +++ b/src/public/services/__tests__/UpdateFormService.test.ts @@ -3,14 +3,14 @@ import { ObjectId } from 'bson' import { StatusCodes } from 'http-status-codes' import MockAxios from 'jest-mock-axios' -import { FormUpdateParams } from 'src/app/modules/form/admin-form/admin-form.types' -import { BasicField, IPopulatedForm, IYesNoFieldSchema } from 'src/types' import { EmailSubmissionDto, EncryptSubmissionDto, SubmissionResponseDto, } from 'src/types/api' +import { BasicField, IPopulatedForm, IYesNoFieldSchema } from '../../../types' +import { FormUpdateParams } from '../../../types/api' import * as SubmissionUtil from '../../utils/submission' import { ADMIN_FORM_ENDPOINT, From 10eb342471c1fdd42d98a415efa56344af37a4c4 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Mon, 7 Jun 2021 12:44:49 +0800 Subject: [PATCH 31/36] style: fix eslint style in tests --- src/public/services/__tests__/PublicFormService.test.ts | 8 ++++++-- src/public/services/__tests__/UpdateFormService.test.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/public/services/__tests__/PublicFormService.test.ts b/src/public/services/__tests__/PublicFormService.test.ts index 6d24283735..ba00ed8b8c 100644 --- a/src/public/services/__tests__/PublicFormService.test.ts +++ b/src/public/services/__tests__/PublicFormService.test.ts @@ -55,7 +55,9 @@ describe('PublicFormService', () => { // Assert await expect(actual).resolves.toEqual(MOCK_RESPONSE) - expect(MockAxios.post).toHaveBeenCalledWith( + expect( + MockAxios.post, + ).toHaveBeenCalledWith( `/api/v3/forms/${MOCK_FORM_ID}/submissions/email`, expectedFormData, { params: { captchaResponse: mockCaptcha } }, @@ -130,7 +132,9 @@ describe('PublicFormService', () => { // Assert await expect(actual).resolves.toEqual(MOCK_RESPONSE) - expect(MockAxios.post).toHaveBeenCalledWith( + expect( + MockAxios.post, + ).toHaveBeenCalledWith( `/api/v3/forms/${MOCK_FORM_ID}/submissions/encrypt`, MOCK_CONTENT, { params: { captchaResponse: mockCaptcha } }, diff --git a/src/public/services/__tests__/UpdateFormService.test.ts b/src/public/services/__tests__/UpdateFormService.test.ts index dcca0e0334..83ddf3becc 100644 --- a/src/public/services/__tests__/UpdateFormService.test.ts +++ b/src/public/services/__tests__/UpdateFormService.test.ts @@ -61,7 +61,9 @@ describe('UpdateFormService', () => { // Assert await expect(actual).resolves.toEqual(MOCK_RESPONSE) - expect(MockAxios.post).toHaveBeenCalledWith( + expect( + MockAxios.post, + ).toHaveBeenCalledWith( `/api/v3/admin/forms/${MOCK_FORM_ID}/preview/submissions/email`, expectedFormData, { params: { captchaResponse: mockCaptcha } }, @@ -136,7 +138,9 @@ describe('UpdateFormService', () => { // Assert await expect(actual).resolves.toEqual(MOCK_RESPONSE) - expect(MockAxios.post).toHaveBeenCalledWith( + expect( + MockAxios.post, + ).toHaveBeenCalledWith( `/api/v3/admin/forms/${MOCK_FORM_ID}/preview/submissions/encrypt`, MOCK_CONTENT, { params: { captchaResponse: mockCaptcha } }, From 8d9b9a63dc3934db93e02a7d37a18d12852cb3ba Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Mon, 7 Jun 2021 15:51:14 +0800 Subject: [PATCH 32/36] style: fix prettier errors --- .../__tests__/AdminViewFormService.test.ts | 4 ++-- .../services/__tests__/ExamplesService.test.ts | 4 ++-- .../services/__tests__/PublicFormService.test.ts | 8 ++------ .../services/__tests__/UpdateFormService.test.ts | 16 ++++------------ 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/public/services/__tests__/AdminViewFormService.test.ts b/src/public/services/__tests__/AdminViewFormService.test.ts index 4a2e0e9d51..1390ee746c 100644 --- a/src/public/services/__tests__/AdminViewFormService.test.ts +++ b/src/public/services/__tests__/AdminViewFormService.test.ts @@ -104,11 +104,11 @@ describe('AdminViewFormService', () => { it('should return public form if GET request succeeds', async () => { // Arrange const MOCK_FORM_ID = 'mock-form-id' - const expected = ({ + const expected = { _id: MOCK_FORM_ID, title: 'mock preview title', admin: MOCK_USER, - } as unknown) as PublicForm + } as unknown as PublicForm MockAxios.get.mockResolvedValueOnce({ data: expected }) // Act diff --git a/src/public/services/__tests__/ExamplesService.test.ts b/src/public/services/__tests__/ExamplesService.test.ts index ad8134f596..8e08714753 100644 --- a/src/public/services/__tests__/ExamplesService.test.ts +++ b/src/public/services/__tests__/ExamplesService.test.ts @@ -151,11 +151,11 @@ describe('ExamplesService', () => { _id: 'mock-user-id', } as IPopulatedUser const MOCK_FORM_ID = 'mock-form-id' - const expected = ({ + const expected = { _id: MOCK_FORM_ID, title: 'mock preview title', admin: MOCK_USER, - } as unknown) as PublicForm + } as unknown as PublicForm MockAxios.get.mockResolvedValueOnce({ data: expected }) // Act diff --git a/src/public/services/__tests__/PublicFormService.test.ts b/src/public/services/__tests__/PublicFormService.test.ts index ba00ed8b8c..6d24283735 100644 --- a/src/public/services/__tests__/PublicFormService.test.ts +++ b/src/public/services/__tests__/PublicFormService.test.ts @@ -55,9 +55,7 @@ describe('PublicFormService', () => { // Assert await expect(actual).resolves.toEqual(MOCK_RESPONSE) - expect( - MockAxios.post, - ).toHaveBeenCalledWith( + expect(MockAxios.post).toHaveBeenCalledWith( `/api/v3/forms/${MOCK_FORM_ID}/submissions/email`, expectedFormData, { params: { captchaResponse: mockCaptcha } }, @@ -132,9 +130,7 @@ describe('PublicFormService', () => { // Assert await expect(actual).resolves.toEqual(MOCK_RESPONSE) - expect( - MockAxios.post, - ).toHaveBeenCalledWith( + expect(MockAxios.post).toHaveBeenCalledWith( `/api/v3/forms/${MOCK_FORM_ID}/submissions/encrypt`, MOCK_CONTENT, { params: { captchaResponse: mockCaptcha } }, diff --git a/src/public/services/__tests__/UpdateFormService.test.ts b/src/public/services/__tests__/UpdateFormService.test.ts index 83ddf3becc..2e32f8ef40 100644 --- a/src/public/services/__tests__/UpdateFormService.test.ts +++ b/src/public/services/__tests__/UpdateFormService.test.ts @@ -61,9 +61,7 @@ describe('UpdateFormService', () => { // Assert await expect(actual).resolves.toEqual(MOCK_RESPONSE) - expect( - MockAxios.post, - ).toHaveBeenCalledWith( + expect(MockAxios.post).toHaveBeenCalledWith( `/api/v3/admin/forms/${MOCK_FORM_ID}/preview/submissions/email`, expectedFormData, { params: { captchaResponse: mockCaptcha } }, @@ -138,9 +136,7 @@ describe('UpdateFormService', () => { // Assert await expect(actual).resolves.toEqual(MOCK_RESPONSE) - expect( - MockAxios.post, - ).toHaveBeenCalledWith( + expect(MockAxios.post).toHaveBeenCalledWith( `/api/v3/admin/forms/${MOCK_FORM_ID}/preview/submissions/encrypt`, MOCK_CONTENT, { params: { captchaResponse: mockCaptcha } }, @@ -263,9 +259,7 @@ describe('UpdateFormService', () => { // Assert expect(actual).toEqual(expected) - expect( - MockAxios.post, - ).toHaveBeenCalledWith( + expect(MockAxios.post).toHaveBeenCalledWith( `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/collaborators/transfer-owner`, { email: MOCK_NEW_OWNER }, ) @@ -283,9 +277,7 @@ describe('UpdateFormService', () => { // Assert await expect(actualPromise).rejects.toEqual(expected) - expect( - MockAxios.post, - ).toHaveBeenCalledWith( + expect(MockAxios.post).toHaveBeenCalledWith( `${ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/collaborators/transfer-owner`, { email: MOCK_NEW_OWNER }, ) From 256d882cd948df2bb2168d054ec8d46f916355a1 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Tue, 8 Jun 2021 11:36:12 +0800 Subject: [PATCH 33/36] fix: fix import statement in AdminSubmissionService --- src/public/services/AdminSubmissionsService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/services/AdminSubmissionsService.ts b/src/public/services/AdminSubmissionsService.ts index 2139651ab8..29ef413308 100644 --- a/src/public/services/AdminSubmissionsService.ts +++ b/src/public/services/AdminSubmissionsService.ts @@ -8,7 +8,7 @@ import { SubmissionResponseQueryDto, } from 'src/types/api' -import { ADMIN_FORM_ENDPOINT } from './AdminFormService' +import { ADMIN_FORM_ENDPOINT } from './UpdateFormService' /** * Counts the number of submissions for a given form From 85d7388acc07d993daa2e486921483951ca10959 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Tue, 8 Jun 2021 11:44:04 +0800 Subject: [PATCH 34/36] fix: fix import statement in test --- src/public/services/__tests__/AdminSubmissionsService.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/services/__tests__/AdminSubmissionsService.test.ts b/src/public/services/__tests__/AdminSubmissionsService.test.ts index 4647b848a7..cd98a7883c 100644 --- a/src/public/services/__tests__/AdminSubmissionsService.test.ts +++ b/src/public/services/__tests__/AdminSubmissionsService.test.ts @@ -3,13 +3,13 @@ import { mocked } from 'ts-jest/utils' import { SubmissionMetadataList } from 'src/types' -import { ADMIN_FORM_ENDPOINT } from '../AdminFormService' import { countFormSubmissions, getEncryptedResponse, getSubmissionMetadataById, getSubmissionsMetadataByPage, } from '../AdminSubmissionsService' +import { ADMIN_FORM_ENDPOINT } from '../UpdateFormService' jest.mock('axios') const MockAxios = mocked(axios, true) From 3bde847b32bcb1ff89fb3130bcaf0f939391919f Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 9 Jun 2021 11:30:55 +0800 Subject: [PATCH 35/36] fix: replace formApi.template with formApi.queryTemplate --- src/public/modules/forms/config/forms.client.routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/modules/forms/config/forms.client.routes.js b/src/public/modules/forms/config/forms.client.routes.js index c76a3aab98..220e2ece7f 100644 --- a/src/public/modules/forms/config/forms.client.routes.js +++ b/src/public/modules/forms/config/forms.client.routes.js @@ -58,7 +58,7 @@ angular.module('forms').config([ 'FormApi', '$transition$', function (FormApi, $transition$) { - return FormApi.template($transition$.params()).then( + return FormApi.queryTemplate($transition$.params().formId).then( (FormData) => { FormData.isTemplate = true return FormData From 5f640e393393190666a3de724825bba75439f343 Mon Sep 17 00:00:00 2001 From: chowyiyin Date: Wed, 9 Jun 2021 11:31:16 +0800 Subject: [PATCH 36/36] fix: fix incorrect endpoint for query and use of template --- src/public/services/ExamplesService.ts | 10 ++-------- src/public/services/__tests__/ExamplesService.test.ts | 8 ++++---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/public/services/ExamplesService.ts b/src/public/services/ExamplesService.ts index e29d16998e..46e26927c4 100644 --- a/src/public/services/ExamplesService.ts +++ b/src/public/services/ExamplesService.ts @@ -9,7 +9,6 @@ import { } from '../../types/api' export const EXAMPLES_ENDPOINT = '/examples' -export const ADMIN_FORM_ENDPOINT = '/api/v3/admin/forms' /** * Gets example forms that matches the specified parameters for listing @@ -52,10 +51,7 @@ export const useTemplate = async ( overrideParams: DuplicateFormBody, ): Promise => { return axios - .post( - `${ADMIN_FORM_ENDPOINT}/${formId}/adminform/copy`, - overrideParams, - ) + .post(`${formId}/adminform/copy`, overrideParams) .then(({ data }) => data) } @@ -68,8 +64,6 @@ export const queryTemplate = async ( formId: string, ): Promise<{ form: PublicForm }> => { return axios - .get<{ form: PublicForm }>( - `${ADMIN_FORM_ENDPOINT}/${formId}/adminform/template`, - ) + .get<{ form: PublicForm }>(`${formId}/adminform/template`) .then(({ data }) => data) } diff --git a/src/public/services/__tests__/ExamplesService.test.ts b/src/public/services/__tests__/ExamplesService.test.ts index 8e08714753..a88815cbe5 100644 --- a/src/public/services/__tests__/ExamplesService.test.ts +++ b/src/public/services/__tests__/ExamplesService.test.ts @@ -117,7 +117,7 @@ describe('ExamplesService', () => { // Assert expect(actual).toEqual(expected) expect(MockAxios.post).toHaveBeenCalledWith( - `${ExamplesService.ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/copy`, + `${MOCK_FORM_ID}/adminform/copy`, MOCK_DUPLICATE_FORM_BODY, ) }) @@ -138,7 +138,7 @@ describe('ExamplesService', () => { // Assert await expect(actualPromise).rejects.toEqual(expected) expect(MockAxios.post).toHaveBeenCalledWith( - `${ExamplesService.ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/copy`, + `${MOCK_FORM_ID}/adminform/copy`, MOCK_DUPLICATE_FORM_BODY, ) }) @@ -164,7 +164,7 @@ describe('ExamplesService', () => { // Assert expect(actual).toEqual(expected) expect(MockAxios.get).toHaveBeenCalledWith( - `${ExamplesService.ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/template`, + `${MOCK_FORM_ID}/adminform/template`, ) }) @@ -180,7 +180,7 @@ describe('ExamplesService', () => { // Assert await expect(actualPromise).rejects.toEqual(expected) expect(MockAxios.get).toHaveBeenCalledWith( - `${ExamplesService.ADMIN_FORM_ENDPOINT}/${MOCK_FORM_ID}/adminform/template`, + `${MOCK_FORM_ID}/adminform/template`, ) }) })