diff --git a/packages/admin/.snyk b/packages/admin/.snyk index 831fedb04..404501649 100644 --- a/packages/admin/.snyk +++ b/packages/admin/.snyk @@ -29,4 +29,4 @@ ignore: Dev dependency - upgrade requires migration to next 13 expires: 2023-10-09T14:10:46.319Z created: 2023-12-09T14:10:46.321Z -patch: {} +patch: {} \ No newline at end of file diff --git a/packages/applicant/.snyk b/packages/applicant/.snyk index 831fedb04..404501649 100644 --- a/packages/applicant/.snyk +++ b/packages/applicant/.snyk @@ -29,4 +29,4 @@ ignore: Dev dependency - upgrade requires migration to next 13 expires: 2023-10-09T14:10:46.319Z created: 2023-12-09T14:10:46.321Z -patch: {} +patch: {} \ No newline at end of file diff --git a/packages/applicant/src/pages/api/redirect-from-find.page.test.tsx b/packages/applicant/src/pages/api/redirect-from-find.page.test.tsx new file mode 100644 index 000000000..f884ebcba --- /dev/null +++ b/packages/applicant/src/pages/api/redirect-from-find.page.test.tsx @@ -0,0 +1,125 @@ +import { merge } from 'lodash'; +import { AdvertDto, getAdvertBySlug } from '../../services/GrantAdvertService'; +import { getJwtFromCookies } from '../../utils/jwt'; +import handler from './redirect-from-find.page'; + +// Mock the getAdvertBySlug function (you may need to adjust this based on your actual implementation) +jest.mock('../../services/GrantAdvertService'); +jest.mock('../../utils/jwt'); +const mockedRedirect = jest.fn(); +const mockedSetHeader = jest.fn(); +const mockedSend = jest.fn(); + +const req = (overrides: any = {}) => + merge({ + query: { + slug: 'slug', + }, + }); + +const res = (overrides: any = {}) => + merge( + { + redirect: mockedRedirect, + setHeader: mockedSetHeader, + send: mockedSend, + }, + overrides + ); + +describe('API Handler Tests', () => { + beforeEach(() => { + process.env.HOST = 'http://localhost'; + jest.resetAllMocks(); + }); + it('should redirect to /applications/ when advert is version 1 and have internal application', async () => { + const advertDTO: AdvertDto = { + id: '123', + version: 1, + grantApplicationId: 123, + isInternal: true, + grantSchemeId: 456, + externalSubmissionUrl: 'http://example.com', + }; + + (getAdvertBySlug as jest.Mock).mockResolvedValue(advertDTO); + (getJwtFromCookies as jest.Mock).mockReturnValue('testJwt'); + await handler(req(), res()); + + expect(mockedRedirect).toHaveBeenCalledWith( + 'http://localhost/applications/123' + ); + }); + it('should redirect to the external Submission Url when advert is version 1 and have internal application', async () => { + const advertDTO: AdvertDto = { + id: '123', + version: 1, + grantApplicationId: 123, + isInternal: false, + grantSchemeId: 456, + externalSubmissionUrl: 'http://example.com', + }; + + (getAdvertBySlug as jest.Mock).mockResolvedValue(advertDTO); + (getJwtFromCookies as jest.Mock).mockReturnValue('testJwt'); + await handler(req(), res()); + + expect(mockedRedirect).toHaveBeenCalledWith('http://example.com'); + }); + + it('should redirect to the new Mandatory Question journey start page when advert is version 2 and have internal application', async () => { + const advertDTO: AdvertDto = { + id: '123', + version: 2, + grantApplicationId: 123, + isInternal: true, + grantSchemeId: 456, + externalSubmissionUrl: 'http://example.com', + }; + + (getAdvertBySlug as jest.Mock).mockResolvedValue(advertDTO); + (getJwtFromCookies as jest.Mock).mockReturnValue('testJwt'); + await handler(req(), res()); + + expect(mockedRedirect).toHaveBeenCalledWith( + 'http://localhost/mandatory-questions/start?schemeId=456' + ); + }); + + it('should redirect to the new Mandatory Question journey start page when advert is version 2 and have external application', async () => { + const advertDTO: AdvertDto = { + id: '123', + version: 2, + grantApplicationId: 123, + isInternal: false, + grantSchemeId: 456, + externalSubmissionUrl: 'http://example.com', + }; + + (getAdvertBySlug as jest.Mock).mockResolvedValue(advertDTO); + (getJwtFromCookies as jest.Mock).mockReturnValue('testJwt'); + await handler(req(), res()); + + expect(mockedRedirect).toHaveBeenCalledWith( + 'http://localhost/mandatory-questions/start?schemeId=456' + ); + }); + it('should redirect to the service Error when there is an error in the call to the backend', async () => { + (getAdvertBySlug as jest.Mock).mockRejectedValue(new Error('error')); + (getJwtFromCookies as jest.Mock).mockReturnValue('testJwt'); + await handler(req(), res()); + const serviceErrorProps = { + errorInformation: 'There was an error in the service', + linkAttributes: { + href: '/dashboard', + linkText: 'Go back to your dashboard', + linkInformation: '', + }, + }; + expect(mockedRedirect).toHaveBeenCalledWith( + `http://localhost/service-error?serviceErrorProps=${JSON.stringify( + serviceErrorProps + )}` + ); + }); +}); diff --git a/packages/applicant/src/pages/api/redirect-from-find.page.tsx b/packages/applicant/src/pages/api/redirect-from-find.page.tsx new file mode 100644 index 000000000..cb46c2326 --- /dev/null +++ b/packages/applicant/src/pages/api/redirect-from-find.page.tsx @@ -0,0 +1,47 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import { getAdvertBySlug } from '../../services/GrantAdvertService'; +import { getJwtFromCookies } from '../../utils/jwt'; +import { routes } from '../../utils/routes'; +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const slug = req.query.slug as string; + try { + const { + externalSubmissionUrl, + version, + grantApplicationId, + isInternal, + grantSchemeId, + } = await getAdvertBySlug(getJwtFromCookies(req), slug); + + if (version === 1) { + const redirectUrl = isInternal + ? `${process.env.HOST}${routes.applications}/${grantApplicationId}` + : externalSubmissionUrl; + res.redirect(redirectUrl); + } + + if (version === 2) { + res.redirect( + `${process.env.HOST}${routes.mandatoryQuestions.startPage( + grantSchemeId.toString() + )}` + ); + } + } catch (e) { + const serviceErrorProps = { + errorInformation: 'There was an error in the service', + linkAttributes: { + href: routes.dashboard, + linkText: 'Go back to your dashboard', + linkInformation: '', + }, + }; + console.log(e); + res.redirect( + `${process.env.HOST}${routes.serviceError(serviceErrorProps)}` + ); + } +} diff --git a/packages/applicant/src/pages/mandatory-questions/start.page.tsx b/packages/applicant/src/pages/mandatory-questions/start.page.tsx new file mode 100644 index 000000000..35f366172 --- /dev/null +++ b/packages/applicant/src/pages/mandatory-questions/start.page.tsx @@ -0,0 +1,17 @@ +import Layout from '../../components/partials/Layout'; +import Meta from '../../components/partials/Meta'; +import { routes } from '../../utils/routes'; + +const FirstMandatoryQuestion = () => { + return ( + <> + + + + <>NEW PAGE + + + ); +}; + +export default FirstMandatoryQuestion; diff --git a/packages/applicant/src/services/GrantAdvertService.test.ts b/packages/applicant/src/services/GrantAdvertService.test.ts new file mode 100644 index 000000000..44b990720 --- /dev/null +++ b/packages/applicant/src/services/GrantAdvertService.test.ts @@ -0,0 +1,48 @@ +import axios from 'axios'; +import { AdvertDto, getAdvertBySlug } from './GrantAdvertService'; + +jest.mock('axios'); + +const BACKEND_HOST = process.env.BACKEND_HOST + '/grant-adverts'; +const advertDTO: AdvertDto = { + id: '123', + version: 2, + grantApplicationId: 123, + isInternal: true, + grantSchemeId: 456, + externalSubmissionUrl: 'http://example.com', +}; +describe('GrantAdvert Service', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('getGrantBeneficiary', () => { + it('Should call axios', async () => { + (axios.get as jest.Mock).mockResolvedValue({}); + + await getAdvertBySlug('testJwt', 'slug'); + + expect(axios.get as jest.Mock).toHaveBeenNthCalledWith( + 1, + `${BACKEND_HOST}?contentfulSlug=slug`, + { + headers: { + Accept: 'application/json', + Authorization: 'Bearer testJwt', + }, + } + ); + }); + + it('Should return a GrantBeneficiary', async () => { + (axios.get as jest.Mock).mockResolvedValue({ + data: advertDTO, + }); + + const response = await getAdvertBySlug('testJwt', 'slug'); + + expect(response).toStrictEqual(advertDTO); + }); + }); +}); diff --git a/packages/applicant/src/services/GrantAdvertService.tsx b/packages/applicant/src/services/GrantAdvertService.tsx new file mode 100644 index 000000000..48b3061c2 --- /dev/null +++ b/packages/applicant/src/services/GrantAdvertService.tsx @@ -0,0 +1,26 @@ +import axios from 'axios'; +import getConfig from 'next/config'; +import { axiosConfig } from '../utils/jwt'; + +const { serverRuntimeConfig } = getConfig(); +const BACKEND_HOST = serverRuntimeConfig.backendHost; + +export async function getAdvertBySlug( + jwt: string, + slug: string +): Promise { + const { data } = await axios.get( + `${BACKEND_HOST}/grant-adverts?contentfulSlug=${slug}`, + axiosConfig(jwt) + ); + return data; +} + +export interface AdvertDto { + id: string; + externalSubmissionUrl: string; + version: number; + grantApplicationId: number | null; + isInternal: boolean; + grantSchemeId: number; +} diff --git a/packages/applicant/src/utils/routes.tsx b/packages/applicant/src/utils/routes.tsx index a8b72e8f5..8fe563536 100644 --- a/packages/applicant/src/utils/routes.tsx +++ b/packages/applicant/src/utils/routes.tsx @@ -16,6 +16,10 @@ export const routes = { name: '/organisation/name', type: '/organisation/type', }, + mandatoryQuestions: { + startPage: (schemeId: string) => + `/mandatory-questions/start?schemeId=${schemeId}`, + }, applications: '/applications', submissions: { index: '/submissions', @@ -33,6 +37,8 @@ export const routes = { `/submissions/${grantSubmissionId}/submission-confirmation`, }, findAGrant: publicRuntimeConfig.FIND_A_GRANT_URL, + serviceError: (serviceErrorProps) => + `/service-error?serviceErrorProps=${JSON.stringify(serviceErrorProps)}`, api: { submissions: { section: (grantSubmissionId: string, sectionId: string) =>