diff --git a/actions/services/caseDirectService.js b/actions/services/caseDirectService.js index 0d84ab44..89e2bc9f 100644 --- a/actions/services/caseDirectService.js +++ b/actions/services/caseDirectService.js @@ -5,7 +5,7 @@ import { getJson, requestJson } from "../clients/endpointClient"; import { buildFileQuery, withBaseUrl } from "../clients/fileRouteBuilder"; export const getCaseMessage = (searchString) => { - const route = buildFileQuery("/api/endpoint/getcasemessage_api", { + const route = buildFileQuery("/api/cases/get-case-message", { id: searchString }); @@ -13,7 +13,7 @@ export const getCaseMessage = (searchString) => { }; export const getIncidentbyID = (searchString) => { - const route = buildFileQuery("/api/endpoint/getincidentbyid_api", { + const route = buildFileQuery("/api/cases/get-incident-by-id", { searchString }); @@ -37,7 +37,7 @@ export const getPartSavedAppeal = (searchString) => { }; export const getSIPSEvents = async (caseid) => { - const route = buildFileQuery("/api/endpoint/getsipsevents_api", { + const route = buildFileQuery("/api/cases/get-sips-events", { caseid }); @@ -45,7 +45,7 @@ export const getSIPSEvents = async (caseid) => { }; export const getSIPSMedia = async (caseid) => { - const route = buildFileQuery("/api/endpoint/getsipsmedia_api", { + const route = buildFileQuery("/api/cases/get-sips-media", { caseid }); diff --git a/actions/services/searchDirectService.js b/actions/services/searchDirectService.js index 9eee7403..d482afe8 100644 --- a/actions/services/searchDirectService.js +++ b/actions/services/searchDirectService.js @@ -244,7 +244,7 @@ export const getSearchDocumentDetailsPaged = ( export const getLinkedCases = (parentIncidentid) => { return getJson( - "/api/endpoint/getlinkedcases_api?parentincidentid=" + parentIncidentid + "/api/cases/get-linked-cases?parentincidentid=" + parentIncidentid ).catch((error) => { consoleLogger(error); }); diff --git a/context/api-grouping-adoption-roadmap.md b/context/api-grouping-adoption-roadmap.md index d08afaec..d1b4d8cd 100644 --- a/context/api-grouping-adoption-roadmap.md +++ b/context/api-grouping-adoption-roadmap.md @@ -318,6 +318,59 @@ UI journey - grouping would require contract change - In these cases, documentation may still classify the area conceptually, but additive façade rollout should wait for a safer bounded slice. +#### Additional vertical-slice proof point — Case Details read journey + +- The active Case Details read journey now has a fourth additive grouping slice for the proven case-specific read family only: + - grouped façade routes exist under `pages/api/cases/` for the proven active reads: + - `get-incident-by-id` + - `get-case-message` + - `get-linked-cases` + - `get-sips-events` + - `get-sips-media` + - the existing service-layer adoption is split across the current owning read services: + - `actions/services/caseDirectService.js` now targets grouped cases routes for: + - `getIncidentbyID` + - `getCaseMessage` + - `getSIPSEvents` + - `getSIPSMedia` + - `actions/services/searchDirectService.js` now targets grouped cases routes for: + - `getLinkedCases` + - legacy endpoint handlers remain present and canonical behind the façade: + - `pages/api/endpoint/getincidentbyid_api.js` + - `pages/api/endpoint/getcasemessage_api.js` + - `pages/api/endpoint/getlinkedcases_api.js` + - `pages/api/endpoint/getsipsevents_api.js` + - `pages/api/endpoint/getsipsmedia_api.js` +- The bounded audit established that the true active Case Details read journey is narrower than the full case page bootstrap: + - public ticketnumber case pages (`pages/case/[ticketnumber].js`) still bootstrap primarily through the search family: + - `getBasicSearch(...)` + - `getSearchDetails(...)` + - incident-id case pages (`pages/case/id/[incident].js` and `pages/myportal/case/id/[incident].js`) use the case-specific incident-id route + - linked cases are actively loaded inside `components/case/summary.js` as a case-details read concern + - SIPS events/media are active case-page enrichments for the relevant case type + - documents remain a separate active journey owned by the documents slice through `components/case/documents.js` and `actions/services/searchDirectService.js` +- The following routes were intentionally left outside this grouped adoption scope: + - `getbasicsearch_api.js` + - `getbasicsearchdetails_api.js` + - `getbasicsearchdetailspaged_api.js` + - `getsearchdocumentdetails_api.js` + - `getsearchdocumentdetailspaged_api.js` + - `getsearchdocumentTypes_api.js` + - `getcase_api.js` + - `getcasebyid_api.js` + - `getportalmoduledetails_api.js` +- Exclusion reasoning: + - search bootstrap/detail-expansion routes remain part of Public Search or search-to-case bootstrap rather than the narrowed case-specific read family + - published document metadata remains owned by the documents slice + - `getcase_api.js` and `getcasebyid_api.js` are not proven active in the current case-page read path and remain adjacent/support routes + - `getportalmoduledetails_api.js` is actively used in myportal aggregation/detail enrichment rather than the bounded public/myportal case-details page read slice itself +- For this Case Details slice, a complete vertical grouping slice means: + - the proven case-specific read family has grouped façade routes + - current owning service calls for that family target the grouped routes + - adjacent search bootstrap and documents routes remain unchanged + - legacy endpoint handlers remain canonical + - no route migration, deletion, or contract change has occurred + --- ### Stage 3 — Documentation Maturity diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index c57d7649..ecf73dd9 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -20,6 +20,86 @@ Follow-ups: ### CL-2026-06-25-API-GROUPING-FACADE-SEARCH-VERTICAL-SLICE: active public search results grouping and service adoption +### CL-2026-06-25-API-GROUPING-FACADE-CASE-DETAILS-VERTICAL-SLICE: bounded case-details read grouping and service adoption + +date: 2026-06-25 +author: Cline +scope: `pages/api/cases/get-incident-by-id.js`, `pages/api/cases/get-case-message.js`, `pages/api/cases/get-linked-cases.js`, `pages/api/cases/get-sips-events.js`, `pages/api/cases/get-sips-media.js`, `actions/services/caseDirectService.js`, `actions/services/searchDirectService.js`, `tests/phase22/api-grouping-facade-case-details.test.cjs`, `context/api-grouping-adoption-roadmap.md` +type: change +rationale: Implement the next additive API grouping façade vertical slice for the bounded Case Details read journey by grouping only the proven case-specific read family under `pages/api/cases/` and adopting those grouped routes in the existing owning service layers without changing bootstrap/search/documents behavior. +impact: Runtime behaviour is intended to remain unchanged; grouped cases façade routes now cover the proven incident-id, case-message, linked-cases, and SIPS enrichment reads, legacy endpoint handlers remain canonical, and no route migration, deletion, contract change, document behaviour change, search behaviour change, auth change, storage change, or submission/orchestration change has occurred. +status: completed + +Summary: + +- Confirmed the required context was read before implementation: + - `context/api-grouping-plan.md` + - `context/api-grouping-adoption-roadmap.md` + - `context/api-route-map.md` + - `context/journey-architecture-map.md` + - `context/architecture.md` + - `memory-bank/change-log.md` +- Performed a bounded audit of the active Case Details read journey across pages, components, services, and endpoint usage. +- Audit findings established that: + - `pages/case/[ticketnumber].js` and `pages/myportal/case/[ticketnumber].js` still bootstrap primarily through the Public Search family using `getBasicSearch(...)` and `getSearchDetails(...)` + - `pages/case/id/[incident].js` and `pages/myportal/case/id/[incident].js` actively use the case-specific incident-id read route through `getIncidentbyID(...)` + - `getCaseMessage(...)` is actively used in case-page loading for banner/message content + - `getLinkedCases(...)` is actively used inside `components/case/summary.js` as a case-details read concern + - `getSIPSEvents(...)` and `getSIPSMedia(...)` are active case-page enrichments for relevant SIPS cases + - `components/case/documents.js` and document metadata routes remain part of the documents slice, not this case-details slice + - `getcase_api.js`, `getcasebyid_api.js`, and `getportalmoduledetails_api.js` are adjacent/support reads but were not proven to belong to the bounded active case-page read path for this slice +- Added grouped façade routes under `pages/api/cases/` for the proven case-specific read family only: + - `get-incident-by-id.js` + - `get-case-message.js` + - `get-linked-cases.js` + - `get-sips-events.js` + - `get-sips-media.js` +- Implemented each façade as the smallest safe compatibility wrapper: + - import the existing legacy endpoint handler + - delegate `req` and `res` directly to that existing handler +- Updated only the existing owning service call sites needed for the bounded slice: + - `actions/services/caseDirectService.js` + - `getIncidentbyID` + - `getCaseMessage` + - `getSIPSEvents` + - `getSIPSMedia` + - `actions/services/searchDirectService.js` + - `getLinkedCases` +- Left the following unchanged: + - legacy endpoint handlers + - search bootstrap routes and search detail expansion routes + - document metadata/download routes + - CRM queries + - transforms + - query parameter names + - response contracts + - status codes + - auth/session behavior + - storage/queue/submission behavior +- Added focused characterization coverage to prove: + - grouped cases façade routes delegate correctly + - legacy endpoint handlers remain present + - adopted service methods now target grouped cases routes + - request-contract cues remain unchanged + - adjacent search/documents/support routes remain outside grouped adoption scope +- Updated the adoption roadmap to record the audited scope boundary, included routes, excluded routes, service adoption status, and behaviour-preservation notes for the Case Details read slice. + +Validation: + +- Focused validation only intended for this slice: + - `npx eslint pages/api/cases/*.js actions/services/caseDirectService.js actions/services/searchDirectService.js tests/phase22/api-grouping-facade-case-details.test.cjs context/api-grouping-adoption-roadmap.md memory-bank/change-log.md` + - `node tests/phase22/api-grouping-facade-case-details.test.cjs` +- No repository-wide validation run. + +Follow-ups: + +- The bounded Case Details read family is now a complete façade grouping slice only for the proven case-specific read routes adopted here. +- Adjacent case bootstrap, document, and myportal aggregation/detail-enrichment routes should remain separate future slices if needed. + +--- + +### CL-2026-06-25-API-GROUPING-FACADE-SEARCH-VERTICAL-SLICE: active public search results grouping and service adoption + ### CL-2026-06-25-API-GROUPING-FACADE-ROLLOUT-CHECKPOINT: proven rollout pattern and guardrails checkpoint date: 2026-06-25 diff --git a/pages/api/cases/get-case-message.js b/pages/api/cases/get-case-message.js new file mode 100644 index 00000000..c02a642c --- /dev/null +++ b/pages/api/cases/get-case-message.js @@ -0,0 +1,5 @@ +import legacyGetCaseMessageHandler from "../endpoint/getcasemessage_api"; + +export default async function getCaseMessageFacade(req, res) { + return legacyGetCaseMessageHandler(req, res); +} diff --git a/pages/api/cases/get-incident-by-id.js b/pages/api/cases/get-incident-by-id.js new file mode 100644 index 00000000..bf937a86 --- /dev/null +++ b/pages/api/cases/get-incident-by-id.js @@ -0,0 +1,5 @@ +import legacyGetIncidentByIdHandler from "../endpoint/getincidentbyid_api"; + +export default async function getIncidentByIdFacade(req, res) { + return legacyGetIncidentByIdHandler(req, res); +} diff --git a/pages/api/cases/get-linked-cases.js b/pages/api/cases/get-linked-cases.js new file mode 100644 index 00000000..94f525d6 --- /dev/null +++ b/pages/api/cases/get-linked-cases.js @@ -0,0 +1,5 @@ +import legacyGetLinkedCasesHandler from "../endpoint/getlinkedcases_api"; + +export default async function getLinkedCasesFacade(req, res) { + return legacyGetLinkedCasesHandler(req, res); +} diff --git a/pages/api/cases/get-sips-events.js b/pages/api/cases/get-sips-events.js new file mode 100644 index 00000000..5941df85 --- /dev/null +++ b/pages/api/cases/get-sips-events.js @@ -0,0 +1,5 @@ +import legacyGetSipsEventsHandler from "../endpoint/getsipsevents_api"; + +export default async function getSipsEventsFacade(req, res) { + return legacyGetSipsEventsHandler(req, res); +} diff --git a/pages/api/cases/get-sips-media.js b/pages/api/cases/get-sips-media.js new file mode 100644 index 00000000..290a65bc --- /dev/null +++ b/pages/api/cases/get-sips-media.js @@ -0,0 +1,5 @@ +import legacyGetSipsMediaHandler from "../endpoint/getsipsmedia_api"; + +export default async function getSipsMediaFacade(req, res) { + return legacyGetSipsMediaHandler(req, res); +} diff --git a/tests/phase22/api-grouping-facade-case-details.test.cjs b/tests/phase22/api-grouping-facade-case-details.test.cjs new file mode 100644 index 00000000..0e4eb752 --- /dev/null +++ b/tests/phase22/api-grouping-facade-case-details.test.cjs @@ -0,0 +1,184 @@ +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const { loadModule, createRes } = require("../phase21/_shared.cjs"); + +const rootDir = path.resolve(__dirname, "..", ".."); + +const tests = []; +const test = (name, fn) => tests.push({ name, fn }); + +const read = (relativePath) => + fs.readFileSync(path.join(rootDir, relativePath), "utf8"); + +test("cases get-incident-by-id facade delegates to legacy handler", async () => { + const calls = []; + const mod = loadModule("pages/api/cases/get-incident-by-id.js", { + legacyGetIncidentByIdHandler: async (req, res) => { + calls.push({ req, res }); + return res.status(200).json({ ok: true, route: "incident-by-id" }); + } + }); + + const req = { method: "GET", query: { searchString: "abc-123" } }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(calls.length, 1); + assert.strictEqual(calls[0].req, req); + assert.strictEqual(calls[0].res, res); +}); + +test("cases get-case-message facade delegates to legacy handler", async () => { + const calls = []; + const mod = loadModule("pages/api/cases/get-case-message.js", { + legacyGetCaseMessageHandler: async (req, res) => { + calls.push({ req, res }); + return res.status(200).json({ ok: true, route: "case-message" }); + } + }); + + const req = { method: "GET", query: { id: "case-1" } }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(calls.length, 1); + assert.strictEqual(calls[0].req, req); + assert.strictEqual(calls[0].res, res); +}); + +test("cases get-linked-cases facade delegates to legacy handler", async () => { + const calls = []; + const mod = loadModule("pages/api/cases/get-linked-cases.js", { + legacyGetLinkedCasesHandler: async (req, res) => { + calls.push({ req, res }); + return res.status(200).json({ ok: true, route: "linked-cases" }); + } + }); + + const req = { method: "GET", query: { parentincidentid: "parent-1" } }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(calls.length, 1); + assert.strictEqual(calls[0].req, req); + assert.strictEqual(calls[0].res, res); +}); + +test("cases get-sips-events facade delegates to legacy handler", async () => { + const calls = []; + const mod = loadModule("pages/api/cases/get-sips-events.js", { + legacyGetSipsEventsHandler: async (req, res) => { + calls.push({ req, res }); + return res.status(200).json({ ok: true, route: "sips-events" }); + } + }); + + const req = { method: "GET", query: { caseid: "sips-1" } }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(calls.length, 1); + assert.strictEqual(calls[0].req, req); + assert.strictEqual(calls[0].res, res); +}); + +test("cases get-sips-media facade delegates to legacy handler", async () => { + const calls = []; + const mod = loadModule("pages/api/cases/get-sips-media.js", { + legacyGetSipsMediaHandler: async (req, res) => { + calls.push({ req, res }); + return res.status(200).json({ ok: true, route: "sips-media" }); + } + }); + + const req = { method: "GET", query: { caseid: "sips-2" } }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(calls.length, 1); + assert.strictEqual(calls[0].req, req); + assert.strictEqual(calls[0].res, res); +}); + +test("legacy case-details endpoint handlers remain present", async () => { + const legacyFiles = [ + "pages/api/endpoint/getincidentbyid_api.js", + "pages/api/endpoint/getcasemessage_api.js", + "pages/api/endpoint/getlinkedcases_api.js", + "pages/api/endpoint/getsipsevents_api.js", + "pages/api/endpoint/getsipsmedia_api.js", + "pages/api/endpoint/getbasicsearch_api.js", + "pages/api/endpoint/getbasicsearchdetails_api.js", + "pages/api/endpoint/getsearchdocumentdetails_api.js" + ]; + + legacyFiles.forEach((filePath) => { + assert.strictEqual( + fs.existsSync(path.join(rootDir, filePath)), + true, + filePath + ); + }); +}); + +test("caseDirectService adopted case-details reads now target grouped cases routes", async () => { + const source = read("actions/services/caseDirectService.js"); + + assert.match(source, /\/api\/cases\/get-incident-by-id/); + assert.match(source, /\/api\/cases\/get-case-message/); + assert.match(source, /\/api\/cases\/get-sips-events/); + assert.match(source, /\/api\/cases\/get-sips-media/); +}); + +test("searchDirectService linked-cases read now targets grouped cases route", async () => { + const source = read("actions/services/searchDirectService.js"); + + assert.match(source, /\/api\/cases\/get-linked-cases\?/); +}); + +test("case-details service contracts remain unchanged", async () => { + const caseSource = read("actions/services/caseDirectService.js"); + const searchSource = read("actions/services/searchDirectService.js"); + + assert.match(caseSource, /id: searchString/); + assert.match(caseSource, /searchString/); + assert.match(caseSource, /caseid/); + assert.match(searchSource, /parentincidentid=/); +}); + +test("adjacent search and documents routes remain outside case-details adoption scope", async () => { + const caseSource = read("actions/services/caseDirectService.js"); + const searchSource = read("actions/services/searchDirectService.js"); + + assert.match(searchSource, /\/api\/endpoint\/getbasicsearch_api/); + assert.match(searchSource, /\/api\/endpoint\/getbasicsearchdetails_api/); + assert.match( + searchSource, + /\/api\/documents\/get-search-document-details\?/ + ); + assert.doesNotMatch(caseSource, /\/api\/cases\/get-case"/); + assert.doesNotMatch(caseSource, /\/api\/cases\/get-case-by-id/); +}); + +async function run() { + let failures = 0; + + for (const { name, fn } of tests) { + try { + await fn(); + console.log(`✓ ${name}`); + } catch (error) { + failures += 1; + console.error(`✗ ${name}`); + console.error(error); + } + } + + if (failures > 0) { + process.exit(1); + } +} + +run();