From c26fc3d9c993e4c6341d6f73c84fd5d03a3ab840 Mon Sep 17 00:00:00 2001 From: BeckaL Date: Thu, 16 May 2024 17:26:52 +0100 Subject: [PATCH] AUT-2578: Introduce test for check reauth user service This also introduces a common test helper for service tests, since there will be a lot of repetition as we add these tests --- .../account-intervention-service.test.ts | 63 ++++++++++ .../tests/check-reauth-user-service.test.ts | 69 +++++++++-- .../tests/account-recovery-service.test.ts | 63 ++++++++++ .../tests/send-notification-service.test.ts | 111 ++++++++++++++++++ .../test/update-profile-service.test.ts | 66 +++++++++++ .../tests/verify-code-service.test.ts | 72 ++++++++++++ .../test/verify-mfa-code-service.test.ts | 74 ++++++++++++ test/helpers/service-test-helper.ts | 66 +++++++++++ 8 files changed, 572 insertions(+), 12 deletions(-) create mode 100644 src/components/account-intervention/tests/account-intervention-service.test.ts create mode 100644 src/components/common/account-recovery/tests/account-recovery-service.test.ts create mode 100644 src/components/common/send-notification/tests/send-notification-service.test.ts create mode 100644 src/components/common/update-profile/test/update-profile-service.test.ts create mode 100644 src/components/common/verify-code/tests/verify-code-service.test.ts create mode 100644 src/components/common/verify-mfa-code/test/verify-mfa-code-service.test.ts create mode 100644 test/helpers/service-test-helper.ts diff --git a/src/components/account-intervention/tests/account-intervention-service.test.ts b/src/components/account-intervention/tests/account-intervention-service.test.ts new file mode 100644 index 0000000000..dab629679c --- /dev/null +++ b/src/components/account-intervention/tests/account-intervention-service.test.ts @@ -0,0 +1,63 @@ +import { describe } from "mocha"; +import sinon, { SinonStub } from "sinon"; +import { AccountInterventionsInterface } from "../types"; +import { accountInterventionService } from "../account-intervention-service"; +import { + checkApiCallMadeWithExpectedBodyAndHeaders, + commonVariables, + expectedHeadersFromCommonVarsWithoutSecurityHeaders, + resetApiKeyAndBaseUrlEnvVars, + setupApiKeyAndBaseUrlEnvVars, +} from "../../../../test/helpers/service-test-helper"; +import { API_ENDPOINTS } from "../../../app.constants"; +import { Http } from "../../../utils/http"; + +describe("account interventions service", () => { + const httpInstance = new Http(); + const service: AccountInterventionsInterface = + accountInterventionService(httpInstance); + let postStub: SinonStub; + + beforeEach(() => { + setupApiKeyAndBaseUrlEnvVars(); + postStub = sinon.stub(httpInstance.client, "post"); + }); + + afterEach(() => { + postStub.reset(); + resetApiKeyAndBaseUrlEnvVars(); + }); + + it("successfully calls the API to check a user's account interventions status", async () => { + const axiosResponse = Promise.resolve({ + data: {}, + status: 200, + statusText: "OK", + }); + postStub.resolves(axiosResponse); + const { sessionId, clientSessionId, email, ip, diPersistentSessionId } = + commonVariables; + + const underTest = () => + service.accountInterventionStatus( + sessionId, + email, + ip, + clientSessionId, + diPersistentSessionId + ); + + const expectedApiCallDetails = { + expectedPath: API_ENDPOINTS.ACCOUNT_INTERVENTIONS, + expectedHeaders: expectedHeadersFromCommonVarsWithoutSecurityHeaders, + expectedBody: { email: commonVariables.email }, + }; + + await checkApiCallMadeWithExpectedBodyAndHeaders( + underTest, + postStub, + true, + expectedApiCallDetails + ); + }); +}); diff --git a/src/components/check-reauth-users/tests/check-reauth-user-service.test.ts b/src/components/check-reauth-users/tests/check-reauth-user-service.test.ts index b9ffa846ab..871391816f 100644 --- a/src/components/check-reauth-users/tests/check-reauth-user-service.test.ts +++ b/src/components/check-reauth-users/tests/check-reauth-user-service.test.ts @@ -1,22 +1,67 @@ -import { expect } from "chai"; import { describe } from "mocha"; - -import { supportReauthentication } from "../../../config"; +import { Http } from "../../../utils/http"; +import { sinon } from "../../../../test/utils/test-utils"; +import { API_ENDPOINTS } from "../../../app.constants"; +import { SinonStub } from "sinon"; +import { checkReauthUsersService } from "../check-reauth-users-service"; +import { CheckReauthServiceInterface } from "../types"; +import { + checkApiCallMadeWithExpectedBodyAndHeaders, + commonVariables, + expectedHeadersFromCommonVarsWithoutSecurityHeaders, + resetApiKeyAndBaseUrlEnvVars, + setupApiKeyAndBaseUrlEnvVars, +} from "../../../../test/helpers/service-test-helper"; describe("re-authentication service", () => { - describe("with auth re-authentication feature flag on", () => { - it("should return true", async () => { - process.env.SUPPORT_REAUTHENTICATION = "1"; + const httpInstance = new Http(); + const service: CheckReauthServiceInterface = + checkReauthUsersService(httpInstance); + const SUBJECT = "123"; + let postStub: SinonStub; - expect(supportReauthentication()).to.be.true; - }); + beforeEach(() => { + setupApiKeyAndBaseUrlEnvVars(); + postStub = sinon.stub(httpInstance.client, "post"); }); - describe("with auth re-authentication feature flag off", () => { - it("should return false", async () => { - process.env.SUPPORT_REAUTHENTICATION = "0"; + afterEach(() => { + postStub.reset(); + resetApiKeyAndBaseUrlEnvVars(); + }); - expect(supportReauthentication()).to.be.false; + it("successfully calls the API to check a reauth user", async () => { + const axiosResponse = Promise.resolve({ + data: {}, + status: 200, + statusText: "OK", }); + postStub.resolves(axiosResponse); + const { sessionId, email, ip, clientSessionId, diPersistentSessionId } = + commonVariables; + + const underTest = () => + service.checkReauthUsers( + sessionId, + email, + SUBJECT, + ip, + clientSessionId, + diPersistentSessionId + ); + + const expectedApiCallDetails = { + expectedPath: API_ENDPOINTS.CHECK_REAUTH_USER, + expectedHeaders: expectedHeadersFromCommonVarsWithoutSecurityHeaders, + expectedBody: { email: commonVariables.email, rpPairwiseId: SUBJECT }, + validateStatus: true, + }; + + await checkApiCallMadeWithExpectedBodyAndHeaders( + underTest, + postStub, + true, + expectedApiCallDetails + ); }); }); diff --git a/src/components/common/account-recovery/tests/account-recovery-service.test.ts b/src/components/common/account-recovery/tests/account-recovery-service.test.ts new file mode 100644 index 0000000000..e60c93276b --- /dev/null +++ b/src/components/common/account-recovery/tests/account-recovery-service.test.ts @@ -0,0 +1,63 @@ +import { describe } from "mocha"; +import sinon, { SinonStub } from "sinon"; +import { Http } from "../../../../utils/http"; +import { AccountRecoveryInterface } from "../types"; +import { accountRecoveryService } from "../account-recovery-service"; +import { + checkApiCallMadeWithExpectedBodyAndHeaders, + commonVariables, + expectedHeadersFromCommonVarsWithoutSecurityHeaders, + resetApiKeyAndBaseUrlEnvVars, + setupApiKeyAndBaseUrlEnvVars, +} from "../../../../../test/helpers/service-test-helper"; +import { API_ENDPOINTS } from "../../../../app.constants"; + +describe("account recovery service", () => { + const httpInstance = new Http(); + const service: AccountRecoveryInterface = + accountRecoveryService(httpInstance); + let postStub: SinonStub; + + beforeEach(() => { + setupApiKeyAndBaseUrlEnvVars(); + postStub = sinon.stub(httpInstance.client, "post"); + }); + + afterEach(() => { + postStub.reset(); + resetApiKeyAndBaseUrlEnvVars(); + }); + + it("successfully calls the API to perform an account recovery request", async () => { + const axiosResponse = Promise.resolve({ + data: {}, + status: 200, + statusText: "OK", + }); + postStub.resolves(axiosResponse); + const { sessionId, clientSessionId, email, ip, diPersistentSessionId } = + commonVariables; + + const underTest = () => + service.accountRecovery( + sessionId, + clientSessionId, + email, + ip, + diPersistentSessionId + ); + + const expectedApiCallDetails = { + expectedPath: API_ENDPOINTS.ACCOUNT_RECOVERY, + expectedHeaders: expectedHeadersFromCommonVarsWithoutSecurityHeaders, + expectedBody: { email: commonVariables.email }, + }; + + await checkApiCallMadeWithExpectedBodyAndHeaders( + underTest, + postStub, + true, + expectedApiCallDetails + ); + }); +}); diff --git a/src/components/common/send-notification/tests/send-notification-service.test.ts b/src/components/common/send-notification/tests/send-notification-service.test.ts new file mode 100644 index 0000000000..d9d25a874c --- /dev/null +++ b/src/components/common/send-notification/tests/send-notification-service.test.ts @@ -0,0 +1,111 @@ +import { describe } from "mocha"; +import sinon, { SinonStub } from "sinon"; +import { Http } from "../../../../utils/http"; +import { + checkApiCallMadeWithExpectedBodyAndHeaders, + commonVariables, + expectedHeadersFromCommonVarsWithoutSecurityHeaders, + resetApiKeyAndBaseUrlEnvVars, + setupApiKeyAndBaseUrlEnvVars, +} from "../../../../../test/helpers/service-test-helper"; +import { API_ENDPOINTS, NOTIFICATION_TYPE } from "../../../../app.constants"; +import { SendNotificationServiceInterface } from "../types"; +import { sendNotificationService } from "../send-notification-service"; +import { JOURNEY_TYPE } from "../../constants"; + +describe("send notification service", () => { + let postStub: SinonStub; + let service: SendNotificationServiceInterface; + const axiosResponse = Promise.resolve({ + data: {}, + status: 200, + statusText: "OK", + }); + const { sessionId, clientSessionId, email, ip, diPersistentSessionId } = + commonVariables; + const notificationType = NOTIFICATION_TYPE.VERIFY_EMAIL; + const userLanguage = "cy"; + const expectedHeaders = { + ...expectedHeadersFromCommonVarsWithoutSecurityHeaders, + "User-Language": userLanguage, + }; + + beforeEach(() => { + const httpInstance = new Http(); + service = sendNotificationService(httpInstance); + postStub = sinon.stub(httpInstance.client, "post"); + setupApiKeyAndBaseUrlEnvVars(); + postStub.resolves(axiosResponse); + }); + + afterEach(() => { + postStub.reset(); + resetApiKeyAndBaseUrlEnvVars(); + }); + + it("successfully calls the API to send a notification", async () => { + const underTest = () => + service.sendNotification( + sessionId, + clientSessionId, + email, + notificationType, + ip, + diPersistentSessionId, + userLanguage + ); + + const expectedApiCallDetails = { + expectedPath: API_ENDPOINTS.SEND_NOTIFICATION, + expectedHeaders, + expectedBody: { email, notificationType }, + validateStatus: true, + }; + + await checkApiCallMadeWithExpectedBodyAndHeaders( + underTest, + postStub, + true, + expectedApiCallDetails + ); + }); + + it("adds the additional details to the payload when these are included", async () => { + const journeyType = JOURNEY_TYPE.CREATE_ACCOUNT; + const phoneNumber = "123456"; + const requestNewCode = true; + const underTest = () => + service.sendNotification( + sessionId, + clientSessionId, + email, + notificationType, + ip, + diPersistentSessionId, + userLanguage, + journeyType, + phoneNumber, + requestNewCode + ); + + const expectedApiCallDetails = { + expectedPath: API_ENDPOINTS.SEND_NOTIFICATION, + expectedHeaders, + expectedBody: { + email, + notificationType, + journeyType, + phoneNumber, + requestNewCode, + }, + validateStatus: true, + }; + + await checkApiCallMadeWithExpectedBodyAndHeaders( + underTest, + postStub, + true, + expectedApiCallDetails + ); + }); +}); diff --git a/src/components/common/update-profile/test/update-profile-service.test.ts b/src/components/common/update-profile/test/update-profile-service.test.ts new file mode 100644 index 0000000000..5ef4541379 --- /dev/null +++ b/src/components/common/update-profile/test/update-profile-service.test.ts @@ -0,0 +1,66 @@ +import { describe } from "mocha"; +import sinon, { SinonStub } from "sinon"; +import { Http } from "../../../../utils/http"; +import { UpdateProfileServiceInterface, UpdateType } from "../types"; +import { + checkApiCallMadeWithExpectedBodyAndHeaders, + commonVariables, + expectedHeadersFromCommonVarsWithoutSecurityHeaders, + resetApiKeyAndBaseUrlEnvVars, + setupApiKeyAndBaseUrlEnvVars, +} from "../../../../../test/helpers/service-test-helper"; +import { API_ENDPOINTS, HTTP_STATUS_CODES } from "../../../../app.constants"; +import { updateProfileService } from "../update-profile-service"; + +describe("update profile service", () => { + const httpInstance = new Http(); + const service: UpdateProfileServiceInterface = + updateProfileService(httpInstance); + let postStub: SinonStub; + + beforeEach(() => { + setupApiKeyAndBaseUrlEnvVars(); + postStub = sinon.stub(httpInstance.client, "post"); + }); + + afterEach(() => { + postStub.reset(); + resetApiKeyAndBaseUrlEnvVars(); + }); + + it("successfully calls the API to update a profile", async () => { + const axiosResponse = Promise.resolve({ + data: {}, + status: HTTP_STATUS_CODES.NO_CONTENT, + statusText: "OK", + }); + postStub.resolves(axiosResponse); + const { sessionId, clientSessionId, email, ip, diPersistentSessionId } = + commonVariables; + const profileInformation = true; + const updateProfileType = UpdateType.CAPTURE_CONSENT; + + const underTest = () => + service.updateProfile( + sessionId, + clientSessionId, + email, + { profileInformation, updateProfileType }, + ip, + diPersistentSessionId + ); + + const expectedApiCallDetails = { + expectedPath: API_ENDPOINTS.UPDATE_PROFILE, + expectedHeaders: expectedHeadersFromCommonVarsWithoutSecurityHeaders, + expectedBody: { email, profileInformation, updateProfileType }, + }; + + await checkApiCallMadeWithExpectedBodyAndHeaders( + underTest, + postStub, + true, + expectedApiCallDetails + ); + }); +}); diff --git a/src/components/common/verify-code/tests/verify-code-service.test.ts b/src/components/common/verify-code/tests/verify-code-service.test.ts new file mode 100644 index 0000000000..5c3c1a1b08 --- /dev/null +++ b/src/components/common/verify-code/tests/verify-code-service.test.ts @@ -0,0 +1,72 @@ +import { describe } from "mocha"; +import sinon, { SinonStub } from "sinon"; +import { Http } from "../../../../utils/http"; +import { + checkApiCallMadeWithExpectedBodyAndHeaders, + commonVariables, + expectedHeadersFromCommonVarsWithoutSecurityHeaders, + resetApiKeyAndBaseUrlEnvVars, + setupApiKeyAndBaseUrlEnvVars, +} from "../../../../../test/helpers/service-test-helper"; +import { + API_ENDPOINTS, + HTTP_STATUS_CODES, + JOURNEY_TYPE, + NOTIFICATION_TYPE, +} from "../../../../app.constants"; +import { VerifyCodeInterface } from "../types"; +import { codeService } from "../verify-code-service"; + +describe("verify code service", () => { + const httpInstance = new Http(); + const service: VerifyCodeInterface = codeService(httpInstance); + let postStub: SinonStub; + + beforeEach(() => { + setupApiKeyAndBaseUrlEnvVars(); + postStub = sinon.stub(httpInstance.client, "post"); + }); + + afterEach(() => { + postStub.reset(); + resetApiKeyAndBaseUrlEnvVars(); + }); + + it("successfully calls the API to verify a code", async () => { + const axiosResponse = Promise.resolve({ + data: {}, + status: HTTP_STATUS_CODES.NO_CONTENT, + statusText: "OK", + }); + postStub.resolves(axiosResponse); + const { sessionId, clientSessionId, ip, diPersistentSessionId } = + commonVariables; + const code = "1234"; + const notificationType = NOTIFICATION_TYPE.VERIFY_EMAIL; + const journeyType = JOURNEY_TYPE.SIGN_IN; + + const underTest = () => + service.verifyCode( + sessionId, + code, + notificationType, + clientSessionId, + ip, + diPersistentSessionId, + journeyType + ); + + const expectedApiCallDetails = { + expectedPath: API_ENDPOINTS.VERIFY_CODE, + expectedHeaders: expectedHeadersFromCommonVarsWithoutSecurityHeaders, + expectedBody: { code, notificationType, journeyType }, + }; + + await checkApiCallMadeWithExpectedBodyAndHeaders( + underTest, + postStub, + true, + expectedApiCallDetails + ); + }); +}); diff --git a/src/components/common/verify-mfa-code/test/verify-mfa-code-service.test.ts b/src/components/common/verify-mfa-code/test/verify-mfa-code-service.test.ts new file mode 100644 index 0000000000..264bf75b77 --- /dev/null +++ b/src/components/common/verify-mfa-code/test/verify-mfa-code-service.test.ts @@ -0,0 +1,74 @@ +import { describe } from "mocha"; +import sinon, { SinonStub } from "sinon"; +import { Http } from "../../../../utils/http"; +import { + checkApiCallMadeWithExpectedBodyAndHeaders, + commonVariables, + expectedHeadersFromCommonVarsWithoutSecurityHeaders, + resetApiKeyAndBaseUrlEnvVars, + setupApiKeyAndBaseUrlEnvVars, +} from "../../../../../test/helpers/service-test-helper"; +import { + API_ENDPOINTS, + HTTP_STATUS_CODES, + JOURNEY_TYPE, + MFA_METHOD_TYPE, +} from "../../../../app.constants"; +import { VerifyMfaCodeInterface } from "../../../enter-authenticator-app-code/types"; +import { verifyMfaCodeService } from "../verify-mfa-code-service"; + +describe("verify mfa code service", () => { + const httpInstance = new Http(); + const service: VerifyMfaCodeInterface = verifyMfaCodeService(httpInstance); + let postStub: SinonStub; + + beforeEach(() => { + setupApiKeyAndBaseUrlEnvVars(); + postStub = sinon.stub(httpInstance.client, "post"); + }); + + afterEach(() => { + postStub.reset(); + resetApiKeyAndBaseUrlEnvVars(); + }); + + it("successfully calls the API to verify an mfa code", async () => { + const axiosResponse = Promise.resolve({ + data: {}, + status: HTTP_STATUS_CODES.NO_CONTENT, + statusText: "OK", + }); + postStub.resolves(axiosResponse); + const { sessionId, clientSessionId, ip, diPersistentSessionId } = + commonVariables; + const code = "1234"; + const journeyType = JOURNEY_TYPE.SIGN_IN; + const mfaMethodType = MFA_METHOD_TYPE.SMS; + const profileInformation = "some profile information"; //TODO check for realistic value + + const underTest = () => + service.verifyMfaCode( + mfaMethodType, + code, + sessionId, + clientSessionId, + ip, + diPersistentSessionId, + journeyType, + profileInformation + ); + + const expectedApiCallDetails = { + expectedPath: API_ENDPOINTS.VERIFY_MFA_CODE, + expectedHeaders: expectedHeadersFromCommonVarsWithoutSecurityHeaders, + expectedBody: { mfaMethodType, code, journeyType, profileInformation }, + }; + + await checkApiCallMadeWithExpectedBodyAndHeaders( + underTest, + postStub, + true, + expectedApiCallDetails + ); + }); +}); diff --git a/test/helpers/service-test-helper.ts b/test/helpers/service-test-helper.ts new file mode 100644 index 0000000000..4f9c469a2d --- /dev/null +++ b/test/helpers/service-test-helper.ts @@ -0,0 +1,66 @@ +import { ApiResponseResult } from "../../src/types"; +import { SinonStub } from "sinon"; +import { expect } from "chai"; +import { sinon } from "../utils/test-utils"; +import exp = require("constants"); + +export const commonVariables = { + email: "joe.bloggs@example.com", + sessionId: "some-session-id", + diPersistentSessionId: "some-persistent-session-id", + clientSessionId: "some-client-session-id", + ip: "123.123.123.123", + apiKey: "apiKey", +}; + +export const expectedHeadersFromCommonVarsWithoutSecurityHeaders = { + "X-API-Key": commonVariables.apiKey, + "Session-Id": commonVariables.sessionId, + "Client-Session-Id": commonVariables.clientSessionId, + "X-Forwarded-For": commonVariables.ip, + "di-persistent-session-id": commonVariables.diPersistentSessionId, +}; + +export interface StubCallExpectations { + expectedPath: string; + expectedHeaders: object; + expectedBody: object; + validateStatus?: boolean; +} + +export async function checkApiCallMadeWithExpectedBodyAndHeaders( + underTest: () => Promise>, + stub: SinonStub, + expectedSuccess: boolean, + expectations: StubCallExpectations +) { + const result = await underTest(); + expect(result.success).to.eq(expectedSuccess); + + const expectedValidateStatus = expectations.validateStatus + ? { validateStatus: sinon.match.func } + : {}; + + expect( + stub.calledOnceWithExactly( + expectations.expectedPath, + expectations.expectedBody, + { + headers: expectations.expectedHeaders, + proxy: sinon.match.bool, + ...expectedValidateStatus, + } + ) + ).to.be.true; +} + +export function setupApiKeyAndBaseUrlEnvVars() { + process.env.API_KEY = commonVariables.apiKey; + process.env.FRONTEND_API_BASE_URL = "https://example-base-url"; +} + +export function resetApiKeyAndBaseUrlEnvVars() { + process.env.API_KEY = commonVariables.apiKey; + process.env.FRONTEND_API_BASE_URL = "https://example-base-url"; + sinon.restore(); +}