From bf2e4a3254af73c031bfb19aaba50a71743bf18e Mon Sep 17 00:00:00 2001 From: robbond Date: Fri, 6 Nov 2026 11:33:49 +0000 Subject: [PATCH] refactor: migrate myportal index account lookup to server helper --- pages/myportal/index.js | 26 +- .../myportal-index-loader-guards.test.cjs | 244 ++++++++++++++++++ 2 files changed, 264 insertions(+), 6 deletions(-) create mode 100644 tests/phase22/myportal-index-loader-guards.test.cjs diff --git a/pages/myportal/index.js b/pages/myportal/index.js index dfc737c9..dc04b2ba 100644 --- a/pages/myportal/index.js +++ b/pages/myportal/index.js @@ -7,10 +7,8 @@ import pLimit from "p-limit"; import { getIP } from "../../actions/core/logger"; import { consoleLogger } from "../../actions/core/logger"; -import { - getPersonalAccount, - getPortalLogin -} from "../../actions/services/accountService"; +import { getPortalLogin } from "../../actions/services/accountService"; +import { getCrmPersonalAccount } from "../../lib/server/getCrmPersonalAccount"; import { getCase, getPortalModuleDetails @@ -257,9 +255,25 @@ export const getServerSideProps = wrapper.getServerSideProps( } await createContainerProxy(thisSession.user.id); - const accountDetails = await getPersonalAccount(loggedInUser); - if (accountDetails?.errorCode) { + let accountDetails; + + try { + accountDetails = await getCrmPersonalAccount(loggedInUser); + } catch (error) { + consoleLogger({ + name: "MyPortalIndexAccountLookup", + reasonCode: "UPSTREAM_FAILURE", + message: "myportal index personal account lookup failed", + error: error?.message + }); + + return { + redirect: { destination: "/myportal/error", permanent: false } + }; + } + + if (!accountDetails || accountDetails.errorCode) { return { redirect: { destination: "/myportal/error", permanent: false } }; diff --git a/tests/phase22/myportal-index-loader-guards.test.cjs b/tests/phase22/myportal-index-loader-guards.test.cjs new file mode 100644 index 00000000..54418852 --- /dev/null +++ b/tests/phase22/myportal-index-loader-guards.test.cjs @@ -0,0 +1,244 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); + +const tests = []; +const test = (name, fn) => tests.push({ name, fn }); + +const loadIndexLoader = (overrides = {}) => { + const filePath = path.join( + __dirname, + "..", + "..", + "pages", + "myportal", + "index.js" + ); + + let source = fs.readFileSync(filePath, "utf8"); + + // Execute only the server-side loader, not the React component. + source = source.slice(source.indexOf("export const getServerSideProps")); + source = source.slice(0, source.indexOf("const mapStateToProps")); + + source = source.replace( + /export\s+const\s+getServerSideProps\s*=/, + "const getServerSideProps =" + ); + source += "\nmodule.exports = { getServerSideProps };\n"; + + const dispatches = []; + const store = { + dispatch: (action) => dispatches.push(action) + }; + + const context = { + module: { exports: {} }, + exports: {}, + process: { env: {} }, + console: { error: () => {} }, + wrapper: { + getServerSideProps: (factory) => async (ctx) => factory(store)(ctx) + }, + getSession: async () => ({ + user: { + id: "session-user-1", + email: "user@example.com" + } + }), + getIP: () => {}, + consoleLogger: () => {}, + getPortalLogin: async () => ({ + value: [{ contactid: "crm-contact-1" }] + }), + createContainerProxy: async () => {}, + getCrmPersonalAccount: async () => ({ + firstname: "Test", + pinswg_typeofinvolvement: 0, + pinswg_preferredlanguage: 0 + }), + getMyCases: async () => ({ value: [] }), + getMyLPACases: async () => ({ value: [] }), + getRepsFromBlob: async () => ({ value: [] }), + getWatchedCases: async () => ({ value: [] }), + getAwaitingSubmissionFromBlob: async () => ({ value: [] }), + splitWatchedCasesBySubmissionState: () => ({ + watchedCases: { value: [] }, + submittedRepresentations: { value: [] } + }), + getPortalModuleDetails: async () => ({}), + getFormCollectionByID: () => ({ + LogicalCollectionName: "pinswg_cases" + }), + pLimit: () => (fn) => fn(), + setLocale: (value) => ({ type: "locale", value }), + setShowReps: () => ({ type: "showReps" }), + setAccountDetails: (value) => ({ type: "accountDetails", value }), + setLoggedInUserId: (value) => ({ type: "loggedInUserId", value }), + setMyCases: () => ({ type: "myCases" }), + setMyCasesDetails: () => ({ type: "myCasesDetails" }), + setMyRepresentations: () => ({ type: "myRepresentations" }), + setMyRepresentationsDetails: () => ({ + type: "myRepresentationsDetails" + }), + setWatchedCases: () => ({ type: "watchedCases" }), + setWatchedCasesDetails: () => ({ type: "watchedCasesDetails" }), + setMySubmittedReps: () => ({ type: "mySubmittedReps" }), + setMySubmittedRepsDetails: () => ({ + type: "mySubmittedRepsDetails" + }), + setAwaitingSubmissionFromBlob: () => ({ + type: "awaitingSubmission" + }), + setAwaitingSubmissionDetails: () => ({ + type: "awaitingSubmissionDetails" + }), + setContainerID: () => ({ type: "containerID" }), + ...overrides + }; + + vm.runInNewContext(source, context, { filename: filePath }); + + return { + getServerSideProps: context.module.exports.getServerSideProps, + dispatches + }; +}; + +const createContext = () => ({ + req: { cookies: {} }, + locale: "en" +}); + +test("missing session redirects to signin", async () => { + const loader = loadIndexLoader({ + getSession: async () => null + }); + + const result = await loader.getServerSideProps(createContext()); + + assert.strictEqual(result.redirect.destination, "/auth/signin"); +}); + +test("missing session identity redirects to signin", async () => { + const loader = loadIndexLoader({ + getSession: async () => ({ + user: { id: "session-user-1" } + }) + }); + + const result = await loader.getServerSideProps(createContext()); + + assert.strictEqual(result.redirect.destination, "/auth/signin"); +}); + +test("CRM contact lookup failure redirects to signin", async () => { + const loader = loadIndexLoader({ + getPortalLogin: async () => { + throw new Error("CRM unavailable"); + } + }); + + const result = await loader.getServerSideProps(createContext()); + + assert.strictEqual(result.redirect.destination, "/auth/signin"); +}); + +test("missing CRM contact redirects to signin", async () => { + const loader = loadIndexLoader({ + getPortalLogin: async () => ({ value: [] }) + }); + + const result = await loader.getServerSideProps(createContext()); + + assert.strictEqual(result.redirect.destination, "/auth/signin"); +}); + +test("personal account lookup exception redirects to error page", async () => { + const loader = loadIndexLoader({ + getCrmPersonalAccount: async () => { + throw new Error("CRM account lookup failed"); + } + }); + + const result = await loader.getServerSideProps(createContext()); + + assert.strictEqual(result.redirect.destination, "/myportal/error"); +}); + +test("missing personal account redirects to error page", async () => { + const loader = loadIndexLoader({ + getCrmPersonalAccount: async () => null + }); + + const result = await loader.getServerSideProps(createContext()); + + assert.strictEqual(result.redirect.destination, "/myportal/error"); +}); + +test("personal account error response redirects to error page", async () => { + const loader = loadIndexLoader({ + getCrmPersonalAccount: async () => ({ + errorCode: "CRM_CONTACT_MISSING" + }) + }); + + const result = await loader.getServerSideProps(createContext()); + + assert.strictEqual(result.redirect.destination, "/myportal/error"); +}); + +test("successful lookup uses contact resolved from session email", async () => { + let capturedEmail; + let capturedContactId; + + const loader = loadIndexLoader({ + getPortalLogin: async (email) => { + capturedEmail = email; + return { value: [{ contactid: "crm-contact-123" }] }; + }, + getCrmPersonalAccount: async (contactId) => { + capturedContactId = contactId; + return { + firstname: "Test", + pinswg_typeofinvolvement: 0, + pinswg_preferredlanguage: 0 + }; + } + }); + + const result = await loader.getServerSideProps(createContext()); + + assert.strictEqual(capturedEmail, "user@example.com"); + assert.strictEqual(capturedContactId, "crm-contact-123"); + assert.strictEqual(result.props.containerID, "session-user-1"); + + const accountDispatch = loader.dispatches.find( + (action) => action.type === "accountDetails" + ); + + assert.strictEqual(accountDispatch.value.firstname, "Test"); +}); + +const run = async () => { + let passed = 0; + + for (const currentTest of tests) { + await currentTest.fn(); + passed += 1; + } + + console.log( + `Phase 22 myportal-index-loader-guards tests passed (${passed}/${tests.length}).` + ); +}; + +module.exports = run; + +if (require.main === module) { + run().catch((error) => { + console.error(error); + process.exit(1); + }); +}