From 6f01382c4f6460a723257c21430be0cd538df52d Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 25 Nov 2024 11:32:18 +0000 Subject: [PATCH 001/133] feat(dtfs2-6892): add api to upsert user --- trade-finance-manager-ui/server/api.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index de2f6f5a24..e0c7c05301 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -478,6 +478,15 @@ const getUser = async (userId, token) => { } }; +const upsertUserFromEntraUser = (entraUser, token) => { + return axios({ + method: 'put', + url: `${TFM_API_URL}/v1/users`, + headers: generateHeaders(token), + data: entraUser, + }); +}; + const createFacilityAmendment = async (facilityId, token) => { try { const isValidFacilityId = isValidMongoId(facilityId); @@ -1396,4 +1405,5 @@ module.exports = { deleteDealCancellation, submitDealCancellation, getFeeRecord, + upsertUserFromEntraUser, }; From 70c1db32337c3bd35d7c6ea18d26ac90c5f09fd1 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 25 Nov 2024 17:34:19 +0000 Subject: [PATCH 002/133] feat(dtfs2-6892): add handle redirect to entra id service --- .../server/services/entra-id.service.ts | 74 ++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/trade-finance-manager-ui/server/services/entra-id.service.ts b/trade-finance-manager-ui/server/services/entra-id.service.ts index 5f140f445e..c7afd50949 100644 --- a/trade-finance-manager-ui/server/services/entra-id.service.ts +++ b/trade-finance-manager-ui/server/services/entra-id.service.ts @@ -1,7 +1,9 @@ import { AuthorizationUrlRequest, ConfidentialClientApplication, Configuration as MsalAppConfig, CryptoProvider } from '@azure/msal-node'; -import { DecodedAuthCodeRequestState } from '../types/entra-id'; +import { EntraIdUser } from '@ukef/dtfs2-common'; +import { DecodedAuthCodeRequestState, EntraIdAuthCodeRedirectResponseBody } from '../types/entra-id'; import { EntraIdConfig } from '../configs/entra-id.config'; import { EntraIdApi } from '../third-party-apis/entra-id.api'; +import { DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA, ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA } from '../schemas'; type GetAuthCodeUrlParams = { successRedirect?: string; @@ -12,6 +14,25 @@ type GetAuthCodeUrlResponse = { authCodeUrlRequest: AuthorizationUrlRequest; }; +type HandleRedirectParams = { + authCodeResponse: EntraIdAuthCodeRedirectResponseBody; + originalAuthCodeUrlRequest?: AuthorizationUrlRequest; +}; + +type HandleRedirectResponse = { + entraIdUser: EntraIdUser; + successRedirect?: string; +}; + +type GetEntraIdUserByAuthCodeParams = { + authCodeResponse: EntraIdAuthCodeRedirectResponseBody; + originalAuthCodeUrlRequest: AuthorizationUrlRequest; +}; + +type GetEntraIdUserByAuthCodeResponse = { + entraIdUser: EntraIdUser; +}; + export class EntraIdService { private readonly msalAppConfig: MsalAppConfig; @@ -48,6 +69,33 @@ export class EntraIdService { return { authCodeUrl, authCodeUrlRequest }; } + public async handleRedirect({ authCodeResponse, originalAuthCodeUrlRequest }: HandleRedirectParams): Promise { + if (!originalAuthCodeUrlRequest) { + throw new Error('No auth code URL request found in session'); + } + + const { entraIdUser } = await this.getEntraIdUserByAuthCode({ + authCodeResponse, + originalAuthCodeUrlRequest, + }); + + const { successRedirect } = this.parseAuthRequestState(authCodeResponse.state); + + return { + entraIdUser, + successRedirect, + }; + } + + private parseAuthRequestState(encodedState: string): DecodedAuthCodeRequestState { + try { + return DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA.parse(JSON.parse(this.cryptoProvider.base64Decode(encodedState))); + } catch (error) { + console.error('Error parsing auth request state: %o', error); + throw error; + } + } + private async getAuthorityMetadata() { try { return await this.entraIdApi.getAuthorityMetadataUrl(); @@ -67,4 +115,28 @@ export class EntraIdService { return new ConfidentialClientApplication(this.msalAppConfig); } + + private async getEntraIdUserByAuthCode({ + authCodeResponse, + originalAuthCodeUrlRequest, + }: GetEntraIdUserByAuthCodeParams): Promise { + const msalApp = await this.getMsalAppInstance(); + + // The token request uses details from our original auth code request so + // that MSAL can validate that the state in our original request matches the + // state in the auth code response received via the redirect. This ensures + // that the originator of the request and the response received are the + // same, which is important for security reasons to protect against CSRF + // attacks. + // See https://datatracker.ietf.org/doc/html/rfc6819#section-3.6 for details + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { responseMode, ...rest } = originalAuthCodeUrlRequest; + const tokenRequest = { ...rest, code: authCodeResponse.code }; + + const { + account: { idTokenClaims }, + } = ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA.parse(await msalApp.acquireTokenByCode(tokenRequest, authCodeResponse)); + + return { entraIdUser: idTokenClaims }; + } } From 4dfd7ddc3912f2a8fff8f758b9897a176c44a8d6 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 27 Nov 2024 15:45:14 +0000 Subject: [PATCH 003/133] feat(dtfs2-6892): add user parital login data error --- libs/common/src/errors/index.ts | 1 + ...rtial-login-data-not-defined.error.test.ts | 62 +++++++++++++++++++ ...er-partial-login-data-not-defined.error.ts | 16 +++++ 3 files changed, 79 insertions(+) create mode 100644 libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts create mode 100644 libs/common/src/errors/user-partial-login-data-not-defined.error.ts diff --git a/libs/common/src/errors/index.ts b/libs/common/src/errors/index.ts index 605ea08b17..2f71aefd4c 100644 --- a/libs/common/src/errors/index.ts +++ b/libs/common/src/errors/index.ts @@ -18,3 +18,4 @@ export * from './user-session.error'; export * from './user-session-not-defined.error'; export * from './user-token-not-defined.error'; export * from './multiple-users-found.error'; +export * from './user-partial-login-data-not-defined.error'; diff --git a/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts b/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts new file mode 100644 index 0000000000..0ee90f8820 --- /dev/null +++ b/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts @@ -0,0 +1,62 @@ +import { HttpStatusCode } from 'axios'; +import { ApiError } from './api.error'; +import { UserSessionError } from './user-session.error'; +import { UserPartialLoginDataNotDefinedError } from './user-partial-login-data-not-defined.error'; + +describe('UserPartialLoginDataNotDefinedError', () => { + it('exposes the message the error was created with', () => { + // Act + const exception = new UserPartialLoginDataNotDefinedError(); + + // Assert + expect(exception.message).toEqual('Expected session.user to be defined'); + }); + + it('exposes the 401 (Unauthorised) status code', () => { + // Act + const exception = new UserPartialLoginDataNotDefinedError(); + + // Assert + expect(exception.status).toEqual(HttpStatusCode.Unauthorized); + }); + + it('exposes the INVALID_USER_SESSION code', () => { + // Act + const exception = new UserPartialLoginDataNotDefinedError(); + + // Assert + expect(exception.code).toEqual('INVALID_USER_SESSION'); + }); + + it('is an instance of UserPartialLoginDataNotDefinedError', () => { + // Act + const exception = new UserPartialLoginDataNotDefinedError(); + + // Assert + expect(exception).toBeInstanceOf(UserPartialLoginDataNotDefinedError); + }); + + it('is an instance of UserSessionError', () => { + // Act + const exception = new UserPartialLoginDataNotDefinedError(); + + // Assert + expect(exception).toBeInstanceOf(UserSessionError); + }); + + it('is an instance of ApiError', () => { + // Act + const exception = new UserPartialLoginDataNotDefinedError(); + + // Assert + expect(exception).toBeInstanceOf(ApiError); + }); + + it('exposes the name of the exception', () => { + // Act + const exception = new UserPartialLoginDataNotDefinedError(); + + // Assert + expect(exception.name).toEqual('UserPartialLoginDataNotDefinedError'); + }); +}); diff --git a/libs/common/src/errors/user-partial-login-data-not-defined.error.ts b/libs/common/src/errors/user-partial-login-data-not-defined.error.ts new file mode 100644 index 0000000000..a1dd4dfc5b --- /dev/null +++ b/libs/common/src/errors/user-partial-login-data-not-defined.error.ts @@ -0,0 +1,16 @@ +import { HttpStatusCode } from 'axios'; +import { UserSessionError } from './user-session.error'; + +/** + * Error to use when a partially logged in user's session does not contain the expected data + */ +export class UserPartialLoginDataNotDefinedError extends UserSessionError { + constructor() { + super({ + status: HttpStatusCode.Unauthorized, + message: 'Expected session.loginData to be defined', + }); + + this.name = this.constructor.name; + } +} From 51e6a20b9c5427522db9ed5c36074e5c0579f45a Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 27 Nov 2024 17:59:33 +0000 Subject: [PATCH 004/133] Revert "feat(dtfs2-6892): add api to upsert user" This reverts commit b8646d19cfe3fc7a7dbf68882b522af6e430dfa7. --- trade-finance-manager-ui/server/api.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index e0c7c05301..7f5127f029 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -478,15 +478,6 @@ const getUser = async (userId, token) => { } }; -const upsertUserFromEntraUser = (entraUser, token) => { - return axios({ - method: 'put', - url: `${TFM_API_URL}/v1/users`, - headers: generateHeaders(token), - data: entraUser, - }); -}; - const createFacilityAmendment = async (facilityId, token) => { try { const isValidFacilityId = isValidMongoId(facilityId); From eae68f8066259a15f44812a81eaf21c1a4a0829d Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 27 Nov 2024 18:00:00 +0000 Subject: [PATCH 005/133] Revert "feat(dtfs2-6892): add handle redirect to entra id service" This reverts commit df69ac7ad3f79ae21c27dfddfa453f0d113757af. --- .../server/services/entra-id.service.ts | 74 +------------------ 1 file changed, 1 insertion(+), 73 deletions(-) diff --git a/trade-finance-manager-ui/server/services/entra-id.service.ts b/trade-finance-manager-ui/server/services/entra-id.service.ts index c7afd50949..5f140f445e 100644 --- a/trade-finance-manager-ui/server/services/entra-id.service.ts +++ b/trade-finance-manager-ui/server/services/entra-id.service.ts @@ -1,9 +1,7 @@ import { AuthorizationUrlRequest, ConfidentialClientApplication, Configuration as MsalAppConfig, CryptoProvider } from '@azure/msal-node'; -import { EntraIdUser } from '@ukef/dtfs2-common'; -import { DecodedAuthCodeRequestState, EntraIdAuthCodeRedirectResponseBody } from '../types/entra-id'; +import { DecodedAuthCodeRequestState } from '../types/entra-id'; import { EntraIdConfig } from '../configs/entra-id.config'; import { EntraIdApi } from '../third-party-apis/entra-id.api'; -import { DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA, ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA } from '../schemas'; type GetAuthCodeUrlParams = { successRedirect?: string; @@ -14,25 +12,6 @@ type GetAuthCodeUrlResponse = { authCodeUrlRequest: AuthorizationUrlRequest; }; -type HandleRedirectParams = { - authCodeResponse: EntraIdAuthCodeRedirectResponseBody; - originalAuthCodeUrlRequest?: AuthorizationUrlRequest; -}; - -type HandleRedirectResponse = { - entraIdUser: EntraIdUser; - successRedirect?: string; -}; - -type GetEntraIdUserByAuthCodeParams = { - authCodeResponse: EntraIdAuthCodeRedirectResponseBody; - originalAuthCodeUrlRequest: AuthorizationUrlRequest; -}; - -type GetEntraIdUserByAuthCodeResponse = { - entraIdUser: EntraIdUser; -}; - export class EntraIdService { private readonly msalAppConfig: MsalAppConfig; @@ -69,33 +48,6 @@ export class EntraIdService { return { authCodeUrl, authCodeUrlRequest }; } - public async handleRedirect({ authCodeResponse, originalAuthCodeUrlRequest }: HandleRedirectParams): Promise { - if (!originalAuthCodeUrlRequest) { - throw new Error('No auth code URL request found in session'); - } - - const { entraIdUser } = await this.getEntraIdUserByAuthCode({ - authCodeResponse, - originalAuthCodeUrlRequest, - }); - - const { successRedirect } = this.parseAuthRequestState(authCodeResponse.state); - - return { - entraIdUser, - successRedirect, - }; - } - - private parseAuthRequestState(encodedState: string): DecodedAuthCodeRequestState { - try { - return DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA.parse(JSON.parse(this.cryptoProvider.base64Decode(encodedState))); - } catch (error) { - console.error('Error parsing auth request state: %o', error); - throw error; - } - } - private async getAuthorityMetadata() { try { return await this.entraIdApi.getAuthorityMetadataUrl(); @@ -115,28 +67,4 @@ export class EntraIdService { return new ConfidentialClientApplication(this.msalAppConfig); } - - private async getEntraIdUserByAuthCode({ - authCodeResponse, - originalAuthCodeUrlRequest, - }: GetEntraIdUserByAuthCodeParams): Promise { - const msalApp = await this.getMsalAppInstance(); - - // The token request uses details from our original auth code request so - // that MSAL can validate that the state in our original request matches the - // state in the auth code response received via the redirect. This ensures - // that the originator of the request and the response received are the - // same, which is important for security reasons to protect against CSRF - // attacks. - // See https://datatracker.ietf.org/doc/html/rfc6819#section-3.6 for details - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { responseMode, ...rest } = originalAuthCodeUrlRequest; - const tokenRequest = { ...rest, code: authCodeResponse.code }; - - const { - account: { idTokenClaims }, - } = ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA.parse(await msalApp.acquireTokenByCode(tokenRequest, authCodeResponse)); - - return { entraIdUser: idTokenClaims }; - } } From 9300c662aac24da7eabccf34f8db5f47b792e3bd Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 27 Nov 2024 18:19:16 +0000 Subject: [PATCH 006/133] feat(dtfs2-6892): add login service --- trade-finance-manager-ui/server/api.js | 25 ++++++ .../login.controller.get-login.test.ts | 76 +++++++++++++------ .../login/login-sso/login.controller.ts | 34 +++++---- .../login.service.get-auth-code-url.test.ts | 58 ++++++++++++++ .../server/services/login.service.ts | 11 +++ .../server/types/login/get-auth-code.ts | 10 +++ .../test-helpers/mocks/index.ts | 1 + .../mocks/login.service.mock.builder.ts | 18 +++++ 8 files changed, 195 insertions(+), 38 deletions(-) create mode 100644 trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts create mode 100644 trade-finance-manager-ui/server/services/login.service.ts create mode 100644 trade-finance-manager-ui/server/types/login/get-auth-code.ts create mode 100644 trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index 7f5127f029..f6d17432dc 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -402,6 +402,7 @@ const createActivity = async (dealId, activityUpdate, token) => { } }; +// TODO DTFS2-6892 - remove this function const login = async (username, password) => { try { const response = await axios({ @@ -420,6 +421,29 @@ const login = async (username, password) => { } }; +/** + * Gets the auth code URL for the SSO login process + * @param {import('./types/login/get-auth-code').GetAuthCodeUrlParams} getAuthCodeUrlParams + * @returns {Promise} + */ +const getAuthCodeUrl = async ({ successRedirect }) => { + try { + const response = await axios({ + method: 'get', + url: `${TFM_API_URL}/v1/sso/auth-code-url`, + headers: { + [HEADERS.CONTENT_TYPE.KEY]: HEADERS.CONTENT_TYPE.VALUES.JSON, + }, + params: { successRedirect }, + }); + + return response.data; + } catch (error) { + console.error('Unable to get auth code url %o', error?.response?.data); + throw error; + } +}; + const updateUserPassword = async (userId, update, token) => { try { const isValidUserId = isValidMongoId(userId); @@ -1353,6 +1377,7 @@ module.exports = { updateLeadUnderwriter, createActivity, login, + getAuthCodeUrl, getFacilities, createFeedback, updateAmendment, diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts index 5d63c68c37..e4127ebdea 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts @@ -2,8 +2,8 @@ import httpMocks from 'node-mocks-http'; import { resetAllWhenMocks, when } from 'jest-when'; import { aTfmSessionUser } from '../../../../test-helpers'; import { LoginController } from './login.controller'; -import { EntraIdService } from '../../../services/entra-id.service'; -import { EntraIdServiceMockBuilder } from '../../../../test-helpers/mocks'; +import { LoginService } from '../../../services/login.service'; +import { LoginServiceMockBuilder } from '../../../../test-helpers/mocks'; describe('controllers - login (sso)', () => { describe('getLogin', () => { @@ -11,18 +11,17 @@ describe('controllers - login (sso)', () => { const mockAuthCodeUrlRequest = `mock-auth-code-url-request`; let loginController: LoginController; - let entraIdService: EntraIdService; + let loginService: LoginService; const getAuthCodeUrlMock = jest.fn(); + const next = jest.fn(); beforeEach(() => { resetAllWhenMocks(); jest.resetAllMocks(); - entraIdService = new EntraIdServiceMockBuilder().with({ getAuthCodeUrl: getAuthCodeUrlMock }).build(); + loginService = new LoginServiceMockBuilder().with({ getAuthCodeUrl: getAuthCodeUrlMock }).build(); - loginController = new LoginController({ entraIdService }); - - mockSuccessfulGetAuthCodeUrl(); + loginController = new LoginController({ loginService }); }); describe('when there is a user session', () => { @@ -41,7 +40,7 @@ describe('controllers - login (sso)', () => { const { req, res } = getHttpMocks(); // Act - await loginController.getLogin(req, res); + await loginController.getLogin(req, res, next); // Assert expect(res._getRedirectUrl()).toEqual('/home'); @@ -49,28 +48,55 @@ describe('controllers - login (sso)', () => { }); describe('when there is no user session', () => { - it('redirects to login URL', async () => { - // Arrange - const { req, res } = httpMocks.createMocks({ session: {} }); + describe('when the getAuthCodeUrl api call is successful', () => { + beforeEach(() => { + mockSuccessfulGetAuthCodeUrl(); + }); - // Act - await loginController.getLogin(req, res); + it('redirects to login URL', async () => { + // Arrange + const { req, res } = httpMocks.createMocks({ session: {} }); - // Assert - expect(res._getRedirectUrl()).toEqual(mockAuthCodeUrl); + // Act + await loginController.getLogin(req, res, next); + + // Assert + expect(res._getRedirectUrl()).toEqual(mockAuthCodeUrl); + }); + + it('overrides session login data if present', async () => { + // Arrange + const { req, res } = httpMocks.createMocks({ + session: { loginData: { authCodeUrlRequest: 'an old auth code url request', aField: 'another field' } }, + }); + + req.session.loginData = { authCodeUrlRequest: 'old-auth-code-url-request' }; + + // Act + await loginController.getLogin(req, res, next); + + // Assert + expect(req.session.loginData).toEqual({ authCodeUrlRequest: mockAuthCodeUrlRequest }); + }); }); - it('overrides session login data if present', async () => { - // Arrange - const { req, res } = httpMocks.createMocks({ session: { loginData: { authCodeUrlRequest: 'an old auth code url request', aField: 'another field' } } }); + describe('when the getAuthCodeUrl api call is unsuccessful', () => { + beforeEach(() => { + mockFailedGetAuthCodeUrl(); + }); - req.session.loginData = { authCodeUrlRequest: 'old-auth-code-url-request' }; + it('calls next with error', async () => { + // Arrange + const { req, res } = httpMocks.createMocks({ session: {} }); + const error = new Error('getAuthCodeUrl error'); + getAuthCodeUrlMock.mockRejectedValueOnce(error); - // Act - await loginController.getLogin(req, res); + // Act + await loginController.getLogin(req, res, next); - // Assert - expect(req.session.loginData).toEqual({ authCodeUrlRequest: mockAuthCodeUrlRequest }); + // Assert + expect(next).toHaveBeenCalledWith(error); + }); }); }); @@ -79,5 +105,9 @@ describe('controllers - login (sso)', () => { .calledWith({ successRedirect: '/' }) .mockResolvedValueOnce({ authCodeUrl: mockAuthCodeUrl, authCodeUrlRequest: mockAuthCodeUrlRequest }); } + + function mockFailedGetAuthCodeUrl() { + when(getAuthCodeUrlMock).calledWith({ successRedirect: '/' }).mockRejectedValueOnce(new Error('getAuthCodeUrl error')); + } }); }); diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts index bc2efb7470..6a7c0c5897 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts @@ -1,26 +1,30 @@ -import { Request, Response } from 'express'; -import { EntraIdService } from '../../../services/entra-id.service'; +import { NextFunction, Request, Response } from 'express'; +import { LoginService } from '../../../services/login.service'; export class LoginController { - private readonly entraIdService: EntraIdService; + private readonly loginService: LoginService; - constructor({ entraIdService }: { entraIdService: EntraIdService }) { - this.entraIdService = entraIdService; + constructor({ loginService }: { loginService: LoginService }) { + this.loginService = loginService; } - public async getLogin(req: Request, res: Response) { - if (req.session.user) { - // User is already logged in. - return res.redirect('/home'); - } + public async getLogin(req: Request, res: Response, next: NextFunction) { + try { + if (req.session.user) { + // User is already logged in. + return res.redirect('/home'); + } - const { authCodeUrl, authCodeUrlRequest } = await this.entraIdService.getAuthCodeUrl({ successRedirect: '/' }); + const { authCodeUrl, authCodeUrlRequest } = await this.loginService.getAuthCodeUrl({ successRedirect: '/' }); - // As this is the user logging in, there should be no existing login data in the session. - // if there is, it should be cleared and set to the authCodeUrlRequest. - req.session.loginData = { authCodeUrlRequest }; + // As this is the user logging in, there should be no existing login data in the session. + // if there is, it should be cleared and set to the authCodeUrlRequest. + req.session.loginData = { authCodeUrlRequest }; - return res.redirect(authCodeUrl); + return res.redirect(authCodeUrl); + } catch (error) { + return next(error); + } } // TODO DTFS2-6892: Update this logout handling diff --git a/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts b/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts new file mode 100644 index 0000000000..06c4cbb721 --- /dev/null +++ b/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts @@ -0,0 +1,58 @@ +import { AuthorizationCodeRequest } from '@azure/msal-node'; +import { LoginService } from './login.service'; +import * as api from '../api'; +import { GetAuthCodeUrlResponse } from '../types/login/get-auth-code'; + +jest.mock('../api'); + +describe('login service', () => { + describe('getAuthCodeUrl', () => { + const getAuthCodeUrlSpy = jest.spyOn(api, 'getAuthCodeUrl'); + const loginService = new LoginService(); + const successRedirect = '/'; + + afterEach(() => { + getAuthCodeUrlSpy.mockReset(); + }); + + it('calls api.getAuthCodeUrl with the request', async () => { + // Act + await loginService.getAuthCodeUrl({ successRedirect }); + + // Assert + expect(getAuthCodeUrlSpy).toHaveBeenCalledWith({ successRedirect }); + }); + + describe('when the getAuthCodeUrl api call is successful', () => { + const mockGetAuthCodeResponse: GetAuthCodeUrlResponse = { + authCodeUrl: 'a-auth-code-url', + authCodeUrlRequest: {} as AuthorizationCodeRequest, + }; + + beforeEach(() => { + getAuthCodeUrlSpy.mockResolvedValueOnce(mockGetAuthCodeResponse); + }); + + it('returns the auth code url', async () => { + // Act + const result = await loginService.getAuthCodeUrl({ successRedirect }); + + // Assert + expect(result).toEqual(mockGetAuthCodeResponse); + }); + }); + + describe('when the getAuthCodeUrl api call is unsuccessful', () => { + const error = new Error('getAuthCodeUrl error'); + + beforeEach(() => { + getAuthCodeUrlSpy.mockRejectedValueOnce(error); + }); + + it('throws the error', async () => { + // Act & Assert + await expect(loginService.getAuthCodeUrl({ successRedirect })).rejects.toThrow(error); + }); + }); + }); +}); diff --git a/trade-finance-manager-ui/server/services/login.service.ts b/trade-finance-manager-ui/server/services/login.service.ts new file mode 100644 index 0000000000..c893c24e19 --- /dev/null +++ b/trade-finance-manager-ui/server/services/login.service.ts @@ -0,0 +1,11 @@ +import { GetAuthCodeUrlParams, GetAuthCodeUrlResponse } from '../types/login/get-auth-code'; +import * as api from '../api'; + +export class LoginService { + /** + * Gets the URL to redirect the user to in order to log in. + */ + public getAuthCodeUrl = async ({ successRedirect }: GetAuthCodeUrlParams): Promise => { + return api.getAuthCodeUrl({ successRedirect }); + }; +} diff --git a/trade-finance-manager-ui/server/types/login/get-auth-code.ts b/trade-finance-manager-ui/server/types/login/get-auth-code.ts new file mode 100644 index 0000000000..6eb64e6761 --- /dev/null +++ b/trade-finance-manager-ui/server/types/login/get-auth-code.ts @@ -0,0 +1,10 @@ +import { AuthorizationUrlRequest } from '@azure/msal-node'; + +export type GetAuthCodeUrlParams = { + successRedirect?: string; +}; + +export type GetAuthCodeUrlResponse = { + authCodeUrl: string; + authCodeUrlRequest: AuthorizationUrlRequest; +}; diff --git a/trade-finance-manager-ui/test-helpers/mocks/index.ts b/trade-finance-manager-ui/test-helpers/mocks/index.ts index 55cb5217ef..a4e951eef7 100644 --- a/trade-finance-manager-ui/test-helpers/mocks/index.ts +++ b/trade-finance-manager-ui/test-helpers/mocks/index.ts @@ -1,3 +1,4 @@ export * from './entra-id.api.mock.builder'; export * from './entra-id.config.mock.builder'; export * from './extra-id.service.mock.builder'; +export * from './login.service.mock.builder'; diff --git a/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts b/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts new file mode 100644 index 0000000000..1f7cdd4711 --- /dev/null +++ b/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts @@ -0,0 +1,18 @@ +import { AuthorizationCodeRequest } from '@azure/msal-node'; +import { LoginService } from '../../server/services/login.service'; +import { BaseMockBuilder } from './mock-builder.mock.builder'; + +export class LoginServiceMockBuilder extends BaseMockBuilder { + constructor() { + super({ + defaultInstance: { + getAuthCodeUrl: jest.fn(async () => { + return Promise.resolve({ + authCodeUrl: 'a-auth-code-url', + authCodeUrlRequest: {} as AuthorizationCodeRequest, + }); + }), + }, + }); + } +} From d95459389008f21c9ea5c117f9dbe4ced55b87da Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 27 Nov 2024 18:25:13 +0000 Subject: [PATCH 007/133] feat(dtfs2-6892): add login service --- .../login-sso/login.controller.get-logout.test.ts | 10 +++++----- .../server/routes/login/configs/login-sso.ts | 12 ++++-------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-logout.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-logout.test.ts index 95fe7671d4..575fd74b9b 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-logout.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-logout.test.ts @@ -1,16 +1,16 @@ import httpMocks from 'node-mocks-http'; import { LoginController } from './login.controller'; -import { EntraIdService } from '../../../services/entra-id.service'; -import { EntraIdServiceMockBuilder } from '../../../../test-helpers/mocks'; +import { LoginService } from '../../../services/login.service'; +import { LoginServiceMockBuilder } from '../../../../test-helpers/mocks'; describe('controllers - login (sso)', () => { describe('getLogout', () => { - let entraIdService: EntraIdService; + let loginService: LoginService; let loginController: LoginController; beforeEach(() => { - entraIdService = new EntraIdServiceMockBuilder().build(); - loginController = new LoginController({ entraIdService }); + loginService = new LoginServiceMockBuilder().build(); + loginController = new LoginController({ loginService }); }); it('redirects to /', () => { diff --git a/trade-finance-manager-ui/server/routes/login/configs/login-sso.ts b/trade-finance-manager-ui/server/routes/login/configs/login-sso.ts index 39d8c4980d..47d1c70a6e 100644 --- a/trade-finance-manager-ui/server/routes/login/configs/login-sso.ts +++ b/trade-finance-manager-ui/server/routes/login/configs/login-sso.ts @@ -1,18 +1,14 @@ import express from 'express'; import { LoginController } from '../../../controllers/login/login-sso/login.controller'; import { GetRouter } from '../../../types/get-router'; -import { EntraIdService } from '../../../services/entra-id.service'; -import { EntraIdConfig } from '../../../configs/entra-id.config'; -import { EntraIdApi } from '../../../third-party-apis/entra-id.api'; +import { LoginService } from '../../../services/login.service'; export const getLoginSsoRouter: GetRouter = () => { - const entraIdConfig = new EntraIdConfig(); - const entraIdApi = new EntraIdApi({ entraIdConfig }); - const entraIdService = new EntraIdService({ entraIdConfig, entraIdApi }); - const loginController = new LoginController({ entraIdService }); + const loginService = new LoginService(); + const loginController = new LoginController({ loginService }); const loginSsoRouter = express.Router(); // eslint-disable-next-line @typescript-eslint/no-misused-promises - loginSsoRouter.get('/', (req, res) => loginController.getLogin(req, res)); + loginSsoRouter.get('/', (req, res, next) => loginController.getLogin(req, res, next)); return loginSsoRouter; }; From 35fc108c28e75998a2f55dc3c20351f7a990761b Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 27 Nov 2024 18:27:07 +0000 Subject: [PATCH 008/133] feat(dtfs2-6892): add login service --- trade-finance-manager-ui/server/api.js | 1 - 1 file changed, 1 deletion(-) diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index f6d17432dc..38aac8e453 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -1421,5 +1421,4 @@ module.exports = { deleteDealCancellation, submitDealCancellation, getFeeRecord, - upsertUserFromEntraUser, }; From f973209145e8c0213dc7f98ff23b3c3bdc3cb40e Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 27 Nov 2024 19:33:36 +0000 Subject: [PATCH 009/133] feat(dtfs2-6892): move entra to tfm api --- libs/common/package.json | 1 + ...ded-auth-code-request-state-schema.test.ts | 4 ++-- ...code-redirect-response-body-schema.test.ts | 4 ++-- ...ra-id-authentication-result-schema.test.ts | 4 ++-- .../src/schemas/tfm}/entra-id.schema.ts | 0 libs/common/src/schemas/tfm/index.ts | 3 ++- libs/common/src/test-helpers/index.ts | 1 + .../src/test-helpers/mock-builders/index.ts | 1 + .../mock-builder.mock.builder.ts | 0 .../common/src/types/tfm}/entra-id.ts | 6 +++++- libs/common/src/types/tfm/get-auth-code.ts | 16 ++++++++++++++ libs/common/src/types/tfm/index.ts | 2 ++ package-lock.json | 2 ++ trade-finance-manager-api/package.json | 1 + .../builders}/entra-id.api.mock.builder.ts | 4 ++-- .../builders}/entra-id.config.mock.builder.ts | 4 ++-- .../extra-id.service.mock.builder.ts | 4 ++-- .../src/v1/__mocks__/builders/index.ts | 3 +++ .../src/v1}/configs/entra-id.config.test.ts | 0 .../src/v1}/configs/entra-id.config.ts | 1 + .../src/v1/controllers/sso.controller.ts | 21 +++++++++++++++++++ ...entra-id.service.get-auth-code-url.test.ts | 2 +- .../src/v1}/services/entra-id.service.ts | 2 +- .../src/v1/sso/routes.ts | 15 +++++++++++++ ...-id.api.get-authority-metadata-url.test.ts | 4 ++-- .../src/v1}/third-party-apis/entra-id.api.ts | 0 trade-finance-manager-ui/server/api.js | 4 ++-- ...-auth.controller.post-sso-redirect.test.ts | 3 +-- .../unauthenticated-auth.controller.ts | 5 ++--- .../server/schemas/index.ts | 1 - .../login.service.get-auth-code-url.test.ts | 2 +- .../server/services/login.service.ts | 2 +- .../server/types/login/get-auth-code.ts | 10 --------- .../test-helpers/mocks/index.ts | 3 --- .../mocks/login.service.mock.builder.ts | 2 +- trade-finance-manager-ui/tsconfig.json | 17 ++++++++++++++- 36 files changed, 111 insertions(+), 43 deletions(-) rename {trade-finance-manager-ui/server/schemas => libs/common/src/schemas/tfm}/entra-id.schema.decoded-auth-code-request-state-schema.test.ts (92%) rename {trade-finance-manager-ui/server/schemas => libs/common/src/schemas/tfm}/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts (94%) rename {trade-finance-manager-ui/server/schemas => libs/common/src/schemas/tfm}/entra-id.schema.entra-id-authentication-result-schema.test.ts (94%) rename {trade-finance-manager-ui/server/schemas => libs/common/src/schemas/tfm}/entra-id.schema.ts (100%) create mode 100644 libs/common/src/test-helpers/mock-builders/index.ts rename {trade-finance-manager-ui/test-helpers/mocks => libs/common/src/test-helpers/mock-builders}/mock-builder.mock.builder.ts (100%) rename {trade-finance-manager-ui/server/types => libs/common/src/types/tfm}/entra-id.ts (64%) create mode 100644 libs/common/src/types/tfm/get-auth-code.ts rename {trade-finance-manager-ui/test-helpers/mocks => trade-finance-manager-api/src/v1/__mocks__/builders}/entra-id.api.mock.builder.ts (65%) rename {trade-finance-manager-ui/test-helpers/mocks => trade-finance-manager-api/src/v1/__mocks__/builders}/entra-id.config.mock.builder.ts (74%) rename {trade-finance-manager-ui/test-helpers/mocks => trade-finance-manager-api/src/v1/__mocks__/builders}/extra-id.service.mock.builder.ts (76%) create mode 100644 trade-finance-manager-api/src/v1/__mocks__/builders/index.ts rename {trade-finance-manager-ui/server => trade-finance-manager-api/src/v1}/configs/entra-id.config.test.ts (100%) rename {trade-finance-manager-ui/server => trade-finance-manager-api/src/v1}/configs/entra-id.config.ts (99%) create mode 100644 trade-finance-manager-api/src/v1/controllers/sso.controller.ts rename {trade-finance-manager-ui/server => trade-finance-manager-api/src/v1}/services/entra-id.service.get-auth-code-url.test.ts (97%) rename {trade-finance-manager-ui/server => trade-finance-manager-api/src/v1}/services/entra-id.service.ts (97%) create mode 100644 trade-finance-manager-api/src/v1/sso/routes.ts rename {trade-finance-manager-ui/server => trade-finance-manager-api/src/v1}/third-party-apis/entra-id.api.get-authority-metadata-url.test.ts (93%) rename {trade-finance-manager-ui/server => trade-finance-manager-api/src/v1}/third-party-apis/entra-id.api.ts (100%) delete mode 100644 trade-finance-manager-ui/server/schemas/index.ts delete mode 100644 trade-finance-manager-ui/server/types/login/get-auth-code.ts diff --git a/libs/common/package.json b/libs/common/package.json index c26b3a5294..7aa5714273 100644 --- a/libs/common/package.json +++ b/libs/common/package.json @@ -47,6 +47,7 @@ "unit-test-ff": "jest --coverage --verbose --config=unit.ff.jest.config.js --passWithNoTests" }, "dependencies": { + "@azure/msal-node": "^2.16.2", "axios": "^1.7.8", "big.js": "^6.2.2", "date-fns": "^3.3.1", diff --git a/trade-finance-manager-ui/server/schemas/entra-id.schema.decoded-auth-code-request-state-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts similarity index 92% rename from trade-finance-manager-ui/server/schemas/entra-id.schema.decoded-auth-code-request-state-schema.test.ts rename to libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts index c704a83498..979c306c00 100644 --- a/trade-finance-manager-ui/server/schemas/entra-id.schema.decoded-auth-code-request-state-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts @@ -1,5 +1,5 @@ -import { withSchemaTests } from '@ukef/dtfs2-common'; -import { DecodedAuthCodeRequestState } from '../types/entra-id'; +import { withSchemaTests } from '../../test-helpers'; +import { DecodedAuthCodeRequestState } from '../../types/tfm/entra-id'; import { DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA } from './entra-id.schema'; describe('DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA', () => { diff --git a/trade-finance-manager-ui/server/schemas/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts similarity index 94% rename from trade-finance-manager-ui/server/schemas/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts rename to libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts index 828cb78fa3..547720e6d2 100644 --- a/trade-finance-manager-ui/server/schemas/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts @@ -1,5 +1,5 @@ -import { withSchemaTests } from '@ukef/dtfs2-common'; -import { EntraIdAuthCodeRedirectResponseBody } from '../types/entra-id'; +import { withSchemaTests } from '../../test-helpers'; +import { EntraIdAuthCodeRedirectResponseBody } from '../../types/tfm/entra-id'; import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from './entra-id.schema'; describe('ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA', () => { diff --git a/trade-finance-manager-ui/server/schemas/entra-id.schema.entra-id-authentication-result-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts similarity index 94% rename from trade-finance-manager-ui/server/schemas/entra-id.schema.entra-id-authentication-result-schema.test.ts rename to libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts index 43d729b605..9f714159db 100644 --- a/trade-finance-manager-ui/server/schemas/entra-id.schema.entra-id-authentication-result-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts @@ -1,5 +1,5 @@ -import { anEntraIdUser, withEntraIdUserSchemaTests, withSchemaTests } from '@ukef/dtfs2-common'; -import { EntraIdAuthenticationResult } from '../types/entra-id'; +import { anEntraIdUser, withEntraIdUserSchemaTests, withSchemaTests } from '../../test-helpers'; +import { EntraIdAuthenticationResult } from '../../types/tfm/entra-id'; import { ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA } from './entra-id.schema'; describe('ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA', () => { diff --git a/trade-finance-manager-ui/server/schemas/entra-id.schema.ts b/libs/common/src/schemas/tfm/entra-id.schema.ts similarity index 100% rename from trade-finance-manager-ui/server/schemas/entra-id.schema.ts rename to libs/common/src/schemas/tfm/entra-id.schema.ts diff --git a/libs/common/src/schemas/tfm/index.ts b/libs/common/src/schemas/tfm/index.ts index 841b938451..81e059d639 100644 --- a/libs/common/src/schemas/tfm/index.ts +++ b/libs/common/src/schemas/tfm/index.ts @@ -1,6 +1,7 @@ export * from './entra-id-user.schema'; +export * from './entra-id-user-to-upsert-tfm-user-request.schema'; +export * from './entra-id.schema'; export * from './create-tfm-user-request.schema'; export * from './update-tfm-user-request.schema'; export * from './upsert-tfm-user-request.schema'; -export * from './entra-id-user-to-upsert-tfm-user-request.schema'; export * from './tfm-team.schema'; diff --git a/libs/common/src/test-helpers/index.ts b/libs/common/src/test-helpers/index.ts index 349ff7d9d4..35d95b4e3d 100644 --- a/libs/common/src/test-helpers/index.ts +++ b/libs/common/src/test-helpers/index.ts @@ -5,3 +5,4 @@ export * from './portal-session-user'; export * from './test-cases-backend'; export * from './schemas'; export * from './convert-milliseconds-to-seconds'; +export * from './mock-builders'; diff --git a/libs/common/src/test-helpers/mock-builders/index.ts b/libs/common/src/test-helpers/mock-builders/index.ts new file mode 100644 index 0000000000..3bf4521b97 --- /dev/null +++ b/libs/common/src/test-helpers/mock-builders/index.ts @@ -0,0 +1 @@ +export * from './mock-builder.mock.builder'; diff --git a/trade-finance-manager-ui/test-helpers/mocks/mock-builder.mock.builder.ts b/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts similarity index 100% rename from trade-finance-manager-ui/test-helpers/mocks/mock-builder.mock.builder.ts rename to libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts diff --git a/trade-finance-manager-ui/server/types/entra-id.ts b/libs/common/src/types/tfm/entra-id.ts similarity index 64% rename from trade-finance-manager-ui/server/types/entra-id.ts rename to libs/common/src/types/tfm/entra-id.ts index a472571ac4..f8f7f8fc89 100644 --- a/trade-finance-manager-ui/server/types/entra-id.ts +++ b/libs/common/src/types/tfm/entra-id.ts @@ -1,5 +1,9 @@ import { z } from 'zod'; -import { DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA, ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA, ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA } from '../schemas'; +import { + DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA, + ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA, + ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA, +} from '../../schemas/tfm/entra-id.schema'; export type DecodedAuthCodeRequestState = z.infer; diff --git a/libs/common/src/types/tfm/get-auth-code.ts b/libs/common/src/types/tfm/get-auth-code.ts new file mode 100644 index 0000000000..091458a281 --- /dev/null +++ b/libs/common/src/types/tfm/get-auth-code.ts @@ -0,0 +1,16 @@ +import { AuthorizationUrlRequest } from '@azure/msal-node'; +import { Response } from 'express'; +import { CustomExpressRequest } from '../express-custom-request'; + +export type GetAuthCodeUrlParams = { + successRedirect: string; +}; + +export type GetAuthCodeUrlResponse = { + authCodeUrl: string; + authCodeUrlRequest: AuthorizationUrlRequest; +}; + +export type GetAuthCodeUrlApiRequest = CustomExpressRequest<{ params: GetAuthCodeUrlParams }>; + +export type GetAuthCodeUrlApiResponse = Response; diff --git a/libs/common/src/types/tfm/index.ts b/libs/common/src/types/tfm/index.ts index 588700eac1..360c96af22 100644 --- a/libs/common/src/types/tfm/index.ts +++ b/libs/common/src/types/tfm/index.ts @@ -3,6 +3,8 @@ export * from './facility-stage'; export * from './team-id'; export * from './team'; export * from './deal-cancellation-status'; +export * from './get-auth-code'; +export * from './entra-id'; export * from './entra-id-user'; export * from './create-tfm-user-request'; export * from './update-tfm-user-request'; diff --git a/package-lock.json b/package-lock.json index ab333db968..b2666596ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -875,6 +875,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@azure/msal-node": "^2.16.2", "axios": "^1.7.8", "big.js": "^6.2.2", "date-fns": "^3.3.1", @@ -27548,6 +27549,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@azure/msal-node": "^2.16.2", "@azure/storage-file-share": "12.14.0", "@babel/plugin-transform-runtime": "7.23.6", "@babel/preset-env": "7.23.6", diff --git a/trade-finance-manager-api/package.json b/trade-finance-manager-api/package.json index de99fe262f..9c764f15b8 100644 --- a/trade-finance-manager-api/package.json +++ b/trade-finance-manager-api/package.json @@ -32,6 +32,7 @@ "unit-test-ff": "jest --coverage --verbose --config=unit.ff.jest.config.js --passWithNoTests" }, "dependencies": { + "@azure/msal-node": "^2.16.2", "@azure/storage-file-share": "12.14.0", "@babel/plugin-transform-runtime": "7.23.6", "@babel/preset-env": "7.23.6", diff --git a/trade-finance-manager-ui/test-helpers/mocks/entra-id.api.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.api.mock.builder.ts similarity index 65% rename from trade-finance-manager-ui/test-helpers/mocks/entra-id.api.mock.builder.ts rename to trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.api.mock.builder.ts index 4c5fbff34c..d058a8d100 100644 --- a/trade-finance-manager-ui/test-helpers/mocks/entra-id.api.mock.builder.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.api.mock.builder.ts @@ -1,5 +1,5 @@ -import { EntraIdApi } from '../../server/third-party-apis/entra-id.api'; -import { BaseMockBuilder } from './mock-builder.mock.builder'; +import { BaseMockBuilder } from '@ukef/dtfs2-common'; +import { EntraIdApi } from '../../third-party-apis/entra-id.api'; export class EntraIdApiMockBuilder extends BaseMockBuilder { constructor() { diff --git a/trade-finance-manager-ui/test-helpers/mocks/entra-id.config.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.config.mock.builder.ts similarity index 74% rename from trade-finance-manager-ui/test-helpers/mocks/entra-id.config.mock.builder.ts rename to trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.config.mock.builder.ts index af8cc333af..7e2deb6b94 100644 --- a/trade-finance-manager-ui/test-helpers/mocks/entra-id.config.mock.builder.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.config.mock.builder.ts @@ -1,5 +1,5 @@ -import { EntraIdConfig } from '../../server/configs/entra-id.config'; -import { BaseMockBuilder } from './mock-builder.mock.builder'; +import { BaseMockBuilder } from '@ukef/dtfs2-common'; +import { EntraIdConfig } from '../../configs/entra-id.config'; export class EntraIdConfigMockBuilder extends BaseMockBuilder { constructor() { diff --git a/trade-finance-manager-ui/test-helpers/mocks/extra-id.service.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts similarity index 76% rename from trade-finance-manager-ui/test-helpers/mocks/extra-id.service.mock.builder.ts rename to trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts index 6962bdd2fa..b639222fe6 100644 --- a/trade-finance-manager-ui/test-helpers/mocks/extra-id.service.mock.builder.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts @@ -1,6 +1,6 @@ import { AuthorizationCodeRequest } from '@azure/msal-node'; -import { EntraIdService } from '../../server/services/entra-id.service'; -import { BaseMockBuilder } from './mock-builder.mock.builder'; +import { BaseMockBuilder } from '@ukef/dtfs2-common'; +import { EntraIdService } from '../../services/entra-id.service'; export class EntraIdServiceMockBuilder extends BaseMockBuilder { constructor() { diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/index.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/index.ts new file mode 100644 index 0000000000..55cb5217ef --- /dev/null +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/index.ts @@ -0,0 +1,3 @@ +export * from './entra-id.api.mock.builder'; +export * from './entra-id.config.mock.builder'; +export * from './extra-id.service.mock.builder'; diff --git a/trade-finance-manager-ui/server/configs/entra-id.config.test.ts b/trade-finance-manager-api/src/v1/configs/entra-id.config.test.ts similarity index 100% rename from trade-finance-manager-ui/server/configs/entra-id.config.test.ts rename to trade-finance-manager-api/src/v1/configs/entra-id.config.test.ts diff --git a/trade-finance-manager-ui/server/configs/entra-id.config.ts b/trade-finance-manager-api/src/v1/configs/entra-id.config.ts similarity index 99% rename from trade-finance-manager-ui/server/configs/entra-id.config.ts rename to trade-finance-manager-api/src/v1/configs/entra-id.config.ts index ffd16090b6..03053001c1 100644 --- a/trade-finance-manager-ui/server/configs/entra-id.config.ts +++ b/trade-finance-manager-api/src/v1/configs/entra-id.config.ts @@ -3,6 +3,7 @@ import { z } from 'zod'; import dotenv from 'dotenv'; dotenv.config(); + export class EntraIdConfig { private static readonly entraIdEnvVarConfigSchema = z.object({ ENTRA_ID_CLIENT_ID: z.string(), diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts new file mode 100644 index 0000000000..c84619febf --- /dev/null +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts @@ -0,0 +1,21 @@ +import { NextFunction } from 'express'; +import { GetAuthCodeUrlApiRequest, GetAuthCodeUrlApiResponse } from '@ukef/dtfs2-common'; +import { EntraIdService } from '../services/entra-id.service'; + +export class SsoController { + private readonly entraIdService: EntraIdService; + + constructor({ entraIdService }: { entraIdService: EntraIdService }) { + this.entraIdService = entraIdService; + } + + async getAuthCodeUrl(req: GetAuthCodeUrlApiRequest, res: GetAuthCodeUrlApiResponse, next: NextFunction) { + try { + const { successRedirect } = req.params; + const getAuthCodeUrlResponse = await this.entraIdService.getAuthCodeUrl({ successRedirect }); + return res.json(getAuthCodeUrlResponse); + } catch (error) { + return next(error); + } + } +} diff --git a/trade-finance-manager-ui/server/services/entra-id.service.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts similarity index 97% rename from trade-finance-manager-ui/server/services/entra-id.service.get-auth-code-url.test.ts rename to trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts index aed8711a84..c3b05025f8 100644 --- a/trade-finance-manager-ui/server/services/entra-id.service.get-auth-code-url.test.ts +++ b/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts @@ -1,8 +1,8 @@ import { ConfidentialClientApplication, CryptoProvider } from '@azure/msal-node'; -import { EntraIdConfigMockBuilder, EntraIdApiMockBuilder } from '../../test-helpers/mocks'; import { EntraIdConfig } from '../configs/entra-id.config'; import { EntraIdService } from './entra-id.service'; import { EntraIdApi } from '../third-party-apis/entra-id.api'; +import { EntraIdApiMockBuilder, EntraIdConfigMockBuilder } from '../__mocks__/builders'; jest.mock('@azure/msal-node', () => { return { diff --git a/trade-finance-manager-ui/server/services/entra-id.service.ts b/trade-finance-manager-api/src/v1/services/entra-id.service.ts similarity index 97% rename from trade-finance-manager-ui/server/services/entra-id.service.ts rename to trade-finance-manager-api/src/v1/services/entra-id.service.ts index 5f140f445e..07912ee6a4 100644 --- a/trade-finance-manager-ui/server/services/entra-id.service.ts +++ b/trade-finance-manager-api/src/v1/services/entra-id.service.ts @@ -1,5 +1,5 @@ import { AuthorizationUrlRequest, ConfidentialClientApplication, Configuration as MsalAppConfig, CryptoProvider } from '@azure/msal-node'; -import { DecodedAuthCodeRequestState } from '../types/entra-id'; +import { DecodedAuthCodeRequestState } from '@ukef/dtfs2-common'; import { EntraIdConfig } from '../configs/entra-id.config'; import { EntraIdApi } from '../third-party-apis/entra-id.api'; diff --git a/trade-finance-manager-api/src/v1/sso/routes.ts b/trade-finance-manager-api/src/v1/sso/routes.ts new file mode 100644 index 0000000000..520c97a78e --- /dev/null +++ b/trade-finance-manager-api/src/v1/sso/routes.ts @@ -0,0 +1,15 @@ +import express from 'express'; +import { SsoController } from '../controllers/sso.controller'; +import { EntraIdService } from '../services/entra-id.service'; +import { EntraIdApi } from '../third-party-apis/entra-id.api'; +import { EntraIdConfig } from '../configs/entra-id.config'; + +export const ssoOpenRouter = express.Router(); + +const entraIdConfig = new EntraIdConfig(); +const entraIdApi = new EntraIdApi({ entraIdConfig }); +const entraIdService = new EntraIdService({ entraIdConfig, entraIdApi }); +const ssoController = new SsoController({ entraIdService }); + +// eslint-disable-next-line @typescript-eslint/no-misused-promises +ssoOpenRouter.route('/sso/auth-code-url').get(ssoController.getAuthCodeUrl.bind(ssoController)); diff --git a/trade-finance-manager-ui/server/third-party-apis/entra-id.api.get-authority-metadata-url.test.ts b/trade-finance-manager-api/src/v1/third-party-apis/entra-id.api.get-authority-metadata-url.test.ts similarity index 93% rename from trade-finance-manager-ui/server/third-party-apis/entra-id.api.get-authority-metadata-url.test.ts rename to trade-finance-manager-api/src/v1/third-party-apis/entra-id.api.get-authority-metadata-url.test.ts index 849676f3fa..a06361c343 100644 --- a/trade-finance-manager-ui/server/third-party-apis/entra-id.api.get-authority-metadata-url.test.ts +++ b/trade-finance-manager-api/src/v1/third-party-apis/entra-id.api.get-authority-metadata-url.test.ts @@ -1,8 +1,8 @@ import axios from 'axios'; import MockAdapter = require('axios-mock-adapter'); -import { EntraIdConfig } from '../configs/entra-id.config'; import { EntraIdApi } from './entra-id.api'; -import { EntraIdConfigMockBuilder } from '../../test-helpers/mocks'; +import { EntraIdConfig } from '../configs/entra-id.config'; +import { EntraIdConfigMockBuilder } from '../__mocks__/builders'; const mockAxios = new MockAdapter(axios); diff --git a/trade-finance-manager-ui/server/third-party-apis/entra-id.api.ts b/trade-finance-manager-api/src/v1/third-party-apis/entra-id.api.ts similarity index 100% rename from trade-finance-manager-ui/server/third-party-apis/entra-id.api.ts rename to trade-finance-manager-api/src/v1/third-party-apis/entra-id.api.ts diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index 38aac8e453..43611fc3b4 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -423,8 +423,8 @@ const login = async (username, password) => { /** * Gets the auth code URL for the SSO login process - * @param {import('./types/login/get-auth-code').GetAuthCodeUrlParams} getAuthCodeUrlParams - * @returns {Promise} + * @param {import('@ukef/dtfs2-common').GetAuthCodeUrlRequest} getAuthCodeUrlParams + * @returns {Promise} */ const getAuthCodeUrl = async ({ successRedirect }) => { try { diff --git a/trade-finance-manager-ui/server/controllers/auth/auth-sso/unauthenticated-auth.controller.post-sso-redirect.test.ts b/trade-finance-manager-ui/server/controllers/auth/auth-sso/unauthenticated-auth.controller.post-sso-redirect.test.ts index c136be35be..b191aa173f 100644 --- a/trade-finance-manager-ui/server/controllers/auth/auth-sso/unauthenticated-auth.controller.post-sso-redirect.test.ts +++ b/trade-finance-manager-ui/server/controllers/auth/auth-sso/unauthenticated-auth.controller.post-sso-redirect.test.ts @@ -1,10 +1,9 @@ import httpMocks, { MockResponse } from 'node-mocks-http'; import { resetAllWhenMocks } from 'jest-when'; import { isVerifiedPayload } from '@ukef/dtfs2-common/payload-verification'; -import { CustomExpressRequest, InvalidPayloadError } from '@ukef/dtfs2-common'; +import { CustomExpressRequest, EntraIdAuthCodeRedirectResponseBody, InvalidPayloadError } from '@ukef/dtfs2-common'; import { Response } from 'express'; import { UnauthenticatedAuthController } from './unauthenticated-auth.controller'; -import { EntraIdAuthCodeRedirectResponseBody } from '../../../types/entra-id'; jest.mock('@ukef/dtfs2-common/payload-verification', () => ({ isVerifiedPayload: jest.fn(), diff --git a/trade-finance-manager-ui/server/controllers/auth/auth-sso/unauthenticated-auth.controller.ts b/trade-finance-manager-ui/server/controllers/auth/auth-sso/unauthenticated-auth.controller.ts index 673fa7bf96..52098e9fc0 100644 --- a/trade-finance-manager-ui/server/controllers/auth/auth-sso/unauthenticated-auth.controller.ts +++ b/trade-finance-manager-ui/server/controllers/auth/auth-sso/unauthenticated-auth.controller.ts @@ -1,8 +1,7 @@ import { Response } from 'express'; -import { CustomExpressRequest, InvalidPayloadError } from '@ukef/dtfs2-common'; +import { CustomExpressRequest, EntraIdAuthCodeRedirectResponseBody, InvalidPayloadError } from '@ukef/dtfs2-common'; import { isVerifiedPayload } from '@ukef/dtfs2-common/payload-verification'; -import { EntraIdAuthCodeRedirectResponseBody } from '../../../types/entra-id'; -import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from '../../../schemas'; +import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from '@ukef/dtfs2-common/schemas'; export class UnauthenticatedAuthController { postSsoRedirect(req: CustomExpressRequest<{ reqBody: EntraIdAuthCodeRedirectResponseBody }>, res: Response) { diff --git a/trade-finance-manager-ui/server/schemas/index.ts b/trade-finance-manager-ui/server/schemas/index.ts deleted file mode 100644 index 47514b893e..0000000000 --- a/trade-finance-manager-ui/server/schemas/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './entra-id.schema'; diff --git a/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts b/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts index 06c4cbb721..7f1deb47d6 100644 --- a/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts +++ b/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts @@ -1,7 +1,7 @@ import { AuthorizationCodeRequest } from '@azure/msal-node'; +import { GetAuthCodeUrlResponse } from '@ukef/dtfs2-common'; import { LoginService } from './login.service'; import * as api from '../api'; -import { GetAuthCodeUrlResponse } from '../types/login/get-auth-code'; jest.mock('../api'); diff --git a/trade-finance-manager-ui/server/services/login.service.ts b/trade-finance-manager-ui/server/services/login.service.ts index c893c24e19..dc3ef1f71c 100644 --- a/trade-finance-manager-ui/server/services/login.service.ts +++ b/trade-finance-manager-ui/server/services/login.service.ts @@ -1,4 +1,4 @@ -import { GetAuthCodeUrlParams, GetAuthCodeUrlResponse } from '../types/login/get-auth-code'; +import { GetAuthCodeUrlParams, GetAuthCodeUrlResponse } from '@ukef/dtfs2-common'; import * as api from '../api'; export class LoginService { diff --git a/trade-finance-manager-ui/server/types/login/get-auth-code.ts b/trade-finance-manager-ui/server/types/login/get-auth-code.ts deleted file mode 100644 index 6eb64e6761..0000000000 --- a/trade-finance-manager-ui/server/types/login/get-auth-code.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { AuthorizationUrlRequest } from '@azure/msal-node'; - -export type GetAuthCodeUrlParams = { - successRedirect?: string; -}; - -export type GetAuthCodeUrlResponse = { - authCodeUrl: string; - authCodeUrlRequest: AuthorizationUrlRequest; -}; diff --git a/trade-finance-manager-ui/test-helpers/mocks/index.ts b/trade-finance-manager-ui/test-helpers/mocks/index.ts index a4e951eef7..a485d3815b 100644 --- a/trade-finance-manager-ui/test-helpers/mocks/index.ts +++ b/trade-finance-manager-ui/test-helpers/mocks/index.ts @@ -1,4 +1 @@ -export * from './entra-id.api.mock.builder'; -export * from './entra-id.config.mock.builder'; -export * from './extra-id.service.mock.builder'; export * from './login.service.mock.builder'; diff --git a/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts b/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts index 1f7cdd4711..5932b5e0bb 100644 --- a/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts +++ b/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts @@ -1,6 +1,6 @@ import { AuthorizationCodeRequest } from '@azure/msal-node'; +import { BaseMockBuilder } from '@ukef/dtfs2-common'; import { LoginService } from '../../server/services/login.service'; -import { BaseMockBuilder } from './mock-builder.mock.builder'; export class LoginServiceMockBuilder extends BaseMockBuilder { constructor() { diff --git a/trade-finance-manager-ui/tsconfig.json b/trade-finance-manager-ui/tsconfig.json index a2979a5b55..63ace412d8 100644 --- a/trade-finance-manager-ui/tsconfig.json +++ b/trade-finance-manager-ui/tsconfig.json @@ -100,6 +100,21 @@ "ts-node": { "files": true }, - "include": ["**/*.ts"], + "include": [ + "**/*.ts", + "../trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts", + "../trade-finance-manager-api/src/v1/services/entra-id.service.ts", + "../trade-finance-manager-api/src/v1/configs/entra-id.config.ts", + "../trade-finance-manager-api/src/v1/configs/entra-id.config.test.ts", + "../libs/common/src/types/tfm/entra-id.ts", + "../libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts", + "../libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts", + "../libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts", + "../libs/common/src/schemas/tfm/entra-id.schema.ts", + "../libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts", + "../trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.api.mock.builder.ts", + "../trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts", + "../trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.config.mock.builder.ts" + ], "exclude": ["./node_modules/*", "node_modules"] } From 019ef20ef3edcc92d59ce929278acb3a39bb445e Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 27 Nov 2024 19:36:42 +0000 Subject: [PATCH 010/133] feat(dtfs2-6892): move entra to tfm api --- trade-finance-manager-api/src/v1/routes.js | 4 ++++ trade-finance-manager-api/src/v1/sso/routes.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/trade-finance-manager-api/src/v1/routes.js b/trade-finance-manager-api/src/v1/routes.js index 99167fd74e..170d85b684 100644 --- a/trade-finance-manager-api/src/v1/routes.js +++ b/trade-finance-manager-api/src/v1/routes.js @@ -24,8 +24,12 @@ const checkApiKey = require('./middleware/headers/check-api-key'); const { teamsRoutes } = require('./teams/routes'); const { dealsOpenRouter, dealsAuthRouter } = require('./deals/routes'); const { tasksRouter } = require('./tasks/routes'); +const { ssoOpenRouter } = require('./sso/routes'); openRouter.use(checkApiKey); + +openRouter.use('/sso', ssoOpenRouter); + authRouter.use(passport.authenticate('jwt', { session: false })); authRouter.route('/api-docs').get(swaggerUi.setup(swaggerSpec, swaggerUiOptions)); diff --git a/trade-finance-manager-api/src/v1/sso/routes.ts b/trade-finance-manager-api/src/v1/sso/routes.ts index 520c97a78e..fc8d32bf20 100644 --- a/trade-finance-manager-api/src/v1/sso/routes.ts +++ b/trade-finance-manager-api/src/v1/sso/routes.ts @@ -12,4 +12,4 @@ const entraIdService = new EntraIdService({ entraIdConfig, entraIdApi }); const ssoController = new SsoController({ entraIdService }); // eslint-disable-next-line @typescript-eslint/no-misused-promises -ssoOpenRouter.route('/sso/auth-code-url').get(ssoController.getAuthCodeUrl.bind(ssoController)); +ssoOpenRouter.route('/auth-code-url').get(ssoController.getAuthCodeUrl.bind(ssoController)); From 9df20edf2b28020c24f977510f8493351ce92656 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 27 Nov 2024 19:37:59 +0000 Subject: [PATCH 011/133] feat(dtfs2-6892): move entra to tfm api --- trade-finance-manager-ui/tsconfig.json | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/trade-finance-manager-ui/tsconfig.json b/trade-finance-manager-ui/tsconfig.json index 63ace412d8..a2979a5b55 100644 --- a/trade-finance-manager-ui/tsconfig.json +++ b/trade-finance-manager-ui/tsconfig.json @@ -100,21 +100,6 @@ "ts-node": { "files": true }, - "include": [ - "**/*.ts", - "../trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts", - "../trade-finance-manager-api/src/v1/services/entra-id.service.ts", - "../trade-finance-manager-api/src/v1/configs/entra-id.config.ts", - "../trade-finance-manager-api/src/v1/configs/entra-id.config.test.ts", - "../libs/common/src/types/tfm/entra-id.ts", - "../libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts", - "../libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts", - "../libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts", - "../libs/common/src/schemas/tfm/entra-id.schema.ts", - "../libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts", - "../trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.api.mock.builder.ts", - "../trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts", - "../trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.config.mock.builder.ts" - ], + "include": ["**/*.ts"], "exclude": ["./node_modules/*", "node_modules"] } From 6aa48cc5bb2bdb43261252aee935df590a283495 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 29 Nov 2024 12:22:52 +0000 Subject: [PATCH 012/133] feat(dtfs2-6892): move entra to tfm api --- libs/common/src/schemas/tfm/entra-id.schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/schemas/tfm/entra-id.schema.ts b/libs/common/src/schemas/tfm/entra-id.schema.ts index 8b98d2f15d..2600db3502 100644 --- a/libs/common/src/schemas/tfm/entra-id.schema.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.ts @@ -1,5 +1,5 @@ -import { ENTRA_ID_USER_SCHEMA } from '@ukef/dtfs2-common/schemas'; import { z } from 'zod'; +import { ENTRA_ID_USER_SCHEMA } from './entra-id-user.schema'; export const DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA = z.object({ csrfToken: z.string(), From c036ea25a965203d886dd701c280577168ada5c0 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 29 Nov 2024 13:40:41 +0000 Subject: [PATCH 013/133] feat(dtfs2-6892): move entra to tfm api --- .../errors/user-partial-login-data-not-defined.error.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts b/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts index 0ee90f8820..3c7f7f28d1 100644 --- a/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts +++ b/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts @@ -9,7 +9,7 @@ describe('UserPartialLoginDataNotDefinedError', () => { const exception = new UserPartialLoginDataNotDefinedError(); // Assert - expect(exception.message).toEqual('Expected session.user to be defined'); + expect(exception.message).toEqual('Expected session.loginData to be defined'); }); it('exposes the 401 (Unauthorised) status code', () => { From d5d1bb42b1533a22c35d064eb81646bb04190bcd Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 3 Dec 2024 19:28:45 +0000 Subject: [PATCH 014/133] feat(dtfs2-6892): add object id schema for tfm sso --- .../src/schemas/audit-database-record.ts | 6 +- libs/common/src/schemas/index.ts | 2 +- libs/common/src/schemas/object-id.test.ts | 67 ++++++++++++++++--- libs/common/src/schemas/object-id.ts | 24 ++++++- 4 files changed, 85 insertions(+), 14 deletions(-) diff --git a/libs/common/src/schemas/audit-database-record.ts b/libs/common/src/schemas/audit-database-record.ts index 925e444e0d..2894884704 100644 --- a/libs/common/src/schemas/audit-database-record.ts +++ b/libs/common/src/schemas/audit-database-record.ts @@ -1,12 +1,12 @@ import z from 'zod'; import { ISO_DATE_TIME_STAMP } from './iso-date-time-stamp'; -import { OBJECT_ID } from './object-id'; +import { OBJECT_ID_OR_OBJECT_ID_STRING } from './object-id'; export const AUDIT_DATABASE_RECORD = z .object({ lastUpdatedAt: ISO_DATE_TIME_STAMP, - lastUpdatedByPortalUserId: OBJECT_ID.nullable(), - lastUpdatedByTfmUserId: OBJECT_ID.nullable(), + lastUpdatedByPortalUserId: OBJECT_ID_OR_OBJECT_ID_STRING.nullable(), + lastUpdatedByTfmUserId: OBJECT_ID_OR_OBJECT_ID_STRING.nullable(), lastUpdatedByIsSystem: z.boolean().nullable(), noUserLoggedIn: z.boolean().nullable(), }) diff --git a/libs/common/src/schemas/index.ts b/libs/common/src/schemas/index.ts index 4b4c497f83..36b221f5f6 100644 --- a/libs/common/src/schemas/index.ts +++ b/libs/common/src/schemas/index.ts @@ -1,6 +1,6 @@ export * as PORTAL_USER from './portal-user'; export * as ISO_DATE_TIME_STAMP from './iso-date-time-stamp'; -export { OBJECT_ID } from './object-id'; +export * from './object-id'; export * from './deal-cancellation'; export * from './tfm'; export * from './unix-timestamp.schema'; diff --git a/libs/common/src/schemas/object-id.test.ts b/libs/common/src/schemas/object-id.test.ts index 63182d35fc..f325e00d1c 100644 --- a/libs/common/src/schemas/object-id.test.ts +++ b/libs/common/src/schemas/object-id.test.ts @@ -1,23 +1,72 @@ import { ObjectId } from 'mongodb'; -import { OBJECT_ID } from './object-id'; +import { OBJECT_ID, OBJECT_ID_OR_OBJECT_ID_STRING, OBJECT_ID_STRING } from './object-id'; import { withSchemaTests } from '../test-helpers'; describe('OBJECT_ID', () => { withSchemaTests({ - successTestCases: getSuccessTestCases(), - failureTestCases: getFailureTestCases(), + successTestCases: getObjectIdSuccessTestCases(), + failureTestCases: getObjectIdSharedFailureTestCases(), schema: OBJECT_ID, }); + + it('should transform a valid string ObjectId to an ObjectId', () => { + const stringObjectId = new ObjectId().toString(); + + const result = OBJECT_ID.parse(stringObjectId); + + expect(result).toEqual(new ObjectId(stringObjectId)); + }); }); -function getSuccessTestCases() { - return [ - { description: 'a valid ObjectId', aTestCase: () => new ObjectId() }, - { description: 'a valid string ObjectId', aTestCase: () => '075bcd157dcb851180e02a7c' }, - ]; +describe('OBJECT_ID_STRING', () => { + withSchemaTests({ + successTestCases: getObjectIdStringSuccessTestCases(), + failureTestCases: getObjectIdSharedFailureTestCases(), + schema: OBJECT_ID_STRING, + }); + + it('should transform a valid ObjectId to a string', () => { + const objectId = new ObjectId(); + + const result = OBJECT_ID_STRING.parse(objectId); + + expect(result).toEqual(objectId.toString()); + }); +}); + +describe('OBJECT_ID_OR_OBJECT_ID_STRING', () => { + withSchemaTests({ + successTestCases: [...getObjectIdSuccessTestCases(), ...getObjectIdStringSuccessTestCases()], + failureTestCases: getObjectIdSharedFailureTestCases(), + schema: OBJECT_ID_OR_OBJECT_ID_STRING, + }); + + it('should not transform a valid ObjectId to a string', () => { + const objectId = new ObjectId(); + + const result = OBJECT_ID_OR_OBJECT_ID_STRING.parse(objectId); + + expect(result).toEqual(objectId); + }); + + it('should not transform a valid string ObjectId to an ObjectId', () => { + const stringObjectId = new ObjectId().toString(); + + const result = OBJECT_ID_OR_OBJECT_ID_STRING.parse(stringObjectId); + + expect(result).toEqual(stringObjectId); + }); +}); + +function getObjectIdSuccessTestCases() { + return [{ description: 'a valid ObjectId', aTestCase: () => new ObjectId() }]; +} + +function getObjectIdStringSuccessTestCases() { + return [{ description: 'a valid string ObjectId', aTestCase: () => '075bcd157dcb851180e02a7c' }]; } -function getFailureTestCases() { +function getObjectIdSharedFailureTestCases() { return [ { description: 'a string', aTestCase: () => 'string' }, { description: 'an object', aTestCase: () => ({ An: 'object' }) }, diff --git a/libs/common/src/schemas/object-id.ts b/libs/common/src/schemas/object-id.ts index 89d2ca9c0c..5e9d5b5e92 100644 --- a/libs/common/src/schemas/object-id.ts +++ b/libs/common/src/schemas/object-id.ts @@ -1,4 +1,26 @@ import { ObjectId } from 'mongodb'; import z from 'zod'; -export const OBJECT_ID = z.union([z.instanceof(ObjectId), z.string().refine((id) => ObjectId.isValid(id))]); +/** + * A zod schema that represents a valid ObjectId as an ObjectId object + * This schema also transforms any valid string into an ObjectId object + */ +export const OBJECT_ID = z.union([ + z.instanceof(ObjectId), + z + .string() + .refine((id) => ObjectId.isValid(id)) + .transform((id) => new ObjectId(id)), +]); + +/** + * A zod schema that represents a valid ObjectId as a string + * This schema also transforms any valid ObjectId object into a string + */ +export const OBJECT_ID_STRING = z.union([z.string().refine((id) => ObjectId.isValid(id)), z.instanceof(ObjectId).transform((id) => id.toString())]); + +/** + * A zod schema that represents a valid ObjectId as an ObjectId object or a string + * This schema does not do any transformation + */ +export const OBJECT_ID_OR_OBJECT_ID_STRING = z.union([z.instanceof(ObjectId), OBJECT_ID_STRING, z.string().refine((id) => ObjectId.isValid(id))]); From fb5a89cc9f2668960d37ecdfddb3bfabf75abfe2 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 3 Dec 2024 21:02:10 +0000 Subject: [PATCH 015/133] feat(dtfs2-6892): add tfm session user commonisation to dtfs-central-api --- .../mocks/test-users/mock-tfm-user.ts | 3 +- .../helpers.test.ts | 2 +- .../helpers.ts | 3 +- .../index.test.ts | 2 +- .../schemas/tfm-session-user.schema.test.ts | 221 ------------------ .../schemas/tfm-session-user.schema.ts | 15 -- .../validate-delete-payment-payload.ts | 4 +- .../validate-patch-payment-payload.ts | 4 +- ...es-to-an-existing-payment-group-payload.ts | 4 +- .../validate-post-keying-data-payload.ts | 4 +- .../validate-post-payment-payload.ts | 5 +- ...-remove-fees-from-payment-group-payload.ts | 4 +- ...alidate-put-keying-data-mark-as-payload.ts | 4 +- .../test-data/tfm-session-user.ts | 2 +- .../tfm/create-tfm-user-request.schema.ts | 24 +- libs/common/src/schemas/tfm/index.ts | 2 + .../schemas/tfm/tfm-session-user.schema.ts | 13 ++ .../common/src/schemas/tfm/tfm-user.schema.ts | 32 +++ .../tfm/update-tfm-user-request.schema.ts | 4 +- .../src/types/mongo-db-models/tfm-users.ts | 43 +--- libs/common/src/types/tfm/index.ts | 1 + libs/common/src/types/tfm/tfm-session-user.ts | 4 + 22 files changed, 89 insertions(+), 311 deletions(-) delete mode 100644 dtfs-central-api/src/v1/routes/middleware/payload-validation/schemas/tfm-session-user.schema.test.ts delete mode 100644 dtfs-central-api/src/v1/routes/middleware/payload-validation/schemas/tfm-session-user.schema.ts create mode 100644 libs/common/src/schemas/tfm/tfm-session-user.schema.ts create mode 100644 libs/common/src/schemas/tfm/tfm-user.schema.ts create mode 100644 libs/common/src/types/tfm/tfm-session-user.ts diff --git a/dtfs-central-api/api-tests/mocks/test-users/mock-tfm-user.ts b/dtfs-central-api/api-tests/mocks/test-users/mock-tfm-user.ts index 529249904c..115ec37d54 100644 --- a/dtfs-central-api/api-tests/mocks/test-users/mock-tfm-user.ts +++ b/dtfs-central-api/api-tests/mocks/test-users/mock-tfm-user.ts @@ -1,4 +1,5 @@ -import { TfmSessionUser } from '../../../src/types/tfm/tfm-session-user'; +import { TfmSessionUser } from '@ukef/dtfs2-common'; +import { ObjectId } from 'mongodb'; export const MOCK_TFM_USER: TfmSessionUser = { _id: '5ce819935e539c343f141ece', diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/helpers.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/helpers.test.ts index 15e830ff2b..cdd0a179bc 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/helpers.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/helpers.test.ts @@ -6,11 +6,11 @@ import { PaymentEntityMockBuilder, RECONCILIATION_IN_PROGRESS, REQUEST_PLATFORM_TYPE, + TfmSessionUser, UtilisationReportEntityMockBuilder, } from '@ukef/dtfs2-common'; import { addFeesToAnExistingPaymentGroup } from './helpers'; import { UtilisationReportStateMachine } from '../../../../services/state-machines/utilisation-report/utilisation-report.state-machine'; -import { TfmSessionUser } from '../../../../types/tfm/tfm-session-user'; import { aTfmSessionUser } from '../../../../../test-helpers/test-data/tfm-session-user'; import { executeWithSqlTransaction } from '../../../../helpers'; import { UTILISATION_REPORT_EVENT_TYPE } from '../../../../services/state-machines/utilisation-report/event/utilisation-report.event-type'; diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/helpers.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/helpers.ts index a8993625fd..ad4cbbdb10 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/helpers.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/helpers.ts @@ -1,6 +1,5 @@ -import { FeeRecordEntity, REQUEST_PLATFORM_TYPE, UtilisationReportEntity } from '@ukef/dtfs2-common'; +import { FeeRecordEntity, REQUEST_PLATFORM_TYPE, TfmSessionUser, UtilisationReportEntity } from '@ukef/dtfs2-common'; import { UTILISATION_REPORT_EVENT_TYPE } from '../../../../services/state-machines/utilisation-report/event/utilisation-report.event-type'; -import { TfmSessionUser } from '../../../../types/tfm/tfm-session-user'; import { executeWithSqlTransaction } from '../../../../helpers'; import { UtilisationReportStateMachine } from '../../../../services/state-machines/utilisation-report/utilisation-report.state-machine'; diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/index.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/index.test.ts index 96ef179cab..4fe59f7654 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/index.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/index.test.ts @@ -7,10 +7,10 @@ import { UtilisationReportEntityMockBuilder, TestApiError, RECONCILIATION_IN_PROGRESS, + TfmSessionUser, } from '@ukef/dtfs2-common'; import { HttpStatusCode } from 'axios'; import { PostRemoveFeesFromPaymentGroupRequest, postRemoveFeesFromPaymentGroup } from '.'; -import { TfmSessionUser } from '../../../../types/tfm/tfm-session-user'; import { aTfmSessionUser } from '../../../../../test-helpers'; import { removeFeesFromPaymentGroup } from './helpers'; import { PostRemoveFeesFromPaymentGroupPayload } from '../../../routes/middleware/payload-validation/validate-post-remove-fees-from-payment-group-payload'; diff --git a/dtfs-central-api/src/v1/routes/middleware/payload-validation/schemas/tfm-session-user.schema.test.ts b/dtfs-central-api/src/v1/routes/middleware/payload-validation/schemas/tfm-session-user.schema.test.ts deleted file mode 100644 index 6907e6c101..0000000000 --- a/dtfs-central-api/src/v1/routes/middleware/payload-validation/schemas/tfm-session-user.schema.test.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { ObjectId } from 'mongodb'; -import { TfmSessionUser } from '../../../../../types/tfm/tfm-session-user'; -import { aTfmSessionUser } from '../../../../../../test-helpers'; -import { TfmSessionUserSchema } from './tfm-session-user.schema'; - -describe('tfm-session-user.schema', () => { - describe('TfmSessionUserSchema', () => { - it("sets the 'success' property to true when the user 'username' is a string", () => { - // Arrange - const username = 'some-user'; - const user = { ...aTfmSessionUser(), username }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(true); - }); - - it("sets the 'success' property to false when the user 'username' is not a string", () => { - // Arrange - const username = 10; - const user = { ...aTfmSessionUser(), username }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(false); - }); - - it("sets the 'success' property to true when the user 'email' is a valid email string", () => { - // Arrange - const email = 'some-user@test.com'; - const user = { ...aTfmSessionUser(), email }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(true); - }); - - it("sets the 'success' property to false when the user 'email' is a string but not an email", () => { - // Arrange - const email = 'user'; - const user = { ...aTfmSessionUser(), email }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(false); - }); - - it("sets the 'success' property to false when the user 'email' is not a string", () => { - // Arrange - const email = 10; - const user = { ...aTfmSessionUser(), email }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(false); - }); - - it("sets the 'success' property to true when the user 'timezone' is a string", () => { - // Arrange - const timezone = 'London'; - const user = { ...aTfmSessionUser(), timezone }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(true); - }); - - it("sets the 'success' property to false when the user 'timezone' is not a string", () => { - // Arrange - const timezone = 10; - const user = { ...aTfmSessionUser(), timezone }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(false); - }); - - it("sets the 'success' property to true when the user 'firstName' is a string", () => { - // Arrange - const firstName = 'Test'; - const user = { ...aTfmSessionUser(), firstName }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(true); - }); - - it("sets the 'success' property to false when the user 'firstName' is not a string", () => { - // Arrange - const firstName = 10; - const user = { ...aTfmSessionUser(), firstName }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(false); - }); - - it("sets the 'success' property to true when the user 'lastName' is a string", () => { - // Arrange - const lastName = 'London'; - const user = { ...aTfmSessionUser(), lastName }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(true); - }); - - it("sets the 'success' property to false when the user 'lastName' is not a string", () => { - // Arrange - const lastName = 10; - const user = { ...aTfmSessionUser(), lastName }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(false); - }); - - it("sets the 'success' property to true when the user 'status' is a string", () => { - // Arrange - const status = 'active'; - const user = { ...aTfmSessionUser(), status }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(true); - }); - - it("sets the 'success' property to false when the user 'status' is not a string", () => { - // Arrange - const status = 10; - const user = { ...aTfmSessionUser(), status }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(false); - }); - - it("sets the 'success' property to true when the user 'lastLogin' is undefined", () => { - // Arrange - const lastLogin = undefined; - const user = { ...aTfmSessionUser(), lastLogin }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(true); - }); - - it("sets the 'success' property to true when the user 'lastLogin' is a number", () => { - // Arrange - const lastLogin = 10; - const user = { ...aTfmSessionUser(), lastLogin }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(true); - }); - - it("sets the 'success' property to false when the user 'lastLogin' is not a number", () => { - // Arrange - const lastLogin = '10'; - const user = { ...aTfmSessionUser(), lastLogin }; - - // Act - const { success } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(success).toEqual(false); - }); - - it("sets the 'data' property to the parsed user", () => { - // Arrange - const user: TfmSessionUser = { - username: 'some-user', - email: 'some-user@test.com', - teams: [], - status: 'active', - timezone: 'London', - firstName: 'Some', - lastName: 'User', - _id: new ObjectId().toString(), - lastLogin: 10, - }; - - // Act - const { data } = TfmSessionUserSchema.safeParse(user); - - // Assert - expect(data).toEqual(user); - }); - }); -}); diff --git a/dtfs-central-api/src/v1/routes/middleware/payload-validation/schemas/tfm-session-user.schema.ts b/dtfs-central-api/src/v1/routes/middleware/payload-validation/schemas/tfm-session-user.schema.ts deleted file mode 100644 index 66f1fbf108..0000000000 --- a/dtfs-central-api/src/v1/routes/middleware/payload-validation/schemas/tfm-session-user.schema.ts +++ /dev/null @@ -1,15 +0,0 @@ -import z from 'zod'; -import { TfmTeamSchema, UNIX_TIMESTAMP_MILLISECONDS_SCHEMA } from '@ukef/dtfs2-common/schemas'; -import { MongoObjectIdSchema } from './mongo-object-id.schema'; - -export const TfmSessionUserSchema = z.object({ - username: z.string(), - email: z.string().email(), - teams: z.array(TfmTeamSchema), - timezone: z.string(), - firstName: z.string(), - lastName: z.string(), - status: z.string(), - _id: MongoObjectIdSchema, - lastLogin: UNIX_TIMESTAMP_MILLISECONDS_SCHEMA.optional(), -}); diff --git a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-delete-payment-payload.ts b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-delete-payment-payload.ts index 7f3789343f..53626fb8b9 100644 --- a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-delete-payment-payload.ts +++ b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-delete-payment-payload.ts @@ -1,9 +1,9 @@ import z from 'zod'; import { createValidationMiddlewareForSchema } from '@ukef/dtfs2-common'; -import { TfmSessionUserSchema } from './schemas'; +import { TFM_SESSION_USER_SCHEMA } from '@ukef/dtfs2-common/schemas'; const DeletePaymentSchema = z.object({ - user: TfmSessionUserSchema, + user: TFM_SESSION_USER_SCHEMA, }); export type DeletePaymentPayload = z.infer; diff --git a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-patch-payment-payload.ts b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-patch-payment-payload.ts index 87d024cb1c..3dd720b285 100644 --- a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-patch-payment-payload.ts +++ b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-patch-payment-payload.ts @@ -1,12 +1,12 @@ import z from 'zod'; import { createValidationMiddlewareForSchema } from '@ukef/dtfs2-common'; -import { TfmSessionUserSchema } from './schemas'; +import { TFM_SESSION_USER_SCHEMA } from '@ukef/dtfs2-common/schemas'; const PatchPaymentSchema = z.object({ paymentAmount: z.number().gte(0), datePaymentReceived: z.coerce.date(), paymentReference: z.union([z.string(), z.null().transform(() => undefined)]), - user: TfmSessionUserSchema, + user: TFM_SESSION_USER_SCHEMA, }); export type PatchPaymentPayload = z.infer; diff --git a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-add-fees-to-an-existing-payment-group-payload.ts b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-add-fees-to-an-existing-payment-group-payload.ts index 9115840ea7..b2aebd8b39 100644 --- a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-add-fees-to-an-existing-payment-group-payload.ts +++ b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-add-fees-to-an-existing-payment-group-payload.ts @@ -1,11 +1,11 @@ import z from 'zod'; import { createValidationMiddlewareForSchema } from '@ukef/dtfs2-common'; -import { TfmSessionUserSchema } from './schemas'; +import { TFM_SESSION_USER_SCHEMA } from '@ukef/dtfs2-common/schemas'; const PostAddFeesToAnExistingPaymentGroupSchema = z.object({ feeRecordIds: z.array(z.number().gte(1)).min(1), paymentIds: z.array(z.number().gte(1)).min(1), - user: TfmSessionUserSchema, + user: TFM_SESSION_USER_SCHEMA, }); export type PostAddFeesToAnExistingPaymentGroupPayload = z.infer; diff --git a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-keying-data-payload.ts b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-keying-data-payload.ts index efed56001e..f159efa8aa 100644 --- a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-keying-data-payload.ts +++ b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-keying-data-payload.ts @@ -1,9 +1,9 @@ import z from 'zod'; import { createValidationMiddlewareForSchema } from '@ukef/dtfs2-common'; -import { TfmSessionUserSchema } from './schemas'; +import { TFM_SESSION_USER_SCHEMA } from '@ukef/dtfs2-common/schemas'; const PostKeyingDataSchema = z.object({ - user: TfmSessionUserSchema, + user: TFM_SESSION_USER_SCHEMA, }); export type PostKeyingDataPayload = z.infer; diff --git a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-payment-payload.ts b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-payment-payload.ts index 2f85c388df..2c79b04e24 100644 --- a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-payment-payload.ts +++ b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-payment-payload.ts @@ -1,6 +1,7 @@ import z from 'zod'; import { createValidationMiddlewareForSchema } from '@ukef/dtfs2-common'; -import { CurrencySchema, TfmSessionUserSchema } from './schemas'; +import { TFM_SESSION_USER_SCHEMA } from '@ukef/dtfs2-common/schemas'; +import { CurrencySchema } from './schemas'; const PostPaymentSchema = z.object({ feeRecordIds: z.array(z.number().gte(1)).min(1), @@ -8,7 +9,7 @@ const PostPaymentSchema = z.object({ paymentAmount: z.number().gte(0), datePaymentReceived: z.coerce.date(), paymentReference: z.string().optional(), - user: TfmSessionUserSchema, + user: TFM_SESSION_USER_SCHEMA, }); export type PostPaymentPayload = z.infer; diff --git a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-remove-fees-from-payment-group-payload.ts b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-remove-fees-from-payment-group-payload.ts index 8ddd92bffe..5bb0fa9505 100644 --- a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-remove-fees-from-payment-group-payload.ts +++ b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-remove-fees-from-payment-group-payload.ts @@ -1,10 +1,10 @@ import z from 'zod'; import { createValidationMiddlewareForSchema } from '@ukef/dtfs2-common'; -import { TfmSessionUserSchema } from './schemas'; +import { TFM_SESSION_USER_SCHEMA } from '@ukef/dtfs2-common/schemas'; const PostRemoveFeesFromPaymentGroupSchema = z.object({ selectedFeeRecordIds: z.array(z.number().gte(1)).min(1), - user: TfmSessionUserSchema, + user: TFM_SESSION_USER_SCHEMA, }); export type PostRemoveFeesFromPaymentGroupPayload = z.infer; diff --git a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-put-keying-data-mark-as-payload.ts b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-put-keying-data-mark-as-payload.ts index d50316df88..91c0eb5f95 100644 --- a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-put-keying-data-mark-as-payload.ts +++ b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-put-keying-data-mark-as-payload.ts @@ -1,10 +1,10 @@ import z from 'zod'; import { createValidationMiddlewareForSchema } from '@ukef/dtfs2-common'; -import { TfmSessionUserSchema } from './schemas'; +import { TFM_SESSION_USER_SCHEMA } from '@ukef/dtfs2-common/schemas'; const PutKeyingDataMarkAsSchema = z.object({ feeRecordIds: z.array(z.number().gte(1)), - user: TfmSessionUserSchema, + user: TFM_SESSION_USER_SCHEMA, }); export type PutKeyingDataMarkAsPayload = z.infer; diff --git a/dtfs-central-api/test-helpers/test-data/tfm-session-user.ts b/dtfs-central-api/test-helpers/test-data/tfm-session-user.ts index 59aa0742a0..c964826f34 100644 --- a/dtfs-central-api/test-helpers/test-data/tfm-session-user.ts +++ b/dtfs-central-api/test-helpers/test-data/tfm-session-user.ts @@ -1,5 +1,5 @@ +import { TfmSessionUser } from '@ukef/dtfs2-common'; import { ObjectId } from 'mongodb'; -import { TfmSessionUser } from '../../src/types/tfm/tfm-session-user'; export const aTfmSessionUser = (): TfmSessionUser => ({ username: 'test-user', diff --git a/libs/common/src/schemas/tfm/create-tfm-user-request.schema.ts b/libs/common/src/schemas/tfm/create-tfm-user-request.schema.ts index 9317b1505a..c5d12cc38a 100644 --- a/libs/common/src/schemas/tfm/create-tfm-user-request.schema.ts +++ b/libs/common/src/schemas/tfm/create-tfm-user-request.schema.ts @@ -1,20 +1,18 @@ -import z from 'zod'; -import { TfmTeamSchema } from './tfm-team.schema'; -import { UNIX_TIMESTAMP_MILLISECONDS_SCHEMA } from '../unix-timestamp.schema'; +import { TFM_USER_SCHEMA } from './tfm-user.schema'; +// TODO update docs, tests /** * Used during the SSO login process when a user is required to be created in TFM * It is used as a foundation to the upsert user request * @see UPSERT_TFM_USER_REQUEST_SCHEMA for the upsert user request schema this create user request schema influences * @see UPDATE_TFM_USER_REQUEST_SCHEMA for the update user schema this create user request schema influences */ -export const CREATE_TFM_USER_REQUEST_SCHEMA = z.object({ - azureOid: z.string(), - email: z.string(), - username: z.string(), - teams: z.array(TfmTeamSchema), - timezone: z.string(), - firstName: z.string(), - lastName: z.string(), - lastLogin: UNIX_TIMESTAMP_MILLISECONDS_SCHEMA, -}); +export const CREATE_TFM_USER_REQUEST_SCHEMA = TFM_USER_SCHEMA.pick({ + username: true, + email: true, + teams: true, + timezone: true, + firstName: true, + lastName: true, + azureOid: true, +}).required(); diff --git a/libs/common/src/schemas/tfm/index.ts b/libs/common/src/schemas/tfm/index.ts index 81e059d639..dff1410b89 100644 --- a/libs/common/src/schemas/tfm/index.ts +++ b/libs/common/src/schemas/tfm/index.ts @@ -5,3 +5,5 @@ export * from './create-tfm-user-request.schema'; export * from './update-tfm-user-request.schema'; export * from './upsert-tfm-user-request.schema'; export * from './tfm-team.schema'; +export * from './tfm-user.schema'; +export * from './tfm-session-user.schema'; diff --git a/libs/common/src/schemas/tfm/tfm-session-user.schema.ts b/libs/common/src/schemas/tfm/tfm-session-user.schema.ts new file mode 100644 index 0000000000..f4ad44be76 --- /dev/null +++ b/libs/common/src/schemas/tfm/tfm-session-user.schema.ts @@ -0,0 +1,13 @@ +import { TFM_USER_SCHEMA } from './tfm-user.schema'; + +export const TFM_SESSION_USER_SCHEMA = TFM_USER_SCHEMA.pick({ + _id: true, + username: true, + email: true, + teams: true, + timezone: true, + firstName: true, + lastName: true, + status: true, + lastLogin: true, +}); diff --git a/libs/common/src/schemas/tfm/tfm-user.schema.ts b/libs/common/src/schemas/tfm/tfm-user.schema.ts new file mode 100644 index 0000000000..a9d9e71d9f --- /dev/null +++ b/libs/common/src/schemas/tfm/tfm-user.schema.ts @@ -0,0 +1,32 @@ +import z from 'zod'; +import { TfmTeamSchema } from './tfm-team.schema'; +import { UNIX_TIMESTAMP_MILLISECONDS_SCHEMA } from '../unix-timestamp.schema'; +import { AUDIT_DATABASE_RECORD } from '../audit-database-record'; +import { OBJECT_ID } from '../object-id'; + +// TODO update docs, tests +const TFM_USER_NON_SSO_SPECIFIC_SCHEMA = z.object({ + salt: z.string(), + hash: z.string(), + loginFailureCount: z.number().optional(), +}); + +const TFM_USER_SSO_SPECIFIC_SCHEMA = z.object({ + azureOid: z.string(), +}); + +const BASE_TFM_USER_SCHEMA = z.object({ + _id: OBJECT_ID, + username: z.string(), + email: z.string(), + teams: z.array(TfmTeamSchema), + timezone: z.string(), + firstName: z.string(), + lastName: z.string(), + status: z.string(), + lastLogin: UNIX_TIMESTAMP_MILLISECONDS_SCHEMA.optional(), + sessionIdentifier: z.string().optional(), + auditRecord: AUDIT_DATABASE_RECORD.optional(), +}); + +export const TFM_USER_SCHEMA = BASE_TFM_USER_SCHEMA.merge(TFM_USER_NON_SSO_SPECIFIC_SCHEMA.partial()).merge(TFM_USER_SSO_SPECIFIC_SCHEMA.partial()); diff --git a/libs/common/src/schemas/tfm/update-tfm-user-request.schema.ts b/libs/common/src/schemas/tfm/update-tfm-user-request.schema.ts index da6a39a7ae..c0854b7ad4 100644 --- a/libs/common/src/schemas/tfm/update-tfm-user-request.schema.ts +++ b/libs/common/src/schemas/tfm/update-tfm-user-request.schema.ts @@ -1,4 +1,4 @@ -import { CREATE_TFM_USER_REQUEST_SCHEMA } from './create-tfm-user-request.schema'; +import { TFM_USER_SCHEMA } from './tfm-user.schema'; /** * Used during the SSO login process when a user is required to be updated in TFM @@ -6,4 +6,4 @@ import { CREATE_TFM_USER_REQUEST_SCHEMA } from './create-tfm-user-request.schema * It is set as partial to allow for partial updates of a user in TFM * @see CREATE_TFM_USER_REQUEST_SCHEMA for the create user request schema this update user request schema is based on */ -export const UPDATE_TFM_USER_REQUEST_SCHEMA = CREATE_TFM_USER_REQUEST_SCHEMA.partial(); +export const UPDATE_TFM_USER_REQUEST_SCHEMA = TFM_USER_SCHEMA.partial(); diff --git a/libs/common/src/types/mongo-db-models/tfm-users.ts b/libs/common/src/types/mongo-db-models/tfm-users.ts index cb482a4c30..6320f45ab3 100644 --- a/libs/common/src/types/mongo-db-models/tfm-users.ts +++ b/libs/common/src/types/mongo-db-models/tfm-users.ts @@ -1,41 +1,4 @@ -import { WithId } from 'mongodb'; -import { TeamId } from '../tfm/team-id'; -import { UnixTimestampMilliseconds } from '../date'; -import { AuditDatabaseRecord } from '../audit-database-record'; +import z from 'zod'; +import { TFM_USER_SCHEMA } from '../../schemas'; -/** - * These properties are only on non-SSO users (TFM users), - * and can be removed once SSO is permanently enabled. - * TODO: DTFS2-6892: Remove NonSSOUserProperties once SSO is permanently enabled - */ -type NonSsoUserProperties = { - salt: string; - hash: string; - loginFailureCount?: number; -}; - -/** - * These properties are only present on users who have logged in via SSO - * The azureOid is taken from the EntraId user from the SSO authority - */ - -type SsoUserProperties = { - // Azure Oid will not exist on users that have not logged in via SSO - azureOid?: string; -}; - -export type TfmUser = WithId< - { - username: string; - email: string; - teams: TeamId[]; - timezone: string; - firstName: string; - lastName: string; - status: string; - lastLogin?: UnixTimestampMilliseconds; - sessionIdentifier?: string; - auditRecord?: AuditDatabaseRecord; - } & Partial & - Partial ->; +export type TfmUser = z.infer; diff --git a/libs/common/src/types/tfm/index.ts b/libs/common/src/types/tfm/index.ts index a4eede05ba..683cd022c1 100644 --- a/libs/common/src/types/tfm/index.ts +++ b/libs/common/src/types/tfm/index.ts @@ -10,3 +10,4 @@ export * from './create-tfm-user-request'; export * from './update-tfm-user-request'; export * from './upsert-tfm-user-request'; export * from './mapped-deal'; +export * from './tfm-session-user'; diff --git a/libs/common/src/types/tfm/tfm-session-user.ts b/libs/common/src/types/tfm/tfm-session-user.ts new file mode 100644 index 0000000000..a8e3fc52f2 --- /dev/null +++ b/libs/common/src/types/tfm/tfm-session-user.ts @@ -0,0 +1,4 @@ +import { z } from 'zod'; +import { TFM_SESSION_USER_SCHEMA } from '../../schemas'; + +export type TfmSessionUser = z.infer; From ca22017bbe8350ba4b3fa269deffcab9ab729e5c Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 3 Dec 2024 21:03:03 +0000 Subject: [PATCH 016/133] feat(dtfs2-6892): add tfm session user commonisation to dtfs-central-api --- .../put-utilisation-report-status.controller/index.ts | 3 +-- .../v1/routes/middleware/payload-validation/schemas/index.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/put-utilisation-report-status.controller/index.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/put-utilisation-report-status.controller/index.ts index 3325c1c2d4..b59be7ebc0 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/put-utilisation-report-status.controller/index.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/put-utilisation-report-status.controller/index.ts @@ -1,9 +1,8 @@ import { Response } from 'express'; import { HttpStatusCode } from 'axios'; import { EntityManager } from 'typeorm'; -import { DbRequestSource, RECONCILIATION_COMPLETED, REQUEST_PLATFORM_TYPE, ReportWithStatus } from '@ukef/dtfs2-common'; +import { DbRequestSource, RECONCILIATION_COMPLETED, REQUEST_PLATFORM_TYPE, ReportWithStatus, TfmSessionUser } from '@ukef/dtfs2-common'; import { UTILISATION_REPORT_EVENT_TYPE } from '../../../../services/state-machines/utilisation-report/event/utilisation-report.event-type'; -import { TfmSessionUser } from '../../../../types/tfm/tfm-session-user'; import { CustomExpressRequest } from '../../../../types/custom-express-request'; import { ApiError, InvalidPayloadError } from '../../../../errors'; import { UtilisationReportStateMachine } from '../../../../services/state-machines/utilisation-report/utilisation-report.state-machine'; diff --git a/dtfs-central-api/src/v1/routes/middleware/payload-validation/schemas/index.ts b/dtfs-central-api/src/v1/routes/middleware/payload-validation/schemas/index.ts index 6828800f4d..1eeef3f676 100644 --- a/dtfs-central-api/src/v1/routes/middleware/payload-validation/schemas/index.ts +++ b/dtfs-central-api/src/v1/routes/middleware/payload-validation/schemas/index.ts @@ -1,5 +1,4 @@ export * from './mongo-object-id.schema'; -export * from './tfm-session-user.schema'; export * from './currency.schema'; export * from './amendment-status.schema'; export * from './audit-details.schema'; From e2eecd185b9bc2b584ff8d9fd09ffb1206745c63 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 3 Dec 2024 21:26:46 +0000 Subject: [PATCH 017/133] feat(dtfs2-6892): add tfm session user commonisation to tfm-api --- .../src/schemas/tfm/tfm-session-user.schema.ts | 4 ++-- .../src/types/express.d.ts | 2 +- .../src/types/tfm-session-user.ts | 5 ----- .../src/v1/__mocks__/mock-tfm-session-user.ts | 2 +- trade-finance-manager-api/src/v1/api.js | 18 +++++++++--------- .../user/helpers/mapUserData.helper.js | 3 ++- .../src/v1/controllers/user/passport.js | 3 ++- ....upsert-tfm-user-from-entra-id-user.test.ts | 3 +-- .../delete-payment.controller.ts | 3 +-- .../patch-payment.controller.ts | 3 +-- ...t-fees-to-an-existing-payment.controller.ts | 2 +- .../post-payment.controller.ts | 3 +-- ...post-remove-fees-from-payment.controller.ts | 2 +- .../put-keying-data-mark-as-done.controller.ts | 3 +-- ...put-keying-data-mark-as-to-do.controller.ts | 3 +-- ...ate-utilisation-report-status.controller.ts | 3 +-- .../index.test.ts | 3 +-- .../test-helpers/tfm-session-user.ts | 2 +- 18 files changed, 28 insertions(+), 39 deletions(-) delete mode 100644 trade-finance-manager-api/src/types/tfm-session-user.ts diff --git a/libs/common/src/schemas/tfm/tfm-session-user.schema.ts b/libs/common/src/schemas/tfm/tfm-session-user.schema.ts index f4ad44be76..1a574118b9 100644 --- a/libs/common/src/schemas/tfm/tfm-session-user.schema.ts +++ b/libs/common/src/schemas/tfm/tfm-session-user.schema.ts @@ -1,7 +1,7 @@ import { TFM_USER_SCHEMA } from './tfm-user.schema'; +import { OBJECT_ID_STRING } from '../object-id'; export const TFM_SESSION_USER_SCHEMA = TFM_USER_SCHEMA.pick({ - _id: true, username: true, email: true, teams: true, @@ -10,4 +10,4 @@ export const TFM_SESSION_USER_SCHEMA = TFM_USER_SCHEMA.pick({ lastName: true, status: true, lastLogin: true, -}); +}).extend({ _id: OBJECT_ID_STRING }); // _id is extended as a string as apposed to objectId due to existing implimentation of TFM session user diff --git a/trade-finance-manager-api/src/types/express.d.ts b/trade-finance-manager-api/src/types/express.d.ts index 404c2c7e9c..f3689672aa 100644 --- a/trade-finance-manager-api/src/types/express.d.ts +++ b/trade-finance-manager-api/src/types/express.d.ts @@ -1,4 +1,4 @@ -import { TfmSessionUser } from './tfm-session-user'; +import { TfmSessionUser } from '@ukef/dtfs2-common'; // This is needed so that we can access the user property on the Request object which is used for generating audit details declare global { diff --git a/trade-finance-manager-api/src/types/tfm-session-user.ts b/trade-finance-manager-api/src/types/tfm-session-user.ts deleted file mode 100644 index 0b26d1f7f1..0000000000 --- a/trade-finance-manager-api/src/types/tfm-session-user.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { TfmUser } from '@ukef/dtfs2-common'; - -export type TfmSessionUser = Pick & { - _id: string; -}; diff --git a/trade-finance-manager-api/src/v1/__mocks__/mock-tfm-session-user.ts b/trade-finance-manager-api/src/v1/__mocks__/mock-tfm-session-user.ts index caa2c5acfd..da5f23754f 100644 --- a/trade-finance-manager-api/src/v1/__mocks__/mock-tfm-session-user.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/mock-tfm-session-user.ts @@ -1,4 +1,4 @@ -import { TfmSessionUser } from '../../types/tfm-session-user'; +import { TfmSessionUser } from '@ukef/dtfs2-common'; export const MOCK_TFM_SESSION_USER: TfmSessionUser = { _id: '5e63c3a5e4232e4cd0274ac2', diff --git a/trade-finance-manager-api/src/v1/api.js b/trade-finance-manager-api/src/v1/api.js index bf3a58090a..cee63bdbd6 100644 --- a/trade-finance-manager-api/src/v1/api.js +++ b/trade-finance-manager-api/src/v1/api.js @@ -1409,7 +1409,7 @@ const getUtilisationReportById = async (id) => { * Sends a payload to DTFS central API to update * the status of one or more utilisation reports * @param {import('@ukef/dtfs2-common').ReportWithStatus[]} reportsWithStatus - * @param {import('../types/tfm-session-user').TfmSessionUser} user - The current user stored in the session + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The current user stored in the session * @returns {Promise<{ status: number }>} */ const updateUtilisationReportStatus = async (reportsWithStatus, user) => { @@ -1485,7 +1485,7 @@ const getUtilisationReportSummariesByBankIdAndYear = async (bankId, year) => { * Adds a new payment to the supplied fee records * @param {string} reportId - The report id * @param {number[]} feeRecordIds - The list of fee record ids to add the payment to - * @param {import('../types/tfm-session-user').TfmSessionUser} user - The user adding the payment + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The user adding the payment * @param {import('@ukef/dtfs2-common').Currency} paymentCurrency - The payment currency * @param {number} paymentAmount - The payment amount * @param {import('@ukef/dtfs2-common').IsoDateTimeStamp} datePaymentReceived - The date the payment was received @@ -1513,7 +1513,7 @@ const addPaymentToFeeRecords = async (reportId, feeRecordIds, user, paymentCurre * Generates keying data for the utilisation report * with the supplied id * @param {string} reportId - The report id - * @param {import('../types/tfm-session-user').TfmSessionUser} user - The session user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The session user * @returns {Promise} */ const generateKeyingData = async (reportId, user) => { @@ -1531,7 +1531,7 @@ const generateKeyingData = async (reportId, user) => { * Updates keying sheet fee records with supplied ids to DONE * @param {string} reportId - The report id * @param {number[]} feeRecordIds - The ids of the fee records to mark as DONE - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The session user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The session user * @returns {Promise<{}>} */ const markKeyingDataAsDone = async (reportId, feeRecordIds, user) => { @@ -1550,7 +1550,7 @@ const markKeyingDataAsDone = async (reportId, feeRecordIds, user) => { * Updates keying sheet fee records with supplied ids to TO_DO * @param {string} reportId - The report id * @param {number[]} feeRecordIds - The ids of the fee records to mark as TO_DO - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The session user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The session user * @returns {Promise<{}>} */ const markKeyingDataAsToDo = async (reportId, feeRecordIds, user) => { @@ -1599,7 +1599,7 @@ const getPaymentDetails = async (reportId, paymentId, includeFeeRecords) => { * Deletes the payment with the specified id * @param {string} reportId - The report id * @param {string} paymentId - The payment id - * @param {import('../types/tfm-session-user').TfmSessionUser} user - The session user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The session user * @returns {Promise} */ const deletePaymentById = async (reportId, paymentId, user) => { @@ -1618,7 +1618,7 @@ const deletePaymentById = async (reportId, paymentId, user) => { * @param {number} paymentAmount - The payment amount * @param {import('@ukef/dtfs2-common').IsoDateTimeStamp} datePaymentReceived - The date the payment was received * @param {string | null} paymentReference - The payment reference - * @param {import('../types/tfm-session-user').TfmSessionUser} user - The user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The user */ const editPayment = async (reportId, paymentId, paymentAmount, datePaymentReceived, paymentReference, user) => { await axios({ @@ -1639,7 +1639,7 @@ const editPayment = async (reportId, paymentId, paymentAmount, datePaymentReceiv * @param {string} reportId - The report id * @param {string} paymentId - The payment id * @param {number[]} selectedFeeRecordIds - The list of fee record ids to remove from the payment - * @param {import('../types/tfm-session-user').TfmSessionUser} user - The user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The user */ const removeFeesFromPayment = async (reportId, paymentId, selectedFeeRecordIds, user) => { await axios({ @@ -1658,7 +1658,7 @@ const removeFeesFromPayment = async (reportId, paymentId, selectedFeeRecordIds, * @param {string} reportId - The report id * @param {number[]} feeRecordIds - The list of fee record ids to add to the payment * @param {number[]} paymentIds - The list of payment ids for the fee records to be added to - * @param {import('../types/tfm-session-user').TfmSessionUser} user - The user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The user */ const addFeesToAnExistingPayment = async (reportId, feeRecordIds, paymentIds, user) => { const response = await axios({ diff --git a/trade-finance-manager-api/src/v1/controllers/user/helpers/mapUserData.helper.js b/trade-finance-manager-api/src/v1/controllers/user/helpers/mapUserData.helper.js index 505db0924d..8f46517600 100644 --- a/trade-finance-manager-api/src/v1/controllers/user/helpers/mapUserData.helper.js +++ b/trade-finance-manager-api/src/v1/controllers/user/helpers/mapUserData.helper.js @@ -1,8 +1,9 @@ +// TODO combine this with other mapper /** * Maps a user object from the database to a user object that can be returned to TFM UI * This strips out sensitive information not used by the frontend service * @param {import("@ukef/dtfs2-common").TfmUser} user - * @returns {import("../../../../types/tfm-session-user").TfmSessionUser} + * @returns {import("@ukef/dtfs2-common").TfmSessionUser} */ const mapUserData = (user) => ({ username: user.username, diff --git a/trade-finance-manager-api/src/v1/controllers/user/passport.js b/trade-finance-manager-api/src/v1/controllers/user/passport.js index ca44f66fac..5f01fb7984 100644 --- a/trade-finance-manager-api/src/v1/controllers/user/passport.js +++ b/trade-finance-manager-api/src/v1/controllers/user/passport.js @@ -14,9 +14,10 @@ const options = { algorithms: ['RS256'], }; +// TODO replace with zod parsing /** * @param {import('@ukef/dtfs2-common').TfmUser} user - The user - * @returns {import('../../../types/tfm-session-user').TfmSessionUser} The user with confidential data removed + * @returns {import('@ukef/dtfs2-common').TfmSessionUser} The user with confidential data removed */ const sanitize = (user) => ({ username: user.username, diff --git a/trade-finance-manager-api/src/v1/controllers/user/user.controller.upsert-tfm-user-from-entra-id-user.test.ts b/trade-finance-manager-api/src/v1/controllers/user/user.controller.upsert-tfm-user-from-entra-id-user.test.ts index 65c40cdbdd..0c55e357e2 100644 --- a/trade-finance-manager-api/src/v1/controllers/user/user.controller.upsert-tfm-user-from-entra-id-user.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/user/user.controller.upsert-tfm-user-from-entra-id-user.test.ts @@ -1,10 +1,9 @@ -import { anEntraIdUser, AuditDetails, EntraIdUser } from '@ukef/dtfs2-common'; +import { anEntraIdUser, AuditDetails, EntraIdUser, TfmSessionUser } from '@ukef/dtfs2-common'; import { generateSystemAuditDetails } from '@ukef/dtfs2-common/change-stream'; import { mapUserData } from './helpers/mapUserData.helper'; import { UpsertTfmUserFromEntraIdUserResponse, UserService } from '../../services/user.service'; import { upsertTfmUserFromEntraIdUser } from './user.controller'; import { userServiceMockResponses } from '../../../../test-helpers'; -import { TfmSessionUser } from '../../../types/tfm-session-user'; describe('user controller', () => { describe('upsertTfmUserFromEntraIdUser', () => { diff --git a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/delete-payment.controller.ts b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/delete-payment.controller.ts index 9b410bc102..9662fa09eb 100644 --- a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/delete-payment.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/delete-payment.controller.ts @@ -1,8 +1,7 @@ import { Response } from 'express'; import { HttpStatusCode, isAxiosError } from 'axios'; -import { CustomExpressRequest } from '@ukef/dtfs2-common'; +import { CustomExpressRequest, TfmSessionUser } from '@ukef/dtfs2-common'; import api from '../../api'; -import { TfmSessionUser } from '../../../types/tfm-session-user'; type DeletePaymentRequest = CustomExpressRequest<{ reqBody: { diff --git a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/patch-payment.controller.ts b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/patch-payment.controller.ts index 278e8331d6..05648af62a 100644 --- a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/patch-payment.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/patch-payment.controller.ts @@ -1,8 +1,7 @@ import { Response } from 'express'; import { HttpStatusCode, isAxiosError } from 'axios'; -import { IsoDateTimeStamp, CustomExpressRequest } from '@ukef/dtfs2-common'; +import { IsoDateTimeStamp, CustomExpressRequest, TfmSessionUser } from '@ukef/dtfs2-common'; import api from '../../api'; -import { TfmSessionUser } from '../../../types/tfm-session-user'; export type PatchPaymentRequest = CustomExpressRequest<{ reqBody: { diff --git a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-fees-to-an-existing-payment.controller.ts b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-fees-to-an-existing-payment.controller.ts index 50529528d2..938b411bab 100644 --- a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-fees-to-an-existing-payment.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-fees-to-an-existing-payment.controller.ts @@ -1,8 +1,8 @@ import { isAxiosError, HttpStatusCode } from 'axios'; import { Response } from 'express'; +import { TfmSessionUser } from '@ukef/dtfs2-common'; import api from '../../api'; import { CustomExpressRequest } from '../../../types/custom-express-request'; -import { TfmSessionUser } from '../../../types/tfm-session-user'; export type PostFeesToAnExistingPaymentRequestBody = { feeRecordIds: number[]; diff --git a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-payment.controller.ts b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-payment.controller.ts index d91619ced3..db41e79275 100644 --- a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-payment.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-payment.controller.ts @@ -1,9 +1,8 @@ import { isAxiosError, HttpStatusCode } from 'axios'; import { Response } from 'express'; -import { Currency } from '@ukef/dtfs2-common'; +import { Currency, TfmSessionUser } from '@ukef/dtfs2-common'; import api from '../../api'; import { CustomExpressRequest } from '../../../types/custom-express-request'; -import { TfmSessionUser } from '../../../types/tfm-session-user'; export type PostPaymentRequestBody = { feeRecordIds: number[]; diff --git a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-remove-fees-from-payment.controller.ts b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-remove-fees-from-payment.controller.ts index 703914b1aa..6c12752f1d 100644 --- a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-remove-fees-from-payment.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-remove-fees-from-payment.controller.ts @@ -2,7 +2,7 @@ import { isAxiosError, HttpStatusCode } from 'axios'; import { Response } from 'express'; import api from '../../api'; import { CustomExpressRequest } from '../../../types/custom-express-request'; -import { TfmSessionUser } from '../../../types/tfm-session-user'; +import { TfmSessionUser } from '@ukef/dtfs2-common'; export type PostRemoveFeesFromPaymentRequestBody = { selectedFeeRecordIds: number[]; diff --git a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/put-keying-data-mark-as-done.controller.ts b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/put-keying-data-mark-as-done.controller.ts index 18fe868488..4cb6dc8e9a 100644 --- a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/put-keying-data-mark-as-done.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/put-keying-data-mark-as-done.controller.ts @@ -1,8 +1,7 @@ import { HttpStatusCode, isAxiosError } from 'axios'; import { Response } from 'express'; -import { CustomExpressRequest } from '@ukef/dtfs2-common'; +import { CustomExpressRequest, TfmSessionUser } from '@ukef/dtfs2-common'; import api from '../../api'; -import { TfmSessionUser } from '../../../types/tfm-session-user'; type PutKeyingDataMarkAsDoneRequest = CustomExpressRequest<{ reqBody: { diff --git a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/put-keying-data-mark-as-to-do.controller.ts b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/put-keying-data-mark-as-to-do.controller.ts index db483b5627..7e4fba6f1d 100644 --- a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/put-keying-data-mark-as-to-do.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/put-keying-data-mark-as-to-do.controller.ts @@ -1,8 +1,7 @@ import { HttpStatusCode, isAxiosError } from 'axios'; import { Response } from 'express'; -import { CustomExpressRequest } from '@ukef/dtfs2-common'; +import { CustomExpressRequest, TfmSessionUser } from '@ukef/dtfs2-common'; import api from '../../api'; -import { TfmSessionUser } from '../../../types/tfm-session-user'; type PutKeyingDataMarkAsToDoRequest = CustomExpressRequest<{ reqBody: { diff --git a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/update-utilisation-report-status.controller.ts b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/update-utilisation-report-status.controller.ts index f549b36d77..29ebdf3505 100644 --- a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/update-utilisation-report-status.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/update-utilisation-report-status.controller.ts @@ -1,7 +1,6 @@ import { Request, Response } from 'express'; import { HttpStatusCode, isAxiosError } from 'axios'; -import { ReportWithStatus } from '@ukef/dtfs2-common'; -import { TfmSessionUser } from '../../../types/tfm-session-user'; +import { ReportWithStatus, TfmSessionUser } from '@ukef/dtfs2-common'; import api from '../../api'; export type UpdateUtilisationReportStatusRequestBody = { diff --git a/trade-finance-manager-api/src/v1/validation/route-validators/update-report-status-payload-validation/index.test.ts b/trade-finance-manager-api/src/v1/validation/route-validators/update-report-status-payload-validation/index.test.ts index 8adcf95cae..2e0916fcf7 100644 --- a/trade-finance-manager-api/src/v1/validation/route-validators/update-report-status-payload-validation/index.test.ts +++ b/trade-finance-manager-api/src/v1/validation/route-validators/update-report-status-payload-validation/index.test.ts @@ -1,9 +1,8 @@ import { Request } from 'express'; import { validationResult } from 'express-validator'; import { createRequest } from 'node-mocks-http'; -import { ReportWithStatus, UtilisationReportStatus, PENDING_RECONCILIATION, UTILISATION_REPORT_STATUS } from '@ukef/dtfs2-common'; +import { ReportWithStatus, UtilisationReportStatus, PENDING_RECONCILIATION, UTILISATION_REPORT_STATUS, TfmSessionUser } from '@ukef/dtfs2-common'; import { updateReportStatusPayloadValidation } from '.'; -import { TfmSessionUser } from '../../../../types/tfm-session-user'; import { UpdateUtilisationReportStatusRequestBody } from '../../../controllers/utilisation-reports/update-utilisation-report-status.controller'; import { MOCK_TFM_SESSION_USER } from '../../../__mocks__/mock-tfm-session-user'; diff --git a/trade-finance-manager-api/test-helpers/tfm-session-user.ts b/trade-finance-manager-api/test-helpers/tfm-session-user.ts index f09a170fcc..c964826f34 100644 --- a/trade-finance-manager-api/test-helpers/tfm-session-user.ts +++ b/trade-finance-manager-api/test-helpers/tfm-session-user.ts @@ -1,5 +1,5 @@ +import { TfmSessionUser } from '@ukef/dtfs2-common'; import { ObjectId } from 'mongodb'; -import { TfmSessionUser } from '../src/types/tfm-session-user'; export const aTfmSessionUser = (): TfmSessionUser => ({ username: 'test-user', From 9a0b1766d00d0eda992d80257dc738afe9bb8f74 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 3 Dec 2024 21:45:31 +0000 Subject: [PATCH 018/133] feat(dtfs2-6892): fix eslint errors --- .../api-tests/mocks/test-users/mock-tfm-user.ts | 1 - .../put-keying-data-mark-as-done.api-test.ts | 2 +- .../helpers.ts | 3 +-- .../index.test.ts | 2 +- .../post-payment.controller/helpers.test.ts | 2 +- .../post-payment.controller/helpers.ts | 3 +-- .../post-payment.controller/index.test.ts | 3 +-- .../helpers.test.ts | 9 +++++++-- .../utilisation-reports/post-keying-data.controller.ts | 3 +-- .../post-remove-fees-from-payment.controller.ts | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/dtfs-central-api/api-tests/mocks/test-users/mock-tfm-user.ts b/dtfs-central-api/api-tests/mocks/test-users/mock-tfm-user.ts index 115ec37d54..8b3506d694 100644 --- a/dtfs-central-api/api-tests/mocks/test-users/mock-tfm-user.ts +++ b/dtfs-central-api/api-tests/mocks/test-users/mock-tfm-user.ts @@ -1,5 +1,4 @@ import { TfmSessionUser } from '@ukef/dtfs2-common'; -import { ObjectId } from 'mongodb'; export const MOCK_TFM_USER: TfmSessionUser = { _id: '5ce819935e539c343f141ece', diff --git a/dtfs-central-api/api-tests/v1/utilisation-reports/put-keying-data-mark-as-done.api-test.ts b/dtfs-central-api/api-tests/v1/utilisation-reports/put-keying-data-mark-as-done.api-test.ts index b671fb250e..8f7a28e969 100644 --- a/dtfs-central-api/api-tests/v1/utilisation-reports/put-keying-data-mark-as-done.api-test.ts +++ b/dtfs-central-api/api-tests/v1/utilisation-reports/put-keying-data-mark-as-done.api-test.ts @@ -5,6 +5,7 @@ import { FeeRecordEntityMockBuilder, RECONCILIATION_COMPLETED, RECONCILIATION_IN_PROGRESS, + TfmSessionUser, UtilisationReportEntity, UtilisationReportEntityMockBuilder, } from '@ukef/dtfs2-common'; @@ -12,7 +13,6 @@ import { withSqlIdPathParameterValidationTests } from '@ukef/dtfs2-common/test-c import { testApi } from '../../test-api'; import { SqlDbHelper } from '../../sql-db-helper'; import { aTfmSessionUser } from '../../../test-helpers'; -import { TfmSessionUser } from '../../../src/types/tfm/tfm-session-user'; console.error = jest.fn(); diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/helpers.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/helpers.ts index 94215d3f6c..3e4480b11b 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/helpers.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/helpers.ts @@ -1,5 +1,4 @@ -import { FeeRecordEntity, PaymentEntity, REQUEST_PLATFORM_TYPE, UtilisationReportEntity } from '@ukef/dtfs2-common'; -import { TfmSessionUser } from '../../../../types/tfm/tfm-session-user'; +import { FeeRecordEntity, PaymentEntity, REQUEST_PLATFORM_TYPE, TfmSessionUser, UtilisationReportEntity } from '@ukef/dtfs2-common'; import { executeWithSqlTransaction } from '../../../../helpers'; import { UtilisationReportStateMachine } from '../../../../services/state-machines/utilisation-report/utilisation-report.state-machine'; import { UTILISATION_REPORT_EVENT_TYPE } from '../../../../services/state-machines/utilisation-report/event/utilisation-report.event-type'; diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/index.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/index.test.ts index 4612a3824b..5cce17741b 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/index.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/index.test.ts @@ -8,10 +8,10 @@ import { TestApiError, FEE_RECORD_STATUS, RECONCILIATION_IN_PROGRESS, + TfmSessionUser, } from '@ukef/dtfs2-common'; import { HttpStatusCode } from 'axios'; import { PostAddFeesToAnExistingPaymentGroupRequest, postAddFeesToAnExistingPaymentGroup } from '.'; -import { TfmSessionUser } from '../../../../types/tfm/tfm-session-user'; import { aTfmSessionUser } from '../../../../../test-helpers/test-data/tfm-session-user'; import { addFeesToAnExistingPaymentGroup } from './helpers'; import { PostAddFeesToAnExistingPaymentGroupPayload } from '../../../routes/middleware/payload-validation'; diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/helpers.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/helpers.test.ts index 4f143d33ad..626f8923cb 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/helpers.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/helpers.test.ts @@ -8,12 +8,12 @@ import { PaymentEntityMockBuilder, RECONCILIATION_IN_PROGRESS, REQUEST_PLATFORM_TYPE, + TfmSessionUser, UtilisationReportEntityMockBuilder, } from '@ukef/dtfs2-common'; import { addPaymentToUtilisationReport } from './helpers'; import { UtilisationReportStateMachine } from '../../../../services/state-machines/utilisation-report/utilisation-report.state-machine'; import { InvalidPayloadError, NotFoundError } from '../../../../errors'; -import { TfmSessionUser } from '../../../../types/tfm/tfm-session-user'; import { aTfmSessionUser } from '../../../../../test-helpers'; import { FeeRecordRepo } from '../../../../repositories/fee-record-repo'; import { NewPaymentDetails } from '../../../../types/utilisation-reports'; diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/helpers.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/helpers.ts index a573f00082..dc101d6288 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/helpers.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/helpers.ts @@ -1,9 +1,8 @@ import { In } from 'typeorm'; -import { FeeRecordEntity, FeeRecordStatus, REQUEST_PLATFORM_TYPE } from '@ukef/dtfs2-common'; +import { FeeRecordEntity, FeeRecordStatus, REQUEST_PLATFORM_TYPE, TfmSessionUser } from '@ukef/dtfs2-common'; import { UtilisationReportStateMachine } from '../../../../services/state-machines/utilisation-report/utilisation-report.state-machine'; import { InvalidPayloadError, NotFoundError } from '../../../../errors'; import { FeeRecordRepo } from '../../../../repositories/fee-record-repo'; -import { TfmSessionUser } from '../../../../types/tfm/tfm-session-user'; import { NewPaymentDetails } from '../../../../types/utilisation-reports'; import { executeWithSqlTransaction } from '../../../../helpers'; import { UTILISATION_REPORT_EVENT_TYPE } from '../../../../services/state-machines/utilisation-report/event/utilisation-report.event-type'; diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/index.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/index.test.ts index a47fbd676f..ba83058407 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/index.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/index.test.ts @@ -1,9 +1,8 @@ import httpMocks from 'node-mocks-http'; import { ObjectId } from 'mongodb'; -import { Currency, CURRENCY, FEE_RECORD_STATUS, TestApiError } from '@ukef/dtfs2-common'; +import { Currency, CURRENCY, FEE_RECORD_STATUS, TestApiError, TfmSessionUser } from '@ukef/dtfs2-common'; import { HttpStatusCode } from 'axios'; import { PostPaymentRequest, postPayment } from '.'; -import { TfmSessionUser } from '../../../../types/tfm/tfm-session-user'; import { aTfmSessionUser } from '../../../../../test-helpers'; import { addPaymentToUtilisationReport } from './helpers'; import { PostPaymentPayload } from '../../../routes/middleware/payload-validation/validate-post-payment-payload'; diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/helpers.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/helpers.test.ts index 061fbaa742..8c22de6214 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/helpers.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/helpers.test.ts @@ -1,9 +1,14 @@ import { ObjectId } from 'mongodb'; import { EntityManager } from 'typeorm'; -import { FeeRecordEntityMockBuilder, RECONCILIATION_IN_PROGRESS, REQUEST_PLATFORM_TYPE, UtilisationReportEntityMockBuilder } from '@ukef/dtfs2-common'; +import { + FeeRecordEntityMockBuilder, + RECONCILIATION_IN_PROGRESS, + REQUEST_PLATFORM_TYPE, + TfmSessionUser, + UtilisationReportEntityMockBuilder, +} from '@ukef/dtfs2-common'; import { removeFeesFromPaymentGroup } from './helpers'; import { UtilisationReportStateMachine } from '../../../../services/state-machines/utilisation-report/utilisation-report.state-machine'; -import { TfmSessionUser } from '../../../../types/tfm/tfm-session-user'; import { aTfmSessionUser } from '../../../../../test-helpers'; import { executeWithSqlTransaction } from '../../../../helpers'; import { UTILISATION_REPORT_EVENT_TYPE } from '../../../../services/state-machines/utilisation-report/event/utilisation-report.event-type'; diff --git a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-keying-data.controller.ts b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-keying-data.controller.ts index 3166c73fa2..1f3c217658 100644 --- a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-keying-data.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-keying-data.controller.ts @@ -1,8 +1,7 @@ import { HttpStatusCode, isAxiosError } from 'axios'; import { Response } from 'express'; -import { CustomExpressRequest } from '@ukef/dtfs2-common'; +import { CustomExpressRequest, TfmSessionUser } from '@ukef/dtfs2-common'; import api from '../../api'; -import { TfmSessionUser } from '../../../types/tfm-session-user'; type PostKeyingDataRequest = CustomExpressRequest<{ reqBody: { diff --git a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-remove-fees-from-payment.controller.ts b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-remove-fees-from-payment.controller.ts index 6c12752f1d..0f16e02019 100644 --- a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-remove-fees-from-payment.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/post-remove-fees-from-payment.controller.ts @@ -1,8 +1,8 @@ import { isAxiosError, HttpStatusCode } from 'axios'; import { Response } from 'express'; +import { TfmSessionUser } from '@ukef/dtfs2-common'; import api from '../../api'; import { CustomExpressRequest } from '../../../types/custom-express-request'; -import { TfmSessionUser } from '@ukef/dtfs2-common'; export type PostRemoveFeesFromPaymentRequestBody = { selectedFeeRecordIds: number[]; From 6668392f25a7f8346cc1dbe2bedf08c37dd5c555 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 3 Dec 2024 21:51:00 +0000 Subject: [PATCH 019/133] feat(dtfs2-6892): update tfm ui --- .../src/types/tfm/tfm-session-user.ts | 4 ---- ...reconciliation-for-report.component-test.ts | 3 +-- trade-finance-manager-ui/server/api.js | 18 +++++++++--------- ...deal-cancellation-enabled.helper.ff-test.ts | 3 +-- .../deal-cancellation-enabled.helper.test.ts | 3 +-- .../deal-cancellation-enabled.helper.ts | 2 +- .../controllers/login/login-non-sso/index.ts | 3 +-- .../helpers/is-pdc-reconcile-user.ts | 3 +-- .../server/helpers/user.test.ts | 3 +-- .../server/helpers/user.ts | 3 +-- .../server/test-mocks/mock-tfm-session-user.ts | 3 +-- .../server/types/express-session.d.ts | 2 +- .../server/types/tfm-session-user.ts | 13 ------------- .../types/view-models/base-view-model.ts | 2 +- .../test-helpers/test-data/tfm-session-user.ts | 3 +-- 15 files changed, 21 insertions(+), 47 deletions(-) delete mode 100644 dtfs-central-api/src/types/tfm/tfm-session-user.ts delete mode 100644 trade-finance-manager-ui/server/types/tfm-session-user.ts diff --git a/dtfs-central-api/src/types/tfm/tfm-session-user.ts b/dtfs-central-api/src/types/tfm/tfm-session-user.ts deleted file mode 100644 index f7ad4326a1..0000000000 --- a/dtfs-central-api/src/types/tfm/tfm-session-user.ts +++ /dev/null @@ -1,4 +0,0 @@ -import z from 'zod'; -import { TfmSessionUserSchema } from '../../v1/routes/middleware/payload-validation/schemas'; - -export type TfmSessionUser = z.infer; diff --git a/trade-finance-manager-ui/component-tests/utilisation-reports/utilisation-report-reconciliation-for-report.component-test.ts b/trade-finance-manager-ui/component-tests/utilisation-reports/utilisation-report-reconciliation-for-report.component-test.ts index affa116fa0..fbcd14be95 100644 --- a/trade-finance-manager-ui/component-tests/utilisation-reports/utilisation-report-reconciliation-for-report.component-test.ts +++ b/trade-finance-manager-ui/component-tests/utilisation-reports/utilisation-report-reconciliation-for-report.component-test.ts @@ -1,9 +1,8 @@ -import { FEE_RECORD_STATUS } from '@ukef/dtfs2-common'; +import { FEE_RECORD_STATUS, TfmSessionUser } from '@ukef/dtfs2-common'; import { PRIMARY_NAVIGATION_KEYS } from '../../server/constants'; import { pageRenderer } from '../pageRenderer'; import { aTfmSessionUser } from '../../test-helpers/test-data/tfm-session-user'; import { UtilisationReportReconciliationForReportViewModel } from '../../server/types/view-models'; -import { TfmSessionUser } from '../../server/types/tfm-session-user'; import { aUtilisationTableRowViewModel } from '../../test-helpers'; const page = '../templates/utilisation-reports/utilisation-report-reconciliation-for-report.njk'; diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index 43611fc3b4..8afa11b05e 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -908,7 +908,7 @@ const downloadUtilisationReport = async (userToken, id) => { }; /** - * @param {import('./types/tfm-session-user').TfmSessionUser} user - the session user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - the session user * @param {import('./types/utilisation-reports').ReportWithStatus[]} reportsWithStatus - array of reports with the status to set * @param {string} userToken - token to validate session * @returns {Promise} @@ -1048,7 +1048,7 @@ const getReportSummariesByBankAndYear = async (userToken, bankId, year) => { * @param {string} reportId - The report id * @param {import('./types/add-payment-form-values').ParsedAddPaymentFormValues} parsedAddPaymentFormValues - The parsed submitted form values * @param {number[]} feeRecordIds - The list of fee record ids to add the payment to - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The user adding the payment + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The user adding the payment * @param {string} userToken - The user token * @returns {Promise} */ @@ -1075,7 +1075,7 @@ const addPaymentToFeeRecords = async (reportId, parsedAddPaymentFormValues, feeR * Generates keying data for the utilisation report * with the supplied id * @param {string} reportId - The report id - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The session user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The session user * @param {string} userToken - The user token * @returns {Promise<{}>} */ @@ -1095,7 +1095,7 @@ const generateKeyingData = async (reportId, user, userToken) => { * Updates keying sheet fee records with supplied ids to DONE * @param {string} reportId - The report id * @param {number[]} feeRecordIds - The ids of the fee records to mark as DONE - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The session user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The session user * @param {string} userToken - The user token * @returns {Promise<{}>} */ @@ -1116,7 +1116,7 @@ const markKeyingDataAsDone = async (reportId, feeRecordIds, user, userToken) => * Updates keying sheet fee records with supplied ids to TO_DO * @param {string} reportId - The report id * @param {number[]} feeRecordIds - The ids of the fee records to mark as TO_DO - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The session user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The session user * @param {string} userToken - The user token * @returns {Promise<{}>} */ @@ -1185,7 +1185,7 @@ const getPaymentDetailsWithoutFeeRecords = async (reportId, paymentId, userToken * Deletes the payment with the specified id * @param {string} reportId - The report id * @param {string} paymentId - The payment id - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The session user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The session user * @param {string} userToken - The user token * @returns {Promise} */ @@ -1203,7 +1203,7 @@ const deletePaymentById = async (reportId, paymentId, user, userToken) => { * @param {string} reportId - The report id * @param {string} paymentId - The payment id * @param {import('./types/edit-payment-form-values').ParsedEditPaymentFormValues} parsedEditPaymentFormValues - The parsed edit payment form values - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The user * @param {string} userToken - The user token */ const editPayment = async (reportId, paymentId, parsedEditPaymentFormValues, user, userToken) => { @@ -1226,7 +1226,7 @@ const editPayment = async (reportId, paymentId, parsedEditPaymentFormValues, use * @param {string} reportId - The report id * @param {string} paymentId - The payment id * @param {number[]} selectedFeeRecordIds - The list of fee record ids to remove from the payment - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The user * @param {string} userToken - The user token */ const removeFeesFromPayment = async (reportId, paymentId, selectedFeeRecordIds, user, userToken) => { @@ -1246,7 +1246,7 @@ const removeFeesFromPayment = async (reportId, paymentId, selectedFeeRecordIds, * @param {string} reportId - The report id * @param {number[]} feeRecordIds - The list of fee record ids to add to the payment * @param {number[]} paymentIds - The list of payment ids for the fee records to be added to - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The user adding the payment + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The user adding the payment * @param {string} userToken - The user token */ const addFeesToAnExistingPayment = async (reportId, feeRecordIds, paymentIds, user, userToken) => { diff --git a/trade-finance-manager-ui/server/controllers/helpers/deal-cancellation-enabled.helper.ff-test.ts b/trade-finance-manager-ui/server/controllers/helpers/deal-cancellation-enabled.helper.ff-test.ts index 55d35b40b0..982389c260 100644 --- a/trade-finance-manager-ui/server/controllers/helpers/deal-cancellation-enabled.helper.ff-test.ts +++ b/trade-finance-manager-ui/server/controllers/helpers/deal-cancellation-enabled.helper.ff-test.ts @@ -1,6 +1,5 @@ -import { DEAL_SUBMISSION_TYPE, TEAM_IDS } from '@ukef/dtfs2-common'; +import { DEAL_SUBMISSION_TYPE, TEAM_IDS, TfmSessionUser } from '@ukef/dtfs2-common'; import { isDealCancellationEnabledForUser, isDealCancellationEnabled } from './deal-cancellation-enabled.helper'; -import { TfmSessionUser } from '../../types/tfm-session-user'; const pimUser = { teams: [TEAM_IDS.PIM] } as TfmSessionUser; const nonPimUser = { teams: [TEAM_IDS.UNDERWRITERS] } as TfmSessionUser; diff --git a/trade-finance-manager-ui/server/controllers/helpers/deal-cancellation-enabled.helper.test.ts b/trade-finance-manager-ui/server/controllers/helpers/deal-cancellation-enabled.helper.test.ts index f37b526af8..9a6d84644c 100644 --- a/trade-finance-manager-ui/server/controllers/helpers/deal-cancellation-enabled.helper.test.ts +++ b/trade-finance-manager-ui/server/controllers/helpers/deal-cancellation-enabled.helper.test.ts @@ -1,4 +1,4 @@ -import { DEAL_SUBMISSION_TYPE, TEAM_IDS, TFM_DEAL_CANCELLATION_STATUS } from '@ukef/dtfs2-common'; +import { DEAL_SUBMISSION_TYPE, TEAM_IDS, TFM_DEAL_CANCELLATION_STATUS, TfmSessionUser } from '@ukef/dtfs2-common'; import { canDealBeCancelled, canSubmissionTypeBeCancelled, @@ -6,7 +6,6 @@ import { isDealCancellationInDraft, isDealCancellationEnabled, } from './deal-cancellation-enabled.helper'; -import { TfmSessionUser } from '../../types/tfm-session-user'; const pimUser = { teams: [TEAM_IDS.PIM] } as TfmSessionUser; const nonPimUser = { teams: [TEAM_IDS.UNDERWRITERS] } as TfmSessionUser; diff --git a/trade-finance-manager-ui/server/controllers/helpers/deal-cancellation-enabled.helper.ts b/trade-finance-manager-ui/server/controllers/helpers/deal-cancellation-enabled.helper.ts index 0d44270b90..9f31d18f16 100644 --- a/trade-finance-manager-ui/server/controllers/helpers/deal-cancellation-enabled.helper.ts +++ b/trade-finance-manager-ui/server/controllers/helpers/deal-cancellation-enabled.helper.ts @@ -5,9 +5,9 @@ import { TEAM_IDS, TFM_DEAL_CANCELLATION_STATUS, TfmDealCancellationStatus, + TfmSessionUser, } from '@ukef/dtfs2-common'; import { userIsInTeam } from '../../helpers/user'; -import { TfmSessionUser } from '../../types/tfm-session-user'; const { AIN, MIN } = DEAL_SUBMISSION_TYPE; /** diff --git a/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.ts b/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.ts index 8aed41739b..64a5668bb1 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.ts @@ -1,6 +1,5 @@ import { Request, Response } from 'express'; -import { CustomExpressRequest } from '@ukef/dtfs2-common'; -import { TfmSessionUser } from '../../../types/tfm-session-user'; +import { CustomExpressRequest, TfmSessionUser } from '@ukef/dtfs2-common'; import api from '../../../api'; import { validationErrorHandler } from '../../../helpers/validationErrorHandler.helper'; diff --git a/trade-finance-manager-ui/server/controllers/utilisation-reports/helpers/is-pdc-reconcile-user.ts b/trade-finance-manager-ui/server/controllers/utilisation-reports/helpers/is-pdc-reconcile-user.ts index 8f6d880f09..e39c59b379 100644 --- a/trade-finance-manager-ui/server/controllers/utilisation-reports/helpers/is-pdc-reconcile-user.ts +++ b/trade-finance-manager-ui/server/controllers/utilisation-reports/helpers/is-pdc-reconcile-user.ts @@ -1,5 +1,4 @@ -import { TEAM_IDS } from '@ukef/dtfs2-common'; -import { TfmSessionUser } from '../../../types/tfm-session-user'; +import { TEAM_IDS, TfmSessionUser } from '@ukef/dtfs2-common'; /** * returns true if user is in PDC_RECONCILE team diff --git a/trade-finance-manager-ui/server/helpers/user.test.ts b/trade-finance-manager-ui/server/helpers/user.test.ts index 50befc3e75..6bd0b2d11c 100644 --- a/trade-finance-manager-ui/server/helpers/user.test.ts +++ b/trade-finance-manager-ui/server/helpers/user.test.ts @@ -1,6 +1,5 @@ -import { TeamId } from '@ukef/dtfs2-common'; +import { TeamId, TfmSessionUser } from '@ukef/dtfs2-common'; import { userFullName, userIsInTeam, userIsOnlyInTeams } from './user'; -import { TfmSessionUser } from '../types/tfm-session-user'; describe('user helpers', () => { describe('userFullName', () => { diff --git a/trade-finance-manager-ui/server/helpers/user.ts b/trade-finance-manager-ui/server/helpers/user.ts index 89339a8186..79edddab60 100644 --- a/trade-finance-manager-ui/server/helpers/user.ts +++ b/trade-finance-manager-ui/server/helpers/user.ts @@ -1,5 +1,4 @@ -import { TeamId } from '@ukef/dtfs2-common'; -import { TfmSessionUser } from '../types/tfm-session-user'; +import { TeamId, TfmSessionUser } from '@ukef/dtfs2-common'; export const userFullName = (user: TfmSessionUser) => { const { firstName, lastName } = user; diff --git a/trade-finance-manager-ui/server/test-mocks/mock-tfm-session-user.ts b/trade-finance-manager-ui/server/test-mocks/mock-tfm-session-user.ts index c6f71f8b5e..ec9a2f6805 100644 --- a/trade-finance-manager-ui/server/test-mocks/mock-tfm-session-user.ts +++ b/trade-finance-manager-ui/server/test-mocks/mock-tfm-session-user.ts @@ -1,5 +1,4 @@ -import { TEAM_IDS } from '@ukef/dtfs2-common'; -import { TfmSessionUser } from '../types/tfm-session-user'; +import { TEAM_IDS, TfmSessionUser } from '@ukef/dtfs2-common'; export const MOCK_TFM_SESSION_USER: TfmSessionUser = { _id: '65954cc526d3899694cafff2', diff --git a/trade-finance-manager-ui/server/types/express-session.d.ts b/trade-finance-manager-ui/server/types/express-session.d.ts index 1867a04e84..17cb5aec3d 100644 --- a/trade-finance-manager-ui/server/types/express-session.d.ts +++ b/trade-finance-manager-ui/server/types/express-session.d.ts @@ -1,4 +1,4 @@ -import { TfmSessionUser } from './tfm-session-user'; +import { TfmSessionUser } from '@ukef/dtfs2-common'; import { RemoveFeesFromPaymentErrorKey } from '../controllers/utilisation-reports/helpers'; import { EditPaymentFormValues } from './edit-payment-form-values'; import { AddPaymentErrorKey, InitiateRecordCorrectionRequestErrorKey, GenerateKeyingDataErrorKey } from './premium-payments-tab-error-keys'; diff --git a/trade-finance-manager-ui/server/types/tfm-session-user.ts b/trade-finance-manager-ui/server/types/tfm-session-user.ts deleted file mode 100644 index caee3508b1..0000000000 --- a/trade-finance-manager-ui/server/types/tfm-session-user.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { TeamId, UnixTimestampMilliseconds } from '@ukef/dtfs2-common'; - -export type TfmSessionUser = { - _id: string; - username: string; - email: string; - teams: TeamId[]; - timezone: string; - firstName: string; - lastName: string; - status: string; - lastLogin: UnixTimestampMilliseconds; -}; diff --git a/trade-finance-manager-ui/server/types/view-models/base-view-model.ts b/trade-finance-manager-ui/server/types/view-models/base-view-model.ts index c239a19788..74ead63c75 100644 --- a/trade-finance-manager-ui/server/types/view-models/base-view-model.ts +++ b/trade-finance-manager-ui/server/types/view-models/base-view-model.ts @@ -1,5 +1,5 @@ +import { TfmSessionUser } from '@ukef/dtfs2-common'; import { PrimaryNavigationKey } from '../primary-navigation-key'; -import { TfmSessionUser } from '../tfm-session-user'; export type BaseViewModel = { user: TfmSessionUser; diff --git a/trade-finance-manager-ui/test-helpers/test-data/tfm-session-user.ts b/trade-finance-manager-ui/test-helpers/test-data/tfm-session-user.ts index 6522f0b81b..6316388705 100644 --- a/trade-finance-manager-ui/test-helpers/test-data/tfm-session-user.ts +++ b/trade-finance-manager-ui/test-helpers/test-data/tfm-session-user.ts @@ -1,5 +1,4 @@ -import { TEAM_IDS } from '@ukef/dtfs2-common'; -import { TfmSessionUser } from '../../server/types/tfm-session-user'; +import { TEAM_IDS, TfmSessionUser } from '@ukef/dtfs2-common'; export const aTfmSessionUser = (): TfmSessionUser => ({ _id: '65954cc526d3899694cafff2', From eb080b325fea296a499f6c29e834bf58c7d4ba8d Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 3 Dec 2024 22:27:04 +0000 Subject: [PATCH 020/133] feat(dtfs2-6892): fix create tfm user request --- .../common/src/test-helpers/mock-data/create-tfm-user-request.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/common/src/test-helpers/mock-data/create-tfm-user-request.ts b/libs/common/src/test-helpers/mock-data/create-tfm-user-request.ts index a251c63500..00c511b450 100644 --- a/libs/common/src/test-helpers/mock-data/create-tfm-user-request.ts +++ b/libs/common/src/test-helpers/mock-data/create-tfm-user-request.ts @@ -8,5 +8,4 @@ export const aCreateTfmUserRequest = (): CreateTfmUserRequest => ({ timezone: 'Europe/London', firstName: 'a-first-name', lastName: 'a-last-name', - lastLogin: Date.now(), }); From d3da1adaa45b86287696ebe66301ebfe23e74f13 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 4 Dec 2024 13:24:19 +0000 Subject: [PATCH 021/133] feat(dtfs2-6892): update schema tests --- .../schemas/audit-database-record.schema.ts | 13 ++ .../src/schemas/audit-database-record.test.ts | 55 +------ .../src/schemas/audit-database-record.ts | 13 -- libs/common/src/schemas/index.ts | 2 +- .../iso-date-time-stamp.schema.test.ts | 9 ++ ...stamp.ts => iso-date-time-stamp.schema.ts} | 2 +- .../src/schemas/iso-date-time-stamp.test.ts | 23 --- libs/common/src/schemas/object-id.test.ts | 80 ++-------- libs/common/src/schemas/object-id.ts | 6 +- .../src/schemas/portal-user.create.test.ts | 4 +- libs/common/src/schemas/portal-user.ts | 4 +- .../src/schemas/portal-user.update.test.ts | 4 +- .../create-tfm-user-request.schema.test.ts | 40 ++++- ...ded-auth-code-request-state-schema.test.ts | 4 +- ...code-redirect-response-body-schema.test.ts | 4 +- ...ra-id-authentication-result-schema.test.ts | 4 +- .../schemas/tfm/tfm-session-user.schema.ts | 4 +- .../src/schemas/tfm/tfm-team.schema.test.ts | 37 +---- .../src/schemas/tfm/tfm-user.schema.test.ts | 121 +++++++++++++++ .../common/src/schemas/tfm/tfm-user.schema.ts | 8 +- libs/common/src/test-helpers/schemas/index.ts | 1 + .../get-tests-for-parameter.tests.ts | 141 ++++++++++++++++++ .../test-helpers/schemas/primitives/index.ts | 1 + .../schemas/primitives/with-array.tests.ts | 40 +++++ ...with-audit-database-record-schema.tests.ts | 73 +++++++++ .../schemas/primitives/with-boolean.tests.ts | 23 +++ .../primitives/with-default-options.tests.ts | 60 ++++++++ .../with-iso-date-time-stamp-schema.tests.ts | 37 +++++ .../schemas/primitives/with-number.tests.ts | 23 +++ ...ect-id-or-object-id-string-schema.tests.ts | 36 +++++ .../primitives/with-object-id-schema.tests.ts | 41 +++++ .../with-object-id-string-schema.tests.ts | 48 ++++++ .../primitives/with-schema-test.type.ts | 10 ++ .../with-schema-validation.tests.ts | 42 ++++++ .../schemas/primitives/with-string.tests.ts | 23 +++ .../primitives/with-tfm-team-schema.tests.ts | 24 +++ 36 files changed, 852 insertions(+), 208 deletions(-) create mode 100644 libs/common/src/schemas/audit-database-record.schema.ts delete mode 100644 libs/common/src/schemas/audit-database-record.ts create mode 100644 libs/common/src/schemas/iso-date-time-stamp.schema.test.ts rename libs/common/src/schemas/{iso-date-time-stamp.ts => iso-date-time-stamp.schema.ts} (76%) delete mode 100644 libs/common/src/schemas/iso-date-time-stamp.test.ts create mode 100644 libs/common/src/schemas/tfm/tfm-user.schema.test.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/get-tests-for-parameter.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/index.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-array.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-audit-database-record-schema.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-boolean.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-default-options.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-iso-date-time-stamp-schema.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-number.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-object-id-or-object-id-string-schema.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-object-id-schema.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-object-id-string-schema.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-schema-test.type.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-schema-validation.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-string.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-tfm-team-schema.tests.ts diff --git a/libs/common/src/schemas/audit-database-record.schema.ts b/libs/common/src/schemas/audit-database-record.schema.ts new file mode 100644 index 0000000000..a4817bdd3a --- /dev/null +++ b/libs/common/src/schemas/audit-database-record.schema.ts @@ -0,0 +1,13 @@ +import z from 'zod'; +import { ISO_DATE_TIME_STAMP_SCHEMA } from './iso-date-time-stamp.schema'; +import { OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA } from './object-id'; + +export const AUDIT_DATABASE_RECORD_SCHEMA = z + .object({ + lastUpdatedAt: ISO_DATE_TIME_STAMP_SCHEMA, + lastUpdatedByPortalUserId: OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA.nullable(), + lastUpdatedByTfmUserId: OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA.nullable(), + lastUpdatedByIsSystem: z.boolean().nullable(), + noUserLoggedIn: z.boolean().nullable(), + }) + .strict(); diff --git a/libs/common/src/schemas/audit-database-record.test.ts b/libs/common/src/schemas/audit-database-record.test.ts index 6c40ce4e02..9d59d4a74f 100644 --- a/libs/common/src/schemas/audit-database-record.test.ts +++ b/libs/common/src/schemas/audit-database-record.test.ts @@ -1,54 +1,9 @@ -import { ObjectId } from 'mongodb'; -import { - generateNoUserLoggedInAuditDatabaseRecord, - generatePortalUserAuditDatabaseRecord, - generateSystemAuditDatabaseRecord, - generateTfmUserAuditDatabaseRecord, -} from '../change-stream'; -import { AUDIT_DATABASE_RECORD } from './audit-database-record'; -import { withSchemaTests } from '../test-helpers'; +import { withAuditDatabaseRecordSchemaTests } from '../test-helpers/schemas/primitives/with-audit-database-record-schema.tests'; +import { AUDIT_DATABASE_RECORD_SCHEMA } from './audit-database-record.schema'; describe('AUDIT_DATABASE_RECORD', () => { - withSchemaTests({ - successTestCases: getSuccessTestCases(), - failureTestCases: getFailureTestCases(), - schema: AUDIT_DATABASE_RECORD, + withAuditDatabaseRecordSchemaTests({ + schema: AUDIT_DATABASE_RECORD_SCHEMA, + getTestObjectWithUpdatedField: (newValue) => newValue, }); }); - -const aValidAuditRecord = () => generateTfmUserAuditDatabaseRecord(new ObjectId()); - -function getSuccessTestCases() { - return [ - { - description: 'a valid tfm user audit database record', - aTestCase: () => generateTfmUserAuditDatabaseRecord(new ObjectId()), - }, - { - description: 'a valid portal user audit database record', - aTestCase: () => generatePortalUserAuditDatabaseRecord(new ObjectId()), - }, - { - description: 'a valid system audit database record', - aTestCase: () => generateSystemAuditDatabaseRecord(), - }, - { description: 'a valid audit record with no user logged in', aTestCase: () => generateNoUserLoggedInAuditDatabaseRecord() }, - ]; -} - -function getFailureTestCases() { - return [ - { description: 'a string', aTestCase: () => 'string' }, - { description: 'an object', aTestCase: () => ({ An: 'object' }) }, - { description: 'an array', aTestCase: () => ['array'] }, - { description: 'a matching object with an incorrect parameter type', aTestCase: () => ({ ...aValidAuditRecord(), _lastUpdatedAt: 1 }) }, - { - description: 'a matching object with a missing parameter', - aTestCase: () => { - const { lastUpdatedAt: _lastUpdatedAt, ...rest } = aValidAuditRecord(); - return rest; - }, - }, - { description: 'a matching object with an additional parameter', aTestCase: () => ({ ...aValidAuditRecord(), invalidField: true }) }, - ]; -} diff --git a/libs/common/src/schemas/audit-database-record.ts b/libs/common/src/schemas/audit-database-record.ts deleted file mode 100644 index 2894884704..0000000000 --- a/libs/common/src/schemas/audit-database-record.ts +++ /dev/null @@ -1,13 +0,0 @@ -import z from 'zod'; -import { ISO_DATE_TIME_STAMP } from './iso-date-time-stamp'; -import { OBJECT_ID_OR_OBJECT_ID_STRING } from './object-id'; - -export const AUDIT_DATABASE_RECORD = z - .object({ - lastUpdatedAt: ISO_DATE_TIME_STAMP, - lastUpdatedByPortalUserId: OBJECT_ID_OR_OBJECT_ID_STRING.nullable(), - lastUpdatedByTfmUserId: OBJECT_ID_OR_OBJECT_ID_STRING.nullable(), - lastUpdatedByIsSystem: z.boolean().nullable(), - noUserLoggedIn: z.boolean().nullable(), - }) - .strict(); diff --git a/libs/common/src/schemas/index.ts b/libs/common/src/schemas/index.ts index 36b221f5f6..951f190317 100644 --- a/libs/common/src/schemas/index.ts +++ b/libs/common/src/schemas/index.ts @@ -1,5 +1,5 @@ export * as PORTAL_USER from './portal-user'; -export * as ISO_DATE_TIME_STAMP from './iso-date-time-stamp'; +export * as ISO_DATE_TIME_STAMP from './iso-date-time-stamp.schema'; export * from './object-id'; export * from './deal-cancellation'; export * from './tfm'; diff --git a/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts b/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts new file mode 100644 index 0000000000..dc19c97f89 --- /dev/null +++ b/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts @@ -0,0 +1,9 @@ +import { withIsoDateTimeStampSchemaTests } from '../test-helpers/schemas/primitives/with-iso-date-time-stamp-schema.tests'; +import { ISO_DATE_TIME_STAMP_SCHEMA } from './iso-date-time-stamp.schema'; + +describe('ISO_DATE_TIME_STAMP_SCHEMA', () => { + withIsoDateTimeStampSchemaTests({ + schema: ISO_DATE_TIME_STAMP_SCHEMA, + getTestObjectWithUpdatedField: (newValue) => newValue, + }); +}); diff --git a/libs/common/src/schemas/iso-date-time-stamp.ts b/libs/common/src/schemas/iso-date-time-stamp.schema.ts similarity index 76% rename from libs/common/src/schemas/iso-date-time-stamp.ts rename to libs/common/src/schemas/iso-date-time-stamp.schema.ts index 7a21c948e2..2adcceb064 100644 --- a/libs/common/src/schemas/iso-date-time-stamp.ts +++ b/libs/common/src/schemas/iso-date-time-stamp.schema.ts @@ -4,4 +4,4 @@ import z from 'zod'; * The following regex complies with whatgetNowAsUtcISOString returns */ const isoDateTimeStampRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3} \+\d{2}:\d{2}$/; -export const ISO_DATE_TIME_STAMP = z.string().regex(isoDateTimeStampRegex); +export const ISO_DATE_TIME_STAMP_SCHEMA = z.string().regex(isoDateTimeStampRegex); diff --git a/libs/common/src/schemas/iso-date-time-stamp.test.ts b/libs/common/src/schemas/iso-date-time-stamp.test.ts deleted file mode 100644 index cb6cef28cb..0000000000 --- a/libs/common/src/schemas/iso-date-time-stamp.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { withSchemaTests } from '../test-helpers'; -import { ISO_DATE_TIME_STAMP } from './iso-date-time-stamp'; - -describe('ISO_DATE_TIME_STAMP', () => { - withSchemaTests({ - successTestCases: getSuccessTestCases(), - failureTestCases: getFailureTestCases(), - schema: ISO_DATE_TIME_STAMP, - }); -}); - -function getSuccessTestCases() { - return [{ description: 'a valid ISO date', aTestCase: () => '2024-05-17T15:35:32.496 +00:00' }]; -} - -function getFailureTestCases() { - return [ - { description: 'an object', aTestCase: () => ({ An: 'object' }) }, - { description: 'an array', aTestCase: () => ['2024-05-17T15:35:32.496 +00:00'] }, - { description: 'an incorrectly formatted date', aTestCase: () => '2021-01-01T00:00:00' }, - { description: 'an incorrectly formatted date', aTestCase: () => '2024-05-17X15:35:32.496 +00:00' }, - ]; -} diff --git a/libs/common/src/schemas/object-id.test.ts b/libs/common/src/schemas/object-id.test.ts index f325e00d1c..0ad51fb257 100644 --- a/libs/common/src/schemas/object-id.test.ts +++ b/libs/common/src/schemas/object-id.test.ts @@ -1,75 +1,25 @@ -import { ObjectId } from 'mongodb'; -import { OBJECT_ID, OBJECT_ID_OR_OBJECT_ID_STRING, OBJECT_ID_STRING } from './object-id'; -import { withSchemaTests } from '../test-helpers'; - -describe('OBJECT_ID', () => { - withSchemaTests({ - successTestCases: getObjectIdSuccessTestCases(), - failureTestCases: getObjectIdSharedFailureTestCases(), - schema: OBJECT_ID, - }); - - it('should transform a valid string ObjectId to an ObjectId', () => { - const stringObjectId = new ObjectId().toString(); - - const result = OBJECT_ID.parse(stringObjectId); - - expect(result).toEqual(new ObjectId(stringObjectId)); +import { OBJECT_ID_SCHEMA, OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA, OBJECT_ID_STRING_SCHEMA } from './object-id'; +import { withObjectIdSchemaTests } from '../test-helpers/schemas/primitives/with-object-id-schema.tests'; +import { withObjectIdOrObjectIdStringSchemaTests } from '../test-helpers/schemas/primitives/with-object-id-or-object-id-string-schema.tests'; +import { withObjectIdStringSchemaTests } from '../test-helpers/schemas/primitives/with-object-id-string-schema.tests'; + +describe('OBJECT_ID_SCHEMA', () => { + withObjectIdSchemaTests({ + schema: OBJECT_ID_SCHEMA, + getTestObjectWithUpdatedField: (newValue) => newValue, }); }); describe('OBJECT_ID_STRING', () => { - withSchemaTests({ - successTestCases: getObjectIdStringSuccessTestCases(), - failureTestCases: getObjectIdSharedFailureTestCases(), - schema: OBJECT_ID_STRING, - }); - - it('should transform a valid ObjectId to a string', () => { - const objectId = new ObjectId(); - - const result = OBJECT_ID_STRING.parse(objectId); - - expect(result).toEqual(objectId.toString()); + withObjectIdStringSchemaTests({ + schema: OBJECT_ID_STRING_SCHEMA, + getTestObjectWithUpdatedField: (newValue) => newValue, }); }); describe('OBJECT_ID_OR_OBJECT_ID_STRING', () => { - withSchemaTests({ - successTestCases: [...getObjectIdSuccessTestCases(), ...getObjectIdStringSuccessTestCases()], - failureTestCases: getObjectIdSharedFailureTestCases(), - schema: OBJECT_ID_OR_OBJECT_ID_STRING, - }); - - it('should not transform a valid ObjectId to a string', () => { - const objectId = new ObjectId(); - - const result = OBJECT_ID_OR_OBJECT_ID_STRING.parse(objectId); - - expect(result).toEqual(objectId); - }); - - it('should not transform a valid string ObjectId to an ObjectId', () => { - const stringObjectId = new ObjectId().toString(); - - const result = OBJECT_ID_OR_OBJECT_ID_STRING.parse(stringObjectId); - - expect(result).toEqual(stringObjectId); + withObjectIdOrObjectIdStringSchemaTests({ + schema: OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA, + getTestObjectWithUpdatedField: (newValue) => newValue, }); }); - -function getObjectIdSuccessTestCases() { - return [{ description: 'a valid ObjectId', aTestCase: () => new ObjectId() }]; -} - -function getObjectIdStringSuccessTestCases() { - return [{ description: 'a valid string ObjectId', aTestCase: () => '075bcd157dcb851180e02a7c' }]; -} - -function getObjectIdSharedFailureTestCases() { - return [ - { description: 'a string', aTestCase: () => 'string' }, - { description: 'an object', aTestCase: () => ({ An: 'object' }) }, - { description: 'an array', aTestCase: () => ['array'] }, - ]; -} diff --git a/libs/common/src/schemas/object-id.ts b/libs/common/src/schemas/object-id.ts index 5e9d5b5e92..80596a79cb 100644 --- a/libs/common/src/schemas/object-id.ts +++ b/libs/common/src/schemas/object-id.ts @@ -5,7 +5,7 @@ import z from 'zod'; * A zod schema that represents a valid ObjectId as an ObjectId object * This schema also transforms any valid string into an ObjectId object */ -export const OBJECT_ID = z.union([ +export const OBJECT_ID_SCHEMA = z.union([ z.instanceof(ObjectId), z .string() @@ -17,10 +17,10 @@ export const OBJECT_ID = z.union([ * A zod schema that represents a valid ObjectId as a string * This schema also transforms any valid ObjectId object into a string */ -export const OBJECT_ID_STRING = z.union([z.string().refine((id) => ObjectId.isValid(id)), z.instanceof(ObjectId).transform((id) => id.toString())]); +export const OBJECT_ID_STRING_SCHEMA = z.union([z.string().refine((id) => ObjectId.isValid(id)), z.instanceof(ObjectId).transform((id) => id.toString())]); /** * A zod schema that represents a valid ObjectId as an ObjectId object or a string * This schema does not do any transformation */ -export const OBJECT_ID_OR_OBJECT_ID_STRING = z.union([z.instanceof(ObjectId), OBJECT_ID_STRING, z.string().refine((id) => ObjectId.isValid(id))]); +export const OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA = z.union([z.instanceof(ObjectId), OBJECT_ID_STRING_SCHEMA, z.string().refine((id) => ObjectId.isValid(id))]); diff --git a/libs/common/src/schemas/portal-user.create.test.ts b/libs/common/src/schemas/portal-user.create.test.ts index 8095eb6bb3..7f518eab7b 100644 --- a/libs/common/src/schemas/portal-user.create.test.ts +++ b/libs/common/src/schemas/portal-user.create.test.ts @@ -1,11 +1,11 @@ import { ObjectId } from 'mongodb'; import { generatePortalUserAuditDatabaseRecord } from '../change-stream'; import { CREATE } from './portal-user'; -import { withSchemaTests } from '../test-helpers'; +import { withSchemaValidationTests } from '../test-helpers'; describe('PORTAL_USER', () => { describe('CREATE', () => { - withSchemaTests({ + withSchemaValidationTests({ successTestCases: getSuccessTestCases(), failureTestCases: getFailureTestCases(), schema: CREATE, diff --git a/libs/common/src/schemas/portal-user.ts b/libs/common/src/schemas/portal-user.ts index 8d955c3c69..2a5a9c9927 100644 --- a/libs/common/src/schemas/portal-user.ts +++ b/libs/common/src/schemas/portal-user.ts @@ -1,5 +1,5 @@ import z from 'zod'; -import { AUDIT_DATABASE_RECORD } from './audit-database-record'; +import { AUDIT_DATABASE_RECORD_SCHEMA } from './audit-database-record.schema'; import { UNIX_TIMESTAMP_MILLISECONDS_SCHEMA } from './unix-timestamp.schema'; const BASE_PORTAL_USER_SCHEMA = z @@ -14,7 +14,7 @@ const BASE_PORTAL_USER_SCHEMA = z 'user-status': z.string(), salt: z.string(), hash: z.string(), - auditRecord: AUDIT_DATABASE_RECORD, + auditRecord: AUDIT_DATABASE_RECORD_SCHEMA, isTrusted: z.boolean(), disabled: z.boolean().optional(), }) diff --git a/libs/common/src/schemas/portal-user.update.test.ts b/libs/common/src/schemas/portal-user.update.test.ts index 2881dbec72..5ebd457d26 100644 --- a/libs/common/src/schemas/portal-user.update.test.ts +++ b/libs/common/src/schemas/portal-user.update.test.ts @@ -1,11 +1,11 @@ import { ObjectId } from 'mongodb'; import { generatePortalUserAuditDatabaseRecord } from '../change-stream'; import { UPDATE } from './portal-user'; -import { withSchemaTests } from '../test-helpers'; +import { withSchemaValidationTests } from '../test-helpers'; describe('PORTAL_USER', () => { describe('UPDATE', () => { - withSchemaTests({ + withSchemaValidationTests({ successTestCases: getSuccessTestCases(), failureTestCases: getFailureTestCases(), schema: UPDATE, diff --git a/libs/common/src/schemas/tfm/create-tfm-user-request.schema.test.ts b/libs/common/src/schemas/tfm/create-tfm-user-request.schema.test.ts index cb80cd2c3a..a6bd2b5ea3 100644 --- a/libs/common/src/schemas/tfm/create-tfm-user-request.schema.test.ts +++ b/libs/common/src/schemas/tfm/create-tfm-user-request.schema.test.ts @@ -1,8 +1,44 @@ -import { withCreateTfmUserRequestSchemaTests } from '../../test-helpers'; +import { TEAM_IDS } from '../../constants'; +import { withSchemaValidationTests } from '../../test-helpers'; import { CREATE_TFM_USER_REQUEST_SCHEMA } from './create-tfm-user-request.schema'; describe('CREATE_TFM_USER_REQUEST_SCHEMA', () => { - withCreateTfmUserRequestSchemaTests({ + withSchemaValidationTests({ schema: CREATE_TFM_USER_REQUEST_SCHEMA, + aValidPayload: () => ({ + username: 'username', + email: 'email', + teams: [TEAM_IDS.PIM], + timezone: 'timezone', + firstName: 'firstName', + lastName: 'lastName', + azureOid: 'azureOid', + }), + testCases: [ + { + parameterPath: 'username', + type: 'string', + }, + { + parameterPath: 'email', + type: 'string', + }, + { + parameterPath: 'timezone', + type: 'string', + }, + { + parameterPath: 'firstName', + type: 'string', + }, + { + parameterPath: 'lastName', + type: 'string', + }, + { + parameterPath: 'azureOid', + type: 'string', + }, + ], }); }); diff --git a/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts index 979c306c00..d5573ba669 100644 --- a/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts @@ -1,9 +1,9 @@ -import { withSchemaTests } from '../../test-helpers'; +import { withSchemaValidationTests } from '../../test-helpers'; import { DecodedAuthCodeRequestState } from '../../types/tfm/entra-id'; import { DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA } from './entra-id.schema'; describe('DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA', () => { - withSchemaTests({ + withSchemaValidationTests({ schema: DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA, failureTestCases: getFailureTestCases(), successTestCases: getSuccessTestCases(), diff --git a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts index 547720e6d2..a249823f8a 100644 --- a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts @@ -1,9 +1,9 @@ -import { withSchemaTests } from '../../test-helpers'; +import { withSchemaValidationTests } from '../../test-helpers'; import { EntraIdAuthCodeRedirectResponseBody } from '../../types/tfm/entra-id'; import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from './entra-id.schema'; describe('ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA', () => { - withSchemaTests({ + withSchemaValidationTests({ schema: ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA, failureTestCases: getFailureTestCases(), successTestCases: getSuccessTestCases(), diff --git a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts index 9f714159db..0881de00e7 100644 --- a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts @@ -1,4 +1,4 @@ -import { anEntraIdUser, withEntraIdUserSchemaTests, withSchemaTests } from '../../test-helpers'; +import { anEntraIdUser, withEntraIdUserSchemaTests, withSchemaValidationTests } from '../../test-helpers'; import { EntraIdAuthenticationResult } from '../../types/tfm/entra-id'; import { ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA } from './entra-id.schema'; @@ -11,7 +11,7 @@ describe('ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA', () => { }), }); - withSchemaTests({ + withSchemaValidationTests({ schema: ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA, failureTestCases: getFailureTestCases(), successTestCases: getSuccessTestCases(), diff --git a/libs/common/src/schemas/tfm/tfm-session-user.schema.ts b/libs/common/src/schemas/tfm/tfm-session-user.schema.ts index 1a574118b9..5a6592b6cb 100644 --- a/libs/common/src/schemas/tfm/tfm-session-user.schema.ts +++ b/libs/common/src/schemas/tfm/tfm-session-user.schema.ts @@ -1,5 +1,5 @@ import { TFM_USER_SCHEMA } from './tfm-user.schema'; -import { OBJECT_ID_STRING } from '../object-id'; +import { OBJECT_ID_STRING_SCHEMA } from '../object-id'; export const TFM_SESSION_USER_SCHEMA = TFM_USER_SCHEMA.pick({ username: true, @@ -10,4 +10,4 @@ export const TFM_SESSION_USER_SCHEMA = TFM_USER_SCHEMA.pick({ lastName: true, status: true, lastLogin: true, -}).extend({ _id: OBJECT_ID_STRING }); // _id is extended as a string as apposed to objectId due to existing implimentation of TFM session user +}).extend({ _id: OBJECT_ID_STRING_SCHEMA }); // _id is extended as a string as apposed to objectId due to existing implimentation of TFM session user diff --git a/libs/common/src/schemas/tfm/tfm-team.schema.test.ts b/libs/common/src/schemas/tfm/tfm-team.schema.test.ts index 64a2450e85..1477adc532 100644 --- a/libs/common/src/schemas/tfm/tfm-team.schema.test.ts +++ b/libs/common/src/schemas/tfm/tfm-team.schema.test.ts @@ -1,37 +1,10 @@ -import { ALL_TEAM_IDS } from '../../constants'; -import { TeamId } from '../../types'; +import { withTfmTeamSchemaTests } from '../../test-helpers/schemas/primitives/with-tfm-team-schema.tests'; import { TfmTeamSchema } from './tfm-team.schema'; describe('tfm-team.schema', () => { - describe('TfmTeamSchema', () => { - it.each(ALL_TEAM_IDS)("sets the 'success' property to true when the team is '%s'", (team) => { - // Act - const { success } = TfmTeamSchema.safeParse(team); - - // Assert - expect(success).toEqual(true); - }); - - it("sets the 'success' property to false when the team is not a valid team", () => { - // Arrange - const invalidTeam = 'some-team'; - - // Act - const { success } = TfmTeamSchema.safeParse(invalidTeam); - - // Assert - expect(success).toEqual(false); - }); - - it("set the 'data' property to the parsed team", () => { - // Arrange - const team: TeamId = 'PDC_READ'; - - // Act - const { data } = TfmTeamSchema.safeParse(team); - - // Assert - expect(data).toEqual(team); - }); + withTfmTeamSchemaTests({ + schema: TfmTeamSchema, + getTestObjectWithUpdatedField: (newValue: unknown) => newValue, + options: { isOptional: false }, }); }); diff --git a/libs/common/src/schemas/tfm/tfm-user.schema.test.ts b/libs/common/src/schemas/tfm/tfm-user.schema.test.ts new file mode 100644 index 0000000000..e4d95a4507 --- /dev/null +++ b/libs/common/src/schemas/tfm/tfm-user.schema.test.ts @@ -0,0 +1,121 @@ +import { ObjectId } from 'mongodb'; +import { withSchemaValidationTests } from '../../test-helpers'; +import { TfmUser } from '../../types'; +import { TFM_USER_SCHEMA } from './tfm-user.schema'; +import { TEAM_IDS } from '../../constants'; + +describe('TFM_USER_SCHEMA', () => { + withSchemaValidationTests({ + schema: TFM_USER_SCHEMA, + aValidPayload, + testCases: [ + { + parameterPath: '_id', + type: 'OBJECT_ID_SCHEMA', + }, + { + parameterPath: 'username', + type: 'string', + }, + { + parameterPath: 'email', + type: 'string', + }, + { + parameterPath: 'timezone', + type: 'string', + }, + { + parameterPath: 'firstName', + type: 'string', + }, + { + parameterPath: 'lastName', + type: 'string', + }, + { + parameterPath: 'status', + type: 'string', + }, + { + parameterPath: 'sessionIdentifier', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'salt', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'hash', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'loginFailureCount', + type: 'number', + options: { isOptional: true }, + }, + { + parameterPath: 'azureOid', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'teams', + type: 'Array', + options: { + arrayTypeTestCase: { + type: 'TfmTeamSchema', + }, + }, + }, + ], + }); + + // withTeamIdSchemaTests({ + // parameterName: 'teams', + // schema: TFM_USER_SCHEMA, + // getTestObjectWithUpdatedField: (newValue) => ({ ...aValidPayload(), teams: newValue }), + // }); + + // withUnixTimestampMillisecondsSchemaTests({ + // parameterName: 'lastLogin', + // schema: TFM_USER_SCHEMA, + // getTestObjectWithUpdatedField: (newValue) => ({ ...aValidPayload(), lastLogin: newValue }), + // }); + + // withAuditDatabaseRecordSchemaTests({}); + // withSchemaTests({ + // schema: TFM_USER_SCHEMA, + // failureTestCases: getFailureTestCases(), + // successTestCases: getSuccessTestCases(), + // }); + + function aValidPayload(): TfmUser { + return { + _id: new ObjectId(), // done + username: 'test-user', // done + email: 'test-user@test.com', // done + teams: [TEAM_IDS.PIM], // done, + timezone: 'Europe/London', // done + firstName: 'FirstName', // done + lastName: 'LastName', // done + status: 'active', // done + lastLogin: 1234567890123, + sessionIdentifier: 'a-session-identifier', // done, + auditRecord: { + lastUpdatedAt: '2024-05-17T15:35:32.496 +00:00', + lastUpdatedByIsSystem: true, + lastUpdatedByPortalUserId: null, + lastUpdatedByTfmUserId: null, + noUserLoggedIn: null, + }, + salt: 'a-salt', // done + hash: 'a-hash', // done + loginFailureCount: 0, // done + azureOid: 'a-azure-oid', // done + }; + } +}); diff --git a/libs/common/src/schemas/tfm/tfm-user.schema.ts b/libs/common/src/schemas/tfm/tfm-user.schema.ts index a9d9e71d9f..33947def11 100644 --- a/libs/common/src/schemas/tfm/tfm-user.schema.ts +++ b/libs/common/src/schemas/tfm/tfm-user.schema.ts @@ -1,8 +1,8 @@ import z from 'zod'; import { TfmTeamSchema } from './tfm-team.schema'; import { UNIX_TIMESTAMP_MILLISECONDS_SCHEMA } from '../unix-timestamp.schema'; -import { AUDIT_DATABASE_RECORD } from '../audit-database-record'; -import { OBJECT_ID } from '../object-id'; +import { AUDIT_DATABASE_RECORD_SCHEMA } from '../audit-database-record.schema'; +import { OBJECT_ID_SCHEMA } from '../object-id'; // TODO update docs, tests const TFM_USER_NON_SSO_SPECIFIC_SCHEMA = z.object({ @@ -16,7 +16,7 @@ const TFM_USER_SSO_SPECIFIC_SCHEMA = z.object({ }); const BASE_TFM_USER_SCHEMA = z.object({ - _id: OBJECT_ID, + _id: OBJECT_ID_SCHEMA, username: z.string(), email: z.string(), teams: z.array(TfmTeamSchema), @@ -26,7 +26,7 @@ const BASE_TFM_USER_SCHEMA = z.object({ status: z.string(), lastLogin: UNIX_TIMESTAMP_MILLISECONDS_SCHEMA.optional(), sessionIdentifier: z.string().optional(), - auditRecord: AUDIT_DATABASE_RECORD.optional(), + auditRecord: AUDIT_DATABASE_RECORD_SCHEMA.optional(), }); export const TFM_USER_SCHEMA = BASE_TFM_USER_SCHEMA.merge(TFM_USER_NON_SSO_SPECIFIC_SCHEMA.partial()).merge(TFM_USER_SSO_SPECIFIC_SCHEMA.partial()); diff --git a/libs/common/src/test-helpers/schemas/index.ts b/libs/common/src/test-helpers/schemas/index.ts index 7913594144..4c1c3dd3fd 100644 --- a/libs/common/src/test-helpers/schemas/index.ts +++ b/libs/common/src/test-helpers/schemas/index.ts @@ -1,3 +1,4 @@ +export * from './primitives'; export * from './with-schema.tests'; export * from './with-entra-id-user-schema.tests'; export * from './with-create-tfm-user-request-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/primitives/get-tests-for-parameter.tests.ts b/libs/common/src/test-helpers/schemas/primitives/get-tests-for-parameter.tests.ts new file mode 100644 index 0000000000..96663e9ccd --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/get-tests-for-parameter.tests.ts @@ -0,0 +1,141 @@ +import { ZodSchema } from 'zod'; +import { withNumberTests } from './with-number.tests'; +import { withObjectIdSchemaTests } from './with-object-id-schema.tests'; +import { withStringTests } from './with-string.tests'; +import { withTfmTeamSchemaTests } from './with-tfm-team-schema.tests'; +import { withArrayTests, WithArrayTestsOptions } from './with-array.tests'; +import { withIsoDateTimeStampSchemaTests } from './with-iso-date-time-stamp-schema.tests'; +import { withAuditDatabaseRecordSchemaTests } from './with-audit-database-record-schema.tests'; +import { withObjectIdOrObjectIdStringSchemaTests } from './with-object-id-or-object-id-string-schema.tests'; +import { withObjectIdStringSchemaTests } from './with-object-id-string-schema.tests'; +import { DefaultOptions } from './with-default-options.tests'; +import { withBooleanTests } from './with-boolean.tests'; + +export type TestCaseTypes = + | 'string' + | 'number' + | 'boolean' + | 'Array' + | 'TfmTeamSchema' + | 'OBJECT_ID_SCHEMA' + | 'OBJECT_ID_STRING_SCHEMA' + | 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA' + | 'ISO_DATE_TIME_STAMP_SCHEMA' + | 'AUDIT_DATABASE_RECORD_SCHEMA'; + +type BaseTestCaseWithType = { + type: Type; + options?: Partial; +}; + +type BaseTestCaseWithTypeRequiredOptions = BaseTestCaseWithType & { + options: OptionsType; +}; +export type TestCase = + | BaseTestCaseWithType<'string'> + | BaseTestCaseWithType<'number'> + | BaseTestCaseWithType<'boolean'> + | BaseTestCaseWithTypeRequiredOptions<'Array', WithArrayTestsOptions> + | BaseTestCaseWithType<'TfmTeamSchema'> + | BaseTestCaseWithType<'OBJECT_ID_SCHEMA'> + | BaseTestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> + | BaseTestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> + | BaseTestCaseWithType<'ISO_DATE_TIME_STAMP_SCHEMA'> + | BaseTestCaseWithType<'AUDIT_DATABASE_RECORD_SCHEMA'>; + +export const getTestsForParameter = ({ + schema, + testCase, + getTestObjectWithUpdatedField, +}: { + schema: Schema; + testCase: TestCase; + getTestObjectWithUpdatedField: (newValue: unknown) => unknown; +}) => { + const { type, options } = testCase; + + switch (type) { + case 'string': + withStringTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + + case 'number': + withNumberTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + + case 'boolean': + withBooleanTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + + case 'Array': + withArrayTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + + case 'TfmTeamSchema': + withTfmTeamSchemaTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + + case 'OBJECT_ID_SCHEMA': + withObjectIdSchemaTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + + case 'OBJECT_ID_STRING_SCHEMA': + withObjectIdStringSchemaTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + + case 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA': + withObjectIdOrObjectIdStringSchemaTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + + case 'ISO_DATE_TIME_STAMP_SCHEMA': + withIsoDateTimeStampSchemaTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + + case 'AUDIT_DATABASE_RECORD_SCHEMA': + withAuditDatabaseRecordSchemaTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + + default: + throw Error(`There are no existing test cases for the type ${type}`); + } +}; diff --git a/libs/common/src/test-helpers/schemas/primitives/index.ts b/libs/common/src/test-helpers/schemas/primitives/index.ts new file mode 100644 index 0000000000..d2feb2609f --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/index.ts @@ -0,0 +1 @@ +export * from './with-schema-validation.tests'; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-array.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-array.tests.ts new file mode 100644 index 0000000000..9223ea75e0 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-array.tests.ts @@ -0,0 +1,40 @@ +import { ZodSchema } from 'zod'; +import { WithSchemaTestParams } from './with-schema-test.type'; +import { getTestsForParameter, TestCase } from './get-tests-for-parameter.tests'; +import { withDefaultOptionsTests } from './with-default-options.tests'; + +export type WithArrayTestsOptions = { + arrayTypeTestCase: TestCase; +}; + +export const withArrayTests = ({ + schema, + options, + getTestObjectWithUpdatedField, +}: WithSchemaTestParams) => { + describe('with array tests', () => { + withDefaultOptionsTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + + it('should fail parsing if the parameter is not a array', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField('not an array')); + expect(success).toBe(false); + }); + + it('should pass parsing if the parameter is an empty array', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField([])); + expect(success).toBe(true); + }); + + describe('when configuring the objects in the array', () => { + getTestsForParameter({ + schema, + testCase: options.arrayTypeTestCase, + getTestObjectWithUpdatedField: (value) => getTestObjectWithUpdatedField([value]), + }); + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-audit-database-record-schema.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-audit-database-record-schema.tests.ts new file mode 100644 index 0000000000..44b142da52 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-audit-database-record-schema.tests.ts @@ -0,0 +1,73 @@ +import { ZodSchema } from 'zod'; +import { ObjectId } from 'mongodb'; +import { WithSchemaTestParams } from './with-schema-test.type'; +import { generateTfmUserAuditDatabaseRecord } from '../../../change-stream'; +import { withSchemaValidationTests } from './with-schema-validation.tests'; +import { withDefaultOptionsTests } from './with-default-options.tests'; + +export const withAuditDatabaseRecordSchemaTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedField, +}: WithSchemaTestParams) => { + describe('with AUDIT_DATABASE_RECORD_SCHEMA tests', () => { + withDefaultOptionsTests({ + schema, + getTestObjectWithUpdatedField, + options, + }); + + withSchemaValidationTests({ + schema, + aValidPayload: aValidAuditRecord, + testCases: [ + { + parameterPath: 'lastUpdatedAt', + type: 'ISO_DATE_TIME_STAMP_SCHEMA', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedField({ ...aValidAuditRecord(), lastUpdatedAt: newValue }), + }, + }, + { + parameterPath: 'lastUpdatedByPortalUserId', + type: 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => + getTestObjectWithUpdatedField({ ...aValidAuditRecord(), lastUpdatedByPortalUserId: newValue }), + isNullable: true, + }, + }, + { + parameterPath: 'lastUpdatedByTfmUserId', + type: 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => + getTestObjectWithUpdatedField({ ...aValidAuditRecord(), lastUpdatedByPortalUserId: newValue }), + isNullable: true, + }, + }, + { + parameterPath: 'lastUpdatedByIsSystem', + type: 'boolean', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => + getTestObjectWithUpdatedField({ ...aValidAuditRecord(), lastUpdatedByIsSystem: newValue }), + isNullable: true, + }, + }, + { + parameterPath: 'noUserLoggedIn', + type: 'boolean', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedField({ ...aValidAuditRecord(), noUserLoggedIn: newValue }), + isNullable: true, + }, + }, + ], + }); + }); + + function aValidAuditRecord() { + return generateTfmUserAuditDatabaseRecord(new ObjectId()); + } +}; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-boolean.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-boolean.tests.ts new file mode 100644 index 0000000000..326f028c37 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-boolean.tests.ts @@ -0,0 +1,23 @@ +import { ZodSchema } from 'zod'; +import { WithSchemaTestParams } from './with-schema-test.type'; +import { withDefaultOptionsTests } from './with-default-options.tests'; + +export const withBooleanTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { + describe('with boolean tests', () => { + withDefaultOptionsTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + + it('should fail parsing if the parameter is not a boolean', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField('true')); + expect(success).toBe(false); + }); + + it('should pass parsing if the parameter is a boolean', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(true)); + expect(success).toBe(true); + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-default-options.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-default-options.tests.ts new file mode 100644 index 0000000000..22bb765dc3 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-default-options.tests.ts @@ -0,0 +1,60 @@ +import { ZodSchema } from 'zod'; +import { WithSchemaTestParams } from './with-schema-test.type'; + +export type DefaultOptions = { + isOptional?: boolean; + isNullable?: boolean; +}; + +export const withDefaultOptionsTests = ({ + schema, + getTestObjectWithUpdatedField, + options = {}, +}: WithSchemaTestParams) => { + const defaultOptions: DefaultOptions = { + ...{ + isOptional: false, + isNullable: false, + }, + ...options, + }; + if (defaultOptions.isOptional) { + withIsOptionalTests(); + } else { + withIsRequiredTests(); + } + + if (defaultOptions.isNullable) { + withIsNullableTrueTests(); + } else { + withIsNullableFalseTests(); + } + + function withIsOptionalTests() { + it('should pass parsing if the parameter is missing', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(undefined)); + expect(success).toBe(true); + }); + } + + function withIsRequiredTests() { + it('should fail parsing if the parameter is missing', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(undefined)); + expect(success).toBe(false); + }); + } + + function withIsNullableTrueTests() { + it('should pass parsing if the parameter is null', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(null)); + expect(success).toBe(true); + }); + } + + function withIsNullableFalseTests() { + it('should fail parsing if the parameter is null', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(null)); + expect(success).toBe(false); + }); + } +}; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-iso-date-time-stamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-iso-date-time-stamp-schema.tests.ts new file mode 100644 index 0000000000..93e5ff399a --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-iso-date-time-stamp-schema.tests.ts @@ -0,0 +1,37 @@ +import { ZodSchema } from 'zod'; +import { WithSchemaTestParams } from './with-schema-test.type'; +import { withDefaultOptionsTests } from './with-default-options.tests'; + +export const withIsoDateTimeStampSchemaTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedField, +}: WithSchemaTestParams) => { + describe('with ISO_DATE_TIME_STAMP_SCHEMA tests', () => { + withDefaultOptionsTests({ + schema, + getTestObjectWithUpdatedField, + options, + }); + + it('should fail parsing if the parameter is not a string', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(1)); + expect(success).toBe(false); + }); + + it('should pass parsing if the parameter is a valid ISO date', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField('2024-05-17T15:35:32.496 +00:00')); + expect(success).toBe(true); + }); + + it('should fail parsing if the parameter is a incorrectly formatted ISO date', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField('2021-01-01T00:00:00')); + expect(success).toBe(false); + }); + + it('should fail parsing if the parameter is a incorrectly formatted ISO date', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField('2024-05-17X15:35:32.496 +00:00')); + expect(success).toBe(false); + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-number.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-number.tests.ts new file mode 100644 index 0000000000..60a4fd8871 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-number.tests.ts @@ -0,0 +1,23 @@ +import { ZodSchema } from 'zod'; +import { WithSchemaTestParams } from './with-schema-test.type'; +import { withDefaultOptionsTests } from './with-default-options.tests'; + +export const withNumberTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { + describe('with number tests', () => { + withDefaultOptionsTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + + it('should fail parsing if the parameter is not a number', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField('1')); + expect(success).toBe(false); + }); + + it('should pass parsing if the parameter is a number', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(1)); + expect(success).toBe(true); + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-object-id-or-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-object-id-or-object-id-string-schema.tests.ts new file mode 100644 index 0000000000..070e45e2f6 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-object-id-or-object-id-string-schema.tests.ts @@ -0,0 +1,36 @@ +import { ZodSchema } from 'zod'; +import { ObjectId } from 'mongodb'; +import { WithSchemaTestParams } from './with-schema-test.type'; +import { withDefaultOptionsTests } from './with-default-options.tests'; + +export const withObjectIdOrObjectIdStringSchemaTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedField, +}: WithSchemaTestParams) => { + describe('with OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA tests', () => { + withDefaultOptionsTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + + it('should fail parsing if the parameter is not an ObjectId', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField('string')); + expect(success).toBe(false); + }); + + it('should pass parsing if the parameter is an ObjectId', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(new ObjectId())); + expect(success).toBe(true); + }); + + it('should pass parsing if the parameter is a string representation of an ObjectId', () => { + const stringObjectId = new ObjectId().toString(); + + const { success } = schema.safeParse(getTestObjectWithUpdatedField(stringObjectId)); + + expect(success).toBe(true); + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-object-id-schema.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-object-id-schema.tests.ts new file mode 100644 index 0000000000..b86c04ffec --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-object-id-schema.tests.ts @@ -0,0 +1,41 @@ +import { ObjectId } from 'mongodb'; +import { ZodSchema } from 'zod'; +import { WithSchemaTestParams } from './with-schema-test.type'; +import { withDefaultOptionsTests } from './with-default-options.tests'; + +export const withObjectIdSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { + describe('with OBJECT_ID_SCHEMA tests', () => { + withDefaultOptionsTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + it('should fail parsing if the parameter is not an ObjectId', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField('string')); + expect(success).toBe(false); + }); + + it('should pass parsing if the parameter is an ObjectId', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(new ObjectId())); + expect(success).toBe(true); + }); + + it('should not transform a valid ObjectId to a string', () => { + const objectId = new ObjectId(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { data } = schema.safeParse(getTestObjectWithUpdatedField(objectId)); + + expect(data).toEqual(getTestObjectWithUpdatedField(objectId)); + }); + + it('should transform a valid string ObjectId to an ObjectId', () => { + const stringObjectId = new ObjectId().toString(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { data } = schema.safeParse(getTestObjectWithUpdatedField(stringObjectId)); + + expect(data).toEqual(getTestObjectWithUpdatedField(new ObjectId(stringObjectId))); + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-object-id-string-schema.tests.ts new file mode 100644 index 0000000000..24a7d6ea5a --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-object-id-string-schema.tests.ts @@ -0,0 +1,48 @@ +import { ObjectId } from 'mongodb'; +import { ZodSchema } from 'zod'; +import { WithSchemaTestParams } from './with-schema-test.type'; +import { withDefaultOptionsTests } from './with-default-options.tests'; + +export const withObjectIdStringSchemaTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedField, +}: WithSchemaTestParams) => { + describe('with OBJECT_ID_STRING_SCHEMA tests', () => { + withDefaultOptionsTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + it('should fail parsing if the parameter is not an ObjectId', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField('string')); + expect(success).toBe(false); + }); + + it('should pass parsing if the parameter is a valid string representation of an ObjectId', () => { + const stringObjectId = new ObjectId().toString(); + + const { success } = schema.safeParse(getTestObjectWithUpdatedField(stringObjectId)); + + expect(success).toBe(true); + }); + + it('should not transform a valid string ObjectId to an ObjectId', () => { + const stringObjectId = new ObjectId().toString(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { data } = schema.safeParse(getTestObjectWithUpdatedField(stringObjectId)); + + expect(data).toEqual(getTestObjectWithUpdatedField(stringObjectId)); + }); + + it('should transform a valid ObjectId to a string', () => { + const objectId = new ObjectId(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { data } = schema.safeParse(getTestObjectWithUpdatedField(objectId)); + + expect(data).toEqual(getTestObjectWithUpdatedField(objectId.toString())); + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-schema-test.type.ts b/libs/common/src/test-helpers/schemas/primitives/with-schema-test.type.ts new file mode 100644 index 0000000000..7a073d521c --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-schema-test.type.ts @@ -0,0 +1,10 @@ +import { ZodSchema } from 'zod'; +import { DefaultOptions } from './with-default-options.tests'; + +export type SchemaTestOptionsRequired = 'Options Required'; +export type SchemaTestOptionsOptional = 'Options Optional'; + +export type WithSchemaTestParams = { + schema: Schema; + getTestObjectWithUpdatedField: (newValue: unknown) => unknown; +} & (SchemaTestOptions extends false ? { options?: Partial } : { options: SchemaTestOptions & Partial }); diff --git a/libs/common/src/test-helpers/schemas/primitives/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-schema-validation.tests.ts new file mode 100644 index 0000000000..fc3c34043f --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-schema-validation.tests.ts @@ -0,0 +1,42 @@ +import { z, ZodSchema } from 'zod'; +import { getTestsForParameter, TestCase } from './get-tests-for-parameter.tests'; + +/** + * This base option allows for overriding of the automatically generated getTestObjectWithUpdatedField function. + * It is useful when looking at testing nested objects, but otherwise can be ignored + */ +type BaseOptions = { + overrideGetTestObjectWithUpdatedField?: (newValue: unknown) => unknown; +}; + +export type TestCaseWithPathParameter = { + parameterPath: string; + options?: BaseOptions; +} & TestCase; + +export const withSchemaValidationTests = ({ + schema, + aValidPayload, + testCases, +}: { + schema: Schema; + testCases: TestCaseWithPathParameter[]; + aValidPayload: () => z.infer; +}) => { + testCases.forEach((testCase) => { + const { parameterPath } = testCase; + + const getTestObjectWithUpdatedField = + testCase.options?.overrideGetTestObjectWithUpdatedField !== undefined + ? testCase.options.overrideGetTestObjectWithUpdatedField + : (newValue: unknown): unknown => ({ ...aValidPayload(), [parameterPath]: newValue }); + + describe(`${parameterPath} parameter tests`, () => { + getTestsForParameter({ + schema, + testCase, + getTestObjectWithUpdatedField, + }); + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-string.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-string.tests.ts new file mode 100644 index 0000000000..93f51f36d2 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-string.tests.ts @@ -0,0 +1,23 @@ +import { ZodSchema } from 'zod'; +import { WithSchemaTestParams } from './with-schema-test.type'; +import { withDefaultOptionsTests } from './with-default-options.tests'; + +export const withStringTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { + describe('with string tests', () => { + withDefaultOptionsTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + + it('should fail parsing if the parameter is not a string', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(1)); + expect(success).toBe(false); + }); + + it('should pass parsing if the parameter is a string', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField('a string')); + expect(success).toBe(true); + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-tfm-team-schema.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-tfm-team-schema.tests.ts new file mode 100644 index 0000000000..3e5335e406 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-tfm-team-schema.tests.ts @@ -0,0 +1,24 @@ +import { ZodSchema } from 'zod'; +import { WithSchemaTestParams } from './with-schema-test.type'; +import { TEAM_IDS } from '../../../constants'; +import { withDefaultOptionsTests } from './with-default-options.tests'; + +export const withTfmTeamSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { + describe('with TfmTeamSchema tests', () => { + withDefaultOptionsTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + + it('should fail parsing if the parameter is not a valid tfm-team', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField('not-a-team')); + expect(success).toBe(false); + }); + + it('should pass parsing if the parameter is a valid tfm-team', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(TEAM_IDS.PIM)); + expect(success).toBe(true); + }); + }); +}; From bdf5a87ac72160aed9b68795ddc7868713b89ef3 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 4 Dec 2024 14:27:27 +0000 Subject: [PATCH 022/133] feat(dtfs2-6892): update entra user id tests --- .../create-tfm-user-request.schema.test.ts | 32 ++-- ...d-user.schema.entra-id-user-schema.test.ts | 1 + ...code-redirect-response-body-schema.test.ts | 96 +++--------- ...ra-id-authentication-result-schema.test.ts | 66 ++------- .../src/schemas/tfm/tfm-user.schema.test.ts | 50 +++---- libs/common/src/test-helpers/schemas/index.ts | 4 +- .../get-tests-for-parameter.tests.ts | 37 +++-- .../schemas/primitives/with-array.tests.ts | 24 ++- .../with-entra-id-user-schema.tests.ts | 80 ++++++++++ .../with-entra-id-user-schema.tests.ts | 140 ------------------ 10 files changed, 203 insertions(+), 327 deletions(-) create mode 100644 libs/common/src/test-helpers/schemas/primitives/with-entra-id-user-schema.tests.ts delete mode 100644 libs/common/src/test-helpers/schemas/with-entra-id-user-schema.tests.ts diff --git a/libs/common/src/schemas/tfm/create-tfm-user-request.schema.test.ts b/libs/common/src/schemas/tfm/create-tfm-user-request.schema.test.ts index a6bd2b5ea3..85e5d66c23 100644 --- a/libs/common/src/schemas/tfm/create-tfm-user-request.schema.test.ts +++ b/libs/common/src/schemas/tfm/create-tfm-user-request.schema.test.ts @@ -1,19 +1,12 @@ import { TEAM_IDS } from '../../constants'; import { withSchemaValidationTests } from '../../test-helpers'; +import { CreateTfmUserRequest } from '../../types'; import { CREATE_TFM_USER_REQUEST_SCHEMA } from './create-tfm-user-request.schema'; describe('CREATE_TFM_USER_REQUEST_SCHEMA', () => { withSchemaValidationTests({ schema: CREATE_TFM_USER_REQUEST_SCHEMA, - aValidPayload: () => ({ - username: 'username', - email: 'email', - teams: [TEAM_IDS.PIM], - timezone: 'timezone', - firstName: 'firstName', - lastName: 'lastName', - azureOid: 'azureOid', - }), + aValidPayload, testCases: [ { parameterPath: 'username', @@ -23,6 +16,15 @@ describe('CREATE_TFM_USER_REQUEST_SCHEMA', () => { parameterPath: 'email', type: 'string', }, + { + parameterPath: 'teams', + type: 'Array', + options: { + arrayTypeTestCase: { + type: 'TfmTeamSchema', + }, + }, + }, { parameterPath: 'timezone', type: 'string', @@ -41,4 +43,16 @@ describe('CREATE_TFM_USER_REQUEST_SCHEMA', () => { }, ], }); + + function aValidPayload(): CreateTfmUserRequest { + return { + username: 'test-user', + email: 'test-user@test.com', + teams: [TEAM_IDS.PIM], + timezone: 'Europe/London', + firstName: 'FirstName', + lastName: 'LastName', + azureOid: 'test-azure-oid', + }; + } }); diff --git a/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts b/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts index 37aa7ec1f4..b84822f81a 100644 --- a/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts @@ -4,5 +4,6 @@ import { ENTRA_ID_USER_SCHEMA } from './entra-id-user.schema'; describe('ENTRA_ID_USER_SCHEMA', () => { withEntraIdUserSchemaTests({ schema: ENTRA_ID_USER_SCHEMA, + getTestObjectWithUpdatedField: (newValue: unknown) => newValue, }); }); diff --git a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts index a249823f8a..1dfca1245e 100644 --- a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts @@ -5,84 +5,30 @@ import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from './entra-id.sch describe('ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA', () => { withSchemaValidationTests({ schema: ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA, - failureTestCases: getFailureTestCases(), - successTestCases: getSuccessTestCases(), + aValidPayload, + testCases: [ + { + parameterPath: 'code', + type: 'string', + }, + { + parameterPath: 'client_info', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'state', + type: 'string', + }, + { + parameterPath: 'session_state', + type: 'string', + options: { isOptional: true }, + }, + ], }); }); function aValidPayload(): EntraIdAuthCodeRedirectResponseBody { return { code: 'a-code', client_info: 'a-client-info', state: 'a-state', session_state: 'a-session-state' }; } - -function getFailureTestCases() { - return [ - { - aTestCase: () => { - const { code: _code, ...rest } = aValidPayload(); - return rest; - }, - description: 'the code is missing', - }, - { - aTestCase: () => { - const { state: _state, ...rest } = aValidPayload(); - return rest; - }, - description: 'the state is missing', - }, - { - aTestCase: () => ({ ...aValidPayload(), code: 1 }), - description: 'the code is not a string', - }, - { - aTestCase: () => ({ ...aValidPayload(), client_info: 1 }), - description: 'the client_info is not a string', - }, - { - aTestCase: () => ({ ...aValidPayload(), state: 1 }), - description: 'the state is not a string', - }, - { - aTestCase: () => ({ ...aValidPayload(), session_state: 1 }), - description: 'the session_state is not a string', - }, - - { - aTestCase: () => ({}), - description: 'the object is empty', - }, - ]; -} - -function getSuccessTestCases() { - return [ - { aTestCase: aValidPayload, description: 'a complete valid payload is present' }, - { - aTestCase: () => { - const { client_info: _clientInfo, ...rest } = aValidPayload(); - return rest; - }, - description: 'the optional client_info is missing', - }, - { - aTestCase: () => { - const { session_state: _sessionState, ...rest } = aValidPayload(); - return rest; - }, - description: 'the optional session_state is missing', - }, - { - aTestCase: () => { - const { client_info: _clientInfo, session_state: _sessionState, ...rest } = aValidPayload(); - return rest; - }, - description: 'the optional client_info and session_state is missing', - }, - { - aTestCase: () => { - return { ...aValidPayload(), extraField: 'extra' }; - }, - description: 'there is an extra field', - }, - ]; -} diff --git a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts index 0881de00e7..fb18e6b188 100644 --- a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts @@ -1,21 +1,22 @@ -import { anEntraIdUser, withEntraIdUserSchemaTests, withSchemaValidationTests } from '../../test-helpers'; +import { anEntraIdUser, withSchemaValidationTests } from '../../test-helpers'; import { EntraIdAuthenticationResult } from '../../types/tfm/entra-id'; import { ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA } from './entra-id.schema'; -describe('ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA', () => { - withEntraIdUserSchemaTests({ - schema: ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA, - getTestObjectWithUpdatedEntraIdUserParams: (entraIdUser) => ({ - ...aValidPayload(), - account: { idTokenClaims: entraIdUser }, - }), - }); +describe('ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA', () => {}); - withSchemaValidationTests({ - schema: ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA, - failureTestCases: getFailureTestCases(), - successTestCases: getSuccessTestCases(), - }); +withSchemaValidationTests({ + schema: ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA, + aValidPayload, + testCases: [ + { + parameterPath: 'accessToken', + type: 'string', + }, + { + parameterPath: 'account', + type: 'ENTRA_ID_USER_SCHEMA', + }, + ], }); function aValidPayload(): EntraIdAuthenticationResult { @@ -24,40 +25,3 @@ function aValidPayload(): EntraIdAuthenticationResult { account: { idTokenClaims: anEntraIdUser() }, }; } - -function getFailureTestCases() { - return [ - { - aTestCase: () => { - const { accessToken: _accessToken, ...rest } = aValidPayload(); - return rest; - }, - description: 'the access token is missing', - }, - { - aTestCase: () => ({ ...aValidPayload(), accessToken: 1 }), - description: 'the access token is not a string', - }, - { - aTestCase: () => { - const { accessToken } = aValidPayload(); - return { accessToken, account: {} }; - }, - description: 'the account does not have an id token claims', - }, - { - aTestCase: () => ({}), - description: 'the object is empty', - }, - ]; -} - -function getSuccessTestCases() { - return [ - { aTestCase: aValidPayload, description: 'a complete valid payload is present' }, - { - aTestCase: () => ({ ...aValidPayload(), extraField: 'extra' }), - description: 'there is an extra field', - }, - ]; -} diff --git a/libs/common/src/schemas/tfm/tfm-user.schema.test.ts b/libs/common/src/schemas/tfm/tfm-user.schema.test.ts index e4d95a4507..03b0b7719b 100644 --- a/libs/common/src/schemas/tfm/tfm-user.schema.test.ts +++ b/libs/common/src/schemas/tfm/tfm-user.schema.test.ts @@ -71,40 +71,26 @@ describe('TFM_USER_SCHEMA', () => { }, }, }, + { + parameterPath: 'auditRecord', + type: 'AUDIT_DATABASE_RECORD_SCHEMA', + options: { isOptional: true }, + }, ], }); - // withTeamIdSchemaTests({ - // parameterName: 'teams', - // schema: TFM_USER_SCHEMA, - // getTestObjectWithUpdatedField: (newValue) => ({ ...aValidPayload(), teams: newValue }), - // }); - - // withUnixTimestampMillisecondsSchemaTests({ - // parameterName: 'lastLogin', - // schema: TFM_USER_SCHEMA, - // getTestObjectWithUpdatedField: (newValue) => ({ ...aValidPayload(), lastLogin: newValue }), - // }); - - // withAuditDatabaseRecordSchemaTests({}); - // withSchemaTests({ - // schema: TFM_USER_SCHEMA, - // failureTestCases: getFailureTestCases(), - // successTestCases: getSuccessTestCases(), - // }); - function aValidPayload(): TfmUser { return { - _id: new ObjectId(), // done - username: 'test-user', // done - email: 'test-user@test.com', // done - teams: [TEAM_IDS.PIM], // done, - timezone: 'Europe/London', // done - firstName: 'FirstName', // done - lastName: 'LastName', // done - status: 'active', // done + _id: new ObjectId(), + username: 'test-user', + email: 'test-user@test.com', + teams: [TEAM_IDS.PIM], + timezone: 'Europe/London', + firstName: 'FirstName', + lastName: 'LastName', + status: 'active', lastLogin: 1234567890123, - sessionIdentifier: 'a-session-identifier', // done, + sessionIdentifier: 'a-session-identifier', auditRecord: { lastUpdatedAt: '2024-05-17T15:35:32.496 +00:00', lastUpdatedByIsSystem: true, @@ -112,10 +98,10 @@ describe('TFM_USER_SCHEMA', () => { lastUpdatedByTfmUserId: null, noUserLoggedIn: null, }, - salt: 'a-salt', // done - hash: 'a-hash', // done - loginFailureCount: 0, // done - azureOid: 'a-azure-oid', // done + salt: 'a-salt', + hash: 'a-hash', + loginFailureCount: 0, + azureOid: 'a-azure-oid', }; } }); diff --git a/libs/common/src/test-helpers/schemas/index.ts b/libs/common/src/test-helpers/schemas/index.ts index 4c1c3dd3fd..48df276e38 100644 --- a/libs/common/src/test-helpers/schemas/index.ts +++ b/libs/common/src/test-helpers/schemas/index.ts @@ -1,6 +1,6 @@ export * from './primitives'; export * from './with-schema.tests'; -export * from './with-entra-id-user-schema.tests'; +export * from './primitives/with-entra-id-user-schema.tests'; export * from './with-create-tfm-user-request-schema.tests'; export * from './with-upsert-tfm-user-request-schema.tests'; -export * from './with-entra-id-user-schema.tests'; +export * from './primitives/with-entra-id-user-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/primitives/get-tests-for-parameter.tests.ts b/libs/common/src/test-helpers/schemas/primitives/get-tests-for-parameter.tests.ts index 96663e9ccd..1bde4ce2f1 100644 --- a/libs/common/src/test-helpers/schemas/primitives/get-tests-for-parameter.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitives/get-tests-for-parameter.tests.ts @@ -10,6 +10,7 @@ import { withObjectIdOrObjectIdStringSchemaTests } from './with-object-id-or-obj import { withObjectIdStringSchemaTests } from './with-object-id-string-schema.tests'; import { DefaultOptions } from './with-default-options.tests'; import { withBooleanTests } from './with-boolean.tests'; +import { withEntraIdUserSchemaTests } from './with-entra-id-user-schema.tests'; export type TestCaseTypes = | 'string' @@ -21,27 +22,29 @@ export type TestCaseTypes = | 'OBJECT_ID_STRING_SCHEMA' | 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA' | 'ISO_DATE_TIME_STAMP_SCHEMA' - | 'AUDIT_DATABASE_RECORD_SCHEMA'; + | 'AUDIT_DATABASE_RECORD_SCHEMA' + | 'ENTRA_ID_USER_SCHEMA'; -type BaseTestCaseWithType = { +type TestCaseWithType = { type: Type; options?: Partial; }; -type BaseTestCaseWithTypeRequiredOptions = BaseTestCaseWithType & { +type TestCaseWithTypeAndRequiredOptions = TestCaseWithType & { options: OptionsType; }; export type TestCase = - | BaseTestCaseWithType<'string'> - | BaseTestCaseWithType<'number'> - | BaseTestCaseWithType<'boolean'> - | BaseTestCaseWithTypeRequiredOptions<'Array', WithArrayTestsOptions> - | BaseTestCaseWithType<'TfmTeamSchema'> - | BaseTestCaseWithType<'OBJECT_ID_SCHEMA'> - | BaseTestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> - | BaseTestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> - | BaseTestCaseWithType<'ISO_DATE_TIME_STAMP_SCHEMA'> - | BaseTestCaseWithType<'AUDIT_DATABASE_RECORD_SCHEMA'>; + | TestCaseWithType<'string'> + | TestCaseWithType<'number'> + | TestCaseWithType<'boolean'> + | TestCaseWithTypeAndRequiredOptions<'Array', WithArrayTestsOptions> + | TestCaseWithType<'TfmTeamSchema'> + | TestCaseWithType<'OBJECT_ID_SCHEMA'> + | TestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> + | TestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> + | TestCaseWithType<'ISO_DATE_TIME_STAMP_SCHEMA'> + | TestCaseWithType<'AUDIT_DATABASE_RECORD_SCHEMA'> + | TestCaseWithType<'ENTRA_ID_USER_SCHEMA'>; export const getTestsForParameter = ({ schema, @@ -135,6 +138,14 @@ export const getTestsForParameter = ({ }); break; + case 'ENTRA_ID_USER_SCHEMA': + withEntraIdUserSchemaTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + default: throw Error(`There are no existing test cases for the type ${type}`); } diff --git a/libs/common/src/test-helpers/schemas/primitives/with-array.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-array.tests.ts index 9223ea75e0..bcd9ba018c 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-array.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitives/with-array.tests.ts @@ -5,6 +5,7 @@ import { withDefaultOptionsTests } from './with-default-options.tests'; export type WithArrayTestsOptions = { arrayTypeTestCase: TestCase; + isAllowEmpty?: boolean; }; export const withArrayTests = ({ @@ -12,6 +13,12 @@ export const withArrayTests = ({ options, getTestObjectWithUpdatedField, }: WithSchemaTestParams) => { + const arrayTestOptionsDefaults = { isAllowEmpty: true }; + const arrayTestOptions = { + ...arrayTestOptionsDefaults, + ...options, + }; + describe('with array tests', () => { withDefaultOptionsTests({ schema, @@ -24,15 +31,22 @@ export const withArrayTests = ({ expect(success).toBe(false); }); - it('should pass parsing if the parameter is an empty array', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField([])); - expect(success).toBe(true); - }); + if (arrayTestOptions.isAllowEmpty) { + it('should pass parsing if the parameter is an empty array', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField([])); + expect(success).toBe(true); + }); + } else { + it('should fail parsing if the parameter is an empty array', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField([])); + expect(success).toBe(false); + }); + } describe('when configuring the objects in the array', () => { getTestsForParameter({ schema, - testCase: options.arrayTypeTestCase, + testCase: arrayTestOptions.arrayTypeTestCase, getTestObjectWithUpdatedField: (value) => getTestObjectWithUpdatedField([value]), }); }); diff --git a/libs/common/src/test-helpers/schemas/primitives/with-entra-id-user-schema.tests.ts b/libs/common/src/test-helpers/schemas/primitives/with-entra-id-user-schema.tests.ts new file mode 100644 index 0000000000..c36687ceba --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitives/with-entra-id-user-schema.tests.ts @@ -0,0 +1,80 @@ +import { ZodSchema } from 'zod'; +import { anEntraIdUser } from '../../mock-data'; +import { withSchemaValidationTests } from '.'; +import { withDefaultOptionsTests } from './with-default-options.tests'; +import { WithSchemaTestParams } from './with-schema-test.type'; + +export const withEntraIdUserSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { + describe('with ENTRA_ID_USER_SCHEMA tests', () => { + withDefaultOptionsTests({ + schema, + getTestObjectWithUpdatedField, + options, + }); + + withSchemaValidationTests({ + schema, + aValidPayload: () => anEntraIdUser(), + testCases: [ + { + parameterPath: 'oid', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedField({ ...aValidEntraIdUser(), oid: newValue }), + }, + }, + { + parameterPath: 'verified_primary_email', + type: 'Array', + options: { + arrayTypeTestCase: { + type: 'string', + }, + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => + getTestObjectWithUpdatedField({ ...aValidEntraIdUser(), verified_primary_email: newValue }), + isAllowEmpty: false, + }, + }, + { + parameterPath: 'verified_secondary_email', + type: 'Array', + options: { + arrayTypeTestCase: { + type: 'string', + }, + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => + getTestObjectWithUpdatedField({ ...aValidEntraIdUser(), verified_secondary_email: newValue }), + }, + }, + { + parameterPath: 'given_name', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedField({ ...aValidEntraIdUser(), given_name: newValue }), + }, + }, + { + parameterPath: 'family_name', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedField({ ...aValidEntraIdUser(), family_name: newValue }), + }, + }, + { + parameterPath: 'roles', + type: 'Array', + options: { + arrayTypeTestCase: { + type: 'TfmTeamSchema', + }, + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedField({ ...aValidEntraIdUser(), roles: newValue }), + }, + }, + ], + }); + }); + + function aValidEntraIdUser() { + return anEntraIdUser(); + } +}; diff --git a/libs/common/src/test-helpers/schemas/with-entra-id-user-schema.tests.ts b/libs/common/src/test-helpers/schemas/with-entra-id-user-schema.tests.ts deleted file mode 100644 index b3ab13c002..0000000000 --- a/libs/common/src/test-helpers/schemas/with-entra-id-user-schema.tests.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { ZodSchema } from 'zod'; -import { withSchemaTests } from './with-schema.tests'; -import { TEAMS } from '../../constants'; -import { anEntraIdUser } from '../mock-data'; - -/** - * This is a reusable test to allow for complete testing of schemas that - * utilise the ENTRA_ID_USER_SCHEMA as part of their definition - */ - -type TestCasesParams = { - getTestObjectWithUpdatedEntraIdUserParams: (entraIdUser: unknown) => unknown; -}; - -type WithEntraIdUserSchemaTestsParams = { - schema: ZodSchema; -} & Partial; - -export function withEntraIdUserSchemaTests({ - schema, - getTestObjectWithUpdatedEntraIdUserParams = (entraIdUser) => entraIdUser, -}: WithEntraIdUserSchemaTestsParams) { - describe('with ENTRA_ID_USER_SCHEMA tests', () => { - withSchemaTests({ - schema, - failureTestCases: getEntraIdUserFailureTestCases({ getTestObjectWithUpdatedEntraIdUserParams }), - successTestCases: getEntraIdUserSuccessTestCases({ getTestObjectWithUpdatedEntraIdUserParams }), - }); - }); -} - -export function getEntraIdUserFailureTestCases({ getTestObjectWithUpdatedEntraIdUserParams = (entraIdUser) => entraIdUser }: TestCasesParams) { - return [ - { - aTestCase: () => { - const { oid: _oid, ...rest } = anEntraIdUser(); - return getTestObjectWithUpdatedEntraIdUserParams(rest); - }, - description: 'the oid is missing', - }, - { - aTestCase: () => { - const { verified_primary_email: _verifiedPrimaryEmail, ...rest } = anEntraIdUser(); - return getTestObjectWithUpdatedEntraIdUserParams(rest); - }, - description: 'the verified primary email is missing', - }, - { - aTestCase: () => { - const { verified_secondary_email: _verifiedSecondaryEmail, ...rest } = anEntraIdUser(); - return getTestObjectWithUpdatedEntraIdUserParams(rest); - }, - description: 'the verified secondary email is missing', - }, - { - aTestCase: () => { - const { given_name: _givenName, ...rest } = anEntraIdUser(); - return getTestObjectWithUpdatedEntraIdUserParams(rest); - }, - description: 'the given name is missing', - }, - - { - aTestCase: () => { - const { family_name: _familyName, ...rest } = anEntraIdUser(); - return getTestObjectWithUpdatedEntraIdUserParams(rest); - }, - description: 'the family name is missing', - }, - { - aTestCase: () => { - const { roles: _roles, ...rest } = anEntraIdUser(); - return getTestObjectWithUpdatedEntraIdUserParams(rest); - }, - description: 'the roles are missing', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), oid: 1 }), - description: 'the oid is not a string', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_primary_email: [] }), - description: 'the verify primary email array is empty', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_primary_email: [1] }), - description: 'the verify primary email is not a string array', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_primary_email: '1' }), - description: 'the verify primary email is not an array', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_secondary_email: [1] }), - description: 'the verify secondary email is not a string array', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_secondary_email: '1' }), - description: 'the verify secondary email is not an array', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), given_name: 1 }), - description: 'the given name is not a string', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), family_name: 1 }), - description: 'the family name is not a string', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), roles: ['NOT_A_USER_ROLE'] }), - description: 'the roles are not an array of user roles', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), roles: TEAMS.BUSINESS_SUPPORT.id }), - description: 'the roles are not an array', - }, - { - aTestCase: () => ({}), - description: 'the object is empty', - }, - ]; -} - -export function getEntraIdUserSuccessTestCases({ getTestObjectWithUpdatedEntraIdUserParams = (entraIdUser) => entraIdUser }: TestCasesParams) { - return [ - { aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams(anEntraIdUser()), description: 'a complete valid payload is present' }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_secondary_email: [] }), - description: 'the verified secondary email array is empty', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), roles: [] }), - description: 'the roles array is empty', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), extraField: 'extra' }), - description: 'there is an extra field', - }, - ]; -} From 2e412f4e4711c8b40f5c29176871cc72f8c72a0d Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 4 Dec 2024 14:33:20 +0000 Subject: [PATCH 023/133] feat(dtfs2-6892): update entra user id auth result tests --- ...ra-id-authentication-result-schema.test.ts | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts index fb18e6b188..2059b0e7ca 100644 --- a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts @@ -1,27 +1,35 @@ import { anEntraIdUser, withSchemaValidationTests } from '../../test-helpers'; +import { EntraIdUser } from '../../types'; import { EntraIdAuthenticationResult } from '../../types/tfm/entra-id'; import { ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA } from './entra-id.schema'; -describe('ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA', () => {}); +describe('ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA', () => { + withSchemaValidationTests({ + schema: ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA, + aValidPayload, + testCases: [ + { + parameterPath: 'accessToken', + type: 'string', + }, + { + parameterPath: 'account.idTokenClaims', + type: 'ENTRA_ID_USER_SCHEMA', + options: { + overrideGetTestObjectWithUpdatedField: (newValue) => { + const testObject = aValidPayload(); + testObject.account.idTokenClaims = newValue as EntraIdUser; + return testObject; + }, + }, + }, + ], + }); -withSchemaValidationTests({ - schema: ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA, - aValidPayload, - testCases: [ - { - parameterPath: 'accessToken', - type: 'string', - }, - { - parameterPath: 'account', - type: 'ENTRA_ID_USER_SCHEMA', - }, - ], + function aValidPayload(): EntraIdAuthenticationResult { + return { + accessToken: 'an-access-token', + account: { idTokenClaims: anEntraIdUser() }, + }; + } }); - -function aValidPayload(): EntraIdAuthenticationResult { - return { - accessToken: 'an-access-token', - account: { idTokenClaims: anEntraIdUser() }, - }; -} From b74aeab74b921306f7c47ee8540b233ad6d2af35 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 4 Dec 2024 15:02:13 +0000 Subject: [PATCH 024/133] feat(dtfs2-6892): fix tests --- .../src/schemas/audit-database-record.test.ts | 2 +- .../iso-date-time-stamp.schema.test.ts | 2 +- libs/common/src/schemas/object-id.test.ts | 6 +- .../src/schemas/portal-user.create.test.ts | 83 ++++++++--- .../src/schemas/portal-user.update.test.ts | 101 +++++++++---- ...-to-upsert-tfm-user-request.schema.test.ts | 1 - ...ded-auth-code-request-state-schema.test.ts | 69 +++------ .../src/schemas/tfm/tfm-team.schema.test.ts | 2 +- .../upsert-tfm-user-request.schema.test.ts | 54 ++++++- .../get-tests-for-parameter.tests.ts | 24 ++-- libs/common/src/test-helpers/schemas/index.ts | 9 +- .../schemas/primitive-object-tests/index.ts | 5 + .../with-array.tests.ts | 4 +- .../with-boolean.tests.ts | 2 +- .../with-default-options.tests.ts | 2 +- .../with-number.tests.ts | 2 +- .../with-string.tests.ts | 2 +- .../test-helpers/schemas/primitives/index.ts | 1 - .../schemas/schema-tests/index.ts | 7 + ...with-audit-database-record-schema.tests.ts | 6 +- .../with-entra-id-user-schema.tests.ts | 6 +- .../with-iso-date-time-stamp-schema.tests.ts | 4 +- ...ect-id-or-object-id-string-schema.tests.ts | 4 +- .../with-object-id-schema.tests.ts | 4 +- .../with-object-id-string-schema.tests.ts | 4 +- .../with-tfm-team-schema.tests.ts | 4 +- ...th-create-tfm-user-request-schema.tests.ts | 134 ------------------ .../{primitives => }/with-schema-test.type.ts | 2 +- .../with-schema-validation.tests.ts | 0 .../test-helpers/schemas/with-schema.tests.ts | 27 ---- ...th-upsert-tfm-user-request-schema.tests.ts | 43 ------ 31 files changed, 263 insertions(+), 353 deletions(-) rename libs/common/src/test-helpers/schemas/{primitives => }/get-tests-for-parameter.tests.ts (74%) create mode 100644 libs/common/src/test-helpers/schemas/primitive-object-tests/index.ts rename libs/common/src/test-helpers/schemas/{primitives => primitive-object-tests}/with-array.tests.ts (91%) rename libs/common/src/test-helpers/schemas/{primitives => primitive-object-tests}/with-boolean.tests.ts (92%) rename libs/common/src/test-helpers/schemas/{primitives => primitive-object-tests}/with-default-options.tests.ts (96%) rename libs/common/src/test-helpers/schemas/{primitives => primitive-object-tests}/with-number.tests.ts (92%) rename libs/common/src/test-helpers/schemas/{primitives => primitive-object-tests}/with-string.tests.ts (92%) delete mode 100644 libs/common/src/test-helpers/schemas/primitives/index.ts create mode 100644 libs/common/src/test-helpers/schemas/schema-tests/index.ts rename libs/common/src/test-helpers/schemas/{primitives => schema-tests}/with-audit-database-record-schema.tests.ts (91%) rename libs/common/src/test-helpers/schemas/{primitives => schema-tests}/with-entra-id-user-schema.tests.ts (92%) rename libs/common/src/test-helpers/schemas/{primitives => schema-tests}/with-iso-date-time-stamp-schema.tests.ts (88%) rename libs/common/src/test-helpers/schemas/{primitives => schema-tests}/with-object-id-or-object-id-string-schema.tests.ts (87%) rename libs/common/src/test-helpers/schemas/{primitives => schema-tests}/with-object-id-schema.tests.ts (90%) rename libs/common/src/test-helpers/schemas/{primitives => schema-tests}/with-object-id-string-schema.tests.ts (91%) rename libs/common/src/test-helpers/schemas/{primitives => schema-tests}/with-tfm-team-schema.tests.ts (83%) delete mode 100644 libs/common/src/test-helpers/schemas/with-create-tfm-user-request-schema.tests.ts rename libs/common/src/test-helpers/schemas/{primitives => }/with-schema-test.type.ts (84%) rename libs/common/src/test-helpers/schemas/{primitives => }/with-schema-validation.tests.ts (100%) delete mode 100644 libs/common/src/test-helpers/schemas/with-schema.tests.ts delete mode 100644 libs/common/src/test-helpers/schemas/with-upsert-tfm-user-request-schema.tests.ts diff --git a/libs/common/src/schemas/audit-database-record.test.ts b/libs/common/src/schemas/audit-database-record.test.ts index 9d59d4a74f..d66e242dee 100644 --- a/libs/common/src/schemas/audit-database-record.test.ts +++ b/libs/common/src/schemas/audit-database-record.test.ts @@ -1,4 +1,4 @@ -import { withAuditDatabaseRecordSchemaTests } from '../test-helpers/schemas/primitives/with-audit-database-record-schema.tests'; +import { withAuditDatabaseRecordSchemaTests } from '../test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests'; import { AUDIT_DATABASE_RECORD_SCHEMA } from './audit-database-record.schema'; describe('AUDIT_DATABASE_RECORD', () => { diff --git a/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts b/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts index dc19c97f89..5dfd29e329 100644 --- a/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts +++ b/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts @@ -1,4 +1,4 @@ -import { withIsoDateTimeStampSchemaTests } from '../test-helpers/schemas/primitives/with-iso-date-time-stamp-schema.tests'; +import { withIsoDateTimeStampSchemaTests } from '../test-helpers/schemas/schema-tests/with-iso-date-time-stamp-schema.tests'; import { ISO_DATE_TIME_STAMP_SCHEMA } from './iso-date-time-stamp.schema'; describe('ISO_DATE_TIME_STAMP_SCHEMA', () => { diff --git a/libs/common/src/schemas/object-id.test.ts b/libs/common/src/schemas/object-id.test.ts index 0ad51fb257..ab4b1234af 100644 --- a/libs/common/src/schemas/object-id.test.ts +++ b/libs/common/src/schemas/object-id.test.ts @@ -1,7 +1,7 @@ import { OBJECT_ID_SCHEMA, OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA, OBJECT_ID_STRING_SCHEMA } from './object-id'; -import { withObjectIdSchemaTests } from '../test-helpers/schemas/primitives/with-object-id-schema.tests'; -import { withObjectIdOrObjectIdStringSchemaTests } from '../test-helpers/schemas/primitives/with-object-id-or-object-id-string-schema.tests'; -import { withObjectIdStringSchemaTests } from '../test-helpers/schemas/primitives/with-object-id-string-schema.tests'; +import { withObjectIdSchemaTests } from '../test-helpers/schemas/schema-tests/with-object-id-schema.tests'; +import { withObjectIdOrObjectIdStringSchemaTests } from '../test-helpers/schemas/schema-tests/with-object-id-or-object-id-string-schema.tests'; +import { withObjectIdStringSchemaTests } from '../test-helpers/schemas/schema-tests/with-object-id-string-schema.tests'; describe('OBJECT_ID_SCHEMA', () => { withObjectIdSchemaTests({ diff --git a/libs/common/src/schemas/portal-user.create.test.ts b/libs/common/src/schemas/portal-user.create.test.ts index 7f518eab7b..976e38e6c0 100644 --- a/libs/common/src/schemas/portal-user.create.test.ts +++ b/libs/common/src/schemas/portal-user.create.test.ts @@ -1,4 +1,5 @@ import { ObjectId } from 'mongodb'; +import z from 'zod'; import { generatePortalUserAuditDatabaseRecord } from '../change-stream'; import { CREATE } from './portal-user'; import { withSchemaValidationTests } from '../test-helpers'; @@ -6,34 +7,70 @@ import { withSchemaValidationTests } from '../test-helpers'; describe('PORTAL_USER', () => { describe('CREATE', () => { withSchemaValidationTests({ - successTestCases: getSuccessTestCases(), - failureTestCases: getFailureTestCases(), schema: CREATE, + aValidPayload, + testCases: [ + { + parameterPath: 'username', + type: 'string', + }, + { + parameterPath: 'firstname', + type: 'string', + }, + { + parameterPath: 'surname', + type: 'string', + }, + { + parameterPath: 'email', + type: 'string', + }, + { + parameterPath: 'timezone', + type: 'string', + }, + { + parameterPath: 'roles', + type: 'Array', + options: { + arrayTypeTestCase: { + type: 'string', + }, + }, + }, + { + parameterPath: 'user-status', + type: 'string', + }, + + { + parameterPath: 'salt', + type: 'string', + }, + { + parameterPath: 'hash', + type: 'string', + }, + { + parameterPath: 'auditRecord', + type: 'AUDIT_DATABASE_RECORD_SCHEMA', + }, + { + parameterPath: 'isTrusted', + type: 'boolean', + }, + { + parameterPath: 'disabled', + type: 'boolean', + options: { isOptional: true }, + }, + ], }); }); - - function getSuccessTestCases() { - return [{ description: 'a valid user', aTestCase: () => aValidPortalUser() }]; - } - - function getFailureTestCases() { - return [ - { description: 'a string', aTestCase: () => 'string' }, - { description: 'an object', aTestCase: () => ({ An: 'object' }) }, - { description: 'an array', aTestCase: () => ['array'] }, - { - description: 'a partial user', - aTestCase: () => { - const { username: _username, ...rest } = aValidPortalUser(); - return rest; - }, - }, - { description: 'a user with an additional parameter', aTestCase: () => ({ ...aValidPortalUser(), invalidField: true }) }, - ]; - } }); -function aValidPortalUser() { +function aValidPayload(): z.infer { return { username: 'HSBC-maker-1', firstname: 'Mister', diff --git a/libs/common/src/schemas/portal-user.update.test.ts b/libs/common/src/schemas/portal-user.update.test.ts index 5ebd457d26..d2016607bf 100644 --- a/libs/common/src/schemas/portal-user.update.test.ts +++ b/libs/common/src/schemas/portal-user.update.test.ts @@ -1,4 +1,5 @@ import { ObjectId } from 'mongodb'; +import z from 'zod'; import { generatePortalUserAuditDatabaseRecord } from '../change-stream'; import { UPDATE } from './portal-user'; import { withSchemaValidationTests } from '../test-helpers'; @@ -6,36 +7,86 @@ import { withSchemaValidationTests } from '../test-helpers'; describe('PORTAL_USER', () => { describe('UPDATE', () => { withSchemaValidationTests({ - successTestCases: getSuccessTestCases(), - failureTestCases: getFailureTestCases(), schema: UPDATE, + aValidPayload, + testCases: [ + { + parameterPath: 'username', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'firstname', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'surname', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'email', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'timezone', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'roles', + type: 'Array', + options: { + isOptional: true, + arrayTypeTestCase: { + type: 'string', + }, + }, + }, + { + parameterPath: 'user-status', + type: 'string', + + options: { isOptional: true }, + }, + + { + parameterPath: 'salt', + type: 'string', + + options: { isOptional: true }, + }, + { + parameterPath: 'hash', + type: 'string', + + options: { isOptional: true }, + }, + { + parameterPath: 'auditRecord', + type: 'AUDIT_DATABASE_RECORD_SCHEMA', + + options: { isOptional: true }, + }, + { + parameterPath: 'isTrusted', + type: 'boolean', + + options: { isOptional: true }, + }, + { + parameterPath: 'disabled', + type: 'boolean', + options: { isOptional: true }, + }, + ], }); }); }); -function getSuccessTestCases() { - return [ - { description: 'a valid user', aTestCase: () => aValidPortalUser() }, - { - description: 'a partial user', - aTestCase: () => { - const { username: _username, ...rest } = aValidPortalUser(); - return rest; - }, - }, - ]; -} - -function getFailureTestCases() { - return [ - { description: 'a string', aTestCase: () => 'string' }, - { description: 'an object', aTestCase: () => ({ An: 'object' }) }, - { description: 'an array', aTestCase: () => ['array'] }, - { description: 'a user with an additional parameter', aTestCase: () => ({ ...aValidPortalUser(), invalidField: true }) }, - ]; -} - -function aValidPortalUser() { +function aValidPayload(): z.infer { return { username: 'HSBC-maker-1', firstname: 'Mister', diff --git a/libs/common/src/schemas/tfm/entra-id-user-to-upsert-tfm-user-request.schema.test.ts b/libs/common/src/schemas/tfm/entra-id-user-to-upsert-tfm-user-request.schema.test.ts index b691ca7147..9a2bf36c86 100644 --- a/libs/common/src/schemas/tfm/entra-id-user-to-upsert-tfm-user-request.schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id-user-to-upsert-tfm-user-request.schema.test.ts @@ -55,7 +55,6 @@ function itShouldReturnAValidUpsertTfmUserRequest(request: EntraIdUser) { timezone: timezoneConfig.DEFAULT, firstName: request.given_name, lastName: request.family_name, - lastLogin: Date.now(), }); }); } diff --git a/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts index d5573ba669..9226444424 100644 --- a/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts @@ -5,55 +5,24 @@ import { DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA } from './entra-id.schema'; describe('DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA', () => { withSchemaValidationTests({ schema: DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA, - failureTestCases: getFailureTestCases(), - successTestCases: getSuccessTestCases(), - }); -}); - -function aValidPayload(): DecodedAuthCodeRequestState { - return { - csrfToken: 'a-csrf-token', - successRedirect: 'a-success-redirect', - }; -} - -function getFailureTestCases() { - return [ - { - aTestCase: () => { - const { csrfToken: _csrfToken, ...rest } = aValidPayload(); - return rest; + aValidPayload, + testCases: [ + { + parameterPath: 'csrfToken', + type: 'string', }, - description: 'the csrf token is missing', - }, - { - aTestCase: () => ({ ...aValidPayload(), csrfToken: 1 }), - description: 'the csrf token is not a string', - }, - { - aTestCase: () => ({ ...aValidPayload(), successRedirect: 1 }), - description: 'the successRedirect is not a string', - }, - { - aTestCase: () => ({}), - description: 'the object is empty', - }, - ]; -} - -function getSuccessTestCases() { - return [ - { aTestCase: aValidPayload, description: 'a complete valid payload is present' }, - { - aTestCase: () => { - const { successRedirect: _successRedirect, ...rest } = aValidPayload(); - return rest; + { + parameterPath: 'successRedirect', + type: 'string', + options: { isOptional: true }, }, - description: 'the optional success redirect is missing', - }, - { - aTestCase: () => ({ ...aValidPayload(), extraField: 'extra' }), - description: 'there is an extra field', - }, - ]; -} + ], + }); + + function aValidPayload(): DecodedAuthCodeRequestState { + return { + csrfToken: 'a-csrf-token', + successRedirect: 'a-success-redirect', + }; + } +}); diff --git a/libs/common/src/schemas/tfm/tfm-team.schema.test.ts b/libs/common/src/schemas/tfm/tfm-team.schema.test.ts index 1477adc532..9575670b64 100644 --- a/libs/common/src/schemas/tfm/tfm-team.schema.test.ts +++ b/libs/common/src/schemas/tfm/tfm-team.schema.test.ts @@ -1,4 +1,4 @@ -import { withTfmTeamSchemaTests } from '../../test-helpers/schemas/primitives/with-tfm-team-schema.tests'; +import { withTfmTeamSchemaTests } from '../../test-helpers/schemas/schema-tests/with-tfm-team-schema.tests'; import { TfmTeamSchema } from './tfm-team.schema'; describe('tfm-team.schema', () => { diff --git a/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.test.ts b/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.test.ts index 8f7710837e..628d6065e6 100644 --- a/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.test.ts +++ b/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.test.ts @@ -1,8 +1,58 @@ -import { withUpsertTfmUserRequestSchemaTests } from '../../test-helpers'; +import { TEAM_IDS } from '../../constants'; +import { withSchemaValidationTests } from '../../test-helpers'; +import { UpsertTfmUserRequest } from '../../types'; import { UPSERT_TFM_USER_REQUEST_SCHEMA } from './upsert-tfm-user-request.schema'; describe('UPSERT_TFM_USER_REQUEST_SCHEMA', () => { - withUpsertTfmUserRequestSchemaTests({ + withSchemaValidationTests({ schema: UPSERT_TFM_USER_REQUEST_SCHEMA, + aValidPayload, + testCases: [ + { + parameterPath: 'username', + type: 'string', + }, + { + parameterPath: 'email', + type: 'string', + }, + { + parameterPath: 'teams', + type: 'Array', + options: { + arrayTypeTestCase: { + type: 'TfmTeamSchema', + }, + }, + }, + { + parameterPath: 'timezone', + type: 'string', + }, + { + parameterPath: 'firstName', + type: 'string', + }, + { + parameterPath: 'lastName', + type: 'string', + }, + { + parameterPath: 'azureOid', + type: 'string', + }, + ], }); + + function aValidPayload(): UpsertTfmUserRequest { + return { + username: 'test-user', + email: 'test-user@test.com', + teams: [TEAM_IDS.PIM], + timezone: 'Europe/London', + firstName: 'FirstName', + lastName: 'LastName', + azureOid: 'test-azure-oid', + }; + } }); diff --git a/libs/common/src/test-helpers/schemas/primitives/get-tests-for-parameter.tests.ts b/libs/common/src/test-helpers/schemas/get-tests-for-parameter.tests.ts similarity index 74% rename from libs/common/src/test-helpers/schemas/primitives/get-tests-for-parameter.tests.ts rename to libs/common/src/test-helpers/schemas/get-tests-for-parameter.tests.ts index 1bde4ce2f1..defeb81ac2 100644 --- a/libs/common/src/test-helpers/schemas/primitives/get-tests-for-parameter.tests.ts +++ b/libs/common/src/test-helpers/schemas/get-tests-for-parameter.tests.ts @@ -1,16 +1,16 @@ import { ZodSchema } from 'zod'; -import { withNumberTests } from './with-number.tests'; -import { withObjectIdSchemaTests } from './with-object-id-schema.tests'; -import { withStringTests } from './with-string.tests'; -import { withTfmTeamSchemaTests } from './with-tfm-team-schema.tests'; -import { withArrayTests, WithArrayTestsOptions } from './with-array.tests'; -import { withIsoDateTimeStampSchemaTests } from './with-iso-date-time-stamp-schema.tests'; -import { withAuditDatabaseRecordSchemaTests } from './with-audit-database-record-schema.tests'; -import { withObjectIdOrObjectIdStringSchemaTests } from './with-object-id-or-object-id-string-schema.tests'; -import { withObjectIdStringSchemaTests } from './with-object-id-string-schema.tests'; -import { DefaultOptions } from './with-default-options.tests'; -import { withBooleanTests } from './with-boolean.tests'; -import { withEntraIdUserSchemaTests } from './with-entra-id-user-schema.tests'; +import { withNumberTests } from './primitive-object-tests/with-number.tests'; +import { withObjectIdSchemaTests } from './schema-tests/with-object-id-schema.tests'; +import { withStringTests } from './primitive-object-tests/with-string.tests'; +import { withTfmTeamSchemaTests } from './schema-tests/with-tfm-team-schema.tests'; +import { withArrayTests, WithArrayTestsOptions } from './primitive-object-tests/with-array.tests'; +import { withIsoDateTimeStampSchemaTests } from './schema-tests/with-iso-date-time-stamp-schema.tests'; +import { withAuditDatabaseRecordSchemaTests } from './schema-tests/with-audit-database-record-schema.tests'; +import { withObjectIdOrObjectIdStringSchemaTests } from './schema-tests/with-object-id-or-object-id-string-schema.tests'; +import { withObjectIdStringSchemaTests } from './schema-tests/with-object-id-string-schema.tests'; +import { DefaultOptions } from './primitive-object-tests/with-default-options.tests'; +import { withBooleanTests } from './primitive-object-tests/with-boolean.tests'; +import { withEntraIdUserSchemaTests } from './schema-tests/with-entra-id-user-schema.tests'; export type TestCaseTypes = | 'string' diff --git a/libs/common/src/test-helpers/schemas/index.ts b/libs/common/src/test-helpers/schemas/index.ts index 48df276e38..c537773c4f 100644 --- a/libs/common/src/test-helpers/schemas/index.ts +++ b/libs/common/src/test-helpers/schemas/index.ts @@ -1,6 +1,3 @@ -export * from './primitives'; -export * from './with-schema.tests'; -export * from './primitives/with-entra-id-user-schema.tests'; -export * from './with-create-tfm-user-request-schema.tests'; -export * from './with-upsert-tfm-user-request-schema.tests'; -export * from './primitives/with-entra-id-user-schema.tests'; +export * from './schema-tests'; +export * from './primitive-object-tests'; +export * from './with-schema-validation.tests'; diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/index.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/index.ts new file mode 100644 index 0000000000..8deedb9337 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/index.ts @@ -0,0 +1,5 @@ +export * from './with-array.tests'; +export * from './with-boolean.tests'; +export * from './with-default-options.tests'; +export * from './with-number.tests'; +export * from './with-string.tests'; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-array.tests.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-array.tests.ts similarity index 91% rename from libs/common/src/test-helpers/schemas/primitives/with-array.tests.ts rename to libs/common/src/test-helpers/schemas/primitive-object-tests/with-array.tests.ts index bcd9ba018c..c3e0bce82b 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-array.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-array.tests.ts @@ -1,6 +1,6 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from './with-schema-test.type'; -import { getTestsForParameter, TestCase } from './get-tests-for-parameter.tests'; +import { WithSchemaTestParams } from '../with-schema-test.type'; +import { getTestsForParameter, TestCase } from '../get-tests-for-parameter.tests'; import { withDefaultOptionsTests } from './with-default-options.tests'; export type WithArrayTestsOptions = { diff --git a/libs/common/src/test-helpers/schemas/primitives/with-boolean.tests.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-boolean.tests.ts similarity index 92% rename from libs/common/src/test-helpers/schemas/primitives/with-boolean.tests.ts rename to libs/common/src/test-helpers/schemas/primitive-object-tests/with-boolean.tests.ts index 326f028c37..65f1b9ba4c 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-boolean.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-boolean.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from './with-schema-test.type'; +import { WithSchemaTestParams } from '../with-schema-test.type'; import { withDefaultOptionsTests } from './with-default-options.tests'; export const withBooleanTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { diff --git a/libs/common/src/test-helpers/schemas/primitives/with-default-options.tests.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-default-options.tests.ts similarity index 96% rename from libs/common/src/test-helpers/schemas/primitives/with-default-options.tests.ts rename to libs/common/src/test-helpers/schemas/primitive-object-tests/with-default-options.tests.ts index 22bb765dc3..ce8ab5db38 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-default-options.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-default-options.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from './with-schema-test.type'; +import { WithSchemaTestParams } from '../with-schema-test.type'; export type DefaultOptions = { isOptional?: boolean; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-number.tests.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-number.tests.ts similarity index 92% rename from libs/common/src/test-helpers/schemas/primitives/with-number.tests.ts rename to libs/common/src/test-helpers/schemas/primitive-object-tests/with-number.tests.ts index 60a4fd8871..17c2c166d8 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-number.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-number.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from './with-schema-test.type'; +import { WithSchemaTestParams } from '../with-schema-test.type'; import { withDefaultOptionsTests } from './with-default-options.tests'; export const withNumberTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { diff --git a/libs/common/src/test-helpers/schemas/primitives/with-string.tests.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-string.tests.ts similarity index 92% rename from libs/common/src/test-helpers/schemas/primitives/with-string.tests.ts rename to libs/common/src/test-helpers/schemas/primitive-object-tests/with-string.tests.ts index 93f51f36d2..45626801bd 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-string.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-string.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from './with-schema-test.type'; +import { WithSchemaTestParams } from '../with-schema-test.type'; import { withDefaultOptionsTests } from './with-default-options.tests'; export const withStringTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { diff --git a/libs/common/src/test-helpers/schemas/primitives/index.ts b/libs/common/src/test-helpers/schemas/primitives/index.ts deleted file mode 100644 index d2feb2609f..0000000000 --- a/libs/common/src/test-helpers/schemas/primitives/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './with-schema-validation.tests'; diff --git a/libs/common/src/test-helpers/schemas/schema-tests/index.ts b/libs/common/src/test-helpers/schemas/schema-tests/index.ts new file mode 100644 index 0000000000..3b8cc53f31 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/schema-tests/index.ts @@ -0,0 +1,7 @@ +export * from './with-audit-database-record-schema.tests'; +export * from './with-entra-id-user-schema.tests'; +export * from './with-iso-date-time-stamp-schema.tests'; +export * from './with-object-id-or-object-id-string-schema.tests'; +export * from './with-object-id-schema.tests'; +export * from './with-object-id-string-schema.tests'; +export * from './with-tfm-team-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-audit-database-record-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts similarity index 91% rename from libs/common/src/test-helpers/schemas/primitives/with-audit-database-record-schema.tests.ts rename to libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts index 44b142da52..dc1321541a 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-audit-database-record-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts @@ -1,9 +1,9 @@ import { ZodSchema } from 'zod'; import { ObjectId } from 'mongodb'; -import { WithSchemaTestParams } from './with-schema-test.type'; +import { WithSchemaTestParams } from '../with-schema-test.type'; import { generateTfmUserAuditDatabaseRecord } from '../../../change-stream'; -import { withSchemaValidationTests } from './with-schema-validation.tests'; -import { withDefaultOptionsTests } from './with-default-options.tests'; +import { withSchemaValidationTests } from '../with-schema-validation.tests'; +import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; export const withAuditDatabaseRecordSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/primitives/with-entra-id-user-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts similarity index 92% rename from libs/common/src/test-helpers/schemas/primitives/with-entra-id-user-schema.tests.ts rename to libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts index c36687ceba..3f83e9c74a 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-entra-id-user-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts @@ -1,8 +1,8 @@ import { ZodSchema } from 'zod'; import { anEntraIdUser } from '../../mock-data'; -import { withSchemaValidationTests } from '.'; -import { withDefaultOptionsTests } from './with-default-options.tests'; -import { WithSchemaTestParams } from './with-schema-test.type'; +import { withDefaultOptionsTests } from '../primitive-object-tests'; +import { withSchemaValidationTests } from '../with-schema-validation.tests'; +import { WithSchemaTestParams } from '../with-schema-test.type'; export const withEntraIdUserSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { describe('with ENTRA_ID_USER_SCHEMA tests', () => { diff --git a/libs/common/src/test-helpers/schemas/primitives/with-iso-date-time-stamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-iso-date-time-stamp-schema.tests.ts similarity index 88% rename from libs/common/src/test-helpers/schemas/primitives/with-iso-date-time-stamp-schema.tests.ts rename to libs/common/src/test-helpers/schemas/schema-tests/with-iso-date-time-stamp-schema.tests.ts index 93e5ff399a..2bb271d54a 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-iso-date-time-stamp-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-iso-date-time-stamp-schema.tests.ts @@ -1,6 +1,6 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from './with-schema-test.type'; -import { withDefaultOptionsTests } from './with-default-options.tests'; +import { WithSchemaTestParams } from '../with-schema-test.type'; +import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; export const withIsoDateTimeStampSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/primitives/with-object-id-or-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-object-id-or-object-id-string-schema.tests.ts similarity index 87% rename from libs/common/src/test-helpers/schemas/primitives/with-object-id-or-object-id-string-schema.tests.ts rename to libs/common/src/test-helpers/schemas/schema-tests/with-object-id-or-object-id-string-schema.tests.ts index 070e45e2f6..b5077ae140 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-object-id-or-object-id-string-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-object-id-or-object-id-string-schema.tests.ts @@ -1,7 +1,7 @@ import { ZodSchema } from 'zod'; import { ObjectId } from 'mongodb'; -import { WithSchemaTestParams } from './with-schema-test.type'; -import { withDefaultOptionsTests } from './with-default-options.tests'; +import { WithSchemaTestParams } from '../with-schema-test.type'; +import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; export const withObjectIdOrObjectIdStringSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/primitives/with-object-id-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-object-id-schema.tests.ts similarity index 90% rename from libs/common/src/test-helpers/schemas/primitives/with-object-id-schema.tests.ts rename to libs/common/src/test-helpers/schemas/schema-tests/with-object-id-schema.tests.ts index b86c04ffec..caa29ef84b 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-object-id-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-object-id-schema.tests.ts @@ -1,7 +1,7 @@ import { ObjectId } from 'mongodb'; import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from './with-schema-test.type'; -import { withDefaultOptionsTests } from './with-default-options.tests'; +import { WithSchemaTestParams } from '../with-schema-test.type'; +import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; export const withObjectIdSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { describe('with OBJECT_ID_SCHEMA tests', () => { diff --git a/libs/common/src/test-helpers/schemas/primitives/with-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-object-id-string-schema.tests.ts similarity index 91% rename from libs/common/src/test-helpers/schemas/primitives/with-object-id-string-schema.tests.ts rename to libs/common/src/test-helpers/schemas/schema-tests/with-object-id-string-schema.tests.ts index 24a7d6ea5a..725f5d9987 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-object-id-string-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-object-id-string-schema.tests.ts @@ -1,7 +1,7 @@ import { ObjectId } from 'mongodb'; import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from './with-schema-test.type'; -import { withDefaultOptionsTests } from './with-default-options.tests'; +import { WithSchemaTestParams } from '../with-schema-test.type'; +import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; export const withObjectIdStringSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/primitives/with-tfm-team-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-team-schema.tests.ts similarity index 83% rename from libs/common/src/test-helpers/schemas/primitives/with-tfm-team-schema.tests.ts rename to libs/common/src/test-helpers/schemas/schema-tests/with-tfm-team-schema.tests.ts index 3e5335e406..77a022ec1d 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-tfm-team-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-team-schema.tests.ts @@ -1,7 +1,7 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from './with-schema-test.type'; +import { WithSchemaTestParams } from '../with-schema-test.type'; import { TEAM_IDS } from '../../../constants'; -import { withDefaultOptionsTests } from './with-default-options.tests'; +import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; export const withTfmTeamSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { describe('with TfmTeamSchema tests', () => { diff --git a/libs/common/src/test-helpers/schemas/with-create-tfm-user-request-schema.tests.ts b/libs/common/src/test-helpers/schemas/with-create-tfm-user-request-schema.tests.ts deleted file mode 100644 index 4d259829e5..0000000000 --- a/libs/common/src/test-helpers/schemas/with-create-tfm-user-request-schema.tests.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { ZodSchema } from 'zod'; -import { withSchemaTests } from './with-schema.tests'; -import { aCreateTfmUserRequest } from '../mock-data/create-tfm-user-request'; - -/** - * This is a reusable test to allow for complete testing of schemas that - * utilise the CREATE_TFM_USER_REQUEST_SCHEMA as part of their definition - */ - -type TestCasesParams = { - getTestObjectWithUpdatedCreateTfmUserRequestParams: (CreateTfmUserRequest: unknown) => unknown; -}; - -type WithCreateTfmUserRequestSchemaTestsParams = { - schema: ZodSchema; -} & Partial; - -export function withCreateTfmUserRequestSchemaTests({ - schema, - getTestObjectWithUpdatedCreateTfmUserRequestParams = (CreateTfmUserRequest) => CreateTfmUserRequest, -}: WithCreateTfmUserRequestSchemaTestsParams) { - describe('with CREATE_TFM_USER_REQUEST_SCHEMA tests', () => { - withSchemaTests({ - schema, - failureTestCases: getCreateTfmUserRequestFailureTestCases({ getTestObjectWithUpdatedCreateTfmUserRequestParams }), - successTestCases: getCreateTfmUserRequestSuccessTestCases({ getTestObjectWithUpdatedCreateTfmUserRequestParams }), - }); - }); -} - -export function getCreateTfmUserRequestFailureTestCases({ getTestObjectWithUpdatedCreateTfmUserRequestParams }: TestCasesParams) { - return [ - { - aTestCase: () => { - const { azureOid: _azureOid, ...rest } = aCreateTfmUserRequest(); - return getTestObjectWithUpdatedCreateTfmUserRequestParams(rest); - }, - description: 'the azureOid is missing', - }, - { - aTestCase: () => { - const { email: _email, ...rest } = aCreateTfmUserRequest(); - return getTestObjectWithUpdatedCreateTfmUserRequestParams(rest); - }, - description: 'the email is missing', - }, - { - aTestCase: () => { - const { username: _username, ...rest } = aCreateTfmUserRequest(); - return getTestObjectWithUpdatedCreateTfmUserRequestParams(rest); - }, - description: 'the username is missing', - }, - { - aTestCase: () => { - const { teams: _teams, ...rest } = aCreateTfmUserRequest(); - return getTestObjectWithUpdatedCreateTfmUserRequestParams(rest); - }, - description: 'the teams is missing', - }, - { - aTestCase: () => { - const { timezone: _timezone, ...rest } = aCreateTfmUserRequest(); - return getTestObjectWithUpdatedCreateTfmUserRequestParams(rest); - }, - description: 'the timezone is missing', - }, - - { - aTestCase: () => { - const { firstName: _firstName, ...rest } = aCreateTfmUserRequest(); - return getTestObjectWithUpdatedCreateTfmUserRequestParams(rest); - }, - description: 'the first name is missing', - }, - { - aTestCase: () => { - const { lastName: _lastName, ...rest } = aCreateTfmUserRequest(); - return getTestObjectWithUpdatedCreateTfmUserRequestParams(rest); - }, - description: 'the last name is missing', - }, - { - aTestCase: () => ({ ...aCreateTfmUserRequest(), azureOid: 1 }), - description: 'the azureOid is not a string', - }, - { - aTestCase: () => ({ ...aCreateTfmUserRequest(), email: 1 }), - description: 'the email is not a string', - }, - { - aTestCase: () => ({ ...aCreateTfmUserRequest(), username: 1 }), - description: 'the username is not a string', - }, - { - aTestCase: () => ({ ...aCreateTfmUserRequest(), teams: [1] }), - description: 'the teams is not a string array', - }, - { - aTestCase: () => ({ ...aCreateTfmUserRequest(), teams: 'BUSINESS_SUPPORT' }), - description: 'the teams is not an array', - }, - { - aTestCase: () => ({ ...aCreateTfmUserRequest(), timezone: 1 }), - description: 'the timezone is not a string', - }, - { - aTestCase: () => ({ ...aCreateTfmUserRequest(), firstName: 1 }), - description: 'the first name is not a string', - }, - { - aTestCase: () => ({ ...aCreateTfmUserRequest(), lastName: 1 }), - description: 'the last name is not a string', - }, - { - aTestCase: () => ({}), - description: 'the object is empty', - }, - ]; -} - -export function getCreateTfmUserRequestSuccessTestCases({ getTestObjectWithUpdatedCreateTfmUserRequestParams }: TestCasesParams) { - return [ - { aTestCase: () => getTestObjectWithUpdatedCreateTfmUserRequestParams(aCreateTfmUserRequest()), description: 'a complete valid payload is present' }, - { - aTestCase: () => getTestObjectWithUpdatedCreateTfmUserRequestParams({ ...aCreateTfmUserRequest(), teams: [] }), - description: 'the teams array is empty', - }, - { - aTestCase: () => getTestObjectWithUpdatedCreateTfmUserRequestParams({ ...aCreateTfmUserRequest(), extraField: 'extra' }), - description: 'there is an extra field', - }, - ]; -} diff --git a/libs/common/src/test-helpers/schemas/primitives/with-schema-test.type.ts b/libs/common/src/test-helpers/schemas/with-schema-test.type.ts similarity index 84% rename from libs/common/src/test-helpers/schemas/primitives/with-schema-test.type.ts rename to libs/common/src/test-helpers/schemas/with-schema-test.type.ts index 7a073d521c..d46d95424c 100644 --- a/libs/common/src/test-helpers/schemas/primitives/with-schema-test.type.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-test.type.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { DefaultOptions } from './with-default-options.tests'; +import { DefaultOptions } from './primitive-object-tests/with-default-options.tests'; export type SchemaTestOptionsRequired = 'Options Required'; export type SchemaTestOptionsOptional = 'Options Optional'; diff --git a/libs/common/src/test-helpers/schemas/primitives/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/primitives/with-schema-validation.tests.ts rename to libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts diff --git a/libs/common/src/test-helpers/schemas/with-schema.tests.ts b/libs/common/src/test-helpers/schemas/with-schema.tests.ts deleted file mode 100644 index ba02cfe501..0000000000 --- a/libs/common/src/test-helpers/schemas/with-schema.tests.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ZodSchema } from 'zod'; - -type SchemaTestCases = { aTestCase: () => any; description: string }[]; - -/** - * This is a reusable test to allow for complete testing of zod schemas - * It can be used on it's own, or built into further test helpers - */ -export const withSchemaTests = ({ - schema, - failureTestCases, - successTestCases, -}: { - schema: ZodSchema; - failureTestCases: SchemaTestCases; - successTestCases: SchemaTestCases; -}) => { - it.each(failureTestCases)('should fail parsing if $description', ({ aTestCase }) => { - const { success } = schema.safeParse(aTestCase()); - expect(success).toBe(false); - }); - - it.each(successTestCases)('should pass parsing if $description', ({ aTestCase }) => { - const { success } = schema.safeParse(aTestCase()); - expect(success).toBe(true); - }); -}; diff --git a/libs/common/src/test-helpers/schemas/with-upsert-tfm-user-request-schema.tests.ts b/libs/common/src/test-helpers/schemas/with-upsert-tfm-user-request-schema.tests.ts deleted file mode 100644 index b1041eec28..0000000000 --- a/libs/common/src/test-helpers/schemas/with-upsert-tfm-user-request-schema.tests.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ZodSchema } from 'zod'; -import { withSchemaTests } from './with-schema.tests'; -import { getCreateTfmUserRequestFailureTestCases, getCreateTfmUserRequestSuccessTestCases } from './with-create-tfm-user-request-schema.tests'; - -/** - * This is a reusable test to allow for complete testing of schemas that - * utilise the UPSERT_TFM_USER_REQUEST_SCHEMA as part of their definition - * - * Note: UPSERT_TFM_USER_REQUEST_SCHEMA is effectively an alias for CREATE_TFM_USER_REQUEST_SCHEMA - */ - -type TestCasesParams = { - getTestObjectWithUpdatedUpsertTfmUserRequestParams: (upsertTfmUserRequest: unknown) => unknown; -}; - -type WithUpsertTfmUserRequestSchemaTestsParams = { - schema: ZodSchema; -} & Partial; - -export function withUpsertTfmUserRequestSchemaTests({ - schema, - getTestObjectWithUpdatedUpsertTfmUserRequestParams = (upsertTfmUserRequest) => upsertTfmUserRequest, -}: WithUpsertTfmUserRequestSchemaTestsParams) { - describe('with UPSERT_TFM_USER_REQUEST_SCHEMA tests', () => { - withSchemaTests({ - schema, - failureTestCases: getUpsertTfmUserRequestFailureTestCases({ getTestObjectWithUpdatedUpsertTfmUserRequestParams }), - successTestCases: getUpsertTfmUserRequestSuccessTestCases({ getTestObjectWithUpdatedUpsertTfmUserRequestParams }), - }); - }); -} - -export function getUpsertTfmUserRequestFailureTestCases({ - getTestObjectWithUpdatedUpsertTfmUserRequestParams = (upsertTfmUserRequest) => upsertTfmUserRequest, -}: TestCasesParams) { - return getCreateTfmUserRequestFailureTestCases({ getTestObjectWithUpdatedCreateTfmUserRequestParams: getTestObjectWithUpdatedUpsertTfmUserRequestParams }); -} - -export function getUpsertTfmUserRequestSuccessTestCases({ - getTestObjectWithUpdatedUpsertTfmUserRequestParams = (upsertTfmUserRequest) => upsertTfmUserRequest, -}: TestCasesParams) { - return getCreateTfmUserRequestSuccessTestCases({ getTestObjectWithUpdatedCreateTfmUserRequestParams: getTestObjectWithUpdatedUpsertTfmUserRequestParams }); -} From a2bf55121637a6d9636c08aa5c067834f7a0350a Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 4 Dec 2024 15:13:17 +0000 Subject: [PATCH 025/133] feat(dtfs2-6892): fix tests --- .../src/schemas/portal-user.update.test.ts | 60 +++++++++++++------ .../schemas/with-schema-validation.tests.ts | 22 +++++++ 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/libs/common/src/schemas/portal-user.update.test.ts b/libs/common/src/schemas/portal-user.update.test.ts index d2016607bf..f820266d4e 100644 --- a/libs/common/src/schemas/portal-user.update.test.ts +++ b/libs/common/src/schemas/portal-user.update.test.ts @@ -8,38 +8,35 @@ describe('PORTAL_USER', () => { describe('UPDATE', () => { withSchemaValidationTests({ schema: UPDATE, + schemaTestOptions: { + isPartial: true, + }, aValidPayload, testCases: [ { parameterPath: 'username', type: 'string', - options: { isOptional: true }, }, { parameterPath: 'firstname', type: 'string', - options: { isOptional: true }, }, { parameterPath: 'surname', type: 'string', - options: { isOptional: true }, }, { parameterPath: 'email', type: 'string', - options: { isOptional: true }, }, { parameterPath: 'timezone', type: 'string', - options: { isOptional: true }, }, { parameterPath: 'roles', type: 'Array', options: { - isOptional: true, arrayTypeTestCase: { type: 'string', }, @@ -48,38 +45,59 @@ describe('PORTAL_USER', () => { { parameterPath: 'user-status', type: 'string', - - options: { isOptional: true }, }, { parameterPath: 'salt', type: 'string', - - options: { isOptional: true }, }, { parameterPath: 'hash', type: 'string', - - options: { isOptional: true }, }, { parameterPath: 'auditRecord', type: 'AUDIT_DATABASE_RECORD_SCHEMA', - - options: { isOptional: true }, }, { parameterPath: 'isTrusted', type: 'boolean', - - options: { isOptional: true }, }, { parameterPath: 'disabled', type: 'boolean', - options: { isOptional: true }, + }, + { + parameterPath: 'lastLogin', + type: 'number', + }, + { + parameterPath: 'loginFailureCount', + type: 'number', + }, + { + parameterPath: 'passwordUpdatedAt', + type: 'number', + }, + { + parameterPath: 'resetPwdToken', + type: 'string', + }, + { + parameterPath: 'resetPwdTimestamp', + type: 'string', + }, + { + parameterPath: 'sessionIdentifier', + type: 'string', + }, + { + parameterPath: 'signInLinkSendDate', + type: 'number', + }, + { + parameterPath: 'signInLinkSendCount', + type: 'number', }, ], }); @@ -104,5 +122,13 @@ function aValidPayload(): z.infer { salt: '01', hash: '02', auditRecord: generatePortalUserAuditDatabaseRecord(new ObjectId()), + lastLogin: 1620000000000, + loginFailureCount: 0, + passwordUpdatedAt: 1620000000000, + resetPwdToken: 'resetPwdToken', + resetPwdTimestamp: 'resetPwdTimestamp', + sessionIdentifier: 'sessionIdentifier', + signInLinkSendDate: 1620000000000, + signInLinkSendCount: 0, }; } diff --git a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts index fc3c34043f..8352384905 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-param-reassign */ import { z, ZodSchema } from 'zod'; import { getTestsForParameter, TestCase } from './get-tests-for-parameter.tests'; @@ -9,6 +10,10 @@ type BaseOptions = { overrideGetTestObjectWithUpdatedField?: (newValue: unknown) => unknown; }; +type SchemaTestOptions = { + isPartial?: boolean; +}; + export type TestCaseWithPathParameter = { parameterPath: string; options?: BaseOptions; @@ -16,16 +21,33 @@ export type TestCaseWithPathParameter = { export const withSchemaValidationTests = ({ schema, + schemaTestOptions = {}, aValidPayload, testCases, }: { schema: Schema; + schemaTestOptions?: SchemaTestOptions; testCases: TestCaseWithPathParameter[]; aValidPayload: () => z.infer; }) => { + const schemaTestOptionsDefaults: Partial = { isPartial: false }; + + const mergedSchemaTestOptions = { + ...schemaTestOptionsDefaults, + ...schemaTestOptions, + }; + testCases.forEach((testCase) => { const { parameterPath } = testCase; + // Turns parameter optional if the schema is a partial + if (mergedSchemaTestOptions.isPartial) { + if (!testCase.options) { + testCase.options = {}; + } + testCase.options.isOptional = true; + } + const getTestObjectWithUpdatedField = testCase.options?.overrideGetTestObjectWithUpdatedField !== undefined ? testCase.options.overrideGetTestObjectWithUpdatedField From f8c998d5aa70a652fe3b1ca7d0e24e584913f49d Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 4 Dec 2024 15:14:58 +0000 Subject: [PATCH 026/133] feat(dtfs2-6892): fix tests --- .../tfm/update-tfm-user.schema.test.ts | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts diff --git a/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts b/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts new file mode 100644 index 0000000000..c92f014d26 --- /dev/null +++ b/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts @@ -0,0 +1,110 @@ +import { ObjectId } from 'mongodb'; +import { withSchemaValidationTests } from '../../test-helpers'; +import { UpdateTfmUserRequest } from '../../types'; +import { TEAM_IDS } from '../../constants'; +import { UPDATE_TFM_USER_REQUEST_SCHEMA } from './update-tfm-user-request.schema'; + +describe('UPDATE_TFM_USER_SCHEMA', () => { + withSchemaValidationTests({ + schema: UPDATE_TFM_USER_REQUEST_SCHEMA, + schemaTestOptions: { + isPartial: true, + }, + aValidPayload, + testCases: [ + { + parameterPath: '_id', + type: 'OBJECT_ID_SCHEMA', + }, + { + parameterPath: 'username', + type: 'string', + }, + { + parameterPath: 'email', + type: 'string', + }, + { + parameterPath: 'timezone', + type: 'string', + }, + { + parameterPath: 'firstName', + type: 'string', + }, + { + parameterPath: 'lastName', + type: 'string', + }, + { + parameterPath: 'status', + type: 'string', + }, + { + parameterPath: 'sessionIdentifier', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'salt', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'hash', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'loginFailureCount', + type: 'number', + options: { isOptional: true }, + }, + { + parameterPath: 'azureOid', + type: 'string', + options: { isOptional: true }, + }, + { + parameterPath: 'teams', + type: 'Array', + options: { + arrayTypeTestCase: { + type: 'TfmTeamSchema', + }, + }, + }, + { + parameterPath: 'auditRecord', + type: 'AUDIT_DATABASE_RECORD_SCHEMA', + options: { isOptional: true }, + }, + ], + }); + + function aValidPayload(): UpdateTfmUserRequest { + return { + _id: new ObjectId(), + username: 'test-user', + email: 'test-user@test.com', + teams: [TEAM_IDS.PIM], + timezone: 'Europe/London', + firstName: 'FirstName', + lastName: 'LastName', + status: 'active', + lastLogin: 1234567890123, + sessionIdentifier: 'a-session-identifier', + auditRecord: { + lastUpdatedAt: '2024-05-17T15:35:32.496 +00:00', + lastUpdatedByIsSystem: true, + lastUpdatedByPortalUserId: null, + lastUpdatedByTfmUserId: null, + noUserLoggedIn: null, + }, + salt: 'a-salt', + hash: 'a-hash', + loginFailureCount: 0, + azureOid: 'a-azure-oid', + }; + } +}); From 74c8cacacd0a8203cfda09cf825e6017e3a6a8d3 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 4 Dec 2024 15:29:24 +0000 Subject: [PATCH 027/133] feat(dtfs2-6892): add unix timestamp tests --- .../iso-date-time-stamp.schema.test.ts | 2 +- libs/common/src/schemas/object-id.test.ts | 6 +-- .../src/schemas/portal-user.update.test.ts | 2 +- .../src/schemas/tfm/tfm-user.schema.test.ts | 4 ++ .../tfm/update-tfm-user.schema.test.ts | 4 ++ .../src/schemas/unix-timestamp.schema.test.ts | 23 ++++++++ .../schemas/custom-objects-tests/index.ts | 7 +++ .../with-iso-date-time-stamp-schema.tests.ts | 0 ...ect-id-or-object-id-string-schema.tests.ts | 0 .../with-object-id-schema.tests.ts | 0 .../with-object-id-string-schema.tests.ts | 0 ...nix-timestamp-milliseconds-schema.tests.ts | 33 ++++++++++++ .../with-unix-timestamp-schema.tests.ts | 33 ++++++++++++ ...ith-unix-timestamp-seconds-schema.tests.ts | 33 ++++++++++++ .../schemas/get-tests-for-parameter.tests.ts | 53 ++++++++++++++----- libs/common/src/test-helpers/schemas/index.ts | 1 + .../schemas/schema-tests/index.ts | 4 -- 17 files changed, 184 insertions(+), 21 deletions(-) create mode 100644 libs/common/src/schemas/unix-timestamp.schema.test.ts create mode 100644 libs/common/src/test-helpers/schemas/custom-objects-tests/index.ts rename libs/common/src/test-helpers/schemas/{schema-tests => custom-objects-tests}/with-iso-date-time-stamp-schema.tests.ts (100%) rename libs/common/src/test-helpers/schemas/{schema-tests => custom-objects-tests}/with-object-id-or-object-id-string-schema.tests.ts (100%) rename libs/common/src/test-helpers/schemas/{schema-tests => custom-objects-tests}/with-object-id-schema.tests.ts (100%) rename libs/common/src/test-helpers/schemas/{schema-tests => custom-objects-tests}/with-object-id-string-schema.tests.ts (100%) create mode 100644 libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts create mode 100644 libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts diff --git a/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts b/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts index 5dfd29e329..1dcd1a7f75 100644 --- a/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts +++ b/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts @@ -1,4 +1,4 @@ -import { withIsoDateTimeStampSchemaTests } from '../test-helpers/schemas/schema-tests/with-iso-date-time-stamp-schema.tests'; +import { withIsoDateTimeStampSchemaTests } from '../test-helpers/schemas/custom-objects-tests/with-iso-date-time-stamp-schema.tests'; import { ISO_DATE_TIME_STAMP_SCHEMA } from './iso-date-time-stamp.schema'; describe('ISO_DATE_TIME_STAMP_SCHEMA', () => { diff --git a/libs/common/src/schemas/object-id.test.ts b/libs/common/src/schemas/object-id.test.ts index ab4b1234af..a0fdecf396 100644 --- a/libs/common/src/schemas/object-id.test.ts +++ b/libs/common/src/schemas/object-id.test.ts @@ -1,7 +1,7 @@ import { OBJECT_ID_SCHEMA, OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA, OBJECT_ID_STRING_SCHEMA } from './object-id'; -import { withObjectIdSchemaTests } from '../test-helpers/schemas/schema-tests/with-object-id-schema.tests'; -import { withObjectIdOrObjectIdStringSchemaTests } from '../test-helpers/schemas/schema-tests/with-object-id-or-object-id-string-schema.tests'; -import { withObjectIdStringSchemaTests } from '../test-helpers/schemas/schema-tests/with-object-id-string-schema.tests'; +import { withObjectIdSchemaTests } from '../test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests'; +import { withObjectIdOrObjectIdStringSchemaTests } from '../test-helpers/schemas/custom-objects-tests/with-object-id-or-object-id-string-schema.tests'; +import { withObjectIdStringSchemaTests } from '../test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests'; describe('OBJECT_ID_SCHEMA', () => { withObjectIdSchemaTests({ diff --git a/libs/common/src/schemas/portal-user.update.test.ts b/libs/common/src/schemas/portal-user.update.test.ts index f820266d4e..91c3b76e1f 100644 --- a/libs/common/src/schemas/portal-user.update.test.ts +++ b/libs/common/src/schemas/portal-user.update.test.ts @@ -69,7 +69,7 @@ describe('PORTAL_USER', () => { }, { parameterPath: 'lastLogin', - type: 'number', + type: 'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA', }, { parameterPath: 'loginFailureCount', diff --git a/libs/common/src/schemas/tfm/tfm-user.schema.test.ts b/libs/common/src/schemas/tfm/tfm-user.schema.test.ts index 03b0b7719b..7f5ef91a22 100644 --- a/libs/common/src/schemas/tfm/tfm-user.schema.test.ts +++ b/libs/common/src/schemas/tfm/tfm-user.schema.test.ts @@ -37,6 +37,10 @@ describe('TFM_USER_SCHEMA', () => { parameterPath: 'status', type: 'string', }, + { + parameterPath: 'lastLogin', + type: 'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA', + }, { parameterPath: 'sessionIdentifier', type: 'string', diff --git a/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts b/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts index c92f014d26..bc2a230560 100644 --- a/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts +++ b/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts @@ -40,6 +40,10 @@ describe('UPDATE_TFM_USER_SCHEMA', () => { parameterPath: 'status', type: 'string', }, + { + parameterPath: 'lastLogin', + type: 'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA', + }, { parameterPath: 'sessionIdentifier', type: 'string', diff --git a/libs/common/src/schemas/unix-timestamp.schema.test.ts b/libs/common/src/schemas/unix-timestamp.schema.test.ts new file mode 100644 index 0000000000..f6348ba997 --- /dev/null +++ b/libs/common/src/schemas/unix-timestamp.schema.test.ts @@ -0,0 +1,23 @@ +import { withUnixTimestampMillisecondsSchemaTests, withUnixTimestampSchemaTests, withUnixTimestampSecondsSchemaTests } from '../test-helpers'; +import { UNIX_TIMESTAMP_MILLISECONDS_SCHEMA, UNIX_TIMESTAMP_SCHEMA, UNIX_TIMESTAMP_SECONDS_SCHEMA } from './unix-timestamp.schema'; + +describe('UNIX_TIMESTAMP_SCHEMA', () => { + withUnixTimestampSchemaTests({ + schema: UNIX_TIMESTAMP_SCHEMA, + getTestObjectWithUpdatedField: (newValue) => newValue, + }); +}); + +describe('UNIX_TIMESTAMP_MILLISECONDS_SCHEMA', () => { + withUnixTimestampMillisecondsSchemaTests({ + schema: UNIX_TIMESTAMP_MILLISECONDS_SCHEMA, + getTestObjectWithUpdatedField: (newValue) => newValue, + }); +}); + +describe('UNIX_TIMESTAMP_SECONDS_SCHEMA', () => { + withUnixTimestampSecondsSchemaTests({ + schema: UNIX_TIMESTAMP_SECONDS_SCHEMA, + getTestObjectWithUpdatedField: (newValue) => newValue, + }); +}); diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/index.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/index.ts new file mode 100644 index 0000000000..76c04e0f20 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/index.ts @@ -0,0 +1,7 @@ +export * from './with-iso-date-time-stamp-schema.tests'; +export * from './with-object-id-or-object-id-string-schema.tests'; +export * from './with-object-id-schema.tests'; +export * from './with-object-id-string-schema.tests'; +export * from './with-unix-timestamp-schema.tests'; +export * from './with-unix-timestamp-milliseconds-schema.tests'; +export * from './with-unix-timestamp-seconds-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-iso-date-time-stamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-iso-date-time-stamp-schema.tests.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/schema-tests/with-iso-date-time-stamp-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-objects-tests/with-iso-date-time-stamp-schema.tests.ts diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-object-id-or-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-or-object-id-string-schema.tests.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/schema-tests/with-object-id-or-object-id-string-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-or-object-id-string-schema.tests.ts diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-object-id-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/schema-tests/with-object-id-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests.ts diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/schema-tests/with-object-id-string-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests.ts diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts new file mode 100644 index 0000000000..68aa5ac348 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts @@ -0,0 +1,33 @@ +import { ZodSchema } from 'zod'; +import { WithSchemaTestParams } from '../with-schema-test.type'; +import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; +import { withNumberTests } from '../primitive-object-tests'; + +export const withUnixTimestampMillisecondsSchemaTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedField, +}: WithSchemaTestParams) => { + describe('with UNIX_TIMESTAMP_MILLISECONDS_SCHEMA tests', () => { + withDefaultOptionsTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + + withNumberTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + it('should fail parsing if the parameter is not positive number', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); + expect(success).toBe(false); + }); + + it('should fail parsing if the parameter is not an int number', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); + expect(success).toBe(false); + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts new file mode 100644 index 0000000000..10fc7aca9a --- /dev/null +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts @@ -0,0 +1,33 @@ +import { ZodSchema } from 'zod'; +import { WithSchemaTestParams } from '../with-schema-test.type'; +import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; +import { withNumberTests } from '../primitive-object-tests'; + +export const withUnixTimestampSchemaTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedField, +}: WithSchemaTestParams) => { + describe('with UNIX_TIMESTAMP_SCHEMA tests', () => { + withDefaultOptionsTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + + withNumberTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + it('should fail parsing if the parameter is not positive number', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); + expect(success).toBe(false); + }); + + it('should fail parsing if the parameter is not an int number', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); + expect(success).toBe(false); + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts new file mode 100644 index 0000000000..8e65668390 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts @@ -0,0 +1,33 @@ +import { ZodSchema } from 'zod'; +import { WithSchemaTestParams } from '../with-schema-test.type'; +import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; +import { withNumberTests } from '../primitive-object-tests'; + +export const withUnixTimestampSecondsSchemaTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedField, +}: WithSchemaTestParams) => { + describe('with UNIX_TIMESTAMP_SECONDS_SCHEMA tests', () => { + withDefaultOptionsTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + + withNumberTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + it('should fail parsing if the parameter is not positive number', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); + expect(success).toBe(false); + }); + + it('should fail parsing if the parameter is not an int number', () => { + const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); + expect(success).toBe(false); + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas/get-tests-for-parameter.tests.ts b/libs/common/src/test-helpers/schemas/get-tests-for-parameter.tests.ts index defeb81ac2..a8a01879fb 100644 --- a/libs/common/src/test-helpers/schemas/get-tests-for-parameter.tests.ts +++ b/libs/common/src/test-helpers/schemas/get-tests-for-parameter.tests.ts @@ -1,16 +1,15 @@ import { ZodSchema } from 'zod'; -import { withNumberTests } from './primitive-object-tests/with-number.tests'; -import { withObjectIdSchemaTests } from './schema-tests/with-object-id-schema.tests'; -import { withStringTests } from './primitive-object-tests/with-string.tests'; -import { withTfmTeamSchemaTests } from './schema-tests/with-tfm-team-schema.tests'; -import { withArrayTests, WithArrayTestsOptions } from './primitive-object-tests/with-array.tests'; -import { withIsoDateTimeStampSchemaTests } from './schema-tests/with-iso-date-time-stamp-schema.tests'; -import { withAuditDatabaseRecordSchemaTests } from './schema-tests/with-audit-database-record-schema.tests'; -import { withObjectIdOrObjectIdStringSchemaTests } from './schema-tests/with-object-id-or-object-id-string-schema.tests'; -import { withObjectIdStringSchemaTests } from './schema-tests/with-object-id-string-schema.tests'; -import { DefaultOptions } from './primitive-object-tests/with-default-options.tests'; -import { withBooleanTests } from './primitive-object-tests/with-boolean.tests'; -import { withEntraIdUserSchemaTests } from './schema-tests/with-entra-id-user-schema.tests'; +import { + withUnixTimestampMillisecondsSchemaTests, + withUnixTimestampSecondsSchemaTests, + withUnixTimestampSchemaTests, + withObjectIdSchemaTests, + withObjectIdStringSchemaTests, + withObjectIdOrObjectIdStringSchemaTests, + withIsoDateTimeStampSchemaTests, +} from './custom-objects-tests'; +import { DefaultOptions, WithArrayTestsOptions, withStringTests, withNumberTests, withBooleanTests, withArrayTests } from './primitive-object-tests'; +import { withTfmTeamSchemaTests, withAuditDatabaseRecordSchemaTests, withEntraIdUserSchemaTests } from './schema-tests'; export type TestCaseTypes = | 'string' @@ -18,6 +17,9 @@ export type TestCaseTypes = | 'boolean' | 'Array' | 'TfmTeamSchema' + | 'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA' + | 'UNIX_TIMESTAMP_SECONDS_SCHEMA' + | 'UNIX_TIMESTAMP_SCHEMA' | 'OBJECT_ID_SCHEMA' | 'OBJECT_ID_STRING_SCHEMA' | 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA' @@ -39,6 +41,9 @@ export type TestCase = | TestCaseWithType<'boolean'> | TestCaseWithTypeAndRequiredOptions<'Array', WithArrayTestsOptions> | TestCaseWithType<'TfmTeamSchema'> + | TestCaseWithType<'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA'> + | TestCaseWithType<'UNIX_TIMESTAMP_SECONDS_SCHEMA'> + | TestCaseWithType<'UNIX_TIMESTAMP_SCHEMA'> | TestCaseWithType<'OBJECT_ID_SCHEMA'> | TestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> | TestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> @@ -98,6 +103,30 @@ export const getTestsForParameter = ({ }); break; + case 'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA': + withUnixTimestampMillisecondsSchemaTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + + case 'UNIX_TIMESTAMP_SECONDS_SCHEMA': + withUnixTimestampSecondsSchemaTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + + case 'UNIX_TIMESTAMP_SCHEMA': + withUnixTimestampSchemaTests({ + schema, + options, + getTestObjectWithUpdatedField, + }); + break; + case 'OBJECT_ID_SCHEMA': withObjectIdSchemaTests({ schema, diff --git a/libs/common/src/test-helpers/schemas/index.ts b/libs/common/src/test-helpers/schemas/index.ts index c537773c4f..dfb9ea7911 100644 --- a/libs/common/src/test-helpers/schemas/index.ts +++ b/libs/common/src/test-helpers/schemas/index.ts @@ -1,3 +1,4 @@ +export * from './custom-objects-tests'; export * from './schema-tests'; export * from './primitive-object-tests'; export * from './with-schema-validation.tests'; diff --git a/libs/common/src/test-helpers/schemas/schema-tests/index.ts b/libs/common/src/test-helpers/schemas/schema-tests/index.ts index 3b8cc53f31..352dbe8665 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/index.ts @@ -1,7 +1,3 @@ export * from './with-audit-database-record-schema.tests'; export * from './with-entra-id-user-schema.tests'; -export * from './with-iso-date-time-stamp-schema.tests'; -export * from './with-object-id-or-object-id-string-schema.tests'; -export * from './with-object-id-schema.tests'; -export * from './with-object-id-string-schema.tests'; export * from './with-tfm-team-schema.tests'; From 836239ca8a95b07ad3ed04ef4c9116268306675f Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 4 Dec 2024 15:56:35 +0000 Subject: [PATCH 028/133] feat(dtfs2-6892): tidy, add comments --- .../src/schemas/tfm/tfm-user.schema.test.ts | 1 + .../schemas/custom-objects-tests/index.ts | 4 ++ .../with-object-id-schema.tests.ts | 1 + .../with-object-id-string-schema.tests.ts | 1 + ...nix-timestamp-milliseconds-schema.tests.ts | 1 + .../with-unix-timestamp-schema.tests.ts | 1 + ...ith-unix-timestamp-seconds-schema.tests.ts | 1 + .../schemas/primitive-object-tests/index.ts | 6 +++ .../with-array.tests.ts | 5 +- .../with-default-options.tests.ts | 8 +++ .../schemas/schema-tests/index.ts | 4 ++ .../schemas/with-schema-test.type.ts | 3 -- .../schemas/with-schema-validation.tests.ts | 20 +++---- .../schemas/with-test-for-test-case.type.ts | 45 ++++++++++++++++ ...er.tests.ts => with-tests-for-testcase.ts} | 52 ++++--------------- 15 files changed, 96 insertions(+), 57 deletions(-) create mode 100644 libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts rename libs/common/src/test-helpers/schemas/{get-tests-for-parameter.tests.ts => with-tests-for-testcase.ts} (66%) diff --git a/libs/common/src/schemas/tfm/tfm-user.schema.test.ts b/libs/common/src/schemas/tfm/tfm-user.schema.test.ts index 7f5ef91a22..173e03fc2b 100644 --- a/libs/common/src/schemas/tfm/tfm-user.schema.test.ts +++ b/libs/common/src/schemas/tfm/tfm-user.schema.test.ts @@ -40,6 +40,7 @@ describe('TFM_USER_SCHEMA', () => { { parameterPath: 'lastLogin', type: 'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA', + options: { isOptional: true }, }, { parameterPath: 'sessionIdentifier', diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/index.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/index.ts index 76c04e0f20..28ffc5d1a4 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/index.ts @@ -1,3 +1,7 @@ +/** + * These tests are for schemas that define specific types, not for schemas that define specific object shapes + * ie these schemas represent a single field, not a whole object + */ export * from './with-iso-date-time-stamp-schema.tests'; export * from './with-object-id-or-object-id-string-schema.tests'; export * from './with-object-id-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests.ts index caa29ef84b..a3e432c857 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests.ts @@ -10,6 +10,7 @@ export const withObjectIdSchemaTests = ({ schema, opti options, getTestObjectWithUpdatedField, }); + it('should fail parsing if the parameter is not an ObjectId', () => { const { success } = schema.safeParse(getTestObjectWithUpdatedField('string')); expect(success).toBe(false); diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests.ts index 725f5d9987..a05e1eee80 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests.ts @@ -14,6 +14,7 @@ export const withObjectIdStringSchemaTests = ({ options, getTestObjectWithUpdatedField, }); + it('should fail parsing if the parameter is not an ObjectId', () => { const { success } = schema.safeParse(getTestObjectWithUpdatedField('string')); expect(success).toBe(false); diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts index 68aa5ac348..7638f59b9e 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts @@ -20,6 +20,7 @@ export const withUnixTimestampMillisecondsSchemaTests = { const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); expect(success).toBe(false); diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts index 10fc7aca9a..b724eb9f78 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts @@ -20,6 +20,7 @@ export const withUnixTimestampSchemaTests = ({ options, getTestObjectWithUpdatedField, }); + it('should fail parsing if the parameter is not positive number', () => { const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); expect(success).toBe(false); diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts index 8e65668390..2b87ae532b 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts @@ -20,6 +20,7 @@ export const withUnixTimestampSecondsSchemaTests = ({ options, getTestObjectWithUpdatedField, }); + it('should fail parsing if the parameter is not positive number', () => { const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); expect(success).toBe(false); diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/index.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/index.ts index 8deedb9337..d2fa0d566c 100644 --- a/libs/common/src/test-helpers/schemas/primitive-object-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/index.ts @@ -1,3 +1,9 @@ +/** + * These tests are for testing primitive types such as strings, numbers, booleans, and arrays + * They can be extended with test specific options if required + * + * Also includes the default options test cases for all tests + */ export * from './with-array.tests'; export * from './with-boolean.tests'; export * from './with-default-options.tests'; diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-array.tests.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-array.tests.ts index c3e0bce82b..946cd08054 100644 --- a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-array.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-array.tests.ts @@ -1,7 +1,8 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; -import { getTestsForParameter, TestCase } from '../get-tests-for-parameter.tests'; +import { withTestsForTestcase } from '../with-tests-for-testcase'; import { withDefaultOptionsTests } from './with-default-options.tests'; +import { TestCase } from '../with-test-for-test-case.type'; export type WithArrayTestsOptions = { arrayTypeTestCase: TestCase; @@ -44,7 +45,7 @@ export const withArrayTests = ({ } describe('when configuring the objects in the array', () => { - getTestsForParameter({ + withTestsForTestcase({ schema, testCase: arrayTestOptions.arrayTypeTestCase, getTestObjectWithUpdatedField: (value) => getTestObjectWithUpdatedField([value]), diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-default-options.tests.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-default-options.tests.ts index ce8ab5db38..d4762838db 100644 --- a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-default-options.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-default-options.tests.ts @@ -1,7 +1,15 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; +/** + * A list of default options + * + * Theses options are used to determine the behavior of the schema, and run the default tests accordingly + * + * Also includes the ability to override how the test object is created, which is useful when creating schema tests. + */ export type DefaultOptions = { + overrideGetTestObjectWithUpdatedField?: (newValue: unknown) => unknown; isOptional?: boolean; isNullable?: boolean; }; diff --git a/libs/common/src/test-helpers/schemas/schema-tests/index.ts b/libs/common/src/test-helpers/schemas/schema-tests/index.ts index 352dbe8665..75342c458f 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/index.ts @@ -1,3 +1,7 @@ +/** + * These tests are for schemas that define specific object shapes, not for schemas that define specific types + * ie these schemas represent a whole object, not a single field + */ export * from './with-audit-database-record-schema.tests'; export * from './with-entra-id-user-schema.tests'; export * from './with-tfm-team-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/with-schema-test.type.ts b/libs/common/src/test-helpers/schemas/with-schema-test.type.ts index d46d95424c..f5fda49356 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-test.type.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-test.type.ts @@ -1,9 +1,6 @@ import { ZodSchema } from 'zod'; import { DefaultOptions } from './primitive-object-tests/with-default-options.tests'; -export type SchemaTestOptionsRequired = 'Options Required'; -export type SchemaTestOptionsOptional = 'Options Optional'; - export type WithSchemaTestParams = { schema: Schema; getTestObjectWithUpdatedField: (newValue: unknown) => unknown; diff --git a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts index 8352384905..3ff0a2118b 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts @@ -1,24 +1,26 @@ /* eslint-disable no-param-reassign */ import { z, ZodSchema } from 'zod'; -import { getTestsForParameter, TestCase } from './get-tests-for-parameter.tests'; +import { withTestsForTestcase } from './with-tests-for-testcase'; +import { TestCase } from './with-test-for-test-case.type'; /** - * This base option allows for overriding of the automatically generated getTestObjectWithUpdatedField function. - * It is useful when looking at testing nested objects, but otherwise can be ignored + * Options that are specific to the schema as a whole, for instance, if the schema is a partial */ -type BaseOptions = { - overrideGetTestObjectWithUpdatedField?: (newValue: unknown) => unknown; -}; - type SchemaTestOptions = { isPartial?: boolean; }; +/** + * Test cases with the path parameter, used to create the getTestObjectWithUpdatedField function + */ export type TestCaseWithPathParameter = { parameterPath: string; - options?: BaseOptions; } & TestCase; +/** + * With schema validation tests allows for the passing in of a schema, a valid payload, and test cases to test the schema. + * It calls pre made test cases through withTestsForTestcase, after applying schema specific options and overrides + */ export const withSchemaValidationTests = ({ schema, schemaTestOptions = {}, @@ -54,7 +56,7 @@ export const withSchemaValidationTests = ({ : (newValue: unknown): unknown => ({ ...aValidPayload(), [parameterPath]: newValue }); describe(`${parameterPath} parameter tests`, () => { - getTestsForParameter({ + withTestsForTestcase({ schema, testCase, getTestObjectWithUpdatedField, diff --git a/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts b/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts new file mode 100644 index 0000000000..946ed1c4b0 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts @@ -0,0 +1,45 @@ +import { DefaultOptions, WithArrayTestsOptions } from './primitive-object-tests'; + +/** + * All test case types that have been tests implemented + */ +export type TestCaseTypes = + | 'string' + | 'number' + | 'boolean' + | 'Array' + | 'TfmTeamSchema' + | 'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA' + | 'UNIX_TIMESTAMP_SECONDS_SCHEMA' + | 'UNIX_TIMESTAMP_SCHEMA' + | 'OBJECT_ID_SCHEMA' + | 'OBJECT_ID_STRING_SCHEMA' + | 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA' + | 'ISO_DATE_TIME_STAMP_SCHEMA' + | 'AUDIT_DATABASE_RECORD_SCHEMA' + | 'ENTRA_ID_USER_SCHEMA'; + +/** + * The test case to be tested, including the type and any options that are required + * + * Allows for the passing in of additional options if required for the specific test case + */ +type TestCaseWithType = { + type: Type; +} & (AdditionalOptions extends false ? { options?: Partial } : { options: AdditionalOptions & Partial }); + +export type TestCase = + | TestCaseWithType<'string'> + | TestCaseWithType<'number'> + | TestCaseWithType<'boolean'> + | TestCaseWithType<'Array', WithArrayTestsOptions> + | TestCaseWithType<'TfmTeamSchema'> + | TestCaseWithType<'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA'> + | TestCaseWithType<'UNIX_TIMESTAMP_SECONDS_SCHEMA'> + | TestCaseWithType<'UNIX_TIMESTAMP_SCHEMA'> + | TestCaseWithType<'OBJECT_ID_SCHEMA'> + | TestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> + | TestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> + | TestCaseWithType<'ISO_DATE_TIME_STAMP_SCHEMA'> + | TestCaseWithType<'AUDIT_DATABASE_RECORD_SCHEMA'> + | TestCaseWithType<'ENTRA_ID_USER_SCHEMA'>; diff --git a/libs/common/src/test-helpers/schemas/get-tests-for-parameter.tests.ts b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts similarity index 66% rename from libs/common/src/test-helpers/schemas/get-tests-for-parameter.tests.ts rename to libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts index a8a01879fb..7c28c917c5 100644 --- a/libs/common/src/test-helpers/schemas/get-tests-for-parameter.tests.ts +++ b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts @@ -8,50 +8,16 @@ import { withObjectIdOrObjectIdStringSchemaTests, withIsoDateTimeStampSchemaTests, } from './custom-objects-tests'; -import { DefaultOptions, WithArrayTestsOptions, withStringTests, withNumberTests, withBooleanTests, withArrayTests } from './primitive-object-tests'; +import { withStringTests, withNumberTests, withBooleanTests, withArrayTests } from './primitive-object-tests'; import { withTfmTeamSchemaTests, withAuditDatabaseRecordSchemaTests, withEntraIdUserSchemaTests } from './schema-tests'; - -export type TestCaseTypes = - | 'string' - | 'number' - | 'boolean' - | 'Array' - | 'TfmTeamSchema' - | 'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA' - | 'UNIX_TIMESTAMP_SECONDS_SCHEMA' - | 'UNIX_TIMESTAMP_SCHEMA' - | 'OBJECT_ID_SCHEMA' - | 'OBJECT_ID_STRING_SCHEMA' - | 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA' - | 'ISO_DATE_TIME_STAMP_SCHEMA' - | 'AUDIT_DATABASE_RECORD_SCHEMA' - | 'ENTRA_ID_USER_SCHEMA'; - -type TestCaseWithType = { - type: Type; - options?: Partial; -}; - -type TestCaseWithTypeAndRequiredOptions = TestCaseWithType & { - options: OptionsType; -}; -export type TestCase = - | TestCaseWithType<'string'> - | TestCaseWithType<'number'> - | TestCaseWithType<'boolean'> - | TestCaseWithTypeAndRequiredOptions<'Array', WithArrayTestsOptions> - | TestCaseWithType<'TfmTeamSchema'> - | TestCaseWithType<'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA'> - | TestCaseWithType<'UNIX_TIMESTAMP_SECONDS_SCHEMA'> - | TestCaseWithType<'UNIX_TIMESTAMP_SCHEMA'> - | TestCaseWithType<'OBJECT_ID_SCHEMA'> - | TestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> - | TestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> - | TestCaseWithType<'ISO_DATE_TIME_STAMP_SCHEMA'> - | TestCaseWithType<'AUDIT_DATABASE_RECORD_SCHEMA'> - | TestCaseWithType<'ENTRA_ID_USER_SCHEMA'>; - -export const getTestsForParameter = ({ +import { TestCase } from './with-test-for-test-case.type'; + +/** + * Gets tests for a test case, using the test case type to determine which tests to run + * + * These tests are all available tests that can be easily used to test a parameter, and should be extended + */ +export const withTestsForTestcase = ({ schema, testCase, getTestObjectWithUpdatedField, From 7c3985081e041acfa3db714a27037c473465d4ac Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 4 Dec 2024 16:11:27 +0000 Subject: [PATCH 029/133] feat(dtfs2-6892): rename field to param --- .../src/schemas/audit-database-record.test.ts | 2 +- .../iso-date-time-stamp.schema.test.ts | 2 +- libs/common/src/schemas/object-id.test.ts | 6 ++-- ...d-user.schema.entra-id-user-schema.test.ts | 2 +- .../src/schemas/tfm/tfm-team.schema.test.ts | 2 +- .../src/schemas/unix-timestamp.schema.test.ts | 6 ++-- .../with-iso-date-time-stamp-schema.tests.ts | 12 +++---- ...ect-id-or-object-id-string-schema.tests.ts | 10 +++--- .../with-object-id-schema.tests.ts | 20 +++++++----- .../with-object-id-string-schema.tests.ts | 16 +++++----- ...nix-timestamp-milliseconds-schema.tests.ts | 10 +++--- .../with-unix-timestamp-schema.tests.ts | 10 +++--- ...ith-unix-timestamp-seconds-schema.tests.ts | 10 +++--- .../with-array.tests.ts | 12 +++---- .../with-boolean.tests.ts | 8 ++--- .../with-default-options.tests.ts | 10 +++--- .../with-number.tests.ts | 8 ++--- .../with-string.tests.ts | 8 ++--- ...with-audit-database-record-schema.tests.ts | 16 ++++++---- .../with-entra-id-user-schema.tests.ts | 20 +++++++----- .../with-tfm-team-schema.tests.ts | 8 ++--- .../schemas/with-schema-test.type.ts | 2 +- .../schemas/with-schema-validation.tests.ts | 6 ++-- .../schemas/with-tests-for-testcase.ts | 32 +++++++++---------- 24 files changed, 124 insertions(+), 114 deletions(-) diff --git a/libs/common/src/schemas/audit-database-record.test.ts b/libs/common/src/schemas/audit-database-record.test.ts index d66e242dee..e20911c161 100644 --- a/libs/common/src/schemas/audit-database-record.test.ts +++ b/libs/common/src/schemas/audit-database-record.test.ts @@ -4,6 +4,6 @@ import { AUDIT_DATABASE_RECORD_SCHEMA } from './audit-database-record.schema'; describe('AUDIT_DATABASE_RECORD', () => { withAuditDatabaseRecordSchemaTests({ schema: AUDIT_DATABASE_RECORD_SCHEMA, - getTestObjectWithUpdatedField: (newValue) => newValue, + getTestObjectWithUpdatedParameter: (newValue) => newValue, }); }); diff --git a/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts b/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts index 1dcd1a7f75..a7248fae8b 100644 --- a/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts +++ b/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts @@ -4,6 +4,6 @@ import { ISO_DATE_TIME_STAMP_SCHEMA } from './iso-date-time-stamp.schema'; describe('ISO_DATE_TIME_STAMP_SCHEMA', () => { withIsoDateTimeStampSchemaTests({ schema: ISO_DATE_TIME_STAMP_SCHEMA, - getTestObjectWithUpdatedField: (newValue) => newValue, + getTestObjectWithUpdatedParameter: (newValue) => newValue, }); }); diff --git a/libs/common/src/schemas/object-id.test.ts b/libs/common/src/schemas/object-id.test.ts index a0fdecf396..e3979c8336 100644 --- a/libs/common/src/schemas/object-id.test.ts +++ b/libs/common/src/schemas/object-id.test.ts @@ -6,20 +6,20 @@ import { withObjectIdStringSchemaTests } from '../test-helpers/schemas/custom-ob describe('OBJECT_ID_SCHEMA', () => { withObjectIdSchemaTests({ schema: OBJECT_ID_SCHEMA, - getTestObjectWithUpdatedField: (newValue) => newValue, + getTestObjectWithUpdatedParameter: (newValue) => newValue, }); }); describe('OBJECT_ID_STRING', () => { withObjectIdStringSchemaTests({ schema: OBJECT_ID_STRING_SCHEMA, - getTestObjectWithUpdatedField: (newValue) => newValue, + getTestObjectWithUpdatedParameter: (newValue) => newValue, }); }); describe('OBJECT_ID_OR_OBJECT_ID_STRING', () => { withObjectIdOrObjectIdStringSchemaTests({ schema: OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA, - getTestObjectWithUpdatedField: (newValue) => newValue, + getTestObjectWithUpdatedParameter: (newValue) => newValue, }); }); diff --git a/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts b/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts index b84822f81a..62194319d9 100644 --- a/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts @@ -4,6 +4,6 @@ import { ENTRA_ID_USER_SCHEMA } from './entra-id-user.schema'; describe('ENTRA_ID_USER_SCHEMA', () => { withEntraIdUserSchemaTests({ schema: ENTRA_ID_USER_SCHEMA, - getTestObjectWithUpdatedField: (newValue: unknown) => newValue, + getTestObjectWithUpdatedParameter: (newValue: unknown) => newValue, }); }); diff --git a/libs/common/src/schemas/tfm/tfm-team.schema.test.ts b/libs/common/src/schemas/tfm/tfm-team.schema.test.ts index 9575670b64..fb172b3fe2 100644 --- a/libs/common/src/schemas/tfm/tfm-team.schema.test.ts +++ b/libs/common/src/schemas/tfm/tfm-team.schema.test.ts @@ -4,7 +4,7 @@ import { TfmTeamSchema } from './tfm-team.schema'; describe('tfm-team.schema', () => { withTfmTeamSchemaTests({ schema: TfmTeamSchema, - getTestObjectWithUpdatedField: (newValue: unknown) => newValue, + getTestObjectWithUpdatedParameter: (newValue: unknown) => newValue, options: { isOptional: false }, }); }); diff --git a/libs/common/src/schemas/unix-timestamp.schema.test.ts b/libs/common/src/schemas/unix-timestamp.schema.test.ts index f6348ba997..5f398d8ffb 100644 --- a/libs/common/src/schemas/unix-timestamp.schema.test.ts +++ b/libs/common/src/schemas/unix-timestamp.schema.test.ts @@ -4,20 +4,20 @@ import { UNIX_TIMESTAMP_MILLISECONDS_SCHEMA, UNIX_TIMESTAMP_SCHEMA, UNIX_TIMESTA describe('UNIX_TIMESTAMP_SCHEMA', () => { withUnixTimestampSchemaTests({ schema: UNIX_TIMESTAMP_SCHEMA, - getTestObjectWithUpdatedField: (newValue) => newValue, + getTestObjectWithUpdatedParameter: (newValue) => newValue, }); }); describe('UNIX_TIMESTAMP_MILLISECONDS_SCHEMA', () => { withUnixTimestampMillisecondsSchemaTests({ schema: UNIX_TIMESTAMP_MILLISECONDS_SCHEMA, - getTestObjectWithUpdatedField: (newValue) => newValue, + getTestObjectWithUpdatedParameter: (newValue) => newValue, }); }); describe('UNIX_TIMESTAMP_SECONDS_SCHEMA', () => { withUnixTimestampSecondsSchemaTests({ schema: UNIX_TIMESTAMP_SECONDS_SCHEMA, - getTestObjectWithUpdatedField: (newValue) => newValue, + getTestObjectWithUpdatedParameter: (newValue) => newValue, }); }); diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-iso-date-time-stamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-iso-date-time-stamp-schema.tests.ts index 2bb271d54a..0866f6f2f9 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-iso-date-time-stamp-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-iso-date-time-stamp-schema.tests.ts @@ -5,32 +5,32 @@ import { withDefaultOptionsTests } from '../primitive-object-tests/with-default- export const withIsoDateTimeStampSchemaTests = ({ schema, options = {}, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }: WithSchemaTestParams) => { describe('with ISO_DATE_TIME_STAMP_SCHEMA tests', () => { withDefaultOptionsTests({ schema, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, options, }); it('should fail parsing if the parameter is not a string', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(1)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(1)); expect(success).toBe(false); }); it('should pass parsing if the parameter is a valid ISO date', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField('2024-05-17T15:35:32.496 +00:00')); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter('2024-05-17T15:35:32.496 +00:00')); expect(success).toBe(true); }); it('should fail parsing if the parameter is a incorrectly formatted ISO date', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField('2021-01-01T00:00:00')); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter('2021-01-01T00:00:00')); expect(success).toBe(false); }); it('should fail parsing if the parameter is a incorrectly formatted ISO date', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField('2024-05-17X15:35:32.496 +00:00')); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter('2024-05-17X15:35:32.496 +00:00')); expect(success).toBe(false); }); }); diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-or-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-or-object-id-string-schema.tests.ts index b5077ae140..ca621d6f83 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-or-object-id-string-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-or-object-id-string-schema.tests.ts @@ -6,29 +6,29 @@ import { withDefaultOptionsTests } from '../primitive-object-tests/with-default- export const withObjectIdOrObjectIdStringSchemaTests = ({ schema, options = {}, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }: WithSchemaTestParams) => { describe('with OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); it('should fail parsing if the parameter is not an ObjectId', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField('string')); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter('string')); expect(success).toBe(false); }); it('should pass parsing if the parameter is an ObjectId', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(new ObjectId())); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(new ObjectId())); expect(success).toBe(true); }); it('should pass parsing if the parameter is a string representation of an ObjectId', () => { const stringObjectId = new ObjectId().toString(); - const { success } = schema.safeParse(getTestObjectWithUpdatedField(stringObjectId)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(stringObjectId)); expect(success).toBe(true); }); diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests.ts index a3e432c857..6e117c9cfb 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests.ts @@ -3,21 +3,25 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; -export const withObjectIdSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { +export const withObjectIdSchemaTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedParameter, +}: WithSchemaTestParams) => { describe('with OBJECT_ID_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); it('should fail parsing if the parameter is not an ObjectId', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField('string')); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter('string')); expect(success).toBe(false); }); it('should pass parsing if the parameter is an ObjectId', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(new ObjectId())); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(new ObjectId())); expect(success).toBe(true); }); @@ -25,18 +29,18 @@ export const withObjectIdSchemaTests = ({ schema, opti const objectId = new ObjectId(); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { data } = schema.safeParse(getTestObjectWithUpdatedField(objectId)); + const { data } = schema.safeParse(getTestObjectWithUpdatedParameter(objectId)); - expect(data).toEqual(getTestObjectWithUpdatedField(objectId)); + expect(data).toEqual(getTestObjectWithUpdatedParameter(objectId)); }); it('should transform a valid string ObjectId to an ObjectId', () => { const stringObjectId = new ObjectId().toString(); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { data } = schema.safeParse(getTestObjectWithUpdatedField(stringObjectId)); + const { data } = schema.safeParse(getTestObjectWithUpdatedParameter(stringObjectId)); - expect(data).toEqual(getTestObjectWithUpdatedField(new ObjectId(stringObjectId))); + expect(data).toEqual(getTestObjectWithUpdatedParameter(new ObjectId(stringObjectId))); }); }); }; diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests.ts index a05e1eee80..049aec1dcd 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests.ts @@ -6,24 +6,24 @@ import { withDefaultOptionsTests } from '../primitive-object-tests/with-default- export const withObjectIdStringSchemaTests = ({ schema, options = {}, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }: WithSchemaTestParams) => { describe('with OBJECT_ID_STRING_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); it('should fail parsing if the parameter is not an ObjectId', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField('string')); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter('string')); expect(success).toBe(false); }); it('should pass parsing if the parameter is a valid string representation of an ObjectId', () => { const stringObjectId = new ObjectId().toString(); - const { success } = schema.safeParse(getTestObjectWithUpdatedField(stringObjectId)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(stringObjectId)); expect(success).toBe(true); }); @@ -32,18 +32,18 @@ export const withObjectIdStringSchemaTests = ({ const stringObjectId = new ObjectId().toString(); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { data } = schema.safeParse(getTestObjectWithUpdatedField(stringObjectId)); + const { data } = schema.safeParse(getTestObjectWithUpdatedParameter(stringObjectId)); - expect(data).toEqual(getTestObjectWithUpdatedField(stringObjectId)); + expect(data).toEqual(getTestObjectWithUpdatedParameter(stringObjectId)); }); it('should transform a valid ObjectId to a string', () => { const objectId = new ObjectId(); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { data } = schema.safeParse(getTestObjectWithUpdatedField(objectId)); + const { data } = schema.safeParse(getTestObjectWithUpdatedParameter(objectId)); - expect(data).toEqual(getTestObjectWithUpdatedField(objectId.toString())); + expect(data).toEqual(getTestObjectWithUpdatedParameter(objectId.toString())); }); }); }; diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts index 7638f59b9e..cd5b41923a 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts @@ -6,28 +6,28 @@ import { withNumberTests } from '../primitive-object-tests'; export const withUnixTimestampMillisecondsSchemaTests = ({ schema, options = {}, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }: WithSchemaTestParams) => { describe('with UNIX_TIMESTAMP_MILLISECONDS_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); withNumberTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); it('should fail parsing if the parameter is not positive number', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(-1)); expect(success).toBe(false); }); it('should fail parsing if the parameter is not an int number', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(-1)); expect(success).toBe(false); }); }); diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts index b724eb9f78..5af2855df2 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts @@ -6,28 +6,28 @@ import { withNumberTests } from '../primitive-object-tests'; export const withUnixTimestampSchemaTests = ({ schema, options = {}, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }: WithSchemaTestParams) => { describe('with UNIX_TIMESTAMP_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); withNumberTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); it('should fail parsing if the parameter is not positive number', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(-1)); expect(success).toBe(false); }); it('should fail parsing if the parameter is not an int number', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(-1)); expect(success).toBe(false); }); }); diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts index 2b87ae532b..9529fe3663 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts @@ -6,28 +6,28 @@ import { withNumberTests } from '../primitive-object-tests'; export const withUnixTimestampSecondsSchemaTests = ({ schema, options = {}, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }: WithSchemaTestParams) => { describe('with UNIX_TIMESTAMP_SECONDS_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); withNumberTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); it('should fail parsing if the parameter is not positive number', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(-1)); expect(success).toBe(false); }); it('should fail parsing if the parameter is not an int number', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(-1)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(-1)); expect(success).toBe(false); }); }); diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-array.tests.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-array.tests.ts index 946cd08054..0e5fb533a3 100644 --- a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-array.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-array.tests.ts @@ -12,7 +12,7 @@ export type WithArrayTestsOptions = { export const withArrayTests = ({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }: WithSchemaTestParams) => { const arrayTestOptionsDefaults = { isAllowEmpty: true }; const arrayTestOptions = { @@ -24,22 +24,22 @@ export const withArrayTests = ({ withDefaultOptionsTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); it('should fail parsing if the parameter is not a array', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField('not an array')); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter('not an array')); expect(success).toBe(false); }); if (arrayTestOptions.isAllowEmpty) { it('should pass parsing if the parameter is an empty array', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField([])); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter([])); expect(success).toBe(true); }); } else { it('should fail parsing if the parameter is an empty array', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField([])); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter([])); expect(success).toBe(false); }); } @@ -48,7 +48,7 @@ export const withArrayTests = ({ withTestsForTestcase({ schema, testCase: arrayTestOptions.arrayTypeTestCase, - getTestObjectWithUpdatedField: (value) => getTestObjectWithUpdatedField([value]), + getTestObjectWithUpdatedParameter: (value) => getTestObjectWithUpdatedParameter([value]), }); }); }); diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-boolean.tests.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-boolean.tests.ts index 65f1b9ba4c..0b6b4b966a 100644 --- a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-boolean.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-boolean.tests.ts @@ -2,21 +2,21 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; import { withDefaultOptionsTests } from './with-default-options.tests'; -export const withBooleanTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { +export const withBooleanTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter }: WithSchemaTestParams) => { describe('with boolean tests', () => { withDefaultOptionsTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); it('should fail parsing if the parameter is not a boolean', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField('true')); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter('true')); expect(success).toBe(false); }); it('should pass parsing if the parameter is a boolean', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(true)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(true)); expect(success).toBe(true); }); }); diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-default-options.tests.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-default-options.tests.ts index d4762838db..78496aff14 100644 --- a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-default-options.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-default-options.tests.ts @@ -16,7 +16,7 @@ export type DefaultOptions = { export const withDefaultOptionsTests = ({ schema, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, options = {}, }: WithSchemaTestParams) => { const defaultOptions: DefaultOptions = { @@ -40,28 +40,28 @@ export const withDefaultOptionsTests = ({ function withIsOptionalTests() { it('should pass parsing if the parameter is missing', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(undefined)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(undefined)); expect(success).toBe(true); }); } function withIsRequiredTests() { it('should fail parsing if the parameter is missing', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(undefined)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(undefined)); expect(success).toBe(false); }); } function withIsNullableTrueTests() { it('should pass parsing if the parameter is null', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(null)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(null)); expect(success).toBe(true); }); } function withIsNullableFalseTests() { it('should fail parsing if the parameter is null', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(null)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(null)); expect(success).toBe(false); }); } diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-number.tests.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-number.tests.ts index 17c2c166d8..5c05ea3247 100644 --- a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-number.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-number.tests.ts @@ -2,21 +2,21 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; import { withDefaultOptionsTests } from './with-default-options.tests'; -export const withNumberTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { +export const withNumberTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter }: WithSchemaTestParams) => { describe('with number tests', () => { withDefaultOptionsTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); it('should fail parsing if the parameter is not a number', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField('1')); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter('1')); expect(success).toBe(false); }); it('should pass parsing if the parameter is a number', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(1)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(1)); expect(success).toBe(true); }); }); diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-string.tests.ts b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-string.tests.ts index 45626801bd..83f000a598 100644 --- a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-string.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-object-tests/with-string.tests.ts @@ -2,21 +2,21 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; import { withDefaultOptionsTests } from './with-default-options.tests'; -export const withStringTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { +export const withStringTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter }: WithSchemaTestParams) => { describe('with string tests', () => { withDefaultOptionsTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); it('should fail parsing if the parameter is not a string', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(1)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(1)); expect(success).toBe(false); }); it('should pass parsing if the parameter is a string', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField('a string')); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter('a string')); expect(success).toBe(true); }); }); diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts index dc1321541a..e80146769f 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts @@ -8,12 +8,12 @@ import { withDefaultOptionsTests } from '../primitive-object-tests/with-default- export const withAuditDatabaseRecordSchemaTests = ({ schema, options = {}, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }: WithSchemaTestParams) => { describe('with AUDIT_DATABASE_RECORD_SCHEMA tests', () => { withDefaultOptionsTests({ schema, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, options, }); @@ -25,7 +25,8 @@ export const withAuditDatabaseRecordSchemaTests = ({ parameterPath: 'lastUpdatedAt', type: 'ISO_DATE_TIME_STAMP_SCHEMA', options: { - overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedField({ ...aValidAuditRecord(), lastUpdatedAt: newValue }), + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => + getTestObjectWithUpdatedParameter({ ...aValidAuditRecord(), lastUpdatedAt: newValue }), }, }, { @@ -33,7 +34,7 @@ export const withAuditDatabaseRecordSchemaTests = ({ type: 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA', options: { overrideGetTestObjectWithUpdatedField: (newValue: unknown) => - getTestObjectWithUpdatedField({ ...aValidAuditRecord(), lastUpdatedByPortalUserId: newValue }), + getTestObjectWithUpdatedParameter({ ...aValidAuditRecord(), lastUpdatedByPortalUserId: newValue }), isNullable: true, }, }, @@ -42,7 +43,7 @@ export const withAuditDatabaseRecordSchemaTests = ({ type: 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA', options: { overrideGetTestObjectWithUpdatedField: (newValue: unknown) => - getTestObjectWithUpdatedField({ ...aValidAuditRecord(), lastUpdatedByPortalUserId: newValue }), + getTestObjectWithUpdatedParameter({ ...aValidAuditRecord(), lastUpdatedByPortalUserId: newValue }), isNullable: true, }, }, @@ -51,7 +52,7 @@ export const withAuditDatabaseRecordSchemaTests = ({ type: 'boolean', options: { overrideGetTestObjectWithUpdatedField: (newValue: unknown) => - getTestObjectWithUpdatedField({ ...aValidAuditRecord(), lastUpdatedByIsSystem: newValue }), + getTestObjectWithUpdatedParameter({ ...aValidAuditRecord(), lastUpdatedByIsSystem: newValue }), isNullable: true, }, }, @@ -59,7 +60,8 @@ export const withAuditDatabaseRecordSchemaTests = ({ parameterPath: 'noUserLoggedIn', type: 'boolean', options: { - overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedField({ ...aValidAuditRecord(), noUserLoggedIn: newValue }), + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => + getTestObjectWithUpdatedParameter({ ...aValidAuditRecord(), noUserLoggedIn: newValue }), isNullable: true, }, }, diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts index 3f83e9c74a..85876061f2 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts @@ -4,11 +4,15 @@ import { withDefaultOptionsTests } from '../primitive-object-tests'; import { withSchemaValidationTests } from '../with-schema-validation.tests'; import { WithSchemaTestParams } from '../with-schema-test.type'; -export const withEntraIdUserSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { +export const withEntraIdUserSchemaTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedParameter, +}: WithSchemaTestParams) => { describe('with ENTRA_ID_USER_SCHEMA tests', () => { withDefaultOptionsTests({ schema, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, options, }); @@ -20,7 +24,7 @@ export const withEntraIdUserSchemaTests = ({ schema, o parameterPath: 'oid', type: 'string', options: { - overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedField({ ...aValidEntraIdUser(), oid: newValue }), + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedParameter({ ...aValidEntraIdUser(), oid: newValue }), }, }, { @@ -31,7 +35,7 @@ export const withEntraIdUserSchemaTests = ({ schema, o type: 'string', }, overrideGetTestObjectWithUpdatedField: (newValue: unknown) => - getTestObjectWithUpdatedField({ ...aValidEntraIdUser(), verified_primary_email: newValue }), + getTestObjectWithUpdatedParameter({ ...aValidEntraIdUser(), verified_primary_email: newValue }), isAllowEmpty: false, }, }, @@ -43,21 +47,21 @@ export const withEntraIdUserSchemaTests = ({ schema, o type: 'string', }, overrideGetTestObjectWithUpdatedField: (newValue: unknown) => - getTestObjectWithUpdatedField({ ...aValidEntraIdUser(), verified_secondary_email: newValue }), + getTestObjectWithUpdatedParameter({ ...aValidEntraIdUser(), verified_secondary_email: newValue }), }, }, { parameterPath: 'given_name', type: 'string', options: { - overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedField({ ...aValidEntraIdUser(), given_name: newValue }), + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedParameter({ ...aValidEntraIdUser(), given_name: newValue }), }, }, { parameterPath: 'family_name', type: 'string', options: { - overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedField({ ...aValidEntraIdUser(), family_name: newValue }), + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedParameter({ ...aValidEntraIdUser(), family_name: newValue }), }, }, { @@ -67,7 +71,7 @@ export const withEntraIdUserSchemaTests = ({ schema, o arrayTypeTestCase: { type: 'TfmTeamSchema', }, - overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedField({ ...aValidEntraIdUser(), roles: newValue }), + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedParameter({ ...aValidEntraIdUser(), roles: newValue }), }, }, ], diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-team-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-team-schema.tests.ts index 77a022ec1d..082ad229fa 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-team-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-team-schema.tests.ts @@ -3,21 +3,21 @@ import { WithSchemaTestParams } from '../with-schema-test.type'; import { TEAM_IDS } from '../../../constants'; import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; -export const withTfmTeamSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedField }: WithSchemaTestParams) => { +export const withTfmTeamSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter }: WithSchemaTestParams) => { describe('with TfmTeamSchema tests', () => { withDefaultOptionsTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); it('should fail parsing if the parameter is not a valid tfm-team', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField('not-a-team')); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter('not-a-team')); expect(success).toBe(false); }); it('should pass parsing if the parameter is a valid tfm-team', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedField(TEAM_IDS.PIM)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(TEAM_IDS.PIM)); expect(success).toBe(true); }); }); diff --git a/libs/common/src/test-helpers/schemas/with-schema-test.type.ts b/libs/common/src/test-helpers/schemas/with-schema-test.type.ts index f5fda49356..0e13cd520a 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-test.type.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-test.type.ts @@ -3,5 +3,5 @@ import { DefaultOptions } from './primitive-object-tests/with-default-options.te export type WithSchemaTestParams = { schema: Schema; - getTestObjectWithUpdatedField: (newValue: unknown) => unknown; + getTestObjectWithUpdatedParameter: (newValue: unknown) => unknown; } & (SchemaTestOptions extends false ? { options?: Partial } : { options: SchemaTestOptions & Partial }); diff --git a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts index 3ff0a2118b..7fcac64ca6 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts @@ -11,7 +11,7 @@ type SchemaTestOptions = { }; /** - * Test cases with the path parameter, used to create the getTestObjectWithUpdatedField function + * Test cases with the path parameter, used to create the getTestObjectWithUpdatedParameter function */ export type TestCaseWithPathParameter = { parameterPath: string; @@ -50,7 +50,7 @@ export const withSchemaValidationTests = ({ testCase.options.isOptional = true; } - const getTestObjectWithUpdatedField = + const getTestObjectWithUpdatedParameter = testCase.options?.overrideGetTestObjectWithUpdatedField !== undefined ? testCase.options.overrideGetTestObjectWithUpdatedField : (newValue: unknown): unknown => ({ ...aValidPayload(), [parameterPath]: newValue }); @@ -59,7 +59,7 @@ export const withSchemaValidationTests = ({ withTestsForTestcase({ schema, testCase, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); }); }); diff --git a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts index 7c28c917c5..59cde1f3c7 100644 --- a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts +++ b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts @@ -20,11 +20,11 @@ import { TestCase } from './with-test-for-test-case.type'; export const withTestsForTestcase = ({ schema, testCase, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }: { schema: Schema; testCase: TestCase; - getTestObjectWithUpdatedField: (newValue: unknown) => unknown; + getTestObjectWithUpdatedParameter: (newValue: unknown) => unknown; }) => { const { type, options } = testCase; @@ -33,7 +33,7 @@ export const withTestsForTestcase = ({ withStringTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; @@ -41,7 +41,7 @@ export const withTestsForTestcase = ({ withNumberTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; @@ -49,7 +49,7 @@ export const withTestsForTestcase = ({ withBooleanTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; @@ -57,7 +57,7 @@ export const withTestsForTestcase = ({ withArrayTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; @@ -65,7 +65,7 @@ export const withTestsForTestcase = ({ withTfmTeamSchemaTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; @@ -73,7 +73,7 @@ export const withTestsForTestcase = ({ withUnixTimestampMillisecondsSchemaTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; @@ -81,7 +81,7 @@ export const withTestsForTestcase = ({ withUnixTimestampSecondsSchemaTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; @@ -89,7 +89,7 @@ export const withTestsForTestcase = ({ withUnixTimestampSchemaTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; @@ -97,7 +97,7 @@ export const withTestsForTestcase = ({ withObjectIdSchemaTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; @@ -105,7 +105,7 @@ export const withTestsForTestcase = ({ withObjectIdStringSchemaTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; @@ -113,7 +113,7 @@ export const withTestsForTestcase = ({ withObjectIdOrObjectIdStringSchemaTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; @@ -121,7 +121,7 @@ export const withTestsForTestcase = ({ withIsoDateTimeStampSchemaTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; @@ -129,7 +129,7 @@ export const withTestsForTestcase = ({ withAuditDatabaseRecordSchemaTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; @@ -137,7 +137,7 @@ export const withTestsForTestcase = ({ withEntraIdUserSchemaTests({ schema, options, - getTestObjectWithUpdatedField, + getTestObjectWithUpdatedParameter, }); break; From a5d8e78a3f9ed9e504730841d21b94183b02d0e7 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 4 Dec 2024 16:55:26 +0000 Subject: [PATCH 030/133] feat(dtfs2-6892): fix test --- .../with-unix-timestamp-milliseconds-schema.tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts index cd5b41923a..06e36dafb0 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts @@ -27,7 +27,7 @@ export const withUnixTimestampMillisecondsSchemaTests = { - const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(-1)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(1.2)); expect(success).toBe(false); }); }); From 010cb9af3fbabbb791d4fd1b0920556c5f0c0de3 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 29 Nov 2024 17:16:12 +0000 Subject: [PATCH 031/133] feat(dtfs2-6892): add missing type for get auth code --- libs/common/src/types/tfm/get-auth-code.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/common/src/types/tfm/get-auth-code.ts b/libs/common/src/types/tfm/get-auth-code.ts index 091458a281..ea03fc59d8 100644 --- a/libs/common/src/types/tfm/get-auth-code.ts +++ b/libs/common/src/types/tfm/get-auth-code.ts @@ -6,6 +6,8 @@ export type GetAuthCodeUrlParams = { successRedirect: string; }; +export type GetAuthCodeUrlRequest = GetAuthCodeUrlParams; + export type GetAuthCodeUrlResponse = { authCodeUrl: string; authCodeUrlRequest: AuthorizationUrlRequest; From 7b4def094bf7f27f332d14f45bd443ddbeacae2d Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 29 Nov 2024 17:30:44 +0000 Subject: [PATCH 032/133] feat(dtfs2-6892): add handle sso redirect form types --- .../src/types/tfm/handle-sso-redirect-form.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 libs/common/src/types/tfm/handle-sso-redirect-form.ts diff --git a/libs/common/src/types/tfm/handle-sso-redirect-form.ts b/libs/common/src/types/tfm/handle-sso-redirect-form.ts new file mode 100644 index 0000000000..85a43f9aaa --- /dev/null +++ b/libs/common/src/types/tfm/handle-sso-redirect-form.ts @@ -0,0 +1,14 @@ +import { AuthorizationUrlRequest } from '@azure/msal-node'; +import { EntraIdAuthCodeRedirectResponseBody } from './entra-id'; +import { TfmSessionUser } from './tfm-session-user'; + +export type handleSsoRedirectFormRequest = { + authCodeResponse: EntraIdAuthCodeRedirectResponseBody; + originalAuthCodeUrlRequest: AuthorizationUrlRequest; +}; + +export type handleSsoRedirectFormResponse = { + user: TfmSessionUser; + userToken: string; + successRedirect: string; +}; From 749e267e09ff6808f5eed303509a08155f427f1c Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 29 Nov 2024 17:33:10 +0000 Subject: [PATCH 033/133] feat(dtfs2-6892): add handle sso redirect form types --- libs/common/src/types/tfm/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/common/src/types/tfm/index.ts b/libs/common/src/types/tfm/index.ts index 683cd022c1..0c183e8536 100644 --- a/libs/common/src/types/tfm/index.ts +++ b/libs/common/src/types/tfm/index.ts @@ -11,3 +11,4 @@ export * from './update-tfm-user-request'; export * from './upsert-tfm-user-request'; export * from './mapped-deal'; export * from './tfm-session-user'; +export * from './handle-sso-redirect-form'; From 1ea6af6caa0a4ee4854f43e798c382a06b32a3a8 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 29 Nov 2024 17:36:10 +0000 Subject: [PATCH 034/133] feat(dtfs2-6892): update user session to be split between fully authed and partially authed users --- .../server/types/express-session.d.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/trade-finance-manager-ui/server/types/express-session.d.ts b/trade-finance-manager-ui/server/types/express-session.d.ts index 17cb5aec3d..501d67e167 100644 --- a/trade-finance-manager-ui/server/types/express-session.d.ts +++ b/trade-finance-manager-ui/server/types/express-session.d.ts @@ -1,21 +1,25 @@ +import { AuthorizationUrlRequest } from '@azure/msal-node'; import { TfmSessionUser } from '@ukef/dtfs2-common'; import { RemoveFeesFromPaymentErrorKey } from '../controllers/utilisation-reports/helpers'; import { EditPaymentFormValues } from './edit-payment-form-values'; import { AddPaymentErrorKey, InitiateRecordCorrectionRequestErrorKey, GenerateKeyingDataErrorKey } from './premium-payments-tab-error-keys'; -type UserSessionLoginData = { +export type UserSessionLoginData = { authCodeUrlRequest: AuthorizationUrlRequest; }; +export type PartiallyLoggedInUserSessionData = { + loginData: UserSessionLoginData; +}; + export type UserSessionData = { user: TfmSessionUser; userToken: string; - loginData?: UserSessionLoginData; }; // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express-session/index.d.ts#L199-L211 declare module 'express-session' { - interface SessionData extends UserSessionData { + interface SessionData extends UserSessionData, PartiallyLoggedInUserSessionData { addPaymentErrorKey: AddPaymentErrorKey; initiateRecordCorrectionRequestErrorKey: InitiateRecordCorrectionRequestErrorKey; checkedCheckboxIds: Record; From cdb3e5f6a25041b47094a7004c2e8381dc3ee267 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 29 Nov 2024 17:44:59 +0000 Subject: [PATCH 035/133] feat(dtfs2-6892): update tfm ui --- trade-finance-manager-ui/server/api.js | 24 +++++++++++++ .../login/login-sso/login.controller.ts | 26 ++++++++++++++ .../server/helpers/express-session.ts | 20 +++++++++-- .../server/routes/auth/configs/auth-sso.ts | 15 ++++++++ .../configs/index.get-auth-router.test.ts | 35 +++++++++++++++++++ .../server/routes/auth/configs/index.ts | 3 ++ .../server/routes/auth/index.ts | 3 +- .../server/services/login.service.ts | 31 +++++++++++++++- 8 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts create mode 100644 trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index 8afa11b05e..94a14f9597 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -421,6 +421,29 @@ const login = async (username, password) => { } }; +/** + * + * @param {import('@ukef/dtfs2-common').handleSsoRedirectFormRequest} handleSsoRedirectFormRequest + * @returns {Promise} + */ +const handleSsoRedirectForm = async (handleSsoRedirectFormRequest) => { + try { + const response = await axios({ + method: 'post', + url: `${TFM_API_URL}/v1/sso/handle-sso-redirect-form`, + headers: { + [HEADERS.CONTENT_TYPE.KEY]: HEADERS.CONTENT_TYPE.VALUES.JSON, + }, + data: handleSsoRedirectFormRequest, + }); + + return response.data; + } catch (error) { + console.error('Unable to log in %o', error?.response?.data); + return { status: error?.response?.status || 500, data: 'Failed to login' }; + } +}; + /** * Gets the auth code URL for the SSO login process * @param {import('@ukef/dtfs2-common').GetAuthCodeUrlRequest} getAuthCodeUrlParams @@ -1377,6 +1400,7 @@ module.exports = { updateLeadUnderwriter, createActivity, login, + handleSsoRedirectForm, getAuthCodeUrl, getFacilities, createFeedback, diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts index 6a7c0c5897..6b1a86481f 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts @@ -1,4 +1,8 @@ import { NextFunction, Request, Response } from 'express'; +import { CustomExpressRequest, EntraIdAuthCodeRedirectResponseBody, InvalidPayloadError } from '@ukef/dtfs2-common'; +import { isVerifiedPayload } from '@ukef/dtfs2-common/payload-verification'; +import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from '@ukef/dtfs2-common/schemas'; +import { asPartiallyLoggedInUserSession } from '../../../helpers/express-session'; import { LoginService } from '../../../services/login.service'; export class LoginController { @@ -10,6 +14,7 @@ export class LoginController { public async getLogin(req: Request, res: Response, next: NextFunction) { try { + // TODO: This validation is legacy code, and can be improved if (req.session.user) { // User is already logged in. return res.redirect('/home'); @@ -27,6 +32,27 @@ export class LoginController { } } + async handleSsoRedirectForm(req: CustomExpressRequest<{ reqBody: EntraIdAuthCodeRedirectResponseBody }>, res: Response, next: NextFunction) { + try { + const { body, session: partiallyLoggedInSession } = req; + const session = asPartiallyLoggedInUserSession(partiallyLoggedInSession); + + if (!isVerifiedPayload({ payload: body, template: ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA })) { + throw new InvalidPayloadError('Invalid payload from SSO redirect'); + } + + const { successRedirect } = await this.loginService.handleSsoRedirectFormAndCreateToken({ + authCodeResponse: body, + originalAuthCodeUrlRequest: session.loginData.authCodeUrlRequest, + session, + }); + + return res.redirect(successRedirect ?? '/'); + } catch (error) { + return next(error); + } + } + // TODO DTFS2-6892: Update this logout handling public getLogout = (req: Request, res: Response) => { req.session.destroy(() => { diff --git a/trade-finance-manager-ui/server/helpers/express-session.ts b/trade-finance-manager-ui/server/helpers/express-session.ts index d554771c39..39264e9715 100644 --- a/trade-finance-manager-ui/server/helpers/express-session.ts +++ b/trade-finance-manager-ui/server/helpers/express-session.ts @@ -1,11 +1,12 @@ import { Request } from 'express'; -import { UserSessionNotDefinedError, UserTokenNotDefinedError } from '@ukef/dtfs2-common'; -import { UserSessionData } from '../types/express-session'; +import { UserPartialLoginDataNotDefinedError, UserSessionNotDefinedError, UserTokenNotDefinedError } from '@ukef/dtfs2-common'; +import { PartiallyLoggedInUserSessionData, UserSessionData } from '../types/express-session'; type Session = Request['session']; -type UserSession = Session & UserSessionData; +export type UserSession = Session & UserSessionData; +type PartiallyLoggedInUserSession = Session & PartiallyLoggedInUserSessionData; /** * By default, all session data will be optional * (see use of `Partial` {@link https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express-session/index.d.ts#L17 here}) @@ -30,3 +31,16 @@ export const asUserSession = (session: Session): UserSession => { return Object.assign(session, { user, userToken }); }; + +export const assertPartiallyLoggedInUser: (session: Session) => asserts session is PartiallyLoggedInUserSession = (session: Session) => { + const { loginData } = session; + + if (!loginData) { + throw new UserPartialLoginDataNotDefinedError(); + } +}; + +export const asPartiallyLoggedInUserSession = (session: Session): PartiallyLoggedInUserSession => { + assertPartiallyLoggedInUser(session); + return session; +}; diff --git a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts new file mode 100644 index 0000000000..f153705f39 --- /dev/null +++ b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts @@ -0,0 +1,15 @@ +import express from 'express'; +import { GetRouter } from '../../../types/get-router'; +import { LoginController } from '../../../controllers/login/login-sso/login.controller'; +import { LoginService } from '../../../services/login.service'; + +export const getAuthSsoRouter: GetRouter = () => { + const loginService = new LoginService(); + const loginController = new LoginController({ loginService }); + const authSsoRouter = express.Router(); + + // Todo: update this to check the right token + // eslint-disable-next-line @typescript-eslint/no-misused-promises + authSsoRouter.post('/auth/sso-redirect/form', loginController.handleSsoRedirectForm.bind(loginController)); + return authSsoRouter; +}; diff --git a/trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts b/trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts new file mode 100644 index 0000000000..dc34246597 --- /dev/null +++ b/trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts @@ -0,0 +1,35 @@ +import { isTfmSsoFeatureFlagEnabled } from '@ukef/dtfs2-common'; +import { Router } from 'express'; +import { getAuthRouter } from '.'; +import { getAuthSsoRouter } from './auth-sso'; + +jest.mock('@ukef/dtfs2-common', () => ({ + isTfmSsoFeatureFlagEnabled: jest.fn(), +})); + +jest.mock('./authSso', () => ({ + getAuthSsoRouter: jest.fn(), +})); + +describe('auth router config', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('getAuthRouter', () => { + it('should return authSsoRouter if isTfmSsoFeatureFlagEnabled is true', () => { + jest.mocked(isTfmSsoFeatureFlagEnabled).mockReturnValue(true); + jest.mocked(getAuthSsoRouter).mockReturnValue('authSsoRouter' as unknown as Router); + + expect(getAuthRouter()).toBe('authSsoRouter'); + expect(getAuthSsoRouter).toHaveBeenCalledTimes(1); + }); + + it('should return undefined if isTfmSsoFeatureFlagEnabled is false', () => { + jest.mocked(isTfmSsoFeatureFlagEnabled).mockReturnValue(false); + + expect(getAuthRouter()).toBe(undefined); + expect(getAuthSsoRouter).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/trade-finance-manager-ui/server/routes/auth/configs/index.ts b/trade-finance-manager-ui/server/routes/auth/configs/index.ts index 2595c42b06..bf3802fe64 100644 --- a/trade-finance-manager-ui/server/routes/auth/configs/index.ts +++ b/trade-finance-manager-ui/server/routes/auth/configs/index.ts @@ -1,4 +1,7 @@ import { isTfmSsoFeatureFlagEnabled } from '@ukef/dtfs2-common'; +import { getAuthSsoRouter } from './auth-sso'; import { getUnauthenticatedAuthSsoRouter } from './unauthenticated-auth-sso'; +export const getAuthRouter = () => (isTfmSsoFeatureFlagEnabled() ? getAuthSsoRouter() : undefined); + export const getUnauthenticatedAuthRouter = () => (isTfmSsoFeatureFlagEnabled() ? getUnauthenticatedAuthSsoRouter() : undefined); diff --git a/trade-finance-manager-ui/server/routes/auth/index.ts b/trade-finance-manager-ui/server/routes/auth/index.ts index c52e3ef8bd..6d31c2f430 100644 --- a/trade-finance-manager-ui/server/routes/auth/index.ts +++ b/trade-finance-manager-ui/server/routes/auth/index.ts @@ -1,3 +1,4 @@ -import { getUnauthenticatedAuthRouter } from './configs'; +import { getAuthRouter, getUnauthenticatedAuthRouter } from './configs'; +export const authRoutes = getAuthRouter(); export const unauthenticatedAuthRoutes = getUnauthenticatedAuthRouter(); diff --git a/trade-finance-manager-ui/server/services/login.service.ts b/trade-finance-manager-ui/server/services/login.service.ts index dc3ef1f71c..48fa7fe454 100644 --- a/trade-finance-manager-ui/server/services/login.service.ts +++ b/trade-finance-manager-ui/server/services/login.service.ts @@ -1,6 +1,15 @@ -import { GetAuthCodeUrlParams, GetAuthCodeUrlResponse } from '@ukef/dtfs2-common'; +import { GetAuthCodeUrlParams, GetAuthCodeUrlResponse, handleSsoRedirectFormRequest } from '@ukef/dtfs2-common'; +import { PartiallyLoggedInUserSessionData } from '../types/express-session'; import * as api from '../api'; +type handleSsoRedirectFormAndCreateTokenRequest = { + session: PartiallyLoggedInUserSessionData; +} & handleSsoRedirectFormRequest; + +type handleSsoRedirectFormAndCreateTokenResponse = { + successRedirect: string; +}; + export class LoginService { /** * Gets the URL to redirect the user to in order to log in. @@ -8,4 +17,24 @@ export class LoginService { public getAuthCodeUrl = async ({ successRedirect }: GetAuthCodeUrlParams): Promise => { return api.getAuthCodeUrl({ successRedirect }); }; + + public handleSsoRedirectFormAndCreateToken = async ({ + authCodeResponse, + originalAuthCodeUrlRequest, + // TODO as part of this ticket: uncomment the session parameter + // eslint-disable-next-line @typescript-eslint/no-unused-vars + session, + }: handleSsoRedirectFormAndCreateTokenRequest): Promise => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { user, userToken, successRedirect } = await api.handleSsoRedirectForm({ authCodeResponse, originalAuthCodeUrlRequest }); + + // TODO: This can be moved into a separate session service + // delete session.loginData; + // session.user = user; + // session.userToken = userToken; + + return { successRedirect }; + }; + + public getSuccessRedirect = () => {}; } From e48b42d034fb7aa6e438effc3d055132f9e2cea5 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 3 Dec 2024 15:38:50 +0000 Subject: [PATCH 036/133] feat(dtfs2-6892): add lost tfm api file changes --- .../src/types/tfm/handle-sso-redirect-form.ts | 14 +++- .../src/v1/controllers/sso.controller.ts | 58 ++++++++++++++- .../src/v1/controllers/user/user.routes.js | 36 +-------- .../src/v1/repo/user.repo.ts | 1 + trade-finance-manager-api/src/v1/routes.js | 8 +- .../src/v1/services/entra-id.service.ts | 74 ++++++++++++++++++- .../src/v1/services/user.service.ts | 25 ++++++- .../src/v1/sso/routes.ts | 10 ++- 8 files changed, 175 insertions(+), 51 deletions(-) diff --git a/libs/common/src/types/tfm/handle-sso-redirect-form.ts b/libs/common/src/types/tfm/handle-sso-redirect-form.ts index 85a43f9aaa..f53eb8d95e 100644 --- a/libs/common/src/types/tfm/handle-sso-redirect-form.ts +++ b/libs/common/src/types/tfm/handle-sso-redirect-form.ts @@ -1,14 +1,24 @@ import { AuthorizationUrlRequest } from '@azure/msal-node'; +import { Response } from 'express'; import { EntraIdAuthCodeRedirectResponseBody } from './entra-id'; import { TfmSessionUser } from './tfm-session-user'; +import { AuditDetails } from '../audit-details'; +import { CustomExpressRequest } from '../express-custom-request'; -export type handleSsoRedirectFormRequest = { +export type HandleSsoRedirectFormRequest = { authCodeResponse: EntraIdAuthCodeRedirectResponseBody; originalAuthCodeUrlRequest: AuthorizationUrlRequest; + auditDetails: AuditDetails<'system'>; }; -export type handleSsoRedirectFormResponse = { +export type HandleSsoRedirectFormResponse = { user: TfmSessionUser; userToken: string; successRedirect: string; }; + +export type HandleSsoRedirectFormUiRequest = CustomExpressRequest<{ reqBody: EntraIdAuthCodeRedirectResponseBody }>; + +export type HandleSsoRedirectFormApiRequest = CustomExpressRequest<{ reqBody: HandleSsoRedirectFormRequest }>; + +export type HandleSsoRedirectFormApiResponse = Response; diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts index c84619febf..55a9b7c605 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts @@ -1,12 +1,25 @@ import { NextFunction } from 'express'; -import { GetAuthCodeUrlApiRequest, GetAuthCodeUrlApiResponse } from '@ukef/dtfs2-common'; +import { + GetAuthCodeUrlApiRequest, + GetAuthCodeUrlApiResponse, + HandleSsoRedirectFormApiRequest, + HandleSsoRedirectFormApiResponse, + InvalidPayloadError, +} from '@ukef/dtfs2-common'; +import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from '@ukef/dtfs2-common/schemas'; +import { isVerifiedPayload } from '@ukef/dtfs2-common/payload-verification'; +import { validateAuditDetailsAndUserType } from '@ukef/dtfs2-common/change-stream'; import { EntraIdService } from '../services/entra-id.service'; +import { UserService } from '../services/user.service'; +import utils from '../../utils/crypto.util'; export class SsoController { private readonly entraIdService: EntraIdService; + private readonly userService: UserService; - constructor({ entraIdService }: { entraIdService: EntraIdService }) { + constructor({ entraIdService, userService }: { entraIdService: EntraIdService; userService: UserService }) { this.entraIdService = entraIdService; + this.userService = userService; } async getAuthCodeUrl(req: GetAuthCodeUrlApiRequest, res: GetAuthCodeUrlApiResponse, next: NextFunction) { @@ -18,4 +31,45 @@ export class SsoController { return next(error); } } + + /** + * Used as part of the SSO process + * + * This endpoint handles the TFM-API side of the SSO process following the automatic redirect from the Entra Id service. + * It takes the response from the Entra Id service and processes it to create or update a user in the TFM-API database. + * It then issues a JWT token for the user and returns it to the client. + */ + async handleSsoRedirectForm(req: HandleSsoRedirectFormApiRequest, res: HandleSsoRedirectFormApiResponse, next: NextFunction) { + try { + const { body } = req; + const { authCodeResponse, originalAuthCodeUrlRequest, auditDetails } = body; + validateAuditDetailsAndUserType(auditDetails, 'system'); + + if (!isVerifiedPayload({ payload: body, template: ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA })) { + throw new InvalidPayloadError('Invalid payload from SSO redirect'); + } + + const { entraIdUser, successRedirect } = await this.entraIdService.handleRedirect({ + authCodeResponse, + originalAuthCodeUrlRequest, + }); + + const user = await this.userService.upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }); + + const { sessionIdentifier, token, expires } = utils.issueJWT(user); + + await this.userService.saveUserLoginInformation({ userId: user._id, sessionIdentifier, auditDetails }); + + const response = { + user, + successRedirect, + token, + expires, + }; + + return response; + } catch (error) { + return next(error); + } + } } diff --git a/trade-finance-manager-api/src/v1/controllers/user/user.routes.js b/trade-finance-manager-api/src/v1/controllers/user/user.routes.js index 3b0ebb03c8..d42132f573 100644 --- a/trade-finance-manager-api/src/v1/controllers/user/user.routes.js +++ b/trade-finance-manager-api/src/v1/controllers/user/user.routes.js @@ -1,12 +1,8 @@ -const { HttpStatusCode } = require('axios'); -const { ZodError } = require('zod'); -const { ApiError } = require('@ukef/dtfs2-common'); -const { ENTRA_ID_USER_SCHEMA } = require('@ukef/dtfs2-common/schemas'); const { ObjectId } = require('mongodb'); -const { generateTfmAuditDetails, generateNoUserLoggedInAuditDetails, generateSystemAuditDetails } = require('@ukef/dtfs2-common/change-stream'); +const { generateTfmAuditDetails, generateNoUserLoggedInAuditDetails } = require('@ukef/dtfs2-common/change-stream'); const utils = require('../../../utils/crypto.util'); const { userIsDisabled, usernameOrPasswordIncorrect, userIsBlocked } = require('../../../constants/login-results.constant'); -const { create, update, removeTfmUserById, findOne, findByUsername, upsertTfmUserFromEntraIdUser } = require('./user.controller'); +const { create, update, removeTfmUserById, findOne, findByUsername } = require('./user.controller'); const { mapUserData } = require('./helpers/mapUserData.helper'); const { loginCallback } = require('./helpers/loginCallback.helper'); @@ -65,34 +61,6 @@ module.exports.createTfmUser = (req, res, next) => { }); }; -module.exports.upsertTfmUserFromEntraIdUser = async (req, res, next) => { - try { - const entraIdUser = ENTRA_ID_USER_SCHEMA.parse(req.body); - const tfmUser = await upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails: generateSystemAuditDetails() }); - - return res.status(HttpStatusCode.Ok).send(tfmUser); - } catch (error) { - console.error('Error calling upsertTfmUserFromEntraIdUser %o', error); - - if (error instanceof ApiError) { - return res.status(error.status).send({ - status: error.status, - message: error.message, - code: error.code, - }); - } - - if (error instanceof ZodError) { - return res.status(HttpStatusCode.BadRequest).send({ - status: HttpStatusCode.BadRequest, - message: 'Error validating payload', - }); - } - - return next(error); - } -}; - module.exports.findTfmUser = (req, res, next) => { if (ObjectId.isValid(req.params.user)) { findOne(req.params.user, (error, user) => { diff --git a/trade-finance-manager-api/src/v1/repo/user.repo.ts b/trade-finance-manager-api/src/v1/repo/user.repo.ts index 1db99ed00a..22fd7451d2 100644 --- a/trade-finance-manager-api/src/v1/repo/user.repo.ts +++ b/trade-finance-manager-api/src/v1/repo/user.repo.ts @@ -19,6 +19,7 @@ type CreateUserParams = { }; type UpdateUserByIdParams = { userId: ObjectId; userUpdate: UpdateTfmUserRequest; auditDetails: AuditDetails }; + export class UserRepo { /** * Gets the tfm users collection diff --git a/trade-finance-manager-api/src/v1/routes.js b/trade-finance-manager-api/src/v1/routes.js index 170d85b684..67f34ffc44 100644 --- a/trade-finance-manager-api/src/v1/routes.js +++ b/trade-finance-manager-api/src/v1/routes.js @@ -8,7 +8,6 @@ const passport = require('passport'); const { swaggerSpec, swaggerUiOptions } = require('./swagger'); const { validateSsoFeatureFlagIsOff, validateSsoFeatureFlagIsOn } = require('./middleware/validate-sso-feature-flag'); -const { validateTfmPutUserPayload } = require('./middleware/validate-put-tfm-user-payload'); const feedbackController = require('./controllers/feedback-controller'); const amendmentController = require('./controllers/amendment.controller'); const facilityController = require('./controllers/facility.controller'); @@ -28,7 +27,7 @@ const { ssoOpenRouter } = require('./sso/routes'); openRouter.use(checkApiKey); -openRouter.use('/sso', ssoOpenRouter); +openRouter.use('/sso', validateSsoFeatureFlagIsOn, ssoOpenRouter); authRouter.use(passport.authenticate('jwt', { session: false })); @@ -75,10 +74,7 @@ authRouter.use('/', tasksRouter); openRouter.route('/feedback').post(feedbackController.create); openRouter.route('/user').post(validateSsoFeatureFlagIsOff, users.createTfmUser); -authRouter - .route('/users') - .post(validateSsoFeatureFlagIsOff, users.createTfmUser) - .put(validateSsoFeatureFlagIsOn, validateTfmPutUserPayload, users.upsertTfmUserFromEntraIdUser); +authRouter.route('/users').post(validateSsoFeatureFlagIsOff, users.createTfmUser); authRouter .route('/users/:user') diff --git a/trade-finance-manager-api/src/v1/services/entra-id.service.ts b/trade-finance-manager-api/src/v1/services/entra-id.service.ts index 07912ee6a4..1f4e3aec42 100644 --- a/trade-finance-manager-api/src/v1/services/entra-id.service.ts +++ b/trade-finance-manager-api/src/v1/services/entra-id.service.ts @@ -1,5 +1,6 @@ import { AuthorizationUrlRequest, ConfidentialClientApplication, Configuration as MsalAppConfig, CryptoProvider } from '@azure/msal-node'; -import { DecodedAuthCodeRequestState } from '@ukef/dtfs2-common'; +import { EntraIdUser, DecodedAuthCodeRequestState, EntraIdAuthCodeRedirectResponseBody } from '@ukef/dtfs2-common'; +import { DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA, ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA } from '@ukef/dtfs2-common/schemas'; import { EntraIdConfig } from '../configs/entra-id.config'; import { EntraIdApi } from '../third-party-apis/entra-id.api'; @@ -12,6 +13,25 @@ type GetAuthCodeUrlResponse = { authCodeUrlRequest: AuthorizationUrlRequest; }; +type HandleRedirectParams = { + authCodeResponse: EntraIdAuthCodeRedirectResponseBody; + originalAuthCodeUrlRequest?: AuthorizationUrlRequest; +}; + +type HandleRedirectResponse = { + entraIdUser: EntraIdUser; + successRedirect?: string; +}; + +type GetAccessTokenAndEntraIdUserByAuthCodeParams = { + authCodeResponse: EntraIdAuthCodeRedirectResponseBody; + originalAuthCodeUrlRequest: AuthorizationUrlRequest; +}; + +type GetEntraIdUserByAuthCodeResponse = { + entraIdUser: EntraIdUser; +}; + export class EntraIdService { private readonly msalAppConfig: MsalAppConfig; @@ -48,6 +68,34 @@ export class EntraIdService { return { authCodeUrl, authCodeUrlRequest }; } + public async handleRedirect({ authCodeResponse, originalAuthCodeUrlRequest }: HandleRedirectParams): Promise { + if (!originalAuthCodeUrlRequest) { + throw new Error('No auth code URL request found in session'); + } + + // TODO -- This validates the user as well, lets consider renaming this + const { entraIdUser } = await this.getEntraIdUserByAuthCode({ + authCodeResponse, + originalAuthCodeUrlRequest, + }); + + const { successRedirect } = this.parseAuthRequestState(authCodeResponse.state); + + return { + entraIdUser, + successRedirect, + }; + } + + private parseAuthRequestState(encodedState: string): DecodedAuthCodeRequestState { + try { + return DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA.parse(JSON.parse(this.cryptoProvider.base64Decode(encodedState))); + } catch (error) { + console.error('Error parsing auth request state: %o', error); + throw error; + } + } + private async getAuthorityMetadata() { try { return await this.entraIdApi.getAuthorityMetadataUrl(); @@ -67,4 +115,28 @@ export class EntraIdService { return new ConfidentialClientApplication(this.msalAppConfig); } + + private async getEntraIdUserByAuthCode({ + authCodeResponse, + originalAuthCodeUrlRequest, + }: GetAccessTokenAndEntraIdUserByAuthCodeParams): Promise { + const msalApp = await this.getMsalAppInstance(); + + // The token request uses details from our original auth code request so + // that MSAL can validate that the state in our original request matches the + // state in the auth code response received via the redirect. This ensures + // that the originator of the request and the response received are the + // same, which is important for security reasons to protect against CSRF + // attacks. + // See https://datatracker.ietf.org/doc/html/rfc6819#section-3.6 for details + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { responseMode, ...rest } = originalAuthCodeUrlRequest; + const tokenRequest = { ...rest, code: authCodeResponse.code }; + + const { + account: { idTokenClaims }, + } = ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA.parse(await msalApp.acquireTokenByCode(tokenRequest, authCodeResponse)); + + return { entraIdUser: idTokenClaims }; + } } diff --git a/trade-finance-manager-api/src/v1/services/user.service.ts b/trade-finance-manager-api/src/v1/services/user.service.ts index 5f82496845..30275e584a 100644 --- a/trade-finance-manager-api/src/v1/services/user.service.ts +++ b/trade-finance-manager-api/src/v1/services/user.service.ts @@ -1,5 +1,6 @@ import { AuditDetails, EntraIdUser, MultipleUsersFoundError, TfmUser, UpsertTfmUserRequest } from '@ukef/dtfs2-common'; import { ENTRA_ID_USER_TO_UPSERT_TFM_USER_REQUEST_SCHEMA } from '@ukef/dtfs2-common/schemas'; +import { ObjectId } from 'mongodb'; import { UserRepo } from '../repo/user.repo'; type UpsertTfmUserFromEntraIdUserParams = { @@ -40,10 +41,7 @@ export class UserService { * @returns The upserted user * @throws MultipleUsersFoundError if multiple users are found */ - public static async upsertTfmUserFromEntraIdUser({ - entraIdUser, - auditDetails, - }: UpsertTfmUserFromEntraIdUserParams): Promise { + public async upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }: UpsertTfmUserFromEntraIdUserParams): Promise { const upsertTfmUserRequest = UserService.transformEntraIdUserToUpsertTfmUserRequest(entraIdUser); const findResult = await UserRepo.findUsersByEmailAddresses([...entraIdUser.verified_primary_email, ...entraIdUser.verified_secondary_email]); @@ -60,4 +58,23 @@ export class UserService { } return upsertedUser; } + + public async saveUserLoginInformation({ + userId, + sessionIdentifier, + auditDetails, + }: { + userId: ObjectId; + sessionIdentifier: string; + auditDetails: AuditDetails; + }) { + await UserRepo.updateUserById({ + userId, + userUpdate: { + lastLogin: Date.now(), + sessionIdentifier, + }, + auditDetails, + }); + } } diff --git a/trade-finance-manager-api/src/v1/sso/routes.ts b/trade-finance-manager-api/src/v1/sso/routes.ts index fc8d32bf20..36908d8f13 100644 --- a/trade-finance-manager-api/src/v1/sso/routes.ts +++ b/trade-finance-manager-api/src/v1/sso/routes.ts @@ -1,15 +1,21 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ import express from 'express'; import { SsoController } from '../controllers/sso.controller'; import { EntraIdService } from '../services/entra-id.service'; import { EntraIdApi } from '../third-party-apis/entra-id.api'; import { EntraIdConfig } from '../configs/entra-id.config'; +import { UserService } from '../services/user.service'; export const ssoOpenRouter = express.Router(); +// TODO -- Update this to have passport control +export const ssoPartialLoginRouter = express.Router(); + const entraIdConfig = new EntraIdConfig(); const entraIdApi = new EntraIdApi({ entraIdConfig }); const entraIdService = new EntraIdService({ entraIdConfig, entraIdApi }); -const ssoController = new SsoController({ entraIdService }); +const userService = new UserService(); +const ssoController = new SsoController({ entraIdService, userService }); -// eslint-disable-next-line @typescript-eslint/no-misused-promises ssoOpenRouter.route('/auth-code-url').get(ssoController.getAuthCodeUrl.bind(ssoController)); +ssoOpenRouter.route('/handle-sso-redirect-form').post(ssoController.handleSsoRedirectForm.bind(ssoController)); From cf47e6608866403c0aa20e5f9ab0819f47754c10 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 3 Dec 2024 16:01:54 +0000 Subject: [PATCH 037/133] feat(dtfs2-6892): add lost files --- .../src/change-stream/generate-audit-details.ts | 4 ++-- .../src/types/tfm/handle-sso-redirect-form.ts | 1 + .../src/v1/controllers/sso.controller.ts | 3 ++- trade-finance-manager-ui/server/api.js | 4 ++-- .../controllers/login/login-sso/login.controller.ts | 7 +++++-- .../server/services/login.service.ts | 13 +++++++------ 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/libs/common/src/change-stream/generate-audit-details.ts b/libs/common/src/change-stream/generate-audit-details.ts index 8ab5f768d7..09da1fbe3b 100644 --- a/libs/common/src/change-stream/generate-audit-details.ts +++ b/libs/common/src/change-stream/generate-audit-details.ts @@ -1,7 +1,7 @@ import { ObjectId } from 'mongodb'; import { AuditDetails, PortalAuditDetails, TfmAuditDetails } from '../types/audit-details'; -export const generateSystemAuditDetails = (): AuditDetails => ({ +export const generateSystemAuditDetails = (): AuditDetails<'system'> => ({ userType: 'system', }); @@ -15,6 +15,6 @@ export const generateTfmAuditDetails = (id: string | ObjectId): TfmAuditDetails id, }); -export const generateNoUserLoggedInAuditDetails = (): AuditDetails => ({ +export const generateNoUserLoggedInAuditDetails = (): AuditDetails<'none'> => ({ userType: 'none', }); diff --git a/libs/common/src/types/tfm/handle-sso-redirect-form.ts b/libs/common/src/types/tfm/handle-sso-redirect-form.ts index f53eb8d95e..f761644e6c 100644 --- a/libs/common/src/types/tfm/handle-sso-redirect-form.ts +++ b/libs/common/src/types/tfm/handle-sso-redirect-form.ts @@ -14,6 +14,7 @@ export type HandleSsoRedirectFormRequest = { export type HandleSsoRedirectFormResponse = { user: TfmSessionUser; userToken: string; + expires: string; successRedirect: string; }; diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts index 55a9b7c605..501d6d1082 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts @@ -4,6 +4,7 @@ import { GetAuthCodeUrlApiResponse, HandleSsoRedirectFormApiRequest, HandleSsoRedirectFormApiResponse, + HandleSsoRedirectFormResponse, InvalidPayloadError, } from '@ukef/dtfs2-common'; import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from '@ukef/dtfs2-common/schemas'; @@ -60,7 +61,7 @@ export class SsoController { await this.userService.saveUserLoginInformation({ userId: user._id, sessionIdentifier, auditDetails }); - const response = { + const response: HandleSsoRedirectFormResponse = { user, successRedirect, token, diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index 94a14f9597..1be2661e8c 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -423,8 +423,8 @@ const login = async (username, password) => { /** * - * @param {import('@ukef/dtfs2-common').handleSsoRedirectFormRequest} handleSsoRedirectFormRequest - * @returns {Promise} + * @param {import('@ukef/dtfs2-common').HandleSsoRedirectFormRequest} handleSsoRedirectFormRequest + * @returns {Promise} */ const handleSsoRedirectForm = async (handleSsoRedirectFormRequest) => { try { diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts index 6b1a86481f..0a5973589b 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts @@ -1,7 +1,8 @@ import { NextFunction, Request, Response } from 'express'; -import { CustomExpressRequest, EntraIdAuthCodeRedirectResponseBody, InvalidPayloadError } from '@ukef/dtfs2-common'; +import { HandleSsoRedirectFormUiRequest, InvalidPayloadError } from '@ukef/dtfs2-common'; import { isVerifiedPayload } from '@ukef/dtfs2-common/payload-verification'; import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from '@ukef/dtfs2-common/schemas'; +import { generateSystemAuditDetails } from '@ukef/dtfs2-common/change-stream'; import { asPartiallyLoggedInUserSession } from '../../../helpers/express-session'; import { LoginService } from '../../../services/login.service'; @@ -32,10 +33,11 @@ export class LoginController { } } - async handleSsoRedirectForm(req: CustomExpressRequest<{ reqBody: EntraIdAuthCodeRedirectResponseBody }>, res: Response, next: NextFunction) { + async handleSsoRedirectForm(req: HandleSsoRedirectFormUiRequest, res: Response, next: NextFunction) { try { const { body, session: partiallyLoggedInSession } = req; const session = asPartiallyLoggedInUserSession(partiallyLoggedInSession); + const auditDetails = generateSystemAuditDetails(); if (!isVerifiedPayload({ payload: body, template: ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA })) { throw new InvalidPayloadError('Invalid payload from SSO redirect'); @@ -45,6 +47,7 @@ export class LoginController { authCodeResponse: body, originalAuthCodeUrlRequest: session.loginData.authCodeUrlRequest, session, + auditDetails, }); return res.redirect(successRedirect ?? '/'); diff --git a/trade-finance-manager-ui/server/services/login.service.ts b/trade-finance-manager-ui/server/services/login.service.ts index 48fa7fe454..8369922e87 100644 --- a/trade-finance-manager-ui/server/services/login.service.ts +++ b/trade-finance-manager-ui/server/services/login.service.ts @@ -1,12 +1,12 @@ -import { GetAuthCodeUrlParams, GetAuthCodeUrlResponse, handleSsoRedirectFormRequest } from '@ukef/dtfs2-common'; +import { GetAuthCodeUrlParams, GetAuthCodeUrlResponse, HandleSsoRedirectFormRequest } from '@ukef/dtfs2-common'; import { PartiallyLoggedInUserSessionData } from '../types/express-session'; import * as api from '../api'; -type handleSsoRedirectFormAndCreateTokenRequest = { +type HandleSsoRedirectFormAndCreateTokenRequest = { session: PartiallyLoggedInUserSessionData; -} & handleSsoRedirectFormRequest; +} & HandleSsoRedirectFormRequest; -type handleSsoRedirectFormAndCreateTokenResponse = { +type HandleSsoRedirectFormAndCreateTokenResponse = { successRedirect: string; }; @@ -24,9 +24,10 @@ export class LoginService { // TODO as part of this ticket: uncomment the session parameter // eslint-disable-next-line @typescript-eslint/no-unused-vars session, - }: handleSsoRedirectFormAndCreateTokenRequest): Promise => { + auditDetails, + }: HandleSsoRedirectFormAndCreateTokenRequest): Promise => { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { user, userToken, successRedirect } = await api.handleSsoRedirectForm({ authCodeResponse, originalAuthCodeUrlRequest }); + const { user, userToken, successRedirect } = await api.handleSsoRedirectForm({ authCodeResponse, originalAuthCodeUrlRequest, auditDetails }); // TODO: This can be moved into a separate session service // delete session.loginData; From 6d2e06d1db35a92b884df6b33b7ea9f2a72ef59e Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 3 Dec 2024 16:19:07 +0000 Subject: [PATCH 038/133] feat(dtfs2-6892): success redirect to be optional --- libs/common/src/types/tfm/handle-sso-redirect-form.ts | 2 +- trade-finance-manager-ui/server/services/login.service.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/libs/common/src/types/tfm/handle-sso-redirect-form.ts b/libs/common/src/types/tfm/handle-sso-redirect-form.ts index f761644e6c..08ae3ab80d 100644 --- a/libs/common/src/types/tfm/handle-sso-redirect-form.ts +++ b/libs/common/src/types/tfm/handle-sso-redirect-form.ts @@ -15,7 +15,7 @@ export type HandleSsoRedirectFormResponse = { user: TfmSessionUser; userToken: string; expires: string; - successRedirect: string; + successRedirect?: string; }; export type HandleSsoRedirectFormUiRequest = CustomExpressRequest<{ reqBody: EntraIdAuthCodeRedirectResponseBody }>; diff --git a/trade-finance-manager-ui/server/services/login.service.ts b/trade-finance-manager-ui/server/services/login.service.ts index 8369922e87..75fd431ac6 100644 --- a/trade-finance-manager-ui/server/services/login.service.ts +++ b/trade-finance-manager-ui/server/services/login.service.ts @@ -6,9 +6,7 @@ type HandleSsoRedirectFormAndCreateTokenRequest = { session: PartiallyLoggedInUserSessionData; } & HandleSsoRedirectFormRequest; -type HandleSsoRedirectFormAndCreateTokenResponse = { - successRedirect: string; -}; +type HandleSsoRedirectFormAndCreateTokenResponse = { successRedirect?: string }; export class LoginService { /** From 4afebe86076ce22cd124d954bf281d18709e275a Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 4 Dec 2024 17:25:24 +0000 Subject: [PATCH 039/133] feat(dtfs2-6892): update sso controller --- libs/common/src/types/tfm/handle-sso-redirect-form.ts | 2 +- .../src/v1/controllers/sso.controller.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/common/src/types/tfm/handle-sso-redirect-form.ts b/libs/common/src/types/tfm/handle-sso-redirect-form.ts index 08ae3ab80d..71159de4a5 100644 --- a/libs/common/src/types/tfm/handle-sso-redirect-form.ts +++ b/libs/common/src/types/tfm/handle-sso-redirect-form.ts @@ -13,7 +13,7 @@ export type HandleSsoRedirectFormRequest = { export type HandleSsoRedirectFormResponse = { user: TfmSessionUser; - userToken: string; + token: string; expires: string; successRedirect?: string; }; diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts index 501d6d1082..901f7784b3 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts @@ -7,7 +7,7 @@ import { HandleSsoRedirectFormResponse, InvalidPayloadError, } from '@ukef/dtfs2-common'; -import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from '@ukef/dtfs2-common/schemas'; +import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA, TFM_SESSION_USER_SCHEMA } from '@ukef/dtfs2-common/schemas'; import { isVerifiedPayload } from '@ukef/dtfs2-common/payload-verification'; import { validateAuditDetailsAndUserType } from '@ukef/dtfs2-common/change-stream'; import { EntraIdService } from '../services/entra-id.service'; @@ -62,13 +62,13 @@ export class SsoController { await this.userService.saveUserLoginInformation({ userId: user._id, sessionIdentifier, auditDetails }); const response: HandleSsoRedirectFormResponse = { - user, - successRedirect, + user: TFM_SESSION_USER_SCHEMA.parse(user), token, expires, + successRedirect, }; - return response; + return res.send(response); } catch (error) { return next(error); } From 1225d7ecce1f5170a8877a88c5d1d2c61a46f4e0 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 5 Dec 2024 15:08:00 +0000 Subject: [PATCH 040/133] feat(dtfs2-6892): move api test file into separate tests --- .../server/api.get-deals.test.js | 75 +++++++++ .../server/api.get-facilities.test.js | 75 +++++++++ .../server/api.get-uk-bank-holidays.test.js | 34 ++++ trade-finance-manager-ui/server/api.test.js | 157 ------------------ 4 files changed, 184 insertions(+), 157 deletions(-) create mode 100644 trade-finance-manager-ui/server/api.get-deals.test.js create mode 100644 trade-finance-manager-ui/server/api.get-facilities.test.js create mode 100644 trade-finance-manager-ui/server/api.get-uk-bank-holidays.test.js delete mode 100644 trade-finance-manager-ui/server/api.test.js diff --git a/trade-finance-manager-ui/server/api.get-deals.test.js b/trade-finance-manager-ui/server/api.get-deals.test.js new file mode 100644 index 0000000000..b90c037533 --- /dev/null +++ b/trade-finance-manager-ui/server/api.get-deals.test.js @@ -0,0 +1,75 @@ +const axios = require('axios'); +const MockAdapter = require('axios-mock-adapter'); +const { HEADERS } = require('@ukef/dtfs2-common'); +const api = require('./api'); +const PageOutOfBoundsError = require('./errors/page-out-of-bounds.error'); + +const mockAxios = new MockAdapter(axios); + +console.error = jest.fn(); + +const { TFM_API_URL, TFM_API_KEY } = process.env; + +afterEach(() => { + jest.clearAllMocks(); + mockAxios.reset(); +}); + +describe('getDeals()', () => { + const dealsUrl = `${TFM_API_URL}/v1/deals`; + + const token = 'testToken'; + const headers = { + Authorization: token, + [HEADERS.CONTENT_TYPE.KEY]: HEADERS.CONTENT_TYPE.VALUES.JSON, + 'x-api-key': TFM_API_KEY, + }; + + const mockResponse = { + deals: [ + { _id: 1, name: 'Deal 1' }, + { _id: 2, name: 'Deal 2' }, + ], + pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, + }; + + it('should return deals data and pagination metadata when TFM API returns this data', async () => { + const queryParams = { page: 0 }; + + mockAxios + .onGet( + dealsUrl, + { params: queryParams }, + expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` + ) + .reply(200, mockResponse); + + const response = await api.getDeals(queryParams, token); + + expect(mockAxios.history.get.length).toEqual(1); + expect(response).toEqual({ + deals: [ + { _id: 1, name: 'Deal 1' }, + { _id: 2, name: 'Deal 2' }, + ], + pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, + }); + }); + + it('should throw a PageOutOfBoundsError when the requested page number exceeds the maximum page number', async () => { + const queryParams = { page: 1 }; + + mockAxios + .onGet( + dealsUrl, + { params: queryParams }, + expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` + ) + .reply(200, mockResponse); + + const errorResponse = api.getDeals(queryParams, token); + + expect(mockAxios.history.get.length).toEqual(1); + await expect(errorResponse).rejects.toThrow(new PageOutOfBoundsError('Requested page number exceeds the maximum page number')); + }); +}); diff --git a/trade-finance-manager-ui/server/api.get-facilities.test.js b/trade-finance-manager-ui/server/api.get-facilities.test.js new file mode 100644 index 0000000000..82d0894bf5 --- /dev/null +++ b/trade-finance-manager-ui/server/api.get-facilities.test.js @@ -0,0 +1,75 @@ +const axios = require('axios'); +const MockAdapter = require('axios-mock-adapter'); +const { HEADERS } = require('@ukef/dtfs2-common'); +const api = require('./api'); +const PageOutOfBoundsError = require('./errors/page-out-of-bounds.error'); + +const mockAxios = new MockAdapter(axios); + +console.error = jest.fn(); + +const { TFM_API_URL, TFM_API_KEY } = process.env; + +afterEach(() => { + jest.clearAllMocks(); + mockAxios.reset(); +}); + +describe('getFacilities()', () => { + const facilitiesUrl = `${TFM_API_URL}/v1/facilities`; + + const token = 'testToken'; + const headers = { + Authorization: token, + [HEADERS.CONTENT_TYPE.KEY]: HEADERS.CONTENT_TYPE.VALUES.JSON, + 'x-api-key': TFM_API_KEY, + }; + + const mockResponse = { + facilities: [ + { facilityId: 1, name: 'Facility 1' }, + { facilityId: 2, name: 'Facility 2' }, + ], + pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, + }; + + it('should return facilities data and pagination metadata when TFM API returns this data', async () => { + const queryParams = { page: 0 }; + + mockAxios + .onGet( + facilitiesUrl, + { params: queryParams }, + expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` + ) + .reply(200, mockResponse); + + const response = await api.getFacilities(queryParams, token); + + expect(mockAxios.history.get.length).toEqual(1); + expect(response).toEqual({ + facilities: [ + { facilityId: 1, name: 'Facility 1' }, + { facilityId: 2, name: 'Facility 2' }, + ], + pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, + }); + }); + + it('should throw a PageOutOfBoundsError when the requested page number exceeds the maximum page number', async () => { + const queryParams = { page: 1 }; + + mockAxios + .onGet( + facilitiesUrl, + { params: queryParams }, + expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` + ) + .reply(200, mockResponse); + + const errorResponse = api.getFacilities(queryParams, token); + + expect(mockAxios.history.get.length).toEqual(1); + await expect(errorResponse).rejects.toThrow(new PageOutOfBoundsError('Requested page number exceeds the maximum page number')); + }); +}); diff --git a/trade-finance-manager-ui/server/api.get-uk-bank-holidays.test.js b/trade-finance-manager-ui/server/api.get-uk-bank-holidays.test.js new file mode 100644 index 0000000000..444d6acfb5 --- /dev/null +++ b/trade-finance-manager-ui/server/api.get-uk-bank-holidays.test.js @@ -0,0 +1,34 @@ +const axios = require('axios'); +const MockAdapter = require('axios-mock-adapter'); +const { MOCK_BANK_HOLIDAYS } = require('./test-mocks/mock-bank-holidays'); +const { getUkBankHolidays } = require('./api'); + +const mockAxios = new MockAdapter(axios); + +console.error = jest.fn(); + +afterEach(() => { + jest.clearAllMocks(); + mockAxios.reset(); +}); + +describe('getUkBankHolidays', () => { + it('gets the bank holidays', async () => { + // Arrange + mockAxios.onGet().reply(200, MOCK_BANK_HOLIDAYS); + + // Act + const response = await getUkBankHolidays('user-token'); + + // Assert + expect(response).toEqual(MOCK_BANK_HOLIDAYS); + }); + + it('throws when the api TFM API request fails', async () => { + // Arrange + mockAxios.onGet().reply(404); + + // Act / Assert + await expect(getUkBankHolidays('user-token')).rejects.toThrowError('Request failed with status code 404'); + }); +}); diff --git a/trade-finance-manager-ui/server/api.test.js b/trade-finance-manager-ui/server/api.test.js deleted file mode 100644 index 2809d185c9..0000000000 --- a/trade-finance-manager-ui/server/api.test.js +++ /dev/null @@ -1,157 +0,0 @@ -const axios = require('axios'); -const MockAdapter = require('axios-mock-adapter'); -const { HEADERS } = require('@ukef/dtfs2-common'); -const api = require('./api'); -const { MOCK_BANK_HOLIDAYS } = require('./test-mocks/mock-bank-holidays'); -const { getUkBankHolidays } = require('./api'); -const PageOutOfBoundsError = require('./errors/page-out-of-bounds.error'); - -const mockAxios = new MockAdapter(axios); - -console.error = jest.fn(); - -const { TFM_API_URL, TFM_API_KEY } = process.env; - -afterEach(() => { - jest.clearAllMocks(); - mockAxios.reset(); -}); - -describe('getDeals()', () => { - const dealsUrl = `${TFM_API_URL}/v1/deals`; - - const token = 'testToken'; - const headers = { - Authorization: token, - [HEADERS.CONTENT_TYPE.KEY]: HEADERS.CONTENT_TYPE.VALUES.JSON, - 'x-api-key': TFM_API_KEY, - }; - - const mockResponse = { - deals: [ - { _id: 1, name: 'Deal 1' }, - { _id: 2, name: 'Deal 2' }, - ], - pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, - }; - - it('should return deals data and pagination metadata when TFM API returns this data', async () => { - const queryParams = { page: 0 }; - - mockAxios - .onGet( - dealsUrl, - { params: queryParams }, - expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` - ) - .reply(200, mockResponse); - - const response = await api.getDeals(queryParams, token); - - expect(mockAxios.history.get.length).toEqual(1); - expect(response).toEqual({ - deals: [ - { _id: 1, name: 'Deal 1' }, - { _id: 2, name: 'Deal 2' }, - ], - pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, - }); - }); - - it('should throw a PageOutOfBoundsError when the requested page number exceeds the maximum page number', async () => { - const queryParams = { page: 1 }; - - mockAxios - .onGet( - dealsUrl, - { params: queryParams }, - expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` - ) - .reply(200, mockResponse); - - const errorResponse = api.getDeals(queryParams, token); - - expect(mockAxios.history.get.length).toEqual(1); - await expect(errorResponse).rejects.toThrow(new PageOutOfBoundsError('Requested page number exceeds the maximum page number')); - }); -}); - -describe('getFacilities()', () => { - const facilitiesUrl = `${TFM_API_URL}/v1/facilities`; - - const token = 'testToken'; - const headers = { - Authorization: token, - [HEADERS.CONTENT_TYPE.KEY]: HEADERS.CONTENT_TYPE.VALUES.JSON, - 'x-api-key': TFM_API_KEY, - }; - - const mockResponse = { - facilities: [ - { facilityId: 1, name: 'Facility 1' }, - { facilityId: 2, name: 'Facility 2' }, - ], - pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, - }; - - it('should return facilities data and pagination metadata when TFM API returns this data', async () => { - const queryParams = { page: 0 }; - - mockAxios - .onGet( - facilitiesUrl, - { params: queryParams }, - expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` - ) - .reply(200, mockResponse); - - const response = await api.getFacilities(queryParams, token); - - expect(mockAxios.history.get.length).toEqual(1); - expect(response).toEqual({ - facilities: [ - { facilityId: 1, name: 'Facility 1' }, - { facilityId: 2, name: 'Facility 2' }, - ], - pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, - }); - }); - - it('should throw a PageOutOfBoundsError when the requested page number exceeds the maximum page number', async () => { - const queryParams = { page: 1 }; - - mockAxios - .onGet( - facilitiesUrl, - { params: queryParams }, - expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` - ) - .reply(200, mockResponse); - - const errorResponse = api.getFacilities(queryParams, token); - - expect(mockAxios.history.get.length).toEqual(1); - await expect(errorResponse).rejects.toThrow(new PageOutOfBoundsError('Requested page number exceeds the maximum page number')); - }); -}); - -describe('getUkBankHolidays', () => { - it('gets the bank holidays', async () => { - // Arrange - mockAxios.onGet().reply(200, MOCK_BANK_HOLIDAYS); - - // Act - const response = await getUkBankHolidays('user-token'); - - // Assert - expect(response).toEqual(MOCK_BANK_HOLIDAYS); - }); - - it('throws when the api TFM API request fails', async () => { - // Arrange - mockAxios.onGet().reply(404); - - // Act / Assert - await expect(getUkBankHolidays('user-token')).rejects.toThrowError('Request failed with status code 404'); - }); -}); From 3681433542a83c5a8bb2e8951b12984b8897bbb3 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 6 Dec 2024 11:49:39 +0000 Subject: [PATCH 041/133] feat(dtfs2-6892): update eslint to allow for _getJsonData --- azure-functions/acbs-function/.eslintrc.js | 14 +++++++++++++- dtfs-central-api/.eslintrc.js | 14 +++++++++++++- e2e-tests/.eslintrc.js | 14 +++++++++++++- external-api/.eslintrc.js | 14 +++++++++++++- gef-ui/.eslintrc.js | 14 +++++++++++++- libs/common/.eslintrc.js | 14 +++++++++++++- portal-api/.eslintrc.js | 14 +++++++++++++- portal/.eslintrc.js | 14 +++++++++++++- trade-finance-manager-api/.eslintrc.js | 14 +++++++++++++- trade-finance-manager-ui/.eslintrc.js | 14 +++++++++++++- utils/.eslintrc.js | 14 +++++++++++++- 11 files changed, 143 insertions(+), 11 deletions(-) diff --git a/azure-functions/acbs-function/.eslintrc.js b/azure-functions/acbs-function/.eslintrc.js index 2abbe77fa7..863735cb6c 100644 --- a/azure-functions/acbs-function/.eslintrc.js +++ b/azure-functions/acbs-function/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getHeaders', + '_getJSONData', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/dtfs-central-api/.eslintrc.js b/dtfs-central-api/.eslintrc.js index 2abbe77fa7..863735cb6c 100644 --- a/dtfs-central-api/.eslintrc.js +++ b/dtfs-central-api/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getHeaders', + '_getJSONData', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/e2e-tests/.eslintrc.js b/e2e-tests/.eslintrc.js index 2a82e264c1..e436d44b47 100644 --- a/e2e-tests/.eslintrc.js +++ b/e2e-tests/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getHeaders', + '_getJSONData', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/external-api/.eslintrc.js b/external-api/.eslintrc.js index 2abbe77fa7..863735cb6c 100644 --- a/external-api/.eslintrc.js +++ b/external-api/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getHeaders', + '_getJSONData', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/gef-ui/.eslintrc.js b/gef-ui/.eslintrc.js index 2abbe77fa7..863735cb6c 100644 --- a/gef-ui/.eslintrc.js +++ b/gef-ui/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getHeaders', + '_getJSONData', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/libs/common/.eslintrc.js b/libs/common/.eslintrc.js index 980bf2a2b2..734b61e8af 100644 --- a/libs/common/.eslintrc.js +++ b/libs/common/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getHeaders', + '_getJSONData', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/portal-api/.eslintrc.js b/portal-api/.eslintrc.js index 2abbe77fa7..863735cb6c 100644 --- a/portal-api/.eslintrc.js +++ b/portal-api/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getHeaders', + '_getJSONData', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/portal/.eslintrc.js b/portal/.eslintrc.js index 2abbe77fa7..863735cb6c 100644 --- a/portal/.eslintrc.js +++ b/portal/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getHeaders', + '_getJSONData', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/trade-finance-manager-api/.eslintrc.js b/trade-finance-manager-api/.eslintrc.js index b8fef1fdb4..3d6e4e08ef 100644 --- a/trade-finance-manager-api/.eslintrc.js +++ b/trade-finance-manager-api/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getHeaders', + '_getJSONData', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/trade-finance-manager-ui/.eslintrc.js b/trade-finance-manager-ui/.eslintrc.js index 9b502a3a0a..6b9139b546 100644 --- a/trade-finance-manager-ui/.eslintrc.js +++ b/trade-finance-manager-ui/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getHeaders', + '_getJSONData', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/utils/.eslintrc.js b/utils/.eslintrc.js index 2abbe77fa7..863735cb6c 100644 --- a/utils/.eslintrc.js +++ b/utils/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getHeaders', + '_getJSONData', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', From 677f531fe1de60ad165efe60cebd8da1647de2ae Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 6 Dec 2024 11:51:35 +0000 Subject: [PATCH 042/133] feat(dtfs2-6892): add sso controller get auth code url tests --- .../mock-data/authorisation-code-request.ts | 9 ++ .../src/test-helpers/mock-data/index.ts | 1 + .../builders/extra-id.service.mock.builder.ts | 11 ++- .../src/v1/__mocks__/builders/index.ts | 1 + .../builders/user.service.mock.builder.ts | 24 ++++++ .../sso.controller.get-auth-code-url.test.ts | 85 +++++++++++++++++++ .../src/v1/controllers/sso.controller.ts | 12 +-- .../src/v1/services/entra-id.service.ts | 11 +-- .../src/v1/services/user.service.ts | 20 ++--- 9 files changed, 146 insertions(+), 28 deletions(-) create mode 100644 libs/common/src/test-helpers/mock-data/authorisation-code-request.ts create mode 100644 trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts create mode 100644 trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts diff --git a/libs/common/src/test-helpers/mock-data/authorisation-code-request.ts b/libs/common/src/test-helpers/mock-data/authorisation-code-request.ts new file mode 100644 index 0000000000..c9dcd358c6 --- /dev/null +++ b/libs/common/src/test-helpers/mock-data/authorisation-code-request.ts @@ -0,0 +1,9 @@ +import { AuthorizationCodeRequest } from '@azure/msal-node'; + +export function anAuthorisationCodeRequest(): AuthorizationCodeRequest { + return { + scopes: ['a-scope'], + redirectUri: 'a-redirect-uri', + code: 'a-code', + }; +} diff --git a/libs/common/src/test-helpers/mock-data/index.ts b/libs/common/src/test-helpers/mock-data/index.ts index 6815b114a8..939b0fc3f8 100644 --- a/libs/common/src/test-helpers/mock-data/index.ts +++ b/libs/common/src/test-helpers/mock-data/index.ts @@ -1,3 +1,4 @@ +export * from './authorisation-code-request'; export * from './azure-file-info.mock'; export * from './utilisation-report.entity.mock-builder'; export * from './fee-record.entity.mock-builder'; diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts index b639222fe6..f1bc0f159f 100644 --- a/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts @@ -1,5 +1,4 @@ -import { AuthorizationCodeRequest } from '@azure/msal-node'; -import { BaseMockBuilder } from '@ukef/dtfs2-common'; +import { anAuthorisationCodeRequest, anEntraIdUser, BaseMockBuilder } from '@ukef/dtfs2-common'; import { EntraIdService } from '../../services/entra-id.service'; export class EntraIdServiceMockBuilder extends BaseMockBuilder { @@ -9,7 +8,13 @@ export class EntraIdServiceMockBuilder extends BaseMockBuilder { getAuthCodeUrl: jest.fn(async () => { return Promise.resolve({ authCodeUrl: 'a-auth-code-url', - authCodeUrlRequest: {} as AuthorizationCodeRequest, + authCodeUrlRequest: anAuthorisationCodeRequest(), + }); + }), + handleRedirect: jest.fn(async () => { + return Promise.resolve({ + entraIdUser: anEntraIdUser(), + successRedirect: 'a-success-redirect', }); }), }, diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/index.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/index.ts index 55cb5217ef..1e286fc95d 100644 --- a/trade-finance-manager-api/src/v1/__mocks__/builders/index.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/index.ts @@ -1,3 +1,4 @@ export * from './entra-id.api.mock.builder'; export * from './entra-id.config.mock.builder'; export * from './extra-id.service.mock.builder'; +export * from './user.service.mock.builder'; diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts new file mode 100644 index 0000000000..12af34a2a8 --- /dev/null +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { BaseMockBuilder } from '@ukef/dtfs2-common'; +import { aTfmUser } from '@ukef/dtfs2-common/mock-data-backend'; +import { + UpsertTfmUserFromEntraIdUserParams, + UpsertTfmUserFromEntraIdUserResponse, + saveUserLoginInformationParams, + UserService, +} from '../../services/user.service'; + +export class UserServiceMockBuilder extends BaseMockBuilder { + constructor() { + super({ + defaultInstance: { + upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }: UpsertTfmUserFromEntraIdUserParams): Promise { + return Promise.resolve(aTfmUser()); + }, + saveUserLoginInformation({ userId, sessionIdentifier, auditDetails }: saveUserLoginInformationParams): Promise { + return Promise.resolve(); + }, + }, + }); + } +} diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts new file mode 100644 index 0000000000..4fbe86b274 --- /dev/null +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts @@ -0,0 +1,85 @@ +import httpMocks, { MockRequest, MockResponse } from 'node-mocks-http'; +import { + anAuthorisationCodeRequest, + GetAuthCodeUrlApiRequest, + GetAuthCodeUrlApiResponse, + GetAuthCodeUrlParams, + GetAuthCodeUrlResponse, +} from '@ukef/dtfs2-common'; +import { EntraIdServiceMockBuilder, UserServiceMockBuilder } from '../__mocks__/builders'; +import { EntraIdService } from '../services/entra-id.service'; +import { UserService } from '../services/user.service'; +import { SsoController } from './sso.controller'; + +describe('SsoController', () => { + describe('getAuthCodeUrl', () => { + let entraIdService: EntraIdService; + let userService: UserService; + let ssoController: SsoController; + let req: MockRequest; + let res: MockResponse; + let next: jest.Mock; + + const getAuthCodeUrlMock = jest.fn(); + + const aSuccessRedirect = 'a-success-redirect'; + + const getHttpMocks = () => + httpMocks.createMocks({ + params: { successRedirect: aSuccessRedirect } as GetAuthCodeUrlParams, + }); + + beforeEach(() => { + jest.resetAllMocks(); + entraIdService = new EntraIdServiceMockBuilder().withDefaults().with({ getAuthCodeUrl: getAuthCodeUrlMock }).build(); + userService = new UserServiceMockBuilder().withDefaults().build(); + ssoController = new SsoController({ entraIdService, userService }); + + ({ req, res } = getHttpMocks()); + next = jest.fn(); + }); + + it('should call entraIdService.getAuthCodeUrl with the correct params', async () => { + // Arrange + const expectedGetAuthCodeUrlParams: GetAuthCodeUrlParams = { + successRedirect: aSuccessRedirect, + }; + // Act + await ssoController.getAuthCodeUrl(req, res, next); + + // Assert + expect(getAuthCodeUrlMock).toHaveBeenCalledWith(expectedGetAuthCodeUrlParams); + }); + + it('should return the result of entraIdService.getAuthCodeUrl', async () => { + // Arrange + const expectedGetAuthCodeUrlResult: GetAuthCodeUrlResponse = { authCodeUrl: 'a-auth-code-url', authCodeUrlRequest: anAuthorisationCodeRequest() }; + getAuthCodeUrlMock.mockResolvedValue(expectedGetAuthCodeUrlResult); + // Act + await ssoController.getAuthCodeUrl(req, res, next); + + // Assert + expect(res._getJSONData()).toEqual(expectedGetAuthCodeUrlResult); + expect(res._getStatusCode()).toEqual(200); + }); + + it('should return a 200', async () => { + // Act + await ssoController.getAuthCodeUrl(req, res, next); + + // Assert + expect(res._getStatusCode()).toEqual(200); + }); + + it('should call next with the error if an error is thrown', async () => { + // Arrange + const expectedError = new Error('an-error'); + getAuthCodeUrlMock.mockRejectedValue(expectedError); + // Act + await ssoController.getAuthCodeUrl(req, res, next); + + // Assert + expect(next).toHaveBeenCalledWith(expectedError); + }); + }); +}); diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts index 901f7784b3..c4e61ff261 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts @@ -23,13 +23,13 @@ export class SsoController { this.userService = userService; } - async getAuthCodeUrl(req: GetAuthCodeUrlApiRequest, res: GetAuthCodeUrlApiResponse, next: NextFunction) { + public async getAuthCodeUrl(req: GetAuthCodeUrlApiRequest, res: GetAuthCodeUrlApiResponse, next: NextFunction) { try { const { successRedirect } = req.params; const getAuthCodeUrlResponse = await this.entraIdService.getAuthCodeUrl({ successRedirect }); - return res.json(getAuthCodeUrlResponse); + res.json(getAuthCodeUrlResponse); } catch (error) { - return next(error); + next(error); } } @@ -40,7 +40,7 @@ export class SsoController { * It takes the response from the Entra Id service and processes it to create or update a user in the TFM-API database. * It then issues a JWT token for the user and returns it to the client. */ - async handleSsoRedirectForm(req: HandleSsoRedirectFormApiRequest, res: HandleSsoRedirectFormApiResponse, next: NextFunction) { + public async handleSsoRedirectForm(req: HandleSsoRedirectFormApiRequest, res: HandleSsoRedirectFormApiResponse, next: NextFunction) { try { const { body } = req; const { authCodeResponse, originalAuthCodeUrlRequest, auditDetails } = body; @@ -68,9 +68,9 @@ export class SsoController { successRedirect, }; - return res.send(response); + res.send(response); } catch (error) { - return next(error); + next(error); } } } diff --git a/trade-finance-manager-api/src/v1/services/entra-id.service.ts b/trade-finance-manager-api/src/v1/services/entra-id.service.ts index 1f4e3aec42..a80b99802d 100644 --- a/trade-finance-manager-api/src/v1/services/entra-id.service.ts +++ b/trade-finance-manager-api/src/v1/services/entra-id.service.ts @@ -1,18 +1,13 @@ import { AuthorizationUrlRequest, ConfidentialClientApplication, Configuration as MsalAppConfig, CryptoProvider } from '@azure/msal-node'; -import { EntraIdUser, DecodedAuthCodeRequestState, EntraIdAuthCodeRedirectResponseBody } from '@ukef/dtfs2-common'; +import { EntraIdUser, DecodedAuthCodeRequestState, EntraIdAuthCodeRedirectResponseBody, GetAuthCodeUrlResponse } from '@ukef/dtfs2-common'; import { DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA, ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA } from '@ukef/dtfs2-common/schemas'; import { EntraIdConfig } from '../configs/entra-id.config'; import { EntraIdApi } from '../third-party-apis/entra-id.api'; -type GetAuthCodeUrlParams = { +// Todo remove this and commonise +export type GetAuthCodeUrlParams = { successRedirect?: string; }; - -type GetAuthCodeUrlResponse = { - authCodeUrl: string; - authCodeUrlRequest: AuthorizationUrlRequest; -}; - type HandleRedirectParams = { authCodeResponse: EntraIdAuthCodeRedirectResponseBody; originalAuthCodeUrlRequest?: AuthorizationUrlRequest; diff --git a/trade-finance-manager-api/src/v1/services/user.service.ts b/trade-finance-manager-api/src/v1/services/user.service.ts index 30275e584a..dad7843690 100644 --- a/trade-finance-manager-api/src/v1/services/user.service.ts +++ b/trade-finance-manager-api/src/v1/services/user.service.ts @@ -3,11 +3,17 @@ import { ENTRA_ID_USER_TO_UPSERT_TFM_USER_REQUEST_SCHEMA } from '@ukef/dtfs2-com import { ObjectId } from 'mongodb'; import { UserRepo } from '../repo/user.repo'; -type UpsertTfmUserFromEntraIdUserParams = { +export type UpsertTfmUserFromEntraIdUserParams = { entraIdUser: EntraIdUser; auditDetails: AuditDetails; }; +export type saveUserLoginInformationParams = { + userId: ObjectId; + sessionIdentifier: string; + auditDetails: AuditDetails; +}; + export type UpsertTfmUserFromEntraIdUserResponse = TfmUser; export class UserService { @@ -17,7 +23,7 @@ export class UserService { * @param entraIdUser * @returns The upsert user request */ - public static transformEntraIdUserToUpsertTfmUserRequest(entraIdUser: EntraIdUser): UpsertTfmUserRequest { + private static transformEntraIdUserToUpsertTfmUserRequest(entraIdUser: EntraIdUser): UpsertTfmUserRequest { return ENTRA_ID_USER_TO_UPSERT_TFM_USER_REQUEST_SCHEMA.parse(entraIdUser); } @@ -59,15 +65,7 @@ export class UserService { return upsertedUser; } - public async saveUserLoginInformation({ - userId, - sessionIdentifier, - auditDetails, - }: { - userId: ObjectId; - sessionIdentifier: string; - auditDetails: AuditDetails; - }) { + public async saveUserLoginInformation({ userId, sessionIdentifier, auditDetails }: saveUserLoginInformationParams) { await UserRepo.updateUserById({ userId, userUpdate: { From c1e97ea0b51737a5a729fce02bbf1c6903f20e20 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 6 Dec 2024 12:55:47 +0000 Subject: [PATCH 043/133] feat(dtfs2-6892): add builder --- ...ra-id-auth-code-redirect-response-body-schema.test.ts | 9 ++------- .../entra-id-auth-code-redirect-response-body.ts | 8 ++++++++ libs/common/src/test-helpers/mock-data/index.ts | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 libs/common/src/test-helpers/mock-data/entra-id-auth-code-redirect-response-body.ts diff --git a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts index 1dfca1245e..a0486861d9 100644 --- a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts @@ -1,11 +1,10 @@ -import { withSchemaValidationTests } from '../../test-helpers'; -import { EntraIdAuthCodeRedirectResponseBody } from '../../types/tfm/entra-id'; +import { anEntraIdAuthCodeRedirectResponseBody, withSchemaValidationTests } from '../../test-helpers'; import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from './entra-id.schema'; describe('ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA', () => { withSchemaValidationTests({ schema: ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA, - aValidPayload, + aValidPayload: anEntraIdAuthCodeRedirectResponseBody, testCases: [ { parameterPath: 'code', @@ -28,7 +27,3 @@ describe('ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA', () => { ], }); }); - -function aValidPayload(): EntraIdAuthCodeRedirectResponseBody { - return { code: 'a-code', client_info: 'a-client-info', state: 'a-state', session_state: 'a-session-state' }; -} diff --git a/libs/common/src/test-helpers/mock-data/entra-id-auth-code-redirect-response-body.ts b/libs/common/src/test-helpers/mock-data/entra-id-auth-code-redirect-response-body.ts new file mode 100644 index 0000000000..f8b23706fc --- /dev/null +++ b/libs/common/src/test-helpers/mock-data/entra-id-auth-code-redirect-response-body.ts @@ -0,0 +1,8 @@ +import { EntraIdAuthCodeRedirectResponseBody } from '../../types'; + +export const anEntraIdAuthCodeRedirectResponseBody = (): EntraIdAuthCodeRedirectResponseBody => ({ + code: 'a-code', + client_info: 'a-client-info', + state: 'a-state', + session_state: 'a-session-state', +}); diff --git a/libs/common/src/test-helpers/mock-data/index.ts b/libs/common/src/test-helpers/mock-data/index.ts index 939b0fc3f8..e61cd18e2b 100644 --- a/libs/common/src/test-helpers/mock-data/index.ts +++ b/libs/common/src/test-helpers/mock-data/index.ts @@ -11,3 +11,4 @@ export * from './utilisation-report-mock-csv-data'; export * from './entra-id-user'; export * from './create-tfm-user-request'; export * from './upsert-tfm-user-request'; +export * from './entra-id-auth-code-redirect-response-body'; From e785461e3043073a1bb5dd4bdae6f4e9d9e41ce1 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 6 Dec 2024 15:57:48 +0000 Subject: [PATCH 044/133] feat(dtfs2-6892): update test api error constructor --- .../helpers/execute-with-sql-transaction.test.ts | 2 +- .../delete-payment.controller/index.test.ts | 4 ++-- .../index.test.ts | 4 +++- .../index.test.ts | 4 ++-- .../post-keying-data.controller/index.test.ts | 2 +- .../post-payment.controller/index.test.ts | 4 ++-- .../index.test.ts | 4 ++-- .../index.test.ts | 4 ++-- .../index.test.ts | 2 +- .../index.test.ts | 4 ++-- .../index.test.ts | 4 ++-- libs/common/src/test-helpers/test-api-error.ts | 14 ++++++++++++-- .../submit-deal-cancellation.post.api-test.ts | 2 +- .../delete-deal-cancellation.controller.test.ts | 7 ++++++- .../get-deal-cancellation.controller.test.ts | 2 +- .../submit-deal-cancellation.controller.test.ts | 7 ++++++- .../update-deal-cancellation.controller.test.ts | 7 ++++++- 17 files changed, 52 insertions(+), 25 deletions(-) diff --git a/dtfs-central-api/src/helpers/execute-with-sql-transaction.test.ts b/dtfs-central-api/src/helpers/execute-with-sql-transaction.test.ts index e93c09bbc9..4e74a4d8b4 100644 --- a/dtfs-central-api/src/helpers/execute-with-sql-transaction.test.ts +++ b/dtfs-central-api/src/helpers/execute-with-sql-transaction.test.ts @@ -136,7 +136,7 @@ describe('executeWithSqlTransaction', () => { it("throws a specific 'TransactionFailedError' if the supplied function throws an 'ApiError'", async () => { // Arrange - const customError = new TestApiError(HttpStatusCode.BadRequest); + const customError = new TestApiError({ status: HttpStatusCode.BadRequest }); const functionToExecute = jest.fn().mockRejectedValue(customError); diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/delete-payment.controller/index.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/delete-payment.controller/index.test.ts index b102b255d0..3417d2ed5d 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/delete-payment.controller/index.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/delete-payment.controller/index.test.ts @@ -129,7 +129,7 @@ describe('delete-payment.controller', () => { const res = httpMocks.createResponse(); const errorStatus = 404; - mockHandleEvent.mockRejectedValue(new TestApiError(errorStatus, undefined)); + mockHandleEvent.mockRejectedValue(new TestApiError({ status: errorStatus })); // Act await deletePayment(req, res); @@ -147,7 +147,7 @@ describe('delete-payment.controller', () => { const res = httpMocks.createResponse(); const errorMessage = 'Some error message'; - mockHandleEvent.mockRejectedValue(new TestApiError(undefined, errorMessage)); + mockHandleEvent.mockRejectedValue(new TestApiError({ message: errorMessage })); // Act await deletePayment(req, res); diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/get-utilisation-report-reconciliation-details-by-id.controller/index.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/get-utilisation-report-reconciliation-details-by-id.controller/index.test.ts index 56f3fb6807..a9d363e56e 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/get-utilisation-report-reconciliation-details-by-id.controller/index.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/get-utilisation-report-reconciliation-details-by-id.controller/index.test.ts @@ -46,7 +46,9 @@ describe('get-utilisation-report-reconciliation-details-by-id.controller', () => const { req, res } = getHttpMocks(); const errorMessage = 'Some error message'; - when(utilisationReportRepoFindSpy).calledWith(reportId).mockRejectedValue(new TestApiError(undefined, errorMessage)); + when(utilisationReportRepoFindSpy) + .calledWith(reportId) + .mockRejectedValue(new TestApiError({ message: errorMessage })); // Act await getUtilisationReportReconciliationDetailsById(req, res); diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/index.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/index.test.ts index 5cce17741b..177ab1aa48 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/index.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-add-fees-to-an-existing-payment-group.controller/index.test.ts @@ -210,7 +210,7 @@ describe('post-fees-to-an-existing-payment-group.controller', () => { const res = httpMocks.createResponse(); const errorStatus = 404; - jest.mocked(addFeesToAnExistingPaymentGroup).mockRejectedValue(new TestApiError(errorStatus, undefined)); + jest.mocked(addFeesToAnExistingPaymentGroup).mockRejectedValue(new TestApiError({ status: errorStatus })); // Act await postAddFeesToAnExistingPaymentGroup(req, res); @@ -228,7 +228,7 @@ describe('post-fees-to-an-existing-payment-group.controller', () => { const res = httpMocks.createResponse(); const errorMessage = 'Some error message'; - jest.mocked(addFeesToAnExistingPaymentGroup).mockRejectedValue(new TestApiError(undefined, errorMessage)); + jest.mocked(addFeesToAnExistingPaymentGroup).mockRejectedValue(new TestApiError({ message: errorMessage })); // Act await postAddFeesToAnExistingPaymentGroup(req, res); diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-keying-data.controller/index.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-keying-data.controller/index.test.ts index fdb6b32930..ee9781e7c6 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-keying-data.controller/index.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-keying-data.controller/index.test.ts @@ -135,7 +135,7 @@ describe('post-keying-data.controller', () => { const { req, res } = getHttpMocks(); const errorMessage = 'Some error message'; - feeRecordRepoFindSpy.mockRejectedValue(new TestApiError(undefined, errorMessage)); + feeRecordRepoFindSpy.mockRejectedValue(new TestApiError({ message: errorMessage })); // Act await postKeyingData(req, res); diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/index.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/index.test.ts index ba83058407..a1557ed1f1 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/index.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-payment.controller/index.test.ts @@ -90,7 +90,7 @@ describe('post-payment.controller', () => { const res = httpMocks.createResponse(); const errorStatus = HttpStatusCode.NotFound; - jest.mocked(addPaymentToUtilisationReport).mockRejectedValue(new TestApiError(errorStatus, undefined)); + jest.mocked(addPaymentToUtilisationReport).mockRejectedValue(new TestApiError({ status: errorStatus })); // Act await postPayment(req, res); @@ -108,7 +108,7 @@ describe('post-payment.controller', () => { const res = httpMocks.createResponse(); const errorMessage = 'Some error message'; - jest.mocked(addPaymentToUtilisationReport).mockRejectedValue(new TestApiError(undefined, errorMessage)); + jest.mocked(addPaymentToUtilisationReport).mockRejectedValue(new TestApiError({ message: errorMessage })); // Act await postPayment(req, res); diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/index.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/index.test.ts index 4fe59f7654..6951e48b39 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/index.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-remove-fees-from-payment-group.controller/index.test.ts @@ -135,7 +135,7 @@ describe('post-remove-fees-from-payment-group.controller', () => { const res = httpMocks.createResponse(); const errorStatus = 404; - jest.mocked(removeFeesFromPaymentGroup).mockRejectedValue(new TestApiError(errorStatus, undefined)); + jest.mocked(removeFeesFromPaymentGroup).mockRejectedValue(new TestApiError({ status: errorStatus })); // Act await postRemoveFeesFromPaymentGroup(req, res); @@ -153,7 +153,7 @@ describe('post-remove-fees-from-payment-group.controller', () => { const res = httpMocks.createResponse(); const errorMessage = 'Some error message'; - jest.mocked(removeFeesFromPaymentGroup).mockRejectedValue(new TestApiError(undefined, errorMessage)); + jest.mocked(removeFeesFromPaymentGroup).mockRejectedValue(new TestApiError({ message: errorMessage })); // Act await postRemoveFeesFromPaymentGroup(req, res); diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-report-data-validation.controller/index.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-report-data-validation.controller/index.test.ts index bef2e7c3e7..aa8884b0dd 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-report-data-validation.controller/index.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-report-data-validation.controller/index.test.ts @@ -47,7 +47,7 @@ describe('post-report-data-validation.controller', () => { const errorStatus = HttpStatusCode.BadRequest; jest.mocked(validateUtilisationReportCsvData).mockImplementation(() => { - throw new TestApiError(errorStatus, undefined); + throw new TestApiError({ status: errorStatus }); }); // Act @@ -66,7 +66,7 @@ describe('post-report-data-validation.controller', () => { const errorMessage = 'Some error message'; jest.mocked(validateUtilisationReportCsvData).mockImplementation(() => { - throw new TestApiError(undefined, errorMessage); + throw new TestApiError({ message: errorMessage }); }); // Act diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-upload-utilisation-report.controller/index.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-upload-utilisation-report.controller/index.test.ts index a973a7cee0..c57a6c9703 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-upload-utilisation-report.controller/index.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/post-upload-utilisation-report.controller/index.test.ts @@ -63,7 +63,7 @@ describe('post-upload-utilisation-report controller', () => { const errorMessage = 'An error message'; const errorStatus = HttpStatusCode.BadRequest; - const testApiError = new TestApiError(errorStatus, errorMessage); + const testApiError = new TestApiError({ message: errorMessage, status: errorStatus }); jest.mocked(executeWithSqlTransaction).mockRejectedValue(TransactionFailedError.forApiError(testApiError)); diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/put-keying-data-mark-as-done.controller/index.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/put-keying-data-mark-as-done.controller/index.test.ts index 4c70616a61..b7675dbcb2 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/put-keying-data-mark-as-done.controller/index.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/put-keying-data-mark-as-done.controller/index.test.ts @@ -67,7 +67,7 @@ describe('put-keying-data-mark-as-done.controller', () => { const res = httpMocks.createResponse(); const errorStatus = 404; - mockHandleEvent.mockRejectedValue(new TestApiError(errorStatus, undefined)); + mockHandleEvent.mockRejectedValue(new TestApiError({ status: errorStatus })); // Act await putKeyingDataMarkAsDone(req, res); @@ -85,7 +85,7 @@ describe('put-keying-data-mark-as-done.controller', () => { const res = httpMocks.createResponse(); const errorMessage = 'Some error message'; - mockHandleEvent.mockRejectedValue(new TestApiError(undefined, errorMessage)); + mockHandleEvent.mockRejectedValue(new TestApiError({ message: errorMessage })); // Act await putKeyingDataMarkAsDone(req, res); diff --git a/dtfs-central-api/src/v1/controllers/utilisation-report-service/put-keying-data-mark-as-to-do.controller/index.test.ts b/dtfs-central-api/src/v1/controllers/utilisation-report-service/put-keying-data-mark-as-to-do.controller/index.test.ts index b69360a37d..54330b95d1 100644 --- a/dtfs-central-api/src/v1/controllers/utilisation-report-service/put-keying-data-mark-as-to-do.controller/index.test.ts +++ b/dtfs-central-api/src/v1/controllers/utilisation-report-service/put-keying-data-mark-as-to-do.controller/index.test.ts @@ -67,7 +67,7 @@ describe('put-keying-data-mark-as-to-do.controller', () => { const res = httpMocks.createResponse(); const errorStatus = 404; - mockHandleEvent.mockRejectedValue(new TestApiError(errorStatus, undefined)); + mockHandleEvent.mockRejectedValue(new TestApiError({ status: errorStatus })); // Act await putKeyingDataMarkAsToDo(req, res); @@ -85,7 +85,7 @@ describe('put-keying-data-mark-as-to-do.controller', () => { const res = httpMocks.createResponse(); const errorMessage = 'Some error message'; - mockHandleEvent.mockRejectedValue(new TestApiError(undefined, errorMessage)); + mockHandleEvent.mockRejectedValue(new TestApiError({ message: errorMessage })); // Act await putKeyingDataMarkAsToDo(req, res); diff --git a/libs/common/src/test-helpers/test-api-error.ts b/libs/common/src/test-helpers/test-api-error.ts index 31e9edefcf..7fb3e8f2b5 100644 --- a/libs/common/src/test-helpers/test-api-error.ts +++ b/libs/common/src/test-helpers/test-api-error.ts @@ -1,8 +1,18 @@ import { HttpStatusCode } from 'axios'; import { ApiError } from '../errors'; +import { ApiErrorCode } from '../types'; + +type TestApiErrorParams = { + status?: HttpStatusCode; + message?: string; + code?: ApiErrorCode; + cause?: unknown; +}; export class TestApiError extends ApiError { - constructor(status?: number, message?: string) { - super({ status: status ?? HttpStatusCode.InternalServerError, message: message ?? '' }); + constructor(testApiErrorParams: TestApiErrorParams = {}) { + const defaults = { status: HttpStatusCode.InternalServerError, message: '' }; + const apiErrorConstructorParams = { ...defaults, ...testApiErrorParams }; + super(apiErrorConstructorParams); } } diff --git a/trade-finance-manager-api/api-tests/v1/deal-cancellation/submit-deal-cancellation.post.api-test.ts b/trade-finance-manager-api/api-tests/v1/deal-cancellation/submit-deal-cancellation.post.api-test.ts index a5d3c9cbb2..9577b38f42 100644 --- a/trade-finance-manager-api/api-tests/v1/deal-cancellation/submit-deal-cancellation.post.api-test.ts +++ b/trade-finance-manager-api/api-tests/v1/deal-cancellation/submit-deal-cancellation.post.api-test.ts @@ -167,7 +167,7 @@ describe('POST /v1/deals/:id/cancellation/submit', () => { // Arrange const errorStatus = HttpStatusCode.BadRequest; const errorMessage = 'An error occurred'; - submitDealCancellationMock.mockRejectedValueOnce(new TestApiError(errorStatus, errorMessage)); + submitDealCancellationMock.mockRejectedValueOnce(new TestApiError({ status: errorStatus, message: errorMessage })); const url = getSubmitTfmDealCancellationUrl({ id: validId }); diff --git a/trade-finance-manager-api/src/v1/controllers/deal-cancellation/delete-deal-cancellation.controller.test.ts b/trade-finance-manager-api/src/v1/controllers/deal-cancellation/delete-deal-cancellation.controller.test.ts index 7643a8fa96..e26f5ea645 100644 --- a/trade-finance-manager-api/src/v1/controllers/deal-cancellation/delete-deal-cancellation.controller.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/deal-cancellation/delete-deal-cancellation.controller.test.ts @@ -52,7 +52,12 @@ describe('controllers - deal cancellation', () => { it('should return an error when there is an API error', async () => { const testErrorStatus = 400; const testApiErrorMessage = 'test api error message'; - jest.mocked(api.deleteDealCancellation).mockRejectedValue(new TestApiError(testErrorStatus, testApiErrorMessage)); + jest.mocked(api.deleteDealCancellation).mockRejectedValue( + new TestApiError({ + status: testErrorStatus, + message: testApiErrorMessage, + }), + ); // Arrange const { req, res } = httpMocks.createMocks({ diff --git a/trade-finance-manager-api/src/v1/controllers/deal-cancellation/get-deal-cancellation.controller.test.ts b/trade-finance-manager-api/src/v1/controllers/deal-cancellation/get-deal-cancellation.controller.test.ts index 7233177c04..6f3e450d51 100644 --- a/trade-finance-manager-api/src/v1/controllers/deal-cancellation/get-deal-cancellation.controller.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/deal-cancellation/get-deal-cancellation.controller.test.ts @@ -54,7 +54,7 @@ describe('controllers - deal cancellation', () => { it('should return an error when there is an API error', async () => { const testErrorStatus = 418; const testApiErrorMessage = 'test api error message'; - jest.mocked(api.getDealCancellation).mockRejectedValue(new TestApiError(testErrorStatus, testApiErrorMessage)); + jest.mocked(api.getDealCancellation).mockRejectedValue(new TestApiError({ status: testErrorStatus, message: testApiErrorMessage })); // Arrange const { req, res } = httpMocks.createMocks({ diff --git a/trade-finance-manager-api/src/v1/controllers/deal-cancellation/submit-deal-cancellation.controller.test.ts b/trade-finance-manager-api/src/v1/controllers/deal-cancellation/submit-deal-cancellation.controller.test.ts index 0128b6229f..83a8498e36 100644 --- a/trade-finance-manager-api/src/v1/controllers/deal-cancellation/submit-deal-cancellation.controller.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/deal-cancellation/submit-deal-cancellation.controller.test.ts @@ -50,7 +50,12 @@ describe('controllers - deal cancellation', () => { // Arrange const errorStatus = HttpStatusCode.BadRequest; const errorMessage = 'An error occurred'; - submitDealCancellationMock.mockRejectedValueOnce(new TestApiError(errorStatus, errorMessage)); + submitDealCancellationMock.mockRejectedValueOnce( + new TestApiError({ + status: errorStatus, + message: errorMessage, + }), + ); const { req, res } = httpMocks.createMocks({ params: { dealId: mockDealId }, diff --git a/trade-finance-manager-api/src/v1/controllers/deal-cancellation/update-deal-cancellation.controller.test.ts b/trade-finance-manager-api/src/v1/controllers/deal-cancellation/update-deal-cancellation.controller.test.ts index 56b68dcc13..bf6ca125c3 100644 --- a/trade-finance-manager-api/src/v1/controllers/deal-cancellation/update-deal-cancellation.controller.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/deal-cancellation/update-deal-cancellation.controller.test.ts @@ -68,7 +68,12 @@ describe('controllers - deal cancellation', () => { it('should return an error when there is an API error', async () => { const testErrorStatus = 404; const testApiErrorMessage = 'test api error message'; - jest.mocked(api.updateDealCancellation).mockRejectedValue(new TestApiError(testErrorStatus, testApiErrorMessage)); + jest.mocked(api.updateDealCancellation).mockRejectedValue( + new TestApiError({ + status: testErrorStatus, + message: testApiErrorMessage, + }), + ); // Arrange const { req, res } = httpMocks.createMocks({ From 48b412551294684f67a4337993f6a66b19f9f4b0 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 6 Dec 2024 16:47:09 +0000 Subject: [PATCH 045/133] feat(dtfs2-6892): add with api error tests helper --- .../test-helpers/test-cases-backend/index.ts | 1 + .../with-api-error-tests.ts | 64 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts diff --git a/libs/common/src/test-helpers/test-cases-backend/index.ts b/libs/common/src/test-helpers/test-cases-backend/index.ts index eca4823b49..3c194e6a79 100644 --- a/libs/common/src/test-helpers/test-cases-backend/index.ts +++ b/libs/common/src/test-helpers/test-cases-backend/index.ts @@ -1,2 +1,3 @@ export * from './with-mongo-id-path-parameter-validation-tests'; export * from './with-sql-id-path-parameter-validation-tests'; +export * from './with-api-error-tests'; diff --git a/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts b/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts new file mode 100644 index 0000000000..9798bbc45f --- /dev/null +++ b/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts @@ -0,0 +1,64 @@ +import { HttpStatusCode } from 'axios'; +import { MockResponse } from 'node-mocks-http'; +import { Response } from 'express'; +import { API_ERROR_CODE } from '../../constants'; +import { TestApiError } from '../test-api-error'; + +export const withApiErrorTests = ({ + mockAnError, + makeRequest, + getRes, + endpointErrorMessage, +}: { + mockAnError: (error: unknown) => void; + makeRequest: () => Promise; + getRes: () => MockResponse; + endpointErrorMessage: string; +}) => { + describe('with api error tests', () => { + describe('when an api error is thrown', () => { + it('should transform and return the error', async () => { + // Arrange + const errorStatus = HttpStatusCode.BadRequest; + const errorMessage = 'a message that should not be exposed'; + const errorCode = API_ERROR_CODE.INVALID_PAYLOAD; + + const error = new TestApiError({ status: errorStatus, message: errorMessage, code: errorCode }); + mockAnError(error); + + // Act + await makeRequest(); + + // Assert + const res = getRes(); + expect(res._getStatusCode()).toEqual(errorStatus); + expect(res._getData()).toEqual({ + status: errorStatus, + message: `${endpointErrorMessage}: ${errorMessage}`, + code: errorCode, + }); + }); + }); + + describe('when a non-api error is thrown', () => { + it('should return 500 with a generic error message', async () => { + // Arrange + const errorMessage = 'a message that should not be exposed'; + + const error = new Error(errorMessage); + mockAnError(error); + + // Act + await makeRequest(); + + // Assert + const res = getRes(); + expect(res._getStatusCode()).toEqual(HttpStatusCode.InternalServerError); + expect(res._getData()).toEqual({ + status: HttpStatusCode.InternalServerError, + message: endpointErrorMessage, + }); + }); + }); + }); +}; From e31235dd34a4fa516a8226e05fd2f92e00035a22 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 6 Dec 2024 16:52:38 +0000 Subject: [PATCH 046/133] feat(dtfs2-6892): remove unused upsert functions --- .../v1/controllers/user/user.controller.js | 14 --- ...upsert-tfm-user-from-entra-id-user.test.ts | 40 ------ .../v1/controllers/user/user.routes.test.js | 115 ------------------ 3 files changed, 169 deletions(-) delete mode 100644 trade-finance-manager-api/src/v1/controllers/user/user.controller.upsert-tfm-user-from-entra-id-user.test.ts delete mode 100644 trade-finance-manager-api/src/v1/controllers/user/user.routes.test.js diff --git a/trade-finance-manager-api/src/v1/controllers/user/user.controller.js b/trade-finance-manager-api/src/v1/controllers/user/user.controller.js index 3d3d44f3c0..20db18fc33 100644 --- a/trade-finance-manager-api/src/v1/controllers/user/user.controller.js +++ b/trade-finance-manager-api/src/v1/controllers/user/user.controller.js @@ -2,7 +2,6 @@ const { ObjectId } = require('mongodb'); const { generateAuditDatabaseRecordFromAuditDetails, deleteOne } = require('@ukef/dtfs2-common/change-stream'); const { PAYLOAD_VERIFICATION, DocumentNotDeletedError } = require('@ukef/dtfs2-common'); const { isVerifiedPayload } = require('@ukef/dtfs2-common/payload-verification'); -const { UserService } = require('../../services/user.service'); const { mongoDbClient: db } = require('../../../drivers/db-client'); const { mapUserData } = require('./helpers/mapUserData.helper'); const { USER } = require('../../../constants'); @@ -107,19 +106,6 @@ exports.update = async (_id, update, auditDetails, callback) => { }); }; -/** - * Used during the SSO login process to keep the TFM user in sync with the Entra ID user. - * Upserts (Creates or updates) a TFM user from an Entra ID user. - * @param {object} upsertTfmUserFromEntraIdUserParams - * @param {import('@ukef/dtfs2-common').EntraIdUser} upsertTfmUserFromEntraIdUserParams.entraIdUser - * @param {import('@ukef/dtfs2-common').AuditDetails} upsertTfmUserFromEntraIdUserParams.auditDetails - */ -exports.upsertTfmUserFromEntraIdUser = async ({ entraIdUser, auditDetails }) => { - const upsertedUser = await UserService.upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }); - const tfmSessionUser = mapUserData(upsertedUser); - return tfmSessionUser; -}; - exports.updateLastLoginAndResetSignInData = async (user, sessionIdentifier, auditDetails, callback) => { if (!ObjectId.isValid(user._id)) { throw new Error('Invalid User Id'); diff --git a/trade-finance-manager-api/src/v1/controllers/user/user.controller.upsert-tfm-user-from-entra-id-user.test.ts b/trade-finance-manager-api/src/v1/controllers/user/user.controller.upsert-tfm-user-from-entra-id-user.test.ts deleted file mode 100644 index 0c55e357e2..0000000000 --- a/trade-finance-manager-api/src/v1/controllers/user/user.controller.upsert-tfm-user-from-entra-id-user.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { anEntraIdUser, AuditDetails, EntraIdUser, TfmSessionUser } from '@ukef/dtfs2-common'; -import { generateSystemAuditDetails } from '@ukef/dtfs2-common/change-stream'; -import { mapUserData } from './helpers/mapUserData.helper'; -import { UpsertTfmUserFromEntraIdUserResponse, UserService } from '../../services/user.service'; -import { upsertTfmUserFromEntraIdUser } from './user.controller'; -import { userServiceMockResponses } from '../../../../test-helpers'; - -describe('user controller', () => { - describe('upsertTfmUserFromEntraIdUser', () => { - let entraIdUser: EntraIdUser; - let auditDetails: AuditDetails; - - let upsertTfmUserFromEntraIdUserResponse: UpsertTfmUserFromEntraIdUserResponse; - let mappedUserDetails: TfmSessionUser; - let upsertTfmUserFromEntraIdUserSpy: jest.SpyInstance; - - beforeEach(() => { - jest.resetAllMocks(); - entraIdUser = anEntraIdUser(); - auditDetails = generateSystemAuditDetails(); - - upsertTfmUserFromEntraIdUserResponse = userServiceMockResponses.anUpsertTfmUserFromEntraIdUserResponse(); - mappedUserDetails = mapUserData(upsertTfmUserFromEntraIdUserResponse); - - upsertTfmUserFromEntraIdUserSpy = jest.spyOn(UserService, 'upsertTfmUserFromEntraIdUser').mockResolvedValueOnce(upsertTfmUserFromEntraIdUserResponse); - }); - - it('should upsert user in the database', async () => { - await upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }); - - expect(upsertTfmUserFromEntraIdUserSpy).toHaveBeenCalledWith({ entraIdUser, auditDetails }); - }); - - it('should return the mapped user', async () => { - const result = await upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }); - - expect(result).toEqual(mappedUserDetails); - }); - }); -}); diff --git a/trade-finance-manager-api/src/v1/controllers/user/user.routes.test.js b/trade-finance-manager-api/src/v1/controllers/user/user.routes.test.js deleted file mode 100644 index ed932c3975..0000000000 --- a/trade-finance-manager-api/src/v1/controllers/user/user.routes.test.js +++ /dev/null @@ -1,115 +0,0 @@ -const httpMocks = require('node-mocks-http'); -const { anEntraIdUser, ApiError } = require('@ukef/dtfs2-common'); -const { HttpStatusCode } = require('axios'); -const { getEntraIdUserSuccessTestCases, getEntraIdUserFailureTestCases } = require('@ukef/dtfs2-common'); -const { upsertTfmUserFromEntraIdUser } = require('./user.routes'); -const userController = require('./user.controller'); -const { withValidatePayloadTests } = require('../../../../test-helpers'); - -jest.mock('../user/user.controller.js', () => ({ - upsertTfmUserFromEntraIdUser: jest.fn(), -})); - -describe('user routes', () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - - describe('upsertTfmUserFromEntraIdUser', () => { - const aSuccessfulResponseFromController = 'aSuccessfulResponse'; - - withValidatePayloadTests({ - makeRequest: upsertTfmUserFromEntraIdUser, - givenTheRequestWouldOtherwiseSucceed: mockSuccessfulUpsertTfmUserFromEntraIdUser, - successTestCases: getEntraIdUserSuccessTestCases({}), - successStatusCode: HttpStatusCode.Ok, - successResponse: aSuccessfulResponseFromController, - failureTestCases: getEntraIdUserFailureTestCases({}), - failureResponse: { - status: HttpStatusCode.BadRequest, - message: 'Error validating payload', - }, - }); - - describe('when the request body is valid', () => { - let validRequest; - - beforeEach(() => { - validRequest = anEntraIdUser(); - }); - - it('calls the user controller', async () => { - const { req, res, next } = getHttpMocks(validRequest); - - await upsertTfmUserFromEntraIdUser(req, res, next); - - expect(jest.mocked(userController.upsertTfmUserFromEntraIdUser)).toHaveBeenCalledTimes(1); - }); - - describe('when the upsert is unsuccessful', () => { - describe('when the error is an api error', () => { - const apiErrorDetails = { - status: 500, - message: 'an api error message', - cause: 'a cause that should not be exposed', - code: 'a code', - }; - - beforeEach(() => { - jest.mocked(userController.upsertTfmUserFromEntraIdUser).mockRejectedValueOnce(new ApiError(apiErrorDetails)); - }); - - it('returns the api error with expected formatting', async () => { - const { req, res, next } = getHttpMocks(validRequest); - const { cause, ...expectedErrorDetails } = apiErrorDetails; - await upsertTfmUserFromEntraIdUser(req, res, next); - - expect(res._getData()).toEqual(expectedErrorDetails); - expect(res._getStatusCode()).toEqual(expectedErrorDetails.status); - }); - }); - - describe('when the error is not an api error', () => { - const errorToThrow = new Error('An Error'); - beforeEach(() => { - jest.mocked(userController.upsertTfmUserFromEntraIdUser).mockRejectedValueOnce(errorToThrow); - }); - - it('passes the error to further middleware', async () => { - const { req, res, next } = getHttpMocks(validRequest); - - await upsertTfmUserFromEntraIdUser(req, res, next); - - expect(next).toHaveBeenCalledWith(errorToThrow); - }); - }); - }); - - describe('when the upsert is successful', () => { - beforeEach(() => { - mockSuccessfulUpsertTfmUserFromEntraIdUser(); - }); - - it('returns the result of the user controller', async () => { - const { req, res, next } = getHttpMocks(validRequest); - - await upsertTfmUserFromEntraIdUser(req, res, next); - - expect(res._getData()).toEqual(aSuccessfulResponseFromController); - }); - }); - }); - - function getHttpMocks(body) { - const { req, res } = httpMocks.createMocks({ - body, - }); - const next = jest.fn(); - return { req, res, next }; - } - - function mockSuccessfulUpsertTfmUserFromEntraIdUser() { - jest.mocked(userController.upsertTfmUserFromEntraIdUser).mockResolvedValueOnce(aSuccessfulResponseFromController); - } - }); -}); From eb16e754438773237347376f81f696e63220ebce Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 6 Dec 2024 16:55:38 +0000 Subject: [PATCH 047/133] feat(dtfs2-6892): update error handling and tests for get auth code --- libs/common/src/types/tfm/get-auth-code.ts | 3 +- .../sso.controller.get-auth-code-url.test.ts | 69 +++++++++---------- .../src/v1/controllers/sso.controller.ts | 46 +++++++++++-- 3 files changed, 75 insertions(+), 43 deletions(-) diff --git a/libs/common/src/types/tfm/get-auth-code.ts b/libs/common/src/types/tfm/get-auth-code.ts index ea03fc59d8..db753a6d3f 100644 --- a/libs/common/src/types/tfm/get-auth-code.ts +++ b/libs/common/src/types/tfm/get-auth-code.ts @@ -1,6 +1,7 @@ import { AuthorizationUrlRequest } from '@azure/msal-node'; import { Response } from 'express'; import { CustomExpressRequest } from '../express-custom-request'; +import { ApiErrorResponseBody } from '../api-error-response-body'; export type GetAuthCodeUrlParams = { successRedirect: string; @@ -15,4 +16,4 @@ export type GetAuthCodeUrlResponse = { export type GetAuthCodeUrlApiRequest = CustomExpressRequest<{ params: GetAuthCodeUrlParams }>; -export type GetAuthCodeUrlApiResponse = Response; +export type GetAuthCodeUrlApiResponse = Response; diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts index 4fbe86b274..7d5356d085 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts @@ -5,6 +5,7 @@ import { GetAuthCodeUrlApiResponse, GetAuthCodeUrlParams, GetAuthCodeUrlResponse, + withApiErrorTests, } from '@ukef/dtfs2-common'; import { EntraIdServiceMockBuilder, UserServiceMockBuilder } from '../__mocks__/builders'; import { EntraIdService } from '../services/entra-id.service'; @@ -18,7 +19,6 @@ describe('SsoController', () => { let ssoController: SsoController; let req: MockRequest; let res: MockResponse; - let next: jest.Mock; const getAuthCodeUrlMock = jest.fn(); @@ -36,50 +36,47 @@ describe('SsoController', () => { ssoController = new SsoController({ entraIdService, userService }); ({ req, res } = getHttpMocks()); - next = jest.fn(); }); - it('should call entraIdService.getAuthCodeUrl with the correct params', async () => { - // Arrange - const expectedGetAuthCodeUrlParams: GetAuthCodeUrlParams = { - successRedirect: aSuccessRedirect, - }; - // Act - await ssoController.getAuthCodeUrl(req, res, next); - - // Assert - expect(getAuthCodeUrlMock).toHaveBeenCalledWith(expectedGetAuthCodeUrlParams); + withApiErrorTests({ + makeRequest: async () => ssoController.getAuthCodeUrl(req, res), + mockAnError: (error: unknown) => getAuthCodeUrlMock.mockRejectedValue(error), + getRes: () => res, + endpointErrorMessage: 'Failed to get auth code url', }); - it('should return the result of entraIdService.getAuthCodeUrl', async () => { - // Arrange - const expectedGetAuthCodeUrlResult: GetAuthCodeUrlResponse = { authCodeUrl: 'a-auth-code-url', authCodeUrlRequest: anAuthorisationCodeRequest() }; - getAuthCodeUrlMock.mockResolvedValue(expectedGetAuthCodeUrlResult); - // Act - await ssoController.getAuthCodeUrl(req, res, next); + describe('when getAuthCodeUrl is successful', () => { + it('should call entraIdService.getAuthCodeUrl with the correct params', async () => { + // Arrange + const expectedGetAuthCodeUrlParams: GetAuthCodeUrlParams = { + successRedirect: aSuccessRedirect, + }; + // Act + await ssoController.getAuthCodeUrl(req, res); - // Assert - expect(res._getJSONData()).toEqual(expectedGetAuthCodeUrlResult); - expect(res._getStatusCode()).toEqual(200); - }); + // Assert + expect(getAuthCodeUrlMock).toHaveBeenCalledWith(expectedGetAuthCodeUrlParams); + }); - it('should return a 200', async () => { - // Act - await ssoController.getAuthCodeUrl(req, res, next); + it('should return the result of entraIdService.getAuthCodeUrl', async () => { + // Arrange + const expectedGetAuthCodeUrlResult: GetAuthCodeUrlResponse = { authCodeUrl: 'a-auth-code-url', authCodeUrlRequest: anAuthorisationCodeRequest() }; + getAuthCodeUrlMock.mockResolvedValue(expectedGetAuthCodeUrlResult); + // Act + await ssoController.getAuthCodeUrl(req, res); - // Assert - expect(res._getStatusCode()).toEqual(200); - }); + // Assert + expect(res._getJSONData()).toEqual(expectedGetAuthCodeUrlResult); + expect(res._getStatusCode()).toEqual(200); + }); - it('should call next with the error if an error is thrown', async () => { - // Arrange - const expectedError = new Error('an-error'); - getAuthCodeUrlMock.mockRejectedValue(expectedError); - // Act - await ssoController.getAuthCodeUrl(req, res, next); + it('should return a 200', async () => { + // Act + await ssoController.getAuthCodeUrl(req, res); - // Assert - expect(next).toHaveBeenCalledWith(expectedError); + // Assert + expect(res._getStatusCode()).toEqual(200); + }); }); }); }); diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts index c4e61ff261..dd68b4fb09 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts @@ -1,5 +1,5 @@ -import { NextFunction } from 'express'; import { + ApiError, GetAuthCodeUrlApiRequest, GetAuthCodeUrlApiResponse, HandleSsoRedirectFormApiRequest, @@ -10,6 +10,7 @@ import { import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA, TFM_SESSION_USER_SCHEMA } from '@ukef/dtfs2-common/schemas'; import { isVerifiedPayload } from '@ukef/dtfs2-common/payload-verification'; import { validateAuditDetailsAndUserType } from '@ukef/dtfs2-common/change-stream'; +import { HttpStatusCode } from 'axios'; import { EntraIdService } from '../services/entra-id.service'; import { UserService } from '../services/user.service'; import utils from '../../utils/crypto.util'; @@ -23,13 +24,27 @@ export class SsoController { this.userService = userService; } - public async getAuthCodeUrl(req: GetAuthCodeUrlApiRequest, res: GetAuthCodeUrlApiResponse, next: NextFunction) { + public async getAuthCodeUrl(req: GetAuthCodeUrlApiRequest, res: GetAuthCodeUrlApiResponse) { try { const { successRedirect } = req.params; const getAuthCodeUrlResponse = await this.entraIdService.getAuthCodeUrl({ successRedirect }); res.json(getAuthCodeUrlResponse); } catch (error) { - next(error); + const errorMessage = 'Failed to get auth code url'; + console.error(errorMessage, error); + + if (error instanceof ApiError) { + res.status(error.status).send({ + status: error.status, + message: `${errorMessage}: ${error.message}`, + code: error.code, + }); + return; + } + res.status(HttpStatusCode.InternalServerError).send({ + status: HttpStatusCode.InternalServerError, + message: errorMessage, + }); } } @@ -40,13 +55,18 @@ export class SsoController { * It takes the response from the Entra Id service and processes it to create or update a user in the TFM-API database. * It then issues a JWT token for the user and returns it to the client. */ - public async handleSsoRedirectForm(req: HandleSsoRedirectFormApiRequest, res: HandleSsoRedirectFormApiResponse, next: NextFunction) { + public async handleSsoRedirectForm(req: HandleSsoRedirectFormApiRequest, res: HandleSsoRedirectFormApiResponse) { try { const { body } = req; const { authCodeResponse, originalAuthCodeUrlRequest, auditDetails } = body; validateAuditDetailsAndUserType(auditDetails, 'system'); - if (!isVerifiedPayload({ payload: body, template: ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA })) { + /** + * We only validate the authCodeResponse here as the originalAuthCodeUrlRequest type (AuthorizationUrlRequest) is + * part of the MSAL auth library, so we allow the MSAL library to handle this for us, and + * we've already validated the auditDetails. + */ + if (!isVerifiedPayload({ payload: authCodeResponse, template: ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA })) { throw new InvalidPayloadError('Invalid payload from SSO redirect'); } @@ -70,7 +90,21 @@ export class SsoController { res.send(response); } catch (error) { - next(error); + const errorMessage = 'Failed to handle redirect form'; + console.error(errorMessage, error); + + if (error instanceof ApiError) { + res.status(error.status).send({ + status: error.status, + message: `${errorMessage}: ${error.message}`, + code: error.code, + }); + } + + res.status(HttpStatusCode.InternalServerError).send({ + status: HttpStatusCode.InternalServerError, + message: errorMessage, + }); } } } From a9280e9058a0315d4099941085c8954c14c47a6b Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 6 Dec 2024 17:00:33 +0000 Subject: [PATCH 048/133] feat(dtfs2-6892): update user service to not be static --- ...rm-entra-id-user-to-tfm-upsert-user-request.test.ts | 4 +++- .../src/v1/services/user.service.ts | 4 ++-- ...user.service.upsert-user-from-entra-id-user.test.ts | 10 ++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/trade-finance-manager-api/src/v1/services/user.service.transform-entra-id-user-to-tfm-upsert-user-request.test.ts b/trade-finance-manager-api/src/v1/services/user.service.transform-entra-id-user-to-tfm-upsert-user-request.test.ts index a9c2a7964d..20bb8cb058 100644 --- a/trade-finance-manager-api/src/v1/services/user.service.transform-entra-id-user-to-tfm-upsert-user-request.test.ts +++ b/trade-finance-manager-api/src/v1/services/user.service.transform-entra-id-user-to-tfm-upsert-user-request.test.ts @@ -4,8 +4,10 @@ import { UserService } from './user.service'; describe('user service', () => { describe('transformEntraIdUserToUpsertTfmUserRequest', () => { + let userService: UserService; beforeAll(() => { jest.useFakeTimers(); + userService = new UserService(); }); afterAll(() => { @@ -15,7 +17,7 @@ describe('user service', () => { it('transforms an entra id user to a tfm upsert user request', () => { const validEntraIdUser = anEntraIdUser(); const expected = ENTRA_ID_USER_TO_UPSERT_TFM_USER_REQUEST_SCHEMA.parse(validEntraIdUser); - const result = UserService.transformEntraIdUserToUpsertTfmUserRequest(validEntraIdUser); + const result = userService.transformEntraIdUserToUpsertTfmUserRequest(validEntraIdUser); expect(result).toEqual(expected); }); diff --git a/trade-finance-manager-api/src/v1/services/user.service.ts b/trade-finance-manager-api/src/v1/services/user.service.ts index dad7843690..93aec6f911 100644 --- a/trade-finance-manager-api/src/v1/services/user.service.ts +++ b/trade-finance-manager-api/src/v1/services/user.service.ts @@ -23,7 +23,7 @@ export class UserService { * @param entraIdUser * @returns The upsert user request */ - private static transformEntraIdUserToUpsertTfmUserRequest(entraIdUser: EntraIdUser): UpsertTfmUserRequest { + public transformEntraIdUserToUpsertTfmUserRequest(entraIdUser: EntraIdUser): UpsertTfmUserRequest { return ENTRA_ID_USER_TO_UPSERT_TFM_USER_REQUEST_SCHEMA.parse(entraIdUser); } @@ -48,7 +48,7 @@ export class UserService { * @throws MultipleUsersFoundError if multiple users are found */ public async upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }: UpsertTfmUserFromEntraIdUserParams): Promise { - const upsertTfmUserRequest = UserService.transformEntraIdUserToUpsertTfmUserRequest(entraIdUser); + const upsertTfmUserRequest = this.transformEntraIdUserToUpsertTfmUserRequest(entraIdUser); const findResult = await UserRepo.findUsersByEmailAddresses([...entraIdUser.verified_primary_email, ...entraIdUser.verified_secondary_email]); let upsertedUser: TfmUser; diff --git a/trade-finance-manager-api/src/v1/services/user.service.upsert-user-from-entra-id-user.test.ts b/trade-finance-manager-api/src/v1/services/user.service.upsert-user-from-entra-id-user.test.ts index 52c8d811b1..06e65f8399 100644 --- a/trade-finance-manager-api/src/v1/services/user.service.upsert-user-from-entra-id-user.test.ts +++ b/trade-finance-manager-api/src/v1/services/user.service.upsert-user-from-entra-id-user.test.ts @@ -22,6 +22,7 @@ describe('user service', () => { let userId: ObjectId; let createUserSpy: jest.SpyInstance; let updateUserByIdSpy: jest.SpyInstance; + let userService: UserService; beforeAll(() => { jest.useFakeTimers(); @@ -31,7 +32,8 @@ describe('user service', () => { jest.resetAllMocks(); entraIdUser = anEntraIdUser(); auditDetails = generateSystemAuditDetails(); - transformedUser = UserService.transformEntraIdUserToUpsertTfmUserRequest(entraIdUser); + userService = new UserService(); + transformedUser = userService.transformEntraIdUserToUpsertTfmUserRequest(entraIdUser); userId = new ObjectId(); existingUser = { ...transformedUser, _id: userId, status: USER.STATUS.ACTIVE }; }); @@ -47,7 +49,7 @@ describe('user service', () => { }); it('creates a new user in the database', async () => { - await UserService.upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }); + await userService.upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }); expect(createUserSpy).toHaveBeenCalledWith({ user: transformedUser, @@ -64,7 +66,7 @@ describe('user service', () => { }); it('updates the user in the database', async () => { - await UserService.upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }); + await userService.upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }); expect(updateUserByIdSpy).toHaveBeenCalledWith({ userId, @@ -81,7 +83,7 @@ describe('user service', () => { }); it('throws an error', async () => { - await expect(UserService.upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails })).rejects.toThrowError(); + await expect(userService.upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails })).rejects.toThrowError(); }); }); }); From bf6056ce22f6ec349d6237ab51b68a4e048d34f5 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 9 Dec 2024 17:08:40 +0000 Subject: [PATCH 049/133] feat(dtfs2-6892): add sso controller tests for handle sso redirect form --- .../mock-builder.mock.builder.ts | 4 +- ....ts => authorisation-code-request.mock.ts} | 0 .../src/test-helpers/mock-data/index.ts | 2 +- .../with-api-error-tests.ts | 15 +- .../src/types/tfm/handle-sso-redirect-form.ts | 3 +- ...ontroller.handle-sso-redirect-form.test.ts | 315 ++++++++++++++++++ .../src/v1/controllers/sso.controller.ts | 2 +- .../src/v1/services/entra-id.service.ts | 3 +- .../test-helpers/handle-redirect.ts | 9 + .../test-helpers/index.ts | 2 + .../upsert-tfm-user-from-entra-id-response.ts | 3 + 11 files changed, 343 insertions(+), 15 deletions(-) rename libs/common/src/test-helpers/mock-data/{authorisation-code-request.ts => authorisation-code-request.mock.ts} (100%) create mode 100644 trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts create mode 100644 trade-finance-manager-api/test-helpers/handle-redirect.ts create mode 100644 trade-finance-manager-api/test-helpers/upsert-tfm-user-from-entra-id-response.ts diff --git a/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts b/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts index d3e0915184..0e74fb97df 100644 --- a/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts +++ b/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts @@ -65,10 +65,10 @@ type Mocked = { * ``` */ export abstract class BaseMockBuilder { - private readonly defaults: Mocked; + private readonly defaults: Partial>; private readonly instance: Mocked = {} as Mocked; - protected constructor(config: { defaultInstance: Mocked }) { + protected constructor(config: { defaultInstance: Partial> }) { this.defaults = config.defaultInstance; } diff --git a/libs/common/src/test-helpers/mock-data/authorisation-code-request.ts b/libs/common/src/test-helpers/mock-data/authorisation-code-request.mock.ts similarity index 100% rename from libs/common/src/test-helpers/mock-data/authorisation-code-request.ts rename to libs/common/src/test-helpers/mock-data/authorisation-code-request.mock.ts diff --git a/libs/common/src/test-helpers/mock-data/index.ts b/libs/common/src/test-helpers/mock-data/index.ts index e61cd18e2b..70660d6a03 100644 --- a/libs/common/src/test-helpers/mock-data/index.ts +++ b/libs/common/src/test-helpers/mock-data/index.ts @@ -1,4 +1,4 @@ -export * from './authorisation-code-request'; +export * from './authorisation-code-request.mock'; export * from './azure-file-info.mock'; export * from './utilisation-report.entity.mock-builder'; export * from './fee-record.entity.mock-builder'; diff --git a/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts b/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts index 9798bbc45f..de3d43f696 100644 --- a/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts +++ b/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts @@ -4,20 +4,17 @@ import { Response } from 'express'; import { API_ERROR_CODE } from '../../constants'; import { TestApiError } from '../test-api-error'; -export const withApiErrorTests = ({ - mockAnError, - makeRequest, - getRes, - endpointErrorMessage, -}: { +type WithApiErrorTestsParams = { mockAnError: (error: unknown) => void; makeRequest: () => Promise; getRes: () => MockResponse; endpointErrorMessage: string; -}) => { +}; + +export const withApiErrorTests = ({ mockAnError, makeRequest, getRes, endpointErrorMessage }: WithApiErrorTestsParams) => { describe('with api error tests', () => { describe('when an api error is thrown', () => { - it('should transform and return the error', async () => { + it('should return the errors status code with error details', async () => { // Arrange const errorStatus = HttpStatusCode.BadRequest; const errorMessage = 'a message that should not be exposed'; @@ -41,7 +38,7 @@ export const withApiErrorTests = ({ }); describe('when a non-api error is thrown', () => { - it('should return 500 with a generic error message', async () => { + it('should return a 500 with a generic error message', async () => { // Arrange const errorMessage = 'a message that should not be exposed'; diff --git a/libs/common/src/types/tfm/handle-sso-redirect-form.ts b/libs/common/src/types/tfm/handle-sso-redirect-form.ts index 71159de4a5..989dc4f76d 100644 --- a/libs/common/src/types/tfm/handle-sso-redirect-form.ts +++ b/libs/common/src/types/tfm/handle-sso-redirect-form.ts @@ -4,6 +4,7 @@ import { EntraIdAuthCodeRedirectResponseBody } from './entra-id'; import { TfmSessionUser } from './tfm-session-user'; import { AuditDetails } from '../audit-details'; import { CustomExpressRequest } from '../express-custom-request'; +import { ApiErrorResponseBody } from '../api-error-response-body'; export type HandleSsoRedirectFormRequest = { authCodeResponse: EntraIdAuthCodeRedirectResponseBody; @@ -22,4 +23,4 @@ export type HandleSsoRedirectFormUiRequest = CustomExpressRequest<{ reqBody: Ent export type HandleSsoRedirectFormApiRequest = CustomExpressRequest<{ reqBody: HandleSsoRedirectFormRequest }>; -export type HandleSsoRedirectFormApiResponse = Response; +export type HandleSsoRedirectFormApiResponse = Response; diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts new file mode 100644 index 0000000000..15a4f9af09 --- /dev/null +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts @@ -0,0 +1,315 @@ +import httpMocks, { MockRequest, MockResponse } from 'node-mocks-http'; +import { + anAuthorisationCodeRequest, + anEntraIdAuthCodeRedirectResponseBody, + AuditDetails, + HandleSsoRedirectFormApiRequest, + HandleSsoRedirectFormApiResponse, + HandleSsoRedirectFormRequest, + withApiErrorTests, +} from '@ukef/dtfs2-common'; +import { generateSystemAuditDetails, generateTfmAuditDetails } from '@ukef/dtfs2-common/change-stream'; +import { ObjectId } from 'mongodb'; +import { TFM_SESSION_USER_SCHEMA } from '@ukef/dtfs2-common/schemas'; +import { aHandleRedirectResponse, anUpsertTfmUserFromEntraIdUserResponse } from '../../../test-helpers'; +import { EntraIdServiceMockBuilder, UserServiceMockBuilder } from '../__mocks__/builders'; +import { EntraIdService, HandleRedirectResponse } from '../services/entra-id.service'; +import { UpsertTfmUserFromEntraIdUserResponse, UserService } from '../services/user.service'; +import { SsoController } from './sso.controller'; +import utils from '../../utils/crypto.util'; + +describe('SsoController', () => { + describe('handleSsoRedirectForm', () => { + let entraIdService: EntraIdService; + let userService: UserService; + let ssoController: SsoController; + let req: MockRequest; + let res: MockResponse; + + const handleRedirectMock = jest.fn(); + const upsertTfmUserFromEntraIdUserMock = jest.fn(); + const saveUserLoginInformationMock = jest.fn(); + + /** + * This is to allow us to assert the response from issuing the JWT, as + * without mocking, the function is not idempotent and will return different values + */ + const issueJWTMock = jest.fn(); + + beforeEach(() => { + jest.resetAllMocks(); + entraIdService = new EntraIdServiceMockBuilder().withDefaults().with({ handleRedirect: handleRedirectMock }).build(); + userService = new UserServiceMockBuilder() + .withDefaults() + .with({ upsertTfmUserFromEntraIdUser: upsertTfmUserFromEntraIdUserMock, saveUserLoginInformation: saveUserLoginInformationMock }) + .build(); + ssoController = new SsoController({ entraIdService, userService }); + + utils.issueJWT = issueJWTMock; + }); + + describe('when the audit details are invalid', () => { + describe('when the audit details are in an incorrect format', () => { + beforeEach(() => { + const incorrectTypeOfAuditDetails = generateTfmAuditDetails(new ObjectId()) as unknown as AuditDetails<'system'>; + ({ req, res } = getHttpMocksWithBody({ + ...aValidRequest(), + auditDetails: incorrectTypeOfAuditDetails, + })); + }); + + it('should return a 400 with error details', async () => { + // Act + await ssoController.handleSsoRedirectForm(req, res); + + // Assert + expect(res._getStatusCode()).toEqual(400); + expect(res._getData()).toEqual({ + status: 400, + message: "Failed to handle redirect form: Supplied auditDetails 'userType' must be 'system' (was 'tfm')", + code: 'INVALID_AUDIT_DETAILS', + }); + }); + }); + + describe('when the audit details are not valid', () => { + beforeEach(() => { + const invalidAuditDetails = { invalidAuditDetailParam: 'invalid field' } as unknown as AuditDetails<'system'>; + ({ req, res } = getHttpMocksWithBody({ + ...aValidRequest(), + auditDetails: invalidAuditDetails, + })); + }); + + it('should return a 400 with error details', async () => { + // Act + await ssoController.handleSsoRedirectForm(req, res); + + // Assert + expect(res._getStatusCode()).toEqual(400); + expect(res._getData()).toEqual({ + status: 400, + message: "Failed to handle redirect form: Supplied auditDetails must contain a 'userType' property", + code: 'INVALID_AUDIT_DETAILS', + }); + }); + }); + }); + + describe('when the audit details are valid', () => { + describe('when the payload is invalid', () => { + beforeEach(() => { + const invalidPayload = { invalidPayloadParam: 'invalid field' }; + ({ req, res } = getHttpMocksWithBody({ + ...aValidRequest(), + authCodeResponse: invalidPayload as unknown as HandleSsoRedirectFormRequest['authCodeResponse'], + })); + }); + + it('should return a 400 with error details', async () => { + // Act + await ssoController.handleSsoRedirectForm(req, res); + + // Assert + expect(res._getStatusCode()).toEqual(400); + expect(res._getData()).toEqual({ + status: 400, + message: 'Failed to handle redirect form: Invalid payload from SSO redirect', + }); + }); + }); + }); + + describe('when the payload is valid', () => { + beforeEach(() => { + ({ req, res } = getHttpMocksWithBody({ + ...aValidRequest(), + })); + }); + + it('should call handleRedirect with the correct params', async () => { + // Act + await ssoController.handleSsoRedirectForm(req, res); + + // Assert + expect(handleRedirectMock).toHaveBeenCalledWith({ + authCodeResponse: req.body.authCodeResponse, + originalAuthCodeUrlRequest: req.body.originalAuthCodeUrlRequest, + }); + }); + + it('should call handleRedirect once', async () => { + // Act + await ssoController.handleSsoRedirectForm(req, res); + + // Assert + expect(handleRedirectMock).toHaveBeenCalledTimes(1); + }); + + describe('when handling the redirect is unsuccessful', () => { + withApiErrorTests({ + makeRequest: async () => await ssoController.handleSsoRedirectForm(req, res), + mockAnError: (error: unknown) => handleRedirectMock.mockRejectedValue(error), + getRes: () => res, + endpointErrorMessage: 'Failed to handle redirect form', + }); + }); + + describe('when handling the redirect is successful', () => { + let handleRedirectResponse: HandleRedirectResponse; + + beforeEach(() => { + handleRedirectResponse = aHandleRedirectResponse(); + handleRedirectMock.mockResolvedValue(handleRedirectResponse); + }); + + it('should call upsertTfmUserFromEntraIdUser with the correct params', async () => { + // Act + await ssoController.handleSsoRedirectForm(req, res); + + // Assert + expect(upsertTfmUserFromEntraIdUserMock).toHaveBeenCalledWith({ + entraIdUser: handleRedirectResponse.entraIdUser, + auditDetails: req.body.auditDetails, + }); + }); + + it('should call upsertTfmUserFromEntraIdUser once', async () => { + // Act + await ssoController.handleSsoRedirectForm(req, res); + + // Assert + expect(upsertTfmUserFromEntraIdUserMock).toHaveBeenCalledTimes(1); + }); + + describe('when upserting the user is unsuccessful', () => { + withApiErrorTests({ + makeRequest: async () => { + ({ req, res } = getHttpMocksWithBody({ + ...aValidRequest(), + })); + return await ssoController.handleSsoRedirectForm(req, res); + }, + mockAnError: (error: unknown) => upsertTfmUserFromEntraIdUserMock.mockRejectedValue(error), + getRes: () => res, + endpointErrorMessage: 'Failed to handle redirect form', + }); + }); + + describe('when upserting the user is successful', () => { + let upsertTfmUserFromEntraIdUserResponse: UpsertTfmUserFromEntraIdUserResponse; + + beforeEach(() => { + upsertTfmUserFromEntraIdUserResponse = anUpsertTfmUserFromEntraIdUserResponse(); + upsertTfmUserFromEntraIdUserMock.mockResolvedValue(upsertTfmUserFromEntraIdUserResponse); + }); + + it('should call issueJWT with the correct params', async () => { + // Act + await ssoController.handleSsoRedirectForm(req, res); + + // Assert + expect(issueJWTMock).toHaveBeenCalledWith(upsertTfmUserFromEntraIdUserResponse); + }); + + it('should call issueJWT once', async () => { + // Act + await ssoController.handleSsoRedirectForm(req, res); + + // Assert + expect(issueJWTMock).toHaveBeenCalledTimes(1); + }); + + describe('when issuing the JWT is successful', () => { + let issueJWTMockResponse: { + token: string; + expires: string; + sessionIdentifier: string; + }; + + beforeEach(() => { + issueJWTMockResponse = { + token: 'a-token', + expires: 'a-expires', + sessionIdentifier: 'a-session-identifier', + }; + + issueJWTMock.mockReturnValue(issueJWTMockResponse); + }); + + it('should call saveUserLoginInformation with the correct params', async () => { + // Act + await ssoController.handleSsoRedirectForm(req, res); + + // Assert + expect(saveUserLoginInformationMock).toHaveBeenCalledWith({ + userId: upsertTfmUserFromEntraIdUserResponse._id, + sessionIdentifier: issueJWTMockResponse.sessionIdentifier, + auditDetails: req.body.auditDetails, + }); + }); + + it('should call saveUserLoginInformation once', async () => { + // Act + await ssoController.handleSsoRedirectForm(req, res); + + // Assert + expect(saveUserLoginInformationMock).toHaveBeenCalledTimes(1); + }); + + describe('when saving the user login information is unsuccessful', () => { + withApiErrorTests({ + makeRequest: async () => { + ({ req, res } = getHttpMocksWithBody({ + ...aValidRequest(), + })); + return await ssoController.handleSsoRedirectForm(req, res); + }, + mockAnError: (error: unknown) => saveUserLoginInformationMock.mockRejectedValue(error), + getRes: () => res, + endpointErrorMessage: 'Failed to handle redirect form', + }); + }); + + describe('when saving the user login information is successful', () => { + beforeEach(() => { + saveUserLoginInformationMock.mockResolvedValue(undefined); + }); + + it('should return a 200 with the user, token, expires and successRedirect', async () => { + // Arrange + const expectedResponse = { + user: TFM_SESSION_USER_SCHEMA.parse(upsertTfmUserFromEntraIdUserResponse), + token: issueJWTMockResponse.token, + expires: issueJWTMockResponse.expires, + successRedirect: handleRedirectResponse.successRedirect, + }; + + // Act + await ssoController.handleSsoRedirectForm(req, res); + + // Assert + expect(res._getStatusCode()).toEqual(200); + expect(res._getData()).toEqual(expectedResponse); + }); + }); + }); + }); + }); + }); + }); + + function getHttpMocksWithBody(body: HandleSsoRedirectFormRequest) { + return httpMocks.createMocks({ + body, + }); + } + + function aValidRequest(): HandleSsoRedirectFormRequest { + return { + authCodeResponse: anEntraIdAuthCodeRedirectResponseBody(), + originalAuthCodeUrlRequest: anAuthorisationCodeRequest(), + auditDetails: generateSystemAuditDetails(), + }; + } +}); diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts index dd68b4fb09..d1cfa2871e 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts @@ -92,13 +92,13 @@ export class SsoController { } catch (error) { const errorMessage = 'Failed to handle redirect form'; console.error(errorMessage, error); - if (error instanceof ApiError) { res.status(error.status).send({ status: error.status, message: `${errorMessage}: ${error.message}`, code: error.code, }); + return; } res.status(HttpStatusCode.InternalServerError).send({ diff --git a/trade-finance-manager-api/src/v1/services/entra-id.service.ts b/trade-finance-manager-api/src/v1/services/entra-id.service.ts index a80b99802d..d5ea927014 100644 --- a/trade-finance-manager-api/src/v1/services/entra-id.service.ts +++ b/trade-finance-manager-api/src/v1/services/entra-id.service.ts @@ -8,12 +8,13 @@ import { EntraIdApi } from '../third-party-apis/entra-id.api'; export type GetAuthCodeUrlParams = { successRedirect?: string; }; + type HandleRedirectParams = { authCodeResponse: EntraIdAuthCodeRedirectResponseBody; originalAuthCodeUrlRequest?: AuthorizationUrlRequest; }; -type HandleRedirectResponse = { +export type HandleRedirectResponse = { entraIdUser: EntraIdUser; successRedirect?: string; }; diff --git a/trade-finance-manager-api/test-helpers/handle-redirect.ts b/trade-finance-manager-api/test-helpers/handle-redirect.ts new file mode 100644 index 0000000000..89906e3eb2 --- /dev/null +++ b/trade-finance-manager-api/test-helpers/handle-redirect.ts @@ -0,0 +1,9 @@ +import { anEntraIdUser } from '@ukef/dtfs2-common'; +import { HandleRedirectResponse } from '../src/v1/services/entra-id.service'; + +export const aHandleRedirectResponse = (): HandleRedirectResponse => { + return { + entraIdUser: anEntraIdUser(), + successRedirect: 'a-success-redirect-url', + }; +}; diff --git a/trade-finance-manager-api/test-helpers/index.ts b/trade-finance-manager-api/test-helpers/index.ts index 43f5f40bd6..3fda48d862 100644 --- a/trade-finance-manager-api/test-helpers/index.ts +++ b/trade-finance-manager-api/test-helpers/index.ts @@ -3,3 +3,5 @@ export * from './tfm-session-user'; export * from './payment'; export * from './with-validate-payload.tests'; export * from './user.service.upsert-user-from-entraId-user-response'; +export * from './handle-redirect'; +export * from './upsert-tfm-user-from-entra-id-response'; diff --git a/trade-finance-manager-api/test-helpers/upsert-tfm-user-from-entra-id-response.ts b/trade-finance-manager-api/test-helpers/upsert-tfm-user-from-entra-id-response.ts new file mode 100644 index 0000000000..818373ea87 --- /dev/null +++ b/trade-finance-manager-api/test-helpers/upsert-tfm-user-from-entra-id-response.ts @@ -0,0 +1,3 @@ +import { aTfmUser } from '@ukef/dtfs2-common/mock-data-backend'; + +export const anUpsertTfmUserFromEntraIdUserResponse = () => aTfmUser(); From 451379400725a6d343723e9e8165f8e36e7103ae Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 9 Dec 2024 18:20:33 +0000 Subject: [PATCH 050/133] feat(dtfs2-6892): add tests --- .../sso.controller.get-auth-code-url.test.ts | 8 +++ ...ervice.save-user-login-information.test.ts | 54 +++++++++++++++++++ .../src/v1/services/user.service.ts | 5 ++ .../src/v1/sso/routes.ts | 3 -- 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 trade-finance-manager-api/src/v1/services/user.service.save-user-login-information.test.ts diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts index 7d5356d085..74bb4ba60c 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts @@ -58,6 +58,14 @@ describe('SsoController', () => { expect(getAuthCodeUrlMock).toHaveBeenCalledWith(expectedGetAuthCodeUrlParams); }); + it('should call entraIdService.getAuthCodeUrl once', async () => { + // Act + await ssoController.getAuthCodeUrl(req, res); + + // Assert + expect(getAuthCodeUrlMock).toHaveBeenCalledTimes(1); + }); + it('should return the result of entraIdService.getAuthCodeUrl', async () => { // Arrange const expectedGetAuthCodeUrlResult: GetAuthCodeUrlResponse = { authCodeUrl: 'a-auth-code-url', authCodeUrlRequest: anAuthorisationCodeRequest() }; diff --git a/trade-finance-manager-api/src/v1/services/user.service.save-user-login-information.test.ts b/trade-finance-manager-api/src/v1/services/user.service.save-user-login-information.test.ts new file mode 100644 index 0000000000..c22dc35863 --- /dev/null +++ b/trade-finance-manager-api/src/v1/services/user.service.save-user-login-information.test.ts @@ -0,0 +1,54 @@ +import { AuditDetails } from '@ukef/dtfs2-common'; +import { generateSystemAuditDetails } from '@ukef/dtfs2-common/change-stream'; +import { ObjectId } from 'mongodb'; +import { UserService } from './user.service'; +import { UserRepo } from '../repo/user.repo'; + +jest.mock('../repo/user.repo.ts', () => ({ + UserRepo: { + updateUserById: jest.fn(), + }, +})); + +describe('user service', () => { + describe('saveUserLoginInformation', () => { + let auditDetails: AuditDetails; + let userId: ObjectId; + let sessionIdentifier: string; + let updateUserByIdSpy: jest.SpyInstance; + let userService: UserService; + + beforeAll(() => { + jest.useFakeTimers(); + }); + + beforeEach(() => { + jest.resetAllMocks(); + auditDetails = generateSystemAuditDetails(); + userService = new UserService(); + userId = new ObjectId(); + sessionIdentifier = 'a-session-identifier'; + + updateUserByIdSpy = jest.spyOn(UserRepo, 'updateUserById'); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('should update the users last login time and session identifier in the database', async () => { + await userService.saveUserLoginInformation({ userId, sessionIdentifier, auditDetails }); + + expect(updateUserByIdSpy).toHaveBeenCalledWith({ + userId, + userUpdate: { + lastLogin: Date.now(), + sessionIdentifier, + }, + auditDetails, + }); + + expect(updateUserByIdSpy).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/trade-finance-manager-api/src/v1/services/user.service.ts b/trade-finance-manager-api/src/v1/services/user.service.ts index 93aec6f911..aa264ffe8c 100644 --- a/trade-finance-manager-api/src/v1/services/user.service.ts +++ b/trade-finance-manager-api/src/v1/services/user.service.ts @@ -16,6 +16,11 @@ export type saveUserLoginInformationParams = { export type UpsertTfmUserFromEntraIdUserResponse = TfmUser; +/** + * User service, primarily used for SSO. + * Note: User repo is not dependency injected as a constructor as it may be used in non-DI code + * DI code is only used for SSO due to existing documentation and other implementations + */ export class UserService { /** * Used as part of the SSO process diff --git a/trade-finance-manager-api/src/v1/sso/routes.ts b/trade-finance-manager-api/src/v1/sso/routes.ts index 36908d8f13..63ae636c79 100644 --- a/trade-finance-manager-api/src/v1/sso/routes.ts +++ b/trade-finance-manager-api/src/v1/sso/routes.ts @@ -8,9 +8,6 @@ import { UserService } from '../services/user.service'; export const ssoOpenRouter = express.Router(); -// TODO -- Update this to have passport control -export const ssoPartialLoginRouter = express.Router(); - const entraIdConfig = new EntraIdConfig(); const entraIdApi = new EntraIdApi({ entraIdConfig }); const entraIdService = new EntraIdService({ entraIdConfig, entraIdApi }); From 466f33ad84b4f8a5856d415a28685c4be1542deb Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 9 Dec 2024 18:38:40 +0000 Subject: [PATCH 051/133] feat(dtfs2-6892): remove tfm ui changes --- trade-finance-manager-ui/server/api.js | 24 --- trade-finance-manager-ui/server/api.test.js | 157 ++++++++++++++++++ .../login/login-sso/login.controller.ts | 29 ---- .../server/helpers/express-session.ts | 20 +-- .../server/routes/auth/configs/index.ts | 3 - .../server/routes/auth/index.ts | 3 +- .../server/services/login.service.ts | 30 +--- .../server/types/express-session.d.ts | 10 +- 8 files changed, 165 insertions(+), 111 deletions(-) create mode 100644 trade-finance-manager-ui/server/api.test.js diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index 2693eb288a..e1a50b1457 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -421,29 +421,6 @@ const login = async (username, password) => { } }; -/** - * - * @param {import('@ukef/dtfs2-common').HandleSsoRedirectFormRequest} handleSsoRedirectFormRequest - * @returns {Promise} - */ -const handleSsoRedirectForm = async (handleSsoRedirectFormRequest) => { - try { - const response = await axios({ - method: 'post', - url: `${TFM_API_URL}/v1/sso/handle-sso-redirect-form`, - headers: { - [HEADERS.CONTENT_TYPE.KEY]: HEADERS.CONTENT_TYPE.VALUES.JSON, - }, - data: handleSsoRedirectFormRequest, - }); - - return response.data; - } catch (error) { - console.error('Unable to log in %o', error?.response?.data); - return { status: error?.response?.status || 500, data: 'Failed to login' }; - } -}; - /** * Gets the auth code URL for the SSO login process * @param {import('@ukef/dtfs2-common').GetAuthCodeUrlRequest} getAuthCodeUrlParams @@ -1484,7 +1461,6 @@ module.exports = { updateLeadUnderwriter, createActivity, login, - handleSsoRedirectForm, getAuthCodeUrl, getFacilities, createFeedback, diff --git a/trade-finance-manager-ui/server/api.test.js b/trade-finance-manager-ui/server/api.test.js new file mode 100644 index 0000000000..2809d185c9 --- /dev/null +++ b/trade-finance-manager-ui/server/api.test.js @@ -0,0 +1,157 @@ +const axios = require('axios'); +const MockAdapter = require('axios-mock-adapter'); +const { HEADERS } = require('@ukef/dtfs2-common'); +const api = require('./api'); +const { MOCK_BANK_HOLIDAYS } = require('./test-mocks/mock-bank-holidays'); +const { getUkBankHolidays } = require('./api'); +const PageOutOfBoundsError = require('./errors/page-out-of-bounds.error'); + +const mockAxios = new MockAdapter(axios); + +console.error = jest.fn(); + +const { TFM_API_URL, TFM_API_KEY } = process.env; + +afterEach(() => { + jest.clearAllMocks(); + mockAxios.reset(); +}); + +describe('getDeals()', () => { + const dealsUrl = `${TFM_API_URL}/v1/deals`; + + const token = 'testToken'; + const headers = { + Authorization: token, + [HEADERS.CONTENT_TYPE.KEY]: HEADERS.CONTENT_TYPE.VALUES.JSON, + 'x-api-key': TFM_API_KEY, + }; + + const mockResponse = { + deals: [ + { _id: 1, name: 'Deal 1' }, + { _id: 2, name: 'Deal 2' }, + ], + pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, + }; + + it('should return deals data and pagination metadata when TFM API returns this data', async () => { + const queryParams = { page: 0 }; + + mockAxios + .onGet( + dealsUrl, + { params: queryParams }, + expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` + ) + .reply(200, mockResponse); + + const response = await api.getDeals(queryParams, token); + + expect(mockAxios.history.get.length).toEqual(1); + expect(response).toEqual({ + deals: [ + { _id: 1, name: 'Deal 1' }, + { _id: 2, name: 'Deal 2' }, + ], + pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, + }); + }); + + it('should throw a PageOutOfBoundsError when the requested page number exceeds the maximum page number', async () => { + const queryParams = { page: 1 }; + + mockAxios + .onGet( + dealsUrl, + { params: queryParams }, + expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` + ) + .reply(200, mockResponse); + + const errorResponse = api.getDeals(queryParams, token); + + expect(mockAxios.history.get.length).toEqual(1); + await expect(errorResponse).rejects.toThrow(new PageOutOfBoundsError('Requested page number exceeds the maximum page number')); + }); +}); + +describe('getFacilities()', () => { + const facilitiesUrl = `${TFM_API_URL}/v1/facilities`; + + const token = 'testToken'; + const headers = { + Authorization: token, + [HEADERS.CONTENT_TYPE.KEY]: HEADERS.CONTENT_TYPE.VALUES.JSON, + 'x-api-key': TFM_API_KEY, + }; + + const mockResponse = { + facilities: [ + { facilityId: 1, name: 'Facility 1' }, + { facilityId: 2, name: 'Facility 2' }, + ], + pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, + }; + + it('should return facilities data and pagination metadata when TFM API returns this data', async () => { + const queryParams = { page: 0 }; + + mockAxios + .onGet( + facilitiesUrl, + { params: queryParams }, + expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` + ) + .reply(200, mockResponse); + + const response = await api.getFacilities(queryParams, token); + + expect(mockAxios.history.get.length).toEqual(1); + expect(response).toEqual({ + facilities: [ + { facilityId: 1, name: 'Facility 1' }, + { facilityId: 2, name: 'Facility 2' }, + ], + pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, + }); + }); + + it('should throw a PageOutOfBoundsError when the requested page number exceeds the maximum page number', async () => { + const queryParams = { page: 1 }; + + mockAxios + .onGet( + facilitiesUrl, + { params: queryParams }, + expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` + ) + .reply(200, mockResponse); + + const errorResponse = api.getFacilities(queryParams, token); + + expect(mockAxios.history.get.length).toEqual(1); + await expect(errorResponse).rejects.toThrow(new PageOutOfBoundsError('Requested page number exceeds the maximum page number')); + }); +}); + +describe('getUkBankHolidays', () => { + it('gets the bank holidays', async () => { + // Arrange + mockAxios.onGet().reply(200, MOCK_BANK_HOLIDAYS); + + // Act + const response = await getUkBankHolidays('user-token'); + + // Assert + expect(response).toEqual(MOCK_BANK_HOLIDAYS); + }); + + it('throws when the api TFM API request fails', async () => { + // Arrange + mockAxios.onGet().reply(404); + + // Act / Assert + await expect(getUkBankHolidays('user-token')).rejects.toThrowError('Request failed with status code 404'); + }); +}); diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts index 0a5973589b..6a7c0c5897 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts @@ -1,9 +1,4 @@ import { NextFunction, Request, Response } from 'express'; -import { HandleSsoRedirectFormUiRequest, InvalidPayloadError } from '@ukef/dtfs2-common'; -import { isVerifiedPayload } from '@ukef/dtfs2-common/payload-verification'; -import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from '@ukef/dtfs2-common/schemas'; -import { generateSystemAuditDetails } from '@ukef/dtfs2-common/change-stream'; -import { asPartiallyLoggedInUserSession } from '../../../helpers/express-session'; import { LoginService } from '../../../services/login.service'; export class LoginController { @@ -15,7 +10,6 @@ export class LoginController { public async getLogin(req: Request, res: Response, next: NextFunction) { try { - // TODO: This validation is legacy code, and can be improved if (req.session.user) { // User is already logged in. return res.redirect('/home'); @@ -33,29 +27,6 @@ export class LoginController { } } - async handleSsoRedirectForm(req: HandleSsoRedirectFormUiRequest, res: Response, next: NextFunction) { - try { - const { body, session: partiallyLoggedInSession } = req; - const session = asPartiallyLoggedInUserSession(partiallyLoggedInSession); - const auditDetails = generateSystemAuditDetails(); - - if (!isVerifiedPayload({ payload: body, template: ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA })) { - throw new InvalidPayloadError('Invalid payload from SSO redirect'); - } - - const { successRedirect } = await this.loginService.handleSsoRedirectFormAndCreateToken({ - authCodeResponse: body, - originalAuthCodeUrlRequest: session.loginData.authCodeUrlRequest, - session, - auditDetails, - }); - - return res.redirect(successRedirect ?? '/'); - } catch (error) { - return next(error); - } - } - // TODO DTFS2-6892: Update this logout handling public getLogout = (req: Request, res: Response) => { req.session.destroy(() => { diff --git a/trade-finance-manager-ui/server/helpers/express-session.ts b/trade-finance-manager-ui/server/helpers/express-session.ts index 39264e9715..d554771c39 100644 --- a/trade-finance-manager-ui/server/helpers/express-session.ts +++ b/trade-finance-manager-ui/server/helpers/express-session.ts @@ -1,12 +1,11 @@ import { Request } from 'express'; -import { UserPartialLoginDataNotDefinedError, UserSessionNotDefinedError, UserTokenNotDefinedError } from '@ukef/dtfs2-common'; -import { PartiallyLoggedInUserSessionData, UserSessionData } from '../types/express-session'; +import { UserSessionNotDefinedError, UserTokenNotDefinedError } from '@ukef/dtfs2-common'; +import { UserSessionData } from '../types/express-session'; type Session = Request['session']; -export type UserSession = Session & UserSessionData; +type UserSession = Session & UserSessionData; -type PartiallyLoggedInUserSession = Session & PartiallyLoggedInUserSessionData; /** * By default, all session data will be optional * (see use of `Partial` {@link https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express-session/index.d.ts#L17 here}) @@ -31,16 +30,3 @@ export const asUserSession = (session: Session): UserSession => { return Object.assign(session, { user, userToken }); }; - -export const assertPartiallyLoggedInUser: (session: Session) => asserts session is PartiallyLoggedInUserSession = (session: Session) => { - const { loginData } = session; - - if (!loginData) { - throw new UserPartialLoginDataNotDefinedError(); - } -}; - -export const asPartiallyLoggedInUserSession = (session: Session): PartiallyLoggedInUserSession => { - assertPartiallyLoggedInUser(session); - return session; -}; diff --git a/trade-finance-manager-ui/server/routes/auth/configs/index.ts b/trade-finance-manager-ui/server/routes/auth/configs/index.ts index bf3802fe64..2595c42b06 100644 --- a/trade-finance-manager-ui/server/routes/auth/configs/index.ts +++ b/trade-finance-manager-ui/server/routes/auth/configs/index.ts @@ -1,7 +1,4 @@ import { isTfmSsoFeatureFlagEnabled } from '@ukef/dtfs2-common'; -import { getAuthSsoRouter } from './auth-sso'; import { getUnauthenticatedAuthSsoRouter } from './unauthenticated-auth-sso'; -export const getAuthRouter = () => (isTfmSsoFeatureFlagEnabled() ? getAuthSsoRouter() : undefined); - export const getUnauthenticatedAuthRouter = () => (isTfmSsoFeatureFlagEnabled() ? getUnauthenticatedAuthSsoRouter() : undefined); diff --git a/trade-finance-manager-ui/server/routes/auth/index.ts b/trade-finance-manager-ui/server/routes/auth/index.ts index 6d31c2f430..c52e3ef8bd 100644 --- a/trade-finance-manager-ui/server/routes/auth/index.ts +++ b/trade-finance-manager-ui/server/routes/auth/index.ts @@ -1,4 +1,3 @@ -import { getAuthRouter, getUnauthenticatedAuthRouter } from './configs'; +import { getUnauthenticatedAuthRouter } from './configs'; -export const authRoutes = getAuthRouter(); export const unauthenticatedAuthRoutes = getUnauthenticatedAuthRouter(); diff --git a/trade-finance-manager-ui/server/services/login.service.ts b/trade-finance-manager-ui/server/services/login.service.ts index 75fd431ac6..dc3ef1f71c 100644 --- a/trade-finance-manager-ui/server/services/login.service.ts +++ b/trade-finance-manager-ui/server/services/login.service.ts @@ -1,13 +1,6 @@ -import { GetAuthCodeUrlParams, GetAuthCodeUrlResponse, HandleSsoRedirectFormRequest } from '@ukef/dtfs2-common'; -import { PartiallyLoggedInUserSessionData } from '../types/express-session'; +import { GetAuthCodeUrlParams, GetAuthCodeUrlResponse } from '@ukef/dtfs2-common'; import * as api from '../api'; -type HandleSsoRedirectFormAndCreateTokenRequest = { - session: PartiallyLoggedInUserSessionData; -} & HandleSsoRedirectFormRequest; - -type HandleSsoRedirectFormAndCreateTokenResponse = { successRedirect?: string }; - export class LoginService { /** * Gets the URL to redirect the user to in order to log in. @@ -15,25 +8,4 @@ export class LoginService { public getAuthCodeUrl = async ({ successRedirect }: GetAuthCodeUrlParams): Promise => { return api.getAuthCodeUrl({ successRedirect }); }; - - public handleSsoRedirectFormAndCreateToken = async ({ - authCodeResponse, - originalAuthCodeUrlRequest, - // TODO as part of this ticket: uncomment the session parameter - // eslint-disable-next-line @typescript-eslint/no-unused-vars - session, - auditDetails, - }: HandleSsoRedirectFormAndCreateTokenRequest): Promise => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { user, userToken, successRedirect } = await api.handleSsoRedirectForm({ authCodeResponse, originalAuthCodeUrlRequest, auditDetails }); - - // TODO: This can be moved into a separate session service - // delete session.loginData; - // session.user = user; - // session.userToken = userToken; - - return { successRedirect }; - }; - - public getSuccessRedirect = () => {}; } diff --git a/trade-finance-manager-ui/server/types/express-session.d.ts b/trade-finance-manager-ui/server/types/express-session.d.ts index 501d67e167..17cb5aec3d 100644 --- a/trade-finance-manager-ui/server/types/express-session.d.ts +++ b/trade-finance-manager-ui/server/types/express-session.d.ts @@ -1,25 +1,21 @@ -import { AuthorizationUrlRequest } from '@azure/msal-node'; import { TfmSessionUser } from '@ukef/dtfs2-common'; import { RemoveFeesFromPaymentErrorKey } from '../controllers/utilisation-reports/helpers'; import { EditPaymentFormValues } from './edit-payment-form-values'; import { AddPaymentErrorKey, InitiateRecordCorrectionRequestErrorKey, GenerateKeyingDataErrorKey } from './premium-payments-tab-error-keys'; -export type UserSessionLoginData = { +type UserSessionLoginData = { authCodeUrlRequest: AuthorizationUrlRequest; }; -export type PartiallyLoggedInUserSessionData = { - loginData: UserSessionLoginData; -}; - export type UserSessionData = { user: TfmSessionUser; userToken: string; + loginData?: UserSessionLoginData; }; // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express-session/index.d.ts#L199-L211 declare module 'express-session' { - interface SessionData extends UserSessionData, PartiallyLoggedInUserSessionData { + interface SessionData extends UserSessionData { addPaymentErrorKey: AddPaymentErrorKey; initiateRecordCorrectionRequestErrorKey: InitiateRecordCorrectionRequestErrorKey; checkedCheckboxIds: Record; From 335708af7bb509949f778e2c2a268dfc29da2cd8 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 9 Dec 2024 18:51:28 +0000 Subject: [PATCH 052/133] feat(dtfs2-6892): remove tfm ui changes --- .../server/routes/auth/configs/auth-sso.ts | 15 -------- .../configs/index.get-auth-router.test.ts | 35 ------------------- 2 files changed, 50 deletions(-) delete mode 100644 trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts delete mode 100644 trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts diff --git a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts deleted file mode 100644 index f153705f39..0000000000 --- a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts +++ /dev/null @@ -1,15 +0,0 @@ -import express from 'express'; -import { GetRouter } from '../../../types/get-router'; -import { LoginController } from '../../../controllers/login/login-sso/login.controller'; -import { LoginService } from '../../../services/login.service'; - -export const getAuthSsoRouter: GetRouter = () => { - const loginService = new LoginService(); - const loginController = new LoginController({ loginService }); - const authSsoRouter = express.Router(); - - // Todo: update this to check the right token - // eslint-disable-next-line @typescript-eslint/no-misused-promises - authSsoRouter.post('/auth/sso-redirect/form', loginController.handleSsoRedirectForm.bind(loginController)); - return authSsoRouter; -}; diff --git a/trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts b/trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts deleted file mode 100644 index dc34246597..0000000000 --- a/trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { isTfmSsoFeatureFlagEnabled } from '@ukef/dtfs2-common'; -import { Router } from 'express'; -import { getAuthRouter } from '.'; -import { getAuthSsoRouter } from './auth-sso'; - -jest.mock('@ukef/dtfs2-common', () => ({ - isTfmSsoFeatureFlagEnabled: jest.fn(), -})); - -jest.mock('./authSso', () => ({ - getAuthSsoRouter: jest.fn(), -})); - -describe('auth router config', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('getAuthRouter', () => { - it('should return authSsoRouter if isTfmSsoFeatureFlagEnabled is true', () => { - jest.mocked(isTfmSsoFeatureFlagEnabled).mockReturnValue(true); - jest.mocked(getAuthSsoRouter).mockReturnValue('authSsoRouter' as unknown as Router); - - expect(getAuthRouter()).toBe('authSsoRouter'); - expect(getAuthSsoRouter).toHaveBeenCalledTimes(1); - }); - - it('should return undefined if isTfmSsoFeatureFlagEnabled is false', () => { - jest.mocked(isTfmSsoFeatureFlagEnabled).mockReturnValue(false); - - expect(getAuthRouter()).toBe(undefined); - expect(getAuthSsoRouter).not.toHaveBeenCalled(); - }); - }); -}); From e2301635879e00ea27ec7f345ca403e6dc7f2bb7 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 11 Dec 2024 13:11:56 +0000 Subject: [PATCH 053/133] feat(dtfs2-6892): update mock builder --- .../mock-builder.mock.builder.ts | 86 ++++++++++--------- .../builders/user.service.mock.builder.ts | 10 ++- .../sso.controller.get-auth-code-url.test.ts | 4 +- ...ontroller.handle-sso-redirect-form.test.ts | 3 +- ...entra-id.service.get-auth-code-url.test.ts | 3 +- 5 files changed, 60 insertions(+), 46 deletions(-) diff --git a/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts b/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts index 0e74fb97df..b4ddaf2e59 100644 --- a/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts +++ b/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts @@ -6,13 +6,16 @@ type Mocked = { }; /** - * Base class for class test data builders. Pass in default values for the class - * instance so that you can build an instance with those values and override - * them as needed. + * Base class for class test data and class builders. + * + * Pass in default values for the instance so that you can build an instance with + * those values and override them as needed. * * @example + * Overriding the defaults of a type: * ```ts - * // Basic builder + * // user.mock.builder.ts + * // Basic builder for a type * export class UserMockBuilder extends BaseMockBuilder { * constructor() { * super({ @@ -24,52 +27,61 @@ type Mocked = { * }) * } * } + * + * // a-test-file.test.ts + * // Usage in a test where we just need a valid class instance + * const user = new UserMockBuilder().build() + * + * // another-test-file.test.ts + * // Usage in a test where a field needs to have a specific value + * const user = new UserMockBuilder() + * .with({ email: 'new.email@ukef.gov.uk' }) + * .build() * ``` + * * @example + * Overriding the defaults of a class: * ```ts - * // With custom static factory methods to initialise the builder - * export class UserMockBuilder extends BaseMockBuilder { - * constructor(defaultInstance?: User) { + * export class LoginServiceMockBuilder extends BaseMockBuilder { + * constructor() { * super({ - * defaultInstance: defaultInstance ?? { - * id: '72f9ca55-943a-401d-a04c-0a9e03ac7f18', - * name: 'Joe Bloggs', - * email: 'joe.bloggs@ukef.gov.uk', + * defaultInstance: { + * getAuthCodeUrl: jest.fn(async () => { + * return Promise.resolve({ + * authCodeUrl: 'a-auth-code-url', + * authCodeUrlRequest: {} as AuthorizationCodeRequest, + * }); + * }), * }, - * }) - * } - * - * public static fromEntity(entity: UserEntity): UserMockBuilder { - * return new UserMockBuilder({ - * id: entity.id, - * name: entity.name, - * email: entity.email, - * }) + * }); * } * } * ``` - * - * @example - * ```ts - * // Usage in a test where we just need a valid class instance - * const user = new UserMockBuilder().build() - * ``` - * - * @example + * + * Keeping existing implimentations of a class: * ```ts - * // Usage in a test where a field needs to have a specific value - * const user = new UserMockBuilder() - * .with({ email: 'new.email@ukef.gov.uk' }) - * .build() + * export class UserServiceMockBuilder extends BaseMockBuilder { + * constructor() { + * const userService = new UserService(); // This can be used as a way to inherit methods we do not wish to mock the implimentation for + * super({ + * defaultInstance: { + * transformEntraIdUserToUpsertTfmUserRequest(entraIdUser: EntraIdUser): UpsertTfmUserRequest { + * return userService.transformEntraIdUserToUpsertTfmUserRequest(entraIdUser); + * }, + * saveUserLoginInformation({ userId, sessionIdentifier, auditDetails }: saveUserLoginInformationParams): Promise { + * return Promise.resolve(); + * }, + * }, + * }); + * } * ``` */ export abstract class BaseMockBuilder { - private readonly defaults: Partial>; private readonly instance: Mocked = {} as Mocked; - protected constructor(config: { defaultInstance: Partial> }) { - this.defaults = config.defaultInstance; + protected constructor(config: { defaultInstance: Mocked }) { + this.with(config.defaultInstance); } /** @@ -90,10 +102,6 @@ export abstract class BaseMockBuilder { return this; } - public withDefaults(): BaseMockBuilder { - return this.with(this.defaults); - } - /** * Build the class instance after setting any fields */ diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts index 12af34a2a8..4c084e83f6 100644 --- a/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { BaseMockBuilder } from '@ukef/dtfs2-common'; +import { BaseMockBuilder, EntraIdUser, UpsertTfmUserRequest } from '@ukef/dtfs2-common'; import { aTfmUser } from '@ukef/dtfs2-common/mock-data-backend'; import { UpsertTfmUserFromEntraIdUserParams, @@ -10,8 +10,16 @@ import { export class UserServiceMockBuilder extends BaseMockBuilder { constructor() { + const userService = new UserService(); super({ defaultInstance: { + /** + * We pass through the actual service implementation for the below method here, + * as it is an existing pattern that we never mock synchronous methods. + */ + transformEntraIdUserToUpsertTfmUserRequest(entraIdUser: EntraIdUser): UpsertTfmUserRequest { + return userService.transformEntraIdUserToUpsertTfmUserRequest(entraIdUser); + }, upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }: UpsertTfmUserFromEntraIdUserParams): Promise { return Promise.resolve(aTfmUser()); }, diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts index 74bb4ba60c..d86d2a3086 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts @@ -31,8 +31,8 @@ describe('SsoController', () => { beforeEach(() => { jest.resetAllMocks(); - entraIdService = new EntraIdServiceMockBuilder().withDefaults().with({ getAuthCodeUrl: getAuthCodeUrlMock }).build(); - userService = new UserServiceMockBuilder().withDefaults().build(); + entraIdService = new EntraIdServiceMockBuilder().with({ getAuthCodeUrl: getAuthCodeUrlMock }).build(); + userService = new UserServiceMockBuilder().build(); ssoController = new SsoController({ entraIdService, userService }); ({ req, res } = getHttpMocks()); diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts index 15a4f9af09..791ad94a0b 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts @@ -38,9 +38,8 @@ describe('SsoController', () => { beforeEach(() => { jest.resetAllMocks(); - entraIdService = new EntraIdServiceMockBuilder().withDefaults().with({ handleRedirect: handleRedirectMock }).build(); + entraIdService = new EntraIdServiceMockBuilder().with({ handleRedirect: handleRedirectMock }).build(); userService = new UserServiceMockBuilder() - .withDefaults() .with({ upsertTfmUserFromEntraIdUser: upsertTfmUserFromEntraIdUserMock, saveUserLoginInformation: saveUserLoginInformationMock }) .build(); ssoController = new SsoController({ entraIdService, userService }); diff --git a/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts index c3b05025f8..d3740153fb 100644 --- a/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts +++ b/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts @@ -35,7 +35,6 @@ describe('EntraIdService', () => { }); entraIdConfig = new EntraIdConfigMockBuilder() - .withDefaults() .with({ authorityMetadataUrl: mockAuthorityMetaDataUrl, scopes: mockScope, @@ -43,7 +42,7 @@ describe('EntraIdService', () => { }) .build(); - entraIdApi = new EntraIdApiMockBuilder().withDefaults().build(); + entraIdApi = new EntraIdApiMockBuilder().build(); }); it('calls base64Encode with the expected stringifiedstate', async () => { From 2151f8bb769fa25b0c608c91fdbadd9c7b5545b4 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 9 Dec 2024 18:41:08 +0000 Subject: [PATCH 054/133] Revert "feat(dtfs2-6892): remove tfm ui changes" This reverts commit 466f33ad84b4f8a5856d415a28685c4be1542deb. --- trade-finance-manager-ui/server/api.js | 24 +++ trade-finance-manager-ui/server/api.test.js | 157 ------------------ .../login/login-sso/login.controller.ts | 29 ++++ .../server/helpers/express-session.ts | 20 ++- .../server/routes/auth/configs/index.ts | 3 + .../server/routes/auth/index.ts | 3 +- .../server/services/login.service.ts | 30 +++- .../server/types/express-session.d.ts | 10 +- 8 files changed, 111 insertions(+), 165 deletions(-) delete mode 100644 trade-finance-manager-ui/server/api.test.js diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index e1a50b1457..2693eb288a 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -421,6 +421,29 @@ const login = async (username, password) => { } }; +/** + * + * @param {import('@ukef/dtfs2-common').HandleSsoRedirectFormRequest} handleSsoRedirectFormRequest + * @returns {Promise} + */ +const handleSsoRedirectForm = async (handleSsoRedirectFormRequest) => { + try { + const response = await axios({ + method: 'post', + url: `${TFM_API_URL}/v1/sso/handle-sso-redirect-form`, + headers: { + [HEADERS.CONTENT_TYPE.KEY]: HEADERS.CONTENT_TYPE.VALUES.JSON, + }, + data: handleSsoRedirectFormRequest, + }); + + return response.data; + } catch (error) { + console.error('Unable to log in %o', error?.response?.data); + return { status: error?.response?.status || 500, data: 'Failed to login' }; + } +}; + /** * Gets the auth code URL for the SSO login process * @param {import('@ukef/dtfs2-common').GetAuthCodeUrlRequest} getAuthCodeUrlParams @@ -1461,6 +1484,7 @@ module.exports = { updateLeadUnderwriter, createActivity, login, + handleSsoRedirectForm, getAuthCodeUrl, getFacilities, createFeedback, diff --git a/trade-finance-manager-ui/server/api.test.js b/trade-finance-manager-ui/server/api.test.js deleted file mode 100644 index 2809d185c9..0000000000 --- a/trade-finance-manager-ui/server/api.test.js +++ /dev/null @@ -1,157 +0,0 @@ -const axios = require('axios'); -const MockAdapter = require('axios-mock-adapter'); -const { HEADERS } = require('@ukef/dtfs2-common'); -const api = require('./api'); -const { MOCK_BANK_HOLIDAYS } = require('./test-mocks/mock-bank-holidays'); -const { getUkBankHolidays } = require('./api'); -const PageOutOfBoundsError = require('./errors/page-out-of-bounds.error'); - -const mockAxios = new MockAdapter(axios); - -console.error = jest.fn(); - -const { TFM_API_URL, TFM_API_KEY } = process.env; - -afterEach(() => { - jest.clearAllMocks(); - mockAxios.reset(); -}); - -describe('getDeals()', () => { - const dealsUrl = `${TFM_API_URL}/v1/deals`; - - const token = 'testToken'; - const headers = { - Authorization: token, - [HEADERS.CONTENT_TYPE.KEY]: HEADERS.CONTENT_TYPE.VALUES.JSON, - 'x-api-key': TFM_API_KEY, - }; - - const mockResponse = { - deals: [ - { _id: 1, name: 'Deal 1' }, - { _id: 2, name: 'Deal 2' }, - ], - pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, - }; - - it('should return deals data and pagination metadata when TFM API returns this data', async () => { - const queryParams = { page: 0 }; - - mockAxios - .onGet( - dealsUrl, - { params: queryParams }, - expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` - ) - .reply(200, mockResponse); - - const response = await api.getDeals(queryParams, token); - - expect(mockAxios.history.get.length).toEqual(1); - expect(response).toEqual({ - deals: [ - { _id: 1, name: 'Deal 1' }, - { _id: 2, name: 'Deal 2' }, - ], - pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, - }); - }); - - it('should throw a PageOutOfBoundsError when the requested page number exceeds the maximum page number', async () => { - const queryParams = { page: 1 }; - - mockAxios - .onGet( - dealsUrl, - { params: queryParams }, - expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` - ) - .reply(200, mockResponse); - - const errorResponse = api.getDeals(queryParams, token); - - expect(mockAxios.history.get.length).toEqual(1); - await expect(errorResponse).rejects.toThrow(new PageOutOfBoundsError('Requested page number exceeds the maximum page number')); - }); -}); - -describe('getFacilities()', () => { - const facilitiesUrl = `${TFM_API_URL}/v1/facilities`; - - const token = 'testToken'; - const headers = { - Authorization: token, - [HEADERS.CONTENT_TYPE.KEY]: HEADERS.CONTENT_TYPE.VALUES.JSON, - 'x-api-key': TFM_API_KEY, - }; - - const mockResponse = { - facilities: [ - { facilityId: 1, name: 'Facility 1' }, - { facilityId: 2, name: 'Facility 2' }, - ], - pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, - }; - - it('should return facilities data and pagination metadata when TFM API returns this data', async () => { - const queryParams = { page: 0 }; - - mockAxios - .onGet( - facilitiesUrl, - { params: queryParams }, - expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` - ) - .reply(200, mockResponse); - - const response = await api.getFacilities(queryParams, token); - - expect(mockAxios.history.get.length).toEqual(1); - expect(response).toEqual({ - facilities: [ - { facilityId: 1, name: 'Facility 1' }, - { facilityId: 2, name: 'Facility 2' }, - ], - pagination: { totalItems: 2, currentPage: 0, totalPages: 1 }, - }); - }); - - it('should throw a PageOutOfBoundsError when the requested page number exceeds the maximum page number', async () => { - const queryParams = { page: 1 }; - - mockAxios - .onGet( - facilitiesUrl, - { params: queryParams }, - expect.objectContaining(headers), // Axios adds its own headers (e.g., 'Accept'), hence the use of `objectContaining()` - ) - .reply(200, mockResponse); - - const errorResponse = api.getFacilities(queryParams, token); - - expect(mockAxios.history.get.length).toEqual(1); - await expect(errorResponse).rejects.toThrow(new PageOutOfBoundsError('Requested page number exceeds the maximum page number')); - }); -}); - -describe('getUkBankHolidays', () => { - it('gets the bank holidays', async () => { - // Arrange - mockAxios.onGet().reply(200, MOCK_BANK_HOLIDAYS); - - // Act - const response = await getUkBankHolidays('user-token'); - - // Assert - expect(response).toEqual(MOCK_BANK_HOLIDAYS); - }); - - it('throws when the api TFM API request fails', async () => { - // Arrange - mockAxios.onGet().reply(404); - - // Act / Assert - await expect(getUkBankHolidays('user-token')).rejects.toThrowError('Request failed with status code 404'); - }); -}); diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts index 6a7c0c5897..0a5973589b 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts @@ -1,4 +1,9 @@ import { NextFunction, Request, Response } from 'express'; +import { HandleSsoRedirectFormUiRequest, InvalidPayloadError } from '@ukef/dtfs2-common'; +import { isVerifiedPayload } from '@ukef/dtfs2-common/payload-verification'; +import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from '@ukef/dtfs2-common/schemas'; +import { generateSystemAuditDetails } from '@ukef/dtfs2-common/change-stream'; +import { asPartiallyLoggedInUserSession } from '../../../helpers/express-session'; import { LoginService } from '../../../services/login.service'; export class LoginController { @@ -10,6 +15,7 @@ export class LoginController { public async getLogin(req: Request, res: Response, next: NextFunction) { try { + // TODO: This validation is legacy code, and can be improved if (req.session.user) { // User is already logged in. return res.redirect('/home'); @@ -27,6 +33,29 @@ export class LoginController { } } + async handleSsoRedirectForm(req: HandleSsoRedirectFormUiRequest, res: Response, next: NextFunction) { + try { + const { body, session: partiallyLoggedInSession } = req; + const session = asPartiallyLoggedInUserSession(partiallyLoggedInSession); + const auditDetails = generateSystemAuditDetails(); + + if (!isVerifiedPayload({ payload: body, template: ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA })) { + throw new InvalidPayloadError('Invalid payload from SSO redirect'); + } + + const { successRedirect } = await this.loginService.handleSsoRedirectFormAndCreateToken({ + authCodeResponse: body, + originalAuthCodeUrlRequest: session.loginData.authCodeUrlRequest, + session, + auditDetails, + }); + + return res.redirect(successRedirect ?? '/'); + } catch (error) { + return next(error); + } + } + // TODO DTFS2-6892: Update this logout handling public getLogout = (req: Request, res: Response) => { req.session.destroy(() => { diff --git a/trade-finance-manager-ui/server/helpers/express-session.ts b/trade-finance-manager-ui/server/helpers/express-session.ts index d554771c39..39264e9715 100644 --- a/trade-finance-manager-ui/server/helpers/express-session.ts +++ b/trade-finance-manager-ui/server/helpers/express-session.ts @@ -1,11 +1,12 @@ import { Request } from 'express'; -import { UserSessionNotDefinedError, UserTokenNotDefinedError } from '@ukef/dtfs2-common'; -import { UserSessionData } from '../types/express-session'; +import { UserPartialLoginDataNotDefinedError, UserSessionNotDefinedError, UserTokenNotDefinedError } from '@ukef/dtfs2-common'; +import { PartiallyLoggedInUserSessionData, UserSessionData } from '../types/express-session'; type Session = Request['session']; -type UserSession = Session & UserSessionData; +export type UserSession = Session & UserSessionData; +type PartiallyLoggedInUserSession = Session & PartiallyLoggedInUserSessionData; /** * By default, all session data will be optional * (see use of `Partial` {@link https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express-session/index.d.ts#L17 here}) @@ -30,3 +31,16 @@ export const asUserSession = (session: Session): UserSession => { return Object.assign(session, { user, userToken }); }; + +export const assertPartiallyLoggedInUser: (session: Session) => asserts session is PartiallyLoggedInUserSession = (session: Session) => { + const { loginData } = session; + + if (!loginData) { + throw new UserPartialLoginDataNotDefinedError(); + } +}; + +export const asPartiallyLoggedInUserSession = (session: Session): PartiallyLoggedInUserSession => { + assertPartiallyLoggedInUser(session); + return session; +}; diff --git a/trade-finance-manager-ui/server/routes/auth/configs/index.ts b/trade-finance-manager-ui/server/routes/auth/configs/index.ts index 2595c42b06..bf3802fe64 100644 --- a/trade-finance-manager-ui/server/routes/auth/configs/index.ts +++ b/trade-finance-manager-ui/server/routes/auth/configs/index.ts @@ -1,4 +1,7 @@ import { isTfmSsoFeatureFlagEnabled } from '@ukef/dtfs2-common'; +import { getAuthSsoRouter } from './auth-sso'; import { getUnauthenticatedAuthSsoRouter } from './unauthenticated-auth-sso'; +export const getAuthRouter = () => (isTfmSsoFeatureFlagEnabled() ? getAuthSsoRouter() : undefined); + export const getUnauthenticatedAuthRouter = () => (isTfmSsoFeatureFlagEnabled() ? getUnauthenticatedAuthSsoRouter() : undefined); diff --git a/trade-finance-manager-ui/server/routes/auth/index.ts b/trade-finance-manager-ui/server/routes/auth/index.ts index c52e3ef8bd..6d31c2f430 100644 --- a/trade-finance-manager-ui/server/routes/auth/index.ts +++ b/trade-finance-manager-ui/server/routes/auth/index.ts @@ -1,3 +1,4 @@ -import { getUnauthenticatedAuthRouter } from './configs'; +import { getAuthRouter, getUnauthenticatedAuthRouter } from './configs'; +export const authRoutes = getAuthRouter(); export const unauthenticatedAuthRoutes = getUnauthenticatedAuthRouter(); diff --git a/trade-finance-manager-ui/server/services/login.service.ts b/trade-finance-manager-ui/server/services/login.service.ts index dc3ef1f71c..75fd431ac6 100644 --- a/trade-finance-manager-ui/server/services/login.service.ts +++ b/trade-finance-manager-ui/server/services/login.service.ts @@ -1,6 +1,13 @@ -import { GetAuthCodeUrlParams, GetAuthCodeUrlResponse } from '@ukef/dtfs2-common'; +import { GetAuthCodeUrlParams, GetAuthCodeUrlResponse, HandleSsoRedirectFormRequest } from '@ukef/dtfs2-common'; +import { PartiallyLoggedInUserSessionData } from '../types/express-session'; import * as api from '../api'; +type HandleSsoRedirectFormAndCreateTokenRequest = { + session: PartiallyLoggedInUserSessionData; +} & HandleSsoRedirectFormRequest; + +type HandleSsoRedirectFormAndCreateTokenResponse = { successRedirect?: string }; + export class LoginService { /** * Gets the URL to redirect the user to in order to log in. @@ -8,4 +15,25 @@ export class LoginService { public getAuthCodeUrl = async ({ successRedirect }: GetAuthCodeUrlParams): Promise => { return api.getAuthCodeUrl({ successRedirect }); }; + + public handleSsoRedirectFormAndCreateToken = async ({ + authCodeResponse, + originalAuthCodeUrlRequest, + // TODO as part of this ticket: uncomment the session parameter + // eslint-disable-next-line @typescript-eslint/no-unused-vars + session, + auditDetails, + }: HandleSsoRedirectFormAndCreateTokenRequest): Promise => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { user, userToken, successRedirect } = await api.handleSsoRedirectForm({ authCodeResponse, originalAuthCodeUrlRequest, auditDetails }); + + // TODO: This can be moved into a separate session service + // delete session.loginData; + // session.user = user; + // session.userToken = userToken; + + return { successRedirect }; + }; + + public getSuccessRedirect = () => {}; } diff --git a/trade-finance-manager-ui/server/types/express-session.d.ts b/trade-finance-manager-ui/server/types/express-session.d.ts index 17cb5aec3d..501d67e167 100644 --- a/trade-finance-manager-ui/server/types/express-session.d.ts +++ b/trade-finance-manager-ui/server/types/express-session.d.ts @@ -1,21 +1,25 @@ +import { AuthorizationUrlRequest } from '@azure/msal-node'; import { TfmSessionUser } from '@ukef/dtfs2-common'; import { RemoveFeesFromPaymentErrorKey } from '../controllers/utilisation-reports/helpers'; import { EditPaymentFormValues } from './edit-payment-form-values'; import { AddPaymentErrorKey, InitiateRecordCorrectionRequestErrorKey, GenerateKeyingDataErrorKey } from './premium-payments-tab-error-keys'; -type UserSessionLoginData = { +export type UserSessionLoginData = { authCodeUrlRequest: AuthorizationUrlRequest; }; +export type PartiallyLoggedInUserSessionData = { + loginData: UserSessionLoginData; +}; + export type UserSessionData = { user: TfmSessionUser; userToken: string; - loginData?: UserSessionLoginData; }; // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express-session/index.d.ts#L199-L211 declare module 'express-session' { - interface SessionData extends UserSessionData { + interface SessionData extends UserSessionData, PartiallyLoggedInUserSessionData { addPaymentErrorKey: AddPaymentErrorKey; initiateRecordCorrectionRequestErrorKey: InitiateRecordCorrectionRequestErrorKey; checkedCheckboxIds: Record; From a137cff62e25c0f23bc84fcda077a730619f9209 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 9 Dec 2024 18:52:37 +0000 Subject: [PATCH 055/133] Revert "feat(dtfs2-6892): remove tfm ui changes" This reverts commit 335708af7bb509949f778e2c2a268dfc29da2cd8. --- .../server/routes/auth/configs/auth-sso.ts | 15 ++++++++ .../configs/index.get-auth-router.test.ts | 35 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts create mode 100644 trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts diff --git a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts new file mode 100644 index 0000000000..f153705f39 --- /dev/null +++ b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts @@ -0,0 +1,15 @@ +import express from 'express'; +import { GetRouter } from '../../../types/get-router'; +import { LoginController } from '../../../controllers/login/login-sso/login.controller'; +import { LoginService } from '../../../services/login.service'; + +export const getAuthSsoRouter: GetRouter = () => { + const loginService = new LoginService(); + const loginController = new LoginController({ loginService }); + const authSsoRouter = express.Router(); + + // Todo: update this to check the right token + // eslint-disable-next-line @typescript-eslint/no-misused-promises + authSsoRouter.post('/auth/sso-redirect/form', loginController.handleSsoRedirectForm.bind(loginController)); + return authSsoRouter; +}; diff --git a/trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts b/trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts new file mode 100644 index 0000000000..dc34246597 --- /dev/null +++ b/trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts @@ -0,0 +1,35 @@ +import { isTfmSsoFeatureFlagEnabled } from '@ukef/dtfs2-common'; +import { Router } from 'express'; +import { getAuthRouter } from '.'; +import { getAuthSsoRouter } from './auth-sso'; + +jest.mock('@ukef/dtfs2-common', () => ({ + isTfmSsoFeatureFlagEnabled: jest.fn(), +})); + +jest.mock('./authSso', () => ({ + getAuthSsoRouter: jest.fn(), +})); + +describe('auth router config', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('getAuthRouter', () => { + it('should return authSsoRouter if isTfmSsoFeatureFlagEnabled is true', () => { + jest.mocked(isTfmSsoFeatureFlagEnabled).mockReturnValue(true); + jest.mocked(getAuthSsoRouter).mockReturnValue('authSsoRouter' as unknown as Router); + + expect(getAuthRouter()).toBe('authSsoRouter'); + expect(getAuthSsoRouter).toHaveBeenCalledTimes(1); + }); + + it('should return undefined if isTfmSsoFeatureFlagEnabled is false', () => { + jest.mocked(isTfmSsoFeatureFlagEnabled).mockReturnValue(false); + + expect(getAuthRouter()).toBe(undefined); + expect(getAuthSsoRouter).not.toHaveBeenCalled(); + }); + }); +}); From 1bdd7383fa590a3d86cfead2195c400d4f8e8df2 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 11 Dec 2024 12:17:15 +0000 Subject: [PATCH 056/133] feat(dtfs2-6892): add user session service --- .../login/login-sso/login.controller.ts | 20 ++++++------ .../server/services/login.service.ts | 26 +++------------ .../server/services/user-session.service.ts | 32 +++++++++++++++++++ 3 files changed, 47 insertions(+), 31 deletions(-) create mode 100644 trade-finance-manager-ui/server/services/user-session.service.ts diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts index 0a5973589b..fbafa9b16a 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts @@ -5,12 +5,15 @@ import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from '@ukef/dtfs2-co import { generateSystemAuditDetails } from '@ukef/dtfs2-common/change-stream'; import { asPartiallyLoggedInUserSession } from '../../../helpers/express-session'; import { LoginService } from '../../../services/login.service'; +import { UserSessionService } from '../../../services/user-session.service'; export class LoginController { private readonly loginService: LoginService; + private readonly userSessionService: UserSessionService; - constructor({ loginService }: { loginService: LoginService }) { + constructor({ loginService, userSessionService }: { loginService: LoginService; userSessionService: UserSessionService }) { this.loginService = loginService; + this.userSessionService = userSessionService; } public async getLogin(req: Request, res: Response, next: NextFunction) { @@ -23,9 +26,7 @@ export class LoginController { const { authCodeUrl, authCodeUrlRequest } = await this.loginService.getAuthCodeUrl({ successRedirect: '/' }); - // As this is the user logging in, there should be no existing login data in the session. - // if there is, it should be cleared and set to the authCodeUrlRequest. - req.session.loginData = { authCodeUrlRequest }; + this.userSessionService.createPartiallyLoggedInSession({ session: req.session, authCodeUrlRequest }); return res.redirect(authCodeUrl); } catch (error) { @@ -35,21 +36,22 @@ export class LoginController { async handleSsoRedirectForm(req: HandleSsoRedirectFormUiRequest, res: Response, next: NextFunction) { try { - const { body, session: partiallyLoggedInSession } = req; - const session = asPartiallyLoggedInUserSession(partiallyLoggedInSession); + const { body, session } = req; + const partiallyLoggedInSession = asPartiallyLoggedInUserSession(session); const auditDetails = generateSystemAuditDetails(); if (!isVerifiedPayload({ payload: body, template: ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA })) { throw new InvalidPayloadError('Invalid payload from SSO redirect'); } - const { successRedirect } = await this.loginService.handleSsoRedirectFormAndCreateToken({ + const { successRedirect, user, token } = await this.loginService.handleSsoRedirectForm({ authCodeResponse: body, - originalAuthCodeUrlRequest: session.loginData.authCodeUrlRequest, - session, + originalAuthCodeUrlRequest: partiallyLoggedInSession.loginData.authCodeUrlRequest, auditDetails, }); + this.userSessionService.createLoggedInSession({ session, user, userToken: token }); + return res.redirect(successRedirect ?? '/'); } catch (error) { return next(error); diff --git a/trade-finance-manager-ui/server/services/login.service.ts b/trade-finance-manager-ui/server/services/login.service.ts index 75fd431ac6..9f7e65e0a1 100644 --- a/trade-finance-manager-ui/server/services/login.service.ts +++ b/trade-finance-manager-ui/server/services/login.service.ts @@ -1,13 +1,6 @@ -import { GetAuthCodeUrlParams, GetAuthCodeUrlResponse, HandleSsoRedirectFormRequest } from '@ukef/dtfs2-common'; -import { PartiallyLoggedInUserSessionData } from '../types/express-session'; +import { GetAuthCodeUrlParams, GetAuthCodeUrlResponse, HandleSsoRedirectFormRequest, HandleSsoRedirectFormResponse } from '@ukef/dtfs2-common'; import * as api from '../api'; -type HandleSsoRedirectFormAndCreateTokenRequest = { - session: PartiallyLoggedInUserSessionData; -} & HandleSsoRedirectFormRequest; - -type HandleSsoRedirectFormAndCreateTokenResponse = { successRedirect?: string }; - export class LoginService { /** * Gets the URL to redirect the user to in order to log in. @@ -16,23 +9,12 @@ export class LoginService { return api.getAuthCodeUrl({ successRedirect }); }; - public handleSsoRedirectFormAndCreateToken = async ({ + public handleSsoRedirectForm = async ({ authCodeResponse, originalAuthCodeUrlRequest, - // TODO as part of this ticket: uncomment the session parameter - // eslint-disable-next-line @typescript-eslint/no-unused-vars - session, auditDetails, - }: HandleSsoRedirectFormAndCreateTokenRequest): Promise => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { user, userToken, successRedirect } = await api.handleSsoRedirectForm({ authCodeResponse, originalAuthCodeUrlRequest, auditDetails }); - - // TODO: This can be moved into a separate session service - // delete session.loginData; - // session.user = user; - // session.userToken = userToken; - - return { successRedirect }; + }: HandleSsoRedirectFormRequest): Promise => { + return api.handleSsoRedirectForm({ authCodeResponse, originalAuthCodeUrlRequest, auditDetails }); }; public getSuccessRedirect = () => {}; diff --git a/trade-finance-manager-ui/server/services/user-session.service.ts b/trade-finance-manager-ui/server/services/user-session.service.ts new file mode 100644 index 0000000000..bdacacd27d --- /dev/null +++ b/trade-finance-manager-ui/server/services/user-session.service.ts @@ -0,0 +1,32 @@ +/* eslint-disable no-param-reassign */ +import { AuthorizationUrlRequest } from '@azure/msal-node'; +import { TfmSessionUser } from '@ukef/dtfs2-common'; +import { Session, SessionData } from 'express-session'; + +export class UserSessionService { + private deleteExistingPartiallyLoggedInSession(session: Session & Partial) { + delete session.loginData; + } + + private deleteExistingLoggedInSession(session: Session & Partial) { + delete session.user; + delete session.userToken; + } + + public createPartiallyLoggedInSession({ + session, + authCodeUrlRequest, + }: { + session: Session & Partial; + authCodeUrlRequest: AuthorizationUrlRequest; + }) { + this.deleteExistingLoggedInSession(session); + session.loginData = { authCodeUrlRequest }; + } + + public createLoggedInSession({ session, user, userToken }: { session: Session & Partial; user: TfmSessionUser; userToken: string }) { + this.deleteExistingPartiallyLoggedInSession(session); + session.user = user; + session.userToken = userToken; + } +} From 5bac4d505818385d633cc9aff7dda95da957bb86 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 11 Dec 2024 12:24:42 +0000 Subject: [PATCH 057/133] feat(dtfs2-6892): update user session types --- .../server/types/express-session.d.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/trade-finance-manager-ui/server/types/express-session.d.ts b/trade-finance-manager-ui/server/types/express-session.d.ts index 501d67e167..17b4e1d3ca 100644 --- a/trade-finance-manager-ui/server/types/express-session.d.ts +++ b/trade-finance-manager-ui/server/types/express-session.d.ts @@ -4,12 +4,19 @@ import { RemoveFeesFromPaymentErrorKey } from '../controllers/utilisation-report import { EditPaymentFormValues } from './edit-payment-form-values'; import { AddPaymentErrorKey, InitiateRecordCorrectionRequestErrorKey, GenerateKeyingDataErrorKey } from './premium-payments-tab-error-keys'; -export type UserSessionLoginData = { +/** + * A wrapper for a partially logged in users session data + */ +type PartiallyLoggedInUserSessionLoginData = { authCodeUrlRequest: AuthorizationUrlRequest; }; +/** + * We keep the partially logged in user session data on a + * separate isolated parameter to allow for easy management + */ export type PartiallyLoggedInUserSessionData = { - loginData: UserSessionLoginData; + loginData: PartiallyLoggedInUserSessionLoginData; }; export type UserSessionData = { From 3a3244a1b66d10b00775ddf6e891e3c3c757448c Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 11 Dec 2024 12:46:35 +0000 Subject: [PATCH 058/133] feat(dtfs2-6892): add user sesion mock builder and tests --- .../login-sso/login.controller.get-login.test.ts | 14 +++++++++----- .../login-sso/login.controller.get-logout.test.ts | 7 +++++-- .../test-helpers/mocks/index.ts | 1 + .../mocks/user-session.service.mock.builder.ts | 10 ++++++++++ 4 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 trade-finance-manager-ui/test-helpers/mocks/user-session.service.mock.builder.ts diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts index e4127ebdea..a4167cf95f 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts @@ -1,9 +1,11 @@ import httpMocks from 'node-mocks-http'; import { resetAllWhenMocks, when } from 'jest-when'; +import { AuthorizationUrlRequest } from '@azure/msal-node'; import { aTfmSessionUser } from '../../../../test-helpers'; import { LoginController } from './login.controller'; import { LoginService } from '../../../services/login.service'; -import { LoginServiceMockBuilder } from '../../../../test-helpers/mocks'; +import { UserSessionService } from '../../../services/user-session.service'; +import { LoginServiceMockBuilder, UserSessionServiceMockBuilder } from '../../../../test-helpers/mocks'; describe('controllers - login (sso)', () => { describe('getLogin', () => { @@ -12,6 +14,8 @@ describe('controllers - login (sso)', () => { let loginController: LoginController; let loginService: LoginService; + let userSessionService: UserSessionService; + const getAuthCodeUrlMock = jest.fn(); const next = jest.fn(); @@ -20,8 +24,8 @@ describe('controllers - login (sso)', () => { jest.resetAllMocks(); loginService = new LoginServiceMockBuilder().with({ getAuthCodeUrl: getAuthCodeUrlMock }).build(); - - loginController = new LoginController({ loginService }); + userSessionService = new UserSessionServiceMockBuilder().build(); + loginController = new LoginController({ loginService, userSessionService }); }); describe('when there is a user session', () => { @@ -64,13 +68,13 @@ describe('controllers - login (sso)', () => { expect(res._getRedirectUrl()).toEqual(mockAuthCodeUrl); }); - it('overrides session login data if present', async () => { + it.only('overrides session login data if present', async () => { // Arrange const { req, res } = httpMocks.createMocks({ session: { loginData: { authCodeUrlRequest: 'an old auth code url request', aField: 'another field' } }, }); - req.session.loginData = { authCodeUrlRequest: 'old-auth-code-url-request' }; + req.session.loginData = { authCodeUrlRequest: 'old-auth-code-url-request' as unknown as AuthorizationUrlRequest }; // Act await loginController.getLogin(req, res, next); diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-logout.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-logout.test.ts index 575fd74b9b..a4626b1337 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-logout.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-logout.test.ts @@ -1,16 +1,19 @@ import httpMocks from 'node-mocks-http'; import { LoginController } from './login.controller'; import { LoginService } from '../../../services/login.service'; -import { LoginServiceMockBuilder } from '../../../../test-helpers/mocks'; +import { LoginServiceMockBuilder, UserSessionServiceMockBuilder } from '../../../../test-helpers/mocks'; +import { UserSessionService } from '../../../services/user-session.service'; describe('controllers - login (sso)', () => { describe('getLogout', () => { let loginService: LoginService; + let userSessionService: UserSessionService; let loginController: LoginController; beforeEach(() => { loginService = new LoginServiceMockBuilder().build(); - loginController = new LoginController({ loginService }); + userSessionService = new UserSessionServiceMockBuilder().build(); + loginController = new LoginController({ loginService, userSessionService }); }); it('redirects to /', () => { diff --git a/trade-finance-manager-ui/test-helpers/mocks/index.ts b/trade-finance-manager-ui/test-helpers/mocks/index.ts index a485d3815b..5f1251769a 100644 --- a/trade-finance-manager-ui/test-helpers/mocks/index.ts +++ b/trade-finance-manager-ui/test-helpers/mocks/index.ts @@ -1 +1,2 @@ export * from './login.service.mock.builder'; +export * from './user-session.service.mock.builder'; diff --git a/trade-finance-manager-ui/test-helpers/mocks/user-session.service.mock.builder.ts b/trade-finance-manager-ui/test-helpers/mocks/user-session.service.mock.builder.ts new file mode 100644 index 0000000000..91d3881904 --- /dev/null +++ b/trade-finance-manager-ui/test-helpers/mocks/user-session.service.mock.builder.ts @@ -0,0 +1,10 @@ +import { BaseMockBuilder } from '@ukef/dtfs2-common'; +import { UserSessionService } from '../../server/services/user-session.service'; + +export class UserSessionServiceMockBuilder extends BaseMockBuilder { + constructor() { + super({ + defaultInstance: {}, + }); + } +} From dee0e9a2cf5a03d2fac87864841e1deff3d79225 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 11 Dec 2024 14:15:17 +0000 Subject: [PATCH 059/133] feat(dtfs2-6892): update mock builders --- .../get-auth-code-url-response.mock.ts | 7 +++++++ libs/common/src/test-helpers/mock-data/index.ts | 1 + .../builders/extra-id.service.mock.builder.ts | 17 ++++++----------- .../login.controller.get-login.test.ts | 2 +- .../server/services/login.service.ts | 2 -- .../mocks/login.service.mock.builder.ts | 12 ++++-------- .../mocks/user-session.service.mock.builder.ts | 6 +++++- .../handle-sso-redirect-form-response.ts | 9 +++++++++ .../test-helpers/test-data/index.ts | 1 + 9 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 libs/common/src/test-helpers/mock-data/get-auth-code-url-response.mock.ts create mode 100644 trade-finance-manager-ui/test-helpers/test-data/handle-sso-redirect-form-response.ts diff --git a/libs/common/src/test-helpers/mock-data/get-auth-code-url-response.mock.ts b/libs/common/src/test-helpers/mock-data/get-auth-code-url-response.mock.ts new file mode 100644 index 0000000000..484ac5977d --- /dev/null +++ b/libs/common/src/test-helpers/mock-data/get-auth-code-url-response.mock.ts @@ -0,0 +1,7 @@ +import { GetAuthCodeUrlResponse } from '../../types'; +import { anAuthorisationCodeRequest } from './authorisation-code-request.mock'; + +export const aGetAuthCodeUrlResponse = (): GetAuthCodeUrlResponse => ({ + authCodeUrl: 'a-auth-code-url', + authCodeUrlRequest: anAuthorisationCodeRequest(), +}); diff --git a/libs/common/src/test-helpers/mock-data/index.ts b/libs/common/src/test-helpers/mock-data/index.ts index 3f11ad0598..165580d897 100644 --- a/libs/common/src/test-helpers/mock-data/index.ts +++ b/libs/common/src/test-helpers/mock-data/index.ts @@ -14,3 +14,4 @@ export * from './create-tfm-user-request'; export * from './upsert-tfm-user-request'; export * from './entra-id-auth-code-redirect-response-body'; export * from './fee-record-correction-transient-form-data.entity.mock-builder'; +export * from './get-auth-code-url-response.mock'; diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts index f1bc0f159f..25cd208385 100644 --- a/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts @@ -1,22 +1,17 @@ -import { anAuthorisationCodeRequest, anEntraIdUser, BaseMockBuilder } from '@ukef/dtfs2-common'; +import { aGetAuthCodeUrlResponse, anEntraIdUser, BaseMockBuilder } from '@ukef/dtfs2-common'; import { EntraIdService } from '../../services/entra-id.service'; export class EntraIdServiceMockBuilder extends BaseMockBuilder { constructor() { super({ defaultInstance: { - getAuthCodeUrl: jest.fn(async () => { - return Promise.resolve({ - authCodeUrl: 'a-auth-code-url', - authCodeUrlRequest: anAuthorisationCodeRequest(), - }); - }), - handleRedirect: jest.fn(async () => { - return Promise.resolve({ + getAuthCodeUrl: jest.fn(async () => Promise.resolve(aGetAuthCodeUrlResponse())), + handleRedirect: jest.fn(async () => + Promise.resolve({ entraIdUser: anEntraIdUser(), successRedirect: 'a-success-redirect', - }); - }), + }), + ), }, }); } diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts index a4167cf95f..4a4663ed91 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts @@ -68,7 +68,7 @@ describe('controllers - login (sso)', () => { expect(res._getRedirectUrl()).toEqual(mockAuthCodeUrl); }); - it.only('overrides session login data if present', async () => { + it('overrides session login data if present', async () => { // Arrange const { req, res } = httpMocks.createMocks({ session: { loginData: { authCodeUrlRequest: 'an old auth code url request', aField: 'another field' } }, diff --git a/trade-finance-manager-ui/server/services/login.service.ts b/trade-finance-manager-ui/server/services/login.service.ts index 9f7e65e0a1..7713c8cd3b 100644 --- a/trade-finance-manager-ui/server/services/login.service.ts +++ b/trade-finance-manager-ui/server/services/login.service.ts @@ -16,6 +16,4 @@ export class LoginService { }: HandleSsoRedirectFormRequest): Promise => { return api.handleSsoRedirectForm({ authCodeResponse, originalAuthCodeUrlRequest, auditDetails }); }; - - public getSuccessRedirect = () => {}; } diff --git a/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts b/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts index 5932b5e0bb..e3966f3c1e 100644 --- a/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts +++ b/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts @@ -1,17 +1,13 @@ -import { AuthorizationCodeRequest } from '@azure/msal-node'; -import { BaseMockBuilder } from '@ukef/dtfs2-common'; +import { aGetAuthCodeUrlResponse, BaseMockBuilder } from '@ukef/dtfs2-common'; import { LoginService } from '../../server/services/login.service'; +import { aHandleSsoRedirectFormResponse } from '../test-data'; export class LoginServiceMockBuilder extends BaseMockBuilder { constructor() { super({ defaultInstance: { - getAuthCodeUrl: jest.fn(async () => { - return Promise.resolve({ - authCodeUrl: 'a-auth-code-url', - authCodeUrlRequest: {} as AuthorizationCodeRequest, - }); - }), + getAuthCodeUrl: jest.fn(async () => Promise.resolve(aGetAuthCodeUrlResponse())), + handleSsoRedirectForm: jest.fn(async () => Promise.resolve(aHandleSsoRedirectFormResponse())), }, }); } diff --git a/trade-finance-manager-ui/test-helpers/mocks/user-session.service.mock.builder.ts b/trade-finance-manager-ui/test-helpers/mocks/user-session.service.mock.builder.ts index 91d3881904..a9dc34270e 100644 --- a/trade-finance-manager-ui/test-helpers/mocks/user-session.service.mock.builder.ts +++ b/trade-finance-manager-ui/test-helpers/mocks/user-session.service.mock.builder.ts @@ -3,8 +3,12 @@ import { UserSessionService } from '../../server/services/user-session.service'; export class UserSessionServiceMockBuilder extends BaseMockBuilder { constructor() { + const userSessionService = new UserSessionService(); super({ - defaultInstance: {}, + defaultInstance: { + createPartiallyLoggedInSession: (...args) => userSessionService.createPartiallyLoggedInSession(...args), + createLoggedInSession: (...args) => userSessionService.createLoggedInSession(...args), + }, }); } } diff --git a/trade-finance-manager-ui/test-helpers/test-data/handle-sso-redirect-form-response.ts b/trade-finance-manager-ui/test-helpers/test-data/handle-sso-redirect-form-response.ts new file mode 100644 index 0000000000..2edce6e92b --- /dev/null +++ b/trade-finance-manager-ui/test-helpers/test-data/handle-sso-redirect-form-response.ts @@ -0,0 +1,9 @@ +import { HandleSsoRedirectFormResponse } from '@ukef/dtfs2-common'; +import { aTfmSessionUser } from './tfm-session-user'; + +export const aHandleSsoRedirectFormResponse = (): HandleSsoRedirectFormResponse => ({ + user: aTfmSessionUser(), + token: 'a-token', + expires: 'a-date', + successRedirect: 'a-redirect', +}); diff --git a/trade-finance-manager-ui/test-helpers/test-data/index.ts b/trade-finance-manager-ui/test-helpers/test-data/index.ts index f2868ca0c4..50ac9f0b87 100644 --- a/trade-finance-manager-ui/test-helpers/test-data/index.ts +++ b/trade-finance-manager-ui/test-helpers/test-data/index.ts @@ -4,3 +4,4 @@ export * from './tfm-session-user'; export * from './fee-record-utilisation'; export * from './utilisation-table-row-view-model'; export * from './a-request-session'; +export * from './handle-sso-redirect-form-response'; From 20941f43fb024f2496b352fb76f188527aa590ec Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 11 Dec 2024 14:58:15 +0000 Subject: [PATCH 060/133] feat(dtfs2-6892): split express session tests --- ...-session.test.ts => express-session.as-a-user-session.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename trade-finance-manager-ui/server/helpers/{express-session.test.ts => express-session.as-a-user-session.test.ts} (100%) diff --git a/trade-finance-manager-ui/server/helpers/express-session.test.ts b/trade-finance-manager-ui/server/helpers/express-session.as-a-user-session.test.ts similarity index 100% rename from trade-finance-manager-ui/server/helpers/express-session.test.ts rename to trade-finance-manager-ui/server/helpers/express-session.as-a-user-session.test.ts From 449e60d949460d04a05883467dcfd89bccba25a1 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 11 Dec 2024 15:00:38 +0000 Subject: [PATCH 061/133] feat(dtfs2-6892): add express session tests --- ...a-partially-logged-in-user-session.test.ts | 31 +++++++++++++++++++ ...on.assert-partially-logged-in-user.test.ts | 28 +++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 trade-finance-manager-ui/server/helpers/express-session.as-a-partially-logged-in-user-session.test.ts create mode 100644 trade-finance-manager-ui/server/helpers/express-session.assert-partially-logged-in-user.test.ts diff --git a/trade-finance-manager-ui/server/helpers/express-session.as-a-partially-logged-in-user-session.test.ts b/trade-finance-manager-ui/server/helpers/express-session.as-a-partially-logged-in-user-session.test.ts new file mode 100644 index 0000000000..b5f4f315e5 --- /dev/null +++ b/trade-finance-manager-ui/server/helpers/express-session.as-a-partially-logged-in-user-session.test.ts @@ -0,0 +1,31 @@ +import httpMocks from 'node-mocks-http'; +import { UserPartialLoginDataNotDefinedError } from '@ukef/dtfs2-common'; +import { asPartiallyLoggedInUserSession } from './express-session'; + +describe('express-session helper', () => { + describe('asAPartiallyLoggedInUserSession', () => { + it('throws if the login data is not defined', () => { + // Arrange + const req = httpMocks.createRequest({ + session: { loginData: undefined }, + }); + + // Act / Assert + expect(() => asPartiallyLoggedInUserSession(req.session)).toThrow(UserPartialLoginDataNotDefinedError); + }); + + it('returns the session user values', () => { + // Arrange + const loginData = 'partial-user-login-data'; + const req = httpMocks.createRequest({ + session: { loginData }, + }); + + // Act + const result = asPartiallyLoggedInUserSession(req.session); + + // Assert + expect(result.loginData).toEqual(loginData); + }); + }); +}); diff --git a/trade-finance-manager-ui/server/helpers/express-session.assert-partially-logged-in-user.test.ts b/trade-finance-manager-ui/server/helpers/express-session.assert-partially-logged-in-user.test.ts new file mode 100644 index 0000000000..2b34510dfc --- /dev/null +++ b/trade-finance-manager-ui/server/helpers/express-session.assert-partially-logged-in-user.test.ts @@ -0,0 +1,28 @@ +import httpMocks from 'node-mocks-http'; +import { UserPartialLoginDataNotDefinedError } from '@ukef/dtfs2-common'; +import { assertPartiallyLoggedInUser } from './express-session'; + +describe('express-session helper', () => { + describe('assertPartiallyLoggedInUser', () => { + it('throws if the login data is not defined', () => { + // Arrange + const req = httpMocks.createRequest({ + session: { loginData: undefined }, + }); + + // Act / Assert + expect(() => assertPartiallyLoggedInUser(req.session)).toThrow(UserPartialLoginDataNotDefinedError); + }); + + it('does not throw if login data is defined', () => { + // Arrange + const loginData = 'partial-user-login-data'; + const req = httpMocks.createRequest({ + session: { loginData }, + }); + + // Act + assertPartiallyLoggedInUser(req.session); + }); + }); +}); From 527b5434366d2e49184c12a150ab039a2af0c60b Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 11 Dec 2024 15:01:18 +0000 Subject: [PATCH 062/133] feat(dtfs2-6892): add express session tests --- .../express-session.assert-partially-logged-in-user.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/trade-finance-manager-ui/server/helpers/express-session.assert-partially-logged-in-user.test.ts b/trade-finance-manager-ui/server/helpers/express-session.assert-partially-logged-in-user.test.ts index 2b34510dfc..839a8694fc 100644 --- a/trade-finance-manager-ui/server/helpers/express-session.assert-partially-logged-in-user.test.ts +++ b/trade-finance-manager-ui/server/helpers/express-session.assert-partially-logged-in-user.test.ts @@ -23,6 +23,8 @@ describe('express-session helper', () => { // Act assertPartiallyLoggedInUser(req.session); + + // There is no assertions. If the function does not throw, it is considered a pass. }); }); }); From 33a1dd2b8df98a71ba276a56726c3df5ef0a5c8d Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 11 Dec 2024 15:32:03 +0000 Subject: [PATCH 063/133] feat(dtfs2-6892): update user session service import --- .../server/routes/auth/configs/auth-sso.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts index f153705f39..a5f6243009 100644 --- a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts +++ b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts @@ -2,10 +2,12 @@ import express from 'express'; import { GetRouter } from '../../../types/get-router'; import { LoginController } from '../../../controllers/login/login-sso/login.controller'; import { LoginService } from '../../../services/login.service'; +import { UserSessionService } from '../../../services/user-session.service'; export const getAuthSsoRouter: GetRouter = () => { const loginService = new LoginService(); - const loginController = new LoginController({ loginService }); + const userSessionService = new UserSessionService(); + const loginController = new LoginController({ loginService, userSessionService }); const authSsoRouter = express.Router(); // Todo: update this to check the right token From a71a3bfaba7ad36f3ae22fdd7c9aeb906c55de2c Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 11 Dec 2024 15:36:24 +0000 Subject: [PATCH 064/133] feat(dtfs2-6892): update user session service tests --- ...n.service.create-logged-in-session.test.ts | 63 +++++++++++++++++++ ...create-partially-logged-in-session.test.ts | 56 +++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 trade-finance-manager-ui/server/services/user-session.service.create-logged-in-session.test.ts create mode 100644 trade-finance-manager-ui/server/services/user-session.service.create-partially-logged-in-session.test.ts diff --git a/trade-finance-manager-ui/server/services/user-session.service.create-logged-in-session.test.ts b/trade-finance-manager-ui/server/services/user-session.service.create-logged-in-session.test.ts new file mode 100644 index 0000000000..ef3c1935fe --- /dev/null +++ b/trade-finance-manager-ui/server/services/user-session.service.create-logged-in-session.test.ts @@ -0,0 +1,63 @@ +import { TfmSessionUser } from '@ukef/dtfs2-common'; +import { Cookie, Session, SessionData } from 'express-session'; +import { UserSessionService } from './user-session.service'; +import { aTfmSessionUser } from '../../test-helpers'; +import { PartiallyLoggedInUserSessionData } from '../types/express-session'; + +describe('user session service', () => { + describe('createLoggedInSession', () => { + let userSessionService: UserSessionService; + let tfmSessionUser: TfmSessionUser; + let userToken: string; + let session: Session & Partial; + + beforeEach(() => { + userSessionService = new UserSessionService(); + + jest.resetAllMocks(); + + tfmSessionUser = aTfmSessionUser(); + userToken = 'a-token'; + + const partiallyLoggedInUserSessionData = { + loginData: 'some-login-data', + } as unknown as PartiallyLoggedInUserSessionData; + + session = { + ...partiallyLoggedInUserSessionData, + id: 'mock-id', + cookie: {} as Cookie, + regenerate: jest.fn(), + destroy: jest.fn(), + reload: jest.fn(), + save: jest.fn(), + touch: jest.fn(), + resetMaxAge: jest.fn(), + }; + }); + + it('deletes existing partially logged in session', () => { + // Act + userSessionService.createLoggedInSession({ session, user: tfmSessionUser, userToken }); + + // Assert + expect(session.loginData).toBeUndefined(); + }); + + it('sets session.user to user', () => { + // Act + userSessionService.createLoggedInSession({ session, user: tfmSessionUser, userToken }); + + // Assert + expect(session.user).toEqual(tfmSessionUser); + }); + + it('sets session.userToken to userToken', () => { + // Act + userSessionService.createLoggedInSession({ session, user: tfmSessionUser, userToken }); + + // Assert + expect(session.userToken).toEqual(userToken); + }); + }); +}); diff --git a/trade-finance-manager-ui/server/services/user-session.service.create-partially-logged-in-session.test.ts b/trade-finance-manager-ui/server/services/user-session.service.create-partially-logged-in-session.test.ts new file mode 100644 index 0000000000..cf40293f81 --- /dev/null +++ b/trade-finance-manager-ui/server/services/user-session.service.create-partially-logged-in-session.test.ts @@ -0,0 +1,56 @@ +import { Cookie, Session, SessionData } from 'express-session'; +import { AuthorizationUrlRequest } from '@azure/msal-node'; +import { UserSessionService } from './user-session.service'; +import { aTfmSessionUser } from '../../test-helpers'; +import { UserSessionData } from '../types/express-session'; + +describe('user session service', () => { + describe('createPartiallyLoggedInSession', () => { + let userSessionService: UserSessionService; + let authCodeUrlRequest: AuthorizationUrlRequest; + let session: Session & Partial; + let userSessionData: UserSessionData; + + beforeEach(() => { + userSessionService = new UserSessionService(); + + jest.resetAllMocks(); + + authCodeUrlRequest = 'mock-auth-code-url-request' as unknown as AuthorizationUrlRequest; + + userSessionData = { + user: aTfmSessionUser(), + userToken: 'a-token', + }; + + session = { + ...userSessionData, + id: 'mock-id', + cookie: {} as Cookie, + regenerate: jest.fn(), + destroy: jest.fn(), + reload: jest.fn(), + save: jest.fn(), + touch: jest.fn(), + resetMaxAge: jest.fn(), + }; + }); + + it('deletes existing partially logged in session', () => { + // Act + userSessionService.createPartiallyLoggedInSession({ session, authCodeUrlRequest }); + + // Assert + expect(session.user).toBeUndefined(); + expect(session.userToken).toBeUndefined(); + }); + + it('sets session.loginData to the login data', () => { + // Act + userSessionService.createPartiallyLoggedInSession({ session, authCodeUrlRequest }); + + // Assert + expect(session.loginData).toEqual({ authCodeUrlRequest }); + }); + }); +}); From 9ef9ed3f1d627937bd3c9890af1c3447ed143cb4 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 11 Dec 2024 15:43:30 +0000 Subject: [PATCH 065/133] feat(dtfs2-6892): update login controller to handle errors --- .../controllers/login/login-sso/login.controller.ts | 12 +++++++----- .../server/routes/login/configs/login-sso.ts | 6 ++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts index fbafa9b16a..2642a359a5 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts @@ -1,4 +1,4 @@ -import { NextFunction, Request, Response } from 'express'; +import { Request, Response } from 'express'; import { HandleSsoRedirectFormUiRequest, InvalidPayloadError } from '@ukef/dtfs2-common'; import { isVerifiedPayload } from '@ukef/dtfs2-common/payload-verification'; import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from '@ukef/dtfs2-common/schemas'; @@ -16,7 +16,7 @@ export class LoginController { this.userSessionService = userSessionService; } - public async getLogin(req: Request, res: Response, next: NextFunction) { + public async getLogin(req: Request, res: Response) { try { // TODO: This validation is legacy code, and can be improved if (req.session.user) { @@ -30,11 +30,12 @@ export class LoginController { return res.redirect(authCodeUrl); } catch (error) { - return next(error); + console.error('Unable to log in user: %O', error); + return res.render('_partials/problem-with-service.njk'); } } - async handleSsoRedirectForm(req: HandleSsoRedirectFormUiRequest, res: Response, next: NextFunction) { + async handleSsoRedirectForm(req: HandleSsoRedirectFormUiRequest, res: Response) { try { const { body, session } = req; const partiallyLoggedInSession = asPartiallyLoggedInUserSession(session); @@ -54,7 +55,8 @@ export class LoginController { return res.redirect(successRedirect ?? '/'); } catch (error) { - return next(error); + console.error('Unable to redirect user after login: %O', error); + return res.render('_partials/problem-with-service.njk'); } } diff --git a/trade-finance-manager-ui/server/routes/login/configs/login-sso.ts b/trade-finance-manager-ui/server/routes/login/configs/login-sso.ts index 47d1c70a6e..6961b33342 100644 --- a/trade-finance-manager-ui/server/routes/login/configs/login-sso.ts +++ b/trade-finance-manager-ui/server/routes/login/configs/login-sso.ts @@ -2,13 +2,15 @@ import express from 'express'; import { LoginController } from '../../../controllers/login/login-sso/login.controller'; import { GetRouter } from '../../../types/get-router'; import { LoginService } from '../../../services/login.service'; +import { UserSessionService } from '../../../services/user-session.service'; export const getLoginSsoRouter: GetRouter = () => { const loginService = new LoginService(); - const loginController = new LoginController({ loginService }); + const userSessionService = new UserSessionService(); + const loginController = new LoginController({ loginService, userSessionService }); const loginSsoRouter = express.Router(); // eslint-disable-next-line @typescript-eslint/no-misused-promises - loginSsoRouter.get('/', (req, res, next) => loginController.getLogin(req, res, next)); + loginSsoRouter.get('/', (req, res) => loginController.getLogin(req, res)); return loginSsoRouter; }; From f70d97f8d47190e25c9c9b9d08f20c79ba41e409 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 11 Dec 2024 17:05:24 +0000 Subject: [PATCH 066/133] feat(dtfs2-6892): add login service handle sso redirect form tests --- ...n.service.handle-sso-redirect-form.test.ts | 54 +++++++++++++++++++ .../handle-sso-redirect-form-request.ts | 8 +++ .../test-helpers/test-data/index.ts | 1 + 3 files changed, 63 insertions(+) create mode 100644 trade-finance-manager-ui/server/services/login.service.handle-sso-redirect-form.test.ts create mode 100644 trade-finance-manager-ui/test-helpers/test-data/handle-sso-redirect-form-request.ts diff --git a/trade-finance-manager-ui/server/services/login.service.handle-sso-redirect-form.test.ts b/trade-finance-manager-ui/server/services/login.service.handle-sso-redirect-form.test.ts new file mode 100644 index 0000000000..5567407cbc --- /dev/null +++ b/trade-finance-manager-ui/server/services/login.service.handle-sso-redirect-form.test.ts @@ -0,0 +1,54 @@ +import { HandleSsoRedirectFormResponse } from '@ukef/dtfs2-common'; +import { aHandleSsoRedirectFormRequest, aHandleSsoRedirectFormResponse } from '../../test-helpers'; +import { LoginService } from './login.service'; +import * as api from '../api'; + +jest.mock('../api'); + +describe('login service', () => { + describe('handleSsoRedirectForm', () => { + const handleSsoRedirectFormSpy = jest.spyOn(api, 'handleSsoRedirectForm'); + const loginService = new LoginService(); + + afterEach(() => { + handleSsoRedirectFormSpy.mockReset(); + }); + + it('calls api.handleSsoRedirectForm with the request', async () => { + // Act + await loginService.handleSsoRedirectForm(aHandleSsoRedirectFormRequest()); + + // Assert + expect(handleSsoRedirectFormSpy).toHaveBeenCalledWith(aHandleSsoRedirectFormRequest()); + }); + + describe('when the handleSsoRedirectForm api call is successful', () => { + const handleSsoRedirectFormResponse: HandleSsoRedirectFormResponse = aHandleSsoRedirectFormResponse(); + + beforeEach(() => { + handleSsoRedirectFormSpy.mockResolvedValueOnce(handleSsoRedirectFormResponse); + }); + + it('returns the auth code url', async () => { + // Act + const result = await loginService.handleSsoRedirectForm(aHandleSsoRedirectFormRequest()); + + // Assert + expect(result).toEqual(handleSsoRedirectFormResponse); + }); + }); + + describe('when the handleSsoRedirectForm api call is unsuccessful', () => { + const error = new Error('handleSsoRedirectForm error'); + + beforeEach(() => { + handleSsoRedirectFormSpy.mockRejectedValueOnce(error); + }); + + it('throws the error', async () => { + // Act & Assert + await expect(loginService.handleSsoRedirectForm(aHandleSsoRedirectFormRequest())).rejects.toThrow(error); + }); + }); + }); +}); diff --git a/trade-finance-manager-ui/test-helpers/test-data/handle-sso-redirect-form-request.ts b/trade-finance-manager-ui/test-helpers/test-data/handle-sso-redirect-form-request.ts new file mode 100644 index 0000000000..546bf317c6 --- /dev/null +++ b/trade-finance-manager-ui/test-helpers/test-data/handle-sso-redirect-form-request.ts @@ -0,0 +1,8 @@ +import { anAuthorisationCodeRequest, anEntraIdAuthCodeRedirectResponseBody, HandleSsoRedirectFormRequest } from '@ukef/dtfs2-common'; +import { generateSystemAuditDetails } from '@ukef/dtfs2-common/change-stream'; + +export const aHandleSsoRedirectFormRequest = (): HandleSsoRedirectFormRequest => ({ + authCodeResponse: anEntraIdAuthCodeRedirectResponseBody(), + originalAuthCodeUrlRequest: anAuthorisationCodeRequest(), + auditDetails: generateSystemAuditDetails(), +}); diff --git a/trade-finance-manager-ui/test-helpers/test-data/index.ts b/trade-finance-manager-ui/test-helpers/test-data/index.ts index 50ac9f0b87..f22806e533 100644 --- a/trade-finance-manager-ui/test-helpers/test-data/index.ts +++ b/trade-finance-manager-ui/test-helpers/test-data/index.ts @@ -5,3 +5,4 @@ export * from './fee-record-utilisation'; export * from './utilisation-table-row-view-model'; export * from './a-request-session'; export * from './handle-sso-redirect-form-response'; +export * from './handle-sso-redirect-form-request'; From 824411e10db526cd4e522427c34c66a21d342cef Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 12 Dec 2024 15:47:47 +0000 Subject: [PATCH 067/133] feat(dtfs2-6892): add login controller tests --- ...ontroller.handle-sso-redirect-form.test.ts | 231 ++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.handle-sso-redirect-form.test.ts diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.handle-sso-redirect-form.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.handle-sso-redirect-form.test.ts new file mode 100644 index 0000000000..ef04f6e238 --- /dev/null +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.handle-sso-redirect-form.test.ts @@ -0,0 +1,231 @@ +import httpMocks, { MockRequest, MockResponse } from 'node-mocks-http'; +import { + anAuthorisationCodeRequest, + anEntraIdAuthCodeRedirectResponseBody, + AuditDetails, + HandleSsoRedirectFormResponse, + HandleSsoRedirectFormUiRequest, + InvalidPayloadError, + UserPartialLoginDataNotDefinedError, +} from '@ukef/dtfs2-common'; +import { Session, SessionData } from 'express-session'; +import { Response } from 'express'; +import { generateSystemAuditDetails } from '@ukef/dtfs2-common/change-stream'; +import { LoginService } from '../../../services/login.service'; +import { LoginController } from './login.controller'; +import { UserSessionService } from '../../../services/user-session.service'; +import { LoginServiceMockBuilder, UserSessionServiceMockBuilder } from '../../../../test-helpers/mocks'; +import { aHandleSsoRedirectFormResponse, aTfmSessionUser } from '../../../../test-helpers'; +import { PartiallyLoggedInUserSessionData } from '../../../types/express-session'; + +describe('controllers - login (sso)', () => { + describe('handleSsoRedirectForm', () => { + let loginService: LoginService; + let userSessionService: UserSessionService; + let loginController: LoginController; + + const handleSsoRedirectFormMock = jest.fn(); + const createLoggedInSessionMock = jest.fn(); + const consoleErrorMock = jest.fn(); + console.error = consoleErrorMock; + + let res: MockResponse; + let req: MockRequest; + + beforeEach(() => { + jest.resetAllMocks(); + loginService = new LoginServiceMockBuilder().with({ handleSsoRedirectForm: handleSsoRedirectFormMock }).build(); + userSessionService = new UserSessionServiceMockBuilder().with({ createLoggedInSession: createLoggedInSessionMock }).build(); + loginController = new LoginController({ loginService, userSessionService }); + }); + + describe('when the user does not have an active partially logged in session', () => { + describe.each([ + { + description: 'no user data', + session: {}, + }, + { + description: 'logged in user data', + session: { + user: aTfmSessionUser(), + userToken: 'token', + }, + }, + ])('when the session has $description', ({ session }) => { + beforeEach(() => { + ({ req, res } = getHttpMocks({ + session: session as Session & Partial, + body: aValidRequestBody(), + })); + }); + + it('logs an error', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(consoleErrorMock).toHaveBeenCalledWith('Unable to redirect user after login: %O', expect.any(UserPartialLoginDataNotDefinedError)); + }); + + it('renders the problem with service page', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(res._getRenderView()).toEqual('_partials/problem-with-service.njk'); + }); + }); + }); + + describe('when the user has an active partially logged in session', () => { + let validSession: PartiallyLoggedInUserSessionData; + + beforeEach(() => { + validSession = { + loginData: { + authCodeUrlRequest: anAuthorisationCodeRequest(), + }, + }; + }); + describe('when the payload is not verified', () => { + beforeEach(() => { + ({ req, res } = getHttpMocks({ + session: validSession as Session & Partial, + body: { invalidBody: 'invalid-body' } as unknown as HandleSsoRedirectFormUiRequest['body'], + })); + }); + + it('logs an error', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(consoleErrorMock).toHaveBeenCalledWith('Unable to redirect user after login: %O', expect.any(InvalidPayloadError)); + }); + + it('renders the problem with service page', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(res._getRenderView()).toEqual('_partials/problem-with-service.njk'); + }); + }); + + describe('when the payload is verified', () => { + beforeEach(() => { + ({ req, res } = getHttpMocks({ + session: validSession as Session & Partial, + body: aValidRequestBody(), + })); + }); + + it('calls loginService.handleSsoRedirectForm with the correct parameters', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(handleSsoRedirectFormMock).toHaveBeenCalledWith({ + authCodeResponse: req.body, + originalAuthCodeUrlRequest: validSession.loginData.authCodeUrlRequest, + auditDetails: expect.objectContaining(generateSystemAuditDetails()) as AuditDetails, + }); + }); + + it('calls loginService.handleSsoRedirectForm once', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(handleSsoRedirectFormMock).toHaveBeenCalledTimes(1); + }); + + describe('when loginService.handleSsoRedirectForm returns an error', () => { + let thrownError: Error; + + beforeEach(() => { + thrownError = new Error('handleSsoRedirectForm error'); + handleSsoRedirectFormMock.mockRejectedValueOnce(thrownError); + }); + + it('logs an error', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(consoleErrorMock).toHaveBeenCalledWith('Unable to redirect user after login: %O', thrownError); + }); + + it('renders the problem with service page', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(res._getRenderView()).toEqual('_partials/problem-with-service.njk'); + }); + }); + + describe('when loginService.handleSsoRedirectForm resolves', () => { + describe('when the success redirect parameter is not set', () => { + let handleSsoRedirectFormResponse: HandleSsoRedirectFormResponse; + + beforeEach(() => { + handleSsoRedirectFormResponse = aHandleSsoRedirectFormResponse(); + delete handleSsoRedirectFormResponse.successRedirect; + handleSsoRedirectFormMock.mockResolvedValueOnce(handleSsoRedirectFormResponse); + }); + + it('updates the session', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(createLoggedInSessionMock).toHaveBeenCalledWith({ + session: req.session, + user: handleSsoRedirectFormResponse.user, + userToken: handleSsoRedirectFormResponse.token, + }); + }); + + it('updates the session once', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(createLoggedInSessionMock).toHaveBeenCalledTimes(1); + }); + + it('redirects to the home page', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(res._getRedirectUrl()).toEqual('/'); + }); + }); + + describe('when the success redirect parameter is set', () => { + let handleSsoRedirectFormResponse: HandleSsoRedirectFormResponse; + + beforeEach(() => { + handleSsoRedirectFormResponse = aHandleSsoRedirectFormResponse(); + handleSsoRedirectFormMock.mockResolvedValueOnce(handleSsoRedirectFormResponse); + }); + + it('updates the session', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(createLoggedInSessionMock).toHaveBeenCalledWith({ + session: req.session, + user: handleSsoRedirectFormResponse.user, + userToken: handleSsoRedirectFormResponse.token, + }); + }); + + it('updates the session once', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(createLoggedInSessionMock).toHaveBeenCalledTimes(1); + }); + + it('redirects to the success redirect parameter', async () => { + await loginController.handleSsoRedirectForm(req, res); + + expect(res._getRedirectUrl()).toEqual(handleSsoRedirectFormResponse.successRedirect); + }); + }); + }); + }); + }); + + function getHttpMocks({ session, body }: { session: Session & Partial; body: HandleSsoRedirectFormUiRequest['body'] }) { + return httpMocks.createMocks({ + session, + body, + }); + } + + function aValidRequestBody(): HandleSsoRedirectFormUiRequest['body'] { + return anEntraIdAuthCodeRedirectResponseBody(); + } + }); +}); From 6f04dad88f1067a8287768c740019156ce294f91 Mon Sep 17 00:00:00 2001 From: Alex <47955140+AlexBramhill@users.noreply.github.com> Date: Thu, 2 Jan 2025 09:56:51 +0000 Subject: [PATCH 068/133] feat(DTFS2-6892): update mock builder to require complete defaults, add documentation demonstrating how to pass through mocked class methods (#4067) Co-authored-by: Abhi Markan --- .../src/test-helpers/mock-builders/index.ts | 2 +- .../mock-builder.mock.builder.ts | 103 ---------------- .../mock-builders/mock-builder.ts | 111 ++++++++++++++++++ .../builders/user.service.mock.builder.ts | 10 +- .../sso.controller.get-auth-code-url.test.ts | 4 +- ...ontroller.handle-sso-redirect-form.test.ts | 3 +- ...entra-id.service.get-auth-code-url.test.ts | 3 +- 7 files changed, 125 insertions(+), 111 deletions(-) delete mode 100644 libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts create mode 100644 libs/common/src/test-helpers/mock-builders/mock-builder.ts diff --git a/libs/common/src/test-helpers/mock-builders/index.ts b/libs/common/src/test-helpers/mock-builders/index.ts index 3bf4521b97..babe587833 100644 --- a/libs/common/src/test-helpers/mock-builders/index.ts +++ b/libs/common/src/test-helpers/mock-builders/index.ts @@ -1 +1 @@ -export * from './mock-builder.mock.builder'; +export * from './mock-builder'; diff --git a/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts b/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts deleted file mode 100644 index 0e74fb97df..0000000000 --- a/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Class to allow for the passing of jest fns into methods on the class object - */ -type Mocked = { - [K in keyof T]: T[K] extends (...args: any[]) => infer R ? jest.Mock> | T[K] : T[K]; -}; - -/** - * Base class for class test data builders. Pass in default values for the class - * instance so that you can build an instance with those values and override - * them as needed. - * - * @example - * ```ts - * // Basic builder - * export class UserMockBuilder extends BaseMockBuilder { - * constructor() { - * super({ - * defaultInstance: { - * id: '72f9ca55-943a-401d-a04c-0a9e03ac7f18', - * name: 'Joe Bloggs', - * email: 'joe.bloggs@ukef.gov.uk', - * }, - * }) - * } - * } - * ``` - * - * @example - * ```ts - * // With custom static factory methods to initialise the builder - * export class UserMockBuilder extends BaseMockBuilder { - * constructor(defaultInstance?: User) { - * super({ - * defaultInstance: defaultInstance ?? { - * id: '72f9ca55-943a-401d-a04c-0a9e03ac7f18', - * name: 'Joe Bloggs', - * email: 'joe.bloggs@ukef.gov.uk', - * }, - * }) - * } - * - * public static fromEntity(entity: UserEntity): UserMockBuilder { - * return new UserMockBuilder({ - * id: entity.id, - * name: entity.name, - * email: entity.email, - * }) - * } - * } - * ``` - * - * @example - * ```ts - * // Usage in a test where we just need a valid class instance - * const user = new UserMockBuilder().build() - * ``` - * - * @example - * ```ts - * // Usage in a test where a field needs to have a specific value - * const user = new UserMockBuilder() - * .with({ email: 'new.email@ukef.gov.uk' }) - * .build() - * ``` - */ -export abstract class BaseMockBuilder { - private readonly defaults: Partial>; - private readonly instance: Mocked = {} as Mocked; - - protected constructor(config: { defaultInstance: Partial> }) { - this.defaults = config.defaultInstance; - } - - /** - * Set fields on the class instance when you want to override the defaults - * - * @example - * ```ts - * const user = new UserMockBuilder() - * .with({ - * email: 'new.email@ukef.gov.uk', - * givenName: 'Fred', - * }) - * .build() - * ``` - */ - public with(values: Partial>): BaseMockBuilder { - Object.assign(this.instance, values); - return this; - } - - public withDefaults(): BaseMockBuilder { - return this.with(this.defaults); - } - - /** - * Build the class instance after setting any fields - */ - public build(): TClass { - return this.instance as TClass; - } -} diff --git a/libs/common/src/test-helpers/mock-builders/mock-builder.ts b/libs/common/src/test-helpers/mock-builders/mock-builder.ts new file mode 100644 index 0000000000..b4ddaf2e59 --- /dev/null +++ b/libs/common/src/test-helpers/mock-builders/mock-builder.ts @@ -0,0 +1,111 @@ +/** + * Class to allow for the passing of jest fns into methods on the class object + */ +type Mocked = { + [K in keyof T]: T[K] extends (...args: any[]) => infer R ? jest.Mock> | T[K] : T[K]; +}; + +/** + * Base class for class test data and class builders. + * + * Pass in default values for the instance so that you can build an instance with + * those values and override them as needed. + * + * @example + * Overriding the defaults of a type: + * ```ts + * // user.mock.builder.ts + * // Basic builder for a type + * export class UserMockBuilder extends BaseMockBuilder { + * constructor() { + * super({ + * defaultInstance: { + * id: '72f9ca55-943a-401d-a04c-0a9e03ac7f18', + * name: 'Joe Bloggs', + * email: 'joe.bloggs@ukef.gov.uk', + * }, + * }) + * } + * } + * + * // a-test-file.test.ts + * // Usage in a test where we just need a valid class instance + * const user = new UserMockBuilder().build() + * + * // another-test-file.test.ts + * // Usage in a test where a field needs to have a specific value + * const user = new UserMockBuilder() + * .with({ email: 'new.email@ukef.gov.uk' }) + * .build() + * ``` + + * + * @example + * Overriding the defaults of a class: + * ```ts + * export class LoginServiceMockBuilder extends BaseMockBuilder { + * constructor() { + * super({ + * defaultInstance: { + * getAuthCodeUrl: jest.fn(async () => { + * return Promise.resolve({ + * authCodeUrl: 'a-auth-code-url', + * authCodeUrlRequest: {} as AuthorizationCodeRequest, + * }); + * }), + * }, + * }); + * } + * } + * ``` + * + * Keeping existing implimentations of a class: + * ```ts + * export class UserServiceMockBuilder extends BaseMockBuilder { + * constructor() { + * const userService = new UserService(); // This can be used as a way to inherit methods we do not wish to mock the implimentation for + * super({ + * defaultInstance: { + * transformEntraIdUserToUpsertTfmUserRequest(entraIdUser: EntraIdUser): UpsertTfmUserRequest { + * return userService.transformEntraIdUserToUpsertTfmUserRequest(entraIdUser); + * }, + * saveUserLoginInformation({ userId, sessionIdentifier, auditDetails }: saveUserLoginInformationParams): Promise { + * return Promise.resolve(); + * }, + * }, + * }); + * } + * ``` + */ +export abstract class BaseMockBuilder { + private readonly instance: Mocked = {} as Mocked; + + protected constructor(config: { defaultInstance: Mocked }) { + this.with(config.defaultInstance); + } + + /** + * Set fields on the class instance when you want to override the defaults + * + * @example + * ```ts + * const user = new UserMockBuilder() + * .with({ + * email: 'new.email@ukef.gov.uk', + * givenName: 'Fred', + * }) + * .build() + * ``` + */ + public with(values: Partial>): BaseMockBuilder { + Object.assign(this.instance, values); + return this; + } + + /** + * Build the class instance after setting any fields + */ + public build(): TClass { + return this.instance as TClass; + } +} diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts index 12af34a2a8..4c084e83f6 100644 --- a/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { BaseMockBuilder } from '@ukef/dtfs2-common'; +import { BaseMockBuilder, EntraIdUser, UpsertTfmUserRequest } from '@ukef/dtfs2-common'; import { aTfmUser } from '@ukef/dtfs2-common/mock-data-backend'; import { UpsertTfmUserFromEntraIdUserParams, @@ -10,8 +10,16 @@ import { export class UserServiceMockBuilder extends BaseMockBuilder { constructor() { + const userService = new UserService(); super({ defaultInstance: { + /** + * We pass through the actual service implementation for the below method here, + * as it is an existing pattern that we never mock synchronous methods. + */ + transformEntraIdUserToUpsertTfmUserRequest(entraIdUser: EntraIdUser): UpsertTfmUserRequest { + return userService.transformEntraIdUserToUpsertTfmUserRequest(entraIdUser); + }, upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }: UpsertTfmUserFromEntraIdUserParams): Promise { return Promise.resolve(aTfmUser()); }, diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts index 74bb4ba60c..d86d2a3086 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts @@ -31,8 +31,8 @@ describe('SsoController', () => { beforeEach(() => { jest.resetAllMocks(); - entraIdService = new EntraIdServiceMockBuilder().withDefaults().with({ getAuthCodeUrl: getAuthCodeUrlMock }).build(); - userService = new UserServiceMockBuilder().withDefaults().build(); + entraIdService = new EntraIdServiceMockBuilder().with({ getAuthCodeUrl: getAuthCodeUrlMock }).build(); + userService = new UserServiceMockBuilder().build(); ssoController = new SsoController({ entraIdService, userService }); ({ req, res } = getHttpMocks()); diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts index 15a4f9af09..791ad94a0b 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts @@ -38,9 +38,8 @@ describe('SsoController', () => { beforeEach(() => { jest.resetAllMocks(); - entraIdService = new EntraIdServiceMockBuilder().withDefaults().with({ handleRedirect: handleRedirectMock }).build(); + entraIdService = new EntraIdServiceMockBuilder().with({ handleRedirect: handleRedirectMock }).build(); userService = new UserServiceMockBuilder() - .withDefaults() .with({ upsertTfmUserFromEntraIdUser: upsertTfmUserFromEntraIdUserMock, saveUserLoginInformation: saveUserLoginInformationMock }) .build(); ssoController = new SsoController({ entraIdService, userService }); diff --git a/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts index c3b05025f8..d3740153fb 100644 --- a/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts +++ b/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts @@ -35,7 +35,6 @@ describe('EntraIdService', () => { }); entraIdConfig = new EntraIdConfigMockBuilder() - .withDefaults() .with({ authorityMetadataUrl: mockAuthorityMetaDataUrl, scopes: mockScope, @@ -43,7 +42,7 @@ describe('EntraIdService', () => { }) .build(); - entraIdApi = new EntraIdApiMockBuilder().withDefaults().build(); + entraIdApi = new EntraIdApiMockBuilder().build(); }); it('calls base64Encode with the expected stringifiedstate', async () => { From 15ecd101866da5b5a5a1db43f24076f3fdc10c93 Mon Sep 17 00:00:00 2001 From: Alex <47955140+AlexBramhill@users.noreply.github.com> Date: Thu, 2 Jan 2025 10:10:45 +0000 Subject: [PATCH 069/133] feat(DTFS2-6892): add object id schema for tfm sso (#4027) --- .../src/schemas/audit-database-record.ts | 6 +- libs/common/src/schemas/index.ts | 2 +- libs/common/src/schemas/object-id.test.ts | 67 ++++++++++++++++--- libs/common/src/schemas/object-id.ts | 24 ++++++- 4 files changed, 85 insertions(+), 14 deletions(-) diff --git a/libs/common/src/schemas/audit-database-record.ts b/libs/common/src/schemas/audit-database-record.ts index 925e444e0d..2894884704 100644 --- a/libs/common/src/schemas/audit-database-record.ts +++ b/libs/common/src/schemas/audit-database-record.ts @@ -1,12 +1,12 @@ import z from 'zod'; import { ISO_DATE_TIME_STAMP } from './iso-date-time-stamp'; -import { OBJECT_ID } from './object-id'; +import { OBJECT_ID_OR_OBJECT_ID_STRING } from './object-id'; export const AUDIT_DATABASE_RECORD = z .object({ lastUpdatedAt: ISO_DATE_TIME_STAMP, - lastUpdatedByPortalUserId: OBJECT_ID.nullable(), - lastUpdatedByTfmUserId: OBJECT_ID.nullable(), + lastUpdatedByPortalUserId: OBJECT_ID_OR_OBJECT_ID_STRING.nullable(), + lastUpdatedByTfmUserId: OBJECT_ID_OR_OBJECT_ID_STRING.nullable(), lastUpdatedByIsSystem: z.boolean().nullable(), noUserLoggedIn: z.boolean().nullable(), }) diff --git a/libs/common/src/schemas/index.ts b/libs/common/src/schemas/index.ts index 4b4c497f83..36b221f5f6 100644 --- a/libs/common/src/schemas/index.ts +++ b/libs/common/src/schemas/index.ts @@ -1,6 +1,6 @@ export * as PORTAL_USER from './portal-user'; export * as ISO_DATE_TIME_STAMP from './iso-date-time-stamp'; -export { OBJECT_ID } from './object-id'; +export * from './object-id'; export * from './deal-cancellation'; export * from './tfm'; export * from './unix-timestamp.schema'; diff --git a/libs/common/src/schemas/object-id.test.ts b/libs/common/src/schemas/object-id.test.ts index 63182d35fc..f325e00d1c 100644 --- a/libs/common/src/schemas/object-id.test.ts +++ b/libs/common/src/schemas/object-id.test.ts @@ -1,23 +1,72 @@ import { ObjectId } from 'mongodb'; -import { OBJECT_ID } from './object-id'; +import { OBJECT_ID, OBJECT_ID_OR_OBJECT_ID_STRING, OBJECT_ID_STRING } from './object-id'; import { withSchemaTests } from '../test-helpers'; describe('OBJECT_ID', () => { withSchemaTests({ - successTestCases: getSuccessTestCases(), - failureTestCases: getFailureTestCases(), + successTestCases: getObjectIdSuccessTestCases(), + failureTestCases: getObjectIdSharedFailureTestCases(), schema: OBJECT_ID, }); + + it('should transform a valid string ObjectId to an ObjectId', () => { + const stringObjectId = new ObjectId().toString(); + + const result = OBJECT_ID.parse(stringObjectId); + + expect(result).toEqual(new ObjectId(stringObjectId)); + }); }); -function getSuccessTestCases() { - return [ - { description: 'a valid ObjectId', aTestCase: () => new ObjectId() }, - { description: 'a valid string ObjectId', aTestCase: () => '075bcd157dcb851180e02a7c' }, - ]; +describe('OBJECT_ID_STRING', () => { + withSchemaTests({ + successTestCases: getObjectIdStringSuccessTestCases(), + failureTestCases: getObjectIdSharedFailureTestCases(), + schema: OBJECT_ID_STRING, + }); + + it('should transform a valid ObjectId to a string', () => { + const objectId = new ObjectId(); + + const result = OBJECT_ID_STRING.parse(objectId); + + expect(result).toEqual(objectId.toString()); + }); +}); + +describe('OBJECT_ID_OR_OBJECT_ID_STRING', () => { + withSchemaTests({ + successTestCases: [...getObjectIdSuccessTestCases(), ...getObjectIdStringSuccessTestCases()], + failureTestCases: getObjectIdSharedFailureTestCases(), + schema: OBJECT_ID_OR_OBJECT_ID_STRING, + }); + + it('should not transform a valid ObjectId to a string', () => { + const objectId = new ObjectId(); + + const result = OBJECT_ID_OR_OBJECT_ID_STRING.parse(objectId); + + expect(result).toEqual(objectId); + }); + + it('should not transform a valid string ObjectId to an ObjectId', () => { + const stringObjectId = new ObjectId().toString(); + + const result = OBJECT_ID_OR_OBJECT_ID_STRING.parse(stringObjectId); + + expect(result).toEqual(stringObjectId); + }); +}); + +function getObjectIdSuccessTestCases() { + return [{ description: 'a valid ObjectId', aTestCase: () => new ObjectId() }]; +} + +function getObjectIdStringSuccessTestCases() { + return [{ description: 'a valid string ObjectId', aTestCase: () => '075bcd157dcb851180e02a7c' }]; } -function getFailureTestCases() { +function getObjectIdSharedFailureTestCases() { return [ { description: 'a string', aTestCase: () => 'string' }, { description: 'an object', aTestCase: () => ({ An: 'object' }) }, diff --git a/libs/common/src/schemas/object-id.ts b/libs/common/src/schemas/object-id.ts index 89d2ca9c0c..5e9d5b5e92 100644 --- a/libs/common/src/schemas/object-id.ts +++ b/libs/common/src/schemas/object-id.ts @@ -1,4 +1,26 @@ import { ObjectId } from 'mongodb'; import z from 'zod'; -export const OBJECT_ID = z.union([z.instanceof(ObjectId), z.string().refine((id) => ObjectId.isValid(id))]); +/** + * A zod schema that represents a valid ObjectId as an ObjectId object + * This schema also transforms any valid string into an ObjectId object + */ +export const OBJECT_ID = z.union([ + z.instanceof(ObjectId), + z + .string() + .refine((id) => ObjectId.isValid(id)) + .transform((id) => new ObjectId(id)), +]); + +/** + * A zod schema that represents a valid ObjectId as a string + * This schema also transforms any valid ObjectId object into a string + */ +export const OBJECT_ID_STRING = z.union([z.string().refine((id) => ObjectId.isValid(id)), z.instanceof(ObjectId).transform((id) => id.toString())]); + +/** + * A zod schema that represents a valid ObjectId as an ObjectId object or a string + * This schema does not do any transformation + */ +export const OBJECT_ID_OR_OBJECT_ID_STRING = z.union([z.instanceof(ObjectId), OBJECT_ID_STRING, z.string().refine((id) => ObjectId.isValid(id))]); From 46c85f5f635096dd1a6781f9078c5ee2a6755108 Mon Sep 17 00:00:00 2001 From: Abhi Markan Date: Fri, 3 Jan 2025 11:17:08 +0000 Subject: [PATCH 070/133] feat(DTFS2-6892): refactored code --- .../src/test-helpers/mock-builders/index.ts | 2 +- ...uilder.mock.builder.ts => mock-builder.ts} | 0 trade-finance-manager-ui/server/api.js | 15 ++++-- .../login-non-sso/index.post-login.test.ts | 4 +- .../controllers/login/login-non-sso/index.ts | 2 +- .../login.controller.get-login.test.ts | 4 +- .../login/login-sso/login.controller.ts | 51 ++++++++++++++++--- .../middleware/validateUserTeam/index.test.ts | 4 +- .../middleware/validateUserTeam/index.ts | 4 +- .../server/routes/auth/configs/auth-sso.ts | 6 +++ .../server/routes/login/configs/login-sso.ts | 5 +- 11 files changed, 75 insertions(+), 22 deletions(-) rename libs/common/src/test-helpers/mock-builders/{mock-builder.mock.builder.ts => mock-builder.ts} (100%) diff --git a/libs/common/src/test-helpers/mock-builders/index.ts b/libs/common/src/test-helpers/mock-builders/index.ts index 3bf4521b97..babe587833 100644 --- a/libs/common/src/test-helpers/mock-builders/index.ts +++ b/libs/common/src/test-helpers/mock-builders/index.ts @@ -1 +1 @@ -export * from './mock-builder.mock.builder'; +export * from './mock-builder'; diff --git a/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts b/libs/common/src/test-helpers/mock-builders/mock-builder.ts similarity index 100% rename from libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts rename to libs/common/src/test-helpers/mock-builders/mock-builder.ts diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index 2693eb288a..3ac5daa450 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -1,4 +1,5 @@ const axios = require('axios'); +const { HttpStatusCode } = require('axios'); const { HEADERS } = require('@ukef/dtfs2-common'); const { isValidMongoId, isValidPartyUrn, isValidGroupId, isValidTaskId, isValidBankId } = require('./helpers/validateIds'); const { assertValidIsoMonth, assertValidIsoYear } = require('./helpers/date'); @@ -422,9 +423,10 @@ const login = async (username, password) => { }; /** + * Handles the SSO redirect form request by sending a POST request to the TFM API. * - * @param {import('@ukef/dtfs2-common').HandleSsoRedirectFormRequest} handleSsoRedirectFormRequest - * @returns {Promise} + * @param {object} handleSsoRedirectFormRequest - The request payload. Shape from import('@ukef/dtfs2-common').HandleSsoRedirectFormRequest + * @returns {Promise} A promise resolving to the response object. Shape from import('@ukef/dtfs2-common').HandleSsoRedirectFormResponse */ const handleSsoRedirectForm = async (handleSsoRedirectFormRequest) => { try { @@ -437,10 +439,15 @@ const handleSsoRedirectForm = async (handleSsoRedirectFormRequest) => { data: handleSsoRedirectFormRequest, }); + if (!response) { + console.error('Invalid response received %o', response); + return { status: HttpStatusCode.BadGateway, data: 'Invalid response received' }; + } + return response.data; } catch (error) { - console.error('Unable to log in %o', error?.response?.data); - return { status: error?.response?.status || 500, data: 'Failed to login' }; + console.error('An exception has occurred while handling TFM SSO %o', error?.response?.data); + return { status: error?.response?.status || HttpStatusCode.InternalServerError, data: 'Failed to login' }; } }; diff --git a/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.post-login.test.ts b/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.post-login.test.ts index 83e3b7a4c6..7b194b0e63 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.post-login.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.post-login.test.ts @@ -55,7 +55,7 @@ describe('controllers - login (sso)', () => { }); }); - it('should redirect to /home if login successful', async () => { + it('should redirect to /deals if login successful', async () => { // Arrange const { req, res } = httpMocks.createMocks({ session: {}, @@ -66,7 +66,7 @@ describe('controllers - login (sso)', () => { await postLogin(req, res); // Assert - expect(res._getRedirectUrl()).toEqual('/home'); + expect(res._getRedirectUrl()).toEqual('/deals'); }); }); }); diff --git a/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.ts b/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.ts index 64a5668bb1..786ab1eaa4 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.ts @@ -48,7 +48,7 @@ export const postLogin = async (req: CustomExpressRequest<{ reqBody: { email?: s }); } - return res.redirect('/home'); + return res.redirect('/deals'); }; export const logout = (req: Request, res: Response) => { diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts index 4a4663ed91..25317ac173 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts @@ -39,7 +39,7 @@ describe('controllers - login (sso)', () => { session: requestSession, }); - it('redirects to /home', async () => { + it('redirects to /deals', async () => { // Arrange const { req, res } = getHttpMocks(); @@ -47,7 +47,7 @@ describe('controllers - login (sso)', () => { await loginController.getLogin(req, res, next); // Assert - expect(res._getRedirectUrl()).toEqual('/home'); + expect(res._getRedirectUrl()).toEqual('/deals'); }); }); diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts index 2642a359a5..819d029f44 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts @@ -16,12 +16,26 @@ export class LoginController { this.userSessionService = userSessionService; } + /** + * Handles the login process for the user. + * + * This method checks if the user is already logged in by inspecting the session. + * If the user is logged in, they are redirected to the '/deals' page. + * If the user is not logged in, it retrieves the authentication code URL and + * creates a partially logged-in session before redirecting the user to the authentication URL. + * + * @param {Request} req - The HTTP request object. + * @param {Response} res - The HTTP response object. + * @returns {Promise} - A promise that resolves when the login process is complete. + * + * @throws Will render a problem with service page if an error occurs during the login process. + */ public async getLogin(req: Request, res: Response) { try { - // TODO: This validation is legacy code, and can be improved - if (req.session.user) { + // TODO DTFS2-7734: This validation is legacy code, and can be improved + if (req?.session?.user) { // User is already logged in. - return res.redirect('/home'); + return res.redirect('/deals'); } const { authCodeUrl, authCodeUrlRequest } = await this.loginService.getAuthCodeUrl({ successRedirect: '/' }); @@ -30,11 +44,24 @@ export class LoginController { return res.redirect(authCodeUrl); } catch (error) { - console.error('Unable to log in user: %O', error); + console.error('Unable to log in user %o', error); return res.render('_partials/problem-with-service.njk'); } } + /** + * Handles the SSO redirect form submission. + * + * This method processes the SSO redirect form, verifies the payload, and logs in the user. + * If the payload is invalid, it throws an `InvalidPayloadError`. + * On successful login, it redirects the user to the specified URL or the home page. + * In case of an error, it logs the error and renders a problem with service page. + * + * @param {HandleSsoRedirectFormUiRequest} req - The request object containing the form data and session. + * @param {Response} res - The response object used to redirect or render a page. + * @returns {Promise} - A promise that resolves when the operation is complete. + * @throws {InvalidPayloadError} - If the payload from the SSO redirect is invalid. + */ async handleSsoRedirectForm(req: HandleSsoRedirectFormUiRequest, res: Response) { try { const { body, session } = req; @@ -51,17 +78,29 @@ export class LoginController { auditDetails, }); + const url = successRedirect ?? '/'; + this.userSessionService.createLoggedInSession({ session, user, userToken: token }); - return res.redirect(successRedirect ?? '/'); + return res.redirect(url); } catch (error) { - console.error('Unable to redirect user after login: %O', error); + console.error('Unable to redirect the user after login %o', error); return res.render('_partials/problem-with-service.njk'); } } // TODO DTFS2-6892: Update this logout handling + /** + * Handles the logout process for the user. + * + * This method logs out the user from the Trade Finance Manager (TFM) application. + * It destroys the user's session and redirects them to the home page. + * + * @param req - The HTTP request object. + * @param res - The HTTP response object. + */ public getLogout = (req: Request, res: Response) => { + console.info('User has been logged out from TFM'); req.session.destroy(() => { res.redirect('/'); }); diff --git a/trade-finance-manager-ui/server/middleware/validateUserTeam/index.test.ts b/trade-finance-manager-ui/server/middleware/validateUserTeam/index.test.ts index 05d6510a86..d84924fcb5 100644 --- a/trade-finance-manager-ui/server/middleware/validateUserTeam/index.test.ts +++ b/trade-finance-manager-ui/server/middleware/validateUserTeam/index.test.ts @@ -20,7 +20,7 @@ describe('validateUserTeam', () => { expect(() => validateUserTeam([])(req, res, next)).toThrow(Error('Expected session.user to be defined')); }); - it('should redirect to the default redirect url (/home) if the user is not in the correct team', () => { + it('should redirect to the default redirect url (/deals) if the user is not in the correct team', () => { // Arrange const requiredTeamIds = [TEAM_IDS.PDC_RECONCILE]; const { req, res } = getHttpMocks({ user: { teams: [] } }); @@ -31,7 +31,7 @@ describe('validateUserTeam', () => { // Assert expect(next).not.toHaveBeenCalled(); - expect(res._getRedirectUrl()).toEqual('/home'); + expect(res._getRedirectUrl()).toEqual('/deals'); }); it('should redirect to the specified redirect url if the user is not in the correct team', () => { diff --git a/trade-finance-manager-ui/server/middleware/validateUserTeam/index.ts b/trade-finance-manager-ui/server/middleware/validateUserTeam/index.ts index f42bf36d6c..51f0f2c1e6 100644 --- a/trade-finance-manager-ui/server/middleware/validateUserTeam/index.ts +++ b/trade-finance-manager-ui/server/middleware/validateUserTeam/index.ts @@ -7,11 +7,11 @@ import { userIsInTeam } from '../../helpers/user'; * Middleware to check if the user is in at least * one of the teams specified in the requiredTeamIds * array. If they are not, they are redirected to - * the redirectUrl, which defaults to '/home' if + * the redirectUrl, which defaults to '/deals' if * it is not explicitly provided. */ export const validateUserTeam = - (requiredTeamIds: TeamId[], redirectUrl: string = '/home'): RequestHandler => + (requiredTeamIds: TeamId[], redirectUrl: string = '/deals'): RequestHandler => (req, res, next) => { const { user } = asUserSession(req.session); if (userIsInTeam(user, requiredTeamIds)) { diff --git a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts index a5f6243009..2ae2a3cca8 100644 --- a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts +++ b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts @@ -4,6 +4,12 @@ import { LoginController } from '../../../controllers/login/login-sso/login.cont import { LoginService } from '../../../services/login.service'; import { UserSessionService } from '../../../services/user-session.service'; +/** + * Creates and configures the authentication SSO router. + * This router handles the Single Sign-On (SSO) redirect form submission. + * + * @returns {Router} The configured authentication SSO router. + */ export const getAuthSsoRouter: GetRouter = () => { const loginService = new LoginService(); const userSessionService = new UserSessionService(); diff --git a/trade-finance-manager-ui/server/routes/login/configs/login-sso.ts b/trade-finance-manager-ui/server/routes/login/configs/login-sso.ts index 6961b33342..3807a53134 100644 --- a/trade-finance-manager-ui/server/routes/login/configs/login-sso.ts +++ b/trade-finance-manager-ui/server/routes/login/configs/login-sso.ts @@ -9,8 +9,9 @@ export const getLoginSsoRouter: GetRouter = () => { const userSessionService = new UserSessionService(); const loginController = new LoginController({ loginService, userSessionService }); const loginSsoRouter = express.Router(); - // eslint-disable-next-line @typescript-eslint/no-misused-promises - loginSsoRouter.get('/', (req, res) => loginController.getLogin(req, res)); + loginSsoRouter.get('/', (req, res, next) => { + loginController.getLogin(req, res).catch(next); + }); return loginSsoRouter; }; From f49019aa7f1bd4a13954faff54323aa69cf56470 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 3 Jan 2025 12:13:35 +0000 Subject: [PATCH 071/133] fix(dtfs2-7647): update tfm session user imports --- .../validate-post-fee-record-correction-payload.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-fee-record-correction-payload.ts b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-fee-record-correction-payload.ts index db5857449a..d5e4fdd12f 100644 --- a/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-fee-record-correction-payload.ts +++ b/dtfs-central-api/src/v1/routes/middleware/payload-validation/validate-post-fee-record-correction-payload.ts @@ -1,9 +1,9 @@ import z from 'zod'; import { createValidationMiddlewareForSchema } from '@ukef/dtfs2-common'; -import { TfmSessionUserSchema } from './schemas'; +import { TFM_SESSION_USER_SCHEMA } from '@ukef/dtfs2-common/schemas'; const PostFeeRecordCorrectionSchema = z.object({ - user: TfmSessionUserSchema, + user: TFM_SESSION_USER_SCHEMA, }); export type PostFeeRecordCorrectionPayload = z.infer; From 654327833b69a6381c866fd5005eeca910166f42 Mon Sep 17 00:00:00 2001 From: Alex <47955140+AlexBramhill@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:57:51 +0000 Subject: [PATCH 072/133] Revert "feat(DTFS2-6892): update mock builder to require complete defaults, add documentation demonstrating how to pass through mocked class methods" (#4110) Reverted to enable better management of branches --- .../src/test-helpers/mock-builders/index.ts | 2 +- .../mock-builder.mock.builder.ts | 103 ++++++++++++++++ .../mock-builders/mock-builder.ts | 111 ------------------ .../builders/user.service.mock.builder.ts | 10 +- .../sso.controller.get-auth-code-url.test.ts | 4 +- ...ontroller.handle-sso-redirect-form.test.ts | 3 +- ...entra-id.service.get-auth-code-url.test.ts | 3 +- 7 files changed, 111 insertions(+), 125 deletions(-) create mode 100644 libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts delete mode 100644 libs/common/src/test-helpers/mock-builders/mock-builder.ts diff --git a/libs/common/src/test-helpers/mock-builders/index.ts b/libs/common/src/test-helpers/mock-builders/index.ts index babe587833..3bf4521b97 100644 --- a/libs/common/src/test-helpers/mock-builders/index.ts +++ b/libs/common/src/test-helpers/mock-builders/index.ts @@ -1 +1 @@ -export * from './mock-builder'; +export * from './mock-builder.mock.builder'; diff --git a/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts b/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts new file mode 100644 index 0000000000..0e74fb97df --- /dev/null +++ b/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts @@ -0,0 +1,103 @@ +/** + * Class to allow for the passing of jest fns into methods on the class object + */ +type Mocked = { + [K in keyof T]: T[K] extends (...args: any[]) => infer R ? jest.Mock> | T[K] : T[K]; +}; + +/** + * Base class for class test data builders. Pass in default values for the class + * instance so that you can build an instance with those values and override + * them as needed. + * + * @example + * ```ts + * // Basic builder + * export class UserMockBuilder extends BaseMockBuilder { + * constructor() { + * super({ + * defaultInstance: { + * id: '72f9ca55-943a-401d-a04c-0a9e03ac7f18', + * name: 'Joe Bloggs', + * email: 'joe.bloggs@ukef.gov.uk', + * }, + * }) + * } + * } + * ``` + * + * @example + * ```ts + * // With custom static factory methods to initialise the builder + * export class UserMockBuilder extends BaseMockBuilder { + * constructor(defaultInstance?: User) { + * super({ + * defaultInstance: defaultInstance ?? { + * id: '72f9ca55-943a-401d-a04c-0a9e03ac7f18', + * name: 'Joe Bloggs', + * email: 'joe.bloggs@ukef.gov.uk', + * }, + * }) + * } + * + * public static fromEntity(entity: UserEntity): UserMockBuilder { + * return new UserMockBuilder({ + * id: entity.id, + * name: entity.name, + * email: entity.email, + * }) + * } + * } + * ``` + * + * @example + * ```ts + * // Usage in a test where we just need a valid class instance + * const user = new UserMockBuilder().build() + * ``` + * + * @example + * ```ts + * // Usage in a test where a field needs to have a specific value + * const user = new UserMockBuilder() + * .with({ email: 'new.email@ukef.gov.uk' }) + * .build() + * ``` + */ +export abstract class BaseMockBuilder { + private readonly defaults: Partial>; + private readonly instance: Mocked = {} as Mocked; + + protected constructor(config: { defaultInstance: Partial> }) { + this.defaults = config.defaultInstance; + } + + /** + * Set fields on the class instance when you want to override the defaults + * + * @example + * ```ts + * const user = new UserMockBuilder() + * .with({ + * email: 'new.email@ukef.gov.uk', + * givenName: 'Fred', + * }) + * .build() + * ``` + */ + public with(values: Partial>): BaseMockBuilder { + Object.assign(this.instance, values); + return this; + } + + public withDefaults(): BaseMockBuilder { + return this.with(this.defaults); + } + + /** + * Build the class instance after setting any fields + */ + public build(): TClass { + return this.instance as TClass; + } +} diff --git a/libs/common/src/test-helpers/mock-builders/mock-builder.ts b/libs/common/src/test-helpers/mock-builders/mock-builder.ts deleted file mode 100644 index b4ddaf2e59..0000000000 --- a/libs/common/src/test-helpers/mock-builders/mock-builder.ts +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Class to allow for the passing of jest fns into methods on the class object - */ -type Mocked = { - [K in keyof T]: T[K] extends (...args: any[]) => infer R ? jest.Mock> | T[K] : T[K]; -}; - -/** - * Base class for class test data and class builders. - * - * Pass in default values for the instance so that you can build an instance with - * those values and override them as needed. - * - * @example - * Overriding the defaults of a type: - * ```ts - * // user.mock.builder.ts - * // Basic builder for a type - * export class UserMockBuilder extends BaseMockBuilder { - * constructor() { - * super({ - * defaultInstance: { - * id: '72f9ca55-943a-401d-a04c-0a9e03ac7f18', - * name: 'Joe Bloggs', - * email: 'joe.bloggs@ukef.gov.uk', - * }, - * }) - * } - * } - * - * // a-test-file.test.ts - * // Usage in a test where we just need a valid class instance - * const user = new UserMockBuilder().build() - * - * // another-test-file.test.ts - * // Usage in a test where a field needs to have a specific value - * const user = new UserMockBuilder() - * .with({ email: 'new.email@ukef.gov.uk' }) - * .build() - * ``` - - * - * @example - * Overriding the defaults of a class: - * ```ts - * export class LoginServiceMockBuilder extends BaseMockBuilder { - * constructor() { - * super({ - * defaultInstance: { - * getAuthCodeUrl: jest.fn(async () => { - * return Promise.resolve({ - * authCodeUrl: 'a-auth-code-url', - * authCodeUrlRequest: {} as AuthorizationCodeRequest, - * }); - * }), - * }, - * }); - * } - * } - * ``` - * - * Keeping existing implimentations of a class: - * ```ts - * export class UserServiceMockBuilder extends BaseMockBuilder { - * constructor() { - * const userService = new UserService(); // This can be used as a way to inherit methods we do not wish to mock the implimentation for - * super({ - * defaultInstance: { - * transformEntraIdUserToUpsertTfmUserRequest(entraIdUser: EntraIdUser): UpsertTfmUserRequest { - * return userService.transformEntraIdUserToUpsertTfmUserRequest(entraIdUser); - * }, - * saveUserLoginInformation({ userId, sessionIdentifier, auditDetails }: saveUserLoginInformationParams): Promise { - * return Promise.resolve(); - * }, - * }, - * }); - * } - * ``` - */ -export abstract class BaseMockBuilder { - private readonly instance: Mocked = {} as Mocked; - - protected constructor(config: { defaultInstance: Mocked }) { - this.with(config.defaultInstance); - } - - /** - * Set fields on the class instance when you want to override the defaults - * - * @example - * ```ts - * const user = new UserMockBuilder() - * .with({ - * email: 'new.email@ukef.gov.uk', - * givenName: 'Fred', - * }) - * .build() - * ``` - */ - public with(values: Partial>): BaseMockBuilder { - Object.assign(this.instance, values); - return this; - } - - /** - * Build the class instance after setting any fields - */ - public build(): TClass { - return this.instance as TClass; - } -} diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts index 4c084e83f6..12af34a2a8 100644 --- a/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { BaseMockBuilder, EntraIdUser, UpsertTfmUserRequest } from '@ukef/dtfs2-common'; +import { BaseMockBuilder } from '@ukef/dtfs2-common'; import { aTfmUser } from '@ukef/dtfs2-common/mock-data-backend'; import { UpsertTfmUserFromEntraIdUserParams, @@ -10,16 +10,8 @@ import { export class UserServiceMockBuilder extends BaseMockBuilder { constructor() { - const userService = new UserService(); super({ defaultInstance: { - /** - * We pass through the actual service implementation for the below method here, - * as it is an existing pattern that we never mock synchronous methods. - */ - transformEntraIdUserToUpsertTfmUserRequest(entraIdUser: EntraIdUser): UpsertTfmUserRequest { - return userService.transformEntraIdUserToUpsertTfmUserRequest(entraIdUser); - }, upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }: UpsertTfmUserFromEntraIdUserParams): Promise { return Promise.resolve(aTfmUser()); }, diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts index d86d2a3086..74bb4ba60c 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts @@ -31,8 +31,8 @@ describe('SsoController', () => { beforeEach(() => { jest.resetAllMocks(); - entraIdService = new EntraIdServiceMockBuilder().with({ getAuthCodeUrl: getAuthCodeUrlMock }).build(); - userService = new UserServiceMockBuilder().build(); + entraIdService = new EntraIdServiceMockBuilder().withDefaults().with({ getAuthCodeUrl: getAuthCodeUrlMock }).build(); + userService = new UserServiceMockBuilder().withDefaults().build(); ssoController = new SsoController({ entraIdService, userService }); ({ req, res } = getHttpMocks()); diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts index 791ad94a0b..15a4f9af09 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts @@ -38,8 +38,9 @@ describe('SsoController', () => { beforeEach(() => { jest.resetAllMocks(); - entraIdService = new EntraIdServiceMockBuilder().with({ handleRedirect: handleRedirectMock }).build(); + entraIdService = new EntraIdServiceMockBuilder().withDefaults().with({ handleRedirect: handleRedirectMock }).build(); userService = new UserServiceMockBuilder() + .withDefaults() .with({ upsertTfmUserFromEntraIdUser: upsertTfmUserFromEntraIdUserMock, saveUserLoginInformation: saveUserLoginInformationMock }) .build(); ssoController = new SsoController({ entraIdService, userService }); diff --git a/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts index d3740153fb..c3b05025f8 100644 --- a/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts +++ b/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts @@ -35,6 +35,7 @@ describe('EntraIdService', () => { }); entraIdConfig = new EntraIdConfigMockBuilder() + .withDefaults() .with({ authorityMetadataUrl: mockAuthorityMetaDataUrl, scopes: mockScope, @@ -42,7 +43,7 @@ describe('EntraIdService', () => { }) .build(); - entraIdApi = new EntraIdApiMockBuilder().build(); + entraIdApi = new EntraIdApiMockBuilder().withDefaults().build(); }); it('calls base64Encode with the expected stringifiedstate', async () => { From d459281b5e7a70f17c8207f9ab1e3db29ff3fd7c Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 6 Jan 2025 18:22:28 +0000 Subject: [PATCH 073/133] feat(dtfs2-6892): add readme --- doc/schemas.md | 100 ++++++++++++++++++ .../src/schemas/currency.schema.test.ts | 2 +- .../iso-date-time-stamp.schema.test.ts | 2 +- libs/common/src/schemas/object-id.test.ts | 6 +- .../src/schemas/tfm/tfm-team.schema.test.ts | 2 +- .../index.ts | 1 + .../with-currency-schema.tests.ts | 2 +- .../with-iso-date-time-stamp-schema.tests.ts | 2 +- ...ect-id-or-object-id-string-schema.tests.ts | 2 +- .../with-object-id-schema.tests.ts | 2 +- .../with-object-id-string-schema.tests.ts | 2 +- .../with-tfm-team-schema.tests.ts | 2 +- ...nix-timestamp-milliseconds-schema.tests.ts | 4 +- .../with-unix-timestamp-schema.tests.ts | 4 +- ...ith-unix-timestamp-seconds-schema.tests.ts | 4 +- libs/common/src/test-helpers/schemas/index.ts | 4 +- .../index.ts | 0 .../with-array.tests.ts | 0 .../with-boolean.tests.ts | 0 .../with-default-options.tests.ts | 0 .../with-number.tests.ts | 0 .../with-string.tests.ts | 0 .../schemas/schema-tests/index.ts | 1 - ...with-audit-database-record-schema.tests.ts | 2 +- .../with-entra-id-user-schema.tests.ts | 2 +- ...so-date-time-stamp-to-date.schema.tests.ts | 2 +- .../schemas/with-schema-test.type.ts | 2 +- .../schemas/with-schema-validation.tests.ts | 60 ++++++++++- .../schemas/with-test-for-test-case.type.ts | 2 +- .../schemas/with-tests-for-testcase.ts | 9 +- 30 files changed, 189 insertions(+), 32 deletions(-) create mode 100644 doc/schemas.md rename libs/common/src/test-helpers/schemas/{custom-objects-tests => custom-types-tests}/index.ts (92%) rename libs/common/src/test-helpers/schemas/{custom-objects-tests => custom-types-tests}/with-currency-schema.tests.ts (90%) rename libs/common/src/test-helpers/schemas/{custom-objects-tests => custom-types-tests}/with-iso-date-time-stamp-schema.tests.ts (93%) rename libs/common/src/test-helpers/schemas/{custom-objects-tests => custom-types-tests}/with-object-id-or-object-id-string-schema.tests.ts (92%) rename libs/common/src/test-helpers/schemas/{custom-objects-tests => custom-types-tests}/with-object-id-schema.tests.ts (94%) rename libs/common/src/test-helpers/schemas/{custom-objects-tests => custom-types-tests}/with-object-id-string-schema.tests.ts (94%) rename libs/common/src/test-helpers/schemas/{schema-tests => custom-types-tests}/with-tfm-team-schema.tests.ts (90%) rename libs/common/src/test-helpers/schemas/{custom-objects-tests => custom-types-tests}/with-unix-timestamp-milliseconds-schema.tests.ts (86%) rename libs/common/src/test-helpers/schemas/{custom-objects-tests => custom-types-tests}/with-unix-timestamp-schema.tests.ts (85%) rename libs/common/src/test-helpers/schemas/{custom-objects-tests => custom-types-tests}/with-unix-timestamp-seconds-schema.tests.ts (86%) rename libs/common/src/test-helpers/schemas/{primitive-object-tests => primitive-types-tests}/index.ts (100%) rename libs/common/src/test-helpers/schemas/{primitive-object-tests => primitive-types-tests}/with-array.tests.ts (100%) rename libs/common/src/test-helpers/schemas/{primitive-object-tests => primitive-types-tests}/with-boolean.tests.ts (100%) rename libs/common/src/test-helpers/schemas/{primitive-object-tests => primitive-types-tests}/with-default-options.tests.ts (100%) rename libs/common/src/test-helpers/schemas/{primitive-object-tests => primitive-types-tests}/with-number.tests.ts (100%) rename libs/common/src/test-helpers/schemas/{primitive-object-tests => primitive-types-tests}/with-string.tests.ts (100%) diff --git a/doc/schemas.md b/doc/schemas.md new file mode 100644 index 0000000000..fe323566ef --- /dev/null +++ b/doc/schemas.md @@ -0,0 +1,100 @@ +# Schemas + +We use Zod to define schemas on DTFS. These schemas serve as a way to define the shape of data that is passed around the application. + +We can use these schemas to validate data in multiple ways, as well as transform data and generate TypeScript types. + +They are incredibly powerful and useful for ensuring that data is correct and consistent throughout the application. + +The majority of schemas are defined in the `libs/common/src/schemas` directory. This allows us to use similar schemas in the front and back end when needed. + +## Schema tests + +We have been testing our schemas in a variety of ways. These tests have proven long and difficult to maintain, with a lot of repetition and boilerplate. + +As a result, we are working towards a new approach to schema testing to make writing tests easy to write. This approach sees: + +- Each schema having their own tests that reference `withSchemaValidationTests`, with a simple test case definition. +- `withSchemaValidationTests` handles the orchestration of combining any schema-wide test options (such as `isPartial` and `isStrict`) and orchestrates the testing of each test case though `withTestsForTestcase` +- `withTestsForTestcase` handles the dispatching of test cases to the relevant test files for the type of schema being tested. +- These test case files are broken down into: + - primitive types (native in JS, ie `with-string.tests`), + - custom types (based on primitives, but with additional validation, for instance a number being non-negative, ie `with-unix-timestamp-milliseconds-schema.tests`), + - schemas (reusable tests for whole schemas that are used in other schemas, ie `with-entra-id-user-schema.tests`) + - transformations (schemas that transform data, for instance changing a string to a date -- ie `with-iso-date-time-stamp-to-date.schema`) + +The result of this structure means that the majority of tests will be effectively 'free' to write. The only exception here are schemas that use types or schemas where a test case does not already exist. + +### Writing tests + +To test -- We create a new file, referencing the `withSchemaValidationTests` function, and follow the instructions found in `with-schema-validation.tests.ts`. + +```ts +// example.schema.ts +import { EXAMPLE_SCHEMA } from './example.schema'; + +describe('EXAMPLE_SCHEMA', () => { + withSchemaValidationTests(); + // Follow examples in with-schema-validation.tests.ts +}); +``` + +9 out of 10 times when you write a test, the above will allow you to write quick, comprehensive tests for your schema. + +However, sometimes you'll have created a new nested schema or type that doesn't have a test case yet. In this case, you'll need to create a new test file. These go in `\libs\common\src\test-helpers\schemas` (in the correct folder, as specified above). + +### To create your own primitive/custom type test: + +- Create a new file in the correct folder (ie `with-string.tests.ts`) +- Follow the existing pattern (see `with-string.tests.ts` for an example) +- Ensure you have `withDefaultTestCases` in your test file +- Add any type specific options as required (see `with-array.tests.ts` for an example) +- Add export of the test to the `index.ts` file in the same folder +- Add your test case name to `with-test-for-test-case.type` in both the `TestCaseTypes` and `TestCaseWithType` declarations +- Add your test case to the `withTestsForTestcase` function to call your test case when provided with the test case name as the `type` in a `testCase` +- This can now be called as + + ```ts + { + testCases: [ + { + parameterPath: 'path to parameter in schema being tested', + type: 'your test case name', + schema: SCHEMA_YOU_ARE_TESTING, + options: { + // any default options you want to pass to your test case + // any type specific options you want to pass to your test case + } + }, + ], + } + ``` + +### To create your own reusable schema / tranformation test: + +- Create a new file in the correct folder (ie `with-audit-database-record-schema.tests.ts`) +- Follow the existing pattern (see `with-audit-database-record-schema.tests.ts` for an example) +- Ensure you have `withDefaultTestCases` in your test file +- Add any type specific options as required (see `with-array.tests.ts` for an example) +- Add export of the test to the `index.ts` file in the same folder +- Add your test case name to `with-test-for-test-case.type` in both the `TestCaseTypes` and `TestCaseWithType` declarations +- Add your test case to the `withTestsForTestcase` function to call your test case when provided with the test case name as the `type` in a `testCase` +- This can now be called as + + ```ts + { + testCases: [ + { + parameterPath: 'path to parameter in schema being tested', + type: 'your test case name', + schema: SCHEMA_YOU_ARE_TESTING, + options: { + // any default options you want to pass to your test case + // any type specific options you want to pass to your test case + } + }, + ], + } + ``` + +- Create a test for this new schema in `libs/common/src/schemas`, and call your new test file (see audit-database-record.test.ts). diff --git a/libs/common/src/schemas/currency.schema.test.ts b/libs/common/src/schemas/currency.schema.test.ts index 91b47bf120..c64f2e1b18 100644 --- a/libs/common/src/schemas/currency.schema.test.ts +++ b/libs/common/src/schemas/currency.schema.test.ts @@ -1,4 +1,4 @@ -import { withCurrencySchemaTests } from '../test-helpers/schemas/custom-objects-tests/with-currency-schema.tests'; +import { withCurrencySchemaTests } from '../test-helpers/schemas/custom-types-tests/with-currency-schema.tests'; import { CURRENCY_SCHEMA } from './currency.schema'; describe('CURRENCY_SCHEMA', () => { diff --git a/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts b/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts index a7248fae8b..7c55e542d7 100644 --- a/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts +++ b/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts @@ -1,4 +1,4 @@ -import { withIsoDateTimeStampSchemaTests } from '../test-helpers/schemas/custom-objects-tests/with-iso-date-time-stamp-schema.tests'; +import { withIsoDateTimeStampSchemaTests } from '../test-helpers/schemas/custom-types-tests/with-iso-date-time-stamp-schema.tests'; import { ISO_DATE_TIME_STAMP_SCHEMA } from './iso-date-time-stamp.schema'; describe('ISO_DATE_TIME_STAMP_SCHEMA', () => { diff --git a/libs/common/src/schemas/object-id.test.ts b/libs/common/src/schemas/object-id.test.ts index e3979c8336..05acb8285c 100644 --- a/libs/common/src/schemas/object-id.test.ts +++ b/libs/common/src/schemas/object-id.test.ts @@ -1,7 +1,7 @@ import { OBJECT_ID_SCHEMA, OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA, OBJECT_ID_STRING_SCHEMA } from './object-id'; -import { withObjectIdSchemaTests } from '../test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests'; -import { withObjectIdOrObjectIdStringSchemaTests } from '../test-helpers/schemas/custom-objects-tests/with-object-id-or-object-id-string-schema.tests'; -import { withObjectIdStringSchemaTests } from '../test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests'; +import { withObjectIdSchemaTests } from '../test-helpers/schemas/custom-types-tests/with-object-id-schema.tests'; +import { withObjectIdOrObjectIdStringSchemaTests } from '../test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests'; +import { withObjectIdStringSchemaTests } from '../test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests'; describe('OBJECT_ID_SCHEMA', () => { withObjectIdSchemaTests({ diff --git a/libs/common/src/schemas/tfm/tfm-team.schema.test.ts b/libs/common/src/schemas/tfm/tfm-team.schema.test.ts index fb172b3fe2..baad26524e 100644 --- a/libs/common/src/schemas/tfm/tfm-team.schema.test.ts +++ b/libs/common/src/schemas/tfm/tfm-team.schema.test.ts @@ -1,4 +1,4 @@ -import { withTfmTeamSchemaTests } from '../../test-helpers/schemas/schema-tests/with-tfm-team-schema.tests'; +import { withTfmTeamSchemaTests } from '../../test-helpers'; import { TfmTeamSchema } from './tfm-team.schema'; describe('tfm-team.schema', () => { diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/index.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts similarity index 92% rename from libs/common/src/test-helpers/schemas/custom-objects-tests/index.ts rename to libs/common/src/test-helpers/schemas/custom-types-tests/index.ts index 28ffc5d1a4..dee82dd937 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts @@ -9,3 +9,4 @@ export * from './with-object-id-string-schema.tests'; export * from './with-unix-timestamp-schema.tests'; export * from './with-unix-timestamp-milliseconds-schema.tests'; export * from './with-unix-timestamp-seconds-schema.tests'; +export * from './with-tfm-team-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-currency-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-currency-schema.tests.ts similarity index 90% rename from libs/common/src/test-helpers/schemas/custom-objects-tests/with-currency-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-types-tests/with-currency-schema.tests.ts index 059cd7e93c..df7e2bca83 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-currency-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-currency-schema.tests.ts @@ -1,6 +1,6 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; -import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; +import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; import { CURRENCY } from '../../../constants'; export const withCurrencySchemaTests = ({ diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-iso-date-time-stamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-iso-date-time-stamp-schema.tests.ts similarity index 93% rename from libs/common/src/test-helpers/schemas/custom-objects-tests/with-iso-date-time-stamp-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-types-tests/with-iso-date-time-stamp-schema.tests.ts index 0866f6f2f9..02395b4aa5 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-iso-date-time-stamp-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-iso-date-time-stamp-schema.tests.ts @@ -1,6 +1,6 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; -import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; +import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; export const withIsoDateTimeStampSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-or-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts similarity index 92% rename from libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-or-object-id-string-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts index ca621d6f83..922a344b16 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-or-object-id-string-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts @@ -1,7 +1,7 @@ import { ZodSchema } from 'zod'; import { ObjectId } from 'mongodb'; import { WithSchemaTestParams } from '../with-schema-test.type'; -import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; +import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; export const withObjectIdOrObjectIdStringSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-schema.tests.ts similarity index 94% rename from libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-schema.tests.ts index 6e117c9cfb..0ab951d7e7 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-schema.tests.ts @@ -1,7 +1,7 @@ import { ObjectId } from 'mongodb'; import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; -import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; +import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; export const withObjectIdSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests.ts similarity index 94% rename from libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests.ts index 049aec1dcd..fa28ae3e2f 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-object-id-string-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests.ts @@ -1,7 +1,7 @@ import { ObjectId } from 'mongodb'; import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; -import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; +import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; export const withObjectIdStringSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-team-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-tfm-team-schema.tests.ts similarity index 90% rename from libs/common/src/test-helpers/schemas/schema-tests/with-tfm-team-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-types-tests/with-tfm-team-schema.tests.ts index 082ad229fa..212f6fa56b 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-team-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-tfm-team-schema.tests.ts @@ -1,7 +1,7 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; import { TEAM_IDS } from '../../../constants'; -import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; +import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; export const withTfmTeamSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter }: WithSchemaTestParams) => { describe('with TfmTeamSchema tests', () => { diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-milliseconds-schema.tests.ts similarity index 86% rename from libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-milliseconds-schema.tests.ts index 06e36dafb0..6ea85eec36 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-milliseconds-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-milliseconds-schema.tests.ts @@ -1,7 +1,7 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; -import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; -import { withNumberTests } from '../primitive-object-tests'; +import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; +import { withNumberTests } from '../primitive-types-tests'; export const withUnixTimestampMillisecondsSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts similarity index 85% rename from libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts index 5af2855df2..121d6b22a7 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts @@ -1,7 +1,7 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; -import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; -import { withNumberTests } from '../primitive-object-tests'; +import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; +import { withNumberTests } from '../primitive-types-tests'; export const withUnixTimestampSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts similarity index 86% rename from libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts index 9529fe3663..d6ff0ea511 100644 --- a/libs/common/src/test-helpers/schemas/custom-objects-tests/with-unix-timestamp-seconds-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts @@ -1,7 +1,7 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; -import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; -import { withNumberTests } from '../primitive-object-tests'; +import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; +import { withNumberTests } from '../primitive-types-tests'; export const withUnixTimestampSecondsSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/index.ts b/libs/common/src/test-helpers/schemas/index.ts index c0eacb96df..300effcfca 100644 --- a/libs/common/src/test-helpers/schemas/index.ts +++ b/libs/common/src/test-helpers/schemas/index.ts @@ -1,5 +1,5 @@ -export * from './custom-objects-tests'; +export * from './custom-types-tests'; export * from './schema-tests'; -export * from './primitive-object-tests'; +export * from './primitive-types-tests'; export * from './with-schema-validation.tests'; export * from './transformation-tests'; diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/index.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/index.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/primitive-object-tests/index.ts rename to libs/common/src/test-helpers/schemas/primitive-types-tests/index.ts diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-array.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-array.tests.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/primitive-object-tests/with-array.tests.ts rename to libs/common/src/test-helpers/schemas/primitive-types-tests/with-array.tests.ts diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-boolean.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-boolean.tests.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/primitive-object-tests/with-boolean.tests.ts rename to libs/common/src/test-helpers/schemas/primitive-types-tests/with-boolean.tests.ts diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-default-options.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-default-options.tests.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/primitive-object-tests/with-default-options.tests.ts rename to libs/common/src/test-helpers/schemas/primitive-types-tests/with-default-options.tests.ts diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-number.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-number.tests.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/primitive-object-tests/with-number.tests.ts rename to libs/common/src/test-helpers/schemas/primitive-types-tests/with-number.tests.ts diff --git a/libs/common/src/test-helpers/schemas/primitive-object-tests/with-string.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-string.tests.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/primitive-object-tests/with-string.tests.ts rename to libs/common/src/test-helpers/schemas/primitive-types-tests/with-string.tests.ts diff --git a/libs/common/src/test-helpers/schemas/schema-tests/index.ts b/libs/common/src/test-helpers/schemas/schema-tests/index.ts index 75342c458f..8b05aaa13d 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/index.ts @@ -4,4 +4,3 @@ */ export * from './with-audit-database-record-schema.tests'; export * from './with-entra-id-user-schema.tests'; -export * from './with-tfm-team-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts index e80146769f..d7bfc14d69 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts @@ -3,7 +3,7 @@ import { ObjectId } from 'mongodb'; import { WithSchemaTestParams } from '../with-schema-test.type'; import { generateTfmUserAuditDatabaseRecord } from '../../../change-stream'; import { withSchemaValidationTests } from '../with-schema-validation.tests'; -import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; +import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; export const withAuditDatabaseRecordSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts index 85876061f2..3b5a3cd113 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts @@ -1,6 +1,6 @@ import { ZodSchema } from 'zod'; import { anEntraIdUser } from '../../mock-data'; -import { withDefaultOptionsTests } from '../primitive-object-tests'; +import { withDefaultOptionsTests } from '../primitive-types-tests'; import { withSchemaValidationTests } from '../with-schema-validation.tests'; import { WithSchemaTestParams } from '../with-schema-test.type'; diff --git a/libs/common/src/test-helpers/schemas/transformation-tests/with-iso-date-time-stamp-to-date.schema.tests.ts b/libs/common/src/test-helpers/schemas/transformation-tests/with-iso-date-time-stamp-to-date.schema.tests.ts index 8e2668c513..8aae91ccd2 100644 --- a/libs/common/src/test-helpers/schemas/transformation-tests/with-iso-date-time-stamp-to-date.schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/transformation-tests/with-iso-date-time-stamp-to-date.schema.tests.ts @@ -1,6 +1,6 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; -import { withDefaultOptionsTests } from '../primitive-object-tests/with-default-options.tests'; +import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; export const withIsoDateTimeStampToDateSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/with-schema-test.type.ts b/libs/common/src/test-helpers/schemas/with-schema-test.type.ts index 0e13cd520a..fb77106530 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-test.type.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-test.type.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { DefaultOptions } from './primitive-object-tests/with-default-options.tests'; +import { DefaultOptions } from './primitive-types-tests/with-default-options.tests'; export type WithSchemaTestParams = { schema: Schema; diff --git a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts index b3baa95a49..2acf9966a4 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts @@ -19,8 +19,64 @@ export type TestCaseWithPathParameter = { } & TestCase; /** - * With schema validation tests allows for the passing in of a schema, a valid payload, and test cases to test the schema. - * It calls pre made test cases through withTestsForTestcase, after applying schema specific options and overrides + * This function orchestrates a schema's test cases. + * It applies schema test options to all test cases, as well as adding and schema-specific tests as required. + * @param params.schema The schema to test + * @param params.schemaTestOptions Options that are specific to the schema as a whole, for instance, if the schema is a partial, or strict + * @param params.aValidPayload A function that returns a valid payload for the schema + * @param params.testCases Test cases to test + * @example Schema test options + * ```ts + * const schemaTestOptions = { isPartial: true, isStrict: true } + * ``` + * @example A valid payload + * ```ts + * const aValidPayload = () => ({ age: 20, + * _id: new ObjectId(), + * sessionIdentifier: 'session-identifier', + * teams: [{ name: 'a-valid-team-name' }] + * }) + * ``` + * + * @example Test case: using a primitive type + * ```ts + * const testCases = [{ + * parameterPath: 'age', + * type: 'number', // with-number.tests will be used for age + * }] + * ``` + * + * @example Test case: using a custom type + * ```ts + * const testCases = [{ + * parameterPath: '_id', + * type: 'OBJECT_ID_SCHEMA', // with-object-id-schema.tests will be used for _id + * }] + * ``` + * + * @example Test case: using options that are available on all types + * ```ts + * const testCases = [{ + * parameterPath: 'sessionIdentifier', + * type: 'string', + * options: { isOptional: true }, + * }] + * ``` + * + * @example Test case: using options that are available on a certain type + * ```ts + * const testCases = [{ + * parameterPath: 'teams', + * type: 'Array', + * options: { + * // In this case, the type specific options allow us to + * //specify the type of each object on the array + * arrayTypeTestCase: { + * type: 'TfmTeamSchema', + * }, + * }, + * }] + * ``` */ export const withSchemaValidationTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts b/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts index 9ad88fcb2f..533750b7e8 100644 --- a/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts +++ b/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts @@ -1,4 +1,4 @@ -import { DefaultOptions, WithArrayTestsOptions } from './primitive-object-tests'; +import { DefaultOptions, WithArrayTestsOptions } from './primitive-types-tests'; /** * All test case types that have been tests implemented diff --git a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts index 8f30e0f59b..db1adccb4e 100644 --- a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts +++ b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts @@ -7,11 +7,12 @@ import { withObjectIdStringSchemaTests, withObjectIdOrObjectIdStringSchemaTests, withIsoDateTimeStampSchemaTests, -} from './custom-objects-tests'; -import { withStringTests, withNumberTests, withBooleanTests, withArrayTests } from './primitive-object-tests'; -import { withTfmTeamSchemaTests, withAuditDatabaseRecordSchemaTests, withEntraIdUserSchemaTests } from './schema-tests'; + withTfmTeamSchemaTests, +} from './custom-types-tests'; +import { withStringTests, withNumberTests, withBooleanTests, withArrayTests } from './primitive-types-tests'; +import { withAuditDatabaseRecordSchemaTests, withEntraIdUserSchemaTests } from './schema-tests'; import { TestCase } from './with-test-for-test-case.type'; -import { withCurrencySchemaTests } from './custom-objects-tests/with-currency-schema.tests'; +import { withCurrencySchemaTests } from './custom-types-tests/with-currency-schema.tests'; import { withIsoDateTimeStampToDateSchemaTests } from './transformation-tests'; /** From 8fe75fd2c8cb8309a6833d941f260fe51254ae0f Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 6 Jan 2025 18:25:40 +0000 Subject: [PATCH 074/133] feat(dtfs2-6892): add readme --- .../src/test-helpers/schemas/with-schema-validation.tests.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts index 2acf9966a4..7473dfc33c 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts @@ -25,6 +25,7 @@ export type TestCaseWithPathParameter = { * @param params.schemaTestOptions Options that are specific to the schema as a whole, for instance, if the schema is a partial, or strict * @param params.aValidPayload A function that returns a valid payload for the schema * @param params.testCases Test cases to test + * @see doc\schemas.md for more information * @example Schema test options * ```ts * const schemaTestOptions = { isPartial: true, isStrict: true } From 4744df8c76828f11525c251a13476b868781142f Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 7 Jan 2025 11:58:09 +0000 Subject: [PATCH 075/133] feat(dtfs2-6892): update tests --- doc/schemas.md | 4 +++- .../src/schemas/audit-database-record.test.ts | 1 + .../common/src/schemas/currency.schema.test.ts | 1 + .../iso-date-time-stamp-to-date.schema.test.ts | 8 +------- .../schemas/iso-date-time-stamp.schema.test.ts | 1 + libs/common/src/schemas/object-id.test.ts | 3 +++ ...id-user.schema.entra-id-user-schema.test.ts | 1 + .../src/schemas/tfm/tfm-team.schema.test.ts | 1 + .../src/schemas/unix-timestamp.schema.test.ts | 3 +++ .../with-currency-schema.tests.ts | 2 ++ .../with-iso-date-time-stamp-schema.tests.ts | 2 ++ ...ject-id-or-object-id-string-schema.tests.ts | 2 ++ .../with-object-id-schema.tests.ts | 2 ++ .../with-object-id-string-schema.tests.ts | 2 ++ .../with-tfm-team-schema.tests.ts | 8 +++++++- ...unix-timestamp-milliseconds-schema.tests.ts | 3 +++ .../with-unix-timestamp-schema.tests.ts | 3 +++ ...with-unix-timestamp-seconds-schema.tests.ts | 3 +++ .../primitive-types-tests/with-array.tests.ts | 3 +++ .../with-boolean.tests.ts | 8 +++++++- .../primitive-types-tests/with-number.tests.ts | 8 +++++++- .../primitive-types-tests/with-string.tests.ts | 8 +++++++- .../with-audit-database-record-schema.tests.ts | 2 ++ .../with-entra-id-user-schema.tests.ts | 2 ++ .../schemas/transformation-tests/index.ts | 3 +++ ...iso-date-time-stamp-to-date.schema.tests.ts | 9 +++++++++ .../schemas/with-schema-test.type.ts | 1 + .../schemas/with-schema-validation.tests.ts | 6 ++++++ .../schemas/with-tests-for-testcase.ts | 18 ++++++++++++++++++ 29 files changed, 106 insertions(+), 12 deletions(-) diff --git a/doc/schemas.md b/doc/schemas.md index fe323566ef..115962f5b9 100644 --- a/doc/schemas.md +++ b/doc/schemas.md @@ -70,7 +70,9 @@ However, sometimes you'll have created a new nested schema or type that doesn't } ``` -### To create your own reusable schema / tranformation test: +### To create your own reusable schema / transformation test: + +n.b. transformation tests use a different test case type to allow access to a function to get the value of the transformed payload -- pay attention to types if working on a transformation test. - Create a new file in the correct folder (ie `with-audit-database-record-schema.tests.ts`) - Follow the existing pattern (see `with-audit-database-record-schema.tests.ts` for an example) diff --git a/libs/common/src/schemas/audit-database-record.test.ts b/libs/common/src/schemas/audit-database-record.test.ts index e20911c161..eec3cd1589 100644 --- a/libs/common/src/schemas/audit-database-record.test.ts +++ b/libs/common/src/schemas/audit-database-record.test.ts @@ -5,5 +5,6 @@ describe('AUDIT_DATABASE_RECORD', () => { withAuditDatabaseRecordSchemaTests({ schema: AUDIT_DATABASE_RECORD_SCHEMA, getTestObjectWithUpdatedParameter: (newValue) => newValue, + getUpdatedParameterFromParsedTestObject: (data) => data, }); }); diff --git a/libs/common/src/schemas/currency.schema.test.ts b/libs/common/src/schemas/currency.schema.test.ts index c64f2e1b18..d2fe885604 100644 --- a/libs/common/src/schemas/currency.schema.test.ts +++ b/libs/common/src/schemas/currency.schema.test.ts @@ -5,6 +5,7 @@ describe('CURRENCY_SCHEMA', () => { withCurrencySchemaTests({ schema: CURRENCY_SCHEMA, getTestObjectWithUpdatedParameter: (newValue: unknown) => newValue, + getUpdatedParameterFromParsedTestObject: (data) => data, options: { isOptional: false }, }); }); diff --git a/libs/common/src/schemas/iso-date-time-stamp-to-date.schema.test.ts b/libs/common/src/schemas/iso-date-time-stamp-to-date.schema.test.ts index 792670ab3f..954d05412a 100644 --- a/libs/common/src/schemas/iso-date-time-stamp-to-date.schema.test.ts +++ b/libs/common/src/schemas/iso-date-time-stamp-to-date.schema.test.ts @@ -5,12 +5,6 @@ describe('ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA', () => { withIsoDateTimeStampToDateSchemaTests({ schema: ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA, getTestObjectWithUpdatedParameter: (newValue: unknown) => newValue, - }); - - it('should return a date object', () => { - const testDateAsIsoTimeStamp = '2023-10-01T12:00:00Z'; - const { data } = ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA.safeParse(testDateAsIsoTimeStamp); - - expect(data).toEqual(new Date(testDateAsIsoTimeStamp)); + getUpdatedParameterFromParsedTestObject: (parsedTestObject) => parsedTestObject, }); }); diff --git a/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts b/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts index 7c55e542d7..9cb23a128d 100644 --- a/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts +++ b/libs/common/src/schemas/iso-date-time-stamp.schema.test.ts @@ -5,5 +5,6 @@ describe('ISO_DATE_TIME_STAMP_SCHEMA', () => { withIsoDateTimeStampSchemaTests({ schema: ISO_DATE_TIME_STAMP_SCHEMA, getTestObjectWithUpdatedParameter: (newValue) => newValue, + getUpdatedParameterFromParsedTestObject: (data) => data, }); }); diff --git a/libs/common/src/schemas/object-id.test.ts b/libs/common/src/schemas/object-id.test.ts index 05acb8285c..d1d0aa40b4 100644 --- a/libs/common/src/schemas/object-id.test.ts +++ b/libs/common/src/schemas/object-id.test.ts @@ -7,6 +7,7 @@ describe('OBJECT_ID_SCHEMA', () => { withObjectIdSchemaTests({ schema: OBJECT_ID_SCHEMA, getTestObjectWithUpdatedParameter: (newValue) => newValue, + getUpdatedParameterFromParsedTestObject: (data) => data, }); }); @@ -14,6 +15,7 @@ describe('OBJECT_ID_STRING', () => { withObjectIdStringSchemaTests({ schema: OBJECT_ID_STRING_SCHEMA, getTestObjectWithUpdatedParameter: (newValue) => newValue, + getUpdatedParameterFromParsedTestObject: (data) => data, }); }); @@ -21,5 +23,6 @@ describe('OBJECT_ID_OR_OBJECT_ID_STRING', () => { withObjectIdOrObjectIdStringSchemaTests({ schema: OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA, getTestObjectWithUpdatedParameter: (newValue) => newValue, + getUpdatedParameterFromParsedTestObject: (data) => data, }); }); diff --git a/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts b/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts index 62194319d9..bc1fe6dca9 100644 --- a/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts @@ -5,5 +5,6 @@ describe('ENTRA_ID_USER_SCHEMA', () => { withEntraIdUserSchemaTests({ schema: ENTRA_ID_USER_SCHEMA, getTestObjectWithUpdatedParameter: (newValue: unknown) => newValue, + getUpdatedParameterFromParsedTestObject: (data) => data, }); }); diff --git a/libs/common/src/schemas/tfm/tfm-team.schema.test.ts b/libs/common/src/schemas/tfm/tfm-team.schema.test.ts index baad26524e..85f3b11132 100644 --- a/libs/common/src/schemas/tfm/tfm-team.schema.test.ts +++ b/libs/common/src/schemas/tfm/tfm-team.schema.test.ts @@ -5,6 +5,7 @@ describe('tfm-team.schema', () => { withTfmTeamSchemaTests({ schema: TfmTeamSchema, getTestObjectWithUpdatedParameter: (newValue: unknown) => newValue, + getUpdatedParameterFromParsedTestObject: (data) => data, options: { isOptional: false }, }); }); diff --git a/libs/common/src/schemas/unix-timestamp.schema.test.ts b/libs/common/src/schemas/unix-timestamp.schema.test.ts index 5f398d8ffb..703a1c4fd2 100644 --- a/libs/common/src/schemas/unix-timestamp.schema.test.ts +++ b/libs/common/src/schemas/unix-timestamp.schema.test.ts @@ -5,6 +5,7 @@ describe('UNIX_TIMESTAMP_SCHEMA', () => { withUnixTimestampSchemaTests({ schema: UNIX_TIMESTAMP_SCHEMA, getTestObjectWithUpdatedParameter: (newValue) => newValue, + getUpdatedParameterFromParsedTestObject: (data) => data, }); }); @@ -12,6 +13,7 @@ describe('UNIX_TIMESTAMP_MILLISECONDS_SCHEMA', () => { withUnixTimestampMillisecondsSchemaTests({ schema: UNIX_TIMESTAMP_MILLISECONDS_SCHEMA, getTestObjectWithUpdatedParameter: (newValue) => newValue, + getUpdatedParameterFromParsedTestObject: (data) => data, }); }); @@ -19,5 +21,6 @@ describe('UNIX_TIMESTAMP_SECONDS_SCHEMA', () => { withUnixTimestampSecondsSchemaTests({ schema: UNIX_TIMESTAMP_SECONDS_SCHEMA, getTestObjectWithUpdatedParameter: (newValue) => newValue, + getUpdatedParameterFromParsedTestObject: (data) => data, }); }); diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-currency-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-currency-schema.tests.ts index df7e2bca83..1fa351be28 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-currency-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-currency-schema.tests.ts @@ -7,12 +7,14 @@ export const withCurrencySchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }: WithSchemaTestParams) => { describe('with CURRENCY_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); it('should fail parsing if the parameter is not a valid currency', () => { diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-iso-date-time-stamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-iso-date-time-stamp-schema.tests.ts index 02395b4aa5..3426dcd547 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-iso-date-time-stamp-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-iso-date-time-stamp-schema.tests.ts @@ -6,11 +6,13 @@ export const withIsoDateTimeStampSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }: WithSchemaTestParams) => { describe('with ISO_DATE_TIME_STAMP_SCHEMA tests', () => { withDefaultOptionsTests({ schema, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, options, }); diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts index 922a344b16..39119f35cf 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts @@ -7,12 +7,14 @@ export const withObjectIdOrObjectIdStringSchemaTests = ) => { describe('with OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); it('should fail parsing if the parameter is not an ObjectId', () => { diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-schema.tests.ts index 0ab951d7e7..0e63cdf140 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-schema.tests.ts @@ -7,12 +7,14 @@ export const withObjectIdSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }: WithSchemaTestParams) => { describe('with OBJECT_ID_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); it('should fail parsing if the parameter is not an ObjectId', () => { diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests.ts index fa28ae3e2f..ceeffc3208 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests.ts @@ -7,12 +7,14 @@ export const withObjectIdStringSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }: WithSchemaTestParams) => { describe('with OBJECT_ID_STRING_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); it('should fail parsing if the parameter is not an ObjectId', () => { diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-tfm-team-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-tfm-team-schema.tests.ts index 212f6fa56b..95845cb064 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-tfm-team-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-tfm-team-schema.tests.ts @@ -3,12 +3,18 @@ import { WithSchemaTestParams } from '../with-schema-test.type'; import { TEAM_IDS } from '../../../constants'; import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; -export const withTfmTeamSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter }: WithSchemaTestParams) => { +export const withTfmTeamSchemaTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, +}: WithSchemaTestParams) => { describe('with TfmTeamSchema tests', () => { withDefaultOptionsTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); it('should fail parsing if the parameter is not a valid tfm-team', () => { diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-milliseconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-milliseconds-schema.tests.ts index 6ea85eec36..1c63331469 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-milliseconds-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-milliseconds-schema.tests.ts @@ -7,18 +7,21 @@ export const withUnixTimestampMillisecondsSchemaTests = ) => { describe('with UNIX_TIMESTAMP_MILLISECONDS_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); withNumberTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); it('should fail parsing if the parameter is not positive number', () => { diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts index 121d6b22a7..5bbe302b0c 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts @@ -7,18 +7,21 @@ export const withUnixTimestampSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }: WithSchemaTestParams) => { describe('with UNIX_TIMESTAMP_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); withNumberTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); it('should fail parsing if the parameter is not positive number', () => { diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts index d6ff0ea511..b94b5ca159 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts @@ -7,18 +7,21 @@ export const withUnixTimestampSecondsSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }: WithSchemaTestParams) => { describe('with UNIX_TIMESTAMP_SECONDS_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); withNumberTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); it('should fail parsing if the parameter is not positive number', () => { diff --git a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-array.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-array.tests.ts index 0e5fb533a3..1f428bcfd8 100644 --- a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-array.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-array.tests.ts @@ -13,6 +13,7 @@ export const withArrayTests = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }: WithSchemaTestParams) => { const arrayTestOptionsDefaults = { isAllowEmpty: true }; const arrayTestOptions = { @@ -25,6 +26,7 @@ export const withArrayTests = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); it('should fail parsing if the parameter is not a array', () => { @@ -49,6 +51,7 @@ export const withArrayTests = ({ schema, testCase: arrayTestOptions.arrayTypeTestCase, getTestObjectWithUpdatedParameter: (value) => getTestObjectWithUpdatedParameter([value]), + getUpdatedParameterFromParsedTestObject: (parsedPayload) => getUpdatedParameterFromParsedTestObject(parsedPayload), }); }); }); diff --git a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-boolean.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-boolean.tests.ts index 0b6b4b966a..1a6e452f77 100644 --- a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-boolean.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-boolean.tests.ts @@ -2,12 +2,18 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; import { withDefaultOptionsTests } from './with-default-options.tests'; -export const withBooleanTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter }: WithSchemaTestParams) => { +export const withBooleanTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, +}: WithSchemaTestParams) => { describe('with boolean tests', () => { withDefaultOptionsTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); it('should fail parsing if the parameter is not a boolean', () => { diff --git a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-number.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-number.tests.ts index 5c05ea3247..3834c25acf 100644 --- a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-number.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-number.tests.ts @@ -2,12 +2,18 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; import { withDefaultOptionsTests } from './with-default-options.tests'; -export const withNumberTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter }: WithSchemaTestParams) => { +export const withNumberTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, +}: WithSchemaTestParams) => { describe('with number tests', () => { withDefaultOptionsTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); it('should fail parsing if the parameter is not a number', () => { diff --git a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-string.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-string.tests.ts index 83f000a598..403299da05 100644 --- a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-string.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-string.tests.ts @@ -2,12 +2,18 @@ import { ZodSchema } from 'zod'; import { WithSchemaTestParams } from '../with-schema-test.type'; import { withDefaultOptionsTests } from './with-default-options.tests'; -export const withStringTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter }: WithSchemaTestParams) => { +export const withStringTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, +}: WithSchemaTestParams) => { describe('with string tests', () => { withDefaultOptionsTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); it('should fail parsing if the parameter is not a string', () => { diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts index d7bfc14d69..cb0f5a0463 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts @@ -9,11 +9,13 @@ export const withAuditDatabaseRecordSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }: WithSchemaTestParams) => { describe('with AUDIT_DATABASE_RECORD_SCHEMA tests', () => { withDefaultOptionsTests({ schema, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, options, }); diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts index 3b5a3cd113..ab54824dbb 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts @@ -8,11 +8,13 @@ export const withEntraIdUserSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }: WithSchemaTestParams) => { describe('with ENTRA_ID_USER_SCHEMA tests', () => { withDefaultOptionsTests({ schema, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, options, }); diff --git a/libs/common/src/test-helpers/schemas/transformation-tests/index.ts b/libs/common/src/test-helpers/schemas/transformation-tests/index.ts index cabd67682a..11d0f0f0fb 100644 --- a/libs/common/src/test-helpers/schemas/transformation-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/transformation-tests/index.ts @@ -1 +1,4 @@ +/** + * These tests are for schemas that also transform data + */ export * from './with-iso-date-time-stamp-to-date.schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/transformation-tests/with-iso-date-time-stamp-to-date.schema.tests.ts b/libs/common/src/test-helpers/schemas/transformation-tests/with-iso-date-time-stamp-to-date.schema.tests.ts index 8aae91ccd2..e871724db1 100644 --- a/libs/common/src/test-helpers/schemas/transformation-tests/with-iso-date-time-stamp-to-date.schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/transformation-tests/with-iso-date-time-stamp-to-date.schema.tests.ts @@ -6,12 +6,14 @@ export const withIsoDateTimeStampToDateSchemaTests = ( schema, options = {}, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }: WithSchemaTestParams) => { describe('with ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); it('should fail parsing if the parameter is not a valid iso date with offset', () => { @@ -23,5 +25,12 @@ export const withIsoDateTimeStampToDateSchemaTests = ( const { success } = schema.safeParse(getTestObjectWithUpdatedParameter('2023-10-01T12:00:00Z')); expect(success).toBe(true); }); + + it('should transform the string into a date object', () => { + const testDateAsIsoTimeStamp = '2023-10-01T12:00:00Z'; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { data } = schema.safeParse(getTestObjectWithUpdatedParameter(testDateAsIsoTimeStamp)); + expect(getUpdatedParameterFromParsedTestObject(data)).toEqual(new Date(testDateAsIsoTimeStamp)); + }); }); }; diff --git a/libs/common/src/test-helpers/schemas/with-schema-test.type.ts b/libs/common/src/test-helpers/schemas/with-schema-test.type.ts index fb77106530..1e4499a5c5 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-test.type.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-test.type.ts @@ -4,4 +4,5 @@ import { DefaultOptions } from './primitive-types-tests/with-default-options.tes export type WithSchemaTestParams = { schema: Schema; getTestObjectWithUpdatedParameter: (newValue: unknown) => unknown; + getUpdatedParameterFromParsedTestObject: (parsedTestObject: unknown) => unknown; } & (SchemaTestOptions extends false ? { options?: Partial } : { options: SchemaTestOptions & Partial }); diff --git a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts index 7473dfc33c..3707a4bee9 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts @@ -122,11 +122,17 @@ export const withSchemaValidationTests = ({ ? testCase.options.overrideGetTestObjectWithUpdatedField : (newValue: unknown): unknown => ({ ...aValidPayload(), [parameterPath]: newValue }); + const getUpdatedParameterFromParsedTestObject = (parsedPayload: unknown) => { + const parsedPayloadAsRecord = parsedPayload as Record; + return parsedPayloadAsRecord[parameterPath]; + }; + describe(`${parameterPath} parameter tests`, () => { withTestsForTestcase({ schema, testCase, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); }); }); diff --git a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts index db1adccb4e..579110598b 100644 --- a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts +++ b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts @@ -24,10 +24,12 @@ export const withTestsForTestcase = ({ schema, testCase, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }: { schema: Schema; testCase: TestCase; getTestObjectWithUpdatedParameter: (newValue: unknown) => unknown; + getUpdatedParameterFromParsedTestObject: (parsedTestObject: unknown) => unknown; }) => { const { type, options } = testCase; @@ -37,6 +39,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -45,6 +48,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -53,6 +57,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -61,6 +66,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -69,6 +75,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -77,6 +84,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -85,6 +93,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -93,6 +102,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -101,6 +111,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -109,6 +120,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -117,6 +129,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -125,6 +138,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -133,6 +147,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -141,6 +156,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -149,6 +165,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; @@ -157,6 +174,7 @@ export const withTestsForTestcase = ({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); break; From cd59c23a5dedd69c1064bd36039cc74ce372682f Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 7 Jan 2025 15:10:30 +0000 Subject: [PATCH 076/133] feat(dtfs2-6892): review comments --- .../user-partial-login-data-not-defined.error.test.ts | 8 ++++---- .../services/entra-id.service.get-auth-code-url.test.ts | 6 +++--- .../entra-id.api.get-authority-metadata-url.test.ts | 2 +- ...uthenticated-auth.controller.post-sso-redirect.test.ts | 2 +- .../login/login-sso/login.controller.get-login.test.ts | 8 ++++---- .../login/login-sso/login.controller.get-logout.test.ts | 4 ++-- .../services/login.service.get-auth-code-url.test.ts | 7 ++++--- 7 files changed, 19 insertions(+), 18 deletions(-) diff --git a/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts b/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts index 3c7f7f28d1..46adc8bd0f 100644 --- a/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts +++ b/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts @@ -4,7 +4,7 @@ import { UserSessionError } from './user-session.error'; import { UserPartialLoginDataNotDefinedError } from './user-partial-login-data-not-defined.error'; describe('UserPartialLoginDataNotDefinedError', () => { - it('exposes the message the error was created with', () => { + it('should expose the message the error was created with', () => { // Act const exception = new UserPartialLoginDataNotDefinedError(); @@ -12,7 +12,7 @@ describe('UserPartialLoginDataNotDefinedError', () => { expect(exception.message).toEqual('Expected session.loginData to be defined'); }); - it('exposes the 401 (Unauthorised) status code', () => { + it('should expose the 401 (Unauthorised) status code', () => { // Act const exception = new UserPartialLoginDataNotDefinedError(); @@ -20,7 +20,7 @@ describe('UserPartialLoginDataNotDefinedError', () => { expect(exception.status).toEqual(HttpStatusCode.Unauthorized); }); - it('exposes the INVALID_USER_SESSION code', () => { + it('should expose the INVALID_USER_SESSION code', () => { // Act const exception = new UserPartialLoginDataNotDefinedError(); @@ -52,7 +52,7 @@ describe('UserPartialLoginDataNotDefinedError', () => { expect(exception).toBeInstanceOf(ApiError); }); - it('exposes the name of the exception', () => { + it('should expose the name of the exception', () => { // Act const exception = new UserPartialLoginDataNotDefinedError(); diff --git a/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts index c3b05025f8..2ba1ba7019 100644 --- a/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts +++ b/trade-finance-manager-api/src/v1/services/entra-id.service.get-auth-code-url.test.ts @@ -46,7 +46,7 @@ describe('EntraIdService', () => { entraIdApi = new EntraIdApiMockBuilder().withDefaults().build(); }); - it('calls base64Encode with the expected stringifiedstate', async () => { + it('should call base64Encode with the expected stringifiedstate', async () => { // Arrange const successRedirect = 'a-success-redirect'; const service = getAEntraIdServiceInstance(); @@ -58,7 +58,7 @@ describe('EntraIdService', () => { expect(base64EncodeSpy).toHaveBeenCalledWith(JSON.stringify({ csrfToken: mockGuid, successRedirect })); }); - it('returns the auth code url from the msal app', async () => { + it('should return the auth code url from the msal app', async () => { // Arrange const service = getAEntraIdServiceInstance(); @@ -69,7 +69,7 @@ describe('EntraIdService', () => { expect(authCodeUrl).toEqual(authCodeUrlFromMsalApp); }); - it('returns the auth code url request', async () => { + it('should return the auth code url request', async () => { // Arrange const service = getAEntraIdServiceInstance(); diff --git a/trade-finance-manager-api/src/v1/third-party-apis/entra-id.api.get-authority-metadata-url.test.ts b/trade-finance-manager-api/src/v1/third-party-apis/entra-id.api.get-authority-metadata-url.test.ts index a06361c343..1c5ee64811 100644 --- a/trade-finance-manager-api/src/v1/third-party-apis/entra-id.api.get-authority-metadata-url.test.ts +++ b/trade-finance-manager-api/src/v1/third-party-apis/entra-id.api.get-authority-metadata-url.test.ts @@ -24,7 +24,7 @@ describe('EntraIdApi', () => { mockAxios.onGet(authorityMetadataUrl).reply(200, { aMetaDataObject: 'a-meta-data-value' }); }); - it('returns the response data from the api call', async () => { + it('should return the response data from the api call', async () => { // Act const result = entraIdApi.getAuthorityMetadataUrl(); diff --git a/trade-finance-manager-ui/server/controllers/auth/auth-sso/unauthenticated-auth.controller.post-sso-redirect.test.ts b/trade-finance-manager-ui/server/controllers/auth/auth-sso/unauthenticated-auth.controller.post-sso-redirect.test.ts index b191aa173f..1b7a18c0a9 100644 --- a/trade-finance-manager-ui/server/controllers/auth/auth-sso/unauthenticated-auth.controller.post-sso-redirect.test.ts +++ b/trade-finance-manager-ui/server/controllers/auth/auth-sso/unauthenticated-auth.controller.post-sso-redirect.test.ts @@ -29,7 +29,7 @@ describe('controllers - unauthenticated auth (sso)', () => { }); describe('postSsoRedirect', () => { - it('throws an error if body validation fails', () => { + it('should throw an error if body validation fails', () => { jest.mocked(isVerifiedPayload).mockReturnValue(false); expect(() => unauthenticatedAuthController.postSsoRedirect(req, res)).toThrow('Invalid payload from SSO redirect'); diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts index e4127ebdea..9e7183f738 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts @@ -35,7 +35,7 @@ describe('controllers - login (sso)', () => { session: requestSession, }); - it('redirects to /home', async () => { + it('should redirect to /home', async () => { // Arrange const { req, res } = getHttpMocks(); @@ -53,7 +53,7 @@ describe('controllers - login (sso)', () => { mockSuccessfulGetAuthCodeUrl(); }); - it('redirects to login URL', async () => { + it('should redirect to login URL', async () => { // Arrange const { req, res } = httpMocks.createMocks({ session: {} }); @@ -64,7 +64,7 @@ describe('controllers - login (sso)', () => { expect(res._getRedirectUrl()).toEqual(mockAuthCodeUrl); }); - it('overrides session login data if present', async () => { + it('should override session login data if present', async () => { // Arrange const { req, res } = httpMocks.createMocks({ session: { loginData: { authCodeUrlRequest: 'an old auth code url request', aField: 'another field' } }, @@ -85,7 +85,7 @@ describe('controllers - login (sso)', () => { mockFailedGetAuthCodeUrl(); }); - it('calls next with error', async () => { + it('should call next with error', async () => { // Arrange const { req, res } = httpMocks.createMocks({ session: {} }); const error = new Error('getAuthCodeUrl error'); diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-logout.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-logout.test.ts index 575fd74b9b..9059e4f766 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-logout.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-logout.test.ts @@ -13,7 +13,7 @@ describe('controllers - login (sso)', () => { loginController = new LoginController({ loginService }); }); - it('redirects to /', () => { + it('should redirect to /', () => { // Arrange const { req, res } = getHttpMocks(); @@ -24,7 +24,7 @@ describe('controllers - login (sso)', () => { expect(res._getRedirectUrl()).toEqual('/'); }); - it('destroys the session', () => { + it('should destroy the session', () => { // Arrange const { req, res } = getHttpMocks(); diff --git a/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts b/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts index 7f1deb47d6..a4afc7e91b 100644 --- a/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts +++ b/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts @@ -15,11 +15,12 @@ describe('login service', () => { getAuthCodeUrlSpy.mockReset(); }); - it('calls api.getAuthCodeUrl with the request', async () => { + it('should call api.getAuthCodeUrl with the request', async () => { // Act await loginService.getAuthCodeUrl({ successRedirect }); // Assert + expect(getAuthCodeUrlSpy).toHaveBeenCalledTimes(1); expect(getAuthCodeUrlSpy).toHaveBeenCalledWith({ successRedirect }); }); @@ -33,7 +34,7 @@ describe('login service', () => { getAuthCodeUrlSpy.mockResolvedValueOnce(mockGetAuthCodeResponse); }); - it('returns the auth code url', async () => { + it('should return the auth code url', async () => { // Act const result = await loginService.getAuthCodeUrl({ successRedirect }); @@ -49,7 +50,7 @@ describe('login service', () => { getAuthCodeUrlSpy.mockRejectedValueOnce(error); }); - it('throws the error', async () => { + it('should throw the error', async () => { // Act & Assert await expect(loginService.getAuthCodeUrl({ successRedirect })).rejects.toThrow(error); }); From f1d97f233a9a398cd3707ee70a7fc137a122440d Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 7 Jan 2025 16:31:12 +0000 Subject: [PATCH 077/133] feat(dtfs2-6892): review comments --- .../sso.controller.get-auth-code-url.test.ts | 9 +-------- ...o.controller.handle-sso-redirect-form.test.ts | 16 +++++----------- .../src/v1/controllers/sso.controller.ts | 1 + 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts index 74bb4ba60c..550c00b7cb 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts @@ -54,16 +54,9 @@ describe('SsoController', () => { // Act await ssoController.getAuthCodeUrl(req, res); - // Assert - expect(getAuthCodeUrlMock).toHaveBeenCalledWith(expectedGetAuthCodeUrlParams); - }); - - it('should call entraIdService.getAuthCodeUrl once', async () => { - // Act - await ssoController.getAuthCodeUrl(req, res); - // Assert expect(getAuthCodeUrlMock).toHaveBeenCalledTimes(1); + expect(getAuthCodeUrlMock).toHaveBeenCalledWith(expectedGetAuthCodeUrlParams); }); it('should return the result of entraIdService.getAuthCodeUrl', async () => { diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts index 15a4f9af09..b248606fc2 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts @@ -2,6 +2,7 @@ import httpMocks, { MockRequest, MockResponse } from 'node-mocks-http'; import { anAuthorisationCodeRequest, anEntraIdAuthCodeRedirectResponseBody, + API_ERROR_CODE, AuditDetails, HandleSsoRedirectFormApiRequest, HandleSsoRedirectFormApiResponse, @@ -67,7 +68,7 @@ describe('SsoController', () => { expect(res._getData()).toEqual({ status: 400, message: "Failed to handle redirect form: Supplied auditDetails 'userType' must be 'system' (was 'tfm')", - code: 'INVALID_AUDIT_DETAILS', + code: API_ERROR_CODE.INVALID_AUDIT_DETAILS, }); }); }); @@ -90,7 +91,7 @@ describe('SsoController', () => { expect(res._getData()).toEqual({ status: 400, message: "Failed to handle redirect form: Supplied auditDetails must contain a 'userType' property", - code: 'INVALID_AUDIT_DETAILS', + code: API_ERROR_CODE.INVALID_AUDIT_DETAILS, }); }); }); @@ -131,21 +132,14 @@ describe('SsoController', () => { // Act await ssoController.handleSsoRedirectForm(req, res); - // Assert + // Assert ] + expect(handleRedirectMock).toHaveBeenCalledTimes(1); expect(handleRedirectMock).toHaveBeenCalledWith({ authCodeResponse: req.body.authCodeResponse, originalAuthCodeUrlRequest: req.body.originalAuthCodeUrlRequest, }); }); - it('should call handleRedirect once', async () => { - // Act - await ssoController.handleSsoRedirectForm(req, res); - - // Assert - expect(handleRedirectMock).toHaveBeenCalledTimes(1); - }); - describe('when handling the redirect is unsuccessful', () => { withApiErrorTests({ makeRequest: async () => await ssoController.handleSsoRedirectForm(req, res), diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts index d1cfa2871e..abba824764 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts @@ -41,6 +41,7 @@ export class SsoController { }); return; } + res.status(HttpStatusCode.InternalServerError).send({ status: HttpStatusCode.InternalServerError, message: errorMessage, From 9f60efa9c697da2f4de5e1e978075169ca54a404 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 7 Jan 2025 16:43:34 +0000 Subject: [PATCH 078/133] feat(dtfs2-6892): review comments --- trade-finance-manager-api/src/v1/services/entra-id.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/trade-finance-manager-api/src/v1/services/entra-id.service.ts b/trade-finance-manager-api/src/v1/services/entra-id.service.ts index d5ea927014..211d90c645 100644 --- a/trade-finance-manager-api/src/v1/services/entra-id.service.ts +++ b/trade-finance-manager-api/src/v1/services/entra-id.service.ts @@ -4,7 +4,6 @@ import { DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA, ENTRA_ID_AUTHENTICATION_RESULT_ import { EntraIdConfig } from '../configs/entra-id.config'; import { EntraIdApi } from '../third-party-apis/entra-id.api'; -// Todo remove this and commonise export type GetAuthCodeUrlParams = { successRedirect?: string; }; From d6929c3c2d83be2069037a80b7bd111017cf0390 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 7 Jan 2025 16:45:43 +0000 Subject: [PATCH 079/133] feat(dtfs2-6892): review comments --- trade-finance-manager-api/src/v1/services/entra-id.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/trade-finance-manager-api/src/v1/services/entra-id.service.ts b/trade-finance-manager-api/src/v1/services/entra-id.service.ts index 211d90c645..c3f53b0c03 100644 --- a/trade-finance-manager-api/src/v1/services/entra-id.service.ts +++ b/trade-finance-manager-api/src/v1/services/entra-id.service.ts @@ -68,7 +68,6 @@ export class EntraIdService { throw new Error('No auth code URL request found in session'); } - // TODO -- This validates the user as well, lets consider renaming this const { entraIdUser } = await this.getEntraIdUserByAuthCode({ authCodeResponse, originalAuthCodeUrlRequest, From 3eb8aef589bfde62e03258b82b5127c486131c5c Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 7 Jan 2025 17:06:38 +0000 Subject: [PATCH 080/133] feat(dtfs2-6892): fix ts error --- .../server/routes/auth/configs/auth-sso.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts index 2ae2a3cca8..ece02b833a 100644 --- a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts +++ b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts @@ -17,7 +17,8 @@ export const getAuthSsoRouter: GetRouter = () => { const authSsoRouter = express.Router(); // Todo: update this to check the right token - // eslint-disable-next-line @typescript-eslint/no-misused-promises - authSsoRouter.post('/auth/sso-redirect/form', loginController.handleSsoRedirectForm.bind(loginController)); + authSsoRouter.post('/auth/sso-redirect/form', (req, res, next) => { + loginController.handleSsoRedirectForm(req, res).catch(next); + }); return authSsoRouter; }; From 9ebfdacb30740f0249dfb65c356e44b901b6b746 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 14 Jan 2025 15:51:02 +0000 Subject: [PATCH 081/133] feat(dtfs2-6892): update to review comments --- .../user-partial-login-data-not-defined.error.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts b/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts index 46adc8bd0f..40504a7ae1 100644 --- a/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts +++ b/libs/common/src/errors/user-partial-login-data-not-defined.error.test.ts @@ -28,7 +28,7 @@ describe('UserPartialLoginDataNotDefinedError', () => { expect(exception.code).toEqual('INVALID_USER_SESSION'); }); - it('is an instance of UserPartialLoginDataNotDefinedError', () => { + it('should be an instance of UserPartialLoginDataNotDefinedError', () => { // Act const exception = new UserPartialLoginDataNotDefinedError(); @@ -36,7 +36,7 @@ describe('UserPartialLoginDataNotDefinedError', () => { expect(exception).toBeInstanceOf(UserPartialLoginDataNotDefinedError); }); - it('is an instance of UserSessionError', () => { + it('should be an instance of UserSessionError', () => { // Act const exception = new UserPartialLoginDataNotDefinedError(); @@ -44,7 +44,7 @@ describe('UserPartialLoginDataNotDefinedError', () => { expect(exception).toBeInstanceOf(UserSessionError); }); - it('is an instance of ApiError', () => { + it('should be an instance of ApiError', () => { // Act const exception = new UserPartialLoginDataNotDefinedError(); From ec175ad0378e6d1c53cdec7e4f44899520122d0e Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 14 Jan 2025 17:16:09 +0000 Subject: [PATCH 082/133] feat(dtfs2-7693): update to review comments --- libs/common/src/schemas/audit-database-record.schema.ts | 2 +- libs/common/src/schemas/index.ts | 6 +++--- .../schemas/{object-id.test.ts => object-id.schema.test.ts} | 2 +- .../src/schemas/{object-id.ts => object-id.schema.ts} | 0 ...al-amendment.test.ts => portal-amendment.schema.test.ts} | 2 +- .../{portal-amendment.ts => portal-amendment.schema.ts} | 0 .../src/schemas/{portal-user.ts => portal-user.schema.ts} | 0 ...ser.update.test.ts => portal-user.schema.update.test.ts} | 2 +- ...user.create.test.ts => portal-user.schma.create.test.ts} | 2 +- libs/common/src/schemas/tfm/tfm-session-user.schema.ts | 2 +- libs/common/src/schemas/tfm/tfm-user.schema.ts | 2 +- 11 files changed, 10 insertions(+), 10 deletions(-) rename libs/common/src/schemas/{object-id.test.ts => object-id.schema.test.ts} (95%) rename libs/common/src/schemas/{object-id.ts => object-id.schema.ts} (100%) rename libs/common/src/schemas/{portal-amendment.test.ts => portal-amendment.schema.test.ts} (99%) rename libs/common/src/schemas/{portal-amendment.ts => portal-amendment.schema.ts} (100%) rename libs/common/src/schemas/{portal-user.ts => portal-user.schema.ts} (100%) rename libs/common/src/schemas/{portal-user.update.test.ts => portal-user.schema.update.test.ts} (98%) rename libs/common/src/schemas/{portal-user.create.test.ts => portal-user.schma.create.test.ts} (97%) diff --git a/libs/common/src/schemas/audit-database-record.schema.ts b/libs/common/src/schemas/audit-database-record.schema.ts index a4817bdd3a..5523ee6f13 100644 --- a/libs/common/src/schemas/audit-database-record.schema.ts +++ b/libs/common/src/schemas/audit-database-record.schema.ts @@ -1,6 +1,6 @@ import z from 'zod'; import { ISO_DATE_TIME_STAMP_SCHEMA } from './iso-date-time-stamp.schema'; -import { OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA } from './object-id'; +import { OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA } from './object-id.schema'; export const AUDIT_DATABASE_RECORD_SCHEMA = z .object({ diff --git a/libs/common/src/schemas/index.ts b/libs/common/src/schemas/index.ts index 3be9126cc9..7eb57db61c 100644 --- a/libs/common/src/schemas/index.ts +++ b/libs/common/src/schemas/index.ts @@ -1,8 +1,8 @@ -export * as PORTAL_USER from './portal-user'; +export * as PORTAL_USER from './portal-user.schema'; export * as ISO_DATE_TIME_STAMP from './iso-date-time-stamp.schema'; -export * from './object-id'; +export * from './object-id.schema'; export * from './deal-cancellation'; export * from './tfm'; export * from './unix-timestamp.schema'; -export * from './portal-amendment'; +export * from './portal-amendment.schema'; export * from './currency.schema'; diff --git a/libs/common/src/schemas/object-id.test.ts b/libs/common/src/schemas/object-id.schema.test.ts similarity index 95% rename from libs/common/src/schemas/object-id.test.ts rename to libs/common/src/schemas/object-id.schema.test.ts index d1d0aa40b4..432c500012 100644 --- a/libs/common/src/schemas/object-id.test.ts +++ b/libs/common/src/schemas/object-id.schema.test.ts @@ -1,4 +1,4 @@ -import { OBJECT_ID_SCHEMA, OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA, OBJECT_ID_STRING_SCHEMA } from './object-id'; +import { OBJECT_ID_SCHEMA, OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA, OBJECT_ID_STRING_SCHEMA } from './object-id.schema'; import { withObjectIdSchemaTests } from '../test-helpers/schemas/custom-types-tests/with-object-id-schema.tests'; import { withObjectIdOrObjectIdStringSchemaTests } from '../test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests'; import { withObjectIdStringSchemaTests } from '../test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests'; diff --git a/libs/common/src/schemas/object-id.ts b/libs/common/src/schemas/object-id.schema.ts similarity index 100% rename from libs/common/src/schemas/object-id.ts rename to libs/common/src/schemas/object-id.schema.ts diff --git a/libs/common/src/schemas/portal-amendment.test.ts b/libs/common/src/schemas/portal-amendment.schema.test.ts similarity index 99% rename from libs/common/src/schemas/portal-amendment.test.ts rename to libs/common/src/schemas/portal-amendment.schema.test.ts index c06b3e6e79..c8a5ab6b2c 100644 --- a/libs/common/src/schemas/portal-amendment.test.ts +++ b/libs/common/src/schemas/portal-amendment.schema.test.ts @@ -1,4 +1,4 @@ -import { PORTAL_FACILITY_AMENDMENT_USER_VALUES } from './portal-amendment'; +import { PORTAL_FACILITY_AMENDMENT_USER_VALUES } from './portal-amendment.schema'; import { withSchemaValidationTests } from '../test-helpers'; import { aPortalFacilityAmendmentUserValues } from '../test-helpers/mock-data-backend'; import { PortalFacilityAmendmentUserValues } from '../types'; diff --git a/libs/common/src/schemas/portal-amendment.ts b/libs/common/src/schemas/portal-amendment.schema.ts similarity index 100% rename from libs/common/src/schemas/portal-amendment.ts rename to libs/common/src/schemas/portal-amendment.schema.ts diff --git a/libs/common/src/schemas/portal-user.ts b/libs/common/src/schemas/portal-user.schema.ts similarity index 100% rename from libs/common/src/schemas/portal-user.ts rename to libs/common/src/schemas/portal-user.schema.ts diff --git a/libs/common/src/schemas/portal-user.update.test.ts b/libs/common/src/schemas/portal-user.schema.update.test.ts similarity index 98% rename from libs/common/src/schemas/portal-user.update.test.ts rename to libs/common/src/schemas/portal-user.schema.update.test.ts index 91c3b76e1f..0de79f9031 100644 --- a/libs/common/src/schemas/portal-user.update.test.ts +++ b/libs/common/src/schemas/portal-user.schema.update.test.ts @@ -1,7 +1,7 @@ import { ObjectId } from 'mongodb'; import z from 'zod'; import { generatePortalUserAuditDatabaseRecord } from '../change-stream'; -import { UPDATE } from './portal-user'; +import { UPDATE } from './portal-user.schema'; import { withSchemaValidationTests } from '../test-helpers'; describe('PORTAL_USER', () => { diff --git a/libs/common/src/schemas/portal-user.create.test.ts b/libs/common/src/schemas/portal-user.schma.create.test.ts similarity index 97% rename from libs/common/src/schemas/portal-user.create.test.ts rename to libs/common/src/schemas/portal-user.schma.create.test.ts index 976e38e6c0..71f3d3906d 100644 --- a/libs/common/src/schemas/portal-user.create.test.ts +++ b/libs/common/src/schemas/portal-user.schma.create.test.ts @@ -1,7 +1,7 @@ import { ObjectId } from 'mongodb'; import z from 'zod'; import { generatePortalUserAuditDatabaseRecord } from '../change-stream'; -import { CREATE } from './portal-user'; +import { CREATE } from './portal-user.schema'; import { withSchemaValidationTests } from '../test-helpers'; describe('PORTAL_USER', () => { diff --git a/libs/common/src/schemas/tfm/tfm-session-user.schema.ts b/libs/common/src/schemas/tfm/tfm-session-user.schema.ts index 5a6592b6cb..8c53a15e67 100644 --- a/libs/common/src/schemas/tfm/tfm-session-user.schema.ts +++ b/libs/common/src/schemas/tfm/tfm-session-user.schema.ts @@ -1,5 +1,5 @@ import { TFM_USER_SCHEMA } from './tfm-user.schema'; -import { OBJECT_ID_STRING_SCHEMA } from '../object-id'; +import { OBJECT_ID_STRING_SCHEMA } from '../object-id.schema'; export const TFM_SESSION_USER_SCHEMA = TFM_USER_SCHEMA.pick({ username: true, diff --git a/libs/common/src/schemas/tfm/tfm-user.schema.ts b/libs/common/src/schemas/tfm/tfm-user.schema.ts index 33947def11..66ee6fa42b 100644 --- a/libs/common/src/schemas/tfm/tfm-user.schema.ts +++ b/libs/common/src/schemas/tfm/tfm-user.schema.ts @@ -2,7 +2,7 @@ import z from 'zod'; import { TfmTeamSchema } from './tfm-team.schema'; import { UNIX_TIMESTAMP_MILLISECONDS_SCHEMA } from '../unix-timestamp.schema'; import { AUDIT_DATABASE_RECORD_SCHEMA } from '../audit-database-record.schema'; -import { OBJECT_ID_SCHEMA } from '../object-id'; +import { OBJECT_ID_SCHEMA } from '../object-id.schema'; // TODO update docs, tests const TFM_USER_NON_SSO_SPECIFIC_SCHEMA = z.object({ From b827c692ff015eb047cf1f4037c9f885109f610f Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 14 Jan 2025 17:20:05 +0000 Subject: [PATCH 083/133] feat(dtfs2-7693): update to review comments --- trade-finance-manager-ui/server/api.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index 839ee6a4e0..4d7b5436d0 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -1075,7 +1075,7 @@ const addPaymentToFeeRecords = async (reportId, parsedAddPaymentFormValues, feeR * Create a record correction * @param {string} reportId - The report id * @param {string} feeRecordId - The fee record id - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The user * @param {string} userToken - The user token * @returns {Promise} */ @@ -1399,7 +1399,7 @@ const getFeeRecordCorrectionRequestReview = async (reportId, feeRecordId, userId * @param {string} reportId - The report id * @param {string} feeRecordId - The fee record id * @param {import('@ukef/dtfs2-common').RecordCorrectionRequestTransientFormData} formData - The transient form data - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The session user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The session user * @param {string} userToken - The user token * @returns {Promise} * @throws {Error} If the API request fails @@ -1425,7 +1425,7 @@ const updateFeeRecordCorrectionTransientFormData = async (reportId, feeRecordId, * Gets the fee record by report id, fee record id and user * @param {string} reportId - The report id * @param {string} feeRecordId - The fee record id - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The session user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The session user * @param {string} userToken - The user token * @returns {Promise} */ @@ -1448,7 +1448,7 @@ const getFeeRecordCorrectionTransientFormData = async (reportId, feeRecordId, us * Deletes the fee record by report id, fee record id and user * @param {string} reportId - The report id * @param {string} feeRecordId - The fee record id - * @param {import('./types/tfm-session-user').TfmSessionUser} user - The session user + * @param {import('@ukef/dtfs2-common').TfmSessionUser} user - The session user * @param {string} userToken - The user token * @returns {Promise} */ From dd1dedfa905a1646cde937a3f55aee492a3744ff Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 15 Jan 2025 15:08:20 +0000 Subject: [PATCH 084/133] feat(dtfs2-6892): update HandleSsoRedirectFormResponse to use schema, add schema validation from api --- ...e-sso-redirect-form-request.schema.test.ts | 37 ++++++ ...andle-sso-redirect-form-response.schema.ts | 9 ++ libs/common/src/schemas/tfm/index.ts | 1 + .../tfm/tfm-session-user.schema.test.ts | 9 ++ .../schemas/schema-tests/index.ts | 1 + .../with-tfm-session-user-schema.tests.ts | 112 ++++++++++++++++++ .../schemas/with-test-for-test-case.type.ts | 6 +- .../schemas/with-tests-for-testcase.ts | 10 +- .../src/types/tfm/handle-sso-redirect-form.ts | 10 +- trade-finance-manager-ui/server/api.js | 7 +- 10 files changed, 189 insertions(+), 13 deletions(-) create mode 100644 libs/common/src/schemas/tfm/handle-sso-redirect-form-request.schema.test.ts create mode 100644 libs/common/src/schemas/tfm/handle-sso-redirect-form-response.schema.ts create mode 100644 libs/common/src/schemas/tfm/tfm-session-user.schema.test.ts create mode 100644 libs/common/src/test-helpers/schemas/schema-tests/with-tfm-session-user-schema.tests.ts diff --git a/libs/common/src/schemas/tfm/handle-sso-redirect-form-request.schema.test.ts b/libs/common/src/schemas/tfm/handle-sso-redirect-form-request.schema.test.ts new file mode 100644 index 0000000000..c5ca62dad3 --- /dev/null +++ b/libs/common/src/schemas/tfm/handle-sso-redirect-form-request.schema.test.ts @@ -0,0 +1,37 @@ +import { aValidTfmSessionUser, withSchemaValidationTests } from '../../test-helpers'; +import { HANDLE_SSO_REDIRECT_FORM_RESPONSE_SCHEMA } from './handle-sso-redirect-form-response.schema'; + +describe('HANDLE_SSO_REDIRECT_FORM_RESPONSE_SCHEMA', () => { + withSchemaValidationTests({ + schema: HANDLE_SSO_REDIRECT_FORM_RESPONSE_SCHEMA, + aValidPayload, + testCases: [ + { + parameterPath: 'user', + type: 'TFM_SESSION_USER_SCHEMA', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => ({ ...aValidPayload(), user: newValue }), + }, + }, + { + parameterPath: 'token', + type: 'string', + }, + { + parameterPath: 'expires', + type: 'string', + }, + { + parameterPath: 'successRedirect', + type: 'string', + options: { + isOptional: true, + }, + }, + ], + }); + + function aValidPayload() { + return { user: aValidTfmSessionUser(), token: 'a-token', expires: 'a-date', successRedirect: 'a-redirect' }; + } +}); diff --git a/libs/common/src/schemas/tfm/handle-sso-redirect-form-response.schema.ts b/libs/common/src/schemas/tfm/handle-sso-redirect-form-response.schema.ts new file mode 100644 index 0000000000..6fc4ca5163 --- /dev/null +++ b/libs/common/src/schemas/tfm/handle-sso-redirect-form-response.schema.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; +import { TFM_SESSION_USER_SCHEMA } from './tfm-session-user.schema'; + +export const HANDLE_SSO_REDIRECT_FORM_RESPONSE_SCHEMA = z.object({ + user: TFM_SESSION_USER_SCHEMA, + token: z.string(), + expires: z.string(), + successRedirect: z.string().optional(), +}); diff --git a/libs/common/src/schemas/tfm/index.ts b/libs/common/src/schemas/tfm/index.ts index dff1410b89..7ea821ae70 100644 --- a/libs/common/src/schemas/tfm/index.ts +++ b/libs/common/src/schemas/tfm/index.ts @@ -7,3 +7,4 @@ export * from './upsert-tfm-user-request.schema'; export * from './tfm-team.schema'; export * from './tfm-user.schema'; export * from './tfm-session-user.schema'; +export * from './handle-sso-redirect-form-response.schema'; diff --git a/libs/common/src/schemas/tfm/tfm-session-user.schema.test.ts b/libs/common/src/schemas/tfm/tfm-session-user.schema.test.ts new file mode 100644 index 0000000000..6facf49bdb --- /dev/null +++ b/libs/common/src/schemas/tfm/tfm-session-user.schema.test.ts @@ -0,0 +1,9 @@ +import { withTfmSessionUserSchemaTests } from '../../test-helpers'; +import { TFM_SESSION_USER_SCHEMA } from './tfm-session-user.schema'; + +describe('TFM_SESSION_USER_SCHEMA', () => { + withTfmSessionUserSchemaTests({ + schema: TFM_SESSION_USER_SCHEMA, + getTestObjectWithUpdatedParameter: (newValue: unknown) => newValue, + }); +}); diff --git a/libs/common/src/test-helpers/schemas/schema-tests/index.ts b/libs/common/src/test-helpers/schemas/schema-tests/index.ts index 75342c458f..f1d56b72aa 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/index.ts @@ -5,3 +5,4 @@ export * from './with-audit-database-record-schema.tests'; export * from './with-entra-id-user-schema.tests'; export * from './with-tfm-team-schema.tests'; +export * from './with-tfm-session-user-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-session-user-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-session-user-schema.tests.ts new file mode 100644 index 0000000000..a0f5c27b5e --- /dev/null +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-session-user-schema.tests.ts @@ -0,0 +1,112 @@ +import { ZodSchema } from 'zod'; +import { ObjectId } from 'mongodb'; +import { WithSchemaTestParams } from '../with-schema-test.type'; +import { TEAM_IDS } from '../../../constants'; +import { withSchemaValidationTests } from '../with-schema-validation.tests'; +import { withDefaultOptionsTests } from '../primitive-object-tests'; + +export const withTfmSessionUserSchemaTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedParameter, +}: WithSchemaTestParams) => { + describe('with TFM_SESSION_USER_SCHEMA tests', () => { + withDefaultOptionsTests({ + schema, + options, + getTestObjectWithUpdatedParameter, + }); + + withSchemaValidationTests({ + schema, + aValidPayload, + testCases: [ + { + parameterPath: '_id', + type: 'OBJECT_ID_STRING_SCHEMA', + options: { + overrideGetTestObjectWithUpdatedField: (newValue) => getTestObjectWithUpdatedParameter({ ...aValidPayload(), _id: newValue }), + }, + }, + { + parameterPath: 'username', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue) => getTestObjectWithUpdatedParameter({ ...aValidPayload(), username: newValue }), + }, + }, + { + parameterPath: 'email', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue) => getTestObjectWithUpdatedParameter({ ...aValidPayload(), email: newValue }), + }, + }, + { + parameterPath: 'timezone', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue) => getTestObjectWithUpdatedParameter({ ...aValidPayload(), timezone: newValue }), + }, + }, + { + parameterPath: 'firstName', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue) => getTestObjectWithUpdatedParameter({ ...aValidPayload(), firstName: newValue }), + }, + }, + { + parameterPath: 'lastName', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue) => getTestObjectWithUpdatedParameter({ ...aValidPayload(), lastName: newValue }), + }, + }, + { + parameterPath: 'status', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue) => getTestObjectWithUpdatedParameter({ ...aValidPayload(), status: newValue }), + }, + }, + { + parameterPath: 'lastLogin', + type: 'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA', + options: { + isOptional: true, + overrideGetTestObjectWithUpdatedField: (newValue) => getTestObjectWithUpdatedParameter({ ...aValidPayload(), lastLogin: newValue }), + }, + }, + { + parameterPath: 'teams', + type: 'Array', + options: { + arrayTypeTestCase: { + type: 'TfmTeamSchema', + }, + overrideGetTestObjectWithUpdatedField: (newValue) => getTestObjectWithUpdatedParameter({ ...aValidPayload(), teams: newValue }), + }, + }, + ], + }); + + function aValidPayload() { + return aValidTfmSessionUser(); + } + }); +}; + +export function aValidTfmSessionUser() { + return { + _id: new ObjectId().toString(), + username: 'test-user', + email: 'test-user@test.com', + teams: [TEAM_IDS.PIM], + timezone: 'Europe/London', + firstName: 'FirstName', + lastName: 'LastName', + status: 'active', + lastLogin: 1234567890123, + }; +} diff --git a/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts b/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts index 9ad88fcb2f..e68ed46c28 100644 --- a/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts +++ b/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts @@ -19,7 +19,8 @@ export type TestCaseTypes = | 'AUDIT_DATABASE_RECORD_SCHEMA' | 'ENTRA_ID_USER_SCHEMA' | 'CURRENCY_SCHEMA' - | 'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA'; + | 'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA' + | 'TFM_SESSION_USER_SCHEMA'; /** * The test case to be tested, including the type and any options that are required @@ -46,4 +47,5 @@ export type TestCase = | TestCaseWithType<'AUDIT_DATABASE_RECORD_SCHEMA'> | TestCaseWithType<'ENTRA_ID_USER_SCHEMA'> | TestCaseWithType<'CURRENCY_SCHEMA'> - | TestCaseWithType<'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA'>; + | TestCaseWithType<'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA'> + | TestCaseWithType<'TFM_SESSION_USER_SCHEMA'>; diff --git a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts index 8f30e0f59b..9d9307a5f3 100644 --- a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts +++ b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts @@ -9,7 +9,7 @@ import { withIsoDateTimeStampSchemaTests, } from './custom-objects-tests'; import { withStringTests, withNumberTests, withBooleanTests, withArrayTests } from './primitive-object-tests'; -import { withTfmTeamSchemaTests, withAuditDatabaseRecordSchemaTests, withEntraIdUserSchemaTests } from './schema-tests'; +import { withTfmTeamSchemaTests, withAuditDatabaseRecordSchemaTests, withEntraIdUserSchemaTests, withTfmSessionUserSchemaTests } from './schema-tests'; import { TestCase } from './with-test-for-test-case.type'; import { withCurrencySchemaTests } from './custom-objects-tests/with-currency-schema.tests'; import { withIsoDateTimeStampToDateSchemaTests } from './transformation-tests'; @@ -159,6 +159,14 @@ export const withTestsForTestcase = ({ }); break; + case 'TFM_SESSION_USER_SCHEMA': + withTfmSessionUserSchemaTests({ + schema, + options, + getTestObjectWithUpdatedParameter, + }); + break; + default: throw Error(`There are no existing test cases for the type ${type}`); } diff --git a/libs/common/src/types/tfm/handle-sso-redirect-form.ts b/libs/common/src/types/tfm/handle-sso-redirect-form.ts index 989dc4f76d..43b5819b98 100644 --- a/libs/common/src/types/tfm/handle-sso-redirect-form.ts +++ b/libs/common/src/types/tfm/handle-sso-redirect-form.ts @@ -1,10 +1,11 @@ import { AuthorizationUrlRequest } from '@azure/msal-node'; import { Response } from 'express'; +import z from 'zod'; import { EntraIdAuthCodeRedirectResponseBody } from './entra-id'; -import { TfmSessionUser } from './tfm-session-user'; import { AuditDetails } from '../audit-details'; import { CustomExpressRequest } from '../express-custom-request'; import { ApiErrorResponseBody } from '../api-error-response-body'; +import { HANDLE_SSO_REDIRECT_FORM_RESPONSE_SCHEMA } from '../../schemas/tfm/handle-sso-redirect-form-response.schema'; export type HandleSsoRedirectFormRequest = { authCodeResponse: EntraIdAuthCodeRedirectResponseBody; @@ -12,12 +13,7 @@ export type HandleSsoRedirectFormRequest = { auditDetails: AuditDetails<'system'>; }; -export type HandleSsoRedirectFormResponse = { - user: TfmSessionUser; - token: string; - expires: string; - successRedirect?: string; -}; +export type HandleSsoRedirectFormResponse = z.infer; export type HandleSsoRedirectFormUiRequest = CustomExpressRequest<{ reqBody: EntraIdAuthCodeRedirectResponseBody }>; diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index 0b86d0b79f..e4f1079e2c 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -1,3 +1,4 @@ +const { HANDLE_SSO_REDIRECT_FORM_RESPONSE_SCHEMA } = require('@ukef/dtfs2-common/schemas'); const axios = require('axios'); const { HttpStatusCode } = require('axios'); const { HEADERS } = require('@ukef/dtfs2-common'); @@ -425,8 +426,8 @@ const login = async (username, password) => { /** * Handles the SSO redirect form request by sending a POST request to the TFM API. * - * @param {object} handleSsoRedirectFormRequest - The request payload. Shape from import('@ukef/dtfs2-common').HandleSsoRedirectFormRequest - * @returns {Promise} A promise resolving to the response object. Shape from import('@ukef/dtfs2-common').HandleSsoRedirectFormResponse + * @param {('@ukef/dtfs2-common').HandleSsoRedirectFormRequest} handleSsoRedirectFormRequest - The request payload. + * @returns {Promise} A promise resolving to the response object. */ const handleSsoRedirectForm = async (handleSsoRedirectFormRequest) => { try { @@ -444,7 +445,7 @@ const handleSsoRedirectForm = async (handleSsoRedirectFormRequest) => { return { status: HttpStatusCode.BadGateway, data: 'Invalid response received' }; } - return response.data; + return HANDLE_SSO_REDIRECT_FORM_RESPONSE_SCHEMA.parse(response.data); } catch (error) { console.error('An exception has occurred while handling TFM SSO %o', error?.response?.data); return { status: error?.response?.status || HttpStatusCode.InternalServerError, data: 'Failed to login' }; From edbe3489f753c6bcbdb78b0733c1c76e20db0b77 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 15 Jan 2025 16:49:19 +0000 Subject: [PATCH 085/133] Revert "feat(DTFS2-6892): refactored code" This reverts commit 46c85f5f635096dd1a6781f9078c5ee2a6755108. --- .../src/test-helpers/mock-builders/index.ts | 2 +- ...uilder.ts => mock-builder.mock.builder.ts} | 0 trade-finance-manager-ui/server/api.js | 5 ---- .../login-non-sso/index.post-login.test.ts | 4 +-- .../controllers/login/login-non-sso/index.ts | 2 +- .../login.controller.get-login.test.ts | 4 +-- .../login/login-sso/login.controller.ts | 27 ++++++++----------- .../middleware/validateUserTeam/index.test.ts | 4 +-- .../middleware/validateUserTeam/index.ts | 4 +-- .../server/routes/auth/configs/auth-sso.ts | 2 +- 10 files changed, 22 insertions(+), 32 deletions(-) rename libs/common/src/test-helpers/mock-builders/{mock-builder.ts => mock-builder.mock.builder.ts} (100%) diff --git a/libs/common/src/test-helpers/mock-builders/index.ts b/libs/common/src/test-helpers/mock-builders/index.ts index babe587833..3bf4521b97 100644 --- a/libs/common/src/test-helpers/mock-builders/index.ts +++ b/libs/common/src/test-helpers/mock-builders/index.ts @@ -1 +1 @@ -export * from './mock-builder'; +export * from './mock-builder.mock.builder'; diff --git a/libs/common/src/test-helpers/mock-builders/mock-builder.ts b/libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts similarity index 100% rename from libs/common/src/test-helpers/mock-builders/mock-builder.ts rename to libs/common/src/test-helpers/mock-builders/mock-builder.mock.builder.ts diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index e4f1079e2c..5951c2dd67 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -440,11 +440,6 @@ const handleSsoRedirectForm = async (handleSsoRedirectFormRequest) => { data: handleSsoRedirectFormRequest, }); - if (!response) { - console.error('Invalid response received %o', response); - return { status: HttpStatusCode.BadGateway, data: 'Invalid response received' }; - } - return HANDLE_SSO_REDIRECT_FORM_RESPONSE_SCHEMA.parse(response.data); } catch (error) { console.error('An exception has occurred while handling TFM SSO %o', error?.response?.data); diff --git a/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.post-login.test.ts b/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.post-login.test.ts index 7b194b0e63..83e3b7a4c6 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.post-login.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.post-login.test.ts @@ -55,7 +55,7 @@ describe('controllers - login (sso)', () => { }); }); - it('should redirect to /deals if login successful', async () => { + it('should redirect to /home if login successful', async () => { // Arrange const { req, res } = httpMocks.createMocks({ session: {}, @@ -66,7 +66,7 @@ describe('controllers - login (sso)', () => { await postLogin(req, res); // Assert - expect(res._getRedirectUrl()).toEqual('/deals'); + expect(res._getRedirectUrl()).toEqual('/home'); }); }); }); diff --git a/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.ts b/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.ts index 786ab1eaa4..64a5668bb1 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-non-sso/index.ts @@ -48,7 +48,7 @@ export const postLogin = async (req: CustomExpressRequest<{ reqBody: { email?: s }); } - return res.redirect('/deals'); + return res.redirect('/home'); }; export const logout = (req: Request, res: Response) => { diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts index 25317ac173..4a4663ed91 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts @@ -39,7 +39,7 @@ describe('controllers - login (sso)', () => { session: requestSession, }); - it('redirects to /deals', async () => { + it('redirects to /home', async () => { // Arrange const { req, res } = getHttpMocks(); @@ -47,7 +47,7 @@ describe('controllers - login (sso)', () => { await loginController.getLogin(req, res, next); // Assert - expect(res._getRedirectUrl()).toEqual('/deals'); + expect(res._getRedirectUrl()).toEqual('/home'); }); }); diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts index 819d029f44..52eec83192 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts @@ -24,18 +24,16 @@ export class LoginController { * If the user is not logged in, it retrieves the authentication code URL and * creates a partially logged-in session before redirecting the user to the authentication URL. * - * @param {Request} req - The HTTP request object. - * @param {Response} res - The HTTP response object. - * @returns {Promise} - A promise that resolves when the login process is complete. - * - * @throws Will render a problem with service page if an error occurs during the login process. + * @param req - The HTTP request object. + * @param res - The HTTP response object. + * @returns - A promise that resolves when the login process is complete. */ public async getLogin(req: Request, res: Response) { try { - // TODO DTFS2-7734: This validation is legacy code, and can be improved - if (req?.session?.user) { + // TODO: This validation is legacy code, and can be improved + if (req.session.user) { // User is already logged in. - return res.redirect('/deals'); + return res.redirect('/home'); } const { authCodeUrl, authCodeUrlRequest } = await this.loginService.getAuthCodeUrl({ successRedirect: '/' }); @@ -44,7 +42,7 @@ export class LoginController { return res.redirect(authCodeUrl); } catch (error) { - console.error('Unable to log in user %o', error); + console.error('Unable to log in user: %o', error); return res.render('_partials/problem-with-service.njk'); } } @@ -57,10 +55,9 @@ export class LoginController { * On successful login, it redirects the user to the specified URL or the home page. * In case of an error, it logs the error and renders a problem with service page. * - * @param {HandleSsoRedirectFormUiRequest} req - The request object containing the form data and session. - * @param {Response} res - The response object used to redirect or render a page. - * @returns {Promise} - A promise that resolves when the operation is complete. - * @throws {InvalidPayloadError} - If the payload from the SSO redirect is invalid. + * @param req - The request object containing the form data and session. + * @param res - The response object used to redirect or render a page. + * @returns - A promise that resolves when the operation is complete. */ async handleSsoRedirectForm(req: HandleSsoRedirectFormUiRequest, res: Response) { try { @@ -78,11 +75,9 @@ export class LoginController { auditDetails, }); - const url = successRedirect ?? '/'; - this.userSessionService.createLoggedInSession({ session, user, userToken: token }); - return res.redirect(url); + return res.redirect(successRedirect ?? '/'); } catch (error) { console.error('Unable to redirect the user after login %o', error); return res.render('_partials/problem-with-service.njk'); diff --git a/trade-finance-manager-ui/server/middleware/validateUserTeam/index.test.ts b/trade-finance-manager-ui/server/middleware/validateUserTeam/index.test.ts index d84924fcb5..05d6510a86 100644 --- a/trade-finance-manager-ui/server/middleware/validateUserTeam/index.test.ts +++ b/trade-finance-manager-ui/server/middleware/validateUserTeam/index.test.ts @@ -20,7 +20,7 @@ describe('validateUserTeam', () => { expect(() => validateUserTeam([])(req, res, next)).toThrow(Error('Expected session.user to be defined')); }); - it('should redirect to the default redirect url (/deals) if the user is not in the correct team', () => { + it('should redirect to the default redirect url (/home) if the user is not in the correct team', () => { // Arrange const requiredTeamIds = [TEAM_IDS.PDC_RECONCILE]; const { req, res } = getHttpMocks({ user: { teams: [] } }); @@ -31,7 +31,7 @@ describe('validateUserTeam', () => { // Assert expect(next).not.toHaveBeenCalled(); - expect(res._getRedirectUrl()).toEqual('/deals'); + expect(res._getRedirectUrl()).toEqual('/home'); }); it('should redirect to the specified redirect url if the user is not in the correct team', () => { diff --git a/trade-finance-manager-ui/server/middleware/validateUserTeam/index.ts b/trade-finance-manager-ui/server/middleware/validateUserTeam/index.ts index 51f0f2c1e6..f42bf36d6c 100644 --- a/trade-finance-manager-ui/server/middleware/validateUserTeam/index.ts +++ b/trade-finance-manager-ui/server/middleware/validateUserTeam/index.ts @@ -7,11 +7,11 @@ import { userIsInTeam } from '../../helpers/user'; * Middleware to check if the user is in at least * one of the teams specified in the requiredTeamIds * array. If they are not, they are redirected to - * the redirectUrl, which defaults to '/deals' if + * the redirectUrl, which defaults to '/home' if * it is not explicitly provided. */ export const validateUserTeam = - (requiredTeamIds: TeamId[], redirectUrl: string = '/deals'): RequestHandler => + (requiredTeamIds: TeamId[], redirectUrl: string = '/home'): RequestHandler => (req, res, next) => { const { user } = asUserSession(req.session); if (userIsInTeam(user, requiredTeamIds)) { diff --git a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts index ece02b833a..e928d6291c 100644 --- a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts +++ b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts @@ -8,7 +8,7 @@ import { UserSessionService } from '../../../services/user-session.service'; * Creates and configures the authentication SSO router. * This router handles the Single Sign-On (SSO) redirect form submission. * - * @returns {Router} The configured authentication SSO router. + * @returns The configured authentication SSO router. */ export const getAuthSsoRouter: GetRouter = () => { const loginService = new LoginService(); From 3e806888d4240560d7c65861ff944c72dd5e0005 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 22 Jan 2025 14:52:43 +0000 Subject: [PATCH 086/133] feat(dtfs2-6892): clarify how zod union works --- libs/common/src/schemas/object-id.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/common/src/schemas/object-id.ts b/libs/common/src/schemas/object-id.ts index 5e9d5b5e92..3185ccdc59 100644 --- a/libs/common/src/schemas/object-id.ts +++ b/libs/common/src/schemas/object-id.ts @@ -21,6 +21,8 @@ export const OBJECT_ID_STRING = z.union([z.string().refine((id) => ObjectId.isVa /** * A zod schema that represents a valid ObjectId as an ObjectId object or a string - * This schema does not do any transformation + * This schema does not do any transformation, only validates. + * This is because we check to see if the value is an ObjectId prior to applying the OBJECT_ID_STRING + * schema, and zod union returns the first valid schema */ -export const OBJECT_ID_OR_OBJECT_ID_STRING = z.union([z.instanceof(ObjectId), OBJECT_ID_STRING, z.string().refine((id) => ObjectId.isValid(id))]); +export const OBJECT_ID_OR_OBJECT_ID_STRING = z.union([z.instanceof(ObjectId), OBJECT_ID_STRING]); From 6c08a10e0ca084c2deb4dc6e4bbf2c74d38761d2 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 22 Jan 2025 17:19:17 +0000 Subject: [PATCH 087/133] feat(dtfs2-6892): update to review comments --- .../src/v1/controllers/sso.controller.ts | 8 ++++---- trade-finance-manager-api/src/v1/sso/routes.ts | 6 ++++-- trade-finance-manager-ui/server/api.js | 2 +- .../login/login-sso/login.controller.get-login.test.ts | 4 +--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts index c84619febf..f468f04152 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts @@ -1,4 +1,3 @@ -import { NextFunction } from 'express'; import { GetAuthCodeUrlApiRequest, GetAuthCodeUrlApiResponse } from '@ukef/dtfs2-common'; import { EntraIdService } from '../services/entra-id.service'; @@ -9,13 +8,14 @@ export class SsoController { this.entraIdService = entraIdService; } - async getAuthCodeUrl(req: GetAuthCodeUrlApiRequest, res: GetAuthCodeUrlApiResponse, next: NextFunction) { + async getAuthCodeUrl(req: GetAuthCodeUrlApiRequest, res: GetAuthCodeUrlApiResponse) { try { const { successRedirect } = req.params; const getAuthCodeUrlResponse = await this.entraIdService.getAuthCodeUrl({ successRedirect }); - return res.json(getAuthCodeUrlResponse); + res.json(getAuthCodeUrlResponse); } catch (error) { - return next(error); + console.error('An error occurred while getting the auth code URL:', error); + throw error; } } } diff --git a/trade-finance-manager-api/src/v1/sso/routes.ts b/trade-finance-manager-api/src/v1/sso/routes.ts index fc8d32bf20..ac5aaaab8b 100644 --- a/trade-finance-manager-api/src/v1/sso/routes.ts +++ b/trade-finance-manager-api/src/v1/sso/routes.ts @@ -1,4 +1,5 @@ import express from 'express'; +import { GetAuthCodeUrlApiRequest, GetAuthCodeUrlApiResponse } from '@ukef/dtfs2-common'; import { SsoController } from '../controllers/sso.controller'; import { EntraIdService } from '../services/entra-id.service'; import { EntraIdApi } from '../third-party-apis/entra-id.api'; @@ -11,5 +12,6 @@ const entraIdApi = new EntraIdApi({ entraIdConfig }); const entraIdService = new EntraIdService({ entraIdConfig, entraIdApi }); const ssoController = new SsoController({ entraIdService }); -// eslint-disable-next-line @typescript-eslint/no-misused-promises -ssoOpenRouter.route('/auth-code-url').get(ssoController.getAuthCodeUrl.bind(ssoController)); +ssoOpenRouter.route('/auth-code-url').get((req: GetAuthCodeUrlApiRequest, res: GetAuthCodeUrlApiResponse, next) => { + ssoController.getAuthCodeUrl(req, res).catch(next); +}); diff --git a/trade-finance-manager-ui/server/api.js b/trade-finance-manager-ui/server/api.js index ab40e6ce9f..93932f3eb4 100644 --- a/trade-finance-manager-ui/server/api.js +++ b/trade-finance-manager-ui/server/api.js @@ -402,7 +402,7 @@ const createActivity = async (dealId, activityUpdate, token) => { } }; -// TODO DTFS2-6892 - remove this function +// TODO DTFS2-7772 - remove this function const login = async (username, password) => { try { const response = await axios({ diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts index 9e7183f738..15b515d20a 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts @@ -53,7 +53,7 @@ describe('controllers - login (sso)', () => { mockSuccessfulGetAuthCodeUrl(); }); - it('should redirect to login URL', async () => { + it('should redirect to auth code URL', async () => { // Arrange const { req, res } = httpMocks.createMocks({ session: {} }); @@ -70,8 +70,6 @@ describe('controllers - login (sso)', () => { session: { loginData: { authCodeUrlRequest: 'an old auth code url request', aField: 'another field' } }, }); - req.session.loginData = { authCodeUrlRequest: 'old-auth-code-url-request' }; - // Act await loginController.getLogin(req, res, next); From 4829eccc50eafc67644bc24bbe63295e5406385a Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 23 Jan 2025 13:40:22 +0000 Subject: [PATCH 088/133] feat(dtfs2-6892): add sso controller test --- azure-functions/acbs-function/.eslintrc.js | 14 +++- dtfs-central-api/.eslintrc.js | 14 +++- e2e-tests/.eslintrc.js | 14 +++- external-api/.eslintrc.js | 14 +++- gef-ui/.eslintrc.js | 14 +++- libs/common/.eslintrc.js | 14 +++- .../mock-data/get-auth-code-url-params.ts | 5 ++ .../mock-data/get-auth-code-url-response.ts | 9 +++ .../src/test-helpers/mock-data/index.ts | 2 + portal-api/.eslintrc.js | 14 +++- portal/.eslintrc.js | 14 +++- trade-finance-manager-api/.eslintrc.js | 14 +++- ...er.ts => entra-id.service.mock.builder.ts} | 8 +- .../src/v1/__mocks__/builders/index.ts | 2 +- .../sso.controller.get-auth-code-url.test.ts | 80 +++++++++++++++++++ .../src/v1/controllers/sso.controller.ts | 3 +- trade-finance-manager-ui/.eslintrc.js | 14 +++- utils/.eslintrc.js | 14 +++- 18 files changed, 243 insertions(+), 20 deletions(-) create mode 100644 libs/common/src/test-helpers/mock-data/get-auth-code-url-params.ts create mode 100644 libs/common/src/test-helpers/mock-data/get-auth-code-url-response.ts rename trade-finance-manager-api/src/v1/__mocks__/builders/{extra-id.service.mock.builder.ts => entra-id.service.mock.builder.ts} (51%) create mode 100644 trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts diff --git a/azure-functions/acbs-function/.eslintrc.js b/azure-functions/acbs-function/.eslintrc.js index 2abbe77fa7..9d8f896f70 100644 --- a/azure-functions/acbs-function/.eslintrc.js +++ b/azure-functions/acbs-function/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getJSONData', + '_getHeaders', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/dtfs-central-api/.eslintrc.js b/dtfs-central-api/.eslintrc.js index 2abbe77fa7..9d8f896f70 100644 --- a/dtfs-central-api/.eslintrc.js +++ b/dtfs-central-api/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getJSONData', + '_getHeaders', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/e2e-tests/.eslintrc.js b/e2e-tests/.eslintrc.js index 2a82e264c1..0cee878ede 100644 --- a/e2e-tests/.eslintrc.js +++ b/e2e-tests/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getJSONData', + '_getHeaders', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/external-api/.eslintrc.js b/external-api/.eslintrc.js index 2abbe77fa7..9d8f896f70 100644 --- a/external-api/.eslintrc.js +++ b/external-api/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getJSONData', + '_getHeaders', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/gef-ui/.eslintrc.js b/gef-ui/.eslintrc.js index 2abbe77fa7..9d8f896f70 100644 --- a/gef-ui/.eslintrc.js +++ b/gef-ui/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getJSONData', + '_getHeaders', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/libs/common/.eslintrc.js b/libs/common/.eslintrc.js index 980bf2a2b2..d00d3b4565 100644 --- a/libs/common/.eslintrc.js +++ b/libs/common/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getJSONData', + '_getHeaders', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/libs/common/src/test-helpers/mock-data/get-auth-code-url-params.ts b/libs/common/src/test-helpers/mock-data/get-auth-code-url-params.ts new file mode 100644 index 0000000000..c070f0176b --- /dev/null +++ b/libs/common/src/test-helpers/mock-data/get-auth-code-url-params.ts @@ -0,0 +1,5 @@ +import { GetAuthCodeUrlParams } from '../../types'; + +export const aGetAuthCodeUrlParams = (): GetAuthCodeUrlParams => ({ + successRedirect: 'an-example-redirect-url', +}); diff --git a/libs/common/src/test-helpers/mock-data/get-auth-code-url-response.ts b/libs/common/src/test-helpers/mock-data/get-auth-code-url-response.ts new file mode 100644 index 0000000000..4fb8d993dd --- /dev/null +++ b/libs/common/src/test-helpers/mock-data/get-auth-code-url-response.ts @@ -0,0 +1,9 @@ +import { GetAuthCodeUrlResponse } from '../../types'; + +export const aGetAuthCodeUrlResponse = (): GetAuthCodeUrlResponse => ({ + authCodeUrl: 'https://auth-code-url', + authCodeUrlRequest: { + scopes: ['user.read'], + redirectUri: 'https://redirect-uri', + }, +}); diff --git a/libs/common/src/test-helpers/mock-data/index.ts b/libs/common/src/test-helpers/mock-data/index.ts index a4bcba4691..dfcb4157a1 100644 --- a/libs/common/src/test-helpers/mock-data/index.ts +++ b/libs/common/src/test-helpers/mock-data/index.ts @@ -13,3 +13,5 @@ export * from './create-tfm-user-request'; export * from './upsert-tfm-user-request'; export * from './record-correction-mock'; export * from './fee-record-correction-request-transient-form-data.entity.mock-builder'; +export * from './get-auth-code-url-response'; +export * from './get-auth-code-url-params'; diff --git a/portal-api/.eslintrc.js b/portal-api/.eslintrc.js index 2abbe77fa7..9d8f896f70 100644 --- a/portal-api/.eslintrc.js +++ b/portal-api/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getJSONData', + '_getHeaders', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/portal/.eslintrc.js b/portal/.eslintrc.js index 2abbe77fa7..9d8f896f70 100644 --- a/portal/.eslintrc.js +++ b/portal/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getJSONData', + '_getHeaders', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/trade-finance-manager-api/.eslintrc.js b/trade-finance-manager-api/.eslintrc.js index b8fef1fdb4..0037a16cae 100644 --- a/trade-finance-manager-api/.eslintrc.js +++ b/trade-finance-manager-api/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getJSONData', + '_getHeaders', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.service.mock.builder.ts similarity index 51% rename from trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts rename to trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.service.mock.builder.ts index b639222fe6..b08749fece 100644 --- a/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.service.mock.builder.ts @@ -1,5 +1,4 @@ -import { AuthorizationCodeRequest } from '@azure/msal-node'; -import { BaseMockBuilder } from '@ukef/dtfs2-common'; +import { aGetAuthCodeUrlResponse, BaseMockBuilder } from '@ukef/dtfs2-common'; import { EntraIdService } from '../../services/entra-id.service'; export class EntraIdServiceMockBuilder extends BaseMockBuilder { @@ -7,10 +6,7 @@ export class EntraIdServiceMockBuilder extends BaseMockBuilder { super({ defaultInstance: { getAuthCodeUrl: jest.fn(async () => { - return Promise.resolve({ - authCodeUrl: 'a-auth-code-url', - authCodeUrlRequest: {} as AuthorizationCodeRequest, - }); + return Promise.resolve(aGetAuthCodeUrlResponse()); }), }, }); diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/index.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/index.ts index 55cb5217ef..cdfb146bf5 100644 --- a/trade-finance-manager-api/src/v1/__mocks__/builders/index.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/index.ts @@ -1,3 +1,3 @@ export * from './entra-id.api.mock.builder'; export * from './entra-id.config.mock.builder'; -export * from './extra-id.service.mock.builder'; +export * from './entra-id.service.mock.builder'; diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts new file mode 100644 index 0000000000..1a94170454 --- /dev/null +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts @@ -0,0 +1,80 @@ +import { aGetAuthCodeUrlParams, aGetAuthCodeUrlResponse, GetAuthCodeUrlApiRequest, GetAuthCodeUrlApiResponse } from '@ukef/dtfs2-common'; +import { resetAllWhenMocks } from 'jest-when'; +import httpMocks from 'node-mocks-http'; +import { SsoController } from './sso.controller'; +import { EntraIdService } from '../services/entra-id.service'; +import { EntraIdServiceMockBuilder } from '../__mocks__/builders'; + +describe('SsoController', () => { + let ssoController: SsoController; + let entraIdService: EntraIdService; + + console.error = jest.fn(); + + const getAuthCodeUrlMock = jest.fn(); + + beforeEach(() => { + resetAllWhenMocks(); + jest.resetAllMocks(); + + entraIdService = new EntraIdServiceMockBuilder() + .with({ + getAuthCodeUrl: getAuthCodeUrlMock, + }) + .build(); + + ssoController = new SsoController({ entraIdService }); + }); + + it('should call getAuthCodeUrl with the correct params', async () => { + const getAuthCodeUrlParmas = aGetAuthCodeUrlParams(); + const { req, res } = getHttpMocks(getAuthCodeUrlParmas); + + await ssoController.getAuthCodeUrl(req, res); + + expect(getAuthCodeUrlMock).toHaveBeenCalledWith(getAuthCodeUrlParmas); + expect(getAuthCodeUrlMock).toHaveBeenCalledTimes(1); + }); + + it('should return auth code URL on success', async () => { + const { req, res } = getHttpMocks(aGetAuthCodeUrlParams()); + + const getAuthCodeUrlResponse = aGetAuthCodeUrlResponse(); + getAuthCodeUrlMock.mockResolvedValue(getAuthCodeUrlResponse); + + await ssoController.getAuthCodeUrl(req, res); + + expect(res._getJSONData()).toEqual(getAuthCodeUrlResponse); + }); + + it('should pass through thrown errors', async () => { + const getAuthCodeUrlParmas = aGetAuthCodeUrlParams(); + const { req, res } = getHttpMocks(getAuthCodeUrlParmas); + + const error = new Error('Test error'); + getAuthCodeUrlMock.mockRejectedValue(error); + + await expect(ssoController.getAuthCodeUrl(req, res)).rejects.toThrow(error); + }); + + it('should call console.error on error', async () => { + const getAuthCodeUrlParmas = aGetAuthCodeUrlParams(); + const { req, res } = getHttpMocks(getAuthCodeUrlParmas); + + const error = new Error('Test error'); + getAuthCodeUrlMock.mockRejectedValue(error); + + await ssoController.getAuthCodeUrl(req, res).catch(() => {}); + + expect(console.error).toHaveBeenCalledWith('An error occurred while getting the auth code URL:', error); + }); + + function getHttpMocks(params: GetAuthCodeUrlApiRequest['params']): { + req: httpMocks.MockRequest; + res: httpMocks.MockResponse; + } { + return httpMocks.createMocks({ + params, + }); + } +}); diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts index f468f04152..dedd5ec98f 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts @@ -10,8 +10,7 @@ export class SsoController { async getAuthCodeUrl(req: GetAuthCodeUrlApiRequest, res: GetAuthCodeUrlApiResponse) { try { - const { successRedirect } = req.params; - const getAuthCodeUrlResponse = await this.entraIdService.getAuthCodeUrl({ successRedirect }); + const getAuthCodeUrlResponse = await this.entraIdService.getAuthCodeUrl(req.params); res.json(getAuthCodeUrlResponse); } catch (error) { console.error('An error occurred while getting the auth code URL:', error); diff --git a/trade-finance-manager-ui/.eslintrc.js b/trade-finance-manager-ui/.eslintrc.js index 9b502a3a0a..8e487a5d10 100644 --- a/trade-finance-manager-ui/.eslintrc.js +++ b/trade-finance-manager-ui/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getJSONData', + '_getHeaders', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', diff --git a/utils/.eslintrc.js b/utils/.eslintrc.js index 2abbe77fa7..9d8f896f70 100644 --- a/utils/.eslintrc.js +++ b/utils/.eslintrc.js @@ -9,7 +9,19 @@ const baseRules = { 'no-underscore-dangle': [ 'error', { - allow: ['_id', '_csrf', '_getBuffer', '_getData', '_getHeaders', '_getStatusCode', '_getRedirectUrl', '_getRenderData', '_getRenderView', '_isEndCalled'], + allow: [ + '_id', + '_csrf', + '_getBuffer', + '_getData', + '_getJSONData', + '_getHeaders', + '_getStatusCode', + '_getRedirectUrl', + '_getRenderData', + '_getRenderView', + '_isEndCalled', + ], }, ], 'import/extensions': 'off', From 3f94a379326a5f681a0cfe8465a90242adef6802 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 23 Jan 2025 13:40:48 +0000 Subject: [PATCH 089/133] feat(dtfs2-6892): update controller reroute, add tests --- .../login.controller.get-login.test.ts | 49 ++++++++++++++----- .../login/login-sso/login.controller.ts | 4 +- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts index 15b515d20a..050ff49d3d 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts @@ -1,5 +1,6 @@ import httpMocks from 'node-mocks-http'; import { resetAllWhenMocks, when } from 'jest-when'; +import { aGetAuthCodeUrlResponse } from '@ukef/dtfs2-common'; import { aTfmSessionUser } from '../../../../test-helpers'; import { LoginController } from './login.controller'; import { LoginService } from '../../../services/login.service'; @@ -7,8 +8,8 @@ import { LoginServiceMockBuilder } from '../../../../test-helpers/mocks'; describe('controllers - login (sso)', () => { describe('getLogin', () => { - const mockAuthCodeUrl = `mock-auth-code-url`; - const mockAuthCodeUrlRequest = `mock-auth-code-url-request`; + const validGetAuthCodeUrlResponse = aGetAuthCodeUrlResponse(); + const aRedirectUrl = '/a-redirect-url'; let loginController: LoginController; let loginService: LoginService; @@ -48,6 +49,32 @@ describe('controllers - login (sso)', () => { }); describe('when there is no user session', () => { + describe('when an originalUrl exists on the request', () => { + it('should call getAuthCodeUrl with the originalUrl if present', async () => { + // Arrange + const { req, res } = httpMocks.createMocks({ session: {}, originalUrl: aRedirectUrl }); + + // Act + await loginController.getLogin(req, res, next); + + // Assert + expect(getAuthCodeUrlMock).toHaveBeenCalledWith({ successRedirect: aRedirectUrl }); + }); + }); + + describe('when an originalUrl does not exist on the request', () => { + it('should call getAuthCodeUrl with "/" as the successRedirect', async () => { + // Arrange + const { req, res } = httpMocks.createMocks({ session: {} }); + + // Act + await loginController.getLogin(req, res, next); + + // Assert + expect(getAuthCodeUrlMock).toHaveBeenCalledWith({ successRedirect: '/' }); + }); + }); + describe('when the getAuthCodeUrl api call is successful', () => { beforeEach(() => { mockSuccessfulGetAuthCodeUrl(); @@ -55,37 +82,39 @@ describe('controllers - login (sso)', () => { it('should redirect to auth code URL', async () => { // Arrange - const { req, res } = httpMocks.createMocks({ session: {} }); + const { req, res } = httpMocks.createMocks({ session: {}, originalUrl: aRedirectUrl }); // Act await loginController.getLogin(req, res, next); // Assert - expect(res._getRedirectUrl()).toEqual(mockAuthCodeUrl); + expect(res._getRedirectUrl()).toEqual(validGetAuthCodeUrlResponse.authCodeUrl); }); it('should override session login data if present', async () => { // Arrange const { req, res } = httpMocks.createMocks({ session: { loginData: { authCodeUrlRequest: 'an old auth code url request', aField: 'another field' } }, + originalUrl: aRedirectUrl, }); // Act await loginController.getLogin(req, res, next); // Assert - expect(req.session.loginData).toEqual({ authCodeUrlRequest: mockAuthCodeUrlRequest }); + expect(req.session.loginData).toEqual({ authCodeUrlRequest: validGetAuthCodeUrlResponse.authCodeUrlRequest }); }); }); - describe('when the getAuthCodeUrl api call is unsuccessful', () => { + describe('when the api call is unsuccessful', () => { beforeEach(() => { mockFailedGetAuthCodeUrl(); }); it('should call next with error', async () => { // Arrange - const { req, res } = httpMocks.createMocks({ session: {} }); + const { req, res } = httpMocks.createMocks({ session: {}, originalUrl: aRedirectUrl }); + const error = new Error('getAuthCodeUrl error'); getAuthCodeUrlMock.mockRejectedValueOnce(error); @@ -99,13 +128,11 @@ describe('controllers - login (sso)', () => { }); function mockSuccessfulGetAuthCodeUrl() { - when(getAuthCodeUrlMock) - .calledWith({ successRedirect: '/' }) - .mockResolvedValueOnce({ authCodeUrl: mockAuthCodeUrl, authCodeUrlRequest: mockAuthCodeUrlRequest }); + when(getAuthCodeUrlMock).calledWith({ successRedirect: aRedirectUrl }).mockResolvedValueOnce(validGetAuthCodeUrlResponse); } function mockFailedGetAuthCodeUrl() { - when(getAuthCodeUrlMock).calledWith({ successRedirect: '/' }).mockRejectedValueOnce(new Error('getAuthCodeUrl error')); + when(getAuthCodeUrlMock).calledWith({ successRedirect: aRedirectUrl }).mockRejectedValueOnce(new Error('getAuthCodeUrl error')); } }); }); diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts index 6a7c0c5897..2fc72ac444 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts @@ -15,10 +15,10 @@ export class LoginController { return res.redirect('/home'); } - const { authCodeUrl, authCodeUrlRequest } = await this.loginService.getAuthCodeUrl({ successRedirect: '/' }); - + const { authCodeUrl, authCodeUrlRequest } = await this.loginService.getAuthCodeUrl({ successRedirect: req.originalUrl ? req.originalUrl : '/' }); // As this is the user logging in, there should be no existing login data in the session. // if there is, it should be cleared and set to the authCodeUrlRequest. + req.session.loginData = { authCodeUrlRequest }; return res.redirect(authCodeUrl); From f2e2f5e1c79a57f86febd63fe235ca3bf24b9289 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 23 Jan 2025 13:47:14 +0000 Subject: [PATCH 090/133] feat(dtfs2-6892): uninstall msal node from tfm ui --- package-lock.json | 1 - trade-finance-manager-ui/package.json | 1 - .../test-helpers/mocks/login.service.mock.builder.ts | 8 ++------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 839d2fe9a7..e2e5e03cb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28294,7 +28294,6 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@azure/msal-node": "^2.16.2", "@babel/polyfill": "^7.12.1", "@ministryofjustice/frontend": "3.0.2", "@ukef/dtfs2-common": "1.0.0", diff --git a/trade-finance-manager-ui/package.json b/trade-finance-manager-ui/package.json index 48de2ed742..75af49ef48 100644 --- a/trade-finance-manager-ui/package.json +++ b/trade-finance-manager-ui/package.json @@ -34,7 +34,6 @@ "unit-test-ff": "jest --coverage --verbose --config=unit.ff.jest.config.js --passWithNoTests" }, "dependencies": { - "@azure/msal-node": "^2.16.2", "@babel/polyfill": "^7.12.1", "@ministryofjustice/frontend": "3.0.2", "@ukef/dtfs2-common": "1.0.0", diff --git a/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts b/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts index 5932b5e0bb..8722999686 100644 --- a/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts +++ b/trade-finance-manager-ui/test-helpers/mocks/login.service.mock.builder.ts @@ -1,5 +1,4 @@ -import { AuthorizationCodeRequest } from '@azure/msal-node'; -import { BaseMockBuilder } from '@ukef/dtfs2-common'; +import { aGetAuthCodeUrlResponse, BaseMockBuilder } from '@ukef/dtfs2-common'; import { LoginService } from '../../server/services/login.service'; export class LoginServiceMockBuilder extends BaseMockBuilder { @@ -7,10 +6,7 @@ export class LoginServiceMockBuilder extends BaseMockBuilder { super({ defaultInstance: { getAuthCodeUrl: jest.fn(async () => { - return Promise.resolve({ - authCodeUrl: 'a-auth-code-url', - authCodeUrlRequest: {} as AuthorizationCodeRequest, - }); + return Promise.resolve(aGetAuthCodeUrlResponse()); }), }, }); From ec2e327158b8f7ec4bde0bd13a7fb71143f53621 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 23 Jan 2025 14:00:50 +0000 Subject: [PATCH 091/133] feat(dtfs2-6892): remove todo --- libs/common/src/schemas/tfm/create-tfm-user-request.schema.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/common/src/schemas/tfm/create-tfm-user-request.schema.ts b/libs/common/src/schemas/tfm/create-tfm-user-request.schema.ts index c5d12cc38a..37bbfb820e 100644 --- a/libs/common/src/schemas/tfm/create-tfm-user-request.schema.ts +++ b/libs/common/src/schemas/tfm/create-tfm-user-request.schema.ts @@ -1,6 +1,5 @@ import { TFM_USER_SCHEMA } from './tfm-user.schema'; -// TODO update docs, tests /** * Used during the SSO login process when a user is required to be created in TFM * It is used as a foundation to the upsert user request From 705d0b69936a946373d65e2fa8eb3f6e2f219c71 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 24 Jan 2025 10:38:48 +0000 Subject: [PATCH 092/133] feat(dtfs2-6892): update mock builder to use test helper --- .../__mocks__/builders/entra-id.service.mock.builder.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.service.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.service.mock.builder.ts index 6653957c73..0d38e515a3 100644 --- a/trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.service.mock.builder.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/entra-id.service.mock.builder.ts @@ -1,5 +1,6 @@ -import { aGetAuthCodeUrlResponse, anEntraIdUser, BaseMockBuilder } from '@ukef/dtfs2-common'; +import { aGetAuthCodeUrlResponse, BaseMockBuilder } from '@ukef/dtfs2-common'; import { EntraIdService } from '../../services/entra-id.service'; +import { aHandleRedirectResponse } from '../../../../test-helpers'; export class EntraIdServiceMockBuilder extends BaseMockBuilder { constructor() { @@ -9,10 +10,7 @@ export class EntraIdServiceMockBuilder extends BaseMockBuilder { return Promise.resolve(aGetAuthCodeUrlResponse()); }), handleRedirect: jest.fn(async () => { - return Promise.resolve({ - entraIdUser: anEntraIdUser(), - successRedirect: 'a-success-redirect', - }); + return Promise.resolve(aHandleRedirectResponse()); }), }, }); From ae9107e28e8c28ad09f32db1143f50010a466058 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 24 Jan 2025 17:17:22 +0000 Subject: [PATCH 093/133] feat(dtfs2-6892): review comments --- .../v1/__mocks__/builders/extra-id.service.mock.builder.ts | 0 .../src/v1/__mocks__/builders/user.service.mock.builder.ts | 6 +++--- trade-finance-manager-api/src/v1/services/user.service.ts | 4 ++-- .../server/controllers/login/login-sso/login.controller.ts | 5 +++-- 4 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/extra-id.service.mock.builder.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts index 4c084e83f6..bc0db4cb79 100644 --- a/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts @@ -4,7 +4,7 @@ import { aTfmUser } from '@ukef/dtfs2-common/mock-data-backend'; import { UpsertTfmUserFromEntraIdUserParams, UpsertTfmUserFromEntraIdUserResponse, - saveUserLoginInformationParams, + SaveUserLoginInformationParams, UserService, } from '../../services/user.service'; @@ -20,10 +20,10 @@ export class UserServiceMockBuilder extends BaseMockBuilder { transformEntraIdUserToUpsertTfmUserRequest(entraIdUser: EntraIdUser): UpsertTfmUserRequest { return userService.transformEntraIdUserToUpsertTfmUserRequest(entraIdUser); }, - upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }: UpsertTfmUserFromEntraIdUserParams): Promise { + upsertTfmUserFromEntraIdUser(upsertTfmUserFromEntraIdUserParams: UpsertTfmUserFromEntraIdUserParams): Promise { return Promise.resolve(aTfmUser()); }, - saveUserLoginInformation({ userId, sessionIdentifier, auditDetails }: saveUserLoginInformationParams): Promise { + saveUserLoginInformation(saveUserLoginInformationParams: SaveUserLoginInformationParams): Promise { return Promise.resolve(); }, }, diff --git a/trade-finance-manager-api/src/v1/services/user.service.ts b/trade-finance-manager-api/src/v1/services/user.service.ts index aa264ffe8c..1815ff8d23 100644 --- a/trade-finance-manager-api/src/v1/services/user.service.ts +++ b/trade-finance-manager-api/src/v1/services/user.service.ts @@ -8,7 +8,7 @@ export type UpsertTfmUserFromEntraIdUserParams = { auditDetails: AuditDetails; }; -export type saveUserLoginInformationParams = { +export type SaveUserLoginInformationParams = { userId: ObjectId; sessionIdentifier: string; auditDetails: AuditDetails; @@ -70,7 +70,7 @@ export class UserService { return upsertedUser; } - public async saveUserLoginInformation({ userId, sessionIdentifier, auditDetails }: saveUserLoginInformationParams) { + public async saveUserLoginInformation({ userId, sessionIdentifier, auditDetails }: SaveUserLoginInformationParams) { await UserRepo.updateUserById({ userId, userUpdate: { diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts index 802c286d58..38800ed17b 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.ts @@ -42,7 +42,7 @@ export class LoginController { return res.redirect(authCodeUrl); } catch (error) { - console.error('Unable to log in user: %o', error); + console.error('Unable to log in user %o', error); return res.render('_partials/problem-with-service.njk'); } } @@ -77,7 +77,8 @@ export class LoginController { this.userSessionService.createLoggedInSession({ session, user, userToken: token }); - return res.redirect(successRedirect ?? '/'); + const url = successRedirect ?? '/'; + return res.redirect(url); } catch (error) { console.error('Unable to redirect the user after login %o', error); return res.render('_partials/problem-with-service.njk'); From d4abcad4ff661965380e7ab3d25acfc6ec2d4dfa Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 28 Jan 2025 13:46:48 +0000 Subject: [PATCH 094/133] feat(dtfs2-6892): review comments --- ...ansform-entra-id-user-to-tfm-upsert-user-request.test.ts | 2 +- .../user.service.upsert-user-from-entra-id-user.test.ts | 6 +++--- .../server/api.get-uk-bank-holidays.test.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/trade-finance-manager-api/src/v1/services/user.service.transform-entra-id-user-to-tfm-upsert-user-request.test.ts b/trade-finance-manager-api/src/v1/services/user.service.transform-entra-id-user-to-tfm-upsert-user-request.test.ts index 20bb8cb058..13b310569d 100644 --- a/trade-finance-manager-api/src/v1/services/user.service.transform-entra-id-user-to-tfm-upsert-user-request.test.ts +++ b/trade-finance-manager-api/src/v1/services/user.service.transform-entra-id-user-to-tfm-upsert-user-request.test.ts @@ -14,7 +14,7 @@ describe('user service', () => { jest.useRealTimers(); }); - it('transforms an entra id user to a tfm upsert user request', () => { + it('should transform an entra id user to a tfm upsert user request', () => { const validEntraIdUser = anEntraIdUser(); const expected = ENTRA_ID_USER_TO_UPSERT_TFM_USER_REQUEST_SCHEMA.parse(validEntraIdUser); const result = userService.transformEntraIdUserToUpsertTfmUserRequest(validEntraIdUser); diff --git a/trade-finance-manager-api/src/v1/services/user.service.upsert-user-from-entra-id-user.test.ts b/trade-finance-manager-api/src/v1/services/user.service.upsert-user-from-entra-id-user.test.ts index 6ba7179458..314cd3c888 100644 --- a/trade-finance-manager-api/src/v1/services/user.service.upsert-user-from-entra-id-user.test.ts +++ b/trade-finance-manager-api/src/v1/services/user.service.upsert-user-from-entra-id-user.test.ts @@ -47,7 +47,7 @@ describe('user service', () => { createUserSpy = jest.spyOn(UserRepo, 'createUser'); }); - it('creates a new user in the database', async () => { + it('should create a new user in the database', async () => { await userService.upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }); expect(createUserSpy).toHaveBeenCalledWith({ @@ -64,7 +64,7 @@ describe('user service', () => { updateUserByIdSpy = jest.spyOn(UserRepo, 'updateUserById'); }); - it('updates the user in the database', async () => { + it('should update the user in the database', async () => { await userService.upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }); expect(updateUserByIdSpy).toHaveBeenCalledWith({ @@ -81,7 +81,7 @@ describe('user service', () => { jest.spyOn(UserRepo, 'findUsersByEmailAddresses').mockResolvedValue([existingUser, existingUser]); }); - it('throws an error', async () => { + it('should throw an error', async () => { await expect(userService.upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails })).rejects.toThrowError(); }); }); diff --git a/trade-finance-manager-ui/server/api.get-uk-bank-holidays.test.js b/trade-finance-manager-ui/server/api.get-uk-bank-holidays.test.js index 444d6acfb5..034ed11c1f 100644 --- a/trade-finance-manager-ui/server/api.get-uk-bank-holidays.test.js +++ b/trade-finance-manager-ui/server/api.get-uk-bank-holidays.test.js @@ -13,7 +13,7 @@ afterEach(() => { }); describe('getUkBankHolidays', () => { - it('gets the bank holidays', async () => { + it('should get the bank holidays', async () => { // Arrange mockAxios.onGet().reply(200, MOCK_BANK_HOLIDAYS); @@ -24,7 +24,7 @@ describe('getUkBankHolidays', () => { expect(response).toEqual(MOCK_BANK_HOLIDAYS); }); - it('throws when the api TFM API request fails', async () => { + it('should throw when the api TFM API request fails', async () => { // Arrange mockAxios.onGet().reply(404); From 494366e7c077e8fc5df89234fefcf64e7287206d Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 3 Feb 2025 11:17:05 +0000 Subject: [PATCH 095/133] feat(dtfs2-6892): fix lint issue --- .../services/login.service.get-auth-code-url.test.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts b/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts index a4afc7e91b..86c35c4d55 100644 --- a/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts +++ b/trade-finance-manager-ui/server/services/login.service.get-auth-code-url.test.ts @@ -1,5 +1,4 @@ -import { AuthorizationCodeRequest } from '@azure/msal-node'; -import { GetAuthCodeUrlResponse } from '@ukef/dtfs2-common'; +import { aGetAuthCodeUrlResponse, GetAuthCodeUrlResponse } from '@ukef/dtfs2-common'; import { LoginService } from './login.service'; import * as api from '../api'; @@ -25,10 +24,7 @@ describe('login service', () => { }); describe('when the getAuthCodeUrl api call is successful', () => { - const mockGetAuthCodeResponse: GetAuthCodeUrlResponse = { - authCodeUrl: 'a-auth-code-url', - authCodeUrlRequest: {} as AuthorizationCodeRequest, - }; + const mockGetAuthCodeResponse: GetAuthCodeUrlResponse = aGetAuthCodeUrlResponse(); beforeEach(() => { getAuthCodeUrlSpy.mockResolvedValueOnce(mockGetAuthCodeResponse); From 5f528b5e07213c8f469cd805d6c9618530e92b70 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 4 Feb 2025 16:37:15 +0000 Subject: [PATCH 096/133] feat(dtfs2-6892): fix following rebase --- .../common/src/test-helpers/mock-data/create-tfm-user-request.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/common/src/test-helpers/mock-data/create-tfm-user-request.ts b/libs/common/src/test-helpers/mock-data/create-tfm-user-request.ts index a251c63500..00c511b450 100644 --- a/libs/common/src/test-helpers/mock-data/create-tfm-user-request.ts +++ b/libs/common/src/test-helpers/mock-data/create-tfm-user-request.ts @@ -8,5 +8,4 @@ export const aCreateTfmUserRequest = (): CreateTfmUserRequest => ({ timezone: 'Europe/London', firstName: 'a-first-name', lastName: 'a-last-name', - lastLogin: Date.now(), }); From 9e98f191493f5f1256cb2dcf24ccb9915f6e14d1 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 4 Feb 2025 16:38:37 +0000 Subject: [PATCH 097/133] feat(dtfs2-6892): fix following rebase --- ...put-fee-record-correction-transient-form-data.controller.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/fee-record-correction/put-fee-record-correction-transient-form-data.controller.ts b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/fee-record-correction/put-fee-record-correction-transient-form-data.controller.ts index 7bb16d661d..8ca76d5a58 100644 --- a/trade-finance-manager-api/src/v1/controllers/utilisation-reports/fee-record-correction/put-fee-record-correction-transient-form-data.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/utilisation-reports/fee-record-correction/put-fee-record-correction-transient-form-data.controller.ts @@ -1,9 +1,8 @@ import { isAxiosError, HttpStatusCode } from 'axios'; import { Response } from 'express'; -import { RecordCorrectionRequestTransientFormData } from '@ukef/dtfs2-common'; +import { RecordCorrectionRequestTransientFormData, TfmSessionUser } from '@ukef/dtfs2-common'; import api from '../../../api'; import { CustomExpressRequest } from '../../../../types/custom-express-request'; -import { TfmSessionUser } from '../../../../types/tfm-session-user'; export type PutFeeRecordCorrectionTransientFormDataRequestBody = { formData: RecordCorrectionRequestTransientFormData; From 038b04baaf46239178421b47948e367fc0f2fb1b Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 4 Feb 2025 16:46:47 +0000 Subject: [PATCH 098/133] feat(dtfs2-6892): fix following rebase --- .../tfm/entra-id-user-to-upsert-tfm-user-request.schema.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/common/src/schemas/tfm/entra-id-user-to-upsert-tfm-user-request.schema.test.ts b/libs/common/src/schemas/tfm/entra-id-user-to-upsert-tfm-user-request.schema.test.ts index b691ca7147..9a2bf36c86 100644 --- a/libs/common/src/schemas/tfm/entra-id-user-to-upsert-tfm-user-request.schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id-user-to-upsert-tfm-user-request.schema.test.ts @@ -55,7 +55,6 @@ function itShouldReturnAValidUpsertTfmUserRequest(request: EntraIdUser) { timezone: timezoneConfig.DEFAULT, firstName: request.given_name, lastName: request.family_name, - lastLogin: Date.now(), }); }); } From 78a813d3cbaaa49657816aae6e1d1d2f43a1ddc2 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Tue, 4 Feb 2025 17:32:01 +0000 Subject: [PATCH 099/133] feat(dtfs2-6892): fix following rebase --- .../upsert-tfm-user-request.schema.test.ts | 56 +-------- .../tfm/upsert-tfm-user-request.schema.ts | 2 +- .../schemas/schema-tests/index.ts | 1 + ...th-upsert-tfm-user-request.schema.tests.ts | 81 +++++++++++++ .../schemas/with-test-for-test-case.type.ts | 6 +- .../schemas/with-tests-for-testcase.ts | 11 +- .../v1/controllers/user/user.routes.test.js | 113 +++++++++++++++++- .../validate-put-tfm-user-payload.test.ts | 64 +++++++++- .../validate-put-tfm-user-payload.ts | 11 +- 9 files changed, 280 insertions(+), 65 deletions(-) create mode 100644 libs/common/src/test-helpers/schemas/schema-tests/with-upsert-tfm-user-request.schema.tests.ts diff --git a/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.test.ts b/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.test.ts index 628d6065e6..cc9389665a 100644 --- a/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.test.ts +++ b/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.test.ts @@ -1,58 +1,10 @@ -import { TEAM_IDS } from '../../constants'; -import { withSchemaValidationTests } from '../../test-helpers'; -import { UpsertTfmUserRequest } from '../../types'; +import { withUpsertTfmUserRequestSchemaTests } from '../../test-helpers'; import { UPSERT_TFM_USER_REQUEST_SCHEMA } from './upsert-tfm-user-request.schema'; describe('UPSERT_TFM_USER_REQUEST_SCHEMA', () => { - withSchemaValidationTests({ + withUpsertTfmUserRequestSchemaTests({ schema: UPSERT_TFM_USER_REQUEST_SCHEMA, - aValidPayload, - testCases: [ - { - parameterPath: 'username', - type: 'string', - }, - { - parameterPath: 'email', - type: 'string', - }, - { - parameterPath: 'teams', - type: 'Array', - options: { - arrayTypeTestCase: { - type: 'TfmTeamSchema', - }, - }, - }, - { - parameterPath: 'timezone', - type: 'string', - }, - { - parameterPath: 'firstName', - type: 'string', - }, - { - parameterPath: 'lastName', - type: 'string', - }, - { - parameterPath: 'azureOid', - type: 'string', - }, - ], + getTestObjectWithUpdatedParameter: (newValue) => newValue, + getUpdatedParameterFromParsedTestObject: (data) => data, }); - - function aValidPayload(): UpsertTfmUserRequest { - return { - username: 'test-user', - email: 'test-user@test.com', - teams: [TEAM_IDS.PIM], - timezone: 'Europe/London', - firstName: 'FirstName', - lastName: 'LastName', - azureOid: 'test-azure-oid', - }; - } }); diff --git a/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.ts b/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.ts index 7696c134ea..db37a0a704 100644 --- a/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.ts +++ b/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.ts @@ -2,7 +2,7 @@ import { CREATE_TFM_USER_REQUEST_SCHEMA } from './create-tfm-user-request.schema /** * Used during the SSO login process when a user is required to be upserted in TFM - * AS this upsert may require the creation of the user, it is directly based on the create user request + * As this upsert may require the creation of the user, it is directly based on the create user request * @see CREATE_TFM_USER_REQUEST_SCHEMA for the create user request schema this update user request schema is based on */ export const UPSERT_TFM_USER_REQUEST_SCHEMA = CREATE_TFM_USER_REQUEST_SCHEMA; diff --git a/libs/common/src/test-helpers/schemas/schema-tests/index.ts b/libs/common/src/test-helpers/schemas/schema-tests/index.ts index 8b05aaa13d..883b12a891 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/index.ts @@ -4,3 +4,4 @@ */ export * from './with-audit-database-record-schema.tests'; export * from './with-entra-id-user-schema.tests'; +export * from './with-upsert-tfm-user-request.schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-upsert-tfm-user-request.schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-upsert-tfm-user-request.schema.tests.ts new file mode 100644 index 0000000000..9b578b8d42 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-upsert-tfm-user-request.schema.tests.ts @@ -0,0 +1,81 @@ +import { ZodSchema } from 'zod'; +import { WithSchemaTestParams } from '../with-schema-test.type'; +import { withDefaultOptionsTests } from '../primitive-types-tests'; +import { withSchemaValidationTests } from '../with-schema-validation.tests'; +import { aUpsertTfmUserRequest } from '../../mock-data'; + +export const withUpsertTfmUserRequestSchemaTests = ({ + schema, + options = {}, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, +}: WithSchemaTestParams) => { + describe('with UPSERT_TFM_USER_REQUEST_SCHEMA tests', () => { + withDefaultOptionsTests({ + schema, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, + options, + }); + + withSchemaValidationTests({ + schema, + aValidPayload: aUpsertTfmUserRequest, + testCases: [ + { + parameterPath: 'username', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedParameter({ ...aUpsertTfmUserRequest(), username: newValue }), + }, + }, + { + parameterPath: 'email', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedParameter({ ...aUpsertTfmUserRequest(), email: newValue }), + }, + }, + { + parameterPath: 'teams', + type: 'Array', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedParameter({ ...aUpsertTfmUserRequest(), teams: newValue }), + arrayTypeTestCase: { + type: 'TfmTeamSchema', + }, + }, + }, + { + parameterPath: 'timezone', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedParameter({ ...aUpsertTfmUserRequest(), timezone: newValue }), + }, + }, + { + parameterPath: 'firstName', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => + getTestObjectWithUpdatedParameter({ ...aUpsertTfmUserRequest(), firstName: newValue }), + }, + }, + { + parameterPath: 'lastName', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedParameter({ ...aUpsertTfmUserRequest(), lastName: newValue }), + }, + }, + { + parameterPath: 'azureOid', + type: 'string', + options: { + overrideGetTestObjectWithUpdatedField: (newValue: unknown) => getTestObjectWithUpdatedParameter({ ...aUpsertTfmUserRequest(), azureOid: newValue }), + }, + }, + ], + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts b/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts index 533750b7e8..85b69618a9 100644 --- a/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts +++ b/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts @@ -19,7 +19,8 @@ export type TestCaseTypes = | 'AUDIT_DATABASE_RECORD_SCHEMA' | 'ENTRA_ID_USER_SCHEMA' | 'CURRENCY_SCHEMA' - | 'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA'; + | 'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA' + | 'UPSERT_TFM_USER_REQUEST_SCHEMA'; /** * The test case to be tested, including the type and any options that are required @@ -46,4 +47,5 @@ export type TestCase = | TestCaseWithType<'AUDIT_DATABASE_RECORD_SCHEMA'> | TestCaseWithType<'ENTRA_ID_USER_SCHEMA'> | TestCaseWithType<'CURRENCY_SCHEMA'> - | TestCaseWithType<'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA'>; + | TestCaseWithType<'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA'> + | TestCaseWithType<'UPSERT_TFM_USER_REQUEST_SCHEMA'>; diff --git a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts index 579110598b..f7ce294b9c 100644 --- a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts +++ b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts @@ -10,7 +10,7 @@ import { withTfmTeamSchemaTests, } from './custom-types-tests'; import { withStringTests, withNumberTests, withBooleanTests, withArrayTests } from './primitive-types-tests'; -import { withAuditDatabaseRecordSchemaTests, withEntraIdUserSchemaTests } from './schema-tests'; +import { withAuditDatabaseRecordSchemaTests, withEntraIdUserSchemaTests, withUpsertTfmUserRequestSchemaTests } from './schema-tests'; import { TestCase } from './with-test-for-test-case.type'; import { withCurrencySchemaTests } from './custom-types-tests/with-currency-schema.tests'; import { withIsoDateTimeStampToDateSchemaTests } from './transformation-tests'; @@ -178,6 +178,15 @@ export const withTestsForTestcase = ({ }); break; + case 'UPSERT_TFM_USER_REQUEST_SCHEMA': + withUpsertTfmUserRequestSchemaTests({ + schema, + options, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, + }); + break; + default: throw Error(`There are no existing test cases for the type ${type}`); } diff --git a/trade-finance-manager-api/src/v1/controllers/user/user.routes.test.js b/trade-finance-manager-api/src/v1/controllers/user/user.routes.test.js index ed932c3975..6f9bd85e41 100644 --- a/trade-finance-manager-api/src/v1/controllers/user/user.routes.test.js +++ b/trade-finance-manager-api/src/v1/controllers/user/user.routes.test.js @@ -1,7 +1,6 @@ const httpMocks = require('node-mocks-http'); -const { anEntraIdUser, ApiError } = require('@ukef/dtfs2-common'); +const { anEntraIdUser, ApiError, TEAMS } = require('@ukef/dtfs2-common'); const { HttpStatusCode } = require('axios'); -const { getEntraIdUserSuccessTestCases, getEntraIdUserFailureTestCases } = require('@ukef/dtfs2-common'); const { upsertTfmUserFromEntraIdUser } = require('./user.routes'); const userController = require('./user.controller'); const { withValidatePayloadTests } = require('../../../../test-helpers'); @@ -113,3 +112,113 @@ describe('user routes', () => { } }); }); + +function getEntraIdUserFailureTestCases({ getTestObjectWithUpdatedEntraIdUserParams = (entraIdUser) => entraIdUser }) { + return [ + { + aTestCase: () => { + const { oid: _oid, ...rest } = anEntraIdUser(); + return getTestObjectWithUpdatedEntraIdUserParams(rest); + }, + description: 'the oid is missing', + }, + { + aTestCase: () => { + const { verified_primary_email: _verifiedPrimaryEmail, ...rest } = anEntraIdUser(); + return getTestObjectWithUpdatedEntraIdUserParams(rest); + }, + description: 'the verified primary email is missing', + }, + { + aTestCase: () => { + const { verified_secondary_email: _verifiedSecondaryEmail, ...rest } = anEntraIdUser(); + return getTestObjectWithUpdatedEntraIdUserParams(rest); + }, + description: 'the verified secondary email is missing', + }, + { + aTestCase: () => { + const { given_name: _givenName, ...rest } = anEntraIdUser(); + return getTestObjectWithUpdatedEntraIdUserParams(rest); + }, + description: 'the given name is missing', + }, + + { + aTestCase: () => { + const { family_name: _familyName, ...rest } = anEntraIdUser(); + return getTestObjectWithUpdatedEntraIdUserParams(rest); + }, + description: 'the family name is missing', + }, + { + aTestCase: () => { + const { roles: _roles, ...rest } = anEntraIdUser(); + return getTestObjectWithUpdatedEntraIdUserParams(rest); + }, + description: 'the roles are missing', + }, + { + aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), oid: 1 }), + description: 'the oid is not a string', + }, + { + aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_primary_email: [] }), + description: 'the verify primary email array is empty', + }, + { + aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_primary_email: [1] }), + description: 'the verify primary email is not a string array', + }, + { + aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_primary_email: '1' }), + description: 'the verify primary email is not an array', + }, + { + aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_secondary_email: [1] }), + description: 'the verify secondary email is not a string array', + }, + { + aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_secondary_email: '1' }), + description: 'the verify secondary email is not an array', + }, + { + aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), given_name: 1 }), + description: 'the given name is not a string', + }, + { + aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), family_name: 1 }), + description: 'the family name is not a string', + }, + { + aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), roles: ['NOT_A_USER_ROLE'] }), + description: 'the roles are not an array of user roles', + }, + { + aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), roles: TEAMS.BUSINESS_SUPPORT.id }), + description: 'the roles are not an array', + }, + { + aTestCase: () => ({}), + description: 'the object is empty', + }, + ]; +} + +function getEntraIdUserSuccessTestCases({ getTestObjectWithUpdatedEntraIdUserParams = (entraIdUser) => entraIdUser }) { + return [ + { aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams(anEntraIdUser()), description: 'a complete valid payload is present' }, + { + aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_secondary_email: [] }), + description: 'the verified secondary email array is empty', + }, + { + aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), roles: [] }), + description: 'the roles array is empty', + }, + { + aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), extraField: 'extra' }), + description: 'there is an extra field', + }, + ]; +} diff --git a/trade-finance-manager-api/src/v1/middleware/validate-put-tfm-user-payload.test.ts b/trade-finance-manager-api/src/v1/middleware/validate-put-tfm-user-payload.test.ts index 80c90868a5..c2e2efa3dd 100644 --- a/trade-finance-manager-api/src/v1/middleware/validate-put-tfm-user-payload.test.ts +++ b/trade-finance-manager-api/src/v1/middleware/validate-put-tfm-user-payload.test.ts @@ -1,8 +1,64 @@ -import { UPSERT_TFM_USER_REQUEST_SCHEMA } from '@ukef/dtfs2-common/schemas'; -import { withUpsertTfmUserRequestSchemaTests } from '@ukef/dtfs2-common'; +import { aUpsertTfmUserRequest } from '@ukef/dtfs2-common'; +import httpMocks from 'node-mocks-http'; +import { HttpStatusCode } from 'axios'; +import { validateTfmPutUserPayload } from './validate-put-tfm-user-payload'; describe('validatePutTfmUserPayload', () => { - withUpsertTfmUserRequestSchemaTests({ - schema: UPSERT_TFM_USER_REQUEST_SCHEMA, + const getHttpMocks = () => httpMocks.createMocks(); + + const aValidPayload = aUpsertTfmUserRequest; + + const invalidPayloads = [ + { + description: 'the payload is undefined', + payload: undefined, + }, + { + description: 'the payload is not valid', + payload: { + // Missing last name field + azureOid: 'an-azure-oid', + email: 'an-email', + username: 'a-username', + teams: ['BUSINESS_SUPPORT'], + timezone: 'Europe/London', + firstName: 'a-first-name', + }, + }, + { + description: 'the payload is an empty object', + payload: {}, + }, + ]; + + it.each(invalidPayloads)(`responds with a '${HttpStatusCode.BadRequest}' if $description`, ({ payload }) => { + // Arrange + const { req, res } = getHttpMocks(); + const next = jest.fn(); + + req.body = payload; + + // Act + validateTfmPutUserPayload(req, res, next); + + // Assert + expect(res._getStatusCode()).toEqual(HttpStatusCode.BadRequest); + expect(res._isEndCalled()).toEqual(true); + expect(next).not.toHaveBeenCalled(); + }); + + it("calls the 'next' function if the payload is valid", () => { + // Arrange + const { req, res } = getHttpMocks(); + const next = jest.fn(); + + req.body = aValidPayload(); + + // Act + validateTfmPutUserPayload(req, res, next); + + // Assert + expect(next).toHaveBeenCalled(); + expect(res._isEndCalled()).toEqual(false); }); }); diff --git a/trade-finance-manager-api/src/v1/middleware/validate-put-tfm-user-payload.ts b/trade-finance-manager-api/src/v1/middleware/validate-put-tfm-user-payload.ts index 39849a490e..bde5b90dcd 100644 --- a/trade-finance-manager-api/src/v1/middleware/validate-put-tfm-user-payload.ts +++ b/trade-finance-manager-api/src/v1/middleware/validate-put-tfm-user-payload.ts @@ -1,9 +1,14 @@ import z from 'zod'; import { createValidationMiddlewareForSchema } from '@ukef/dtfs2-common'; -import { CREATE_TFM_USER_REQUEST_SCHEMA } from '@ukef/dtfs2-common/schemas'; +import { UPSERT_TFM_USER_REQUEST_SCHEMA } from '@ukef/dtfs2-common/schemas'; -const PUT_TFM_USER_SCHEMA = CREATE_TFM_USER_REQUEST_SCHEMA; +/** + * We alias this type for extendability, however while it is only an alias + * we only need to test the original schema + * @see UPSERT_TFM_USER_REQUEST_SCHEMA + */ +const PUT_TFM_USER_SCHEMA = UPSERT_TFM_USER_REQUEST_SCHEMA; -export type PutTfmUserPayload = z.infer; +export type PutTfmUserPayload = z.infer; export const validateTfmPutUserPayload = createValidationMiddlewareForSchema(PUT_TFM_USER_SCHEMA); From 7e725ae8ed24a151085c92025f078611b1baae07 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 5 Feb 2025 13:01:30 +0000 Subject: [PATCH 100/133] feat(dtfs2-6892): fix following rebase --- .../sso.controller.get-auth-code-url.test.ts | 37 +++++++++++++++---- .../src/v1/controllers/sso.controller.ts | 2 +- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts index 79f3206071..bae78e3047 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts @@ -1,4 +1,4 @@ -import { aGetAuthCodeUrlParams, aGetAuthCodeUrlResponse, GetAuthCodeUrlApiRequest, GetAuthCodeUrlApiResponse } from '@ukef/dtfs2-common'; +import { aGetAuthCodeUrlParams, aGetAuthCodeUrlResponse, GetAuthCodeUrlApiRequest, GetAuthCodeUrlApiResponse, TestApiError } from '@ukef/dtfs2-common'; import { resetAllWhenMocks } from 'jest-when'; import httpMocks from 'node-mocks-http'; import { HttpStatusCode } from 'axios'; @@ -63,26 +63,47 @@ describe('SsoController', () => { expect(res.statusCode).toBe(HttpStatusCode.Ok); }); - it('should pass through thrown errors', async () => { + it('should call console.error on error', async () => { const getAuthCodeUrlParmas = aGetAuthCodeUrlParams(); const { req, res } = getHttpMocks(getAuthCodeUrlParmas); const error = new Error('Test error'); getAuthCodeUrlMock.mockRejectedValue(error); - await expect(ssoController.getAuthCodeUrl(req, res)).rejects.toThrow(error); + await ssoController.getAuthCodeUrl(req, res).catch(() => {}); + + expect(console.error).toHaveBeenCalledWith('Failed to get auth code url', error); }); - it('should call console.error on error', async () => { - const getAuthCodeUrlParmas = aGetAuthCodeUrlParams(); - const { req, res } = getHttpMocks(getAuthCodeUrlParmas); + it('should return an error response on api error', async () => { + const { req, res } = getHttpMocks(aGetAuthCodeUrlParams()); + + const error = new TestApiError({ status: HttpStatusCode.BadGateway, message: 'Test error' }); + getAuthCodeUrlMock.mockRejectedValue(error); + + await ssoController.getAuthCodeUrl(req, res); + + expect(res.statusCode).toBe(error.status); + + expect(res._getData()).toEqual({ + status: error.status, + message: `Failed to get auth code url: ${error.message}`, + }); + }); + + it('should return a 500 status code on non api error', async () => { + const { req, res } = getHttpMocks(aGetAuthCodeUrlParams()); const error = new Error('Test error'); getAuthCodeUrlMock.mockRejectedValue(error); - await ssoController.getAuthCodeUrl(req, res).catch(() => {}); + await ssoController.getAuthCodeUrl(req, res); - expect(console.error).toHaveBeenCalledWith('An error occurred while getting the auth code URL:', error); + expect(res.statusCode).toBe(HttpStatusCode.InternalServerError); + expect(res._getData()).toEqual({ + status: HttpStatusCode.InternalServerError, + message: 'Failed to get auth code url', + }); }); function getHttpMocks(params: GetAuthCodeUrlApiRequest['params']): { diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts index 4d306dc7a6..43f795ff93 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.ts @@ -26,7 +26,7 @@ export class SsoController { public async getAuthCodeUrl(req: GetAuthCodeUrlApiRequest, res: GetAuthCodeUrlApiResponse) { try { - const getAuthCodeUrlResponse = await this.entraIdService.getAuthCodeUrl({ successRedirect: req.originalUrl || '/' }); + const getAuthCodeUrlResponse = await this.entraIdService.getAuthCodeUrl({ successRedirect: req.params.successRedirect || '/' }); res.json(getAuthCodeUrlResponse); } catch (error) { const errorMessage = 'Failed to get auth code url'; From 47ee83c53977a5c440637d691ec8635550def0d1 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 5 Feb 2025 13:29:37 +0000 Subject: [PATCH 101/133] feat(dtfs2-6892): fix following rebase --- .../common/src/schemas/portal-amendment.schema.test.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/libs/common/src/schemas/portal-amendment.schema.test.ts b/libs/common/src/schemas/portal-amendment.schema.test.ts index c8a5ab6b2c..99d0b72273 100644 --- a/libs/common/src/schemas/portal-amendment.schema.test.ts +++ b/libs/common/src/schemas/portal-amendment.schema.test.ts @@ -23,11 +23,6 @@ describe('PORTAL_FACILITY_AMENDMENT_USER_VALUES', () => { type: 'UNIX_TIMESTAMP_SECONDS_SCHEMA', options: { isOptional: true }, }, - { - parameterPath: 'currentCoverEndDate', - type: 'UNIX_TIMESTAMP_SECONDS_SCHEMA', - options: { isOptional: true }, - }, { parameterPath: 'isUsingFacilityEndDate', type: 'boolean', @@ -53,13 +48,12 @@ describe('PORTAL_FACILITY_AMENDMENT_USER_VALUES', () => { type: 'number', options: { isOptional: true }, }, + { parameterPath: 'currency', type: 'CURRENCY_SCHEMA', options: { isOptional: true } }, { - parameterPath: 'currentValue', + parameterPath: 'effectiveDate', type: 'number', options: { isOptional: true }, }, - { parameterPath: 'currency', type: 'CURRENCY_SCHEMA', options: { isOptional: true } }, - { parameterPath: 'coveredPercentage', type: 'number', options: { isOptional: true } }, ], }); }); From 47dcaa137e35b7644921b03499d28b63a2a47d01 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 5 Feb 2025 14:07:18 +0000 Subject: [PATCH 102/133] feat(dtfs2-6892): fix following rebase --- libs/common/src/schemas/tfm/tfm-session-user.schema.test.ts | 1 + .../schemas/schema-tests/with-tfm-session-user-schema.tests.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/libs/common/src/schemas/tfm/tfm-session-user.schema.test.ts b/libs/common/src/schemas/tfm/tfm-session-user.schema.test.ts index 6facf49bdb..e3fd82c5a2 100644 --- a/libs/common/src/schemas/tfm/tfm-session-user.schema.test.ts +++ b/libs/common/src/schemas/tfm/tfm-session-user.schema.test.ts @@ -5,5 +5,6 @@ describe('TFM_SESSION_USER_SCHEMA', () => { withTfmSessionUserSchemaTests({ schema: TFM_SESSION_USER_SCHEMA, getTestObjectWithUpdatedParameter: (newValue: unknown) => newValue, + getUpdatedParameterFromParsedTestObject: (data) => data, }); }); diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-session-user-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-session-user-schema.tests.ts index c2d7d70d23..3a492b4916 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-session-user-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-session-user-schema.tests.ts @@ -9,12 +9,14 @@ export const withTfmSessionUserSchemaTests = ({ schema, options = {}, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }: WithSchemaTestParams) => { describe('with TFM_SESSION_USER_SCHEMA tests', () => { withDefaultOptionsTests({ schema, options, getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, }); withSchemaValidationTests({ From 1da8335b017df19a96e6a241fb0d4cff1ef66680 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 5 Feb 2025 14:37:01 +0000 Subject: [PATCH 103/133] feat(dtfs2-6892): fix following rebase --- .../server/routes/auth/configs/index.get-auth-router.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts b/trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts index dc34246597..f064acc32d 100644 --- a/trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts +++ b/trade-finance-manager-ui/server/routes/auth/configs/index.get-auth-router.test.ts @@ -7,7 +7,7 @@ jest.mock('@ukef/dtfs2-common', () => ({ isTfmSsoFeatureFlagEnabled: jest.fn(), })); -jest.mock('./authSso', () => ({ +jest.mock('./auth-sso', () => ({ getAuthSsoRouter: jest.fn(), })); From 8a9ca34ec6da2dcb594b0e57c12394919fbf4466 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 5 Feb 2025 14:41:31 +0000 Subject: [PATCH 104/133] feat(dtfs2-6892): fix following rebase --- .../login.controller.get-login.test.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts index f687507358..336c5aa14f 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.get-login.test.ts @@ -17,7 +17,8 @@ describe('controllers - login (sso)', () => { let userSessionService: UserSessionService; const getAuthCodeUrlMock = jest.fn(); - const next = jest.fn(); + + console.error = jest.fn(); beforeEach(() => { resetAllWhenMocks(); @@ -114,7 +115,7 @@ describe('controllers - login (sso)', () => { mockFailedGetAuthCodeUrl(); }); - it('should call next with error', async () => { + it('should call console.error', async () => { // Arrange const { req, res } = httpMocks.createMocks({ session: {}, originalUrl: aRedirectUrl }); @@ -125,7 +126,19 @@ describe('controllers - login (sso)', () => { await loginController.getLogin(req, res); // Assert - expect(next).toHaveBeenCalledWith(error); + expect(console.error).toHaveBeenCalledWith('Unable to log in user %o', error); + }); + + it('should redirect to problem-with-service page', async () => { + // Arrange + + const { req, res } = httpMocks.createMocks({ session: {}, originalUrl: aRedirectUrl }); + + // Act + await loginController.getLogin(req, res); + + // Assert + expect(res._getRenderView()).toEqual('_partials/problem-with-service.njk'); }); }); }); From 930f3f8b6b9652670b84b76454baf86526b35ef7 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 5 Feb 2025 14:45:06 +0000 Subject: [PATCH 105/133] feat(dtfs2-6892): fix following rebase --- ...ontroller.handle-sso-redirect-form.test.ts | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.handle-sso-redirect-form.test.ts b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.handle-sso-redirect-form.test.ts index ef04f6e238..6f9580df2d 100644 --- a/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.handle-sso-redirect-form.test.ts +++ b/trade-finance-manager-ui/server/controllers/login/login-sso/login.controller.handle-sso-redirect-form.test.ts @@ -60,13 +60,13 @@ describe('controllers - login (sso)', () => { })); }); - it('logs an error', async () => { + it('should log an error', async () => { await loginController.handleSsoRedirectForm(req, res); - expect(consoleErrorMock).toHaveBeenCalledWith('Unable to redirect user after login: %O', expect.any(UserPartialLoginDataNotDefinedError)); + expect(consoleErrorMock).toHaveBeenCalledWith('Unable to redirect the user after login %o', expect.any(UserPartialLoginDataNotDefinedError)); }); - it('renders the problem with service page', async () => { + it('should render the problem with service page', async () => { await loginController.handleSsoRedirectForm(req, res); expect(res._getRenderView()).toEqual('_partials/problem-with-service.njk'); @@ -92,13 +92,13 @@ describe('controllers - login (sso)', () => { })); }); - it('logs an error', async () => { + it('should log an error', async () => { await loginController.handleSsoRedirectForm(req, res); - expect(consoleErrorMock).toHaveBeenCalledWith('Unable to redirect user after login: %O', expect.any(InvalidPayloadError)); + expect(consoleErrorMock).toHaveBeenCalledWith('Unable to redirect the user after login %o', expect.any(InvalidPayloadError)); }); - it('renders the problem with service page', async () => { + it('should render the problem with service page', async () => { await loginController.handleSsoRedirectForm(req, res); expect(res._getRenderView()).toEqual('_partials/problem-with-service.njk'); @@ -113,7 +113,7 @@ describe('controllers - login (sso)', () => { })); }); - it('calls loginService.handleSsoRedirectForm with the correct parameters', async () => { + it('should call loginService.handleSsoRedirectForm with the correct parameters', async () => { await loginController.handleSsoRedirectForm(req, res); expect(handleSsoRedirectFormMock).toHaveBeenCalledWith({ @@ -123,7 +123,7 @@ describe('controllers - login (sso)', () => { }); }); - it('calls loginService.handleSsoRedirectForm once', async () => { + it('should call loginService.handleSsoRedirectForm once', async () => { await loginController.handleSsoRedirectForm(req, res); expect(handleSsoRedirectFormMock).toHaveBeenCalledTimes(1); @@ -137,13 +137,13 @@ describe('controllers - login (sso)', () => { handleSsoRedirectFormMock.mockRejectedValueOnce(thrownError); }); - it('logs an error', async () => { + it('should log an error', async () => { await loginController.handleSsoRedirectForm(req, res); - expect(consoleErrorMock).toHaveBeenCalledWith('Unable to redirect user after login: %O', thrownError); + expect(consoleErrorMock).toHaveBeenCalledWith('Unable to redirect the user after login %o', thrownError); }); - it('renders the problem with service page', async () => { + it('should render the problem with service page', async () => { await loginController.handleSsoRedirectForm(req, res); expect(res._getRenderView()).toEqual('_partials/problem-with-service.njk'); @@ -160,7 +160,7 @@ describe('controllers - login (sso)', () => { handleSsoRedirectFormMock.mockResolvedValueOnce(handleSsoRedirectFormResponse); }); - it('updates the session', async () => { + it('should update the session', async () => { await loginController.handleSsoRedirectForm(req, res); expect(createLoggedInSessionMock).toHaveBeenCalledWith({ @@ -170,13 +170,13 @@ describe('controllers - login (sso)', () => { }); }); - it('updates the session once', async () => { + it('should update the session once', async () => { await loginController.handleSsoRedirectForm(req, res); expect(createLoggedInSessionMock).toHaveBeenCalledTimes(1); }); - it('redirects to the home page', async () => { + it('should redirect to the home page', async () => { await loginController.handleSsoRedirectForm(req, res); expect(res._getRedirectUrl()).toEqual('/'); @@ -191,7 +191,7 @@ describe('controllers - login (sso)', () => { handleSsoRedirectFormMock.mockResolvedValueOnce(handleSsoRedirectFormResponse); }); - it('updates the session', async () => { + it('should update the session', async () => { await loginController.handleSsoRedirectForm(req, res); expect(createLoggedInSessionMock).toHaveBeenCalledWith({ @@ -201,13 +201,13 @@ describe('controllers - login (sso)', () => { }); }); - it('updates the session once', async () => { + it('should update the session once', async () => { await loginController.handleSsoRedirectForm(req, res); expect(createLoggedInSessionMock).toHaveBeenCalledTimes(1); }); - it('redirects to the success redirect parameter', async () => { + it('should redirect to the success redirect parameter', async () => { await loginController.handleSsoRedirectForm(req, res); expect(res._getRedirectUrl()).toEqual(handleSsoRedirectFormResponse.successRedirect); From d889fb14547a1fd633bd16ff54c419656c081288 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 5 Feb 2025 17:38:15 +0000 Subject: [PATCH 106/133] feat(dtfs2-6892): review comments --- libs/common/src/schemas/tfm/tfm-user.schema.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/libs/common/src/schemas/tfm/tfm-user.schema.ts b/libs/common/src/schemas/tfm/tfm-user.schema.ts index a9d9e71d9f..78cddf80f9 100644 --- a/libs/common/src/schemas/tfm/tfm-user.schema.ts +++ b/libs/common/src/schemas/tfm/tfm-user.schema.ts @@ -4,17 +4,26 @@ import { UNIX_TIMESTAMP_MILLISECONDS_SCHEMA } from '../unix-timestamp.schema'; import { AUDIT_DATABASE_RECORD } from '../audit-database-record'; import { OBJECT_ID } from '../object-id'; -// TODO update docs, tests +/** + * These fields only are relevant to users when SSO is not enabled + */ const TFM_USER_NON_SSO_SPECIFIC_SCHEMA = z.object({ salt: z.string(), hash: z.string(), loginFailureCount: z.number().optional(), }); +/** + * These fields only are relevant to users when SSO is enabled + */ const TFM_USER_SSO_SPECIFIC_SCHEMA = z.object({ azureOid: z.string(), }); +/** + * The base schema for a TFM user + * This schema contains login agnostic properties of a TFM user + */ const BASE_TFM_USER_SCHEMA = z.object({ _id: OBJECT_ID, username: z.string(), From 60df4645ca9d576931d5bdec1edd9b1846635b91 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 5 Feb 2025 17:40:06 +0000 Subject: [PATCH 107/133] feat(dtfs2-6892): review comments --- libs/common/src/schemas/tfm/tfm-user.schema.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/common/src/schemas/tfm/tfm-user.schema.ts b/libs/common/src/schemas/tfm/tfm-user.schema.ts index 78cddf80f9..cdf2df0acd 100644 --- a/libs/common/src/schemas/tfm/tfm-user.schema.ts +++ b/libs/common/src/schemas/tfm/tfm-user.schema.ts @@ -38,4 +38,7 @@ const BASE_TFM_USER_SCHEMA = z.object({ auditRecord: AUDIT_DATABASE_RECORD.optional(), }); +/** + * The user schema can contain a mix of login specific properties + */ export const TFM_USER_SCHEMA = BASE_TFM_USER_SCHEMA.merge(TFM_USER_NON_SSO_SPECIFIC_SCHEMA.partial()).merge(TFM_USER_SSO_SPECIFIC_SCHEMA.partial()); From 1ef4091953b69b8ecf8a9ab58ca0fec854d87f7a Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 6 Feb 2025 11:49:36 +0000 Subject: [PATCH 108/133] feat(dtfs2-6892): review comments --- libs/common/src/schemas/tfm/tfm-session-user.schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/schemas/tfm/tfm-session-user.schema.ts b/libs/common/src/schemas/tfm/tfm-session-user.schema.ts index 1a574118b9..e073d422b6 100644 --- a/libs/common/src/schemas/tfm/tfm-session-user.schema.ts +++ b/libs/common/src/schemas/tfm/tfm-session-user.schema.ts @@ -10,4 +10,4 @@ export const TFM_SESSION_USER_SCHEMA = TFM_USER_SCHEMA.pick({ lastName: true, status: true, lastLogin: true, -}).extend({ _id: OBJECT_ID_STRING }); // _id is extended as a string as apposed to objectId due to existing implimentation of TFM session user +}).extend({ _id: OBJECT_ID_STRING }); // _id is extended as a string as opposed to objectId due to existing implementation of TFM session user From 8589fc2dea1a7f12095a9f905d996f69df68892c Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 10 Feb 2025 13:46:54 +0000 Subject: [PATCH 109/133] feat(dtfs2-6892): review comments --- ...se-record.test.ts => audit-database-record.schema.test.ts} | 2 +- libs/common/src/schemas/currency.schema.test.ts | 2 +- libs/common/src/schemas/iso-date-time-stamp.schema.ts | 4 ++-- .../src/test-helpers/schemas/custom-types-tests/index.ts | 1 + .../custom-types-tests/with-unix-timestamp-schema.tests.ts | 2 +- .../with-unix-timestamp-seconds-schema.tests.ts | 2 +- .../src/test-helpers/schemas/with-tests-for-testcase.ts | 2 +- 7 files changed, 8 insertions(+), 7 deletions(-) rename libs/common/src/schemas/{audit-database-record.test.ts => audit-database-record.schema.test.ts} (89%) diff --git a/libs/common/src/schemas/audit-database-record.test.ts b/libs/common/src/schemas/audit-database-record.schema.test.ts similarity index 89% rename from libs/common/src/schemas/audit-database-record.test.ts rename to libs/common/src/schemas/audit-database-record.schema.test.ts index eec3cd1589..6606109292 100644 --- a/libs/common/src/schemas/audit-database-record.test.ts +++ b/libs/common/src/schemas/audit-database-record.schema.test.ts @@ -1,7 +1,7 @@ import { withAuditDatabaseRecordSchemaTests } from '../test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests'; import { AUDIT_DATABASE_RECORD_SCHEMA } from './audit-database-record.schema'; -describe('AUDIT_DATABASE_RECORD', () => { +describe('AUDIT_DATABASE_RECORD_SCHEMA', () => { withAuditDatabaseRecordSchemaTests({ schema: AUDIT_DATABASE_RECORD_SCHEMA, getTestObjectWithUpdatedParameter: (newValue) => newValue, diff --git a/libs/common/src/schemas/currency.schema.test.ts b/libs/common/src/schemas/currency.schema.test.ts index d2fe885604..41f42f7222 100644 --- a/libs/common/src/schemas/currency.schema.test.ts +++ b/libs/common/src/schemas/currency.schema.test.ts @@ -1,4 +1,4 @@ -import { withCurrencySchemaTests } from '../test-helpers/schemas/custom-types-tests/with-currency-schema.tests'; +import { withCurrencySchemaTests } from '../test-helpers'; import { CURRENCY_SCHEMA } from './currency.schema'; describe('CURRENCY_SCHEMA', () => { diff --git a/libs/common/src/schemas/iso-date-time-stamp.schema.ts b/libs/common/src/schemas/iso-date-time-stamp.schema.ts index 2adcceb064..1f4bbe4843 100644 --- a/libs/common/src/schemas/iso-date-time-stamp.schema.ts +++ b/libs/common/src/schemas/iso-date-time-stamp.schema.ts @@ -1,7 +1,7 @@ import z from 'zod'; /** - * ISO datetimes throughout the codebase vary due to mixed implimentation - * The following regex complies with whatgetNowAsUtcISOString returns + * ISO datetimes throughout the codebase vary due to mixed implementation + * The following regex complies with what getNowAsUtcISOString returns */ const isoDateTimeStampRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3} \+\d{2}:\d{2}$/; export const ISO_DATE_TIME_STAMP_SCHEMA = z.string().regex(isoDateTimeStampRegex); diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts index dee82dd937..4b63b3fc74 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts @@ -10,3 +10,4 @@ export * from './with-unix-timestamp-schema.tests'; export * from './with-unix-timestamp-milliseconds-schema.tests'; export * from './with-unix-timestamp-seconds-schema.tests'; export * from './with-tfm-team-schema.tests'; +export * from './with-currency-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts index 5bbe302b0c..55f4b731e3 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts @@ -30,7 +30,7 @@ export const withUnixTimestampSchemaTests = ({ }); it('should fail parsing if the parameter is not an int number', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(-1)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(1.1)); expect(success).toBe(false); }); }); diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts index b94b5ca159..d663cbc689 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts @@ -30,7 +30,7 @@ export const withUnixTimestampSecondsSchemaTests = ({ }); it('should fail parsing if the parameter is not an int number', () => { - const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(-1)); + const { success } = schema.safeParse(getTestObjectWithUpdatedParameter(1.1)); expect(success).toBe(false); }); }); diff --git a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts index f7ce294b9c..cc95308a33 100644 --- a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts +++ b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts @@ -8,11 +8,11 @@ import { withObjectIdOrObjectIdStringSchemaTests, withIsoDateTimeStampSchemaTests, withTfmTeamSchemaTests, + withCurrencySchemaTests, } from './custom-types-tests'; import { withStringTests, withNumberTests, withBooleanTests, withArrayTests } from './primitive-types-tests'; import { withAuditDatabaseRecordSchemaTests, withEntraIdUserSchemaTests, withUpsertTfmUserRequestSchemaTests } from './schema-tests'; import { TestCase } from './with-test-for-test-case.type'; -import { withCurrencySchemaTests } from './custom-types-tests/with-currency-schema.tests'; import { withIsoDateTimeStampToDateSchemaTests } from './transformation-tests'; /** From 0e0fffaac44acea9b809c4d17a2d991d8ecb4873 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 10 Feb 2025 14:42:55 +0000 Subject: [PATCH 110/133] feat(dtfs2-6892): review comments --- .../test-cases-backend/with-api-error-tests.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts b/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts index de3d43f696..6e0cd054b3 100644 --- a/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts +++ b/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts @@ -4,6 +4,18 @@ import { Response } from 'express'; import { API_ERROR_CODE } from '../../constants'; import { TestApiError } from '../test-api-error'; +/** + * Parameters for the withApiErrorTests function + * + * We use a getter for the response object to ensure the correct instance is used. + * It also allows for res to not be initiated until the test is run, + * which is a common pattern when writing our tests + * + * @param mockAnError - a function that will mock an error being thrown + * @param makeRequest - a function that will make the request to the endpoint + * @param getRes - a function that will return the response object + * @param endpointErrorMessage - the error message that should be returned from the endpoint (either with an api message when throwing an api error, or by itself otherwise) + */ type WithApiErrorTestsParams = { mockAnError: (error: unknown) => void; makeRequest: () => Promise; @@ -11,13 +23,17 @@ type WithApiErrorTestsParams = { endpointErrorMessage: string; }; +/** + * Generates tests for testing errors thrown from a backend endpoint, including + * testing the responses for the api error class, and generic error classes + */ export const withApiErrorTests = ({ mockAnError, makeRequest, getRes, endpointErrorMessage }: WithApiErrorTestsParams) => { describe('with api error tests', () => { describe('when an api error is thrown', () => { it('should return the errors status code with error details', async () => { // Arrange const errorStatus = HttpStatusCode.BadRequest; - const errorMessage = 'a message that should not be exposed'; + const errorMessage = 'a message that should be exposed'; const errorCode = API_ERROR_CODE.INVALID_PAYLOAD; const error = new TestApiError({ status: errorStatus, message: errorMessage, code: errorCode }); From 12c1215c8abcc331d705b1ae7816001ba012eecc Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 10 Feb 2025 17:06:53 +0000 Subject: [PATCH 111/133] feat(dtfs2-6892): review comments --- ...schma.create.test.ts => portal-user.schema.create.test.ts} | 0 .../src/v1/middleware/validate-put-tfm-user-payload.test.ts | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename libs/common/src/schemas/{portal-user.schma.create.test.ts => portal-user.schema.create.test.ts} (100%) diff --git a/libs/common/src/schemas/portal-user.schma.create.test.ts b/libs/common/src/schemas/portal-user.schema.create.test.ts similarity index 100% rename from libs/common/src/schemas/portal-user.schma.create.test.ts rename to libs/common/src/schemas/portal-user.schema.create.test.ts diff --git a/trade-finance-manager-api/src/v1/middleware/validate-put-tfm-user-payload.test.ts b/trade-finance-manager-api/src/v1/middleware/validate-put-tfm-user-payload.test.ts index c2e2efa3dd..be51459b4f 100644 --- a/trade-finance-manager-api/src/v1/middleware/validate-put-tfm-user-payload.test.ts +++ b/trade-finance-manager-api/src/v1/middleware/validate-put-tfm-user-payload.test.ts @@ -31,7 +31,7 @@ describe('validatePutTfmUserPayload', () => { }, ]; - it.each(invalidPayloads)(`responds with a '${HttpStatusCode.BadRequest}' if $description`, ({ payload }) => { + it.each(invalidPayloads)(`should respond with a '${HttpStatusCode.BadRequest}' if $description`, ({ payload }) => { // Arrange const { req, res } = getHttpMocks(); const next = jest.fn(); @@ -47,7 +47,7 @@ describe('validatePutTfmUserPayload', () => { expect(next).not.toHaveBeenCalled(); }); - it("calls the 'next' function if the payload is valid", () => { + it("should call the 'next' function if the payload is valid", () => { // Arrange const { req, res } = getHttpMocks(); const next = jest.fn(); From 9d2856c974edbfd11ac302cebf33dda255bb4486 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Mon, 10 Feb 2025 17:51:55 +0000 Subject: [PATCH 112/133] feat(dtfs2-6892): review comments --- .../schemas/portal-amendment.schema.test.ts | 21 +- .../src/schemas/portal-amendment.schema.ts | 2 +- .../src/schemas/portal-amendment.test.ts | 260 ------------------ 3 files changed, 6 insertions(+), 277 deletions(-) delete mode 100644 libs/common/src/schemas/portal-amendment.test.ts diff --git a/libs/common/src/schemas/portal-amendment.schema.test.ts b/libs/common/src/schemas/portal-amendment.schema.test.ts index c8a5ab6b2c..8156d3cb83 100644 --- a/libs/common/src/schemas/portal-amendment.schema.test.ts +++ b/libs/common/src/schemas/portal-amendment.schema.test.ts @@ -21,27 +21,22 @@ describe('PORTAL_FACILITY_AMENDMENT_USER_VALUES', () => { { parameterPath: 'coverEndDate', type: 'UNIX_TIMESTAMP_SECONDS_SCHEMA', - options: { isOptional: true }, - }, - { - parameterPath: 'currentCoverEndDate', - type: 'UNIX_TIMESTAMP_SECONDS_SCHEMA', - options: { isOptional: true }, + options: { isOptional: true, isNullable: true }, }, { parameterPath: 'isUsingFacilityEndDate', type: 'boolean', - options: { isOptional: true }, + options: { isOptional: true, isNullable: true }, }, { parameterPath: 'facilityEndDate', type: 'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA', - options: { isOptional: true }, + options: { isOptional: true, isNullable: true }, }, { parameterPath: 'bankReviewDate', type: 'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA', - options: { isOptional: true }, + options: { isOptional: true, isNullable: true }, }, { parameterPath: 'changeFacilityValue', @@ -51,15 +46,9 @@ describe('PORTAL_FACILITY_AMENDMENT_USER_VALUES', () => { { parameterPath: 'value', type: 'number', - options: { isOptional: true }, - }, - { - parameterPath: 'currentValue', - type: 'number', - options: { isOptional: true }, + options: { isOptional: true, isNullable: true }, }, { parameterPath: 'currency', type: 'CURRENCY_SCHEMA', options: { isOptional: true } }, - { parameterPath: 'coveredPercentage', type: 'number', options: { isOptional: true } }, ], }); }); diff --git a/libs/common/src/schemas/portal-amendment.schema.ts b/libs/common/src/schemas/portal-amendment.schema.ts index 6c665e4f5e..76c705f8c6 100644 --- a/libs/common/src/schemas/portal-amendment.schema.ts +++ b/libs/common/src/schemas/portal-amendment.schema.ts @@ -11,7 +11,7 @@ import { CURRENCY_SCHEMA } from './currency.schema'; export const PORTAL_FACILITY_AMENDMENT_USER_VALUES = z .object({ changeCoverEndDate: z.boolean().optional(), - coverEndDate: z.preprocess((value) => (value instanceof Date ? getEpochMs(value) : value), z.number().nonnegative().nullable().optional()), + coverEndDate: z.preprocess((value) => (value instanceof Date ? getEpochMs(value) : value), z.number().int().nonnegative().nullable().optional()), isUsingFacilityEndDate: z.boolean().nullable().optional(), facilityEndDate: ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA.nullable().optional(), bankReviewDate: ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA.nullable().optional(), diff --git a/libs/common/src/schemas/portal-amendment.test.ts b/libs/common/src/schemas/portal-amendment.test.ts deleted file mode 100644 index b9604c5a91..0000000000 --- a/libs/common/src/schemas/portal-amendment.test.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { aPortalFacilityAmendmentUserValues } from '../test-helpers/mock-data-backend'; -import { AnyObject } from '../types'; -import { PORTAL_FACILITY_AMENDMENT_USER_VALUES } from './portal-amendment.schema'; -import { withSchemaTests } from './with-schema-tests'; - -const aValidPayload = () => JSON.parse(JSON.stringify(aPortalFacilityAmendmentUserValues())) as AnyObject; - -describe('PORTAL_FACILITY_AMENDMENT_USER_VALUES', () => { - withSchemaTests({ - successTestCases: getSuccessTestCases(), - failureTestCases: getFailureTestCases(), - schema: PORTAL_FACILITY_AMENDMENT_USER_VALUES, - }); - - function getSuccessTestCases() { - return [ - { - description: 'the payload is valid', - aTestCase: aValidPayload, - }, - ]; - } - - function getFailureTestCases() { - return [ - { - description: 'amendment is undefined', - aTestCase: () => undefined, - }, - { - description: 'amendment is null', - aTestCase: () => null, - }, - { - description: 'amendment is a number', - aTestCase: () => 1, - }, - { - description: 'amendment is a string', - aTestCase: () => 'not an object', - }, - { - description: 'object contains additional properties', - aTestCase: () => ({ - ...aValidPayload(), - extra: 'property', - }), - }, - { - description: 'changeCoverEndDate is a string', - aTestCase: () => ({ - ...aValidPayload(), - changeCoverEndDate: 'true', - }), - }, - { - description: 'coverEndDate is not an integer', - aTestCase: () => ({ - ...aValidPayload(), - coverEndDate: 'not an integer', - }), - }, - { - description: 'coverEndDate is negative', - aTestCase: () => ({ - ...aValidPayload(), - coverEndDate: -42, - }), - }, - { - description: 'currentCoverEndDate is not an integer', - aTestCase: () => ({ - ...aValidPayload(), - currentCoverEndDate: 'not an integer', - }), - }, - { - description: 'currentCoverEndDate is negative', - aTestCase: () => ({ - ...aValidPayload(), - currentCoverEndDate: -42, - }), - }, - { - description: 'isUsingFacilityEndDate is not a boolean', - aTestCase: () => ({ - ...aValidPayload(), - isUsingFacilityEndDate: 'not a boolean', - }), - }, - { - description: 'facilityEndDate is not a number', - aTestCase: () => ({ - ...aValidPayload(), - facilityEndDate: 'not a number', - }), - }, - { - description: 'facilityEndDate is negative', - aTestCase: () => ({ - ...aValidPayload(), - facilityEndDate: -23, - }), - }, - { - description: 'bankReviewDate is not a number', - aTestCase: () => ({ - ...aValidPayload(), - bankReviewDate: 'not a number', - }), - }, - { - description: 'bankReviewDate is negative', - aTestCase: () => ({ - ...aValidPayload(), - bankReviewDate: -23, - }), - }, - { - description: 'changeFacilityValue is not a boolean', - aTestCase: () => ({ - ...aValidPayload(), - changeFacilityValue: 'not a boolean', - }), - }, - { - description: 'value is not a number', - aTestCase: () => ({ - ...aValidPayload(), - value: 'not a number', - }), - }, - { - description: 'currentValue is not a number', - aTestCase: () => ({ - ...aValidPayload(), - currentValue: 'not a number', - }), - }, - { - description: 'currency is not a valid enum value', - aTestCase: () => ({ - ...aValidPayload(), - currency: 'invalid currency', - }), - }, - { - description: 'ukefExposure is not a number', - aTestCase: () => ({ - ...aValidPayload(), - ukefExposure: 'not a number', - }), - }, - { - description: 'coveredPercentage is not a number', - aTestCase: () => ({ - ...aValidPayload(), - coveredPercentage: 'not a number', - }), - }, - { - description: 'eligibilityCriteria contains a non numerical version value', - aTestCase: () => ({ - ...aValidPayload(), - eligibilityCriteria: { version: '1', criteria: [] }, - }), - }, - { - description: 'eligibilityCriteria is missing version', - aTestCase: () => ({ - ...aValidPayload(), - eligibilityCriteria: { criteria: [] }, - }), - }, - { - description: 'eligibilityCriteria is missing criteria', - aTestCase: () => ({ - ...aValidPayload(), - eligibilityCriteria: { version: 1 }, - }), - }, - { - description: 'criteria has a non numerical id', - aTestCase: () => ({ - ...aValidPayload(), - eligibilityCriteria: { version: 1, criteria: [{ id: '1', text: 'test-text', answer: true }] }, - }), - }, - { - description: 'criteria is missing id', - aTestCase: () => ({ - ...aValidPayload(), - eligibilityCriteria: { version: 1, criteria: [{ text: 'test-text', answer: true }] }, - }), - }, - { - description: 'criteria is missing text', - aTestCase: () => ({ - ...aValidPayload(), - eligibilityCriteria: { - version: 1, - criteria: [ - { id: 1, answer: true }, - { id: 2, answer: false, text: 'test-text' }, - ], - }, - }), - }, - { - description: 'criteria is missing answer', - aTestCase: () => ({ - ...aValidPayload(), - eligibilityCriteria: { version: 1, criteria: [{ id: 1, text: 'test-text' }] }, - }), - }, - { - description: 'criteria has a non boolean answer', - aTestCase: () => ({ - ...aValidPayload(), - eligibilityCriteria: { version: 1, criteria: [{ id: 1, text: 'test-text', answer: 'true' }] }, - }), - }, - { - description: 'criteria has a null answer', - aTestCase: () => ({ - ...aValidPayload(), - eligibilityCriteria: { version: 1, criteria: [{ id: 1, text: 'test-text', answer: null }] }, - }), - }, - ]; - } - - it('should parse `facilityEndDate` from ISO-8601 into a Date', () => { - const facilityEndDate = new Date(); - const amendment = { - facilityEndDate: JSON.parse(JSON.stringify(facilityEndDate)) as string, - }; - - // Act - const { success, data } = PORTAL_FACILITY_AMENDMENT_USER_VALUES.safeParse(amendment); - - // Assert - expect(success).toEqual(true); - expect(data).toEqual({ facilityEndDate }); - }); - - it('should parse `bankReviewDate` from ISO-8601 into a Date', () => { - const bankReviewDate = new Date(); - const amendment = { - bankReviewDate: JSON.parse(JSON.stringify(bankReviewDate)) as string, - }; - - // Act - const { success, data } = PORTAL_FACILITY_AMENDMENT_USER_VALUES.safeParse(amendment); - - // Assert - expect(success).toEqual(true); - expect(data).toEqual({ bankReviewDate }); - }); -}); From 08d6ce176b276ace1ef39de85219d8431ccd2635 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 12 Feb 2025 10:42:35 +0000 Subject: [PATCH 113/133] feat(dtfs2-6892): review comments --- .../test-cases-backend/with-api-error-tests.ts | 2 +- .../controllers/sso.controller.get-auth-code-url.test.ts | 4 ++-- .../sso.controller.handle-sso-redirect-form.test.ts | 9 +++++---- .../server/api.get-uk-bank-holidays.test.js | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts b/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts index 6e0cd054b3..b62b5defae 100644 --- a/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts +++ b/libs/common/src/test-helpers/test-cases-backend/with-api-error-tests.ts @@ -54,7 +54,7 @@ export const withApiErrorTests = ({ mockAnError, makeRequest, getRes, endpointEr }); describe('when a non-api error is thrown', () => { - it('should return a 500 with a generic error message', async () => { + it(`should return a ${HttpStatusCode.InternalServerError} with a generic error message`, async () => { // Arrange const errorMessage = 'a message that should not be exposed'; diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts index bae78e3047..bee1d203f6 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.get-auth-code-url.test.ts @@ -52,7 +52,7 @@ describe('SsoController', () => { expect(res._getJSONData()).toEqual(getAuthCodeUrlResponse); }); - it('should return a 200 status code on success', async () => { + it(`should return a ${HttpStatusCode.Ok} status code on success`, async () => { const { req, res } = getHttpMocks(aGetAuthCodeUrlParams()); const getAuthCodeUrlResponse = aGetAuthCodeUrlResponse(); @@ -91,7 +91,7 @@ describe('SsoController', () => { }); }); - it('should return a 500 status code on non api error', async () => { + it(`should return a ${HttpStatusCode.InternalServerError} status code on non api error`, async () => { const { req, res } = getHttpMocks(aGetAuthCodeUrlParams()); const error = new Error('Test error'); diff --git a/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts b/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts index b248606fc2..194c49f048 100644 --- a/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts +++ b/trade-finance-manager-api/src/v1/controllers/sso.controller.handle-sso-redirect-form.test.ts @@ -12,6 +12,7 @@ import { import { generateSystemAuditDetails, generateTfmAuditDetails } from '@ukef/dtfs2-common/change-stream'; import { ObjectId } from 'mongodb'; import { TFM_SESSION_USER_SCHEMA } from '@ukef/dtfs2-common/schemas'; +import { HttpStatusCode } from 'axios'; import { aHandleRedirectResponse, anUpsertTfmUserFromEntraIdUserResponse } from '../../../test-helpers'; import { EntraIdServiceMockBuilder, UserServiceMockBuilder } from '../__mocks__/builders'; import { EntraIdService, HandleRedirectResponse } from '../services/entra-id.service'; @@ -59,12 +60,12 @@ describe('SsoController', () => { })); }); - it('should return a 400 with error details', async () => { + it(`should return a ${HttpStatusCode.BadRequest} with error details`, async () => { // Act await ssoController.handleSsoRedirectForm(req, res); // Assert - expect(res._getStatusCode()).toEqual(400); + expect(res._getStatusCode()).toEqual(HttpStatusCode.BadRequest); expect(res._getData()).toEqual({ status: 400, message: "Failed to handle redirect form: Supplied auditDetails 'userType' must be 'system' (was 'tfm')", @@ -270,7 +271,7 @@ describe('SsoController', () => { saveUserLoginInformationMock.mockResolvedValue(undefined); }); - it('should return a 200 with the user, token, expires and successRedirect', async () => { + it(`should return a ${HttpStatusCode.Ok} with the user, token, expires and successRedirect`, async () => { // Arrange const expectedResponse = { user: TFM_SESSION_USER_SCHEMA.parse(upsertTfmUserFromEntraIdUserResponse), @@ -283,7 +284,7 @@ describe('SsoController', () => { await ssoController.handleSsoRedirectForm(req, res); // Assert - expect(res._getStatusCode()).toEqual(200); + expect(res._getStatusCode()).toEqual(HttpStatusCode.Ok); expect(res._getData()).toEqual(expectedResponse); }); }); diff --git a/trade-finance-manager-ui/server/api.get-uk-bank-holidays.test.js b/trade-finance-manager-ui/server/api.get-uk-bank-holidays.test.js index 034ed11c1f..4cf4c3d162 100644 --- a/trade-finance-manager-ui/server/api.get-uk-bank-holidays.test.js +++ b/trade-finance-manager-ui/server/api.get-uk-bank-holidays.test.js @@ -24,7 +24,7 @@ describe('getUkBankHolidays', () => { expect(response).toEqual(MOCK_BANK_HOLIDAYS); }); - it('should throw when the api TFM API request fails', async () => { + it('should throw when the TFM API request fails', async () => { // Arrange mockAxios.onGet().reply(404); From 694c80b8622db5d9db88e2ca5758b88b74de79f2 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 12 Feb 2025 13:12:01 +0000 Subject: [PATCH 114/133] feat(dtfs2-6892): fix issue due to unit test and e2e tests running on jsdom --- .../audit-database-record.schema.test.ts | 2 +- .../src/schemas/object-id.schema.test.ts | 6 +- .../custom-types-tests/index.ts | 3 + ...ect-id-or-object-id-string-schema.tests.ts | 4 +- .../with-object-id-schema.tests.ts | 4 +- .../with-object-id-string-schema.tests.ts | 4 +- .../schemas-backend/schema-tests/index.ts | 1 + ...with-audit-database-record-schema.tests.ts | 6 +- .../with-schema-validation.tests.ts | 139 ++++++++++++++++++ .../with-test-for-test-case.type.ts | 21 +++ .../with-tests-for-testcase.ts | 66 +++++++++ .../schemas/custom-types-tests/index.ts | 3 - .../schemas/schema-tests/index.ts | 1 - .../schemas/with-test-for-test-case.type.ts | 2 - .../schemas/with-tests-for-testcase.ts | 41 +----- 15 files changed, 244 insertions(+), 59 deletions(-) create mode 100644 libs/common/src/test-helpers/schemas-backend/custom-types-tests/index.ts rename libs/common/src/test-helpers/{schemas => schemas-backend}/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts (87%) rename libs/common/src/test-helpers/{schemas => schemas-backend}/custom-types-tests/with-object-id-schema.tests.ts (90%) rename libs/common/src/test-helpers/{schemas => schemas-backend}/custom-types-tests/with-object-id-string-schema.tests.ts (90%) create mode 100644 libs/common/src/test-helpers/schemas-backend/schema-tests/index.ts rename libs/common/src/test-helpers/{schemas => schemas-backend}/schema-tests/with-audit-database-record-schema.tests.ts (90%) create mode 100644 libs/common/src/test-helpers/schemas-backend/with-schema-validation.tests.ts create mode 100644 libs/common/src/test-helpers/schemas-backend/with-test-for-test-case.type.ts create mode 100644 libs/common/src/test-helpers/schemas-backend/with-tests-for-testcase.ts diff --git a/libs/common/src/schemas/audit-database-record.schema.test.ts b/libs/common/src/schemas/audit-database-record.schema.test.ts index 6606109292..fda3a52183 100644 --- a/libs/common/src/schemas/audit-database-record.schema.test.ts +++ b/libs/common/src/schemas/audit-database-record.schema.test.ts @@ -1,4 +1,4 @@ -import { withAuditDatabaseRecordSchemaTests } from '../test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests'; +import { withAuditDatabaseRecordSchemaTests } from '../test-helpers/schemas-backend/schema-tests/with-audit-database-record-schema.tests'; import { AUDIT_DATABASE_RECORD_SCHEMA } from './audit-database-record.schema'; describe('AUDIT_DATABASE_RECORD_SCHEMA', () => { diff --git a/libs/common/src/schemas/object-id.schema.test.ts b/libs/common/src/schemas/object-id.schema.test.ts index 05caaab941..cd66a85c94 100644 --- a/libs/common/src/schemas/object-id.schema.test.ts +++ b/libs/common/src/schemas/object-id.schema.test.ts @@ -1,7 +1,7 @@ import { OBJECT_ID_SCHEMA, OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA, OBJECT_ID_STRING_SCHEMA } from './object-id.schema'; -import { withObjectIdSchemaTests } from '../test-helpers/schemas/custom-types-tests/with-object-id-schema.tests'; -import { withObjectIdOrObjectIdStringSchemaTests } from '../test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests'; -import { withObjectIdStringSchemaTests } from '../test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests'; +import { withObjectIdSchemaTests } from '../test-helpers/schemas-backend/custom-types-tests/with-object-id-schema.tests'; +import { withObjectIdOrObjectIdStringSchemaTests } from '../test-helpers/schemas-backend/custom-types-tests/with-object-id-or-object-id-string-schema.tests'; +import { withObjectIdStringSchemaTests } from '../test-helpers/schemas-backend/custom-types-tests/with-object-id-string-schema.tests'; describe('OBJECT_ID_SCHEMA', () => { withObjectIdSchemaTests({ diff --git a/libs/common/src/test-helpers/schemas-backend/custom-types-tests/index.ts b/libs/common/src/test-helpers/schemas-backend/custom-types-tests/index.ts new file mode 100644 index 0000000000..d951bea62d --- /dev/null +++ b/libs/common/src/test-helpers/schemas-backend/custom-types-tests/index.ts @@ -0,0 +1,3 @@ +export * from './with-object-id-or-object-id-string-schema.tests'; +export * from './with-object-id-schema.tests'; +export * from './with-object-id-string-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts similarity index 87% rename from libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts rename to libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts index 39119f35cf..a77ae73cb8 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts @@ -1,7 +1,7 @@ import { ZodSchema } from 'zod'; import { ObjectId } from 'mongodb'; -import { WithSchemaTestParams } from '../with-schema-test.type'; -import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; +import { WithSchemaTestParams } from '../../schemas/with-schema-test.type'; +import { withDefaultOptionsTests } from '../../schemas/primitive-types-tests/with-default-options.tests'; export const withObjectIdOrObjectIdStringSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-schema.tests.ts b/libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-schema.tests.ts similarity index 90% rename from libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-schema.tests.ts rename to libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-schema.tests.ts index 0e63cdf140..c59828a8a8 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-schema.tests.ts @@ -1,7 +1,7 @@ import { ObjectId } from 'mongodb'; import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; -import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; +import { WithSchemaTestParams } from '../../schemas/with-schema-test.type'; +import { withDefaultOptionsTests } from '../../schemas/primitive-types-tests/with-default-options.tests'; export const withObjectIdSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-string-schema.tests.ts similarity index 90% rename from libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests.ts rename to libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-string-schema.tests.ts index ceeffc3208..0752cd59a5 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-string-schema.tests.ts @@ -1,7 +1,7 @@ import { ObjectId } from 'mongodb'; import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; -import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; +import { WithSchemaTestParams } from '../../schemas/with-schema-test.type'; +import { withDefaultOptionsTests } from '../../schemas/primitive-types-tests/with-default-options.tests'; export const withObjectIdStringSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas-backend/schema-tests/index.ts b/libs/common/src/test-helpers/schemas-backend/schema-tests/index.ts new file mode 100644 index 0000000000..dc650a70d9 --- /dev/null +++ b/libs/common/src/test-helpers/schemas-backend/schema-tests/index.ts @@ -0,0 +1 @@ +export * from './with-audit-database-record-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts b/libs/common/src/test-helpers/schemas-backend/schema-tests/with-audit-database-record-schema.tests.ts similarity index 90% rename from libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts rename to libs/common/src/test-helpers/schemas-backend/schema-tests/with-audit-database-record-schema.tests.ts index cb0f5a0463..e3db9cdc9f 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas-backend/schema-tests/with-audit-database-record-schema.tests.ts @@ -1,9 +1,9 @@ import { ZodSchema } from 'zod'; import { ObjectId } from 'mongodb'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../../schemas/with-schema-test.type'; import { generateTfmUserAuditDatabaseRecord } from '../../../change-stream'; -import { withSchemaValidationTests } from '../with-schema-validation.tests'; -import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; +import { withSchemaValidationTests } from '../../schemas/with-schema-validation.tests'; +import { withDefaultOptionsTests } from '../../schemas/primitive-types-tests/with-default-options.tests'; export const withAuditDatabaseRecordSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas-backend/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas-backend/with-schema-validation.tests.ts new file mode 100644 index 0000000000..3707a4bee9 --- /dev/null +++ b/libs/common/src/test-helpers/schemas-backend/with-schema-validation.tests.ts @@ -0,0 +1,139 @@ +/* eslint-disable no-param-reassign */ +import { z, ZodSchema } from 'zod'; +import { withTestsForTestcase } from './with-tests-for-testcase'; +import { TestCase } from './with-test-for-test-case.type'; + +/** + * Options that are specific to the schema as a whole, for instance, if the schema is a partial + */ +type SchemaTestOptions = { + isPartial?: boolean; + isStrict?: boolean; +}; + +/** + * Test cases with the path parameter, used to create the getTestObjectWithUpdatedParameter function + */ +export type TestCaseWithPathParameter = { + parameterPath: string; +} & TestCase; + +/** + * This function orchestrates a schema's test cases. + * It applies schema test options to all test cases, as well as adding and schema-specific tests as required. + * @param params.schema The schema to test + * @param params.schemaTestOptions Options that are specific to the schema as a whole, for instance, if the schema is a partial, or strict + * @param params.aValidPayload A function that returns a valid payload for the schema + * @param params.testCases Test cases to test + * @see doc\schemas.md for more information + * @example Schema test options + * ```ts + * const schemaTestOptions = { isPartial: true, isStrict: true } + * ``` + * @example A valid payload + * ```ts + * const aValidPayload = () => ({ age: 20, + * _id: new ObjectId(), + * sessionIdentifier: 'session-identifier', + * teams: [{ name: 'a-valid-team-name' }] + * }) + * ``` + * + * @example Test case: using a primitive type + * ```ts + * const testCases = [{ + * parameterPath: 'age', + * type: 'number', // with-number.tests will be used for age + * }] + * ``` + * + * @example Test case: using a custom type + * ```ts + * const testCases = [{ + * parameterPath: '_id', + * type: 'OBJECT_ID_SCHEMA', // with-object-id-schema.tests will be used for _id + * }] + * ``` + * + * @example Test case: using options that are available on all types + * ```ts + * const testCases = [{ + * parameterPath: 'sessionIdentifier', + * type: 'string', + * options: { isOptional: true }, + * }] + * ``` + * + * @example Test case: using options that are available on a certain type + * ```ts + * const testCases = [{ + * parameterPath: 'teams', + * type: 'Array', + * options: { + * // In this case, the type specific options allow us to + * //specify the type of each object on the array + * arrayTypeTestCase: { + * type: 'TfmTeamSchema', + * }, + * }, + * }] + * ``` + */ +export const withSchemaValidationTests = ({ + schema, + schemaTestOptions = {}, + aValidPayload, + testCases, +}: { + schema: Schema; + schemaTestOptions?: SchemaTestOptions; + testCases: TestCaseWithPathParameter[]; + aValidPayload: () => z.infer; +}) => { + const schemaTestOptionsDefaults: Partial = { isPartial: false, isStrict: false }; + + const mergedSchemaTestOptions = { + ...schemaTestOptionsDefaults, + ...schemaTestOptions, + }; + + if (mergedSchemaTestOptions.isStrict) { + describe('strict schema validation tests', () => { + it('should fail parsing if a parameter not in the schema exists', () => { + const { success } = schema.safeParse({ ...aValidPayload(), aFieldThatDoesNotBelong: 'a-value' }); + expect(success).toBe(false); + }); + }); + } + + testCases.forEach((testCase) => { + const { parameterPath } = testCase; + + // Turns parameter optional if the schema is a partial + if (mergedSchemaTestOptions.isPartial) { + if (!testCase.options) { + testCase.options = {}; + } + testCase.options.isOptional = true; + } + + const getTestObjectWithUpdatedParameter = + testCase.options?.overrideGetTestObjectWithUpdatedField !== undefined + ? testCase.options.overrideGetTestObjectWithUpdatedField + : (newValue: unknown): unknown => ({ ...aValidPayload(), [parameterPath]: newValue }); + + const getUpdatedParameterFromParsedTestObject = (parsedPayload: unknown) => { + const parsedPayloadAsRecord = parsedPayload as Record; + return parsedPayloadAsRecord[parameterPath]; + }; + + describe(`${parameterPath} parameter tests`, () => { + withTestsForTestcase({ + schema, + testCase, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, + }); + }); + }); +}; diff --git a/libs/common/src/test-helpers/schemas-backend/with-test-for-test-case.type.ts b/libs/common/src/test-helpers/schemas-backend/with-test-for-test-case.type.ts new file mode 100644 index 0000000000..070fd9c1eb --- /dev/null +++ b/libs/common/src/test-helpers/schemas-backend/with-test-for-test-case.type.ts @@ -0,0 +1,21 @@ +import { DefaultOptions } from '../schemas/primitive-types-tests'; + +/** + * All test case types that have been tests implemented + */ +export type TestCaseTypes = 'OBJECT_ID_SCHEMA' | 'OBJECT_ID_STRING_SCHEMA' | 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA' | 'AUDIT_DATABASE_RECORD_SCHEMA'; + +/** + * The test case to be tested, including the type and any options that are required + * + * Allows for the passing in of additional options if required for the specific test case + */ +type TestCaseWithType = { + type: Type; +} & (AdditionalOptions extends false ? { options?: Partial } : { options: AdditionalOptions & Partial }); + +export type TestCase = + | TestCaseWithType<'OBJECT_ID_SCHEMA'> + | TestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> + | TestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> + | TestCaseWithType<'AUDIT_DATABASE_RECORD_SCHEMA'>; diff --git a/libs/common/src/test-helpers/schemas-backend/with-tests-for-testcase.ts b/libs/common/src/test-helpers/schemas-backend/with-tests-for-testcase.ts new file mode 100644 index 0000000000..7806a501f1 --- /dev/null +++ b/libs/common/src/test-helpers/schemas-backend/with-tests-for-testcase.ts @@ -0,0 +1,66 @@ +import { ZodSchema } from 'zod'; +import { TestCase } from './with-test-for-test-case.type'; +import { withObjectIdSchemaTests } from './custom-types-tests/with-object-id-schema.tests'; +import { withObjectIdStringSchemaTests } from './custom-types-tests/with-object-id-string-schema.tests'; +import { withObjectIdOrObjectIdStringSchemaTests } from './custom-types-tests/with-object-id-or-object-id-string-schema.tests'; +import { withAuditDatabaseRecordSchemaTests } from './schema-tests'; + +/** + * Gets tests for a test case, using the test case type to determine which tests to run + * + * These tests are all available tests that can be easily used to test a parameter, and should be extended + */ +export const withTestsForTestcase = ({ + schema, + testCase, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, +}: { + schema: Schema; + testCase: TestCase; + getTestObjectWithUpdatedParameter: (newValue: unknown) => unknown; + getUpdatedParameterFromParsedTestObject: (parsedTestObject: unknown) => unknown; +}) => { + const { type, options } = testCase; + + switch (type) { + case 'OBJECT_ID_SCHEMA': + withObjectIdSchemaTests({ + schema, + options, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, + }); + break; + + case 'OBJECT_ID_STRING_SCHEMA': + withObjectIdStringSchemaTests({ + schema, + options, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, + }); + break; + + case 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA': + withObjectIdOrObjectIdStringSchemaTests({ + schema, + options, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, + }); + break; + + case 'AUDIT_DATABASE_RECORD_SCHEMA': + withAuditDatabaseRecordSchemaTests({ + schema, + options, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, + }); + break; + + default: + throw Error(`There are no existing test cases for the type ${type}`); + } +}; diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts index 4b63b3fc74..483b2006e7 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts @@ -3,9 +3,6 @@ * ie these schemas represent a single field, not a whole object */ export * from './with-iso-date-time-stamp-schema.tests'; -export * from './with-object-id-or-object-id-string-schema.tests'; -export * from './with-object-id-schema.tests'; -export * from './with-object-id-string-schema.tests'; export * from './with-unix-timestamp-schema.tests'; export * from './with-unix-timestamp-milliseconds-schema.tests'; export * from './with-unix-timestamp-seconds-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/schema-tests/index.ts b/libs/common/src/test-helpers/schemas/schema-tests/index.ts index 883b12a891..2c033862f1 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/index.ts @@ -2,6 +2,5 @@ * These tests are for schemas that define specific object shapes, not for schemas that define specific types * ie these schemas represent a whole object, not a single field */ -export * from './with-audit-database-record-schema.tests'; export * from './with-entra-id-user-schema.tests'; export * from './with-upsert-tfm-user-request.schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts b/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts index 85b69618a9..9c5da49ca5 100644 --- a/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts +++ b/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts @@ -16,7 +16,6 @@ export type TestCaseTypes = | 'OBJECT_ID_STRING_SCHEMA' | 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA' | 'ISO_DATE_TIME_STAMP_SCHEMA' - | 'AUDIT_DATABASE_RECORD_SCHEMA' | 'ENTRA_ID_USER_SCHEMA' | 'CURRENCY_SCHEMA' | 'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA' @@ -44,7 +43,6 @@ export type TestCase = | TestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> | TestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> | TestCaseWithType<'ISO_DATE_TIME_STAMP_SCHEMA'> - | TestCaseWithType<'AUDIT_DATABASE_RECORD_SCHEMA'> | TestCaseWithType<'ENTRA_ID_USER_SCHEMA'> | TestCaseWithType<'CURRENCY_SCHEMA'> | TestCaseWithType<'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA'> diff --git a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts index cc95308a33..080d296ab0 100644 --- a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts +++ b/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts @@ -3,15 +3,12 @@ import { withUnixTimestampMillisecondsSchemaTests, withUnixTimestampSecondsSchemaTests, withUnixTimestampSchemaTests, - withObjectIdSchemaTests, - withObjectIdStringSchemaTests, - withObjectIdOrObjectIdStringSchemaTests, withIsoDateTimeStampSchemaTests, withTfmTeamSchemaTests, withCurrencySchemaTests, } from './custom-types-tests'; import { withStringTests, withNumberTests, withBooleanTests, withArrayTests } from './primitive-types-tests'; -import { withAuditDatabaseRecordSchemaTests, withEntraIdUserSchemaTests, withUpsertTfmUserRequestSchemaTests } from './schema-tests'; +import { withEntraIdUserSchemaTests, withUpsertTfmUserRequestSchemaTests } from './schema-tests'; import { TestCase } from './with-test-for-test-case.type'; import { withIsoDateTimeStampToDateSchemaTests } from './transformation-tests'; @@ -106,33 +103,6 @@ export const withTestsForTestcase = ({ }); break; - case 'OBJECT_ID_SCHEMA': - withObjectIdSchemaTests({ - schema, - options, - getTestObjectWithUpdatedParameter, - getUpdatedParameterFromParsedTestObject, - }); - break; - - case 'OBJECT_ID_STRING_SCHEMA': - withObjectIdStringSchemaTests({ - schema, - options, - getTestObjectWithUpdatedParameter, - getUpdatedParameterFromParsedTestObject, - }); - break; - - case 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA': - withObjectIdOrObjectIdStringSchemaTests({ - schema, - options, - getTestObjectWithUpdatedParameter, - getUpdatedParameterFromParsedTestObject, - }); - break; - case 'ISO_DATE_TIME_STAMP_SCHEMA': withIsoDateTimeStampSchemaTests({ schema, @@ -142,15 +112,6 @@ export const withTestsForTestcase = ({ }); break; - case 'AUDIT_DATABASE_RECORD_SCHEMA': - withAuditDatabaseRecordSchemaTests({ - schema, - options, - getTestObjectWithUpdatedParameter, - getUpdatedParameterFromParsedTestObject, - }); - break; - case 'ENTRA_ID_USER_SCHEMA': withEntraIdUserSchemaTests({ schema, From 8552633da2338d1707f567a7c21722de7d516dc4 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 12 Feb 2025 14:47:16 +0000 Subject: [PATCH 115/133] feat(dtfs2-6892): fix issue due to unit test and e2e tests running on jsdom --- .../audit-database-record.schema.test.ts | 2 +- .../src/schemas/object-id.schema.test.ts | 8 +- .../with-schema-validation.tests.ts | 139 ------------------ .../with-test-for-test-case.type.ts | 21 --- .../backend-custom-types-tests}/index.ts | 0 ...ect-id-or-object-id-string-schema.tests.ts | 4 +- .../with-object-id-schema.tests.ts | 4 +- .../with-object-id-string-schema.tests.ts | 4 +- .../backend-schema-tests}/index.ts | 0 ...with-audit-database-record-schema.tests.ts | 11 +- .../backend-test-cases/backend-test-cases.ts | 9 ++ .../with-tests-for-backend-testcase.ts} | 33 ++--- .../with-currency-schema.tests.ts | 2 +- .../with-iso-date-time-stamp-schema.tests.ts | 2 +- .../with-tfm-team-schema.tests.ts | 2 +- ...nix-timestamp-milliseconds-schema.tests.ts | 2 +- .../with-unix-timestamp-schema.tests.ts | 2 +- ...ith-unix-timestamp-seconds-schema.tests.ts | 2 +- .../primitive-types-tests/with-array.tests.ts | 6 +- .../with-boolean.tests.ts | 2 +- .../with-default-options.tests.ts | 2 +- .../with-number.tests.ts | 2 +- .../with-string.tests.ts | 2 +- .../with-entra-id-user-schema.tests.ts | 2 +- ...th-upsert-tfm-user-request.schema.tests.ts | 2 +- .../schemas/test-cases/test-case.ts | 17 +++ .../{ => tests}/with-tests-for-testcase.ts | 24 +-- ...so-date-time-stamp-to-date.schema.tests.ts | 2 +- .../schemas/types/schema-test-options.type.ts | 7 + .../test-case-with-path-parameter.type.ts | 8 + .../schemas/types/test-case-with-type.type.ts | 10 ++ .../{ => types}/with-schema-test.type.ts | 2 +- .../schemas/types/with-tests-for-test-case.ts | 6 + .../schemas/with-schema-validation.tests.ts | 30 ++-- .../schemas/with-test-for-test-case.type.ts | 49 ------ 35 files changed, 127 insertions(+), 293 deletions(-) delete mode 100644 libs/common/src/test-helpers/schemas-backend/with-schema-validation.tests.ts delete mode 100644 libs/common/src/test-helpers/schemas-backend/with-test-for-test-case.type.ts rename libs/common/src/test-helpers/{schemas-backend/custom-types-tests => schemas/backend-custom-types-tests}/index.ts (100%) rename libs/common/src/test-helpers/{schemas-backend/custom-types-tests => schemas/backend-custom-types-tests}/with-object-id-or-object-id-string-schema.tests.ts (87%) rename libs/common/src/test-helpers/{schemas-backend/custom-types-tests => schemas/backend-custom-types-tests}/with-object-id-schema.tests.ts (90%) rename libs/common/src/test-helpers/{schemas-backend/custom-types-tests => schemas/backend-custom-types-tests}/with-object-id-string-schema.tests.ts (90%) rename libs/common/src/test-helpers/{schemas-backend/schema-tests => schemas/backend-schema-tests}/index.ts (100%) rename libs/common/src/test-helpers/{schemas-backend/schema-tests => schemas/backend-schema-tests}/with-audit-database-record-schema.tests.ts (83%) create mode 100644 libs/common/src/test-helpers/schemas/backend-test-cases/backend-test-cases.ts rename libs/common/src/test-helpers/{schemas-backend/with-tests-for-testcase.ts => schemas/backend-tests/with-tests-for-backend-testcase.ts} (50%) create mode 100644 libs/common/src/test-helpers/schemas/test-cases/test-case.ts rename libs/common/src/test-helpers/schemas/{ => tests}/with-tests-for-testcase.ts (85%) create mode 100644 libs/common/src/test-helpers/schemas/types/schema-test-options.type.ts create mode 100644 libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts create mode 100644 libs/common/src/test-helpers/schemas/types/test-case-with-type.type.ts rename libs/common/src/test-helpers/schemas/{ => types}/with-schema-test.type.ts (83%) create mode 100644 libs/common/src/test-helpers/schemas/types/with-tests-for-test-case.ts delete mode 100644 libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts diff --git a/libs/common/src/schemas/audit-database-record.schema.test.ts b/libs/common/src/schemas/audit-database-record.schema.test.ts index fda3a52183..cd1cf9cf84 100644 --- a/libs/common/src/schemas/audit-database-record.schema.test.ts +++ b/libs/common/src/schemas/audit-database-record.schema.test.ts @@ -1,4 +1,4 @@ -import { withAuditDatabaseRecordSchemaTests } from '../test-helpers/schemas-backend/schema-tests/with-audit-database-record-schema.tests'; +import { withAuditDatabaseRecordSchemaTests } from '../test-helpers/schemas/backend-schema-tests'; import { AUDIT_DATABASE_RECORD_SCHEMA } from './audit-database-record.schema'; describe('AUDIT_DATABASE_RECORD_SCHEMA', () => { diff --git a/libs/common/src/schemas/object-id.schema.test.ts b/libs/common/src/schemas/object-id.schema.test.ts index cd66a85c94..ce706c9f4a 100644 --- a/libs/common/src/schemas/object-id.schema.test.ts +++ b/libs/common/src/schemas/object-id.schema.test.ts @@ -1,7 +1,9 @@ +import { + withObjectIdOrObjectIdStringSchemaTests, + withObjectIdSchemaTests, + withObjectIdStringSchemaTests, +} from '../test-helpers/schemas/backend-custom-types-tests'; import { OBJECT_ID_SCHEMA, OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA, OBJECT_ID_STRING_SCHEMA } from './object-id.schema'; -import { withObjectIdSchemaTests } from '../test-helpers/schemas-backend/custom-types-tests/with-object-id-schema.tests'; -import { withObjectIdOrObjectIdStringSchemaTests } from '../test-helpers/schemas-backend/custom-types-tests/with-object-id-or-object-id-string-schema.tests'; -import { withObjectIdStringSchemaTests } from '../test-helpers/schemas-backend/custom-types-tests/with-object-id-string-schema.tests'; describe('OBJECT_ID_SCHEMA', () => { withObjectIdSchemaTests({ diff --git a/libs/common/src/test-helpers/schemas-backend/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas-backend/with-schema-validation.tests.ts deleted file mode 100644 index 3707a4bee9..0000000000 --- a/libs/common/src/test-helpers/schemas-backend/with-schema-validation.tests.ts +++ /dev/null @@ -1,139 +0,0 @@ -/* eslint-disable no-param-reassign */ -import { z, ZodSchema } from 'zod'; -import { withTestsForTestcase } from './with-tests-for-testcase'; -import { TestCase } from './with-test-for-test-case.type'; - -/** - * Options that are specific to the schema as a whole, for instance, if the schema is a partial - */ -type SchemaTestOptions = { - isPartial?: boolean; - isStrict?: boolean; -}; - -/** - * Test cases with the path parameter, used to create the getTestObjectWithUpdatedParameter function - */ -export type TestCaseWithPathParameter = { - parameterPath: string; -} & TestCase; - -/** - * This function orchestrates a schema's test cases. - * It applies schema test options to all test cases, as well as adding and schema-specific tests as required. - * @param params.schema The schema to test - * @param params.schemaTestOptions Options that are specific to the schema as a whole, for instance, if the schema is a partial, or strict - * @param params.aValidPayload A function that returns a valid payload for the schema - * @param params.testCases Test cases to test - * @see doc\schemas.md for more information - * @example Schema test options - * ```ts - * const schemaTestOptions = { isPartial: true, isStrict: true } - * ``` - * @example A valid payload - * ```ts - * const aValidPayload = () => ({ age: 20, - * _id: new ObjectId(), - * sessionIdentifier: 'session-identifier', - * teams: [{ name: 'a-valid-team-name' }] - * }) - * ``` - * - * @example Test case: using a primitive type - * ```ts - * const testCases = [{ - * parameterPath: 'age', - * type: 'number', // with-number.tests will be used for age - * }] - * ``` - * - * @example Test case: using a custom type - * ```ts - * const testCases = [{ - * parameterPath: '_id', - * type: 'OBJECT_ID_SCHEMA', // with-object-id-schema.tests will be used for _id - * }] - * ``` - * - * @example Test case: using options that are available on all types - * ```ts - * const testCases = [{ - * parameterPath: 'sessionIdentifier', - * type: 'string', - * options: { isOptional: true }, - * }] - * ``` - * - * @example Test case: using options that are available on a certain type - * ```ts - * const testCases = [{ - * parameterPath: 'teams', - * type: 'Array', - * options: { - * // In this case, the type specific options allow us to - * //specify the type of each object on the array - * arrayTypeTestCase: { - * type: 'TfmTeamSchema', - * }, - * }, - * }] - * ``` - */ -export const withSchemaValidationTests = ({ - schema, - schemaTestOptions = {}, - aValidPayload, - testCases, -}: { - schema: Schema; - schemaTestOptions?: SchemaTestOptions; - testCases: TestCaseWithPathParameter[]; - aValidPayload: () => z.infer; -}) => { - const schemaTestOptionsDefaults: Partial = { isPartial: false, isStrict: false }; - - const mergedSchemaTestOptions = { - ...schemaTestOptionsDefaults, - ...schemaTestOptions, - }; - - if (mergedSchemaTestOptions.isStrict) { - describe('strict schema validation tests', () => { - it('should fail parsing if a parameter not in the schema exists', () => { - const { success } = schema.safeParse({ ...aValidPayload(), aFieldThatDoesNotBelong: 'a-value' }); - expect(success).toBe(false); - }); - }); - } - - testCases.forEach((testCase) => { - const { parameterPath } = testCase; - - // Turns parameter optional if the schema is a partial - if (mergedSchemaTestOptions.isPartial) { - if (!testCase.options) { - testCase.options = {}; - } - testCase.options.isOptional = true; - } - - const getTestObjectWithUpdatedParameter = - testCase.options?.overrideGetTestObjectWithUpdatedField !== undefined - ? testCase.options.overrideGetTestObjectWithUpdatedField - : (newValue: unknown): unknown => ({ ...aValidPayload(), [parameterPath]: newValue }); - - const getUpdatedParameterFromParsedTestObject = (parsedPayload: unknown) => { - const parsedPayloadAsRecord = parsedPayload as Record; - return parsedPayloadAsRecord[parameterPath]; - }; - - describe(`${parameterPath} parameter tests`, () => { - withTestsForTestcase({ - schema, - testCase, - getTestObjectWithUpdatedParameter, - getUpdatedParameterFromParsedTestObject, - }); - }); - }); -}; diff --git a/libs/common/src/test-helpers/schemas-backend/with-test-for-test-case.type.ts b/libs/common/src/test-helpers/schemas-backend/with-test-for-test-case.type.ts deleted file mode 100644 index 070fd9c1eb..0000000000 --- a/libs/common/src/test-helpers/schemas-backend/with-test-for-test-case.type.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { DefaultOptions } from '../schemas/primitive-types-tests'; - -/** - * All test case types that have been tests implemented - */ -export type TestCaseTypes = 'OBJECT_ID_SCHEMA' | 'OBJECT_ID_STRING_SCHEMA' | 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA' | 'AUDIT_DATABASE_RECORD_SCHEMA'; - -/** - * The test case to be tested, including the type and any options that are required - * - * Allows for the passing in of additional options if required for the specific test case - */ -type TestCaseWithType = { - type: Type; -} & (AdditionalOptions extends false ? { options?: Partial } : { options: AdditionalOptions & Partial }); - -export type TestCase = - | TestCaseWithType<'OBJECT_ID_SCHEMA'> - | TestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> - | TestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> - | TestCaseWithType<'AUDIT_DATABASE_RECORD_SCHEMA'>; diff --git a/libs/common/src/test-helpers/schemas-backend/custom-types-tests/index.ts b/libs/common/src/test-helpers/schemas/backend-custom-types-tests/index.ts similarity index 100% rename from libs/common/src/test-helpers/schemas-backend/custom-types-tests/index.ts rename to libs/common/src/test-helpers/schemas/backend-custom-types-tests/index.ts diff --git a/libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts similarity index 87% rename from libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts rename to libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts index a77ae73cb8..e442e105f3 100644 --- a/libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts @@ -1,7 +1,7 @@ import { ZodSchema } from 'zod'; import { ObjectId } from 'mongodb'; -import { WithSchemaTestParams } from '../../schemas/with-schema-test.type'; -import { withDefaultOptionsTests } from '../../schemas/primitive-types-tests/with-default-options.tests'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; +import { withDefaultOptionsTests } from '../primitive-types-tests'; export const withObjectIdOrObjectIdStringSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-schema.tests.ts b/libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-schema.tests.ts similarity index 90% rename from libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-schema.tests.ts rename to libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-schema.tests.ts index c59828a8a8..9b9aa97ecd 100644 --- a/libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-schema.tests.ts @@ -1,7 +1,7 @@ import { ObjectId } from 'mongodb'; import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../../schemas/with-schema-test.type'; -import { withDefaultOptionsTests } from '../../schemas/primitive-types-tests/with-default-options.tests'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; +import { withDefaultOptionsTests } from '../primitive-types-tests'; export const withObjectIdSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-string-schema.tests.ts similarity index 90% rename from libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-string-schema.tests.ts rename to libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-string-schema.tests.ts index 0752cd59a5..9d716aa5ab 100644 --- a/libs/common/src/test-helpers/schemas-backend/custom-types-tests/with-object-id-string-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-string-schema.tests.ts @@ -1,7 +1,7 @@ import { ObjectId } from 'mongodb'; import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../../schemas/with-schema-test.type'; -import { withDefaultOptionsTests } from '../../schemas/primitive-types-tests/with-default-options.tests'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; +import { withDefaultOptionsTests } from '../primitive-types-tests'; export const withObjectIdStringSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas-backend/schema-tests/index.ts b/libs/common/src/test-helpers/schemas/backend-schema-tests/index.ts similarity index 100% rename from libs/common/src/test-helpers/schemas-backend/schema-tests/index.ts rename to libs/common/src/test-helpers/schemas/backend-schema-tests/index.ts diff --git a/libs/common/src/test-helpers/schemas-backend/schema-tests/with-audit-database-record-schema.tests.ts b/libs/common/src/test-helpers/schemas/backend-schema-tests/with-audit-database-record-schema.tests.ts similarity index 83% rename from libs/common/src/test-helpers/schemas-backend/schema-tests/with-audit-database-record-schema.tests.ts rename to libs/common/src/test-helpers/schemas/backend-schema-tests/with-audit-database-record-schema.tests.ts index e3db9cdc9f..525f13b384 100644 --- a/libs/common/src/test-helpers/schemas-backend/schema-tests/with-audit-database-record-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/backend-schema-tests/with-audit-database-record-schema.tests.ts @@ -1,9 +1,11 @@ import { ZodSchema } from 'zod'; import { ObjectId } from 'mongodb'; -import { WithSchemaTestParams } from '../../schemas/with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { generateTfmUserAuditDatabaseRecord } from '../../../change-stream'; -import { withSchemaValidationTests } from '../../schemas/with-schema-validation.tests'; -import { withDefaultOptionsTests } from '../../schemas/primitive-types-tests/with-default-options.tests'; +import { withDefaultOptionsTests } from '../primitive-types-tests'; +import { withSchemaValidationTests } from '../with-schema-validation.tests'; +import { withTestsForBackendTestcase } from '../backend-tests/with-tests-for-backend-testcase'; +import { BackendTestCase } from '../backend-test-cases/backend-test-cases'; export const withAuditDatabaseRecordSchemaTests = ({ schema, @@ -19,7 +21,8 @@ export const withAuditDatabaseRecordSchemaTests = ({ options, }); - withSchemaValidationTests({ + withSchemaValidationTests({ + withTestsForTestCases: withTestsForBackendTestcase, schema, aValidPayload: aValidAuditRecord, testCases: [ diff --git a/libs/common/src/test-helpers/schemas/backend-test-cases/backend-test-cases.ts b/libs/common/src/test-helpers/schemas/backend-test-cases/backend-test-cases.ts new file mode 100644 index 0000000000..dca6fd0ff7 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/backend-test-cases/backend-test-cases.ts @@ -0,0 +1,9 @@ +import { TestCase } from '../test-cases/test-case'; +import { TestCaseWithType } from '../types/test-case-with-type.type'; + +export type BackendTestCase = + | TestCase + | TestCaseWithType<'OBJECT_ID_SCHEMA'> + | TestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> + | TestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> + | TestCaseWithType<'AUDIT_DATABASE_RECORD_SCHEMA'>; diff --git a/libs/common/src/test-helpers/schemas-backend/with-tests-for-testcase.ts b/libs/common/src/test-helpers/schemas/backend-tests/with-tests-for-backend-testcase.ts similarity index 50% rename from libs/common/src/test-helpers/schemas-backend/with-tests-for-testcase.ts rename to libs/common/src/test-helpers/schemas/backend-tests/with-tests-for-backend-testcase.ts index 7806a501f1..5141951ca3 100644 --- a/libs/common/src/test-helpers/schemas-backend/with-tests-for-testcase.ts +++ b/libs/common/src/test-helpers/schemas/backend-tests/with-tests-for-backend-testcase.ts @@ -1,26 +1,19 @@ import { ZodSchema } from 'zod'; -import { TestCase } from './with-test-for-test-case.type'; -import { withObjectIdSchemaTests } from './custom-types-tests/with-object-id-schema.tests'; -import { withObjectIdStringSchemaTests } from './custom-types-tests/with-object-id-string-schema.tests'; -import { withObjectIdOrObjectIdStringSchemaTests } from './custom-types-tests/with-object-id-or-object-id-string-schema.tests'; -import { withAuditDatabaseRecordSchemaTests } from './schema-tests'; +import { BackendTestCase } from '../backend-test-cases/backend-test-cases'; +import { withObjectIdSchemaTests } from '../backend-custom-types-tests/with-object-id-schema.tests'; +import { withObjectIdStringSchemaTests } from '../backend-custom-types-tests/with-object-id-string-schema.tests'; +import { withObjectIdOrObjectIdStringSchemaTests } from '../backend-custom-types-tests/with-object-id-or-object-id-string-schema.tests'; +import { withAuditDatabaseRecordSchemaTests } from '../backend-schema-tests'; +import { WithTestsForTestCaseProps } from '../types/with-tests-for-test-case'; +import { withTestsForTestcase } from '../tests/with-tests-for-testcase'; /** * Gets tests for a test case, using the test case type to determine which tests to run * * These tests are all available tests that can be easily used to test a parameter, and should be extended */ -export const withTestsForTestcase = ({ - schema, - testCase, - getTestObjectWithUpdatedParameter, - getUpdatedParameterFromParsedTestObject, -}: { - schema: Schema; - testCase: TestCase; - getTestObjectWithUpdatedParameter: (newValue: unknown) => unknown; - getUpdatedParameterFromParsedTestObject: (parsedTestObject: unknown) => unknown; -}) => { +export const withTestsForBackendTestcase = (props: WithTestsForTestCaseProps): void => { + const { schema, testCase, getTestObjectWithUpdatedParameter, getUpdatedParameterFromParsedTestObject } = props; const { type, options } = testCase; switch (type) { @@ -61,6 +54,12 @@ export const withTestsForTestcase = ({ break; default: - throw Error(`There are no existing test cases for the type ${type}`); + // We fall through to the normal tests by default, which throws an error if the type is not found + withTestsForTestcase({ + schema, + testCase, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, + }); } }; diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-currency-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-currency-schema.tests.ts index 1fa351be28..78e11111ec 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-currency-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-currency-schema.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; import { CURRENCY } from '../../../constants'; diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-iso-date-time-stamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-iso-date-time-stamp-schema.tests.ts index 3426dcd547..aee72e8424 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-iso-date-time-stamp-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-iso-date-time-stamp-schema.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; export const withIsoDateTimeStampSchemaTests = ({ diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-tfm-team-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-tfm-team-schema.tests.ts index 95845cb064..50af5ce7ee 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-tfm-team-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-tfm-team-schema.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { TEAM_IDS } from '../../../constants'; import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-milliseconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-milliseconds-schema.tests.ts index 1c63331469..412a34ec19 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-milliseconds-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-milliseconds-schema.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; import { withNumberTests } from '../primitive-types-tests'; diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts index 55f4b731e3..5770927bf1 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-schema.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; import { withNumberTests } from '../primitive-types-tests'; diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts index d663cbc689..40a85b1fa3 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/with-unix-timestamp-seconds-schema.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; import { withNumberTests } from '../primitive-types-tests'; diff --git a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-array.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-array.tests.ts index 1f428bcfd8..0c7030b33a 100644 --- a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-array.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-array.tests.ts @@ -1,8 +1,8 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; -import { withTestsForTestcase } from '../with-tests-for-testcase'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; +import { withTestsForTestcase } from '../tests/with-tests-for-testcase'; import { withDefaultOptionsTests } from './with-default-options.tests'; -import { TestCase } from '../with-test-for-test-case.type'; +import { TestCase } from '../test-cases/test-case'; export type WithArrayTestsOptions = { arrayTypeTestCase: TestCase; diff --git a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-boolean.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-boolean.tests.ts index 1a6e452f77..953780466f 100644 --- a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-boolean.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-boolean.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { withDefaultOptionsTests } from './with-default-options.tests'; export const withBooleanTests = ({ diff --git a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-default-options.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-default-options.tests.ts index 78496aff14..66d21e7acb 100644 --- a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-default-options.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-default-options.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; /** * A list of default options diff --git a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-number.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-number.tests.ts index 3834c25acf..bdb8bc060b 100644 --- a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-number.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-number.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { withDefaultOptionsTests } from './with-default-options.tests'; export const withNumberTests = ({ diff --git a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-string.tests.ts b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-string.tests.ts index 403299da05..1c0d66ab3a 100644 --- a/libs/common/src/test-helpers/schemas/primitive-types-tests/with-string.tests.ts +++ b/libs/common/src/test-helpers/schemas/primitive-types-tests/with-string.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { withDefaultOptionsTests } from './with-default-options.tests'; export const withStringTests = ({ diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts index ab54824dbb..13f7ba4b7a 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-entra-id-user-schema.tests.ts @@ -2,7 +2,7 @@ import { ZodSchema } from 'zod'; import { anEntraIdUser } from '../../mock-data'; import { withDefaultOptionsTests } from '../primitive-types-tests'; import { withSchemaValidationTests } from '../with-schema-validation.tests'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; export const withEntraIdUserSchemaTests = ({ schema, diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-upsert-tfm-user-request.schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-upsert-tfm-user-request.schema.tests.ts index 9b578b8d42..be1f1717d0 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-upsert-tfm-user-request.schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-upsert-tfm-user-request.schema.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { withDefaultOptionsTests } from '../primitive-types-tests'; import { withSchemaValidationTests } from '../with-schema-validation.tests'; import { aUpsertTfmUserRequest } from '../../mock-data'; diff --git a/libs/common/src/test-helpers/schemas/test-cases/test-case.ts b/libs/common/src/test-helpers/schemas/test-cases/test-case.ts new file mode 100644 index 0000000000..a783dcf3ed --- /dev/null +++ b/libs/common/src/test-helpers/schemas/test-cases/test-case.ts @@ -0,0 +1,17 @@ +import { WithArrayTestsOptions } from '../primitive-types-tests'; +import { TestCaseWithType } from '../types/test-case-with-type.type'; + +export type TestCase = + | TestCaseWithType<'string'> + | TestCaseWithType<'number'> + | TestCaseWithType<'boolean'> + | TestCaseWithType<'Array', WithArrayTestsOptions> + | TestCaseWithType<'TfmTeamSchema'> + | TestCaseWithType<'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA'> + | TestCaseWithType<'UNIX_TIMESTAMP_SECONDS_SCHEMA'> + | TestCaseWithType<'UNIX_TIMESTAMP_SCHEMA'> + | TestCaseWithType<'ISO_DATE_TIME_STAMP_SCHEMA'> + | TestCaseWithType<'ENTRA_ID_USER_SCHEMA'> + | TestCaseWithType<'CURRENCY_SCHEMA'> + | TestCaseWithType<'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA'> + | TestCaseWithType<'UPSERT_TFM_USER_REQUEST_SCHEMA'>; diff --git a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts b/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts similarity index 85% rename from libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts rename to libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts index 080d296ab0..6d98217d30 100644 --- a/libs/common/src/test-helpers/schemas/with-tests-for-testcase.ts +++ b/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts @@ -6,28 +6,20 @@ import { withIsoDateTimeStampSchemaTests, withTfmTeamSchemaTests, withCurrencySchemaTests, -} from './custom-types-tests'; -import { withStringTests, withNumberTests, withBooleanTests, withArrayTests } from './primitive-types-tests'; -import { withEntraIdUserSchemaTests, withUpsertTfmUserRequestSchemaTests } from './schema-tests'; -import { TestCase } from './with-test-for-test-case.type'; -import { withIsoDateTimeStampToDateSchemaTests } from './transformation-tests'; +} from '../custom-types-tests'; +import { withStringTests, withNumberTests, withBooleanTests, withArrayTests } from '../primitive-types-tests'; +import { withEntraIdUserSchemaTests, withUpsertTfmUserRequestSchemaTests } from '../schema-tests'; +import { withIsoDateTimeStampToDateSchemaTests } from '../transformation-tests'; +import { WithTestsForTestCaseProps } from '../types/with-tests-for-test-case'; +import { TestCase } from '../test-cases/test-case'; /** * Gets tests for a test case, using the test case type to determine which tests to run * * These tests are all available tests that can be easily used to test a parameter, and should be extended */ -export const withTestsForTestcase = ({ - schema, - testCase, - getTestObjectWithUpdatedParameter, - getUpdatedParameterFromParsedTestObject, -}: { - schema: Schema; - testCase: TestCase; - getTestObjectWithUpdatedParameter: (newValue: unknown) => unknown; - getUpdatedParameterFromParsedTestObject: (parsedTestObject: unknown) => unknown; -}) => { +export const withTestsForTestcase = (props: WithTestsForTestCaseProps): void => { + const { schema, testCase, getTestObjectWithUpdatedParameter, getUpdatedParameterFromParsedTestObject } = props; const { type, options } = testCase; switch (type) { diff --git a/libs/common/src/test-helpers/schemas/transformation-tests/with-iso-date-time-stamp-to-date.schema.tests.ts b/libs/common/src/test-helpers/schemas/transformation-tests/with-iso-date-time-stamp-to-date.schema.tests.ts index e871724db1..d0d471cd60 100644 --- a/libs/common/src/test-helpers/schemas/transformation-tests/with-iso-date-time-stamp-to-date.schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/transformation-tests/with-iso-date-time-stamp-to-date.schema.tests.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { WithSchemaTestParams } from '../with-schema-test.type'; +import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { withDefaultOptionsTests } from '../primitive-types-tests/with-default-options.tests'; export const withIsoDateTimeStampToDateSchemaTests = ({ diff --git a/libs/common/src/test-helpers/schemas/types/schema-test-options.type.ts b/libs/common/src/test-helpers/schemas/types/schema-test-options.type.ts new file mode 100644 index 0000000000..05c0afbcaa --- /dev/null +++ b/libs/common/src/test-helpers/schemas/types/schema-test-options.type.ts @@ -0,0 +1,7 @@ +/** + * Options that are specific to the schema as a whole, for instance, if the schema is a partial + */ +export type SchemaTestOptions = { + isPartial?: boolean; + isStrict?: boolean; +}; diff --git a/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts b/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts new file mode 100644 index 0000000000..64fda97da8 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts @@ -0,0 +1,8 @@ +import { TestCase } from '../test-cases/test-case'; + +/** + * Test cases with the path parameter, used to create the getTestObjectWithUpdatedParameter function + */ +export type TestCaseWithPathParameter = { + parameterPath: string; +} & T; diff --git a/libs/common/src/test-helpers/schemas/types/test-case-with-type.type.ts b/libs/common/src/test-helpers/schemas/types/test-case-with-type.type.ts new file mode 100644 index 0000000000..f6a2d63fff --- /dev/null +++ b/libs/common/src/test-helpers/schemas/types/test-case-with-type.type.ts @@ -0,0 +1,10 @@ +import { DefaultOptions } from '../primitive-types-tests'; + +/** + * The test case to be tested, including the type and any options that are required + * + * Allows for the passing in of additional options if required for the specific test case + */ +export type TestCaseWithType = { + type: Type; +} & (AdditionalOptions extends false ? { options?: Partial } : { options: AdditionalOptions & Partial }); diff --git a/libs/common/src/test-helpers/schemas/with-schema-test.type.ts b/libs/common/src/test-helpers/schemas/types/with-schema-test.type.ts similarity index 83% rename from libs/common/src/test-helpers/schemas/with-schema-test.type.ts rename to libs/common/src/test-helpers/schemas/types/with-schema-test.type.ts index 1e4499a5c5..3e8b611a45 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-test.type.ts +++ b/libs/common/src/test-helpers/schemas/types/with-schema-test.type.ts @@ -1,5 +1,5 @@ import { ZodSchema } from 'zod'; -import { DefaultOptions } from './primitive-types-tests/with-default-options.tests'; +import { DefaultOptions } from '../primitive-types-tests/with-default-options.tests'; export type WithSchemaTestParams = { schema: Schema; diff --git a/libs/common/src/test-helpers/schemas/types/with-tests-for-test-case.ts b/libs/common/src/test-helpers/schemas/types/with-tests-for-test-case.ts new file mode 100644 index 0000000000..4ec3b30457 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/types/with-tests-for-test-case.ts @@ -0,0 +1,6 @@ +export type WithTestsForTestCaseProps = { + schema: Schema; + testCase: TestCase; + getTestObjectWithUpdatedParameter: (newValue: unknown) => unknown; + getUpdatedParameterFromParsedTestObject: (parsedTestObject: unknown) => unknown; +}; diff --git a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts index 3707a4bee9..f215c0ac4a 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts @@ -1,22 +1,10 @@ /* eslint-disable no-param-reassign */ import { z, ZodSchema } from 'zod'; -import { withTestsForTestcase } from './with-tests-for-testcase'; -import { TestCase } from './with-test-for-test-case.type'; - -/** - * Options that are specific to the schema as a whole, for instance, if the schema is a partial - */ -type SchemaTestOptions = { - isPartial?: boolean; - isStrict?: boolean; -}; - -/** - * Test cases with the path parameter, used to create the getTestObjectWithUpdatedParameter function - */ -export type TestCaseWithPathParameter = { - parameterPath: string; -} & TestCase; +import { withTestsForTestcase } from './tests/with-tests-for-testcase'; +import { SchemaTestOptions } from './types/schema-test-options.type'; +import { TestCaseWithPathParameter } from './types/test-case-with-path-parameter.type'; +import { WithTestsForTestCaseProps } from './types/with-tests-for-test-case'; +import { TestCase } from './test-cases/test-case'; /** * This function orchestrates a schema's test cases. @@ -79,16 +67,18 @@ export type TestCaseWithPathParameter = { * }] * ``` */ -export const withSchemaValidationTests = ({ +export const withSchemaValidationTests = ({ schema, schemaTestOptions = {}, aValidPayload, testCases, + withTestsForTestCases = withTestsForTestcase, }: { schema: Schema; schemaTestOptions?: SchemaTestOptions; - testCases: TestCaseWithPathParameter[]; + testCases: TestCaseWithPathParameter[]; aValidPayload: () => z.infer; + withTestsForTestCases?: (props: WithTestsForTestCaseProps) => void; }) => { const schemaTestOptionsDefaults: Partial = { isPartial: false, isStrict: false }; @@ -128,7 +118,7 @@ export const withSchemaValidationTests = ({ }; describe(`${parameterPath} parameter tests`, () => { - withTestsForTestcase({ + withTestsForTestCases({ schema, testCase, getTestObjectWithUpdatedParameter, diff --git a/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts b/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts deleted file mode 100644 index 9c5da49ca5..0000000000 --- a/libs/common/src/test-helpers/schemas/with-test-for-test-case.type.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { DefaultOptions, WithArrayTestsOptions } from './primitive-types-tests'; - -/** - * All test case types that have been tests implemented - */ -export type TestCaseTypes = - | 'string' - | 'number' - | 'boolean' - | 'Array' - | 'TfmTeamSchema' - | 'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA' - | 'UNIX_TIMESTAMP_SECONDS_SCHEMA' - | 'UNIX_TIMESTAMP_SCHEMA' - | 'OBJECT_ID_SCHEMA' - | 'OBJECT_ID_STRING_SCHEMA' - | 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA' - | 'ISO_DATE_TIME_STAMP_SCHEMA' - | 'ENTRA_ID_USER_SCHEMA' - | 'CURRENCY_SCHEMA' - | 'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA' - | 'UPSERT_TFM_USER_REQUEST_SCHEMA'; - -/** - * The test case to be tested, including the type and any options that are required - * - * Allows for the passing in of additional options if required for the specific test case - */ -type TestCaseWithType = { - type: Type; -} & (AdditionalOptions extends false ? { options?: Partial } : { options: AdditionalOptions & Partial }); - -export type TestCase = - | TestCaseWithType<'string'> - | TestCaseWithType<'number'> - | TestCaseWithType<'boolean'> - | TestCaseWithType<'Array', WithArrayTestsOptions> - | TestCaseWithType<'TfmTeamSchema'> - | TestCaseWithType<'UNIX_TIMESTAMP_MILLISECONDS_SCHEMA'> - | TestCaseWithType<'UNIX_TIMESTAMP_SECONDS_SCHEMA'> - | TestCaseWithType<'UNIX_TIMESTAMP_SCHEMA'> - | TestCaseWithType<'OBJECT_ID_SCHEMA'> - | TestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> - | TestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> - | TestCaseWithType<'ISO_DATE_TIME_STAMP_SCHEMA'> - | TestCaseWithType<'ENTRA_ID_USER_SCHEMA'> - | TestCaseWithType<'CURRENCY_SCHEMA'> - | TestCaseWithType<'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA'> - | TestCaseWithType<'UPSERT_TFM_USER_REQUEST_SCHEMA'>; From 5f3b1061841003a1488313e08dd2a7d34ec0f55f Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 12 Feb 2025 16:28:46 +0000 Subject: [PATCH 116/133] feat(dtfs2-6892): fix issue due to unit test and e2e tests running on jsdom --- libs/common/src/schemas/portal-user.schema.create.test.ts | 2 ++ libs/common/src/schemas/portal-user.schema.update.test.ts | 2 ++ libs/common/src/schemas/tfm/tfm-user.schema.test.ts | 2 ++ libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts | 2 ++ .../with-audit-database-record-schema.tests.ts | 4 ++-- .../schemas/backend-test-cases/backend-test-cases.ts | 2 -- .../schemas/backend-tests/with-tests-for-backend-testcase.ts | 4 ++-- .../src/test-helpers/schemas/test-cases/base-test-case.ts | 4 ++++ .../src/test-helpers/schemas/tests/with-tests-for-testcase.ts | 4 ++-- .../schemas/types/test-case-with-path-parameter.type.ts | 4 ++-- .../test-helpers/schemas/types/with-tests-for-test-case.ts | 4 ++-- .../src/test-helpers/schemas/with-schema-validation.tests.ts | 4 ++-- 12 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 libs/common/src/test-helpers/schemas/test-cases/base-test-case.ts diff --git a/libs/common/src/schemas/portal-user.schema.create.test.ts b/libs/common/src/schemas/portal-user.schema.create.test.ts index 71f3d3906d..b833042aa9 100644 --- a/libs/common/src/schemas/portal-user.schema.create.test.ts +++ b/libs/common/src/schemas/portal-user.schema.create.test.ts @@ -3,10 +3,12 @@ import z from 'zod'; import { generatePortalUserAuditDatabaseRecord } from '../change-stream'; import { CREATE } from './portal-user.schema'; import { withSchemaValidationTests } from '../test-helpers'; +import { withTestsForBackendTestcase } from '../test-helpers/schemas/backend-tests/with-tests-for-backend-testcase'; describe('PORTAL_USER', () => { describe('CREATE', () => { withSchemaValidationTests({ + withTestsForTestCases: withTestsForBackendTestcase, schema: CREATE, aValidPayload, testCases: [ diff --git a/libs/common/src/schemas/portal-user.schema.update.test.ts b/libs/common/src/schemas/portal-user.schema.update.test.ts index 0de79f9031..35e5220e4e 100644 --- a/libs/common/src/schemas/portal-user.schema.update.test.ts +++ b/libs/common/src/schemas/portal-user.schema.update.test.ts @@ -3,10 +3,12 @@ import z from 'zod'; import { generatePortalUserAuditDatabaseRecord } from '../change-stream'; import { UPDATE } from './portal-user.schema'; import { withSchemaValidationTests } from '../test-helpers'; +import { withTestsForBackendTestcase } from '../test-helpers/schemas/backend-tests/with-tests-for-backend-testcase'; describe('PORTAL_USER', () => { describe('UPDATE', () => { withSchemaValidationTests({ + withTestsForTestCases: withTestsForBackendTestcase, schema: UPDATE, schemaTestOptions: { isPartial: true, diff --git a/libs/common/src/schemas/tfm/tfm-user.schema.test.ts b/libs/common/src/schemas/tfm/tfm-user.schema.test.ts index 173e03fc2b..b150110466 100644 --- a/libs/common/src/schemas/tfm/tfm-user.schema.test.ts +++ b/libs/common/src/schemas/tfm/tfm-user.schema.test.ts @@ -3,9 +3,11 @@ import { withSchemaValidationTests } from '../../test-helpers'; import { TfmUser } from '../../types'; import { TFM_USER_SCHEMA } from './tfm-user.schema'; import { TEAM_IDS } from '../../constants'; +import { withTestsForBackendTestcase } from '../../test-helpers/schemas/backend-tests/with-tests-for-backend-testcase'; describe('TFM_USER_SCHEMA', () => { withSchemaValidationTests({ + withTestsForTestCases: withTestsForBackendTestcase, schema: TFM_USER_SCHEMA, aValidPayload, testCases: [ diff --git a/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts b/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts index bc2a230560..ae48f7103f 100644 --- a/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts +++ b/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts @@ -3,9 +3,11 @@ import { withSchemaValidationTests } from '../../test-helpers'; import { UpdateTfmUserRequest } from '../../types'; import { TEAM_IDS } from '../../constants'; import { UPDATE_TFM_USER_REQUEST_SCHEMA } from './update-tfm-user-request.schema'; +import { withTestsForBackendTestcase } from '../../test-helpers/schemas/backend-tests/with-tests-for-backend-testcase'; describe('UPDATE_TFM_USER_SCHEMA', () => { withSchemaValidationTests({ + withTestsForTestCases: withTestsForBackendTestcase, schema: UPDATE_TFM_USER_REQUEST_SCHEMA, schemaTestOptions: { isPartial: true, diff --git a/libs/common/src/test-helpers/schemas/backend-schema-tests/with-audit-database-record-schema.tests.ts b/libs/common/src/test-helpers/schemas/backend-schema-tests/with-audit-database-record-schema.tests.ts index 525f13b384..309073782f 100644 --- a/libs/common/src/test-helpers/schemas/backend-schema-tests/with-audit-database-record-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/backend-schema-tests/with-audit-database-record-schema.tests.ts @@ -5,7 +5,7 @@ import { generateTfmUserAuditDatabaseRecord } from '../../../change-stream'; import { withDefaultOptionsTests } from '../primitive-types-tests'; import { withSchemaValidationTests } from '../with-schema-validation.tests'; import { withTestsForBackendTestcase } from '../backend-tests/with-tests-for-backend-testcase'; -import { BackendTestCase } from '../backend-test-cases/backend-test-cases'; +import { BaseTestCase } from '../test-cases/base-test-case'; export const withAuditDatabaseRecordSchemaTests = ({ schema, @@ -21,7 +21,7 @@ export const withAuditDatabaseRecordSchemaTests = ({ options, }); - withSchemaValidationTests({ + withSchemaValidationTests({ withTestsForTestCases: withTestsForBackendTestcase, schema, aValidPayload: aValidAuditRecord, diff --git a/libs/common/src/test-helpers/schemas/backend-test-cases/backend-test-cases.ts b/libs/common/src/test-helpers/schemas/backend-test-cases/backend-test-cases.ts index dca6fd0ff7..808820eb63 100644 --- a/libs/common/src/test-helpers/schemas/backend-test-cases/backend-test-cases.ts +++ b/libs/common/src/test-helpers/schemas/backend-test-cases/backend-test-cases.ts @@ -1,8 +1,6 @@ -import { TestCase } from '../test-cases/test-case'; import { TestCaseWithType } from '../types/test-case-with-type.type'; export type BackendTestCase = - | TestCase | TestCaseWithType<'OBJECT_ID_SCHEMA'> | TestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> | TestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> diff --git a/libs/common/src/test-helpers/schemas/backend-tests/with-tests-for-backend-testcase.ts b/libs/common/src/test-helpers/schemas/backend-tests/with-tests-for-backend-testcase.ts index 5141951ca3..52a45d02ec 100644 --- a/libs/common/src/test-helpers/schemas/backend-tests/with-tests-for-backend-testcase.ts +++ b/libs/common/src/test-helpers/schemas/backend-tests/with-tests-for-backend-testcase.ts @@ -1,18 +1,18 @@ import { ZodSchema } from 'zod'; -import { BackendTestCase } from '../backend-test-cases/backend-test-cases'; import { withObjectIdSchemaTests } from '../backend-custom-types-tests/with-object-id-schema.tests'; import { withObjectIdStringSchemaTests } from '../backend-custom-types-tests/with-object-id-string-schema.tests'; import { withObjectIdOrObjectIdStringSchemaTests } from '../backend-custom-types-tests/with-object-id-or-object-id-string-schema.tests'; import { withAuditDatabaseRecordSchemaTests } from '../backend-schema-tests'; import { WithTestsForTestCaseProps } from '../types/with-tests-for-test-case'; import { withTestsForTestcase } from '../tests/with-tests-for-testcase'; +import { BaseTestCase } from '../test-cases/base-test-case'; /** * Gets tests for a test case, using the test case type to determine which tests to run * * These tests are all available tests that can be easily used to test a parameter, and should be extended */ -export const withTestsForBackendTestcase = (props: WithTestsForTestCaseProps): void => { +export const withTestsForBackendTestcase = (props: WithTestsForTestCaseProps): void => { const { schema, testCase, getTestObjectWithUpdatedParameter, getUpdatedParameterFromParsedTestObject } = props; const { type, options } = testCase; diff --git a/libs/common/src/test-helpers/schemas/test-cases/base-test-case.ts b/libs/common/src/test-helpers/schemas/test-cases/base-test-case.ts new file mode 100644 index 0000000000..2ec5da0139 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/test-cases/base-test-case.ts @@ -0,0 +1,4 @@ +import { BackendTestCase } from '../backend-test-cases/backend-test-cases'; +import { TestCase } from './test-case'; + +export type BaseTestCase = TestCase | BackendTestCase; diff --git a/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts b/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts index 6d98217d30..5fa7c94a48 100644 --- a/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts +++ b/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts @@ -11,14 +11,14 @@ import { withStringTests, withNumberTests, withBooleanTests, withArrayTests } fr import { withEntraIdUserSchemaTests, withUpsertTfmUserRequestSchemaTests } from '../schema-tests'; import { withIsoDateTimeStampToDateSchemaTests } from '../transformation-tests'; import { WithTestsForTestCaseProps } from '../types/with-tests-for-test-case'; -import { TestCase } from '../test-cases/test-case'; +import { BaseTestCase } from '../test-cases/base-test-case'; /** * Gets tests for a test case, using the test case type to determine which tests to run * * These tests are all available tests that can be easily used to test a parameter, and should be extended */ -export const withTestsForTestcase = (props: WithTestsForTestCaseProps): void => { +export const withTestsForTestcase = (props: WithTestsForTestCaseProps): void => { const { schema, testCase, getTestObjectWithUpdatedParameter, getUpdatedParameterFromParsedTestObject } = props; const { type, options } = testCase; diff --git a/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts b/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts index 64fda97da8..d0da4620b8 100644 --- a/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts +++ b/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts @@ -1,8 +1,8 @@ -import { TestCase } from '../test-cases/test-case'; +import { BaseTestCase } from '../test-cases/base-test-case'; /** * Test cases with the path parameter, used to create the getTestObjectWithUpdatedParameter function */ -export type TestCaseWithPathParameter = { +export type TestCaseWithPathParameter = { parameterPath: string; } & T; diff --git a/libs/common/src/test-helpers/schemas/types/with-tests-for-test-case.ts b/libs/common/src/test-helpers/schemas/types/with-tests-for-test-case.ts index 4ec3b30457..87fd66378b 100644 --- a/libs/common/src/test-helpers/schemas/types/with-tests-for-test-case.ts +++ b/libs/common/src/test-helpers/schemas/types/with-tests-for-test-case.ts @@ -1,6 +1,6 @@ -export type WithTestsForTestCaseProps = { +export type WithTestsForTestCaseProps = { schema: Schema; - testCase: TestCase; + testCase: T; getTestObjectWithUpdatedParameter: (newValue: unknown) => unknown; getUpdatedParameterFromParsedTestObject: (parsedTestObject: unknown) => unknown; }; diff --git a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts index f215c0ac4a..cc01634037 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts @@ -4,7 +4,7 @@ import { withTestsForTestcase } from './tests/with-tests-for-testcase'; import { SchemaTestOptions } from './types/schema-test-options.type'; import { TestCaseWithPathParameter } from './types/test-case-with-path-parameter.type'; import { WithTestsForTestCaseProps } from './types/with-tests-for-test-case'; -import { TestCase } from './test-cases/test-case'; +import { BaseTestCase } from './test-cases/base-test-case'; /** * This function orchestrates a schema's test cases. @@ -67,7 +67,7 @@ import { TestCase } from './test-cases/test-case'; * }] * ``` */ -export const withSchemaValidationTests = ({ +export const withSchemaValidationTests = ({ schema, schemaTestOptions = {}, aValidPayload, From 7fdc71e3311f7d1bda43599c4d0b2e2b4e632da2 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Wed, 12 Feb 2025 17:08:27 +0000 Subject: [PATCH 117/133] feat(dtfs2-6892): fix issue due to unit test and e2e tests running on jsdom --- cspell.json | 17 +++-------------- doc/schemas.md | 14 ++++++++++++-- .../schemas/backend-custom-types-tests/index.ts | 7 +++++++ .../schemas/backend-schema-tests/index.ts | 7 +++++++ .../schemas/backend-test-cases/index.ts | 9 +++++++++ .../test-helpers/schemas/backend-tests/index.ts | 8 ++++++++ .../test-helpers/schemas/test-cases/index.ts | 2 ++ .../src/test-helpers/schemas/tests/index.ts | 1 + 8 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 libs/common/src/test-helpers/schemas/backend-test-cases/index.ts create mode 100644 libs/common/src/test-helpers/schemas/backend-tests/index.ts create mode 100644 libs/common/src/test-helpers/schemas/test-cases/index.ts create mode 100644 libs/common/src/test-helpers/schemas/tests/index.ts diff --git a/cspell.json b/cspell.json index 8b99b246e7..3738e6bc4b 100644 --- a/cspell.json +++ b/cspell.json @@ -207,27 +207,16 @@ "venv", "VNET", "WCAG", + "whatwg", "wordprocessingml", "wovens", "XLXS" ], - "dictionaries": [ - "en-gb", - "companies", - "softwareTerms", - "misc", - "lorem-ipsum", - "typescript", - "node", - "bash", - "npm" - ], + "dictionaries": ["en-gb", "companies", "softwareTerms", "misc", "lorem-ipsum", "typescript", "node", "bash", "npm"], "languageSettings": [ { "languageId": "commit-msg", - "ignoreRegExpList": [ - "/^#.*/gm" - ] + "ignoreRegExpList": ["/^#.*/gm"] } ], "version": "0.2" diff --git a/doc/schemas.md b/doc/schemas.md index 115962f5b9..1a09253725 100644 --- a/doc/schemas.md +++ b/doc/schemas.md @@ -25,6 +25,14 @@ As a result, we are working towards a new approach to schema testing to make wri The result of this structure means that the majority of tests will be effectively 'free' to write. The only exception here are schemas that use types or schemas where a test case does not already exist. +### Note on backend schema tests + +We currently run our UI tests using mongodb in a [jsdom environment] (https://stackoverflow.com/questions/68468203/why-am-i-getting-textencoder-is-not-defined-in-jest). E2E tests also run in the jsdom environment. + +As part of our schema tests, we export tests that contain `mongodb`'s `ObjectId`. This `ObjectId` eventually references the `whatwg-url` library, which in turn calls `TextEncoder`. `TextEncoder` is a node global and is not available in the `jsdom` environments. + +As we should only be using `ObjectId` in the backend, we have seperated out these test files into `backend-filename` tests and do not export these by default in `libs/common` (much like other backend-specific functionality in `libs/common`). + ### Writing tests To test -- We create a new file, referencing the `withSchemaValidationTests` function, and follow the instructions found in `with-schema-validation.tests.ts`. @@ -45,12 +53,14 @@ However, sometimes you'll have created a new nested schema or type that doesn't ### To create your own primitive/custom type test: +**Note: If your schema requires testing ObjectId use the folders and files prefixed `backend`** + - Create a new file in the correct folder (ie `with-string.tests.ts`) - Follow the existing pattern (see `with-string.tests.ts` for an example) - Ensure you have `withDefaultTestCases` in your test file - Add any type specific options as required (see `with-array.tests.ts` for an example) - Add export of the test to the `index.ts` file in the same folder -- Add your test case name to `with-test-for-test-case.type` in both the `TestCaseTypes` and `TestCaseWithType` declarations +- Add your test case name to `with-test-for-test-case.type` in the `TestCaseWithType` declaration - Add your test case to the `withTestsForTestcase` function to call your test case when provided with the test case name as the `type` in a `testCase` - This can now be called as @@ -79,7 +89,7 @@ n.b. transformation tests use a different test case type to allow access to a fu - Ensure you have `withDefaultTestCases` in your test file - Add any type specific options as required (see `with-array.tests.ts` for an example) - Add export of the test to the `index.ts` file in the same folder -- Add your test case name to `with-test-for-test-case.type` in both the `TestCaseTypes` and `TestCaseWithType` declarations +- Add your test case name to `with-test-for-test-case.type` in the `TestCaseWithType` declaration - Add your test case to the `withTestsForTestcase` function to call your test case when provided with the test case name as the `type` in a `testCase` - This can now be called as diff --git a/libs/common/src/test-helpers/schemas/backend-custom-types-tests/index.ts b/libs/common/src/test-helpers/schemas/backend-custom-types-tests/index.ts index d951bea62d..67b61a37af 100644 --- a/libs/common/src/test-helpers/schemas/backend-custom-types-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/backend-custom-types-tests/index.ts @@ -1,3 +1,10 @@ +/* + * Do not export these out of libs common in the default exports -- + * Follow a similar pattern for existing backend test helpers + * This is due to issues with mongodb and our jsdom test environment + * + * See schemas.md for more information + */ export * from './with-object-id-or-object-id-string-schema.tests'; export * from './with-object-id-schema.tests'; export * from './with-object-id-string-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/backend-schema-tests/index.ts b/libs/common/src/test-helpers/schemas/backend-schema-tests/index.ts index dc650a70d9..21fa2880fd 100644 --- a/libs/common/src/test-helpers/schemas/backend-schema-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/backend-schema-tests/index.ts @@ -1 +1,8 @@ +/* + * Do not export these out of libs common in the default exports -- + * Follow a similar pattern for existing backend test helpers + * This is due to issues with mongodb and our jsdom test environment + * + * See schemas.md for more information + */ export * from './with-audit-database-record-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/backend-test-cases/index.ts b/libs/common/src/test-helpers/schemas/backend-test-cases/index.ts new file mode 100644 index 0000000000..12944c7c46 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/backend-test-cases/index.ts @@ -0,0 +1,9 @@ +/* + * Do not export these out of libs common in the default exports -- + * Follow a similar pattern for existing backend test helpers + * This is due to issues with mongodb and our jsdom test environment + * + * See schemas.md for more information + */ + +export * from './backend-test-cases'; diff --git a/libs/common/src/test-helpers/schemas/backend-tests/index.ts b/libs/common/src/test-helpers/schemas/backend-tests/index.ts new file mode 100644 index 0000000000..dd786b88f8 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/backend-tests/index.ts @@ -0,0 +1,8 @@ +/* + * Do not export these out of libs common in the default exports -- + * Follow a similar pattern for existing backend test helpers + * This is due to issues with mongodb and our jsdom test environment + * + * See schemas.md for more information + */ +export * from './with-tests-for-backend-testcase'; diff --git a/libs/common/src/test-helpers/schemas/test-cases/index.ts b/libs/common/src/test-helpers/schemas/test-cases/index.ts new file mode 100644 index 0000000000..4b34e88c62 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/test-cases/index.ts @@ -0,0 +1,2 @@ +export * from './test-case'; +export * from './base-test-case'; diff --git a/libs/common/src/test-helpers/schemas/tests/index.ts b/libs/common/src/test-helpers/schemas/tests/index.ts new file mode 100644 index 0000000000..6e7aed30b7 --- /dev/null +++ b/libs/common/src/test-helpers/schemas/tests/index.ts @@ -0,0 +1 @@ +export * from './with-tests-for-testcase'; From f09d87ed6f36147c9b6ae79ed711bc5e2d54bbd9 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 13 Feb 2025 11:31:52 +0000 Subject: [PATCH 118/133] feat(dtfs2-6892): fix issue due to unit test and e2e tests running on jsdom --- doc/schemas.md | 9 +++++++++ .../test-helpers/schemas/with-schema-validation.tests.ts | 1 + 2 files changed, 10 insertions(+) diff --git a/doc/schemas.md b/doc/schemas.md index 1a09253725..d47e08aff1 100644 --- a/doc/schemas.md +++ b/doc/schemas.md @@ -33,6 +33,15 @@ As part of our schema tests, we export tests that contain `mongodb`'s `ObjectId` As we should only be using `ObjectId` in the backend, we have seperated out these test files into `backend-filename` tests and do not export these by default in `libs/common` (much like other backend-specific functionality in `libs/common`). +When back end tests are required to be used (for instance, due to checking objectId etc), we should use the following pattern when calling the schema validation tests: + +```ts +withSchemaValidationTests({ + withTestsForTestCases: withTestsForBackendTestcase, + ...rest, +}); +``` + ### Writing tests To test -- We create a new file, referencing the `withSchemaValidationTests` function, and follow the instructions found in `with-schema-validation.tests.ts`. diff --git a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts index cc01634037..8765257ed0 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts @@ -13,6 +13,7 @@ import { BaseTestCase } from './test-cases/base-test-case'; * @param params.schemaTestOptions Options that are specific to the schema as a whole, for instance, if the schema is a partial, or strict * @param params.aValidPayload A function that returns a valid payload for the schema * @param params.testCases Test cases to test + * @param params.withTestsForTestCases pass in withTestsForBackendTestCase when using backend specific test cases, otherwise this can be left as default * @see doc\schemas.md for more information * @example Schema test options * ```ts From 41a5dc7d6ea7e6b11c884cb0812f1956d96b15dc Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 13 Feb 2025 12:00:32 +0000 Subject: [PATCH 119/133] Merge branch 'feat/DTFS2-6892/add-schema-tests-for-commonised-tfm-user' into feat/DTFS2-6892/add-token-issuance --- .../__mocks__/builders/user.service.mock.builder.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts index 12af34a2a8..dff8026940 100644 --- a/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts @@ -1,21 +1,16 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { BaseMockBuilder } from '@ukef/dtfs2-common'; import { aTfmUser } from '@ukef/dtfs2-common/mock-data-backend'; -import { - UpsertTfmUserFromEntraIdUserParams, - UpsertTfmUserFromEntraIdUserResponse, - saveUserLoginInformationParams, - UserService, -} from '../../services/user.service'; +import { UpsertTfmUserFromEntraIdUserResponse, UserService } from '../../services/user.service'; export class UserServiceMockBuilder extends BaseMockBuilder { constructor() { super({ defaultInstance: { - upsertTfmUserFromEntraIdUser({ entraIdUser, auditDetails }: UpsertTfmUserFromEntraIdUserParams): Promise { + upsertTfmUserFromEntraIdUser(): Promise { return Promise.resolve(aTfmUser()); }, - saveUserLoginInformation({ userId, sessionIdentifier, auditDetails }: saveUserLoginInformationParams): Promise { + saveUserLoginInformation(): Promise { return Promise.resolve(); }, }, From 5e8d7375b1d15d33d3e989cb3fdda0e7c9bb6196 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 13 Feb 2025 15:49:36 +0000 Subject: [PATCH 120/133] feat(dtfs2-6892): review comments --- .../get-fee-record-correction-request-review.api-test.ts | 2 +- libs/common/src/schemas/portal-user.schema.create.test.ts | 4 ++-- libs/common/src/schemas/portal-user.schema.update.test.ts | 4 ++-- portal-api/test-helpers/unit-test-mocks/mock-user.js | 4 ++-- .../src/v1/__mocks__/builders/user.service.mock.builder.ts | 1 - .../record-corrections/check-the-information/index.test.ts | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/dtfs-central-api/api-tests/v1/utilisation-reports/fee-record-corrections/get-fee-record-correction-request-review.api-test.ts b/dtfs-central-api/api-tests/v1/utilisation-reports/fee-record-corrections/get-fee-record-correction-request-review.api-test.ts index 6428af292f..045b212a69 100644 --- a/dtfs-central-api/api-tests/v1/utilisation-reports/fee-record-corrections/get-fee-record-correction-request-review.api-test.ts +++ b/dtfs-central-api/api-tests/v1/utilisation-reports/fee-record-corrections/get-fee-record-correction-request-review.api-test.ts @@ -33,7 +33,7 @@ describe(`GET ${BASE_URL}`, () => { id: bankId, name: 'Test bank', paymentOfficerTeam: { - emails: ['one@email.com', 'two@email.com'], + emails: ['one@ukexportfinance.gov.uk', 'two@ukexportfinance.gov.uk'], teamName: 'Test team', }, }; diff --git a/libs/common/src/schemas/portal-user.schema.create.test.ts b/libs/common/src/schemas/portal-user.schema.create.test.ts index b833042aa9..927cbbd381 100644 --- a/libs/common/src/schemas/portal-user.schema.create.test.ts +++ b/libs/common/src/schemas/portal-user.schema.create.test.ts @@ -74,10 +74,10 @@ describe('PORTAL_USER', () => { function aValidPayload(): z.infer { return { - username: 'HSBC-maker-1', + username: 'maker-1', firstname: 'Mister', surname: 'One', - email: 'one@email.com', + email: 'one@ukexportfinance.gov.uk', timezone: 'Europe/London', roles: ['maker'], bank: { diff --git a/libs/common/src/schemas/portal-user.schema.update.test.ts b/libs/common/src/schemas/portal-user.schema.update.test.ts index 35e5220e4e..fd53d34056 100644 --- a/libs/common/src/schemas/portal-user.schema.update.test.ts +++ b/libs/common/src/schemas/portal-user.schema.update.test.ts @@ -108,10 +108,10 @@ describe('PORTAL_USER', () => { function aValidPayload(): z.infer { return { - username: 'HSBC-maker-1', + username: 'maker-1', firstname: 'Mister', surname: 'One', - email: 'one@email.com', + email: 'one@ukexportfinance.gov.uk', timezone: 'Europe/London', roles: ['maker'], bank: { diff --git a/portal-api/test-helpers/unit-test-mocks/mock-user.js b/portal-api/test-helpers/unit-test-mocks/mock-user.js index 203a0eea2e..703b7101ae 100644 --- a/portal-api/test-helpers/unit-test-mocks/mock-user.js +++ b/portal-api/test-helpers/unit-test-mocks/mock-user.js @@ -5,10 +5,10 @@ const { MAKER } = require('../../src/v1/roles/roles'); const BASE_TEST_USER = { _id: '075bcd157dcb851180e02a7c', - username: 'HSBC-maker-1', + username: 'maker-1', firstname: 'Mister', surname: 'One', - email: 'one@email.com', + email: 'one@ukexportfinance.gov.uk', timezone: 'Europe/London', roles: [MAKER], bank: { diff --git a/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts index dff8026940..e9eb4a3375 100644 --- a/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts +++ b/trade-finance-manager-api/src/v1/__mocks__/builders/user.service.mock.builder.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { BaseMockBuilder } from '@ukef/dtfs2-common'; import { aTfmUser } from '@ukef/dtfs2-common/mock-data-backend'; import { UpsertTfmUserFromEntraIdUserResponse, UserService } from '../../services/user.service'; diff --git a/trade-finance-manager-ui/server/controllers/utilisation-reports/record-corrections/check-the-information/index.test.ts b/trade-finance-manager-ui/server/controllers/utilisation-reports/record-corrections/check-the-information/index.test.ts index 64a9f78e5b..4601c68b98 100644 --- a/trade-finance-manager-ui/server/controllers/utilisation-reports/record-corrections/check-the-information/index.test.ts +++ b/trade-finance-manager-ui/server/controllers/utilisation-reports/record-corrections/check-the-information/index.test.ts @@ -37,7 +37,7 @@ describe('controllers/utilisation-reports/record-corrections/check-the-informati exporter: 'Test company', reasons: [RECORD_CORRECTION_REASON.FACILITY_ID_INCORRECT, RECORD_CORRECTION_REASON.OTHER], additionalInfo: 'The facility ID does not match the facility ID held on file', - contactEmailAddresses: ['one@email.com', 'two@email.com'], + contactEmailAddresses: ['one@ukexportfinance.gov.uk', 'two@ukexportfinance.gov.uk'], }; const mockApiResponse = { From a73928ae046e883d8dbe2b45379f3d0990ffd388 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 13 Feb 2025 16:26:31 +0000 Subject: [PATCH 121/133] Merge branch 'main' into feat/DTFS2-6892/add-token-issuance --- .../v1/controllers/user/user.routes.test.js | 224 ------------------ 1 file changed, 224 deletions(-) delete mode 100644 trade-finance-manager-api/src/v1/controllers/user/user.routes.test.js diff --git a/trade-finance-manager-api/src/v1/controllers/user/user.routes.test.js b/trade-finance-manager-api/src/v1/controllers/user/user.routes.test.js deleted file mode 100644 index 6f9bd85e41..0000000000 --- a/trade-finance-manager-api/src/v1/controllers/user/user.routes.test.js +++ /dev/null @@ -1,224 +0,0 @@ -const httpMocks = require('node-mocks-http'); -const { anEntraIdUser, ApiError, TEAMS } = require('@ukef/dtfs2-common'); -const { HttpStatusCode } = require('axios'); -const { upsertTfmUserFromEntraIdUser } = require('./user.routes'); -const userController = require('./user.controller'); -const { withValidatePayloadTests } = require('../../../../test-helpers'); - -jest.mock('../user/user.controller.js', () => ({ - upsertTfmUserFromEntraIdUser: jest.fn(), -})); - -describe('user routes', () => { - beforeEach(() => { - jest.resetAllMocks(); - }); - - describe('upsertTfmUserFromEntraIdUser', () => { - const aSuccessfulResponseFromController = 'aSuccessfulResponse'; - - withValidatePayloadTests({ - makeRequest: upsertTfmUserFromEntraIdUser, - givenTheRequestWouldOtherwiseSucceed: mockSuccessfulUpsertTfmUserFromEntraIdUser, - successTestCases: getEntraIdUserSuccessTestCases({}), - successStatusCode: HttpStatusCode.Ok, - successResponse: aSuccessfulResponseFromController, - failureTestCases: getEntraIdUserFailureTestCases({}), - failureResponse: { - status: HttpStatusCode.BadRequest, - message: 'Error validating payload', - }, - }); - - describe('when the request body is valid', () => { - let validRequest; - - beforeEach(() => { - validRequest = anEntraIdUser(); - }); - - it('calls the user controller', async () => { - const { req, res, next } = getHttpMocks(validRequest); - - await upsertTfmUserFromEntraIdUser(req, res, next); - - expect(jest.mocked(userController.upsertTfmUserFromEntraIdUser)).toHaveBeenCalledTimes(1); - }); - - describe('when the upsert is unsuccessful', () => { - describe('when the error is an api error', () => { - const apiErrorDetails = { - status: 500, - message: 'an api error message', - cause: 'a cause that should not be exposed', - code: 'a code', - }; - - beforeEach(() => { - jest.mocked(userController.upsertTfmUserFromEntraIdUser).mockRejectedValueOnce(new ApiError(apiErrorDetails)); - }); - - it('returns the api error with expected formatting', async () => { - const { req, res, next } = getHttpMocks(validRequest); - const { cause, ...expectedErrorDetails } = apiErrorDetails; - await upsertTfmUserFromEntraIdUser(req, res, next); - - expect(res._getData()).toEqual(expectedErrorDetails); - expect(res._getStatusCode()).toEqual(expectedErrorDetails.status); - }); - }); - - describe('when the error is not an api error', () => { - const errorToThrow = new Error('An Error'); - beforeEach(() => { - jest.mocked(userController.upsertTfmUserFromEntraIdUser).mockRejectedValueOnce(errorToThrow); - }); - - it('passes the error to further middleware', async () => { - const { req, res, next } = getHttpMocks(validRequest); - - await upsertTfmUserFromEntraIdUser(req, res, next); - - expect(next).toHaveBeenCalledWith(errorToThrow); - }); - }); - }); - - describe('when the upsert is successful', () => { - beforeEach(() => { - mockSuccessfulUpsertTfmUserFromEntraIdUser(); - }); - - it('returns the result of the user controller', async () => { - const { req, res, next } = getHttpMocks(validRequest); - - await upsertTfmUserFromEntraIdUser(req, res, next); - - expect(res._getData()).toEqual(aSuccessfulResponseFromController); - }); - }); - }); - - function getHttpMocks(body) { - const { req, res } = httpMocks.createMocks({ - body, - }); - const next = jest.fn(); - return { req, res, next }; - } - - function mockSuccessfulUpsertTfmUserFromEntraIdUser() { - jest.mocked(userController.upsertTfmUserFromEntraIdUser).mockResolvedValueOnce(aSuccessfulResponseFromController); - } - }); -}); - -function getEntraIdUserFailureTestCases({ getTestObjectWithUpdatedEntraIdUserParams = (entraIdUser) => entraIdUser }) { - return [ - { - aTestCase: () => { - const { oid: _oid, ...rest } = anEntraIdUser(); - return getTestObjectWithUpdatedEntraIdUserParams(rest); - }, - description: 'the oid is missing', - }, - { - aTestCase: () => { - const { verified_primary_email: _verifiedPrimaryEmail, ...rest } = anEntraIdUser(); - return getTestObjectWithUpdatedEntraIdUserParams(rest); - }, - description: 'the verified primary email is missing', - }, - { - aTestCase: () => { - const { verified_secondary_email: _verifiedSecondaryEmail, ...rest } = anEntraIdUser(); - return getTestObjectWithUpdatedEntraIdUserParams(rest); - }, - description: 'the verified secondary email is missing', - }, - { - aTestCase: () => { - const { given_name: _givenName, ...rest } = anEntraIdUser(); - return getTestObjectWithUpdatedEntraIdUserParams(rest); - }, - description: 'the given name is missing', - }, - - { - aTestCase: () => { - const { family_name: _familyName, ...rest } = anEntraIdUser(); - return getTestObjectWithUpdatedEntraIdUserParams(rest); - }, - description: 'the family name is missing', - }, - { - aTestCase: () => { - const { roles: _roles, ...rest } = anEntraIdUser(); - return getTestObjectWithUpdatedEntraIdUserParams(rest); - }, - description: 'the roles are missing', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), oid: 1 }), - description: 'the oid is not a string', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_primary_email: [] }), - description: 'the verify primary email array is empty', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_primary_email: [1] }), - description: 'the verify primary email is not a string array', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_primary_email: '1' }), - description: 'the verify primary email is not an array', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_secondary_email: [1] }), - description: 'the verify secondary email is not a string array', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_secondary_email: '1' }), - description: 'the verify secondary email is not an array', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), given_name: 1 }), - description: 'the given name is not a string', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), family_name: 1 }), - description: 'the family name is not a string', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), roles: ['NOT_A_USER_ROLE'] }), - description: 'the roles are not an array of user roles', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), roles: TEAMS.BUSINESS_SUPPORT.id }), - description: 'the roles are not an array', - }, - { - aTestCase: () => ({}), - description: 'the object is empty', - }, - ]; -} - -function getEntraIdUserSuccessTestCases({ getTestObjectWithUpdatedEntraIdUserParams = (entraIdUser) => entraIdUser }) { - return [ - { aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams(anEntraIdUser()), description: 'a complete valid payload is present' }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), verified_secondary_email: [] }), - description: 'the verified secondary email array is empty', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), roles: [] }), - description: 'the roles array is empty', - }, - { - aTestCase: () => getTestObjectWithUpdatedEntraIdUserParams({ ...anEntraIdUser(), extraField: 'extra' }), - description: 'there is an extra field', - }, - ]; -} From 719970d9b08dc75feae94e1aa5f59f74fdba0be3 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 13 Feb 2025 16:36:55 +0000 Subject: [PATCH 122/133] feat(dtfs2-6892): review comments --- trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts index e928d6291c..0a8ae7bf15 100644 --- a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts +++ b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts @@ -16,7 +16,6 @@ export const getAuthSsoRouter: GetRouter = () => { const loginController = new LoginController({ loginService, userSessionService }); const authSsoRouter = express.Router(); - // Todo: update this to check the right token authSsoRouter.post('/auth/sso-redirect/form', (req, res, next) => { loginController.handleSsoRedirectForm(req, res).catch(next); }); From e331e1222451683d03110d5cecf40b500390035b Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 13 Feb 2025 17:27:24 +0000 Subject: [PATCH 123/133] feat(dtfs2-6892): remove need for backend separate schema tests --- doc/schemas.md | 29 +++------ .../audit-database-record.schema.test.ts | 2 +- .../src/schemas/currency.schema.test.ts | 2 +- .../src/schemas/object-id.schema.test.ts | 6 +- .../schemas/portal-amendment.schema.test.ts | 2 +- .../schemas/portal-user.schema.create.test.ts | 4 +- .../schemas/portal-user.schema.update.test.ts | 4 +- .../create-tfm-user-request.schema.test.ts | 2 +- ...d-user.schema.entra-id-user-schema.test.ts | 2 +- ...ded-auth-code-request-state-schema.test.ts | 2 +- ...code-redirect-response-body-schema.test.ts | 3 +- ...ra-id-authentication-result-schema.test.ts | 3 +- .../src/schemas/tfm/tfm-team.schema.test.ts | 2 +- .../src/schemas/tfm/tfm-user.schema.test.ts | 4 +- .../tfm/update-tfm-user.schema.test.ts | 4 +- .../upsert-tfm-user-request.schema.test.ts | 2 +- .../src/schemas/unix-timestamp.schema.test.ts | 2 +- libs/common/src/test-helpers/index.ts | 4 +- .../backend-custom-types-tests/index.ts | 10 --- .../schemas/backend-schema-tests/index.ts | 8 --- .../backend-test-cases/backend-test-cases.ts | 7 -- .../schemas/backend-test-cases/index.ts | 9 --- .../schemas/backend-tests/index.ts | 8 --- .../with-tests-for-backend-testcase.ts | 65 ------------------- .../schemas/custom-types-tests/index.ts | 3 + ...ect-id-or-object-id-string-schema.tests.ts | 0 .../with-object-id-schema.tests.ts | 0 .../with-object-id-string-schema.tests.ts | 0 .../schemas/schema-tests/index.ts | 1 + ...with-audit-database-record-schema.tests.ts | 6 +- .../schemas/test-cases/base-test-case.ts | 4 -- .../test-helpers/schemas/test-cases/index.ts | 1 - .../schemas/test-cases/test-case.ts | 6 +- .../schemas/tests/with-tests-for-testcase.ts | 44 ++++++++++++- .../test-case-with-path-parameter.type.ts | 4 +- .../schemas/with-schema-validation.tests.ts | 12 ++-- 36 files changed, 89 insertions(+), 178 deletions(-) delete mode 100644 libs/common/src/test-helpers/schemas/backend-custom-types-tests/index.ts delete mode 100644 libs/common/src/test-helpers/schemas/backend-schema-tests/index.ts delete mode 100644 libs/common/src/test-helpers/schemas/backend-test-cases/backend-test-cases.ts delete mode 100644 libs/common/src/test-helpers/schemas/backend-test-cases/index.ts delete mode 100644 libs/common/src/test-helpers/schemas/backend-tests/index.ts delete mode 100644 libs/common/src/test-helpers/schemas/backend-tests/with-tests-for-backend-testcase.ts rename libs/common/src/test-helpers/schemas/{backend-custom-types-tests => custom-types-tests}/with-object-id-or-object-id-string-schema.tests.ts (100%) rename libs/common/src/test-helpers/schemas/{backend-custom-types-tests => custom-types-tests}/with-object-id-schema.tests.ts (100%) rename libs/common/src/test-helpers/schemas/{backend-custom-types-tests => custom-types-tests}/with-object-id-string-schema.tests.ts (100%) rename libs/common/src/test-helpers/schemas/{backend-schema-tests => schema-tests}/with-audit-database-record-schema.tests.ts (91%) delete mode 100644 libs/common/src/test-helpers/schemas/test-cases/base-test-case.ts diff --git a/doc/schemas.md b/doc/schemas.md index d47e08aff1..408a1ff1ef 100644 --- a/doc/schemas.md +++ b/doc/schemas.md @@ -10,6 +10,16 @@ The majority of schemas are defined in the `libs/common/src/schemas` directory. ## Schema tests +### A note on exporting schema tests in libs/common + +These tests are not exported as part of libs/commmon default export as they break several test pipelines. + +This is due to us currently run our UI tests using mongodb in a [jsdom environment](https://stackoverflow.com/questions/68468203/why-am-i-getting-textencoder-is-not-defined-in-jest). E2E tests also run in the jsdom environment. This clashes with our schema tests, as we export tests that contain `mongodb`'s `ObjectId`. This `ObjectId` eventually references the `whatwg-url` library, which in turn calls `TextEncoder`. `TextEncoder` is a node global and is not available in the `jsdom` environments. + +One solution to this is to create backend-specific schema tests that would not be exported as part of libs/common. However, this is more complicated than just not exporting all schema tests as part of libs/common. + +### Schema testing + We have been testing our schemas in a variety of ways. These tests have proven long and difficult to maintain, with a lot of repetition and boilerplate. As a result, we are working towards a new approach to schema testing to make writing tests easy to write. This approach sees: @@ -25,23 +35,6 @@ As a result, we are working towards a new approach to schema testing to make wri The result of this structure means that the majority of tests will be effectively 'free' to write. The only exception here are schemas that use types or schemas where a test case does not already exist. -### Note on backend schema tests - -We currently run our UI tests using mongodb in a [jsdom environment] (https://stackoverflow.com/questions/68468203/why-am-i-getting-textencoder-is-not-defined-in-jest). E2E tests also run in the jsdom environment. - -As part of our schema tests, we export tests that contain `mongodb`'s `ObjectId`. This `ObjectId` eventually references the `whatwg-url` library, which in turn calls `TextEncoder`. `TextEncoder` is a node global and is not available in the `jsdom` environments. - -As we should only be using `ObjectId` in the backend, we have seperated out these test files into `backend-filename` tests and do not export these by default in `libs/common` (much like other backend-specific functionality in `libs/common`). - -When back end tests are required to be used (for instance, due to checking objectId etc), we should use the following pattern when calling the schema validation tests: - -```ts -withSchemaValidationTests({ - withTestsForTestCases: withTestsForBackendTestcase, - ...rest, -}); -``` - ### Writing tests To test -- We create a new file, referencing the `withSchemaValidationTests` function, and follow the instructions found in `with-schema-validation.tests.ts`. @@ -62,8 +55,6 @@ However, sometimes you'll have created a new nested schema or type that doesn't ### To create your own primitive/custom type test: -**Note: If your schema requires testing ObjectId use the folders and files prefixed `backend`** - - Create a new file in the correct folder (ie `with-string.tests.ts`) - Follow the existing pattern (see `with-string.tests.ts` for an example) - Ensure you have `withDefaultTestCases` in your test file diff --git a/libs/common/src/schemas/audit-database-record.schema.test.ts b/libs/common/src/schemas/audit-database-record.schema.test.ts index cd1cf9cf84..d3359851ae 100644 --- a/libs/common/src/schemas/audit-database-record.schema.test.ts +++ b/libs/common/src/schemas/audit-database-record.schema.test.ts @@ -1,4 +1,4 @@ -import { withAuditDatabaseRecordSchemaTests } from '../test-helpers/schemas/backend-schema-tests'; +import { withAuditDatabaseRecordSchemaTests } from '../test-helpers/schemas'; import { AUDIT_DATABASE_RECORD_SCHEMA } from './audit-database-record.schema'; describe('AUDIT_DATABASE_RECORD_SCHEMA', () => { diff --git a/libs/common/src/schemas/currency.schema.test.ts b/libs/common/src/schemas/currency.schema.test.ts index 41f42f7222..d5427746e4 100644 --- a/libs/common/src/schemas/currency.schema.test.ts +++ b/libs/common/src/schemas/currency.schema.test.ts @@ -1,4 +1,4 @@ -import { withCurrencySchemaTests } from '../test-helpers'; +import { withCurrencySchemaTests } from '../test-helpers/schemas'; import { CURRENCY_SCHEMA } from './currency.schema'; describe('CURRENCY_SCHEMA', () => { diff --git a/libs/common/src/schemas/object-id.schema.test.ts b/libs/common/src/schemas/object-id.schema.test.ts index ce706c9f4a..3e83a30b23 100644 --- a/libs/common/src/schemas/object-id.schema.test.ts +++ b/libs/common/src/schemas/object-id.schema.test.ts @@ -1,8 +1,4 @@ -import { - withObjectIdOrObjectIdStringSchemaTests, - withObjectIdSchemaTests, - withObjectIdStringSchemaTests, -} from '../test-helpers/schemas/backend-custom-types-tests'; +import { withObjectIdOrObjectIdStringSchemaTests, withObjectIdSchemaTests, withObjectIdStringSchemaTests } from '../test-helpers/schemas'; import { OBJECT_ID_SCHEMA, OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA, OBJECT_ID_STRING_SCHEMA } from './object-id.schema'; describe('OBJECT_ID_SCHEMA', () => { diff --git a/libs/common/src/schemas/portal-amendment.schema.test.ts b/libs/common/src/schemas/portal-amendment.schema.test.ts index d0a4df284d..df0740cbac 100644 --- a/libs/common/src/schemas/portal-amendment.schema.test.ts +++ b/libs/common/src/schemas/portal-amendment.schema.test.ts @@ -1,5 +1,5 @@ import { PORTAL_FACILITY_AMENDMENT_USER_VALUES } from './portal-amendment.schema'; -import { withSchemaValidationTests } from '../test-helpers'; +import { withSchemaValidationTests } from '../test-helpers/schemas'; import { aPortalFacilityAmendmentUserValues } from '../test-helpers/mock-data-backend'; import { PortalFacilityAmendmentUserValues } from '../types'; diff --git a/libs/common/src/schemas/portal-user.schema.create.test.ts b/libs/common/src/schemas/portal-user.schema.create.test.ts index 927cbbd381..e8c24fd1e2 100644 --- a/libs/common/src/schemas/portal-user.schema.create.test.ts +++ b/libs/common/src/schemas/portal-user.schema.create.test.ts @@ -2,13 +2,11 @@ import { ObjectId } from 'mongodb'; import z from 'zod'; import { generatePortalUserAuditDatabaseRecord } from '../change-stream'; import { CREATE } from './portal-user.schema'; -import { withSchemaValidationTests } from '../test-helpers'; -import { withTestsForBackendTestcase } from '../test-helpers/schemas/backend-tests/with-tests-for-backend-testcase'; +import { withSchemaValidationTests } from '../test-helpers/schemas'; describe('PORTAL_USER', () => { describe('CREATE', () => { withSchemaValidationTests({ - withTestsForTestCases: withTestsForBackendTestcase, schema: CREATE, aValidPayload, testCases: [ diff --git a/libs/common/src/schemas/portal-user.schema.update.test.ts b/libs/common/src/schemas/portal-user.schema.update.test.ts index fd53d34056..3611289785 100644 --- a/libs/common/src/schemas/portal-user.schema.update.test.ts +++ b/libs/common/src/schemas/portal-user.schema.update.test.ts @@ -2,13 +2,11 @@ import { ObjectId } from 'mongodb'; import z from 'zod'; import { generatePortalUserAuditDatabaseRecord } from '../change-stream'; import { UPDATE } from './portal-user.schema'; -import { withSchemaValidationTests } from '../test-helpers'; -import { withTestsForBackendTestcase } from '../test-helpers/schemas/backend-tests/with-tests-for-backend-testcase'; +import { withSchemaValidationTests } from '../test-helpers/schemas'; describe('PORTAL_USER', () => { describe('UPDATE', () => { withSchemaValidationTests({ - withTestsForTestCases: withTestsForBackendTestcase, schema: UPDATE, schemaTestOptions: { isPartial: true, diff --git a/libs/common/src/schemas/tfm/create-tfm-user-request.schema.test.ts b/libs/common/src/schemas/tfm/create-tfm-user-request.schema.test.ts index 85e5d66c23..f425d4bd83 100644 --- a/libs/common/src/schemas/tfm/create-tfm-user-request.schema.test.ts +++ b/libs/common/src/schemas/tfm/create-tfm-user-request.schema.test.ts @@ -1,5 +1,5 @@ import { TEAM_IDS } from '../../constants'; -import { withSchemaValidationTests } from '../../test-helpers'; +import { withSchemaValidationTests } from '../../test-helpers/schemas'; import { CreateTfmUserRequest } from '../../types'; import { CREATE_TFM_USER_REQUEST_SCHEMA } from './create-tfm-user-request.schema'; diff --git a/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts b/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts index bc1fe6dca9..7dc1b7a404 100644 --- a/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id-user.schema.entra-id-user-schema.test.ts @@ -1,4 +1,4 @@ -import { withEntraIdUserSchemaTests } from '../../test-helpers'; +import { withEntraIdUserSchemaTests } from '../../test-helpers/schemas'; import { ENTRA_ID_USER_SCHEMA } from './entra-id-user.schema'; describe('ENTRA_ID_USER_SCHEMA', () => { diff --git a/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts index 9226444424..7504a4e79e 100644 --- a/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.decoded-auth-code-request-state-schema.test.ts @@ -1,4 +1,4 @@ -import { withSchemaValidationTests } from '../../test-helpers'; +import { withSchemaValidationTests } from '../../test-helpers/schemas'; import { DecodedAuthCodeRequestState } from '../../types/tfm/entra-id'; import { DECODED_AUTH_CODE_REQUEST_STATE_SCHEMA } from './entra-id.schema'; diff --git a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts index a0486861d9..781c31b261 100644 --- a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-auth-code-redirect-response-body-schema.test.ts @@ -1,4 +1,5 @@ -import { anEntraIdAuthCodeRedirectResponseBody, withSchemaValidationTests } from '../../test-helpers'; +import { anEntraIdAuthCodeRedirectResponseBody } from '../../test-helpers'; +import { withSchemaValidationTests } from '../../test-helpers/schemas'; import { ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA } from './entra-id.schema'; describe('ENTRA_ID_AUTH_CODE_REDIRECT_RESPONSE_BODY_SCHEMA', () => { diff --git a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts index 2059b0e7ca..92e153edc0 100644 --- a/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts +++ b/libs/common/src/schemas/tfm/entra-id.schema.entra-id-authentication-result-schema.test.ts @@ -1,4 +1,5 @@ -import { anEntraIdUser, withSchemaValidationTests } from '../../test-helpers'; +import { anEntraIdUser } from '../../test-helpers'; +import { withSchemaValidationTests } from '../../test-helpers/schemas'; import { EntraIdUser } from '../../types'; import { EntraIdAuthenticationResult } from '../../types/tfm/entra-id'; import { ENTRA_ID_AUTHENTICATION_RESULT_SCHEMA } from './entra-id.schema'; diff --git a/libs/common/src/schemas/tfm/tfm-team.schema.test.ts b/libs/common/src/schemas/tfm/tfm-team.schema.test.ts index 85f3b11132..27140582c8 100644 --- a/libs/common/src/schemas/tfm/tfm-team.schema.test.ts +++ b/libs/common/src/schemas/tfm/tfm-team.schema.test.ts @@ -1,4 +1,4 @@ -import { withTfmTeamSchemaTests } from '../../test-helpers'; +import { withTfmTeamSchemaTests } from '../../test-helpers/schemas'; import { TfmTeamSchema } from './tfm-team.schema'; describe('tfm-team.schema', () => { diff --git a/libs/common/src/schemas/tfm/tfm-user.schema.test.ts b/libs/common/src/schemas/tfm/tfm-user.schema.test.ts index b150110466..74670f0481 100644 --- a/libs/common/src/schemas/tfm/tfm-user.schema.test.ts +++ b/libs/common/src/schemas/tfm/tfm-user.schema.test.ts @@ -1,13 +1,11 @@ import { ObjectId } from 'mongodb'; -import { withSchemaValidationTests } from '../../test-helpers'; +import { withSchemaValidationTests } from '../../test-helpers/schemas'; import { TfmUser } from '../../types'; import { TFM_USER_SCHEMA } from './tfm-user.schema'; import { TEAM_IDS } from '../../constants'; -import { withTestsForBackendTestcase } from '../../test-helpers/schemas/backend-tests/with-tests-for-backend-testcase'; describe('TFM_USER_SCHEMA', () => { withSchemaValidationTests({ - withTestsForTestCases: withTestsForBackendTestcase, schema: TFM_USER_SCHEMA, aValidPayload, testCases: [ diff --git a/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts b/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts index ae48f7103f..11bdc9d548 100644 --- a/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts +++ b/libs/common/src/schemas/tfm/update-tfm-user.schema.test.ts @@ -1,13 +1,11 @@ import { ObjectId } from 'mongodb'; -import { withSchemaValidationTests } from '../../test-helpers'; +import { withSchemaValidationTests } from '../../test-helpers/schemas'; import { UpdateTfmUserRequest } from '../../types'; import { TEAM_IDS } from '../../constants'; import { UPDATE_TFM_USER_REQUEST_SCHEMA } from './update-tfm-user-request.schema'; -import { withTestsForBackendTestcase } from '../../test-helpers/schemas/backend-tests/with-tests-for-backend-testcase'; describe('UPDATE_TFM_USER_SCHEMA', () => { withSchemaValidationTests({ - withTestsForTestCases: withTestsForBackendTestcase, schema: UPDATE_TFM_USER_REQUEST_SCHEMA, schemaTestOptions: { isPartial: true, diff --git a/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.test.ts b/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.test.ts index cc9389665a..a263cd432f 100644 --- a/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.test.ts +++ b/libs/common/src/schemas/tfm/upsert-tfm-user-request.schema.test.ts @@ -1,4 +1,4 @@ -import { withUpsertTfmUserRequestSchemaTests } from '../../test-helpers'; +import { withUpsertTfmUserRequestSchemaTests } from '../../test-helpers/schemas'; import { UPSERT_TFM_USER_REQUEST_SCHEMA } from './upsert-tfm-user-request.schema'; describe('UPSERT_TFM_USER_REQUEST_SCHEMA', () => { diff --git a/libs/common/src/schemas/unix-timestamp.schema.test.ts b/libs/common/src/schemas/unix-timestamp.schema.test.ts index 703a1c4fd2..fdcbc8867b 100644 --- a/libs/common/src/schemas/unix-timestamp.schema.test.ts +++ b/libs/common/src/schemas/unix-timestamp.schema.test.ts @@ -1,4 +1,4 @@ -import { withUnixTimestampMillisecondsSchemaTests, withUnixTimestampSchemaTests, withUnixTimestampSecondsSchemaTests } from '../test-helpers'; +import { withUnixTimestampMillisecondsSchemaTests, withUnixTimestampSchemaTests, withUnixTimestampSecondsSchemaTests } from '../test-helpers/schemas'; import { UNIX_TIMESTAMP_MILLISECONDS_SCHEMA, UNIX_TIMESTAMP_SCHEMA, UNIX_TIMESTAMP_SECONDS_SCHEMA } from './unix-timestamp.schema'; describe('UNIX_TIMESTAMP_SCHEMA', () => { diff --git a/libs/common/src/test-helpers/index.ts b/libs/common/src/test-helpers/index.ts index 5bbaa9aea1..95d5ea97ef 100644 --- a/libs/common/src/test-helpers/index.ts +++ b/libs/common/src/test-helpers/index.ts @@ -4,7 +4,9 @@ export * from './portal-session-user'; export * from './portal-session-bank'; // './test-cases-backend' has its own export because it uses the 'supertest' and 'mongodb' packages export * from './test-cases-backend'; -export * from './schemas'; +// './schemas' should be exported seperately from other test helpers, and not part of libs/common because of issues running in jsdom +// for more information, please see schemas.md +// export * from './schemas'; export * from './convert-milliseconds-to-seconds'; export * from './mock-builders'; export * from './fee-record-correction-review-information'; diff --git a/libs/common/src/test-helpers/schemas/backend-custom-types-tests/index.ts b/libs/common/src/test-helpers/schemas/backend-custom-types-tests/index.ts deleted file mode 100644 index 67b61a37af..0000000000 --- a/libs/common/src/test-helpers/schemas/backend-custom-types-tests/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Do not export these out of libs common in the default exports -- - * Follow a similar pattern for existing backend test helpers - * This is due to issues with mongodb and our jsdom test environment - * - * See schemas.md for more information - */ -export * from './with-object-id-or-object-id-string-schema.tests'; -export * from './with-object-id-schema.tests'; -export * from './with-object-id-string-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/backend-schema-tests/index.ts b/libs/common/src/test-helpers/schemas/backend-schema-tests/index.ts deleted file mode 100644 index 21fa2880fd..0000000000 --- a/libs/common/src/test-helpers/schemas/backend-schema-tests/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Do not export these out of libs common in the default exports -- - * Follow a similar pattern for existing backend test helpers - * This is due to issues with mongodb and our jsdom test environment - * - * See schemas.md for more information - */ -export * from './with-audit-database-record-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/backend-test-cases/backend-test-cases.ts b/libs/common/src/test-helpers/schemas/backend-test-cases/backend-test-cases.ts deleted file mode 100644 index 808820eb63..0000000000 --- a/libs/common/src/test-helpers/schemas/backend-test-cases/backend-test-cases.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { TestCaseWithType } from '../types/test-case-with-type.type'; - -export type BackendTestCase = - | TestCaseWithType<'OBJECT_ID_SCHEMA'> - | TestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> - | TestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> - | TestCaseWithType<'AUDIT_DATABASE_RECORD_SCHEMA'>; diff --git a/libs/common/src/test-helpers/schemas/backend-test-cases/index.ts b/libs/common/src/test-helpers/schemas/backend-test-cases/index.ts deleted file mode 100644 index 12944c7c46..0000000000 --- a/libs/common/src/test-helpers/schemas/backend-test-cases/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Do not export these out of libs common in the default exports -- - * Follow a similar pattern for existing backend test helpers - * This is due to issues with mongodb and our jsdom test environment - * - * See schemas.md for more information - */ - -export * from './backend-test-cases'; diff --git a/libs/common/src/test-helpers/schemas/backend-tests/index.ts b/libs/common/src/test-helpers/schemas/backend-tests/index.ts deleted file mode 100644 index dd786b88f8..0000000000 --- a/libs/common/src/test-helpers/schemas/backend-tests/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Do not export these out of libs common in the default exports -- - * Follow a similar pattern for existing backend test helpers - * This is due to issues with mongodb and our jsdom test environment - * - * See schemas.md for more information - */ -export * from './with-tests-for-backend-testcase'; diff --git a/libs/common/src/test-helpers/schemas/backend-tests/with-tests-for-backend-testcase.ts b/libs/common/src/test-helpers/schemas/backend-tests/with-tests-for-backend-testcase.ts deleted file mode 100644 index 52a45d02ec..0000000000 --- a/libs/common/src/test-helpers/schemas/backend-tests/with-tests-for-backend-testcase.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { ZodSchema } from 'zod'; -import { withObjectIdSchemaTests } from '../backend-custom-types-tests/with-object-id-schema.tests'; -import { withObjectIdStringSchemaTests } from '../backend-custom-types-tests/with-object-id-string-schema.tests'; -import { withObjectIdOrObjectIdStringSchemaTests } from '../backend-custom-types-tests/with-object-id-or-object-id-string-schema.tests'; -import { withAuditDatabaseRecordSchemaTests } from '../backend-schema-tests'; -import { WithTestsForTestCaseProps } from '../types/with-tests-for-test-case'; -import { withTestsForTestcase } from '../tests/with-tests-for-testcase'; -import { BaseTestCase } from '../test-cases/base-test-case'; - -/** - * Gets tests for a test case, using the test case type to determine which tests to run - * - * These tests are all available tests that can be easily used to test a parameter, and should be extended - */ -export const withTestsForBackendTestcase = (props: WithTestsForTestCaseProps): void => { - const { schema, testCase, getTestObjectWithUpdatedParameter, getUpdatedParameterFromParsedTestObject } = props; - const { type, options } = testCase; - - switch (type) { - case 'OBJECT_ID_SCHEMA': - withObjectIdSchemaTests({ - schema, - options, - getTestObjectWithUpdatedParameter, - getUpdatedParameterFromParsedTestObject, - }); - break; - - case 'OBJECT_ID_STRING_SCHEMA': - withObjectIdStringSchemaTests({ - schema, - options, - getTestObjectWithUpdatedParameter, - getUpdatedParameterFromParsedTestObject, - }); - break; - - case 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA': - withObjectIdOrObjectIdStringSchemaTests({ - schema, - options, - getTestObjectWithUpdatedParameter, - getUpdatedParameterFromParsedTestObject, - }); - break; - - case 'AUDIT_DATABASE_RECORD_SCHEMA': - withAuditDatabaseRecordSchemaTests({ - schema, - options, - getTestObjectWithUpdatedParameter, - getUpdatedParameterFromParsedTestObject, - }); - break; - - default: - // We fall through to the normal tests by default, which throws an error if the type is not found - withTestsForTestcase({ - schema, - testCase, - getTestObjectWithUpdatedParameter, - getUpdatedParameterFromParsedTestObject, - }); - } -}; diff --git a/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts index 483b2006e7..d94c7ee88e 100644 --- a/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/custom-types-tests/index.ts @@ -8,3 +8,6 @@ export * from './with-unix-timestamp-milliseconds-schema.tests'; export * from './with-unix-timestamp-seconds-schema.tests'; export * from './with-tfm-team-schema.tests'; export * from './with-currency-schema.tests'; +export * from './with-object-id-schema.tests'; +export * from './with-object-id-string-schema.tests'; +export * from './with-object-id-or-object-id-string-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-or-object-id-string-schema.tests.ts diff --git a/libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-schema.tests.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-schema.tests.ts diff --git a/libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-string-schema.tests.ts b/libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests.ts similarity index 100% rename from libs/common/src/test-helpers/schemas/backend-custom-types-tests/with-object-id-string-schema.tests.ts rename to libs/common/src/test-helpers/schemas/custom-types-tests/with-object-id-string-schema.tests.ts diff --git a/libs/common/src/test-helpers/schemas/schema-tests/index.ts b/libs/common/src/test-helpers/schemas/schema-tests/index.ts index 2c033862f1..6d62aae5f3 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/index.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/index.ts @@ -4,3 +4,4 @@ */ export * from './with-entra-id-user-schema.tests'; export * from './with-upsert-tfm-user-request.schema.tests'; +export * from './with-audit-database-record-schema.tests'; diff --git a/libs/common/src/test-helpers/schemas/backend-schema-tests/with-audit-database-record-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts similarity index 91% rename from libs/common/src/test-helpers/schemas/backend-schema-tests/with-audit-database-record-schema.tests.ts rename to libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts index 309073782f..d69d07db97 100644 --- a/libs/common/src/test-helpers/schemas/backend-schema-tests/with-audit-database-record-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts @@ -4,8 +4,7 @@ import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { generateTfmUserAuditDatabaseRecord } from '../../../change-stream'; import { withDefaultOptionsTests } from '../primitive-types-tests'; import { withSchemaValidationTests } from '../with-schema-validation.tests'; -import { withTestsForBackendTestcase } from '../backend-tests/with-tests-for-backend-testcase'; -import { BaseTestCase } from '../test-cases/base-test-case'; +import { TestCase } from '../test-cases'; export const withAuditDatabaseRecordSchemaTests = ({ schema, @@ -21,8 +20,7 @@ export const withAuditDatabaseRecordSchemaTests = ({ options, }); - withSchemaValidationTests({ - withTestsForTestCases: withTestsForBackendTestcase, + withSchemaValidationTests({ schema, aValidPayload: aValidAuditRecord, testCases: [ diff --git a/libs/common/src/test-helpers/schemas/test-cases/base-test-case.ts b/libs/common/src/test-helpers/schemas/test-cases/base-test-case.ts deleted file mode 100644 index 2ec5da0139..0000000000 --- a/libs/common/src/test-helpers/schemas/test-cases/base-test-case.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { BackendTestCase } from '../backend-test-cases/backend-test-cases'; -import { TestCase } from './test-case'; - -export type BaseTestCase = TestCase | BackendTestCase; diff --git a/libs/common/src/test-helpers/schemas/test-cases/index.ts b/libs/common/src/test-helpers/schemas/test-cases/index.ts index 4b34e88c62..638d20a4d1 100644 --- a/libs/common/src/test-helpers/schemas/test-cases/index.ts +++ b/libs/common/src/test-helpers/schemas/test-cases/index.ts @@ -1,2 +1 @@ export * from './test-case'; -export * from './base-test-case'; diff --git a/libs/common/src/test-helpers/schemas/test-cases/test-case.ts b/libs/common/src/test-helpers/schemas/test-cases/test-case.ts index a783dcf3ed..bcda34a515 100644 --- a/libs/common/src/test-helpers/schemas/test-cases/test-case.ts +++ b/libs/common/src/test-helpers/schemas/test-cases/test-case.ts @@ -14,4 +14,8 @@ export type TestCase = | TestCaseWithType<'ENTRA_ID_USER_SCHEMA'> | TestCaseWithType<'CURRENCY_SCHEMA'> | TestCaseWithType<'ISO_DATE_TIME_STAMP_TO_DATE_SCHEMA'> - | TestCaseWithType<'UPSERT_TFM_USER_REQUEST_SCHEMA'>; + | TestCaseWithType<'UPSERT_TFM_USER_REQUEST_SCHEMA'> + | TestCaseWithType<'OBJECT_ID_SCHEMA'> + | TestCaseWithType<'OBJECT_ID_STRING_SCHEMA'> + | TestCaseWithType<'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA'> + | TestCaseWithType<'AUDIT_DATABASE_RECORD_SCHEMA'>; diff --git a/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts b/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts index 5fa7c94a48..1daccbc126 100644 --- a/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts +++ b/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts @@ -6,19 +6,22 @@ import { withIsoDateTimeStampSchemaTests, withTfmTeamSchemaTests, withCurrencySchemaTests, + withObjectIdSchemaTests, + withObjectIdStringSchemaTests, + withObjectIdOrObjectIdStringSchemaTests, } from '../custom-types-tests'; import { withStringTests, withNumberTests, withBooleanTests, withArrayTests } from '../primitive-types-tests'; -import { withEntraIdUserSchemaTests, withUpsertTfmUserRequestSchemaTests } from '../schema-tests'; +import { withAuditDatabaseRecordSchemaTests, withEntraIdUserSchemaTests, withUpsertTfmUserRequestSchemaTests } from '../schema-tests'; import { withIsoDateTimeStampToDateSchemaTests } from '../transformation-tests'; import { WithTestsForTestCaseProps } from '../types/with-tests-for-test-case'; -import { BaseTestCase } from '../test-cases/base-test-case'; +import { TestCase } from '../test-cases'; /** * Gets tests for a test case, using the test case type to determine which tests to run * * These tests are all available tests that can be easily used to test a parameter, and should be extended */ -export const withTestsForTestcase = (props: WithTestsForTestCaseProps): void => { +export const withTestsForTestcase = (props: WithTestsForTestCaseProps): void => { const { schema, testCase, getTestObjectWithUpdatedParameter, getUpdatedParameterFromParsedTestObject } = props; const { type, options } = testCase; @@ -139,6 +142,41 @@ export const withTestsForTestcase = (props: WithTestsF getUpdatedParameterFromParsedTestObject, }); break; + case 'OBJECT_ID_SCHEMA': + withObjectIdSchemaTests({ + schema, + options, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, + }); + break; + + case 'OBJECT_ID_STRING_SCHEMA': + withObjectIdStringSchemaTests({ + schema, + options, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, + }); + break; + + case 'OBJECT_ID_OR_OBJECT_ID_STRING_SCHEMA': + withObjectIdOrObjectIdStringSchemaTests({ + schema, + options, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, + }); + break; + + case 'AUDIT_DATABASE_RECORD_SCHEMA': + withAuditDatabaseRecordSchemaTests({ + schema, + options, + getTestObjectWithUpdatedParameter, + getUpdatedParameterFromParsedTestObject, + }); + break; default: throw Error(`There are no existing test cases for the type ${type}`); diff --git a/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts b/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts index d0da4620b8..da6fdc16ff 100644 --- a/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts +++ b/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts @@ -1,8 +1,8 @@ -import { BaseTestCase } from '../test-cases/base-test-case'; +import { TestCase } from '../test-cases'; /** * Test cases with the path parameter, used to create the getTestObjectWithUpdatedParameter function */ -export type TestCaseWithPathParameter = { +export type TestCaseWithPathParameter = { parameterPath: string; } & T; diff --git a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts index 8765257ed0..76a973774c 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts @@ -1,10 +1,9 @@ /* eslint-disable no-param-reassign */ import { z, ZodSchema } from 'zod'; -import { withTestsForTestcase } from './tests/with-tests-for-testcase'; import { SchemaTestOptions } from './types/schema-test-options.type'; import { TestCaseWithPathParameter } from './types/test-case-with-path-parameter.type'; -import { WithTestsForTestCaseProps } from './types/with-tests-for-test-case'; -import { BaseTestCase } from './test-cases/base-test-case'; +import { TestCase } from './test-cases'; +import { withTestsForTestcase } from './tests'; /** * This function orchestrates a schema's test cases. @@ -13,7 +12,6 @@ import { BaseTestCase } from './test-cases/base-test-case'; * @param params.schemaTestOptions Options that are specific to the schema as a whole, for instance, if the schema is a partial, or strict * @param params.aValidPayload A function that returns a valid payload for the schema * @param params.testCases Test cases to test - * @param params.withTestsForTestCases pass in withTestsForBackendTestCase when using backend specific test cases, otherwise this can be left as default * @see doc\schemas.md for more information * @example Schema test options * ```ts @@ -68,18 +66,16 @@ import { BaseTestCase } from './test-cases/base-test-case'; * }] * ``` */ -export const withSchemaValidationTests = ({ +export const withSchemaValidationTests = ({ schema, schemaTestOptions = {}, aValidPayload, testCases, - withTestsForTestCases = withTestsForTestcase, }: { schema: Schema; schemaTestOptions?: SchemaTestOptions; testCases: TestCaseWithPathParameter[]; aValidPayload: () => z.infer; - withTestsForTestCases?: (props: WithTestsForTestCaseProps) => void; }) => { const schemaTestOptionsDefaults: Partial = { isPartial: false, isStrict: false }; @@ -119,7 +115,7 @@ export const withSchemaValidationTests = { - withTestsForTestCases({ + withTestsForTestcase({ schema, testCase, getTestObjectWithUpdatedParameter, From 6874d392e6145955206a39f5257548daae2a8ded Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Thu, 13 Feb 2025 17:30:05 +0000 Subject: [PATCH 124/133] feat(dtfs2-6892): remove need for backend separate schema tests --- .../schema-tests/with-audit-database-record-schema.tests.ts | 3 +-- .../test-helpers/schemas/tests/with-tests-for-testcase.ts | 3 +-- .../schemas/types/test-case-with-path-parameter.type.ts | 4 ++-- .../test-helpers/schemas/types/with-tests-for-test-case.ts | 6 ++++-- .../test-helpers/schemas/with-schema-validation.tests.ts | 5 ++--- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts index d69d07db97..c6f8311b91 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-audit-database-record-schema.tests.ts @@ -4,7 +4,6 @@ import { WithSchemaTestParams } from '../types/with-schema-test.type'; import { generateTfmUserAuditDatabaseRecord } from '../../../change-stream'; import { withDefaultOptionsTests } from '../primitive-types-tests'; import { withSchemaValidationTests } from '../with-schema-validation.tests'; -import { TestCase } from '../test-cases'; export const withAuditDatabaseRecordSchemaTests = ({ schema, @@ -20,7 +19,7 @@ export const withAuditDatabaseRecordSchemaTests = ({ options, }); - withSchemaValidationTests({ + withSchemaValidationTests({ schema, aValidPayload: aValidAuditRecord, testCases: [ diff --git a/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts b/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts index 1daccbc126..bbeac97cb9 100644 --- a/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts +++ b/libs/common/src/test-helpers/schemas/tests/with-tests-for-testcase.ts @@ -14,14 +14,13 @@ import { withStringTests, withNumberTests, withBooleanTests, withArrayTests } fr import { withAuditDatabaseRecordSchemaTests, withEntraIdUserSchemaTests, withUpsertTfmUserRequestSchemaTests } from '../schema-tests'; import { withIsoDateTimeStampToDateSchemaTests } from '../transformation-tests'; import { WithTestsForTestCaseProps } from '../types/with-tests-for-test-case'; -import { TestCase } from '../test-cases'; /** * Gets tests for a test case, using the test case type to determine which tests to run * * These tests are all available tests that can be easily used to test a parameter, and should be extended */ -export const withTestsForTestcase = (props: WithTestsForTestCaseProps): void => { +export const withTestsForTestcase = (props: WithTestsForTestCaseProps): void => { const { schema, testCase, getTestObjectWithUpdatedParameter, getUpdatedParameterFromParsedTestObject } = props; const { type, options } = testCase; diff --git a/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts b/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts index da6fdc16ff..9748be4aa2 100644 --- a/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts +++ b/libs/common/src/test-helpers/schemas/types/test-case-with-path-parameter.type.ts @@ -3,6 +3,6 @@ import { TestCase } from '../test-cases'; /** * Test cases with the path parameter, used to create the getTestObjectWithUpdatedParameter function */ -export type TestCaseWithPathParameter = { +export type TestCaseWithPathParameter = { parameterPath: string; -} & T; +} & TestCase; diff --git a/libs/common/src/test-helpers/schemas/types/with-tests-for-test-case.ts b/libs/common/src/test-helpers/schemas/types/with-tests-for-test-case.ts index 87fd66378b..627ca77f46 100644 --- a/libs/common/src/test-helpers/schemas/types/with-tests-for-test-case.ts +++ b/libs/common/src/test-helpers/schemas/types/with-tests-for-test-case.ts @@ -1,6 +1,8 @@ -export type WithTestsForTestCaseProps = { +import { TestCase } from '../test-cases'; + +export type WithTestsForTestCaseProps = { schema: Schema; - testCase: T; + testCase: TestCase; getTestObjectWithUpdatedParameter: (newValue: unknown) => unknown; getUpdatedParameterFromParsedTestObject: (parsedTestObject: unknown) => unknown; }; diff --git a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts index 76a973774c..7eb6129685 100644 --- a/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts +++ b/libs/common/src/test-helpers/schemas/with-schema-validation.tests.ts @@ -2,7 +2,6 @@ import { z, ZodSchema } from 'zod'; import { SchemaTestOptions } from './types/schema-test-options.type'; import { TestCaseWithPathParameter } from './types/test-case-with-path-parameter.type'; -import { TestCase } from './test-cases'; import { withTestsForTestcase } from './tests'; /** @@ -66,7 +65,7 @@ import { withTestsForTestcase } from './tests'; * }] * ``` */ -export const withSchemaValidationTests = ({ +export const withSchemaValidationTests = ({ schema, schemaTestOptions = {}, aValidPayload, @@ -74,7 +73,7 @@ export const withSchemaValidationTests = []; + testCases: TestCaseWithPathParameter[]; aValidPayload: () => z.infer; }) => { const schemaTestOptionsDefaults: Partial = { isPartial: false, isStrict: false }; From c0efbb6105c13d33bd2f490f570f2a17b756aaa3 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 14 Feb 2025 10:10:19 +0000 Subject: [PATCH 125/133] feat(dtfs2-6892): review comments --- .../schemas/schema-tests/with-tfm-session-user-schema.tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-session-user-schema.tests.ts b/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-session-user-schema.tests.ts index 25d289d84b..4896dcf253 100644 --- a/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-session-user-schema.tests.ts +++ b/libs/common/src/test-helpers/schemas/schema-tests/with-tfm-session-user-schema.tests.ts @@ -103,7 +103,7 @@ export function aValidTfmSessionUser() { return { _id: new ObjectId().toString(), username: 'test-user', - email: 'test-user@test.com', + email: 'test-user@ukexportfinance.gov.uk', teams: [TEAM_IDS.PIM], timezone: 'Europe/London', firstName: 'FirstName', From 264eee30ee57ee5942601c154320d4e84ac58681 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 14 Feb 2025 10:32:26 +0000 Subject: [PATCH 126/133] feat(dtfs2-6892): add msal to tfm ui --- package-lock.json | 2929 +++++++++++++++++++++++-- trade-finance-manager-ui/package.json | 3 + 2 files changed, 2751 insertions(+), 181 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15fa7a904f..73b3546357 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8817,7 +8817,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", @@ -11451,7 +11450,6 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, "funding": [ { "type": "github", @@ -11497,7 +11495,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -15895,7 +15892,6 @@ "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4.9.1" @@ -17238,6 +17234,14 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/i": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", + "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -17393,7 +17397,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -17429,7 +17432,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -20766,7 +20768,6 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -21541,6 +21542,166 @@ "node": ">=0.10.0" } }, + "node_modules/npm": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.2.tgz", + "integrity": "sha512-iriPEPIkoMYUy3F6f3wwSZAU93E0Eg6cHwIR6jzzOXWSy+SD/rOODEs74cVONHKSx2obXtuUoyidVEhISrisgQ==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^8.0.0", + "@npmcli/config": "^9.0.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.1.0", + "@npmcli/promise-spawn": "^8.0.2", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^9.0.1", + "@sigstore/tuf": "^3.0.0", + "abbrev": "^3.0.0", + "archy": "~1.0.0", + "cacache": "^19.0.1", + "chalk": "^5.3.0", + "ci-info": "^4.1.0", + "cli-columns": "^4.0.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.4.5", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^8.0.2", + "ini": "^5.0.0", + "init-package-json": "^7.0.2", + "is-cidr": "^5.1.0", + "json-parse-even-better-errors": "^4.0.0", + "libnpmaccess": "^9.0.0", + "libnpmdiff": "^7.0.0", + "libnpmexec": "^9.0.0", + "libnpmfund": "^6.0.0", + "libnpmhook": "^11.0.0", + "libnpmorg": "^7.0.0", + "libnpmpack": "^8.0.0", + "libnpmpublish": "^10.0.1", + "libnpmsearch": "^8.0.0", + "libnpmteam": "^7.0.0", + "libnpmversion": "^7.0.0", + "make-fetch-happen": "^14.0.3", + "minimatch": "^9.0.5", + "minipass": "^7.1.1", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^11.0.0", + "nopt": "^8.0.0", + "normalize-package-data": "^7.0.0", + "npm-audit-report": "^6.0.0", + "npm-install-checks": "^7.1.1", + "npm-package-arg": "^12.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-profile": "^11.0.1", + "npm-registry-fetch": "^18.0.2", + "npm-user-validate": "^3.0.0", + "p-map": "^4.0.0", + "pacote": "^19.0.1", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^4.0.0", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "ssri": "^12.0.0", + "supports-color": "^9.4.0", + "tar": "^6.2.1", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^6.0.0", + "which": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -21554,240 +21715,2625 @@ "node": ">=8" } }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/nunjucks": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", - "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", - "license": "BSD-2-Clause", + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", "dependencies": { - "a-sync-waterfall": "^1.0.0", - "asap": "^2.0.3", - "commander": "^5.1.0" - }, - "bin": { - "nunjucks-precompile": "bin/precompile" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">= 6.9.0" - }, - "peerDependencies": { - "chokidar": "^3.3.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } + "node": ">=12" } }, - "node_modules/nunjucks/node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "inBundle": true, "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/nwsapi": { - "version": "2.2.16", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", - "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", - "dev": true, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, "license": "MIT" }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "inBundle": true, "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "devOptional": true, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "inBundle": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/object.entries": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", - "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", - "dev": true, - "license": "MIT", + "node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "inBundle": true, + "license": "ISC", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "minipass": "^7.0.4" }, "engines": { - "node": ">= 0.4" + "node": ">=18.0.0" } }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "license": "MIT", + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "license": "MIT", + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/metavuln-calculator": "^8.0.0", + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.1", + "@npmcli/query": "^4.0.0", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^9.0.1", + "bin-links": "^5.0.0", + "cacache": "^19.0.1", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^8.0.0", + "npm-install-checks": "^7.1.0", + "npm-package-arg": "^12.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.1", + "pacote": "^19.0.0", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "proggy": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "ssri": "^12.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" }, "engines": { - "node": ">= 0.4" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "dev": true, - "license": "MIT", + "node_modules/npm/node_modules/@npmcli/config": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/package-json": "^6.0.1", + "ci-info": "^4.0.0", + "ini": "^5.0.0", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", "dependencies": { - "ee-first": "1.1.1" + "semver": "^7.3.5" }, "engines": { - "node": ">= 0.8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "license": "MIT", + "node_modules/npm/node_modules/@npmcli/git": { + "version": "6.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, "engines": { - "node": ">= 0.8" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", + "inBundle": true, "license": "ISC", "dependencies": { - "wrappy": "1" + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "4.0.2", + "inBundle": true, + "license": "ISC", "dependencies": { - "mimic-fn": "^2.1.0" + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "license": "MIT", + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "8.0.1", + "inBundle": true, + "license": "ISC", "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" + "cacache": "^19.0.0", + "json-parse-even-better-errors": "^4.0.0", + "pacote": "^20.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { + "version": "20.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "6.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "normalize-package-data": "^7.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.1.2" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "9.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.3.2", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^7.0.0", + "npm-normalize-package-bin": "^4.0.0", + "proc-log": "^5.0.0", + "read-cmd-shim": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "19.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/minizlib": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/p-map": { + "version": "7.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.1.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.1.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.6", + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.7", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.2.0", + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.1", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.3.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.4.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.1", + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "7.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^6.0.0", + "npm-package-arg": "^12.0.0", + "promzard": "^2.0.0", + "read": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.1.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^4.1.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "3.4.3", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "binary-extensions": "^2.3.0", + "diff": "^5.1.0", + "minimatch": "^9.0.4", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "tar": "^6.2.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.0", + "@npmcli/run-script": "^9.0.1", + "ci-info": "^4.0.0", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "proc-log": "^5.0.0", + "read": "^4.0.0", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "11.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.0", + "@npmcli/run-script": "^9.0.1", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "10.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^7.0.0", + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1", + "proc-log": "^5.0.0", + "semver": "^7.3.7", + "sigstore": "^3.0.0", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.1", + "@npmcli/run-script": "^9.0.1", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "10.4.3", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "14.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.1.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-fetch/node_modules/minizlib": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "11.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/minizlib": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/nopt/node_modules/abbrev": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "7.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "7.1.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "12.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^7.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "10.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "11.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "18.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch/node_modules/minizlib": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "3.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.1", + "inBundle": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/npm/node_modules/pacote": { + "version": "19.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.11.1", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/proggy": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.2", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^2.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/rimraf": { + "version": "5.0.10", + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.6.3", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.0.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^3.0.0", + "@sigstore/tuf": "^3.0.0", + "@sigstore/verify": "^2.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": { + "version": "2.0.0", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.0.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^14.0.1", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { + "version": "2.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.0.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.8.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.20", + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/sprintf-js": { + "version": "1.1.3", + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/npm/node_modules/ssri": { + "version": "12.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.2.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "3.0.1", + "debug": "^4.3.6", + "make-fetch-happen": "^14.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/which": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nunjucks": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", + "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", + "license": "BSD-2-Clause", + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "chokidar": "^3.3.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/nunjucks/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/nwsapi": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", + "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/openapi-types": { @@ -21895,7 +24441,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" @@ -24434,7 +26979,6 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, "license": "ISC" }, "node_modules/simple-update-notifier": { @@ -25620,7 +28164,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, "license": "MIT" }, "node_modules/tfm-api": { @@ -27240,7 +29783,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -27405,7 +29947,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", @@ -28410,6 +30951,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@azure/msal-node": "^3.2.2", "@babel/polyfill": "^7.12.1", "@ministryofjustice/frontend": "3.0.2", "@ukef/dtfs2-common": "1.0.0", @@ -28433,6 +30975,7 @@ "express-validator": "7.0.1", "express-xss-sanitizer": "^1.2.1", "govuk-frontend": "^5.7.1", + "i": "^0.3.7", "imask": "^6.6.3", "joi": "^17.13.3", "jquery": "3.6.4", @@ -28442,6 +30985,7 @@ "lodash.orderby": "^4.6.0", "morgan": "1.10.0", "node-fetch": "^2.7.0", + "npm": "^10.9.2", "nunjucks": "3.2.4", "path": "0.12.7", "redis": "^3.1.2", @@ -28499,6 +31043,29 @@ "npm": ">=10.8.2" } }, + "trade-finance-manager-ui/node_modules/@azure/msal-common": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.1.1.tgz", + "integrity": "sha512-bvLWYq9fleAcTJ6H+hfkG91On6vI/UhGyOB7Z6r0Bsa+KTL3zPtigmGCOJgdxrEklOYD88X9SehexLDH/5NRKQ==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "trade-finance-manager-ui/node_modules/@azure/msal-node": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.2.2.tgz", + "integrity": "sha512-BmRNHHQ8y5tz8zMdLtpXnVSujc++5mW0cqz8S40Mf/qNjRGyIQdcNnMMa9j/jJzvMSDQhCbH7L0XWxfidV3LxA==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "15.1.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, "trade-finance-manager-ui/node_modules/@babel/core": { "version": "7.21.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.5.tgz", diff --git a/trade-finance-manager-ui/package.json b/trade-finance-manager-ui/package.json index 77c30e038c..4fc86c14ef 100644 --- a/trade-finance-manager-ui/package.json +++ b/trade-finance-manager-ui/package.json @@ -34,6 +34,7 @@ "unit-test-ff": "jest --coverage --verbose --config=unit.ff.jest.config.js --passWithNoTests" }, "dependencies": { + "@azure/msal-node": "^3.2.2", "@babel/polyfill": "^7.12.1", "@ministryofjustice/frontend": "3.0.2", "@ukef/dtfs2-common": "1.0.0", @@ -57,6 +58,7 @@ "express-validator": "7.0.1", "express-xss-sanitizer": "^1.2.1", "govuk-frontend": "^5.7.1", + "i": "^0.3.7", "imask": "^6.6.3", "joi": "^17.13.3", "jquery": "3.6.4", @@ -66,6 +68,7 @@ "lodash.orderby": "^4.6.0", "morgan": "1.10.0", "node-fetch": "^2.7.0", + "npm": "^10.9.2", "nunjucks": "3.2.4", "path": "0.12.7", "redis": "^3.1.2", From e509575db8e7e249ac152e1d5e6720431d3dc77d Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 14 Feb 2025 10:45:16 +0000 Subject: [PATCH 127/133] feat(dtfs2-6892): review comments --- trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts index 0a8ae7bf15..3c1f107261 100644 --- a/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts +++ b/trade-finance-manager-ui/server/routes/auth/configs/auth-sso.ts @@ -16,6 +16,7 @@ export const getAuthSsoRouter: GetRouter = () => { const loginController = new LoginController({ loginService, userSessionService }); const authSsoRouter = express.Router(); + // TODO DTFS2-6892: This router is to be deleted in future ticket authSsoRouter.post('/auth/sso-redirect/form', (req, res, next) => { loginController.handleSsoRedirectForm(req, res).catch(next); }); From c0a9b7eed7055493378d1654b85e6d1f04c94ccd Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 14 Feb 2025 10:48:46 +0000 Subject: [PATCH 128/133] Revert "feat(dtfs2-6892): add msal to tfm ui" This reverts commit 264eee30ee57ee5942601c154320d4e84ac58681. --- package-lock.json | 2929 ++----------------------- trade-finance-manager-ui/package.json | 3 - 2 files changed, 181 insertions(+), 2751 deletions(-) diff --git a/package-lock.json b/package-lock.json index 73b3546357..15fa7a904f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8817,6 +8817,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", @@ -11450,6 +11451,7 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, "funding": [ { "type": "github", @@ -11495,6 +11497,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -15892,6 +15895,7 @@ "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4.9.1" @@ -17234,14 +17238,6 @@ "url": "https://github.com/sponsors/typicode" } }, - "node_modules/i": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", - "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==", - "engines": { - "node": ">=0.4" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -17397,6 +17393,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -17432,6 +17429,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -20768,6 +20766,7 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -21542,166 +21541,6 @@ "node": ">=0.10.0" } }, - "node_modules/npm": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.2.tgz", - "integrity": "sha512-iriPEPIkoMYUy3F6f3wwSZAU93E0Eg6cHwIR6jzzOXWSy+SD/rOODEs74cVONHKSx2obXtuUoyidVEhISrisgQ==", - "bundleDependencies": [ - "@isaacs/string-locale-compare", - "@npmcli/arborist", - "@npmcli/config", - "@npmcli/fs", - "@npmcli/map-workspaces", - "@npmcli/package-json", - "@npmcli/promise-spawn", - "@npmcli/redact", - "@npmcli/run-script", - "@sigstore/tuf", - "abbrev", - "archy", - "cacache", - "chalk", - "ci-info", - "cli-columns", - "fastest-levenshtein", - "fs-minipass", - "glob", - "graceful-fs", - "hosted-git-info", - "ini", - "init-package-json", - "is-cidr", - "json-parse-even-better-errors", - "libnpmaccess", - "libnpmdiff", - "libnpmexec", - "libnpmfund", - "libnpmhook", - "libnpmorg", - "libnpmpack", - "libnpmpublish", - "libnpmsearch", - "libnpmteam", - "libnpmversion", - "make-fetch-happen", - "minimatch", - "minipass", - "minipass-pipeline", - "ms", - "node-gyp", - "nopt", - "normalize-package-data", - "npm-audit-report", - "npm-install-checks", - "npm-package-arg", - "npm-pick-manifest", - "npm-profile", - "npm-registry-fetch", - "npm-user-validate", - "p-map", - "pacote", - "parse-conflict-json", - "proc-log", - "qrcode-terminal", - "read", - "semver", - "spdx-expression-parse", - "ssri", - "supports-color", - "tar", - "text-table", - "tiny-relative-date", - "treeverse", - "validate-npm-package-name", - "which", - "write-file-atomic" - ], - "license": "Artistic-2.0", - "workspaces": [ - "docs", - "smoke-tests", - "mock-globals", - "mock-registry", - "workspaces/*" - ], - "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^8.0.0", - "@npmcli/config": "^9.0.0", - "@npmcli/fs": "^4.0.0", - "@npmcli/map-workspaces": "^4.0.2", - "@npmcli/package-json": "^6.1.0", - "@npmcli/promise-spawn": "^8.0.2", - "@npmcli/redact": "^3.0.0", - "@npmcli/run-script": "^9.0.1", - "@sigstore/tuf": "^3.0.0", - "abbrev": "^3.0.0", - "archy": "~1.0.0", - "cacache": "^19.0.1", - "chalk": "^5.3.0", - "ci-info": "^4.1.0", - "cli-columns": "^4.0.0", - "fastest-levenshtein": "^1.0.16", - "fs-minipass": "^3.0.3", - "glob": "^10.4.5", - "graceful-fs": "^4.2.11", - "hosted-git-info": "^8.0.2", - "ini": "^5.0.0", - "init-package-json": "^7.0.2", - "is-cidr": "^5.1.0", - "json-parse-even-better-errors": "^4.0.0", - "libnpmaccess": "^9.0.0", - "libnpmdiff": "^7.0.0", - "libnpmexec": "^9.0.0", - "libnpmfund": "^6.0.0", - "libnpmhook": "^11.0.0", - "libnpmorg": "^7.0.0", - "libnpmpack": "^8.0.0", - "libnpmpublish": "^10.0.1", - "libnpmsearch": "^8.0.0", - "libnpmteam": "^7.0.0", - "libnpmversion": "^7.0.0", - "make-fetch-happen": "^14.0.3", - "minimatch": "^9.0.5", - "minipass": "^7.1.1", - "minipass-pipeline": "^1.2.4", - "ms": "^2.1.2", - "node-gyp": "^11.0.0", - "nopt": "^8.0.0", - "normalize-package-data": "^7.0.0", - "npm-audit-report": "^6.0.0", - "npm-install-checks": "^7.1.1", - "npm-package-arg": "^12.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-profile": "^11.0.1", - "npm-registry-fetch": "^18.0.2", - "npm-user-validate": "^3.0.0", - "p-map": "^4.0.0", - "pacote": "^19.0.1", - "parse-conflict-json": "^4.0.0", - "proc-log": "^5.0.0", - "qrcode-terminal": "^0.12.0", - "read": "^4.0.0", - "semver": "^7.6.3", - "spdx-expression-parse": "^4.0.0", - "ssri": "^12.0.0", - "supports-color": "^9.4.0", - "tar": "^6.2.1", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "treeverse": "^3.0.0", - "validate-npm-package-name": "^6.0.0", - "which": "^5.0.0", - "write-file-atomic": "^6.0.0" - }, - "bin": { - "npm": "bin/npm-cli.js", - "npx": "bin/npx-cli.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -21715,2625 +21554,240 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/@isaacs/cliui": { - "version": "8.0.2", - "inBundle": true, - "license": "ISC", + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nunjucks": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", + "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", + "license": "BSD-2-Clause", + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" }, "engines": { - "node": ">=12" + "node": ">= 6.9.0" + }, + "peerDependencies": { + "chokidar": "^3.3.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } } }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "inBundle": true, + "node_modules/nunjucks/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">= 6" } }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "inBundle": true, + "node_modules/nwsapi": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", + "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", + "dev": true, "license": "MIT" }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "inBundle": true, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, "engines": { - "node": ">=12" + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "inBundle": true, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "devOptional": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/npm/node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "inBundle": true, - "license": "ISC", + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^7.0.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">= 0.4" } }, - "node_modules/npm/node_modules/@isaacs/string-locale-compare": { - "version": "1.1.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/@npmcli/agent": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.3" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "8.0.0", - "inBundle": true, - "license": "ISC", + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^4.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/map-workspaces": "^4.0.1", - "@npmcli/metavuln-calculator": "^8.0.0", - "@npmcli/name-from-folder": "^3.0.0", - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.1", - "@npmcli/query": "^4.0.0", - "@npmcli/redact": "^3.0.0", - "@npmcli/run-script": "^9.0.1", - "bin-links": "^5.0.0", - "cacache": "^19.0.1", - "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "json-stringify-nice": "^1.1.4", - "lru-cache": "^10.2.2", - "minimatch": "^9.0.4", - "nopt": "^8.0.0", - "npm-install-checks": "^7.1.0", - "npm-package-arg": "^12.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.1", - "pacote": "^19.0.0", - "parse-conflict-json": "^4.0.0", - "proc-log": "^5.0.0", - "proggy": "^3.0.0", - "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^3.0.1", - "read-package-json-fast": "^4.0.0", - "semver": "^7.3.7", - "ssri": "^12.0.0", - "treeverse": "^3.0.0", - "walk-up-path": "^3.0.1" - }, - "bin": { - "arborist": "bin/index.js" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.4" } }, - "node_modules/npm/node_modules/@npmcli/config": { - "version": "9.0.0", - "inBundle": true, - "license": "ISC", + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", "dependencies": { - "@npmcli/map-workspaces": "^4.0.1", - "@npmcli/package-json": "^6.0.1", - "ci-info": "^4.0.0", - "ini": "^5.0.0", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "walk-up-path": "^3.0.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/npm/node_modules/@npmcli/fs": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { - "semver": "^7.3.5" + "ee-first": "1.1.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.8" } }, - "node_modules/npm/node_modules/@npmcli/git": { - "version": "6.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/promise-spawn": "^8.0.0", - "ini": "^5.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^10.0.0", - "proc-log": "^5.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^5.0.0" - }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.8" } }, - "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "3.0.0", - "inBundle": true, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { - "npm-bundled": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "wrappy": "1" } }, - "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "4.0.2", - "inBundle": true, - "license": "ISC", + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", "dependencies": { - "@npmcli/name-from-folder": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "glob": "^10.2.2", - "minimatch": "^9.0.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "8.0.1", - "inBundle": true, - "license": "ISC", + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", "dependencies": { - "cacache": "^19.0.0", - "json-parse-even-better-errors": "^4.0.0", - "pacote": "^20.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5" + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { - "version": "20.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^9.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "bin/index.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/name-from-folder": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/node-gyp": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "6.1.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^6.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "normalize-package-data": "^7.0.0", - "proc-log": "^5.0.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "8.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "which": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/query": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "postcss-selector-parser": "^6.1.2" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/redact": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "9.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "node-gyp": "^11.0.0", - "proc-log": "^5.0.0", - "which": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "inBundle": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.3.2", - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "3.0.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2", - "tuf-js": "^3.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/@tufjs/canonical-json": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/abbrev": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/agent-base": { - "version": "7.1.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/aggregate-error": { - "version": "3.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/ansi-styles": { - "version": "6.2.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/npm/node_modules/aproba": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/archy": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/balanced-match": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/bin-links": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "cmd-shim": "^7.0.0", - "npm-normalize-package-bin": "^4.0.0", - "proc-log": "^5.0.0", - "read-cmd-shim": "^5.0.0", - "write-file-atomic": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/binary-extensions": { - "version": "2.3.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/brace-expansion": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/npm/node_modules/cacache": { - "version": "19.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^4.0.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/chownr": { - "version": "3.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/minizlib": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/p-map": { - "version": "7.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/tar": { - "version": "7.4.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/cacache/node_modules/yallist": { - "version": "5.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/chalk": { - "version": "5.3.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/npm/node_modules/chownr": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/ci-info": { - "version": "4.1.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/cidr-regex": { - "version": "4.1.1", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "ip-regex": "^5.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/npm/node_modules/clean-stack": { - "version": "2.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/cli-columns": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/npm/node_modules/cmd-shim": { - "version": "7.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/color-convert": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/npm/node_modules/color-name": { - "version": "1.1.4", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/common-ancestor-path": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/cross-spawn": { - "version": "7.0.6", - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/cssesc": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/debug": { - "version": "4.3.7", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/npm/node_modules/diff": { - "version": "5.2.0", - "inBundle": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/npm/node_modules/eastasianwidth": { - "version": "0.2.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/emoji-regex": { - "version": "8.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/encoding": { - "version": "0.1.13", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/npm/node_modules/env-paths": { - "version": "2.2.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/npm/node_modules/err-code": { - "version": "2.0.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/exponential-backoff": { - "version": "3.1.1", - "inBundle": true, - "license": "Apache-2.0" - }, - "node_modules/npm/node_modules/fastest-levenshtein": { - "version": "1.0.16", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/npm/node_modules/foreground-child": { - "version": "3.3.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/fs-minipass": { - "version": "3.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/glob": { - "version": "10.4.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/graceful-fs": { - "version": "4.2.11", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/hosted-git-info": { - "version": "8.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/http-cache-semantics": { - "version": "4.1.1", - "inBundle": true, - "license": "BSD-2-Clause" - }, - "node_modules/npm/node_modules/http-proxy-agent": { - "version": "7.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.5", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/iconv-lite": { - "version": "0.6.3", - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm/node_modules/ignore-walk": { - "version": "7.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minimatch": "^9.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/imurmurhash": { - "version": "0.1.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/npm/node_modules/indent-string": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/ini": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/init-package-json": { - "version": "7.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/package-json": "^6.0.0", - "npm-package-arg": "^12.0.0", - "promzard": "^2.0.0", - "read": "^4.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/ip-address": { - "version": "9.0.5", - "inBundle": true, - "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/npm/node_modules/ip-regex": { - "version": "5.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/is-cidr": { - "version": "5.1.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "cidr-regex": "^4.1.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/npm/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/isexe": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/jackspeak": { - "version": "3.4.3", - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/npm/node_modules/jsbn": { - "version": "1.1.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/json-stringify-nice": { - "version": "1.1.4", - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/jsonparse": { - "version": "1.3.1", - "engines": [ - "node >= 0.2.0" - ], - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/just-diff": { - "version": "6.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/just-diff-apply": { - "version": "5.5.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/libnpmaccess": { - "version": "9.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-package-arg": "^12.0.0", - "npm-registry-fetch": "^18.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmdiff": { - "version": "7.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^8.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "binary-extensions": "^2.3.0", - "diff": "^5.1.0", - "minimatch": "^9.0.4", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0", - "tar": "^6.2.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmexec": { - "version": "9.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^8.0.0", - "@npmcli/run-script": "^9.0.1", - "ci-info": "^4.0.0", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0", - "proc-log": "^5.0.0", - "read": "^4.0.0", - "read-package-json-fast": "^4.0.0", - "semver": "^7.3.7", - "walk-up-path": "^3.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmfund": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^8.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmhook": { - "version": "11.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmorg": { - "version": "7.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmpack": { - "version": "8.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/arborist": "^8.0.0", - "@npmcli/run-script": "^9.0.1", - "npm-package-arg": "^12.0.0", - "pacote": "^19.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmpublish": { - "version": "10.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "ci-info": "^4.0.0", - "normalize-package-data": "^7.0.0", - "npm-package-arg": "^12.0.0", - "npm-registry-fetch": "^18.0.1", - "proc-log": "^5.0.0", - "semver": "^7.3.7", - "sigstore": "^3.0.0", - "ssri": "^12.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmsearch": { - "version": "8.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-registry-fetch": "^18.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmteam": { - "version": "7.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^18.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/libnpmversion": { - "version": "7.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^6.0.1", - "@npmcli/run-script": "^9.0.1", - "json-parse-even-better-errors": "^4.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/lru-cache": { - "version": "10.4.3", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/make-fetch-happen": { - "version": "14.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "ssri": "^12.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/npm/node_modules/minimatch": { - "version": "9.0.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/minipass": { - "version": "7.1.2", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/npm/node_modules/minipass-collect": { - "version": "2.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/npm/node_modules/minipass-fetch": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^3.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/npm/node_modules/minipass-fetch/node_modules/minizlib": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/npm/node_modules/minipass-flush": { - "version": "1.0.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-pipeline": { - "version": "1.2.4", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-sized": { - "version": "1.0.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/minizlib": { - "version": "2.1.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/mkdirp": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/ms": { - "version": "2.1.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/mute-stream": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/node-gyp": { - "version": "11.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "tar": "^7.4.3", - "which": "^5.0.0" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { - "version": "3.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/minizlib": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/tar": { - "version": "7.4.3", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { - "version": "5.0.0", - "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/npm/node_modules/nopt": { - "version": "8.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "abbrev": "^2.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/nopt/node_modules/abbrev": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/normalize-package-data": { - "version": "7.0.0", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^8.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-audit-report": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-bundled": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-normalize-package-bin": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-install-checks": { - "version": "7.1.1", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "semver": "^7.1.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-normalize-package-bin": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-package-arg": { - "version": "12.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-packlist": { - "version": "9.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "ignore-walk": "^7.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "10.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-install-checks": "^7.1.0", - "npm-normalize-package-bin": "^4.0.0", - "npm-package-arg": "^12.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-profile": { - "version": "11.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "18.0.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/redact": "^3.0.0", - "jsonparse": "^1.3.1", - "make-fetch-happen": "^14.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", - "minizlib": "^3.0.1", - "npm-package-arg": "^12.0.0", - "proc-log": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/npm-registry-fetch/node_modules/minizlib": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.0.4", - "rimraf": "^5.0.5" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/npm/node_modules/npm-user-validate": { - "version": "3.0.0", - "inBundle": true, - "license": "BSD-2-Clause", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/p-map": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/package-json-from-dist": { - "version": "1.0.1", - "inBundle": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/npm/node_modules/pacote": { - "version": "19.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^9.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "bin/index.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/parse-conflict-json": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^4.0.0", - "just-diff": "^6.0.0", - "just-diff-apply": "^5.2.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/path-key": { - "version": "3.1.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/path-scurry": { - "version": "1.11.1", - "inBundle": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "6.1.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm/node_modules/proc-log": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/proggy": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/promise-all-reject-late": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/promise-call-limit": { - "version": "3.0.2", - "inBundle": true, - "license": "ISC", - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/promise-inflight": { - "version": "1.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/promise-retry": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/promzard": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "read": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/qrcode-terminal": { - "version": "0.12.0", - "inBundle": true, - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" - } - }, - "node_modules/npm/node_modules/read": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "mute-stream": "^2.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/read-cmd-shim": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/read-package-json-fast": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "json-parse-even-better-errors": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/retry": { - "version": "0.12.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm/node_modules/rimraf": { - "version": "5.0.10", - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/safer-buffer": { - "version": "2.1.2", - "inBundle": true, - "license": "MIT", - "optional": true - }, - "node_modules/npm/node_modules/semver": { - "version": "7.6.3", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/shebang-command": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/shebang-regex": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/signal-exit": { - "version": "4.1.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/npm/node_modules/sigstore": { - "version": "3.0.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.0.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "@sigstore/sign": "^3.0.0", - "@sigstore/tuf": "^3.0.0", - "@sigstore/verify": "^2.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/bundle": { - "version": "3.0.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.3.2" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/core": { - "version": "2.0.0", - "inBundle": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/sign": { - "version": "3.0.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.0.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2", - "make-fetch-happen": "^14.0.1", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/sigstore/node_modules/@sigstore/verify": { - "version": "2.0.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.0.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.3.2" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/smart-buffer": { - "version": "4.2.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/npm/node_modules/socks": { - "version": "2.8.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.1", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/npm/node_modules/spdx-correct": { - "version": "3.2.0", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.5.0", - "inBundle": true, - "license": "CC-BY-3.0" - }, - "node_modules/npm/node_modules/spdx-expression-parse": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.20", - "inBundle": true, - "license": "CC0-1.0" - }, - "node_modules/npm/node_modules/sprintf-js": { - "version": "1.1.3", - "inBundle": true, - "license": "BSD-3-Clause" - }, - "node_modules/npm/node_modules/ssri": { - "version": "12.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/string-width": { - "version": "4.2.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/strip-ansi": { - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/supports-color": { - "version": "9.4.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/npm/node_modules/tar": { - "version": "6.2.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/npm/node_modules/text-table": { - "version": "0.2.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/tiny-relative-date": { - "version": "1.3.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/treeverse": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/tuf-js": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "@tufjs/models": "3.0.1", - "debug": "^4.3.6", - "make-fetch-happen": "^14.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/unique-filename": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "unique-slug": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/unique-slug": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/util-deprecate": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/validate-npm-package-license": { - "version": "3.0.4", - "inBundle": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { - "version": "3.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/walk-up-path": { - "version": "3.0.1", - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/which": { - "version": "5.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/which/node_modules/isexe": { - "version": "3.1.1", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">=16" - } - }, - "node_modules/npm/node_modules/wrap-ansi": { - "version": "8.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/npm/node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.1.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "9.2.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { - "version": "5.1.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/npm/node_modules/write-file-atomic": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm/node_modules/yallist": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC" - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/nunjucks": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", - "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", - "license": "BSD-2-Clause", - "dependencies": { - "a-sync-waterfall": "^1.0.0", - "asap": "^2.0.3", - "commander": "^5.1.0" - }, - "bin": { - "nunjucks-precompile": "bin/precompile" - }, - "engines": { - "node": ">= 6.9.0" - }, - "peerDependencies": { - "chokidar": "^3.3.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/nunjucks/node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/nwsapi": { - "version": "2.2.16", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", - "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", - "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/openapi-types": { @@ -24441,6 +21895,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" @@ -26979,6 +24434,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, "license": "ISC" }, "node_modules/simple-update-notifier": { @@ -28164,6 +25620,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, "license": "MIT" }, "node_modules/tfm-api": { @@ -29783,6 +27240,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -29947,6 +27405,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", @@ -30951,7 +28410,6 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@azure/msal-node": "^3.2.2", "@babel/polyfill": "^7.12.1", "@ministryofjustice/frontend": "3.0.2", "@ukef/dtfs2-common": "1.0.0", @@ -30975,7 +28433,6 @@ "express-validator": "7.0.1", "express-xss-sanitizer": "^1.2.1", "govuk-frontend": "^5.7.1", - "i": "^0.3.7", "imask": "^6.6.3", "joi": "^17.13.3", "jquery": "3.6.4", @@ -30985,7 +28442,6 @@ "lodash.orderby": "^4.6.0", "morgan": "1.10.0", "node-fetch": "^2.7.0", - "npm": "^10.9.2", "nunjucks": "3.2.4", "path": "0.12.7", "redis": "^3.1.2", @@ -31043,29 +28499,6 @@ "npm": ">=10.8.2" } }, - "trade-finance-manager-ui/node_modules/@azure/msal-common": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.1.1.tgz", - "integrity": "sha512-bvLWYq9fleAcTJ6H+hfkG91On6vI/UhGyOB7Z6r0Bsa+KTL3zPtigmGCOJgdxrEklOYD88X9SehexLDH/5NRKQ==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "trade-finance-manager-ui/node_modules/@azure/msal-node": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.2.2.tgz", - "integrity": "sha512-BmRNHHQ8y5tz8zMdLtpXnVSujc++5mW0cqz8S40Mf/qNjRGyIQdcNnMMa9j/jJzvMSDQhCbH7L0XWxfidV3LxA==", - "license": "MIT", - "dependencies": { - "@azure/msal-common": "15.1.1", - "jsonwebtoken": "^9.0.0", - "uuid": "^8.3.0" - }, - "engines": { - "node": ">=16" - } - }, "trade-finance-manager-ui/node_modules/@babel/core": { "version": "7.21.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.5.tgz", diff --git a/trade-finance-manager-ui/package.json b/trade-finance-manager-ui/package.json index 4fc86c14ef..77c30e038c 100644 --- a/trade-finance-manager-ui/package.json +++ b/trade-finance-manager-ui/package.json @@ -34,7 +34,6 @@ "unit-test-ff": "jest --coverage --verbose --config=unit.ff.jest.config.js --passWithNoTests" }, "dependencies": { - "@azure/msal-node": "^3.2.2", "@babel/polyfill": "^7.12.1", "@ministryofjustice/frontend": "3.0.2", "@ukef/dtfs2-common": "1.0.0", @@ -58,7 +57,6 @@ "express-validator": "7.0.1", "express-xss-sanitizer": "^1.2.1", "govuk-frontend": "^5.7.1", - "i": "^0.3.7", "imask": "^6.6.3", "joi": "^17.13.3", "jquery": "3.6.4", @@ -68,7 +66,6 @@ "lodash.orderby": "^4.6.0", "morgan": "1.10.0", "node-fetch": "^2.7.0", - "npm": "^10.9.2", "nunjucks": "3.2.4", "path": "0.12.7", "redis": "^3.1.2", From eceae2355eee7d04f6e054b6aca8d58c4d17e5fe Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 14 Feb 2025 10:49:24 +0000 Subject: [PATCH 129/133] feat(dtfs2-6892): add msal to tfm ui --- package-lock.json | 3 +-- package.json | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15fa7a904f..a8404b6b56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "utils" ], "dependencies": { + "@azure/msal": "^1.4.5", "axios": "1.7.8", "express": "^4.21.2", "release-please": "^16.15.0" @@ -2398,7 +2399,6 @@ "resolved": "https://registry.npmjs.org/@azure/msal/-/msal-1.4.5.tgz", "integrity": "sha512-ux+Bu5Na/PvTAoFTQxm/EDbUTuesxGEEuWDeJyjz2ep5a8y2VPfYdGSAi8Iz06cKlCTFT+4gSHb74FWboRiSkw==", "deprecated": "This package is no longer supported. Please use @azure/msal-browser instead.", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^1.9.3" @@ -2446,7 +2446,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, "license": "0BSD" }, "node_modules/@azure/storage-file-share": { diff --git a/package.json b/package.json index 9f4c00aa05..a9737371d5 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ ] }, "dependencies": { + "@azure/msal": "^1.4.5", "axios": "1.7.8", "express": "^4.21.2", "release-please": "^16.15.0" From c2a2310d2b10b5a77b4ae14c948156aa03a403f6 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 14 Feb 2025 10:49:54 +0000 Subject: [PATCH 130/133] Revert "feat(dtfs2-6892): add msal to tfm ui" This reverts commit eceae2355eee7d04f6e054b6aca8d58c4d17e5fe. --- package-lock.json | 3 ++- package.json | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a8404b6b56..15fa7a904f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,6 @@ "utils" ], "dependencies": { - "@azure/msal": "^1.4.5", "axios": "1.7.8", "express": "^4.21.2", "release-please": "^16.15.0" @@ -2399,6 +2398,7 @@ "resolved": "https://registry.npmjs.org/@azure/msal/-/msal-1.4.5.tgz", "integrity": "sha512-ux+Bu5Na/PvTAoFTQxm/EDbUTuesxGEEuWDeJyjz2ep5a8y2VPfYdGSAi8Iz06cKlCTFT+4gSHb74FWboRiSkw==", "deprecated": "This package is no longer supported. Please use @azure/msal-browser instead.", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^1.9.3" @@ -2446,6 +2446,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, "license": "0BSD" }, "node_modules/@azure/storage-file-share": { diff --git a/package.json b/package.json index a9737371d5..9f4c00aa05 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,6 @@ ] }, "dependencies": { - "@azure/msal": "^1.4.5", "axios": "1.7.8", "express": "^4.21.2", "release-please": "^16.15.0" From 64c2f41ba331c246786ab40f8c0e19b6ef0b4411 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 14 Feb 2025 10:50:24 +0000 Subject: [PATCH 131/133] feat(dtfs2-6892): add msal to tfm ui --- package-lock.json | 76 ++++++++++++++++++++++++++++++++++++++++++++--- package.json | 1 + 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15fa7a904f..134ef1b598 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "utils" ], "dependencies": { + "@azure/msal-node": "^3.2.2", "axios": "1.7.8", "express": "^4.21.2", "release-please": "^16.15.0" @@ -928,6 +929,20 @@ "npm": ">=10.8.2" } }, + "libs/common/node_modules/@azure/msal-node": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.2.tgz", + "integrity": "sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "14.16.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, "libs/common/node_modules/@types/node": { "version": "20.17.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.12.tgz", @@ -2270,6 +2285,20 @@ "node": ">=18.0.0" } }, + "node_modules/@azure/identity/node_modules/@azure/msal-node": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.2.tgz", + "integrity": "sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "14.16.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@azure/identity/node_modules/jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", @@ -2429,12 +2458,12 @@ } }, "node_modules/@azure/msal-node": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.2.tgz", - "integrity": "sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.2.2.tgz", + "integrity": "sha512-BmRNHHQ8y5tz8zMdLtpXnVSujc++5mW0cqz8S40Mf/qNjRGyIQdcNnMMa9j/jJzvMSDQhCbH7L0XWxfidV3LxA==", "license": "MIT", "dependencies": { - "@azure/msal-common": "14.16.0", + "@azure/msal-common": "15.1.1", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" }, @@ -2442,6 +2471,15 @@ "node": ">=16" } }, + "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.1.1.tgz", + "integrity": "sha512-bvLWYq9fleAcTJ6H+hfkG91On6vI/UhGyOB7Z6r0Bsa+KTL3zPtigmGCOJgdxrEklOYD88X9SehexLDH/5NRKQ==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@azure/msal/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -21020,6 +21058,22 @@ "node": ">=14.0.0" } }, + "node_modules/mssql/node_modules/@azure/msal-node": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.2.tgz", + "integrity": "sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@azure/msal-common": "14.16.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/mssql/node_modules/bl": { "version": "6.0.18", "resolved": "https://registry.npmjs.org/bl/-/bl-6.0.18.tgz", @@ -28117,6 +28171,20 @@ "npm": ">=10.8.2" } }, + "trade-finance-manager-api/node_modules/@azure/msal-node": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.2.tgz", + "integrity": "sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "14.16.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, "trade-finance-manager-api/node_modules/@babel/helper-define-polyfill-provider": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", diff --git a/package.json b/package.json index 9f4c00aa05..eab122adf5 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ ] }, "dependencies": { + "@azure/msal-node": "^3.2.2", "axios": "1.7.8", "express": "^4.21.2", "release-please": "^16.15.0" From b013e6d85a91a5e8d24599a40b53756ee87b1d67 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 14 Feb 2025 10:52:00 +0000 Subject: [PATCH 132/133] Revert "feat(dtfs2-6892): add msal to tfm ui" This reverts commit 64c2f41ba331c246786ab40f8c0e19b6ef0b4411. --- package-lock.json | 76 +++-------------------------------------------- package.json | 1 - 2 files changed, 4 insertions(+), 73 deletions(-) diff --git a/package-lock.json b/package-lock.json index 134ef1b598..15fa7a904f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,6 @@ "utils" ], "dependencies": { - "@azure/msal-node": "^3.2.2", "axios": "1.7.8", "express": "^4.21.2", "release-please": "^16.15.0" @@ -929,20 +928,6 @@ "npm": ">=10.8.2" } }, - "libs/common/node_modules/@azure/msal-node": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.2.tgz", - "integrity": "sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==", - "license": "MIT", - "dependencies": { - "@azure/msal-common": "14.16.0", - "jsonwebtoken": "^9.0.0", - "uuid": "^8.3.0" - }, - "engines": { - "node": ">=16" - } - }, "libs/common/node_modules/@types/node": { "version": "20.17.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.12.tgz", @@ -2285,20 +2270,6 @@ "node": ">=18.0.0" } }, - "node_modules/@azure/identity/node_modules/@azure/msal-node": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.2.tgz", - "integrity": "sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==", - "license": "MIT", - "dependencies": { - "@azure/msal-common": "14.16.0", - "jsonwebtoken": "^9.0.0", - "uuid": "^8.3.0" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/@azure/identity/node_modules/jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", @@ -2458,12 +2429,12 @@ } }, "node_modules/@azure/msal-node": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.2.2.tgz", - "integrity": "sha512-BmRNHHQ8y5tz8zMdLtpXnVSujc++5mW0cqz8S40Mf/qNjRGyIQdcNnMMa9j/jJzvMSDQhCbH7L0XWxfidV3LxA==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.2.tgz", + "integrity": "sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==", "license": "MIT", "dependencies": { - "@azure/msal-common": "15.1.1", + "@azure/msal-common": "14.16.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" }, @@ -2471,15 +2442,6 @@ "node": ">=16" } }, - "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.1.1.tgz", - "integrity": "sha512-bvLWYq9fleAcTJ6H+hfkG91On6vI/UhGyOB7Z6r0Bsa+KTL3zPtigmGCOJgdxrEklOYD88X9SehexLDH/5NRKQ==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/@azure/msal/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -21058,22 +21020,6 @@ "node": ">=14.0.0" } }, - "node_modules/mssql/node_modules/@azure/msal-node": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.2.tgz", - "integrity": "sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@azure/msal-common": "14.16.0", - "jsonwebtoken": "^9.0.0", - "uuid": "^8.3.0" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/mssql/node_modules/bl": { "version": "6.0.18", "resolved": "https://registry.npmjs.org/bl/-/bl-6.0.18.tgz", @@ -28171,20 +28117,6 @@ "npm": ">=10.8.2" } }, - "trade-finance-manager-api/node_modules/@azure/msal-node": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.2.tgz", - "integrity": "sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==", - "license": "MIT", - "dependencies": { - "@azure/msal-common": "14.16.0", - "jsonwebtoken": "^9.0.0", - "uuid": "^8.3.0" - }, - "engines": { - "node": ">=16" - } - }, "trade-finance-manager-api/node_modules/@babel/helper-define-polyfill-provider": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", diff --git a/package.json b/package.json index eab122adf5..9f4c00aa05 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,6 @@ ] }, "dependencies": { - "@azure/msal-node": "^3.2.2", "axios": "1.7.8", "express": "^4.21.2", "release-please": "^16.15.0" From d2896eaf32eb8785c1eabce91202f11be592d204 Mon Sep 17 00:00:00 2001 From: Alex Bramhill Date: Fri, 14 Feb 2025 10:54:00 +0000 Subject: [PATCH 133/133] feat(dtfs2-6892): add msal to tfm ui --- package-lock.json | 46 ++++++++++++++------------- trade-finance-manager-ui/package.json | 2 +- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15fa7a904f..e0e83701f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2393,20 +2393,6 @@ "node": ">=18.0.0" } }, - "node_modules/@azure/msal": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@azure/msal/-/msal-1.4.5.tgz", - "integrity": "sha512-ux+Bu5Na/PvTAoFTQxm/EDbUTuesxGEEuWDeJyjz2ep5a8y2VPfYdGSAi8Iz06cKlCTFT+4gSHb74FWboRiSkw==", - "deprecated": "This package is no longer supported. Please use @azure/msal-browser instead.", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/@azure/msal-browser": { "version": "3.28.0", "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.28.0.tgz", @@ -2442,13 +2428,6 @@ "node": ">=16" } }, - "node_modules/@azure/msal/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" - }, "node_modules/@azure/storage-file-share": { "version": "12.14.0", "resolved": "https://registry.npmjs.org/@azure/storage-file-share/-/storage-file-share-12.14.0.tgz", @@ -28410,6 +28389,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@azure/msal-node": "^3.2.2", "@babel/polyfill": "^7.12.1", "@ministryofjustice/frontend": "3.0.2", "@ukef/dtfs2-common": "1.0.0", @@ -28454,7 +28434,6 @@ "zod": "^3.24.1" }, "devDependencies": { - "@azure/msal": "^1.4.5", "@babel/core": "7.21.5", "@babel/plugin-transform-runtime": "7.21.4", "@babel/preset-env": "7.21.5", @@ -28499,6 +28478,29 @@ "npm": ">=10.8.2" } }, + "trade-finance-manager-ui/node_modules/@azure/msal-common": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.1.1.tgz", + "integrity": "sha512-bvLWYq9fleAcTJ6H+hfkG91On6vI/UhGyOB7Z6r0Bsa+KTL3zPtigmGCOJgdxrEklOYD88X9SehexLDH/5NRKQ==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "trade-finance-manager-ui/node_modules/@azure/msal-node": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.2.2.tgz", + "integrity": "sha512-BmRNHHQ8y5tz8zMdLtpXnVSujc++5mW0cqz8S40Mf/qNjRGyIQdcNnMMa9j/jJzvMSDQhCbH7L0XWxfidV3LxA==", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "15.1.1", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, "trade-finance-manager-ui/node_modules/@babel/core": { "version": "7.21.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.5.tgz", diff --git a/trade-finance-manager-ui/package.json b/trade-finance-manager-ui/package.json index 77c30e038c..76f2e009b9 100644 --- a/trade-finance-manager-ui/package.json +++ b/trade-finance-manager-ui/package.json @@ -34,6 +34,7 @@ "unit-test-ff": "jest --coverage --verbose --config=unit.ff.jest.config.js --passWithNoTests" }, "dependencies": { + "@azure/msal-node": "^3.2.2", "@babel/polyfill": "^7.12.1", "@ministryofjustice/frontend": "3.0.2", "@ukef/dtfs2-common": "1.0.0", @@ -78,7 +79,6 @@ "zod": "^3.24.1" }, "devDependencies": { - "@azure/msal": "^1.4.5", "@babel/core": "7.21.5", "@babel/plugin-transform-runtime": "7.21.4", "@babel/preset-env": "7.21.5",