Merged PR 2197: update endpoint api slice

Related work items: #22168
This commit is contained in:
Robert Bond
2026-03-23 10:45:31 +00:00
24 changed files with 1653 additions and 4552 deletions
+4
View File
@@ -61,3 +61,7 @@ context/
memory-bank/
workflows/
AI_CONTEXT.md
memory-bank/activeContext.md
memory-bank/change-log.md
memory-bank/refactor-plan-actions-index.md
memory-bank/progress.md
-648
View File
@@ -1,648 +0,0 @@
# Active Context — PEDW FrontEnd
## Current development focus (from recent commits)
- API contract consistency rollout (TASK22102), starting with helper-based response normalization in selected email/admin handlers.
- Search/case navigation correctness, especially breadcrumb and back-link behavior.
- My Portal "view all" and DNS application path handling.
- Welsh/English email behavior for specific notification templates.
- PDF output formatting and hyperlink behavior.
## Recently changed areas (high signal)
- `components/breadcrumbs.js`
- `components/case/summary.js`
- `components/search/searchresults.js`
- `components/search/addresssearchresults.js`
- `components/myportal/viewall.js`
- `pages/myportal/case/[ticketnumber].js`
- `pages/api/email/notify.js`
- `actions/index.js`
- `actions/core/env.js`
- `actions/core/logger.js`
- `actions/core/hash.js`
- `actions/core/token.js`
- `actions/core/headers.js`
- `actions/services/legacyActionsService.js`
- `i18n.js`
## Active concerns
- Breadcrumb and route-state logic is complex and query-dependent (`va`, `adv`, `ads`, `key`), so regressions are easy.
- Locale-specific route and label behavior is still a high-risk area due to rewrite + component logic coupling.
- Sensitive logging remains present in auth/email/actions paths and should be reduced/redacted over time.
- Hash validation on some file endpoints appears inconsistent (some checks active, some commented), requiring careful change discipline.
## Refactor status update (2026-03-12)
- Priority 1 refactor (Phase 1) is in place:
- `actions/index.js` now acts as a compatibility barrel.
- Core helper concerns were extracted into `actions/core/*`.
- Existing API/file/domain wrappers were moved into `actions/services/legacyActionsService.js` with backward-compatible exports retained via the barrel.
- No intentional behavior changes were introduced in this phase; focus was structural risk reduction.
- Priority 1 refactor (Phase 2) is now in place:
- Domain service grouping modules added under `actions/services/*`.
- `actions/services/index.js` now provides grouped service barrel exports.
- `actions/index.js` now re-exports from `./services` and `./core/*` while preserving existing public action names.
- `legacyActionsService` remains as a compatibility backing module and should be slimmed in later increments as consumers move to direct service imports.
## Refactor status update (Priority 1 execution pass — 2026-03-12)
- Completed an additional Priority 1 consumer-migration pass focused on high-use portal/form helpers.
- Updated these files to use focused service/core imports instead of broad `actions` barrel imports:
- `components/case/representation/representationElements.js`
- `components/elements/index.js`
- `components/utils/index.js`
- `lib/newappeal/loadNewAppealPage.js`
- `lib/myportal/loadMyPortalAppealPage.js`
- `components/myportal/topthree.js`
- `components/myportal/topthree_reps.js`
- `components/myportal/awaitingsubmissionfromblob.js`
- Compatibility model remains in place via `actions/index.js`; migration is incremental and non-breaking by design.
## Refactor status update (Priority 1 execution pass — viewall/search/unsubscribe)
- Completed another focused import migration pass to reduce broad `actions` barrel coupling in user-facing high-use areas:
- `components/myportal/viewall.js`
- `components/search/addresssearchresults.js`
- `pages/unsubscribe/[watchlistid].js`
- `pages/unsubscribeall/[watchlistid].js`
- These files now use explicit imports from `actions/services/*` and `actions/core/*` modules.
- Remaining non-comment broad `actions` imports are now primarily in selected `pages/api/endpoint/*` and `pages/api/file/*` handlers.
## Refactor status update (Priority 1 execution pass — endpoint/file proxies)
- Completed focused import migration for selected endpoint and file proxy handlers:
- `pages/api/endpoint/getmylpacases_api.js`
- `pages/api/endpoint/getbasicdnssearchpaged_api.js`
- `pages/api/endpoint/getsearchdocumentdetailspaged_api.js`
- `pages/api/endpoint/getbasicsearchpaged_api.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/createappealcompletemessageproxy_api.js`
- Replaced broad `../../../actions` imports with focused `actions/core/*` and service-module imports.
- Eliminated duplicated local hash helper in `getmylpacases_api.js` by reusing shared `actions/core/hash`.
- Current broad-import scan indicates only comment-only legacy references remain.
## Delivery handover status (2026-03-13)
- Current import-migration wave has been checked in and completed through PR.
- This branch is now treated as the completed migration baseline.
- Next implementation chunk should start on a **new branch** and target decomposition of `actions/services/legacyActionsService.js`.
- Immediate focus for next branch:
1. extract low-risk reference/search wrappers into direct service/client modules,
2. migrate portal/document internals in small parity-checked batches,
3. slim compatibility layer once parity is proven.
## Phase 5 kickoff status (2026-03-13)
- Branch in progress: `TASK21997-phase5-legacyactions-split`.
- First decomposition slice completed:
- search wrappers extracted to `actions/services/searchDirectService.js`
- reference-data wrappers extracted to `actions/services/referenceDataDirectService.js`
- `searchService` and `referenceDataService` now route through these direct modules.
- `legacyActionsService` remains in place for non-migrated domains (portal/document/account/case/admin/notify/integration) pending next slices.
## Phase 5 status update (2026-03-13 — document slice)
- Added `actions/services/documentDirectService.js` and migrated `documentService.js` to route through it.
- `legacyActionsService` now remains for:
- portal, account, case, admin, notify, integration domains.
- Search/reference/document grouped services now point at dedicated direct modules.
## Phase 5 completion status (2026-03-13)
- Grouped services now route through focused direct modules for all domains:
- search, reference-data, document, portal, account, case, admin, integration, notify.
- `actions/services/legacyActionsService.js` has been removed.
- Current architecture baseline:
- `actions/index.js` -> compatibility barrel re-exporting `actions/services` + core modules
- `actions/services/*Service.js` -> grouped public service boundaries
- `actions/services/*DirectService.js` -> implementation modules per domain
- Validation checkpoints completed:
- zero `legacyActionsService` imports in `actions/services/*.js`
- lint passes with warnings only
- build succeeds
## Phase 6 hardening status (2026-03-13 — in progress)
- Working branch: `TASK21998-phase6-postphase5-hardening` (created from `SIPS-Development`).
- Main hardening actions completed so far:
- Added focused parity test harness:
- `tests/phase6/service-parity.test.cjs`
- verifies grouped service import/export parity and `actions/services/index.js` re-export stability.
- Added shared error-path helper:
- `actions/services/httpServiceUtils.js`
- consolidates repeated `consoleLogger + error.response` and `ErrResponse` generation patterns.
- Applied safe duplication reduction in:
- `searchDirectService.js`
- `referenceDataDirectService.js`
- `caseDirectService.js`
- `adminDirectService.js`
- Logging cleanup for sensitive/noisy service paths (removed debug `console.log` usage) in:
- `searchDirectService.js`
- `documentDirectService.js`
- `portalDirectService.js`
- `caseDirectService.js`
- `adminDirectService.js`
### Current validation evidence
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `npm run lint` -> warnings only
- Targeted local smoke snapshots:
- `GET /advancedsearch` and `GET /cy/advancedsearch` -> 200
- `GET /case` and `GET /cy/case` -> 200
- `GET /myportal` and `GET /cy/myportal` -> 307 redirect to `/auth/signin` (unauthenticated negative path)
- `GET /api/file/getbloblistproxy?container=test&casefolderID=test` -> 400 (negative path)
- `POST /api/email/notify` with empty payload -> 400 (negative path)
### Active caveat
- `GET /searchresults` and `GET /cy/searchresults` return 500 in current local dev run due pre-existing SSR serialization issue:
- `initialState.search.searchString` is undefined in `getServerSideProps` payload.
- captured in `/tmp/phase6-dev.log`.
## Phase 7 hardening status (2026-03-13)
- Working branch created from `origin/SIPS-Development`:
- `TASK21988a-phase7-postphase6-hardening`
- Pre-existing search results SSR serialization issue now addressed:
- `pages/searchresults.js`
- changed `setSearch(query.q)` to `setSearch(query?.q || "")` in `getServerSideProps`.
- local smoke result: both `/searchresults` and `/cy/searchresults` return 200 in this run.
- Behavioural test expansion delivered for remaining service domains:
- `tests/phase7/service-behaviour.test.cjs`
- covers document, portal, account, notify, integration.
### Phase 7 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `npm run lint` -> warnings only (existing react-hooks dependency warnings; no new lint errors)
### Phase 7 smoke/negative-path snapshot
- Dev server auto-bound to `localhost:3001` (3000 already in use).
- `GET /searchresults`, `GET /cy/searchresults` -> 200
- `GET /case`, `GET /cy/case` -> 200
- `GET /myportal`, `GET /cy/myportal` -> 307 redirect to `/auth/signin`
- `GET /api/file/getbloblistproxy?container=test&casefolderID=test` -> 400
- `POST /api/email/notify` with `{}` -> 400
### Testing strategy decision (captured)
- Current phase includes **both**:
1. migration/parity guard tests,
2. focused behavioural unit tests for critical response/error contracts.
- Broader behavioural coverage remains a follow-on expansion item after this focused baseline.
## Phase 8 hardening status (2026-03-13)
- Working branch created from `origin/SIPS-Development`:
- `TASK22017-phase8-hardening-slice`
- Sensitive API hardening slice completed across:
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createappealcompletemessage_api.js`
- `pages/api/endpoint/getportallogin_api.js`
- Hash/integrity posture updates:
- re-enabled hash validation on previously bypassed/commented file handlers
- added standardized early 400 handling for missing/invalid hash and missing key params
- preserved response-shape contracts and function signatures
- Logging discipline updates:
- removed noisy direct `console.log` usage in `createappealcompletemessage_api`
- routed error paths through `consoleLogger` in updated sensitive flow
- Required consumer parity updates applied:
- `actions/services/documentDirectService.js`
- now appends hash for `deleteblobcase` and `deleteblobrep` calls
- `actions/services/portalDirectService.js`
- now appends hash for `createappealcompletemessage_api` call using existing hash path contract
### Phase 8 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (pre-existing hook dependency warnings)
### Phase 8 manual HTTP snapshot
- Dev server on `http://localhost:3001`.
- Negative-path checks for invalid/missing hash (and missing required params where relevant) return **400** across all 4 target handlers.
- Valid-hash spot-check for `getportallogin_api` reached hash-validated path but still returned **400** due local upstream relay/CRM dependency behavior.
## Phase 9 hardening status (2026-03-13)
- Working branch created from `origin/SIPS-Development`:
- `TASK22019-phase-9-hardening`
- Additional sensitive API hardening slice completed across:
- `pages/api/file/createrepcompletemessage_api.js`
- `pages/api/file/upload.js`
- `pages/api/file/uploadsinglefile.js`
- `pages/api/file/setupcontainer.js`
- Hash/integrity posture updates:
- re-enabled/enforced hash checks in handlers where hash validation was bypassed/commented
- standardized early 400 handling for missing/invalid hash and missing key query/body values
- preserved response-shape contracts and function signatures
- Logging discipline updates:
- removed noisy `console.log` traces from sensitive upload/rep-complete message paths
- retained centralized error logging via `consoleLogger` where present
- Required caller parity updates applied:
- `actions/services/portalDirectService.js`
- `sendRepCompleteMessage` now appends hash
- `actions/services/documentDirectService.js`
- `uploadFiles`, `uploadRepFiles`, `uploadSingleFile` now append hash
### Phase 9 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (pre-existing hook dependency warnings)
### Phase 9 manual HTTP snapshot
- Negative-path checks for missing/invalid hash and invalid/missing key params on all 4 phase-9 handlers return **400** on local server (`localhost:3000`).
- Valid-hash happy-path spot-check completed using `.env.local` hash key:
- `POST /api/file/uploadsinglefile?hash=<valid>` -> **200**
## Phase 10 hardening status (2026-03-13)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Additional sensitive API hardening slice completed across:
- `pages/api/file/getawaitingsubmissionfromblob.js`
- `pages/api/file/getprogressobjblob.js`
- `pages/api/file/getbloblist.js`
- `pages/api/file/getrepsblob.js`
- Hash/integrity posture updates:
- explicit early 400 guards for missing/empty required query inputs and hash
- standardized early hash-mismatch 400 behaviour in all 4 handlers
- preserved response-shape contracts and function signatures
- Logging discipline updates:
- removed old commented debug traces in touched handlers
- Caller parity:
- no additional service-layer updates required in this slice; existing direct services already append hash for these APIs.
### Phase 10 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (pre-existing hook dependency warnings)
### Phase 10 manual HTTP snapshot
- Missing-hash negative-path checks for all 4 handlers return **400** (`localhost:3000`).
- Valid-hash spot-check result:
- `getbloblist` returned **500** with valid hash in local environment (consistent with downstream/local dependency constraints; hash gate passed).
## Phase 11 hardening status (2026-03-13)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Additional sensitive API hardening slice completed across:
- `pages/api/file/getbloblist.js`
- `pages/api/file/deleteblob.js`
- `pages/api/file/deleteawaitingsubmissionfromblob.js`
- Hash/integrity posture updates:
- explicit early 400 guards for missing/empty required query inputs and hash
- standardized early hash-mismatch 400 behaviour in all touched handlers
- preserved response-shape contracts and function signatures
- Logging discipline updates:
- removed old commented debug traces in touched handlers
- Caller parity:
- no additional service-layer updates required in this slice.
### Phase 11 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `npm run lint` -> warnings only (pre-existing hook dependency warnings)
### Phase 11 manual HTTP snapshot
- Missing/invalid-hash negative-path checks for selected handlers return **400** (`localhost:3000`).
- Valid-hash spot-check result:
- `getbloblist` returned **500** with valid hash in local environment (consistent with downstream/local dependency constraints; hash gate passed).
## Phase 12 hardening status (2026-03-13)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Additional sensitive API hardening slice completed across:
- `pages/api/file/downloadblob.js`
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- Hash/integrity posture updates:
- explicit early 400 guards for missing/empty required query inputs
- standardized early hash-mismatch 400 behaviour in `downloadblob`
- preserved response-shape contracts and function signatures
### Phase 12 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> pass (4/4)
- `npm run lint` -> warnings only (pre-existing hook dependency warnings)
### Phase 12 manual HTTP snapshot
- Missing/invalid-input negative-path checks for selected handlers return **400** (`localhost:3000`).
- Valid-hash spot-check result:
- `downloadblob` returned **500** with valid hash in local environment (consistent with downstream/local dependency constraints; hash gate passed).
## Phase 13 hardening status (2026-03-13)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Additional sensitive API hardening slice completed across:
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createcaseinvolvement_api.js`
- `pages/api/file/createrepinvolvement_api.js`
- Hash/integrity posture updates:
- explicit early 400 guards for missing/empty required query/body inputs
- standardized early hash-mismatch 400 behaviour in delete-blob handlers
- preserved response-shape contracts and function signatures
- Logging discipline updates:
- removed noisy direct body/query logging from involvement handlers
### Phase 13 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> pass (7/7)
- `npm run lint` -> warnings only (pre-existing hook dependency warnings)
### Phase 13 manual HTTP snapshot
- Missing/invalid-input negative-path checks for selected handlers return **400** (`localhost:3000`).
- Valid-hash spot-check result:
- `deleteblobcase` returned **500** with valid hash in local environment (consistent with downstream/local dependency constraints; hash gate passed).
## Phase 14 hardening status (2026-03-13)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Addressed browser runtime error in authenticated new-appeal journey:
- `TypeError` in `hashAPIPath` caused by client-side code attempting to parse server-only `HASHKEY`.
- Implemented server-side hash signing bridge for browser callers:
- new endpoint `pages/api/endpoint/gethash_api.js`
- session-gated (`getSession`), allow-listed supported API prefixes, returns `{ hash }`.
- Updated direct service callers to use signer endpoint instead of direct client hash computation:
- `actions/services/documentDirectService.js`
- `actions/services/portalDirectService.js`
- Maintained contract compatibility for target file APIs and existing response shapes.
### Phase 14 validation evidence snapshot
- phase6 parity/behaviour tests -> pass
- phase7/8/9/10/11/12/13 behaviour tests -> pass
- phase14 behaviour tests -> pass (4/4)
- lint -> warnings only (pre-existing hook dependency warnings)
## Phase 14 closeout follow-up status (2026-03-13)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Browser-safe hash signing expanded for portal login flow:
- `actions/services/accountDirectService.js`
- `getPortalLogin` now requests signed hash from `/api/endpoint/gethash_api`
- server fallback retained for non-browser contexts when `HASHKEY` is present
- `pages/api/endpoint/gethash_api.js`
- allow-list now includes `/api/endpoint/getportallogin_api`
- Contract alignment + runtime fix updates:
- `pages/api/file/getrepsblob.js`
- removed hard requirement for `casefolderID` to match actual route contract (`container + hash`)
- `components/elements/index.js`
- restored missing `updateLinks` import required by Quill-related runtime path
### Follow-up validation evidence
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase14/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- full previously-run phase6phase14 suite remains passing
- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps` set)
### Active risk posture after follow-up
- Signer endpoint scope remains controlled by strict allow-list (single new path added).
- Response-shape contract stability preserved for touched handlers/services.
- Remaining risk is primarily external dependency behavior on valid-hash happy paths in local environments (relay/storage), not hash-bypass behavior.
## Phase 15 hardening status (2026-03-13)
- Working branch created from `origin/SIPS-Development`:
- `TASK22028-phase15-hardening-slice`
- Delivered consistency-only hardening across 4 additional sensitive file handlers:
- `pages/api/file/upload.js`
- `pages/api/file/uploadsinglefile.js`
- `pages/api/file/createappealcompletemessage_api.js`
- `pages/api/file/setupcontainer.js`
- Hardening posture updates:
- standardized explicit early 400 on missing hash where validation was already required
- standardized early hash-mismatch return path style in `setupcontainer`
- preserved response-shape/signature contracts (no intended contract changes)
- Added focused phase-15 tests:
- `tests/phase15/service-behaviour.test.cjs`
- negative-path checks for missing/invalid hash across all selected handlers
- required-input negative-path checks where applicable
- one valid-hash mocked happy-path check (`setupcontainer` response shape)
### Phase 15 validation evidence snapshot
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> pass (7/7)
- `node tests/phase14/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase15/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps` warnings)
### Phase 15 manual HTTP snapshot
- Dev server run on `http://localhost:3002`.
- Missing/invalid hash negative-path checks for selected handlers -> **400**.
- Missing required params where applicable -> **400**.
- Valid-hash spot-check:
- `GET /api/file/setupcontainer?ident=c1&hash=<valid>` -> **200**.
## Phase 16 hardening status (2026-03-13)
- Continued on branch:
- `TASK22028-phase15-hardening-slice`
- Added a consistency-only file-proxy hardening slice across:
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- Changes focused on:
- standardized required-input guards via `hasValue`
- removed unused imports/dead constants from touched proxy handlers
- preserved existing response contracts and status semantics
- Added focused test pack:
- `tests/phase16/service-behaviour.test.cjs`
### Phase 16 validation evidence snapshot
- phase6 parity + phase6phase15 behavioural suites: pass
- phase16 behavioural suite: pass (4/4)
- lint: warnings only (pre-existing)
## Follow-up status (2026-03-13)
- Applied targeted signer allow-list fix for browser delete-awaiting-submission path.
- Updated `pages/api/endpoint/gethash_api.js` allow-list with:
- `/api/file/deleteblobcase`
- Added focused behavioural test:
- `tests/phase14/service-behaviour.test.cjs` now includes allow-list coverage for deleteblobcase path.
## Follow-up status (2026-03-13 — additional delete flows)
- Expanded signer allow-list for additional browser delete paths:
- `/api/endpoint/deletemyrepresentations_api`
- `/api/endpoint/deletewatchedcases_api`
- `/api/file/deleteblobrep`
- Migrated affected direct-service delete calls to signer-based hash retrieval:
- `portalDirectService.deleteWatchedCases`
- `portalDirectService.deleteMyRepresentations`
- `documentDirectService.deleteAwaitingSubmissionsFromBlob`
- `documentDirectService.deleteMyRepresentationsFromBlob`
## Follow-up status (2026-03-13 — myportal + i18n alignment)
- Updated myportal representation delete call sites to pass `caseRef`:
- `components/myportal/topthree_reps.js`
- `components/myportal/viewall.js`
- Applied small view-all presentation cleanup and watched-case refresh filtering adjustment.
- Added missing `myportal` namespace mapping in `i18n.js` for `/newappeal/[appealtypes]`.
## Phase 17 closeout status (2026-03-13)
- Working branch created from `origin/SIPS-Development`:
- `TASK22057-phase17-hardening-closeout`
- Completed remaining closeout hardening slice for sensitive file handlers:
- `pages/api/file/generatepdf.js`
- `pages/api/file/generateappealpdf.js`
- `pages/api/file/createappealcompletemessageproxy_api.js`
- Applied consistency-only updates:
- standardized missing/invalid hash 400 negative paths in selected PDF handlers
- standardized required-input 400 guards where route contract requires it
- removed noisy direct logs from touched sensitive handlers
- preserved existing response shapes/signatures
- Added focused behavioural coverage:
- `tests/phase17/service-behaviour.test.cjs` (6/6 passing)
- includes missing/invalid hash negative-path checks and valid-input proxy happy-path contract check
- Broader consistency scan note:
- no additional broad hash/400 consistency gaps were identified in `pages/api/endpoint/**` in this pass.
## Phase 18 additional consolidation slices (2026-03-14)
- Continued on branch:
- `TASK22057-phase17-hardening-closeout`
- Completed all three requested additional slices:
1. endpoint logging/input hygiene
2. file hash-guard flow consistency
3. create/update input-guard + logging hygiene
- Touched handlers:
- `pages/api/endpoint/deletewatchedcases_api.js`
- `pages/api/endpoint/getaccounts_api.js`
- `pages/api/endpoint/getbasicsearchpaged_api.js`
- `pages/api/endpoint/getappealpdfdocuments_api.js`
- `pages/api/file/deleteblob.js`
- `pages/api/file/deleteawaitingsubmissionfromblob.js`
- `pages/api/file/getprogressobjblob.js`
- `pages/api/file/getbloblist.js`
- `pages/api/file/createcase_api.js`
- `pages/api/file/updatecase_api.js`
- `pages/api/file/editRepJson.js`
- Outcome:
- consistent early 400 guard behavior for missing/invalid required inputs/hash in selected handlers
- noisy direct logs removed in touched sensitive flows
- no response-shape/signature contract changes intended
- Added focused test pack:
- `tests/phase18/service-behaviour.test.cjs` (8/8 pass)
## Phase 19 endpoint consistency slice (2026-03-14)
- Continued on branch:
- `TASK22057-phase17-hardening-closeout`
- Applied a small consistency-only slice across:
- `pages/api/endpoint/createwatchedcases_api.js`
- `pages/api/endpoint/getappealtypes_api.js`
- `pages/api/endpoint/getdnslist_api.js`
- Scope:
- standardized missing/malformed required `@odata.bind` handling in `createwatchedcases_api` with early 400
- removed noisy query logging in appeal-types and dns-list endpoint handlers
- preserved response shapes/signatures
- Added focused test file:
- `tests/phase19/service-behaviour.test.cjs` (5/5 pass)
## Phase 20 endpoint consistency slice (2026-03-14)
- Continued on branch:
- `TASK22057-phase17-hardening-closeout`
- Applied a small consistency-only slice across:
- `pages/api/endpoint/getbasicdnssearch_api.js`
- `pages/api/endpoint/getbasicdnsurlsearch_api.js`
- `pages/api/endpoint/getbasicsearchdetailspaged_api.js`
- Scope:
- removed noisy query logging in `getbasicdnssearch_api`
- added early 400 for missing/empty `searchString` in `getbasicdnsurlsearch_api`
- added early 400 for missing required params in `getbasicsearchdetailspaged_api`
- preserved response shapes/signatures
- Added focused test file:
- `tests/phase20/service-behaviour.test.cjs` (5/5 pass)
## Likely next steps
1. Stabilize and simplify breadcrumb/back-link decision logic with focused regression checks.
2. Standardize hash-check enforcement patterns across file and endpoint APIs.
3. Reduce verbose logging in sensitive flows (auth/email/file/account).
4. Expand automated tests for navigation and representation eligibility edge-cases (currently minimal `tests/`).
See `memory-bank/refactor-backlog.md` for the prioritized refactor plan and sequencing.
Priority 1 detailed execution plan is documented in `memory-bank/refactor-plan-actions-index.md`.
## Areas currently important for contributors
- `components/breadcrumbs.js`
- `components/case/summary.js`
- `pages/api/endpoint/**`
- `pages/api/file/**`
- `pages/api/auth/[...nextauth].js`
- `pages/api/email/notify.js`
- `next.config.js`, `i18n.js`, `locales/**`
File diff suppressed because it is too large Load Diff
-858
View File
@@ -1,858 +0,0 @@
# Progress — PEDW FrontEnd
## Completed / evidenced milestones
- Bilingual routing structure is established with extensive Welsh rewrites and locale namespace mapping.
- Next-auth + Prisma SQL Server persistence is integrated for user/session lifecycle.
- Broad API surface exists for search, case, portal, admin, and file/document operations.
- CRM/relay hash pattern is implemented across many endpoint handlers.
- Recent shipped updates focused on:
- search/case back-navigation and breadcrumb behavior,
- DNS/view-all navigation logic,
- preferred-language handling for notify template selection,
- PDF formatting/hyperlink fixes,
- upload validation character updates.
## Work in progress signals
- Ongoing iterative fixes in breadcrumb/case-summary flows suggest navigation consistency is still being tuned.
- Mixed proxy/non-proxy endpoint implementations continue to evolve in parallel.
- Priority 1 refactor plan execution started for `actions/index.js` split (Phase 1 foundation complete).
## Planned / partially implemented (inferred from code state)
- Partial migration or coexistence of deployment/runtime patterns (Next runtime plus custom server artifacts).
- Continued refinement of document/email workflow behavior (template/language and formatting quality).
- Prioritized structural refactor backlog documented in `memory-bank/refactor-backlog.md`.
## Known technical debt
- `actions/services/legacyActionsService.js` still holds broad domain wrappers after Phase 1 extraction (core helpers now separated).
- Significant duplication of token/hash/relay logic across endpoint files.
- Inconsistent security/hash guard enforcement in some file routes.
- Sparse automated test coverage (`tests/` currently empty).
- Verbose logging in sensitive flows (auth/email/actions/file) increases privacy risk.
## Latest update (2026-03-12)
- Branch `0000-update-actions-monolith` created from development branch for Priority 1 refactor implementation.
- `actions/index.js` reduced to a compatibility barrel.
- New core helper modules added under `actions/core/`:
- `env.js`
- `logger.js`
- `hash.js`
- `token.js`
- `headers.js`
- Existing action wrappers moved to `actions/services/legacyActionsService.js` and continue to be exported via the barrel for compatibility.
- `actions/clients/` scaffolded for upcoming client extraction phase.
- Phase 2 service grouping completed with dedicated service files and `actions/services/index.js` barrel.
- Export parity check confirms grouped service modules cover all legacy exported action names.
- Phase 3 migration started by moving selected high-churn consumers and representative endpoint handlers to direct `actions/services/*` and `actions/core/*` imports.
- Phase 4 hardening baseline added with new guard helpers and redacted logging, adopted in selected notify/login/contact API handlers.
- Additional Priority 1 consumer migration pass completed for high-use portal/form/representation helpers:
- Updated direct service/core imports in:
- `components/admin/tabs/documents.js`
- `components/admin/tabs/storage.js`
- `components/admin/utils/serverside.js`
- `components/case/documents.js`
- `components/case/representation/representationComplete.js`
- `components/case/representation/representationElements.js`
- `components/elements/index.js`
- `components/myportal/awaitingsubmissionfromblob.js`
- `components/myportal/topthree.js`
- `components/myportal/topthree_reps.js`
- `components/utils/index.js`
- `lib/myportal/loadMyPortalAppealPage.js`
- `lib/newappeal/loadNewAppealPage.js`
- Additional Priority 1 consumer migration pass completed for view-all/search/unsubscribe flows:
- Updated direct service/core imports in:
- `components/myportal/viewall.js`
- `components/search/addresssearchresults.js`
- `pages/unsubscribe/[watchlistid].js`
- `pages/unsubscribeall/[watchlistid].js`
- Broad `actions` barrel import count reduced again; remaining non-comment usages are now concentrated in selected `pages/api/endpoint/*` and `pages/api/file/*` handlers.
- Additional Priority 1 endpoint/file proxy migration chunk completed:
- Updated imports in:
- `pages/api/endpoint/getmylpacases_api.js`
- `pages/api/endpoint/getbasicdnssearchpaged_api.js`
- `pages/api/endpoint/getsearchdocumentdetailspaged_api.js`
- `pages/api/endpoint/getbasicsearchpaged_api.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/createappealcompletemessageproxy_api.js`
- Migrated from broad `../../../actions` imports to focused `actions/core/*` + service imports.
- Removed duplicated local hash helper in `getmylpacases_api.js` and reused `actions/core/hash`.
- Broad `actions` import scan now reports only comment-only references (no active broad imports in `.js` files).
- Lint check re-run outcome unchanged: `next lint` fails due legacy/unsupported ESLint options in repo tooling config (not introduced by this refactor pass).
## Handover update (2026-03-13)
- Priority 1 import-migration wave has been checked in and completed via pull request.
- Recent migration commits captured in this wave:
- `f0202bb`
- `fa98535`
- `bef8f25`
- Active broad `.../actions` imports are now eliminated from live `.js` code paths.
- Next refactor phase will begin on a **new branch** and focus on splitting `actions/services/legacyActionsService.js` into smaller direct implementation modules.
- Recommended decomposition order for the new branch:
1. reference/search extraction
2. portal/document extraction
3. compatibility layer slimming + dead wrapper removal
## Latest update (2026-03-13 — Phase 5 kickoff)
- New branch created from `SIPS-Development`: `TASK21997-phase5-legacyactions-split`.
- Completed first Phase 5 decomposition slice from `legacyActionsService`:
- Added `actions/services/searchDirectService.js`
- Added `actions/services/referenceDataDirectService.js`
- Updated `actions/services/searchService.js` to source from `searchDirectService`
- Updated `actions/services/referenceDataService.js` to source from `referenceDataDirectService`
- Compatibility approach preserved: public grouped service exports and function signatures remain unchanged for callers.
- Validation status unchanged:
- `npm run lint` still fails due pre-existing Next/ESLint option incompatibility in repo tooling configuration.
## Latest update (2026-03-13 — Phase 5 document slice)
- Continued on branch `TASK21997-phase5-legacyactions-split`.
- Completed second decomposition slice from `legacyActionsService`:
- Added `actions/services/documentDirectService.js`
- Updated `actions/services/documentService.js` to source from `documentDirectService`
- Compatibility approach preserved:
- grouped service export names/signatures unchanged for consumers.
- Verification:
- service import scan now shows remaining legacy-backed grouped modules are:
- `portalService`, `accountService`, `caseService`, `adminService`, `notifyService`, `integrationService`
- `npm run lint` passes with warnings only (no blocking errors).
## Latest update (2026-03-13 — Phase 5 completion)
- Continued decomposition and completed all remaining slices on `TASK21997-phase5-legacyactions-split`:
- portal direct extraction
- account + case direct extraction
- admin + integration + notify direct extraction
- legacy cleanup/removal
- New direct modules now in place:
- `portalDirectService.js`
- `accountDirectService.js`
- `caseDirectService.js`
- `adminDirectService.js`
- `integrationDirectService.js`
- `notifyDirectService.js`
- `actions/services/legacyActionsService.js` removed after import parity checks.
- Validation evidence:
- `actions/services` import scan: zero `./legacyActionsService` references
- `npm run lint`: warnings only, no blocking errors
- `npm run build`: successful production build
## Latest update (2026-03-13 — Phase 6 post-Phase-5 hardening, in progress)
- Branch created from `SIPS-Development`: `TASK21998-phase6-postphase5-hardening`.
- Added focused parity checks for grouped/direct service stability and service index barrel stability:
- `tests/phase6/service-parity.test.cjs`
- validates grouped service import/export parity across:
- search, referenceData, document, portal, account, case, admin, integration, notify
- validates `actions/services/index.js` export list stability/order.
- Added shared service error helper:
- `actions/services/httpServiceUtils.js`
- centralizes common `consoleLogger + return error.response` and `ErrResponse` shape patterns.
- Applied low-risk duplication reduction in direct services (no signature/shape changes intended):
- `searchDirectService.js`
- `referenceDataDirectService.js`
- `caseDirectService.js`
- `adminDirectService.js`
- Logging cleanup/redaction-oriented hardening (sensitive-flow noisy logs removed):
- removed debug `console.log` statements from:
- `searchDirectService.js`
- `documentDirectService.js`
- `portalDirectService.js`
- `caseDirectService.js`
- `adminDirectService.js`
### Validation snapshot (Phase 6)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `npm run lint` -> **warnings only** (same pre-existing warnings)
### Decision captured (Phase 6)
- Agreed approach: keep parity guard tests **and** add focused behavioural unit tests in the current refactor stream (instead of deferring all behavioural checks to long-term roadmap).
- Implemented now:
- `tests/phase6/service-behaviour.test.cjs`
- mocked-axios behavioural checks for critical success/error contracts across search/reference/case/admin direct services.
- Wider behavioural expansion remains on roadmap for additional domains/functions.
### Targeted smoke snapshot (local dev server)
- Search flow:
- `GET /searchresults` (EN/CY) -> **500** in local env due existing serialization issue (`initialState.search.searchString` undefined in SSR payload), observed in `/tmp/phase6-dev.log`.
- `GET /advancedsearch` and `GET /cy/advancedsearch` -> **200**
- Case flow:
- `GET /case` and `GET /cy/case` -> **200**
- My Portal flow (negative-path):
- `GET /myportal` and `GET /cy/myportal` -> **307** redirect to `/auth/signin` (expected unauthenticated behavior)
- Document flow (negative-path):
- `GET /api/file/getbloblistproxy?container=test&casefolderID=test` -> **400**
- Notify flow (negative-path):
- `POST /api/email/notify` with `{}` -> **400**
## Latest update (2026-03-13 — Phase 8 hardening slice)
- New branch created from `origin/SIPS-Development` with work item prefix:
- `TASK22017-phase8-hardening-slice`
- Hardened four sensitive API handlers with minimal reversible guards:
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createappealcompletemessage_api.js`
- `pages/api/endpoint/getportallogin_api.js`
- Scope delivered:
- re-enabled/enforced hash checks where bypassed/commented
- standardized early 400 negative paths for missing/invalid required query inputs
- reduced noisy sensitive-path logging and routed errors through `consoleLogger`
- preserved function signatures and existing response-shape contracts
- Consumer parity updates (required to preserve behaviour after hash enforcement):
- `actions/services/documentDirectService.js`
- append hash for delete-blob-case/delete-blob-rep calls
- `actions/services/portalDirectService.js`
- append hash for create-appeal-complete-message call (hash path preserved to existing API contract)
- Added focused tests:
- `tests/phase8/service-behaviour.test.cjs`
- covers negative paths for all 4 handlers (missing/invalid hash and missing key params as applicable)
- includes one happy-path check for `getportallogin_api` with valid hash via mocked dependencies
### Validation snapshot (Phase 8)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `npm run lint` -> **warnings only** (pre-existing react-hooks dependency warnings; no new lint errors)
### Manual HTTP checks (Phase 8)
- Local dev server on `http://localhost:3001`.
- Negative-path checks:
- `GET /api/file/deleteblobcase?...` missing hash -> **400**
- `GET /api/file/deleteblobcase?...&hash=bad` -> **400**
- `GET /api/file/deleteblobrep?...` missing hash -> **400**
- `GET /api/file/deleteblobrep?...&hash=bad` -> **400**
- `GET /api/file/deleteblobrep?...` missing `repfile` -> **400**
- `GET /api/file/createappealcompletemessage_api?...` missing hash -> **400**
- `GET /api/file/createappealcompletemessage_api?...&hash=bad` -> **400**
- `GET /api/file/createappealcompletemessage_api?...` missing `tempcaseref` -> **400**
- `GET /api/endpoint/getportallogin_api?...` missing hash -> **400**
- `GET /api/endpoint/getportallogin_api?...&hash=bad` -> **400**
- `GET /api/endpoint/getportallogin_api?...` missing `emailAddress` -> **400**
- Valid-hash spot-check:
- `GET /api/endpoint/getportallogin_api?...&hash=<valid>` reached handler with valid hash but returned **400** from upstream relay/CRM call in local environment (expected environmental dependency risk, not hash-guard bypass).
## Latest update (2026-03-13 — Phase 9 hardening slice)
- New branch created from `origin/SIPS-Development` with work item prefix:
- `TASK22019-phase-9-hardening`
- Hardened four additional sensitive file handlers with minimal reversible changes:
- `pages/api/file/createrepcompletemessage_api.js`
- `pages/api/file/upload.js`
- `pages/api/file/uploadsinglefile.js`
- `pages/api/file/setupcontainer.js`
- Scope delivered:
- re-enabled/enforced hash validation where bypassed/commented
- added/standardized early 400 negative paths for missing/invalid hash and missing key parameters
- reduced noisy logging in sensitive file-upload/message paths
- preserved handler signatures and external response-shape contracts
- Consumer parity updates:
- `actions/services/portalDirectService.js`
- append hash for `createrepcompletemessage_api` call
- `actions/services/documentDirectService.js`
- append hash for `upload` and `uploadsinglefile` calls
- Added focused tests:
- `tests/phase9/service-behaviour.test.cjs`
- covers negative paths across all 4 phase-9 handlers
### Validation snapshot (Phase 9)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `npm run lint` -> **warnings only** (pre-existing react-hooks warnings; no new lint errors)
### Manual HTTP checks (Phase 9)
- Negative-path checks on `localhost:3000`:
- `GET /api/file/createrepcompletemessage_api?...` missing hash -> **400**
- `GET /api/file/createrepcompletemessage_api?...&hash=bad` -> **400**
- `POST /api/file/upload` missing hash -> **400**
- `POST /api/file/upload?hash=bad` -> **400**
- `POST /api/file/uploadsinglefile` missing hash -> **400**
- `POST /api/file/uploadsinglefile?hash=bad` -> **400**
- `GET /api/file/setupcontainer?ident=...` missing hash -> **400**
- `GET /api/file/setupcontainer?ident=...&hash=bad` -> **400**
- `GET /api/file/setupcontainer?hash=bad` (missing ident) -> **400**
- Valid-hash happy-path spot-check:
- Completed using `.env.local` runtime key: `POST /api/file/uploadsinglefile?hash=<valid>` -> **200**
### Rollback plan (Phase 9)
1. Revert the four hardened API handlers.
2. Revert direct-service hash append changes in `documentDirectService` and `portalDirectService`.
3. Remove `tests/phase9/service-behaviour.test.cjs` if full slice rollback required.
4. Re-run phase6/7/8/9 baseline tests + lint after rollback.
## Latest update (2026-03-13 — Phase 10 hardening slice)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Hardened four additional sensitive file handlers with minimal reversible changes:
- `pages/api/file/getawaitingsubmissionfromblob.js`
- `pages/api/file/getprogressobjblob.js`
- `pages/api/file/getbloblist.js`
- `pages/api/file/getrepsblob.js`
- Scope delivered:
- added explicit early 400 handling for missing/empty required inputs (`container`, `casefolderID` where applicable, `hash`)
- standardized hash-mismatch negative-path checks to early return 400
- removed legacy/noisy commented debug blocks from touched handlers
- preserved existing handler signatures and response-shape contracts
- Caller parity impact:
- no new caller changes required in this slice because direct-service consumers already append hash for these APIs.
- Added focused tests:
- `tests/phase10/service-behaviour.test.cjs`
- covers negative paths for all 4 selected phase-10 handlers
- includes one valid-hash contract-preserving happy-path check (`getbloblist` via mocked dependencies)
### Validation snapshot (Phase 10)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `npm run lint` -> **warnings only** (pre-existing react-hooks warnings; no new lint errors)
### Manual HTTP checks (Phase 10)
- Negative-path checks on `localhost:3000`:
- `GET /api/file/getawaitingsubmissionfromblob?container=test` (missing hash) -> **400**
- `GET /api/file/getprogressobjblob?container=test&casefolderID=case-1` (missing hash) -> **400**
- `GET /api/file/getbloblist?container=test&casefolderID=case-1` (missing hash) -> **400**
- `GET /api/file/getrepsblob?container=test&casefolderID=case-1` (missing hash) -> **400**
- Valid-hash happy-path spot-check:
- `GET /api/file/getbloblist?container=test&casefolderID=case-1&hash=<valid>` -> **500**
- expected due local storage/upstream dependency constraints, while confirming hash gate is no longer the failing check.
### Rollback plan (Phase 10)
1. Revert the four hardened API handlers.
2. Remove `tests/phase10/service-behaviour.test.cjs` if full phase-10 rollback required.
3. Re-run phase6/7/8/9/10 baseline tests + lint after rollback.
## Latest update (2026-03-13 — Phase 11 hardening slice)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Hardened three additional sensitive file handlers with minimal reversible changes:
- `pages/api/file/getbloblist.js`
- `pages/api/file/deleteblob.js`
- `pages/api/file/deleteawaitingsubmissionfromblob.js`
- Scope delivered:
- added explicit early 400 handling for missing/empty required query inputs (`container`, `casefolderID`, `blobname`, `hash` as applicable)
- standardized hash-mismatch negative-path checks to early return 400
- removed legacy/noisy commented debug traces in touched handlers
- preserved existing handler signatures and response-shape contracts
- Caller parity impact:
- no new caller changes required in this slice; direct-service consumers already pass expected query/hash data.
- Added focused tests:
- `tests/phase11/service-behaviour.test.cjs`
- covers negative paths for selected phase-11 handlers
- includes one valid-hash contract-preserving happy-path check (`getbloblist` via mocked dependencies)
### Validation snapshot (Phase 11)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `npm run lint` -> **warnings only** (pre-existing react-hooks warnings; no new lint errors)
### Manual HTTP checks (Phase 11)
- Negative-path checks on `localhost:3000`:
- `GET /api/file/getbloblist?container=test&casefolderID=case-1` (missing hash) -> **400**
- `GET /api/file/deleteblob?container=test&casefolderID=case-1&blobname=file.pdf` (missing hash) -> **400**
- `GET /api/file/deleteawaitingsubmissionfromblob?container=test&casefolderID=case-1&blobname=file.pdf` (missing hash) -> **400**
- `GET /api/file/deleteblob?container=test&casefolderID=case-1&hash=bad` (missing blobname/invalid hash) -> **400**
- Valid-hash happy-path spot-check:
- `GET /api/file/getbloblist?container=test&casefolderID=case-1&hash=<valid>` -> **500**
- expected due local storage/upstream dependency constraints, while confirming hash gate is no longer the failing check.
### Rollback plan (Phase 11)
1. Revert the three hardened API handlers.
2. Remove `tests/phase11/service-behaviour.test.cjs` if full phase-11 rollback required.
3. Re-run phase6/7/8/9/10/11 baseline tests + lint after rollback.
## Latest update (2026-03-13 — Phase 12 hardening slice)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Hardened four additional sensitive handlers with minimal reversible changes:
- `pages/api/file/downloadblob.js`
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- Scope delivered:
- added explicit early 400 handling for missing/empty required query inputs (`container`, `casefolderID`, `blobname`, `hash` as applicable)
- standardized hash-mismatch negative-path checks for `downloadblob`
- preserved existing function signatures and response-shape contracts
- Added focused tests:
- `tests/phase12/service-behaviour.test.cjs`
- covers negative paths for selected phase-12 handlers
- includes one valid-hash contract-preserving happy-path check (`downloadblob` via mocked dependencies)
### Validation snapshot (Phase 12)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> **pass** (4/4)
- `npm run lint` -> **warnings only** (pre-existing react-hooks warnings; no new lint errors)
### Manual HTTP checks (Phase 12)
- Negative-path checks on `localhost:3000`:
- `GET /api/file/downloadblob?container=test&casefolderID=case-1&blobname=file.pdf` (missing hash) -> **400**
- `GET /api/file/downloadblob?container=test&casefolderID=case-1&blobname=file.pdf&hash=bad` -> **400**
- `GET /api/file/getbloblistproxy?casefolderID=case-1` (missing container) -> **400**
- `GET /api/file/getrepsblobproxy` (missing container) -> **400**
- `GET /api/file/getawaitingsubmissionfromblobproxy` (missing container) -> **400**
- Valid-hash happy-path spot-check:
- `GET /api/file/downloadblob?container=test&casefolderID=case-1&blobname=file.pdf&hash=<valid>` -> **500**
- expected due local storage/upstream dependency constraints, while confirming hash gate is no longer the failing check.
### Rollback plan (Phase 12)
1. Revert the four hardened API handlers.
2. Remove `tests/phase12/service-behaviour.test.cjs` if full phase-12 rollback required.
3. Re-run phase6/7/8/9/10/11/12 baseline tests + lint after rollback.
## Latest update (2026-03-13 — Phase 13 hardening slice)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Hardened four additional sensitive handlers with minimal reversible changes:
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createcaseinvolvement_api.js`
- `pages/api/file/createrepinvolvement_api.js`
- Scope delivered:
- added explicit early 400 handling for missing/empty required query/body inputs (`hash`, `contactid`, `incidentid` as applicable)
- standardized hash-mismatch negative-path checks for delete-blob handlers
- removed noisy body/query logging in involvement handlers
- preserved existing function signatures and response-shape contracts
- Added focused tests:
- `tests/phase13/service-behaviour.test.cjs`
- covers negative paths for selected phase-13 handlers
- includes valid-hash/valid-body contract-preserving happy-path checks via mocked dependencies
### Validation snapshot (Phase 13)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> **pass** (7/7)
- `npm run lint` -> **warnings only** (pre-existing react-hooks warnings; no new lint errors)
### Manual HTTP checks (Phase 13)
- Negative-path checks on `localhost:3000`:
- `GET /api/file/deleteblobcase?container=test&casefolderID=case-1` (missing hash) -> **400**
- `GET /api/file/deleteblobcase?container=test&casefolderID=case-1&hash=bad` -> **400**
- `GET /api/file/deleteblobrep?container=test&casefolderID=case-1&repfile=r.pdf` (missing hash) -> **400**
- `POST /api/file/createcaseinvolvement_api` with `{}` -> **400**
- `POST /api/file/createrepinvolvement_api` with `{}` -> **400**
- Valid-hash happy-path spot-check:
- `GET /api/file/deleteblobcase?container=test&casefolderID=case-1&hash=<valid>` -> **500**
- expected due local storage/upstream dependency constraints, while confirming hash gate is no longer the failing check.
### Rollback plan (Phase 13)
1. Revert the four hardened API handlers.
2. Remove `tests/phase13/service-behaviour.test.cjs` if full phase-13 rollback required.
3. Re-run phase6/7/8/9/10/11/12/13 baseline tests + lint after rollback.
## Latest update (2026-03-13 — Phase 14 client-hash signing fix)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Fixed authenticated user-journey runtime error during new appeal flow where client-side hashing tried to use server-only `HASHKEY`.
- Added authenticated server-side hash signer endpoint:
- `pages/api/endpoint/gethash_api.js`
- requires session (`getSession`), allow-lists supported API paths, returns `{ hash }`.
- Updated direct services to request hash from server signer for browser calls, with server-side fallback only when `HASHKEY` exists:
- `actions/services/documentDirectService.js`
- `actions/services/portalDirectService.js`
- Preserved existing API contracts and response shapes in file handlers.
- Added focused tests:
- `tests/phase14/service-behaviour.test.cjs`
- covers signer endpoint negative paths + authenticated happy path.
### Validation snapshot (Phase 14)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> **pass** (7/7)
- `node tests/phase14/service-behaviour.test.cjs` -> **pass** (4/4)
- `npm run lint` -> **warnings only** (pre-existing hook dependency warnings)
### Rollback plan (Phase 14)
1. Revert `pages/api/endpoint/gethash_api.js`.
2. Revert direct-service hash signer usage in `documentDirectService.js` and `portalDirectService.js`.
3. Remove `tests/phase14/service-behaviour.test.cjs` if rolling back full phase-14 slice.
4. Re-run phase6phase14 tests + lint.
## Latest update (2026-03-13 — Phase 14 closeout follow-up)
- Continued on branch:
- `TASK22019-phase-9-hardening`
- Completed follow-up hardening and contract-alignment after runtime verification:
- `actions/services/accountDirectService.js`
- `getPortalLogin` now uses authenticated signer endpoint (`/api/endpoint/gethash_api`) for browser-safe hash generation
- retains server-only fallback to local `hashAPIPath` when `HASHKEY` is available
- `pages/api/endpoint/gethash_api.js`
- allow-list expanded for `/api/endpoint/getportallogin_api`
- `pages/api/file/getrepsblob.js`
- removed enforced `casefolderID` guard and argument pass-through to align with route contract (`/api/file/getrepsblob?container=...`)
- `components/elements/index.js`
- added missing `updateLinks` import from `components/utils` to resolve Quill runtime error
- Tests updated:
- `tests/phase7/service-behaviour.test.cjs`
- updated `getPortalLogin` expectations for signer endpoint behavior
- `tests/phase14/service-behaviour.test.cjs`
- added allow-list coverage for signer path `/api/endpoint/getportallogin_api`
### Validation snapshot (Phase 14 follow-up)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> **pass** (7/7)
- `node tests/phase14/service-behaviour.test.cjs` -> **pass** (5/5)
- `npm run lint` -> **warnings only** (pre-existing hook dependency warnings)
### Risks + mitigations (follow-up)
- Risk: signer endpoint over-expansion could broaden hash issuance.
- Mitigation: strict allow-list retained; only required `getportallogin_api` path added.
- Risk: tightened/changed guards could break existing caller contracts.
- Mitigation: `getrepsblob` guard aligned back to actual route contract; response shape unchanged.
- Risk: UI runtime dependency regressions during hardening verification.
- Mitigation: missing `updateLinks` import restored with minimal diff.
### Rollback plan (follow-up)
1. Revert signer-path migration commit `6094874` (or files: `accountDirectService.js`, `gethash_api.js`, `tests/phase7`, `tests/phase14`).
2. Revert contract/import alignment commit `b8fa514` (or files: `getrepsblob.js`, `components/elements/index.js`).
3. Re-run phase6phase14 tests and lint to confirm rollback parity.
### Rollback plan (Phase 8)
1. Revert the four hardened API handlers.
2. Revert direct-service hash append changes in `documentDirectService` and `portalDirectService`.
3. Remove `tests/phase8/service-behaviour.test.cjs` if full slice rollback required.
4. Re-run phase6/7 baseline tests + lint after rollback.
## Latest update (2026-03-13 — Phase 15 hardening slice)
- New branch created from `origin/SIPS-Development` with confirmed work item prefix:
- `TASK22028-phase15-hardening-slice`
- Delivered a small consistency-only hardening slice across 4 additional sensitive file handlers:
- `pages/api/file/upload.js`
- `pages/api/file/uploadsinglefile.js`
- `pages/api/file/createappealcompletemessage_api.js`
- `pages/api/file/setupcontainer.js`
- Scope delivered:
- standardized explicit early 400 handling for missing hash in handlers where hash validation was present but missing-hash guard was inconsistent
- standardized guard style in `setupcontainer` to early-return on hash mismatch (same status/contract)
- standardized missing required hash handling in `createappealcompletemessage_api`
- preserved response shapes and signatures (no contract changes)
- Added focused phase-15 tests:
- `tests/phase15/service-behaviour.test.cjs`
- negative-path coverage for missing hash across all 4 handlers
- additional required-input checks where applicable
- one valid-hash happy-path contract check (`setupcontainer` -> 200 with existing response shape)
### Validation snapshot (Phase 15)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> **pass** (7/7)
- `node tests/phase14/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase15/service-behaviour.test.cjs` -> **pass** (5/5)
- `npm run lint` -> **warnings only** (pre-existing `react-hooks/exhaustive-deps` warnings)
## Latest update (2026-03-14 — Phase 19 endpoint consistency slice)
- Continued on branch:
- `TASK22057-phase17-hardening-closeout`
- Delivered a small additional consistency-only endpoint slice across:
- `pages/api/endpoint/createwatchedcases_api.js`
- `pages/api/endpoint/getappealtypes_api.js`
- `pages/api/endpoint/getdnslist_api.js`
- Scope delivered:
- added required early 400 handling for malformed/missing `@odata.bind` values in `createwatchedcases_api`
- removed noisy query logging in `getappealtypes_api` and `getdnslist_api`
- preserved response-shape/signature contracts
- Added focused tests:
- `tests/phase19/service-behaviour.test.cjs` (5/5 passing)
### Validation snapshot (Phase 19)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (12/12)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> **pass** (7/7)
- `node tests/phase14/service-behaviour.test.cjs` -> **pass** (9/9)
- `node tests/phase15/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase16/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase17/service-behaviour.test.cjs` -> **pass** (6/6)
- `node tests/phase18/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase19/service-behaviour.test.cjs` -> **pass** (5/5)
- `npm run lint` -> **warnings only** (pre-existing `react-hooks/exhaustive-deps` warnings)
## Latest update (2026-03-14 — Phase 20 endpoint consistency slice)
- Continued on branch:
- `TASK22057-phase17-hardening-closeout`
- Delivered a small additional consistency-only endpoint slice across:
- `pages/api/endpoint/getbasicdnssearch_api.js`
- `pages/api/endpoint/getbasicdnsurlsearch_api.js`
- `pages/api/endpoint/getbasicsearchdetailspaged_api.js`
- Scope delivered:
- removed noisy query logging in `getbasicdnssearch_api`
- standardized missing/empty `searchString` handling in `getbasicdnsurlsearch_api` with early 400
- standardized missing required input handling in `getbasicsearchdetailspaged_api` (`appealTypeName`, `primaryIdAttribute`, `incidentID`) with early 400
- preserved response-shape/signature contracts
- Added focused tests:
- `tests/phase20/service-behaviour.test.cjs` (5/5 passing)
### Validation snapshot (Phase 20)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (12/12)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> **pass** (7/7)
- `node tests/phase14/service-behaviour.test.cjs` -> **pass** (9/9)
- `node tests/phase15/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase16/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase17/service-behaviour.test.cjs` -> **pass** (6/6)
- `node tests/phase18/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase19/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase20/service-behaviour.test.cjs` -> **pass** (5/5)
- `npm run lint` -> **warnings only** (pre-existing `react-hooks/exhaustive-deps` warnings)
### Manual HTTP snapshot (Phase 15)
- Dev server run on `http://localhost:3002`.
- Negative-path checks:
- `POST /api/file/upload` missing hash -> **400**
- `POST /api/file/upload?hash=wrong` -> **400**
- `POST /api/file/uploadsinglefile` missing hash -> **400**
- `POST /api/file/uploadsinglefile?hash=wrong` -> **400**
- `GET /api/file/createappealcompletemessage_api?container=c1&tempcaseref=t1` missing hash -> **400**
- `GET /api/file/createappealcompletemessage_api?container=c1&tempcaseref=t1&hash=wrong` -> **400**
- `GET /api/file/createappealcompletemessage_api?container=c1` missing required params/hash -> **400**
- `GET /api/file/setupcontainer?ident=c1` missing hash -> **400**
- `GET /api/file/setupcontainer?ident=c1&hash=wrong` -> **400**
- `GET /api/file/setupcontainer?hash=<valid>` missing required `ident` -> **400**
- Valid-hash spot-check:
- `GET /api/file/setupcontainer?ident=c1&hash=<valid>` -> **200**
### Rollback plan (Phase 15)
1. Revert these files:
- `pages/api/file/upload.js`
- `pages/api/file/uploadsinglefile.js`
- `pages/api/file/createappealcompletemessage_api.js`
- `pages/api/file/setupcontainer.js`
- `tests/phase15/service-behaviour.test.cjs`
2. Re-run phase6phase15 tests and lint to confirm parity.
## Latest update (2026-03-13 — Phase 16 file-proxy consistency slice)
- Continued on branch:
- `TASK22028-phase15-hardening-slice`
- Delivered a small consistency-only hardening slice across 3 sensitive file proxy handlers:
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- Scope delivered:
- removed unused imports/constants in touched handlers to reduce noisy/unsafe dead code
- standardized required input checks using shared local `hasValue` pattern
- preserved response shape/signature/status behavior contracts
- Added focused tests:
- `tests/phase16/service-behaviour.test.cjs`
- negative-path checks for missing required container/casefolder inputs
- one valid-input happy-path contract check for `getbloblistproxy` (200 + passthrough shape)
### Validation snapshot (Phase 16)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> **pass** (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> **pass** (7/7)
- `node tests/phase14/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase15/service-behaviour.test.cjs` -> **pass** (5/5)
- `node tests/phase16/service-behaviour.test.cjs` -> **pass** (4/4)
- `npm run lint` -> **warnings only** (pre-existing `react-hooks/exhaustive-deps` warnings)
### Manual HTTP checks (Phase 16)
- Dev server on `http://localhost:3002`.
- Negative-path checks:
- `/api/file/getbloblistproxy?casefolderID=case-1` -> **400**
- `/api/file/getbloblistproxy?container=c1` -> **400**
- `/api/file/getrepsblobproxy` -> **400**
- `/api/file/getawaitingsubmissionfromblobproxy` -> **400**
- Feasible valid-input spot-check:
- `/api/file/getbloblistproxy?container=c1&casefolderID=case-1` -> **400** (expected downstream/local dependency behavior; input guard passed)
### Rollback plan (Phase 16)
1. Revert commit `c196edb` or files:
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- `tests/phase16/service-behaviour.test.cjs`
2. Re-run phase6phase16 tests and lint.
## Latest update (2026-03-13 — signer allow-list follow-up)
- Branch: `TASK22028-phase15-hardening-slice`
- Fix applied:
- added `/api/file/deleteblobcase` to signer endpoint allow-list in `pages/api/endpoint/gethash_api.js`
- Test update:
- extended `tests/phase14/service-behaviour.test.cjs` with deleteblobcase allow-list coverage
### Validation snapshot (follow-up)
- `node tests/phase14/service-behaviour.test.cjs` -> **pass** (6/6)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `npm run lint` -> **warnings only** (pre-existing)
## Latest update (2026-03-13 — signer delete-flow follow-up)
- Branch: `TASK22028-phase15-hardening-slice`
- Extended signer allow-list and browser signer usage for additional delete flows:
- `deleteWatchedCases`
- `deleteMyRepresentations`
- `deleteAwaitingSubmissionsFromBlob`
- `deleteMyRepresentationsFromBlob`
### Validation snapshot (delete-flow follow-up)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (12/12)
- `node tests/phase14/service-behaviour.test.cjs` -> **pass** (9/9)
- `npm run lint` -> **warnings only** (pre-existing)
## Latest update (2026-03-13 — myportal + i18n follow-up)
- Branch: `TASK22028-phase15-hardening-slice`
- Delivered follow-up fixes:
- myportal representation delete calls now use `caseRef` in top-three and view-all flows
- missing `myportal` namespace added for `/newappeal/[appealtypes]` in `i18n.js`
### Validation snapshot (myportal + i18n follow-up)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (12/12)
- `npm run lint` -> **warnings only** (pre-existing)
## Outstanding risks / gaps
- Navigation regressions across EN/CY + route query combinations.
- Drift between rewrite config and component-level locale path logic.
- Deployment ambiguity due to multiple CI/CD artifacts (Azure pipeline, Jenkins, Docker) with unclear active source of truth.
- Security posture variability where hash checks are bypassed/commented in selected handlers.
## Latest update (2026-03-13 — Phase 7 post-Phase-6 hardening)
- New branch created from `origin/SIPS-Development` with required work item prefix:
- `TASK21988a-phase7-postphase6-hardening`
- Fixed known pre-existing SSR serialization issue on search results route:
- `pages/searchresults.js`
- updated SSR dispatch fallback from `setSearch(query.q)` to `setSearch(query?.q || "")`
- result: `/searchresults` and `/cy/searchresults` now return 200 in local smoke checks (no 500 observed in this run).
- Added expanded behavioural coverage for remaining direct-service domains:
- new test file: `tests/phase7/service-behaviour.test.cjs`
- includes document, portal, account, notify, integration behavioural checks
- includes negative-path assertions for relay/hash/token-sensitive behaviors where feasible:
- hashed URL append checks (`hashAPIPath`)
- error handling contracts (undefined/error string/rethrow depending on existing function contract)
### Validation snapshot (Phase 7)
- `node tests/phase6/service-parity.test.cjs` -> **pass**
- `node tests/phase6/service-behaviour.test.cjs` -> **pass** (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> **pass** (10/10)
- `npm run lint` -> **warnings only** (pre-existing hook dependency warnings; no new lint errors)
### Targeted smoke snapshot (local dev server)
- Note: dev server auto-started on `http://localhost:3001` because port `3000` was in use.
- Search flow:
- `GET /searchresults` and `GET /cy/searchresults` -> **200**
- Case flow:
- `GET /case` and `GET /cy/case` -> **200**
- My Portal flow (negative-path):
- `GET /myportal` and `GET /cy/myportal` -> **307** redirect to `/auth/signin`
- Document flow (negative-path):
- `GET /api/file/getbloblistproxy?container=test&casefolderID=test` -> **400**
- Notify flow (negative-path):
- `POST /api/email/notify` with `{}` -> **400**
-991
View File
@@ -1,991 +0,0 @@
# Refactor Plan — Priority 1 (`actions/index.js` split)
Last updated: 2026-03-13
## Handover note (2026-03-13)
- Import-migration wave from `actions` barrel to focused modules has been checked in and completed via PR.
- Next chunk will be delivered from a **new branch** and should start at legacy decomposition.
### Next branch kickoff scope (Phase 5)
Primary objective: split `actions/services/legacyActionsService.js` into smaller direct implementations while preserving function signatures.
Recommended first slice:
1. Extract reference/search internals from `legacyActionsService.js` into dedicated modules.
2. Keep `actions/services/*` exports stable and route through new direct implementations.
3. Add focused parity checks per extracted function group (inputs, headers, hash behavior, return shapes).
Then continue with:
4. Portal/document extraction in small batches.
5. Remove dead wrappers from `legacyActionsService.js` once call paths are fully migrated.
### Phase 5 slice roadmap (completed)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Search + reference direct extraction
- `searchDirectService.js`, `referenceDataDirectService.js`
- `searchService.js` / `referenceDataService.js` re-pointed
2. `[x]` Document direct extraction
- create `documentDirectService.js`
- re-point `documentService.js`
- parity checks: blob paths/hash behavior/return shapes
3. `[x]` Portal direct extraction
- create `portalDirectService.js`
- re-point `portalService.js`
- parity checks: watched-case and completion-message flows
4. `[x]` Account + case direct extraction
- create `accountDirectService.js`, `caseDirectService.js`
- re-point grouped services
5. `[x]` Notify + integration + admin direct extraction
- create `notifyDirectService.js`, `integrationDirectService.js`, `adminDirectService.js`
6. `[x]` Legacy slim-down pass
- remove dead wrappers from `legacyActionsService.js`
- keep only temporary compatibility exports still required
7. `[x]` Final cleanup + validation pass
- grep checks for remaining `./legacyActionsService` imports
- lint/manual smoke checks
### Phase 5 completion snapshot (2026-03-13)
- All grouped service modules now route through focused direct modules under `actions/services/*DirectService.js`.
- `actions/services/legacyActionsService.js` has been removed.
- Verification completed:
- `actions/services` scan returns zero `./legacyActionsService` imports.
- `npm run lint` completes with warnings only.
- `npm run build` completes successfully.
### Next phase kickoff recommendation
Focus on post-split hardening and cleanup:
1. Add parity-focused tests for extracted direct services and `actions/services/index.js` export stability.
2. Consolidate repeated axios/error-handling patterns into shared clients/utilities where safe.
3. Reduce verbose debug logging in direct services (especially account/case/document paths) with redaction discipline.
4. Run targeted manual smoke matrix for search/case/myportal/document/notify with EN/CY parity checks.
### Phase 6 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Add parity-focused tests for extracted services and barrel stability
- `tests/phase6/service-parity.test.cjs`
- validates grouped/direct service export parity
- validates `actions/services/index.js` export stability
2. `[x]` Reduce repeated axios/error-handling patterns safely
- added shared helper: `actions/services/httpServiceUtils.js`
- adopted in selected direct modules without signature/return-shape changes
3. `[x]` Logging cleanup in sensitive direct-service flows
- removed noisy debug logs from search/document/portal/case/admin direct services
4. `[x]` Add focused behavioural contract tests in current phase
- `tests/phase6/service-behaviour.test.cjs`
- success/error contract checks with mocked axios/logger (8/8 passing)
5. `[x]` Run targeted validation/smoke checks
- parity test: pass
- behavioural test: pass
- manual smoke matrix executed (search/case/myportal/document/notify, EN/CY + negative paths)
Notes:
- Known pre-existing caveat during smoke checks:
- `/searchresults` and `/cy/searchresults` return 500 in local dev due SSR serialization issue (`initialState.search.searchString` undefined).
- treated as existing issue, not introduced by this refactor phase.
### Future requirements / next-phase backlog (post-Phase-6)
1. Investigate and fix `/searchresults` SSR serialization issue (EN/CY parity).
2. Expand behavioural service tests to remaining domains:
- document
- portal
- account
- notify
- integration
3. Add focused negative-path tests for token/hash/relay-sensitive handlers.
4. Continue small-batch hardening with reversible commits and parity checks per batch.
5. Prepare PR evidence bundle for each increment:
- lint + test commands
- behavioural/parity test outputs
- manual EN/CY + negative-path smoke matrix
### Phase 7 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Create new branch from `origin/SIPS-Development` with required work item prefix
- branch: `TASK21988a-phase7-postphase6-hardening`
2. `[x]` Fix pre-existing `/searchresults` SSR serialization issue with EN/CY parity
- updated `pages/searchresults.js`:
- `setSearch(query.q)` -> `setSearch(query?.q || "")`
3. `[x]` Expand behavioural tests to remaining service domains
- added `tests/phase7/service-behaviour.test.cjs`
- coverage includes: document, portal, account, notify, integration
4. `[x]` Add feasible negative-path tests for token/hash/relay-sensitive flows
- validated hashed path composition (e.g., `hashAPIPath` usage)
- validated existing function error contracts (undefined/error string/rethrow)
5. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `npm run lint` -> warnings only (no new errors)
6. `[x]` Execute targeted manual smoke matrix (EN/CY + negative paths)
- local server on `localhost:3001` (3000 occupied)
- `/searchresults`, `/cy/searchresults` -> 200
- `/case`, `/cy/case` -> 200
- `/myportal`, `/cy/myportal` -> 307 -> `/auth/signin`
- `/api/file/getbloblistproxy?container=test&casefolderID=test` -> 400
- `POST /api/email/notify` with `{}` -> 400
### Phase 7 rollback notes
- Revert `pages/searchresults.js` fallback change if search behaviour regresses unexpectedly.
- Remove `tests/phase7/service-behaviour.test.cjs` if test scope needs to be rolled back.
- Reset branch to pre-Phase-7 commit if full rollback required.
### Phase 8 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Create new branch from `origin/SIPS-Development` with required work item prefix
- branch: `TASK22017-phase8-hardening-slice`
2. `[x]` Re-enable/enforce hash validation in 4 sensitive target handlers
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createappealcompletemessage_api.js`
- `pages/api/endpoint/getportallogin_api.js`
3. `[x]` Standardize negative-path behavior for invalid/missing hash and required keys
- added early 400 guards for missing required query params where applicable
- preserved response-shape contracts/signatures
4. `[x]` Improve logging discipline in sensitive flow
- removed noisy direct console output in `createappealcompletemessage_api`
- routed updated error path logging via `consoleLogger`
5. `[x]` Keep caller behavior compatible after hash re-enforcement
- `actions/services/documentDirectService.js` now appends hash for:
- `deleteblobcase`
- `deleteblobrep`
- `actions/services/portalDirectService.js` now appends hash for:
- `createappealcompletemessage_api` (existing hash-path contract retained)
6. `[x]` Add focused Phase 8 tests
- added `tests/phase8/service-behaviour.test.cjs`
- includes negative-path coverage for all 4 target handlers
- includes one valid-hash happy-path contract check (`getportallogin_api`) via mocks
7. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (pre-existing react-hooks dependency warnings)
8. `[x]` Execute manual HTTP negative-path matrix + feasible happy-path spot-check
- invalid/missing hash across 4 target handlers -> 400
- missing required params where tested -> 400
- valid-hash `getportallogin_api` spot-check returned 400 in local env due upstream relay/CRM dependency
### Phase 8 rollback notes
- Revert these files to rollback the full hardening slice:
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createappealcompletemessage_api.js`
- `pages/api/endpoint/getportallogin_api.js`
- `actions/services/documentDirectService.js`
- `actions/services/portalDirectService.js`
- `tests/phase8/service-behaviour.test.cjs`
- Re-run phase6/phase7 baseline tests and lint after rollback to confirm parity.
### Phase 9 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Create new branch from `origin/SIPS-Development` with required work item prefix
- branch: `TASK22019-phase-9-hardening`
2. `[x]` Re-enable/enforce hash validation in 4 additional sensitive file handlers
- `pages/api/file/createrepcompletemessage_api.js`
- `pages/api/file/upload.js`
- `pages/api/file/uploadsinglefile.js`
- `pages/api/file/setupcontainer.js`
3. `[x]` Standardize negative-path behavior for invalid/missing hash and key params
- added early 400 handling for missing required query/body values where applicable
4. `[x]` Improve logging discipline in sensitive paths
- removed noisy direct console logging in touched handlers
5. `[x]` Keep caller behavior compatible after hash re-enforcement
- `actions/services/portalDirectService.js`
- `sendRepCompleteMessage` now appends hash
- `actions/services/documentDirectService.js`
- upload-related calls now append hash
6. `[x]` Add focused Phase 9 tests
- added `tests/phase9/service-behaviour.test.cjs`
- negative-path tests for all 4 selected handlers
7. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (no new lint errors)
8. `[x]` Execute manual HTTP negative-path matrix
- missing/invalid hash checks across selected handlers -> 400
- missing key params where tested -> 400
9. `[x]` Valid-hash HTTP happy-path spot-check
- `POST /api/file/uploadsinglefile?hash=<valid>` -> 200 (hash generated from `.env.local` key)
### Phase 9 rollback notes
- Revert these files to rollback the full hardening slice:
- `pages/api/file/createrepcompletemessage_api.js`
- `pages/api/file/upload.js`
- `pages/api/file/uploadsinglefile.js`
- `pages/api/file/setupcontainer.js`
- `actions/services/portalDirectService.js`
- `actions/services/documentDirectService.js`
- `tests/phase9/service-behaviour.test.cjs`
- Re-run phase6/phase7/phase8/phase9 baseline tests and lint after rollback to confirm parity.
### Phase 10 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Continue connected hardening slices on same branch
- branch: `TASK22019-phase-9-hardening`
2. `[x]` Re-enable/standardize hash + param guard handling in 4 additional file retrieval handlers
- `pages/api/file/getawaitingsubmissionfromblob.js`
- `pages/api/file/getprogressobjblob.js`
- `pages/api/file/getbloblist.js`
- `pages/api/file/getrepsblob.js`
3. `[x]` Standardize negative-path behavior
- added explicit early 400 for missing required query/hash values
- standardized early 400 for hash mismatch
4. `[x]` Improve logging discipline in touched handlers
- removed old commented debug traces
5. `[x]` Add focused Phase 10 tests
- added `tests/phase10/service-behaviour.test.cjs`
- includes negative-path tests for all 4 handlers
6. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `npm run lint` -> warnings only (no new lint errors)
7. `[x]` Execute manual HTTP negative-path matrix + valid-hash spot-check
- missing hash across selected handlers -> 400
- valid hash `getbloblist` spot-check -> 500 (downstream/local dependency)
### Phase 10 rollback notes
- Revert these files to rollback the full hardening slice:
- `pages/api/file/getawaitingsubmissionfromblob.js`
- `pages/api/file/getprogressobjblob.js`
- `pages/api/file/getbloblist.js`
- `pages/api/file/getrepsblob.js`
- `tests/phase10/service-behaviour.test.cjs`
- Re-run phase6/phase7/phase8/phase9/phase10 baseline tests and lint after rollback to confirm parity.
### Phase 11 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Continue connected hardening slices on same branch
- branch: `TASK22019-phase-9-hardening`
2. `[x]` Re-enable/standardize hash + param guard handling in additional sensitive handlers
- `pages/api/file/getbloblist.js`
- `pages/api/file/deleteblob.js`
- `pages/api/file/deleteawaitingsubmissionfromblob.js`
3. `[x]` Standardize negative-path behavior
- added explicit early 400 for missing required query/hash values
- standardized early 400 for hash mismatch
4. `[x]` Improve logging discipline in touched handlers
- removed old commented debug traces
5. `[x]` Add focused Phase 11 tests
- added `tests/phase11/service-behaviour.test.cjs`
- includes negative-path tests for selected handlers
6. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `npm run lint` -> warnings only (no new lint errors)
7. `[x]` Execute manual HTTP negative-path matrix + valid-hash spot-check
- missing/invalid hash across selected handlers -> 400
- valid hash `getbloblist` spot-check -> 500 (downstream/local dependency)
### Phase 11 rollback notes
- Revert these files to rollback the full hardening slice:
- `pages/api/file/getbloblist.js`
- `pages/api/file/deleteblob.js`
- `pages/api/file/deleteawaitingsubmissionfromblob.js`
- `tests/phase11/service-behaviour.test.cjs`
- Re-run phase6/phase7/phase8/phase9/phase10/phase11 baseline tests and lint after rollback to confirm parity.
### Phase 12 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Continue connected hardening slices on same branch
- branch: `TASK22019-phase-9-hardening`
2. `[x]` Re-enable/standardize hash + param guard handling in additional sensitive handlers
- `pages/api/file/downloadblob.js`
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
3. `[x]` Standardize negative-path behavior
- added explicit early 400 for missing required query/hash values
- standardized early 400 for hash mismatch in `downloadblob`
4. `[x]` Add focused Phase 12 tests
- added `tests/phase12/service-behaviour.test.cjs`
- includes negative-path tests for selected handlers
5. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> pass (4/4)
- `npm run lint` -> warnings only (no new lint errors)
6. `[x]` Execute manual HTTP negative-path matrix + valid-hash spot-check
- missing/invalid inputs across selected handlers -> 400
- valid hash `downloadblob` spot-check -> 500 (downstream/local dependency)
### Phase 12 rollback notes
- Revert these files to rollback the full hardening slice:
- `pages/api/file/downloadblob.js`
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- `tests/phase12/service-behaviour.test.cjs`
- Re-run phase6/phase7/phase8/phase9/phase10/phase11/phase12 baseline tests and lint after rollback to confirm parity.
### Phase 13 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Continue connected hardening slices on same branch
- branch: `TASK22019-phase-9-hardening`
2. `[x]` Re-enable/standardize hash + param guard handling in additional sensitive handlers
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createcaseinvolvement_api.js`
- `pages/api/file/createrepinvolvement_api.js`
3. `[x]` Standardize negative-path behavior
- added explicit early 400 for missing required query/hash/body values
- standardized early 400 for hash mismatch in delete handlers
4. `[x]` Improve logging discipline in touched handlers
- removed noisy body/query logging in involvement handlers
5. `[x]` Add focused Phase 13 tests
- added `tests/phase13/service-behaviour.test.cjs`
- includes negative-path tests and valid-input mocked happy paths
6. `[x]` Execute validation bundle
- `node tests/phase6/service-parity.test.cjs` -> pass
- `node tests/phase6/service-behaviour.test.cjs` -> pass (8/8)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (10/10)
- `node tests/phase8/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase9/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase10/service-behaviour.test.cjs` -> pass (5/5)
- `node tests/phase11/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase12/service-behaviour.test.cjs` -> pass (4/4)
- `node tests/phase13/service-behaviour.test.cjs` -> pass (7/7)
- `npm run lint` -> warnings only (no new lint errors)
7. `[x]` Execute manual HTTP negative-path matrix + valid-hash spot-check
- missing/invalid inputs across selected handlers -> 400
- valid hash `deleteblobcase` spot-check -> 500 (downstream/local dependency)
### Phase 13 rollback notes
- Revert these files to rollback the full hardening slice:
- `pages/api/file/deleteblobcase.js`
- `pages/api/file/deleteblobrep.js`
- `pages/api/file/createcaseinvolvement_api.js`
- `pages/api/file/createrepinvolvement_api.js`
- `tests/phase13/service-behaviour.test.cjs`
- Re-run phase6/phase7/phase8/phase9/phase10/phase11/phase12/phase13 baseline tests and lint after rollback to confirm parity.
### Phase 14 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Diagnose authenticated browser journey runtime error
- traced to client-side `hashAPIPath` usage reading server-only `HASHKEY`
2. `[x]` Implement minimal reversible signing bridge
- added `pages/api/endpoint/gethash_api.js`
- session-gated with allow-listed API path prefixes
3. `[x]` Keep client/service contracts stable
- updated `documentDirectService` and `portalDirectService` to request hash from signer endpoint
- kept server-side fallback hashing only when env key exists
4. `[x]` Add focused phase-14 tests
- added `tests/phase14/service-behaviour.test.cjs`
- covers 400/401 negative paths and authenticated happy path
5. `[x]` Execute validation bundle
- phase6phase14 test set all pass
- lint warnings only (pre-existing)
### Phase 14 rollback notes
- Revert these files to rollback the fix slice:
- `pages/api/endpoint/gethash_api.js`
- `actions/services/documentDirectService.js`
- `actions/services/portalDirectService.js`
- `tests/phase14/service-behaviour.test.cjs`
- Re-run phase6phase14 tests + lint after rollback.
### Phase 14 closeout follow-up snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Expand signer usage to remaining browser-sensitive hashed call in account domain
- `actions/services/accountDirectService.js`
- `getPortalLogin` now requests hash from authenticated signer endpoint
2. `[x]` Extend signer allow-list minimally for required route
- `pages/api/endpoint/gethash_api.js`
- added `/api/endpoint/getportallogin_api` only
3. `[x]` Preserve and verify behavioural contract coverage
- `tests/phase7/service-behaviour.test.cjs` updated for signer flow
- `tests/phase14/service-behaviour.test.cjs` extended with allow-list path test
4. `[x]` Apply runtime-discovered contract alignment fixes (minimal/reversible)
- `pages/api/file/getrepsblob.js`
- removed unnecessary `casefolderID` requirement to match endpoint contract
- `components/elements/index.js`
- restored missing `updateLinks` import for Quill path
5. `[x]` Re-run required validation
- phase6 parity + behaviour -> pass
- phase7/8/9/10/11/12/13/14 behaviour -> pass
- lint -> warnings only (pre-existing)
6. `[x]` Push branch for review continuity
- `TASK22019-phase-9-hardening` pushed to origin
### Phase 14 closeout follow-up rollback notes
- Revert `6094874` to remove portal-login signer migration and tests.
- Revert `b8fa514` to remove getrepsblob contract-alignment + Quill import fix.
- Re-run phase6phase14 tests and lint after rollback.
### Phase 15 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Create new branch from `origin/SIPS-Development` with required work item prefix
- branch: `TASK22028-phase15-hardening-slice`
2. `[x]` Apply minimal consistency-only hardening in 4 additional sensitive handlers
- `pages/api/file/upload.js`
- `pages/api/file/uploadsinglefile.js`
- `pages/api/file/createappealcompletemessage_api.js`
- `pages/api/file/setupcontainer.js`
3. `[x]` Standardize negative-path behavior
- explicit early 400 for missing/invalid hash in selected handlers
- explicit missing required-input checks where route contract requires them
- preserved response-shape contracts and function signatures
4. `[x]` Add focused Phase 15 tests
- added `tests/phase15/service-behaviour.test.cjs`
- includes negative-path tests for missing/invalid hash + required-input checks
- includes one valid-hash mocked happy-path response-shape check
5. `[x]` Execute required validation bundle
- phase6 parity + phase6phase14 behaviour tests -> pass
- phase15 behaviour tests -> pass
- lint -> warnings only (pre-existing)
6. `[x]` Execute targeted manual checks
- missing/invalid hash across selected handlers -> 400
- missing required params where applicable -> 400
- valid-hash `setupcontainer` spot-check -> 200
### Phase 15 rollback notes
- Revert these files to rollback the full hardening slice:
- `pages/api/file/upload.js`
- `pages/api/file/uploadsinglefile.js`
- `pages/api/file/createappealcompletemessage_api.js`
- `pages/api/file/setupcontainer.js`
- `tests/phase15/service-behaviour.test.cjs`
- Re-run phase6phase15 baseline tests and lint after rollback to confirm parity.
### Phase 16 completion snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Continue on current branch with separate commit for additional small slice
- branch: `TASK22028-phase15-hardening-slice`
2. `[x]` Apply minimal consistency hardening in 3 file proxy handlers
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
3. `[x]` Standardize required-input guard pattern
- added local `hasValue` checks for required params
- removed unused/dead imports/constants in touched files
4. `[x]` Add focused Phase 16 tests
- added `tests/phase16/service-behaviour.test.cjs`
- missing-input negative-path checks + one valid-input contract check
5. `[x]` Execute validation bundle
- phase6 parity + phase6phase16 behaviour tests pass
- lint warnings only (pre-existing)
6. `[x]` Execute manual checks
- missing required-input proxy calls -> 400
### Phase 16 rollback notes
- Revert commit `c196edb` (or files):
- `pages/api/file/getbloblistproxy.js`
- `pages/api/file/getrepsblobproxy.js`
- `pages/api/file/getawaitingsubmissionfromblobproxy.js`
- `tests/phase16/service-behaviour.test.cjs`
- Re-run phase6phase16 baseline tests and lint.
### Follow-up completion snapshot (2026-03-13)
1. `[x]` Address browser signer failure for delete-awaiting-submission path
- added signer allow-list entry: `/api/file/deleteblobcase`
2. `[x]` Add focused behavioural test coverage
- extended `tests/phase14/service-behaviour.test.cjs`
3. `[x]` Validate
- phase14 behaviour tests pass (6/6)
- phase7 behaviour tests pass (10/10)
- lint warnings only (pre-existing)
### Follow-up rollback notes
- Revert commit `49dd990` to undo allow-list/test follow-up.
### Follow-up completion snapshot (2026-03-13 — additional delete flows)
1. `[x]` Expand signer allow-list for required browser delete paths
2. `[x]` Move remaining delete service calls to signer-based hash retrieval
3. `[x]` Extend focused service/signer tests
4. `[x]` Validate
- phase7 behaviour tests pass (12/12)
- phase14 behaviour tests pass (9/9)
- lint warnings only (pre-existing)
### Follow-up rollback notes (additional delete flows)
- Revert the follow-up commit for this slice to restore prior hashing behavior.
### Follow-up completion snapshot (2026-03-13 — myportal + i18n alignment)
1. `[x]` Align myportal delete-representation calls to use `caseRef`
2. `[x]` Add missing i18n namespace mapping for `/newappeal/[appealtypes]`
3. `[x]` Validate
- phase7 behaviour tests pass (12/12)
- lint warnings only (pre-existing)
### Follow-up rollback notes (myportal + i18n alignment)
- Revert commit `d32d7d0` to restore prior myportal/i18n state.
### Phase 17 closeout snapshot (2026-03-13)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Create new branch from `origin/SIPS-Development` with required work item prefix
- branch: `TASK22057-phase17-hardening-closeout`
2. `[x]` Complete remaining consistency-only hardening targets in `pages/api/file/**`
- `pages/api/file/generatepdf.js`
- `pages/api/file/generateappealpdf.js`
- `pages/api/file/createappealcompletemessageproxy_api.js`
3. `[x]` Standardize negative-path behavior and logging discipline
- explicit early 400 guards for missing/invalid hash where applicable
- explicit early 400 guards for missing required params where applicable
- noisy direct logs removed from touched handlers
- response-shape/signature contracts preserved
4. `[x]` Add focused Phase 17 tests
- added `tests/phase17/service-behaviour.test.cjs`
- includes missing/invalid hash negative-path checks
- includes missing required-param checks
- includes valid-input happy-path check for proxy contract
5. `[x]` Execute required validation bundle
- phase6 parity + phase6phase17 behaviour tests -> pass
- lint -> warnings only (pre-existing)
6. `[x]` Consistency scan for remaining `pages/api/**`
- no additional broad hash/400 consistency gaps identified in `pages/api/endpoint/**` during this pass
### Phase 17 rollback notes
- Revert these files to rollback the full closeout slice:
- `pages/api/file/generatepdf.js`
- `pages/api/file/generateappealpdf.js`
- `pages/api/file/createappealcompletemessageproxy_api.js`
- `tests/phase17/service-behaviour.test.cjs`
- Re-run phase6phase17 baseline tests and lint after rollback to confirm parity.
### Phase 18 additional consolidation slices snapshot (2026-03-14)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Confirm requested scope expansion to implement all 3 additional slices
2. `[x]` Apply endpoint logging/input hygiene slice
- `pages/api/endpoint/deletewatchedcases_api.js`
- `pages/api/endpoint/getaccounts_api.js`
- `pages/api/endpoint/getbasicsearchpaged_api.js`
- `pages/api/endpoint/getappealpdfdocuments_api.js`
3. `[x]` Apply file hash-guard flow consistency slice
- `pages/api/file/deleteblob.js`
- `pages/api/file/deleteawaitingsubmissionfromblob.js`
- `pages/api/file/getprogressobjblob.js`
- `pages/api/file/getbloblist.js`
4. `[x]` Apply create/update input-guard + logging hygiene slice
- `pages/api/file/createcase_api.js`
- `pages/api/file/updatecase_api.js`
- `pages/api/file/editRepJson.js`
5. `[x]` Add focused phase-18 behavioural tests
- added `tests/phase18/service-behaviour.test.cjs`
- negative-path coverage for required inputs/hash
- contract-preserving happy-path checks on selected handlers
6. `[x]` Execute validation bundle
- phase6 parity + phase6phase18 behavioural tests all pass
- lint warnings only (pre-existing `react-hooks/exhaustive-deps` set)
### Phase 18 rollback notes
- Revert files in this slice:
- `pages/api/endpoint/deletewatchedcases_api.js`
- `pages/api/endpoint/getaccounts_api.js`
- `pages/api/endpoint/getbasicsearchpaged_api.js`
- `pages/api/endpoint/getappealpdfdocuments_api.js`
- `pages/api/file/deleteblob.js`
- `pages/api/file/deleteawaitingsubmissionfromblob.js`
- `pages/api/file/getprogressobjblob.js`
- `pages/api/file/getbloblist.js`
- `pages/api/file/createcase_api.js`
- `pages/api/file/updatecase_api.js`
- `pages/api/file/editRepJson.js`
- `tests/phase18/service-behaviour.test.cjs`
- Re-run phase6phase18 baseline tests and lint after rollback to confirm parity.
### Phase 19 endpoint consistency slice snapshot (2026-03-14)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Apply small consistency-only endpoint hardening in selected handlers
- `pages/api/endpoint/createwatchedcases_api.js`
- `pages/api/endpoint/getappealtypes_api.js`
- `pages/api/endpoint/getdnslist_api.js`
2. `[x]` Standardize negative-path required input handling
- early 400 on missing/malformed required `@odata.bind` values in `createwatchedcases_api`
3. `[x]` Remove noisy sensitive-path logging
- removed direct query logging in appeal-types and dns-list handlers
4. `[x]` Add focused tests
- `tests/phase19/service-behaviour.test.cjs` (5/5)
5. `[x]` Execute validation bundle
- phase6 parity + phase6phase19 behavioural suites pass
- lint warnings only (pre-existing)
### Phase 19 rollback notes
- Revert files in this slice:
- `pages/api/endpoint/createwatchedcases_api.js`
- `pages/api/endpoint/getappealtypes_api.js`
- `pages/api/endpoint/getdnslist_api.js`
- `tests/phase19/service-behaviour.test.cjs`
- Re-run phase6phase19 baseline tests and lint after rollback.
### Phase 20 endpoint consistency slice snapshot (2026-03-14)
Status key: `[x] done`, `[ ] pending`
1. `[x]` Apply small consistency-only endpoint hardening in selected handlers
- `pages/api/endpoint/getbasicdnssearch_api.js`
- `pages/api/endpoint/getbasicdnsurlsearch_api.js`
- `pages/api/endpoint/getbasicsearchdetailspaged_api.js`
2. `[x]` Standardize negative-path required input handling
- early 400 for missing/empty `searchString` in `getbasicdnsurlsearch_api`
- early 400 for missing required params in `getbasicsearchdetailspaged_api`
3. `[x]` Remove noisy sensitive-path logging
- removed direct query logging in `getbasicdnssearch_api`
4. `[x]` Add focused tests
- `tests/phase20/service-behaviour.test.cjs` (5/5)
5. `[x]` Execute validation bundle
- phase6 parity + phase6phase20 behavioural suites pass
- lint warnings only (pre-existing)
### Phase 20 rollback notes
- Revert files in this slice:
- `pages/api/endpoint/getbasicdnssearch_api.js`
- `pages/api/endpoint/getbasicdnsurlsearch_api.js`
- `pages/api/endpoint/getbasicsearchdetailspaged_api.js`
- `tests/phase20/service-behaviour.test.cjs`
- Re-run phase6phase20 baseline tests and lint after rollback.
## Safe execution mode for migration chunks (required)
To reduce terminal hangs during bulk migration work, run refactor chunks in **safe stepwise mode** instead of long chained commands.
Required command pattern:
1. Read/inspect target files.
2. Apply edits only.
3. Verify with targeted grep for remaining broad imports.
4. Check `git status`.
5. Stage files.
6. Commit.
7. Re-run verification and then continue to the next chunk.
Rules:
- Do **not** combine edit + verify + add + commit + log in one long chained command.
- Keep each terminal call short and single-purpose.
- If a command is interrupted, re-check `git status` and resume from the next incomplete step.
- Preserve smallest viable diff and behavior parity.
## Implementation status snapshot (2026-03-12)
- Phase 1 completed:
- Extracted core helpers into `actions/core/{env,logger,hash,token,headers}.js`.
- Moved wrapper functions into `actions/services/legacyActionsService.js`.
- Reduced `actions/index.js` to compatibility barrel re-exports.
- Added `actions/clients/README.md` scaffold for upcoming client extraction.
- Validation status:
- `npm run lint` currently fails at framework/tooling option level (legacy ESLint options), not due to this refactor logic.
- Manual test matrix is required next (search/case/myportal/auth/notify/file).
## Implementation status snapshot (Phase 2 update — 2026-03-12)
- Phase 2 completed:
- Added grouped service modules under `actions/services/`:
- `searchService.js`
- `caseService.js`
- `accountService.js`
- `portalService.js`
- `documentService.js`
- `referenceDataService.js`
- `notifyService.js`
- `adminService.js`
- `integrationService.js`
- Added `actions/services/index.js` barrel.
- Updated `actions/index.js` to export from `./services` and `./core/*`.
- Verified export parity: all `legacyActionsService` exports are represented by grouped service modules.
- Current compatibility model:
- Consumers can continue importing from `../actions` with unchanged function names/signatures.
- `legacyActionsService` remains an internal compatibility implementation module until Phase 3 consumer migration.
## Implementation status snapshot (Phase 3 update — 2026-03-12)
- Phase 3 started with targeted consumer migration to focused imports.
- Updated high-churn consumers:
- `components/case/summary.js`
- `createWatchedCases`, `deleteWatchedCases`, `getWatchedCasesProxy`, `setCaseInvolvment` -> `actions/services/portalService`
- `getLinkedCases` -> `actions/services/searchService`
- `getPortalModuleDetailsProxy` -> `actions/services/caseService`
- `pages/api/email/notify.js`
- `consoleLogger` -> `actions/core/logger`
- `getPreferredLanguage` -> `actions/services/accountService`
- Updated selected endpoint handlers to core imports:
- `pages/api/endpoint/getwatchedcases_api.js`
- `pages/api/endpoint/getadvancedsearchpaged_api.js`
- `pages/api/endpoint/getpreferredlanguage_api.js`
- `pages/api/endpoint/createcrmtask_api.js`
- Compatibility remains preserved via `actions/index.js` barrel while migration proceeds incrementally.
## Implementation status snapshot (Phase 4 update — 2026-03-12)
- Phase 4 started with targeted hardening helpers and sensitive-path adoption.
- Added `actions/core/guards.js` with:
- `isNonEmptyString`
- `sanitizeString`
- `escapeODataString`
- Expanded `actions/core/logger.js` with redaction support:
- `redactSensitive`
- masking for likely email/token/secret patterns in logged content
- Updated `actions/index.js` compatibility barrel to export `./core/guards`.
- Applied hardening in selected sensitive/high-churn handlers:
- `pages/api/email/notify.js`
- email input sanitization + required-field guard
- redacted logging for outbound payload diagnostics
- `pages/api/endpoint/getpreferredlanguage_api.js`
- sanitized/validated `emailAddress`
- OData string escaping for query construction
- redacted query logging
- `pages/api/endpoint/createcrmtask_api.js`
- sanitized/validated request inputs for subject/email/body
- centralized structured error logging via `consoleLogger`
## Implementation status snapshot (Priority 1 execution pass — 2026-03-12)
- Completed another targeted consumer migration pass from broad `actions` barrel imports to focused service/core imports in these files:
- `components/admin/tabs/documents.js`
- `components/admin/tabs/storage.js`
- `components/admin/utils/serverside.js`
- `components/case/documents.js`
- `components/case/representation/representationComplete.js`
- `components/case/representation/representationElements.js`
- `components/elements/index.js`
- `components/myportal/awaitingsubmissionfromblob.js`
- `components/myportal/topthree.js`
- `components/myportal/topthree_reps.js`
- `components/utils/index.js`
- `lib/myportal/loadMyPortalAppealPage.js`
- `lib/newappeal/loadNewAppealPage.js`
- Kept compatibility behavior via existing `actions/index.js` barrel.
- Validation note: `npm run lint` remains blocked by repository ESLint/Next option incompatibility (pre-existing tooling configuration).
## Goal
Reduce coupling and regression risk by splitting `actions/index.js` into focused modules while preserving existing behavior and call signatures during migration.
## Why this is first
`actions/index.js` is currently a high-risk hotspot: API wrappers, relay/hash helpers, token acquisition, logging, file helpers, and notification helpers are mixed in one module. This increases blast radius for every change.
## Scope (planned)
- In scope:
- Module extraction and internal architecture cleanup.
- Backward-compatible export strategy.
- Migration plan for consumers under `components/**`, `pages/**`, and `pages/api/**`.
- Out of scope (for this refactor phase):
- Functional changes to business logic.
- Contract changes to API handlers.
- Dependency swaps.
## Proposed target structure
```text
actions/
index.js # compatibility barrel (temporary)
core/
env.js # BASE_URL/API_ROOT/relay roots
logger.js # consoleLogger/conLog with redaction helpers
hash.js # hashAPIPath/hashString/dehashString
token.js # getToken + token request config
headers.js # azureHeaders* helpers
clients/
relayClient.js # signed relay calls + shared axios behavior
endpointClient.js # endpoint route wrappers
fileClient.js # file route wrappers
notifyClient.js # email/notify wrappers
services/
caseService.js
searchService.js
portalService.js
accountService.js
documentService.js
```
Notes:
- Keep `actions/index.js` as a façade initially to avoid breaking imports.
- Move internals first, then update call sites incrementally.
## Migration strategy (phased)
### Phase 1 — Safe extraction with no consumer changes
1. Create new modules under `actions/core/**` and `actions/clients/**`.
2. Move utility functions (`hash`, `headers`, `token`, `logger`, env helpers).
3. Re-export all existing functions from `actions/index.js` unchanged.
4. Verify parity with lint + smoke checks.
### Phase 2 — Service grouping
1. Group route wrapper functions into service modules by domain (case/search/portal/account/document).
2. Keep identical function names/signatures.
3. Add thin unit tests for pure helpers first (`hash`, `env`, route builder helpers).
### Phase 3 — Consumer migration
1. Replace broad imports from `actions/index.js` with targeted imports from new modules.
2. Migrate high-churn areas first:
- `components/breadcrumbs.js`
- `components/case/summary.js`
- `pages/api/email/notify.js`
- selected `pages/api/endpoint/**` handlers
3. Keep index barrel until migration completion.
### Phase 4 — Harden + slim
1. Introduce typed/validated request helper boundaries (JS doc + runtime guard checks).
2. Remove dead exports and duplicate wrappers.
3. Finalize redacted logging policy in sensitive flows.
## Backward compatibility rules
- Do not change public function names/signatures during Phases 12.
- Keep return shapes identical unless explicitly planned and validated.
- Preserve existing hash and relay behavior contract.
- Preserve EN/CY behavior where helper functions influence locale-sensitive flows.
## Validation plan
- `npm run lint`
- Manual smoke paths:
- search -> results -> case summary -> back nav
- myportal viewall -> case summary -> breadcrumb return path
- auth email sign-in flow
- notify new case reference language selection path
- file/document fetch paths touched by moved wrappers
- Negative-path checks:
- invalid hash or malformed query for sensitive handlers
- token acquisition failures and relay timeout handling
## Regression tests and coverage plan (to add during implementation)
### Test tooling recommendation
Current repo has no active automated test runner configured in `package.json`. For this refactor, introduce a minimal unit test setup (recommended: Jest) focused on pure logic first.
Suggested scripts:
- `test`: run all unit tests
- `test:watch`: local watch mode
- `test:coverage`: coverage output for CI and PR evidence
### Minimum tests for Priority 1
1. `actions/core/hash.js`
- deterministic hash for known input
- query string handling (`?hash=` vs `&hash=`)
- malformed/edge inputs
2. `actions/core/headers.js`
- expected OData/auth headers produced for each helper
3. `actions/core/token.js`
- token request config generation
- success and error mapping behavior (mock axios)
4. `actions/core/env.js`
- server vs browser base URL resolution
5. compatibility barrel (`actions/index.js`)
- exports parity test to ensure existing function names remain available during migration
### Integration-style safeguards (mocked external calls)
- Relay client request assembly test:
- signed URL includes hash
- token is attached
- timeout/error behavior is consistent
- Notify client wrapper test:
- language/template routing remains unchanged for PEDW-NEW-CASEREF path
### Coverage targets for this refactor
- `actions/core/**`: >= 90% lines/functions
- `actions/clients/**`: >= 80% lines/functions
- Global coverage gate for this phase: >= 70% (new tests only; avoid blocking unrelated legacy code)
### Regression gate in PR
Required evidence for each phase:
1. `npm run lint`
2. `npm run test`
3. `npm run test:coverage` (attach summary)
4. Manual smoke matrix from this plan (search/case/myportal/auth/notify/file)
## Risks and mitigations
- Risk: import breakage due to broad existing usage.
- Mitigation: keep compatibility barrel and migrate in small batches.
- Risk: hidden behavior differences from refactor-only moves.
- Mitigation: freeze signatures + add helper tests + run route smoke matrix each phase.
- Risk: sensitive logging leakage while touching shared helpers.
- Mitigation: centralize logger early and enforce redaction helper.
## Definition of done (for this priority)
1. `actions/index.js` reduced to compatibility exports only (minimal logic).
2. Core helper modules and service modules exist and are used by migrated consumers.
3. Lint and manual validation matrix pass.
4. Memory docs updated (`activeContext`, `progress`, `change-log`).
+37 -26
View File
@@ -11,40 +11,51 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var token = await getToken();
var queryUrl = "contacts";
var data = JSON.stringify(req.body);
var config = {
method: "post",
url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Accept": "application/json",
"Prefer": 'odata.include-annotations="*",return=representation',
"Authorization": "Bearer " + token.access_token,
"Content-Type": "application/json"
},
data: data
};
return axios(config)
.then(({ data }) => {
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
if (!req.body || Object.keys(req.body).length === 0) {
return respondError(res, {
status: 400,
code: "ACCOUNT_PAYLOAD_REQUIRED",
message: "Account payload is required"
});
}
try {
const token = await getToken();
const queryUrl = "contacts";
const data = JSON.stringify(req.body);
const config = {
method: "post",
url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
Accept: "application/json",
Prefer: 'odata.include-annotations="*",return=representation',
Authorization: "Bearer " + token.access_token,
"Content-Type": "application/json"
},
data
};
const response = await axios(config);
return respondSuccess(res, response.data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "ACCOUNT_CREATE_FAILED",
message: "Failed to create account"
});
}
}
+27 -31
View File
@@ -16,49 +16,45 @@
* description: hello world
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import _ from "lodash";
import { azureHeaders } from "../../../actions/core/headers";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var emailAddress = req.query.emailAddress;
const emailAddress = req.query.emailAddress;
if (typeof emailAddress === "undefined" || emailAddress.length === 0) {
return res.status(400).json();
if (typeof emailAddress !== "string" || emailAddress.trim().length === 0) {
return respondError(res, {
status: 400,
code: "EMAIL_ADDRESS_REQUIRED",
message: "emailAddress is required"
});
}
var token = await getToken();
try {
const token = await getToken();
const queryUrl =
"contacts?$filter=emailaddress1 eq '" +
emailAddress +
"'&$count=true&$select=emailaddress1, contactid";
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
);
var queryUrl =
"contacts?$filter=emailaddress1 eq '" +
emailAddress +
"'&$count=true&$select=emailaddress1, contactid";
// var queryUrl = "contacts/?$count=true&$select=emailaddress1, contactid";
var apiResponse = _.isEmpty(req.query)
? res.status(400).json()
: typeof emailAddress != "undefined" && emailAddress.length > 0
? axios
.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
)
.then(({ data }) => {
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
})
: res.status(400).json();
return apiResponse;
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "ACCOUNTS_FETCH_FAILED",
message: "Failed to fetch accounts"
});
}
}
+96 -96
View File
@@ -22,149 +22,149 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import _ from "lodash";
import { azureHeadersPaged } from "../../../actions/core/headers";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var searchString = req.query.searchstring;
var token = await getToken();
const encodedSearchString = req.query.searchstring;
searchString = _.isEmpty(searchString)
? searchString
: JSON.parse(decodeURI(searchString));
if (
typeof encodedSearchString !== "string" ||
encodedSearchString.trim().length === 0
) {
return respondError(res, {
status: 400,
code: "SEARCH_STRING_REQUIRED",
message: "searchstring is required"
});
}
var queryString = "";
let searchString;
try {
searchString = JSON.parse(decodeURI(encodedSearchString));
} catch (error) {
return respondError(res, {
status: 400,
code: "INVALID_SEARCH_STRING",
message: "searchstring must be valid JSON"
});
}
if (
!searchString ||
typeof searchString !== "object" ||
Object.keys(searchString).length === 0
) {
return respondError(res, {
status: 400,
code: "SEARCH_STRING_REQUIRED",
message: "searchstring is required"
});
}
const toEscapedString = (value) => String(value).replace(/\'/g, "''");
let queryString = "";
queryString +=
_.has(searchString, "q") && searchString.q != null
searchString.q != null
? "(contains(title, '" +
searchString.q.replace(/\'/g, "''") +
toEscapedString(searchString.q) +
"') or contains(ticketnumber,'" +
searchString.q.replace(/\'/g, "''") +
toEscapedString(searchString.q) +
"')) and"
: "";
queryString +=
_.has(searchString, "lpaRef") && searchString.lpaRef != null
searchString.lpaRef != null
? "(contains(pinswg_lpareference, '" +
searchString.lpaRef.replace(/\'/g, "''") +
toEscapedString(searchString.lpaRef) +
"')) and"
: "";
queryString +=
(_.has(searchString, "lpa") || _.has(searchString, "LPA")) &&
(searchString.lpa != null || searchString.LPA != null) &&
searchString.lpa != null
? " _pinswg_associatedlpa_value eq " + searchString.lpa + " and"
: "";
queryString +=
_.has(searchString, "apt") && searchString.apt != null
searchString.apt != null
? " pinswg_appealcasetype eq " + searchString.apt + " and"
: "";
queryString +=
_.has(searchString, "statuscode") && searchString.statuscode != null
searchString.statuscode != null
? " statuscode eq " + searchString.statuscode + " and"
: "";
//console.log(queryString);
var queryUrl =
const queryUrl =
"incidents?$select=pinswg_environmentalstatementlocation,pinswg_caseaddress,modifiedon,description,numberofchildincidents,_accountid_value,_customerid_value,_pinswg_associatedlpa_value,_ownerid_value,pinswg_appealcasetype,statuscode,ticketnumber,title,_primarycontactid_value,pinswg_lpareference,pinswg_appellantagent,pinswg_appellantfirstname,pinswg_appellantlastname&$expand=primarycontactid($select=fullname)&$filter=" +
queryString +
" pinswg_appealcasetype ne null and pinswg_publishtoweb eq true&$orderby=createdon desc&$count=true";
//console.log(queryString, "\n", queryUrl);
try {
const token = await getToken();
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeadersPaged(token.access_token)
);
var apiResponse = _.isEmpty(searchString)
? res.status(400).json()
: axios
.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeadersPaged(token.access_token)
)
.then(async ({ data }) => {
var dataStr;
_.has(data, "@odata.nextLink") == true &&
((dataStr = JSON.stringify(data["@odata.nextLink"])),
(data["@odata.nextLink"] = dataStr.split("/v8.2/")[1]));
if (Object.prototype.hasOwnProperty.call(data, "@odata.nextLink")) {
const dataStr = JSON.stringify(data["@odata.nextLink"]);
data["@odata.nextLink"] = dataStr.split("/v8.2/")[1];
}
if (_.has(searchString, "projecttype")) {
await updateValueArray(data, token);
if (searchString.projecttype != null) {
for (let i = 0; i < data.value.length; i++) {
const item = data.value[i];
async function updateValueArray(data, token) {
for (let i = 0; i < data.value.length; i++) {
const item = data.value[i];
try {
const projectTypeQuery =
"pinswg_sipses?$filter=_pinswg_sipscase_value eq " +
item.incidentid +
" and _pinswg_projecttype_value eq " +
searchString.projecttype +
"&$select=_pinswg_projecttype_value";
try {
// Axios call using the `incidentid` to fetch additional data
const response = await axios.get(
WEBAPI_URL +
"pinswg_sipses?$filter=_pinswg_sipscase_value eq " +
item.incidentid +
" and _pinswg_projecttype_value eq " +
searchString.projecttype +
"&$select=_pinswg_projecttype_value" +
hashAPIPath(
"pinswg_sipses?$filter=_pinswg_sipscase_value eq " +
item.incidentid +
" and _pinswg_projecttype_value eq " +
searchString.projecttype +
"&$select=_pinswg_projecttype_value"
),
azureHeadersPaged(token.access_token)
);
const response = await axios.get(
WEBAPI_URL +
projectTypeQuery +
hashAPIPath(projectTypeQuery),
azureHeadersPaged(token.access_token)
);
// Assuming the response contains the additional data you want to add
// console.log(response.data.value[0]);
console.log(response.data.value);
if (
response.data.value.length > 0 &&
response.data.value[0]._pinswg_projecttype_value != null
) {
Object.assign(item, response.data.value[0]);
}
} catch (error) {
consoleLogger(error);
}
}
if (
response.data.value.length > 0 &&
response.data.value[0]
._pinswg_projecttype_value != null
) {
Object.assign(
item,
response.data.value[0]
); // Update item with new data
} else {
console.log(
`No project type found for incident ID ${item.incidentid}`
);
}
} catch (error) {
console.error(
`Error fetching data for incident ID ${item.incidentid}:`,
error
);
}
}
}
data.value = data.value.filter(
(item) => item._pinswg_projecttype_value != null
);
data.value = data.value.filter(
(item) => item._pinswg_projecttype_value != null
);
data["@odata.count"] = data.value.length;
}
data["@odata.count"] = data.value.length;
// console.log(newDataObj);
}
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
});
return apiResponse;
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "ADVANCED_SEARCH_FETCH_FAILED",
message: "Failed to fetch advanced search results"
});
}
}
@@ -11,39 +11,53 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import { azureHeaders } from "../../../actions/core/headers";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var loggedInUserId = req.query.loggedInUserId;
var token = await getToken();
const loggedInUserId = req.query.loggedInUserId;
var queryUrl =
"incidents?$select=pinswg_environmentalstatementlocation,modifiedon,description,numberofchildincidents,_accountid_value,_customerid_value,_pinswg_associatedlpa_value,_ownerid_value,pinswg_appealcasetype,statuscode,ticketnumber,title, _primarycontactid_value,pinswg_lpareference,pinswg_appellantlastname,pinswg_appellantfirstname,pinswg_appellantagent,pinswg_agentfirstname,pinswg_agentlastname,pinswg_agentcompanyname&$expand=primarycontactid($select=fullname)&$filter=_customerid_value eq " +
loggedInUserId +
" and pinswg_appealcasetype ne null&$orderby=createdon desc&$count=true";
if (
typeof loggedInUserId !== "string" ||
loggedInUserId.trim().length === 0
) {
return respondError(res, {
status: 400,
code: "LOGGED_IN_USER_ID_REQUIRED",
message: "loggedInUserId is required"
});
}
//console.log(WEBAPI_URL + queryUrl + hashAPIPath(queryUrl));
return axios
.get(
try {
const token = await getToken();
const queryUrl =
"incidents?$select=pinswg_environmentalstatementlocation,modifiedon,description,numberofchildincidents,_accountid_value,_customerid_value,_pinswg_associatedlpa_value,_ownerid_value,pinswg_appealcasetype,statuscode,ticketnumber,title, _primarycontactid_value,pinswg_lpareference,pinswg_appellantlastname,pinswg_appellantfirstname,pinswg_appellantagent,pinswg_agentfirstname,pinswg_agentlastname,pinswg_agentcompanyname&$expand=primarycontactid($select=fullname)&$filter=_customerid_value eq " +
loggedInUserId +
" and pinswg_appealcasetype ne null&$orderby=createdon desc&$count=true";
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
)
.then(({ data }) => {
data.value.forEach(function (element) {
element.pinswg_title = element.title;
});
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
);
data.value.forEach(function (element) {
element.pinswg_title = element.title;
});
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "AWAITING_SUBMISSION_PROXY_FETCH_FAILED",
message: "Failed to fetch awaiting submission proxy details"
});
}
}
+42 -46
View File
@@ -16,65 +16,61 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import _ from "lodash";
import { azureHeadersPaged } from "../../../actions/core/headers";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var searchString = req.query.searchString;
var token = await getToken();
const searchString = req.query.searchString;
searchString = searchString.replace(/\'/g, "''");
if (typeof searchString !== "string" || searchString.trim().length === 0) {
return respondError(res, {
status: 400,
code: "SEARCH_STRING_REQUIRED",
message: "searchString is required"
});
}
var queryUrl =
"incidents?$select=pinswg_environmentalstatementlocation,pinswg_caseaddress,modifiedon,description,numberofchildincidents,_accountid_value,_customerid_value,_pinswg_associatedlpa_value,_ownerid_value,pinswg_appealcasetype,statuscode,ticketnumber,title, _primarycontactid_value,pinswg_lpareference,pinswg_appellantagent,pinswg_appellantfirstname,pinswg_appellantlastname&$expand=primarycontactid($select=fullname)&$filter=(contains(title, '" +
searchString +
"') or contains(ticketnumber, '" +
searchString +
"') or contains(pinswg_lpareference, '" +
searchString +
"')) and pinswg_appealcasetype ne null " +
(process.env.SHOWSIPS !== "true"
? "and pinswg_appealcasetype ne 846040002 "
: "") +
"and pinswg_publishtoweb eq true&$orderby=createdon desc&$count=true";
try {
const token = await getToken();
const escapedSearchString = searchString.replace(/\'/g, "''");
console.log(
"\n==========================================\n",
"\nBasic search ",
"\nSearch String: " + searchString,
"\n\nQuery url: " + queryUrl,
"\n\nRelay link: " + WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
"\n==========================================\n"
);
const queryUrl =
"incidents?$select=pinswg_environmentalstatementlocation,pinswg_caseaddress,modifiedon,description,numberofchildincidents,_accountid_value,_customerid_value,_pinswg_associatedlpa_value,_ownerid_value,pinswg_appealcasetype,statuscode,ticketnumber,title, _primarycontactid_value,pinswg_lpareference,pinswg_appellantagent,pinswg_appellantfirstname,pinswg_appellantlastname&$expand=primarycontactid($select=fullname)&$filter=(contains(title, '" +
escapedSearchString +
"') or contains(ticketnumber, '" +
escapedSearchString +
"') or contains(pinswg_lpareference, '" +
escapedSearchString +
"')) and pinswg_appealcasetype ne null " +
(process.env.SHOWSIPS !== "true"
? "and pinswg_appealcasetype ne 846040002 "
: "") +
"and pinswg_publishtoweb eq true&$orderby=createdon desc&$count=true";
var apiResponse = _.isEmpty(req.query)
? res.status(400).json()
: searchString.length > 0
? axios
.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeadersPaged(token.access_token)
)
.then(({ data }) => {
var dataStr;
_.has(data, "@odata.nextLink") == true &&
((dataStr = JSON.stringify(data["@odata.nextLink"])),
(data["@odata.nextLink"] = dataStr.split("/v8.2/")[1]));
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
})
: res.status(400).json();
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeadersPaged(token.access_token)
);
return apiResponse;
if (Object.prototype.hasOwnProperty.call(data, "@odata.nextLink")) {
const dataStr = JSON.stringify(data["@odata.nextLink"]);
data["@odata.nextLink"] = dataStr.split("/v8.2/")[1];
}
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "BASIC_SEARCH_FETCH_FAILED",
message: "Failed to fetch basic search results"
});
}
}
+29 -30
View File
@@ -16,48 +16,47 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import _ from "lodash";
import { azureHeadersPaged } from "../../../actions/core/headers";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var searchString = req.query.id;
var token = await getToken();
const caseId = req.query.id;
var queryUrl = `tasks?$select=_regardingobjectid_value,subject,description,createdon,statuscode,scheduledend,scheduledstart&$count=true&$filter=_regardingobjectid_value eq ${searchString} and contains(subject, 'Banner') and statuscode ne 5&$orderby=createdon desc`;
if (typeof caseId !== "string" || caseId.trim().length === 0) {
return respondError(res, {
status: 400,
code: "CASE_ID_REQUIRED",
message: "id is required"
});
}
console.log(
"\n==========================================\n",
"\nCase messages ",
"\nSearch String: " + searchString,
"\n\nQuery url: " + queryUrl,
"\n\nRelay link: " + WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
"\n==========================================\n"
);
try {
const token = await getToken();
var apiResponse = _.isEmpty(req.query)
? res.status(400).json()
: searchString.length > 0
? axios
.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeadersPaged(token.access_token)
)
.then(({ data }) => {
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
})
: res.status(400).json();
const queryUrl =
"tasks?$select=_regardingobjectid_value,subject,description,createdon,statuscode,scheduledend,scheduledstart&$count=true&$filter=_regardingobjectid_value eq " +
caseId +
" and contains(subject, 'Banner') and statuscode ne 5&$orderby=createdon desc";
return apiResponse;
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeadersPaged(token.access_token)
);
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "CASE_MESSAGE_FETCH_FAILED",
message: "Failed to fetch case messages"
});
}
}
+28 -25
View File
@@ -16,42 +16,45 @@
* description: hello world
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import _ from "lodash";
import { azureHeaders } from "../../../actions/core/headers";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var emailAddress = req.query.emailAddress;
var token = await getToken();
const emailAddress = req.query.emailAddress;
var queryUrl =
"contacts?$filter=emailaddress1 eq '" +
emailAddress +
"'&$count=true&$select=emailaddress1, contactid";
if (typeof emailAddress !== "string" || emailAddress.trim().length === 0) {
return respondError(res, {
status: 400,
code: "EMAIL_ADDRESS_REQUIRED",
message: "emailAddress is required"
});
}
var apiResponse = _.isEmpty(req.query)
? res.status(400).json()
: typeof emailAddress != "undefined" && emailAddress.length > 0
? axios
.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
)
.then(({ data }) => {
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
})
: res.status(400).json();
try {
const token = await getToken();
const queryUrl =
"contacts?$filter=emailaddress1 eq '" +
emailAddress +
"'&$count=true&$select=emailaddress1, contactid";
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
);
return apiResponse;
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "EMAIL_ACCOUNT_CHECK_FAILED",
message: "Failed to check email account"
});
}
}
+38 -33
View File
@@ -22,51 +22,56 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import _ from "lodash";
import { azureHeadersPaged } from "../../../actions/core/headers";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var emailAddress = req.query.emailAddress;
var pwd = req.query.pwd;
var token = await getToken();
const emailAddress = req.query.emailAddress;
const pwd = req.query.pwd;
var queryUrl =
"contacts?$filter=emailaddress1 eq '" +
emailAddress +
"' and pinswg_custom_password eq '" +
pwd +
"'&$count=true&$select=emailaddress1,contactid,pinswg_custom_password,yomifullname,firstname,lastname";
if (typeof emailAddress !== "string" || emailAddress.trim().length === 0) {
return respondError(res, {
status: 400,
code: "EMAIL_ADDRESS_REQUIRED",
message: "emailAddress is required"
});
}
//console.log(_.isEmpty(req.query));
if (typeof pwd !== "string" || pwd.trim().length === 0) {
return respondError(res, {
status: 400,
code: "PASSWORD_REQUIRED",
message: "pwd is required"
});
}
var apiResponse = _.isEmpty(req.query)
? res.status(400).json()
: typeof emailAddress != "undefined" &&
emailAddress.length > 0 &&
typeof pwd != "undefined" &&
pwd.length > 0
? axios
.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeadersPaged(token.access_token)
)
.then(({ data }) => {
console.log(data);
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
})
: res.status(400).json();
try {
const token = await getToken();
const queryUrl =
"contacts?$filter=emailaddress1 eq '" +
emailAddress +
"' and pinswg_custom_password eq '" +
pwd +
"'&$count=true&$select=emailaddress1,contactid,pinswg_custom_password,yomifullname,firstname,lastname";
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeadersPaged(token.access_token)
);
return apiResponse;
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "LOGIN_FETCH_FAILED",
message: "Failed to fetch login details"
});
}
}
@@ -18,35 +18,50 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import { azureHeaders } from "../../../actions/core/headers";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var loggedInUserId = req.query.loggedInUserId;
var token = await getToken();
const loggedInUserId = req.query.loggedInUserId;
var queryUrl =
"pinswg_representationses?$filter= _pinswg_contact_value eq " +
loggedInUserId +
"&$count=true&$orderby=createdon desc";
if (
typeof loggedInUserId !== "string" ||
loggedInUserId.trim().length === 0
) {
return respondError(res, {
status: 400,
code: "LOGGED_IN_USER_ID_REQUIRED",
message: "loggedInUserId is required"
});
}
return axios
.get(
try {
const token = await getToken();
const queryUrl =
"pinswg_representationses?$filter= _pinswg_contact_value eq " +
loggedInUserId +
"&$count=true&$orderby=createdon desc";
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
)
.then(({ data }) => {
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
);
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "MY_REPRESENTATIONS_PROXY_FETCH_FAILED",
message: "Failed to fetch my representations proxy details"
});
}
}
+28 -25
View File
@@ -18,42 +18,45 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import { azureHeaders } from "../../../actions/core/headers";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var contactid = req.query.contactid;
var token = await getToken();
const contactid = req.query.contactid;
var queryUrl =
"contacts(" +
contactid +
")?$select=firstname, lastname, emailaddress1, telephone1, company, address1_line1,address1_line2,address1_city, address1_county,address1_postalcode,pinswg_typeofinvolvement,pinswg_contact_associatedlpa,pinswg_preferredlanguage&$count=true";
if (typeof contactid !== "string" || contactid.trim().length === 0) {
return respondError(res, {
status: 400,
code: "CONTACT_ID_REQUIRED",
message: "contactid is required"
});
}
//console.log(WEBAPI_URL + queryUrl + hashAPIPath(queryUrl));
try {
const token = await getToken();
const queryUrl =
"contacts(" +
contactid +
")?$select=firstname, lastname, emailaddress1, telephone1, company, address1_line1,address1_line2,address1_city, address1_county,address1_postalcode,pinswg_typeofinvolvement,pinswg_contact_associatedlpa,pinswg_preferredlanguage&$count=true";
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
);
var apiResponse =
contactid.length > 0
? axios
.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
)
.then(({ data }) => {
res.status(200).json(data);
})
.catch((err) => {
console.log(consoleLogger(err));
res.status(400).json(err);
})
: res.status(400).json();
return apiResponse;
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "PERSONAL_ACCOUNT_FETCH_FAILED",
message: "Failed to fetch personal account"
});
}
}
+40 -25
View File
@@ -17,50 +17,65 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import { azureHeadersPaged } from "../../../actions/core/headers";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var emailAddress = req.query.emailAddress;
var checkHash = req.query.hash;
const emailAddress = req.query.emailAddress;
const checkHash = req.query.hash;
if (typeof emailAddress == "undefined" || emailAddress.length === 0) {
return res.status(400).json();
if (typeof emailAddress !== "string" || emailAddress.trim().length === 0) {
return respondError(res, {
status: 400,
code: "EMAIL_ADDRESS_REQUIRED",
message: "emailAddress is required"
});
}
var checkquerypath =
if (typeof checkHash !== "string" || checkHash.length === 0) {
return respondError(res, {
status: 400,
code: "HASH_REQUIRED",
message: "hash is required"
});
}
const checkquerypath =
"/api/endpoint/getportallogin_api?emailAddress=" + emailAddress;
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
return res.status(400).json();
return respondError(res, {
status: 400,
code: "INVALID_HASH",
message: "Invalid hash"
});
}
var token = await getToken();
var queryUrl =
"contacts?$filter=emailaddress1 eq '" +
emailAddress +
"' and statuscode eq 1&$count=true&$select=emailaddress1,contactid,yomifullname,firstname,lastname";
var apiResponse = axios
.get(
try {
const token = await getToken();
const queryUrl =
"contacts?$filter=emailaddress1 eq '" +
emailAddress +
"' and statuscode eq 1&$count=true&$select=emailaddress1,contactid,yomifullname,firstname,lastname";
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeadersPaged(token.access_token)
)
.then(({ data }) => {
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
});
);
return apiResponse;
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "PORTAL_LOGIN_FETCH_FAILED",
message: "Failed to fetch portal login"
});
}
}
+28 -28
View File
@@ -17,45 +17,45 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import _ from "lodash";
import { azureHeadersPaged } from "../../../actions/core/headers";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var emailAddress = req.query.emailAddress;
var token = await getToken();
const emailAddress = req.query.emailAddress;
var queryUrl =
"contacts?$filter=emailaddress1 eq '" +
emailAddress +
"'&$count=true&$select=emailaddress1,contactid,yomifullname,firstname,lastname";
if (typeof emailAddress !== "string" || emailAddress.trim().length === 0) {
return respondError(res, {
status: 400,
code: "EMAIL_ADDRESS_REQUIRED",
message: "emailAddress is required"
});
}
//console.log(req.query);
try {
const token = await getToken();
const queryUrl =
"contacts?$filter=emailaddress1 eq '" +
emailAddress +
"'&$count=true&$select=emailaddress1,contactid,yomifullname,firstname,lastname";
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeadersPaged(token.access_token)
);
var apiResponse = _.isEmpty(req.query)
? res.status(400).json()
: typeof emailAddress != "undefined" && emailAddress.length > 0
? axios
.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeadersPaged(token.access_token)
)
.then(({ data }) => {
//console.log(data);
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
})
: res.status(400).json();
return apiResponse;
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "PORTAL_LOGIN_PROXY_FETCH_FAILED",
message: "Failed to fetch portal login proxy details"
});
}
}
+27 -30
View File
@@ -16,54 +16,51 @@
* description: hello world
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import _ from "lodash";
import { azureHeaders } from "../../../actions/core/headers";
import {
escapeODataString,
isNonEmptyString,
sanitizeString
} from "../../../actions/core/guards";
import { consoleLogger, redactSensitive } from "../../../actions/core/logger";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var emailAddress = sanitizeString(req.query.emailAddress);
var token = await getToken();
const emailAddress = sanitizeString(req.query.emailAddress);
if (!isNonEmptyString(emailAddress)) {
return res.status(400).json({ error: "emailAddress is required" });
return respondError(res, {
status: 400,
code: "EMAIL_ADDRESS_REQUIRED",
message: "emailAddress is required"
});
}
var queryUrl =
"contacts?$filter=emailaddress1 eq '" +
escapeODataString(emailAddress) +
"'&$count=true&$select=pinswg_preferredlanguage,contactid";
try {
const token = await getToken();
const queryUrl =
"contacts?$filter=emailaddress1 eq '" +
escapeODataString(emailAddress) +
"'&$count=true&$select=pinswg_preferredlanguage,contactid";
console.log(redactSensitive(queryUrl));
// var queryUrl = "contacts/?$count=true&$select=emailaddress1, contactid";
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
);
var apiResponse = _.isEmpty(req.query)
? res.status(400).json()
: typeof emailAddress != "undefined" && emailAddress.length > 0
? axios
.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
)
.then(({ data }) => {
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
})
: res.status(400).json();
return apiResponse;
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "PREFERRED_LANGUAGE_FETCH_FAILED",
message: "Failed to fetch preferred language"
});
}
}
@@ -18,35 +18,47 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import { azureHeaders } from "../../../actions/core/headers";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var incidentID = req.query.incidentID;
var token = await getToken();
const incidentID = req.query.incidentID;
var queryUrl =
"pinswg_representationses?$filter= _pinswg_case_value eq " +
incidentID +
"&$count=true&$orderby=createdon desc";
if (typeof incidentID !== "string" || incidentID.trim().length === 0) {
return respondError(res, {
status: 400,
code: "INCIDENT_ID_REQUIRED",
message: "incidentID is required"
});
}
return axios
.get(
try {
const token = await getToken();
const queryUrl =
"pinswg_representationses?$filter= _pinswg_case_value eq " +
incidentID +
"&$count=true&$orderby=createdon desc";
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
)
.then(({ data }) => {
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
);
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "REPRESENTATIONS_PROXY_FETCH_FAILED",
message: "Failed to fetch representations proxy details"
});
}
}
+50 -44
View File
@@ -18,69 +18,75 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import { azureHeaders } from "../../../actions/core/headers";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
const BASE_URL = process.env.API_ROOT || `http://localhost:${port}`;
export default async function ApiProxy(req, res) {
var loggedInUserId = req.query.loggedInUserId;
var token = await getToken();
const loggedInUserId = req.query.loggedInUserId;
// var queryUrl =
// "pinswg_watchlists?$filter= _pinswg_contact_value eq " +
// loggedInUserId +
// "&$select=pinswg_emailnotifications,modifiedon,pinswg_appealcasetype,pinswg_watchlistid,_pinswg_watchedcase_value,statuscode,pinswg_representationsubmitted,pinswg_representationtype&$count=true&$orderby=createdon desc&$expand=pinswg_WatchedCase($select=pinswg_AssociatedLPA)";
if (
typeof loggedInUserId !== "string" ||
loggedInUserId.trim().length === 0
) {
return respondError(res, {
status: 400,
code: "LOGGED_IN_USER_ID_REQUIRED",
message: "loggedInUserId is required"
});
}
var queryUrl =
"pinswg_watchlists?$filter= _pinswg_contact_value eq " +
loggedInUserId +
"&$select=modifiedon,pinswg_appealcasetype,pinswg_watchlistid,_pinswg_watchedcase_value,statuscode,pinswg_representationsubmitted,pinswg_representationtype&$count=true&$orderby=createdon desc&$expand=pinswg_WatchedCase($select=pinswg_AssociatedLPA)";
try {
const token = await getToken();
const queryUrl =
"pinswg_watchlists?$filter= _pinswg_contact_value eq " +
loggedInUserId +
"&$select=modifiedon,pinswg_appealcasetype,pinswg_watchlistid,_pinswg_watchedcase_value,statuscode,pinswg_representationsubmitted,pinswg_representationtype&$count=true&$orderby=createdon desc&$expand=pinswg_WatchedCase($select=pinswg_AssociatedLPA)";
return axios
.get(
const { data } = await axios.get(
WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
azureHeaders(token.access_token)
)
.then(({ data }) => {
data.value.forEach(function (element) {
element.ticketnumber = element.pinswg_WatchedCase.ticketnumber;
element.pinswg_title =
element[
"_pinswg_watchedcase_value@OData.Community.Display.V1.FormattedValue"
];
);
data.value.forEach(function (element) {
element.ticketnumber = element.pinswg_WatchedCase.ticketnumber;
element.pinswg_title =
element[
"_pinswg_watchedcase_value@OData.Community.Display.V1.FormattedValue"
];
element[
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue"
] =
element.pinswg_WatchedCase[
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue"
] =
element.pinswg_WatchedCase[
"_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue"
];
element._pinswg_associatedlpa_value =
element.pinswg_WatchedCase._pinswg_associatedlpa_value;
element[
];
element._pinswg_associatedlpa_value =
element.pinswg_WatchedCase._pinswg_associatedlpa_value;
element[
"_ownerid_value@OData.Community.Display.V1.FormattedValue"
] =
element.pinswg_WatchedCase[
"_ownerid_value@OData.Community.Display.V1.FormattedValue"
] =
element.pinswg_WatchedCase[
"_ownerid_value@OData.Community.Display.V1.FormattedValue"
];
element._ownerid_value =
element.pinswg_WatchedCase._ownerid_value;
];
element._ownerid_value = element.pinswg_WatchedCase._ownerid_value;
delete element.pinswg_WatchedCase;
});
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
delete element.pinswg_WatchedCase;
});
return respondSuccess(res, data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "WATCHED_CASES_PROXY_FETCH_FAILED",
message: "Failed to fetch watched cases proxy details"
});
}
}
+46 -28
View File
@@ -11,43 +11,61 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var contactId = req.query.contactId;
var token = await getToken();
var data = JSON.stringify(req.body);
var queryUrl = "contacts(" + contactId + ")";
const contactId = req.query.contactId;
var config = {
method: "patch",
url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Accept": "application/json",
"Prefer": 'odata.include-annotations="*",return=representation',
"Authorization": "Bearer " + token.access_token,
"Content-Type": "application/json"
},
data: data
};
console.log(config);
return axios(config)
.then(({ data }) => {
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
if (typeof contactId !== "string" || contactId.trim().length === 0) {
return respondError(res, {
status: 400,
code: "CONTACT_ID_REQUIRED",
message: "contactId is required"
});
}
if (!req.body || Object.keys(req.body).length === 0) {
return respondError(res, {
status: 400,
code: "ACCOUNT_UPDATE_PAYLOAD_REQUIRED",
message: "Account update payload is required"
});
}
try {
const token = await getToken();
const data = JSON.stringify(req.body);
const queryUrl = "contacts(" + contactId + ")";
const config = {
method: "patch",
url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
Accept: "application/json",
Prefer: 'odata.include-annotations="*",return=representation',
Authorization: "Bearer " + token.access_token,
"Content-Type": "application/json"
},
data
};
const response = await axios(config);
return respondSuccess(res, response.data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "ACCOUNT_UPDATE_FAILED",
message: "Failed to update account"
});
}
}
+46 -26
View File
@@ -11,41 +11,61 @@
*/
import axios from "axios";
import CryptoJS from "crypto-js";
import { consoleLogger } from "../../../actions/core/logger";
import { getToken } from "../../../actions/core/token";
import { hashAPIPath } from "../../../actions/core/hash";
import { respondError, respondSuccess } from "../middleware/apiResponse";
const WEBAPI_URL =
process.env.RELAY_ROOT ||
"https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/";
export default async function ApiProxy(req, res) {
var contactId = req.query.contactId;
var token = await getToken();
var data = JSON.stringify(req.body);
var queryUrl = "contacts(" + contactId + ")";
const contactId = req.query.contactId;
var config = {
method: "patch",
url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Accept": "application/json",
"Prefer": 'odata.include-annotations="*",return=representation',
"Authorization": "Bearer " + token.access_token,
"Content-Type": "application/json"
},
data: data
};
return axios(config)
.then(({ data }) => {
res.status(200).json(data);
})
.catch((error) => {
consoleLogger(error);
res.status(400).json(error);
if (typeof contactId !== "string" || contactId.trim().length === 0) {
return respondError(res, {
status: 400,
code: "CONTACT_ID_REQUIRED",
message: "contactId is required"
});
}
if (!req.body || Object.keys(req.body).length === 0) {
return respondError(res, {
status: 400,
code: "PASSWORD_UPDATE_PAYLOAD_REQUIRED",
message: "Password update payload is required"
});
}
try {
const token = await getToken();
const data = JSON.stringify(req.body);
const queryUrl = "contacts(" + contactId + ")";
const config = {
method: "patch",
url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
Accept: "application/json",
Prefer: 'odata.include-annotations="*",return=representation',
Authorization: "Bearer " + token.access_token,
"Content-Type": "application/json"
},
data
};
const response = await axios(config);
return respondSuccess(res, response.data);
} catch (error) {
consoleLogger(error);
return respondError(res, {
status: 400,
code: "PASSWORD_UPDATE_FAILED",
message: "Failed to update password"
});
}
}
@@ -1,11 +1,13 @@
const runHelperTests = require("./api-response-helper.test.cjs");
const runHandlerTests = require("./file-handler-contract.test.cjs");
const runEmailHandlerTests = require("./email-handler-contract.test.cjs");
const runEndpointHandlerTests = require("./endpoint-handler-contract.test.cjs");
const run = async () => {
await runHelperTests();
await runHandlerTests();
await runEmailHandlerTests();
await runEndpointHandlerTests();
console.log("Phase 21 combined suite passed.");
};
@@ -0,0 +1,992 @@
const assert = require("assert");
const {
loadModule,
createRes,
respondSuccessMock,
respondErrorMock
} = require("./_shared.cjs");
const tests = [];
const test = (name, fn) => tests.push({ name, fn });
test("getaccounts returns EMAIL_ADDRESS_REQUIRED when emailAddress missing", async () => {
const mod = loadModule("pages/api/endpoint/getaccounts_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
});
const req = { query: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "EMAIL_ADDRESS_REQUIRED");
});
test("getaccounts catch path returns ACCOUNTS_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getaccounts_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
});
const req = { query: { emailAddress: "user@test.local" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "ACCOUNTS_FETCH_FAILED");
});
test("getemailaccountcheck returns EMAIL_ADDRESS_REQUIRED when emailAddress missing", async () => {
const mod = loadModule("pages/api/endpoint/getemailaccountcheck_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
});
const req = { query: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "EMAIL_ADDRESS_REQUIRED");
});
test("getemailaccountcheck catch path returns EMAIL_ACCOUNT_CHECK_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getemailaccountcheck_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
});
const req = { query: { emailAddress: "user@test.local" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"EMAIL_ACCOUNT_CHECK_FAILED"
);
});
test("getpreferredlanguage returns EMAIL_ADDRESS_REQUIRED when emailAddress missing", async () => {
const mod = loadModule("pages/api/endpoint/getpreferredlanguage_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
sanitizeString: () => "",
isNonEmptyString: () => false,
escapeODataString: (value) => value,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
});
const req = { query: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "EMAIL_ADDRESS_REQUIRED");
});
test("getpreferredlanguage catch path returns PREFERRED_LANGUAGE_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getpreferredlanguage_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
sanitizeString: (value) => value,
isNonEmptyString: () => true,
escapeODataString: (value) => value,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
});
const req = { query: { emailAddress: "user@test.local" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"PREFERRED_LANGUAGE_FETCH_FAILED"
);
});
test("getpreferredlanguage success returns existing data payload contract", async () => {
const mod = loadModule("pages/api/endpoint/getpreferredlanguage_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
sanitizeString: (value) => value,
isNonEmptyString: () => true,
escapeODataString: (value) => value,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: {
get: async () => ({
data: {
value: [
{
contactid: "c1",
pinswg_preferredlanguage: 807570001
}
]
}
})
},
consoleLogger: () => {}
});
const req = { query: { emailAddress: "user@test.local" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
value: [
{
contactid: "c1",
pinswg_preferredlanguage: 807570001
}
]
});
});
test("getlogin returns EMAIL_ADDRESS_REQUIRED when emailAddress missing", async () => {
const mod = loadModule("pages/api/endpoint/getlogin_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
});
const req = { query: { pwd: "abc123" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "EMAIL_ADDRESS_REQUIRED");
});
test("getlogin returns PASSWORD_REQUIRED when pwd missing", async () => {
const mod = loadModule("pages/api/endpoint/getlogin_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
});
const req = { query: { emailAddress: "user@test.local" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "PASSWORD_REQUIRED");
});
test("getlogin catch path returns LOGIN_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getlogin_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
});
const req = { query: { emailAddress: "user@test.local", pwd: "abc123" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "LOGIN_FETCH_FAILED");
});
test("getportallogin returns HASH_REQUIRED when hash missing", async () => {
const mod = loadModule("pages/api/endpoint/getportallogin_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: (input) =>
input && input.startsWith("/api/endpoint/getportallogin_api")
? "&hash=expected"
: "&hash=relay",
azureHeadersPaged: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
});
const req = { query: { emailAddress: "user@test.local" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "HASH_REQUIRED");
});
test("getportallogin returns INVALID_HASH when hash mismatch", async () => {
const mod = loadModule("pages/api/endpoint/getportallogin_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: (input) =>
input && input.startsWith("/api/endpoint/getportallogin_api")
? "&hash=expected"
: "&hash=relay",
azureHeadersPaged: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
});
const req = { query: { emailAddress: "user@test.local", hash: "wrong" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "INVALID_HASH");
});
test("getportallogin catch path returns PORTAL_LOGIN_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getportallogin_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: (input) =>
input && input.startsWith("/api/endpoint/getportallogin_api")
? "&hash=expected"
: "&hash=relay",
azureHeadersPaged: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
});
const req = {
query: { emailAddress: "user@test.local", hash: "expected" }
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"PORTAL_LOGIN_FETCH_FAILED"
);
});
test("getportalloginproxy catch path returns PORTAL_LOGIN_PROXY_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getportalloginproxy_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
});
const req = { query: { emailAddress: "user@test.local" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"PORTAL_LOGIN_PROXY_FETCH_FAILED"
);
});
test("getpersonalaccount returns CONTACT_ID_REQUIRED when contactid missing", async () => {
const mod = loadModule("pages/api/endpoint/getpersonalaccount_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: { get: async () => ({ data: { contactid: "c1" } }) },
consoleLogger: () => {}
});
const req = { query: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "CONTACT_ID_REQUIRED");
});
test("getpersonalaccount catch path returns PERSONAL_ACCOUNT_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getpersonalaccount_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
});
const req = { query: { contactid: "c1" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"PERSONAL_ACCOUNT_FETCH_FAILED"
);
});
test("getaccounts success returns existing data payload contract", async () => {
const mod = loadModule("pages/api/endpoint/getaccounts_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: {
get: async () => ({ data: { value: [{ contactid: "c1" }] } })
},
consoleLogger: () => {}
});
const req = { query: { emailAddress: "user@test.local" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
value: [{ contactid: "c1" }]
});
});
test("getemailaccountcheck success returns existing data payload contract", async () => {
const mod = loadModule("pages/api/endpoint/getemailaccountcheck_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: {
get: async () => ({
data: { value: [{ emailaddress1: "u@test" }] }
})
},
consoleLogger: () => {}
});
const req = { query: { emailAddress: "user@test.local" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
value: [{ emailaddress1: "u@test" }]
});
});
test("getwatchedcasesproxy returns LOGGED_IN_USER_ID_REQUIRED when loggedInUserId missing", async () => {
const mod = loadModule("pages/api/endpoint/getwatchedcasesproxy_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
});
const req = { query: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"LOGGED_IN_USER_ID_REQUIRED"
);
});
test("getwatchedcasesproxy catch path returns WATCHED_CASES_PROXY_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getwatchedcasesproxy_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
});
const req = { query: { loggedInUserId: "c1" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"WATCHED_CASES_PROXY_FETCH_FAILED"
);
});
test("getmyrepresentationsproxy returns LOGGED_IN_USER_ID_REQUIRED when loggedInUserId missing", async () => {
const mod = loadModule(
"pages/api/endpoint/getmyrepresentationsproxy_api.js",
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
}
);
const req = { query: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"LOGGED_IN_USER_ID_REQUIRED"
);
});
test("getmyrepresentationsproxy catch path returns MY_REPRESENTATIONS_PROXY_FETCH_FAILED", async () => {
const mod = loadModule(
"pages/api/endpoint/getmyrepresentationsproxy_api.js",
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
}
);
const req = { query: { loggedInUserId: "c1" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"MY_REPRESENTATIONS_PROXY_FETCH_FAILED"
);
});
test("getrepresentationsproxy returns INCIDENT_ID_REQUIRED when incidentID missing", async () => {
const mod = loadModule(
"pages/api/endpoint/getrepresentationsproxy_api.js",
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
}
);
const req = { query: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "INCIDENT_ID_REQUIRED");
});
test("getrepresentationsproxy catch path returns REPRESENTATIONS_PROXY_FETCH_FAILED", async () => {
const mod = loadModule(
"pages/api/endpoint/getrepresentationsproxy_api.js",
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
}
);
const req = { query: { incidentID: "i1" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"REPRESENTATIONS_PROXY_FETCH_FAILED"
);
});
test("getawaitingsubmissionproxy returns LOGGED_IN_USER_ID_REQUIRED when loggedInUserId missing", async () => {
const mod = loadModule(
"pages/api/endpoint/getawaitingsubmissionproxy_api.js",
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
}
);
const req = { query: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"LOGGED_IN_USER_ID_REQUIRED"
);
});
test("getawaitingsubmissionproxy catch path returns AWAITING_SUBMISSION_PROXY_FETCH_FAILED", async () => {
const mod = loadModule(
"pages/api/endpoint/getawaitingsubmissionproxy_api.js",
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeaders: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
}
);
const req = { query: { loggedInUserId: "c1" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"AWAITING_SUBMISSION_PROXY_FETCH_FAILED"
);
});
test("createaccount returns ACCOUNT_PAYLOAD_REQUIRED when body missing", async () => {
const mod = loadModule("pages/api/endpoint/createaccount_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
axios: async () => ({ data: { contactid: "c1" } }),
consoleLogger: () => {}
});
const req = { query: {}, body: undefined };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"ACCOUNT_PAYLOAD_REQUIRED"
);
});
test("createaccount catch path returns ACCOUNT_CREATE_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/createaccount_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
axios: async () => {
throw new Error("relay failed");
},
consoleLogger: () => {}
});
const req = { query: {}, body: { firstname: "A" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "ACCOUNT_CREATE_FAILED");
});
test("updateaccount returns CONTACT_ID_REQUIRED when contactId missing", async () => {
const mod = loadModule("pages/api/endpoint/updateaccount_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
axios: async () => ({ data: { contactid: "c1" } }),
consoleLogger: () => {}
});
const req = { query: {}, body: { firstname: "A" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "CONTACT_ID_REQUIRED");
});
test("updateaccount returns ACCOUNT_UPDATE_PAYLOAD_REQUIRED when body missing", async () => {
const mod = loadModule("pages/api/endpoint/updateaccount_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
axios: async () => ({ data: { contactid: "c1" } }),
consoleLogger: () => {}
});
const req = { query: { contactId: "c1" }, body: undefined };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"ACCOUNT_UPDATE_PAYLOAD_REQUIRED"
);
});
test("updateaccount catch path returns ACCOUNT_UPDATE_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/updateaccount_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
axios: async () => {
throw new Error("relay failed");
},
consoleLogger: () => {}
});
const req = { query: { contactId: "c1" }, body: { firstname: "A" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "ACCOUNT_UPDATE_FAILED");
});
test("updatepassword returns CONTACT_ID_REQUIRED when contactId missing", async () => {
const mod = loadModule("pages/api/endpoint/updatepassword_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
axios: async () => ({ data: { contactid: "c1" } }),
consoleLogger: () => {}
});
const req = { query: {}, body: { pinswg_password: "abc" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "CONTACT_ID_REQUIRED");
});
test("updatepassword returns PASSWORD_UPDATE_PAYLOAD_REQUIRED when body missing", async () => {
const mod = loadModule("pages/api/endpoint/updatepassword_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
axios: async () => ({ data: { contactid: "c1" } }),
consoleLogger: () => {}
});
const req = { query: { contactId: "c1" }, body: undefined };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"PASSWORD_UPDATE_PAYLOAD_REQUIRED"
);
});
test("updatepassword catch path returns PASSWORD_UPDATE_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/updatepassword_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
axios: async () => {
throw new Error("relay failed");
},
consoleLogger: () => {}
});
const req = {
query: { contactId: "c1" },
body: { pinswg_password: "abc" }
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "PASSWORD_UPDATE_FAILED");
});
test("getbasicsearch returns SEARCH_STRING_REQUIRED when searchString missing", async () => {
const mod = loadModule("pages/api/endpoint/getbasicsearch_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
});
const req = { query: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "SEARCH_STRING_REQUIRED");
});
test("getbasicsearch catch path returns BASIC_SEARCH_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getbasicsearch_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
});
const req = { query: { searchString: "CAS" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"BASIC_SEARCH_FETCH_FAILED"
);
});
test("getcasemessage returns CASE_ID_REQUIRED when id missing", async () => {
const mod = loadModule("pages/api/endpoint/getcasemessage_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
});
const req = { query: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "CASE_ID_REQUIRED");
});
test("getcasemessage catch path returns CASE_MESSAGE_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getcasemessage_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
});
const req = { query: { id: "i1" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"CASE_MESSAGE_FETCH_FAILED"
);
});
test("getadvancedsearch returns SEARCH_STRING_REQUIRED when searchstring missing", async () => {
const mod = loadModule("pages/api/endpoint/getadvancedsearch_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
});
const req = { query: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "SEARCH_STRING_REQUIRED");
});
test("getadvancedsearch returns INVALID_SEARCH_STRING when searchstring invalid JSON", async () => {
const mod = loadModule("pages/api/endpoint/getadvancedsearch_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
axios: { get: async () => ({ data: { value: [] } }) },
consoleLogger: () => {}
});
const req = { query: { searchstring: "not-json" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "INVALID_SEARCH_STRING");
});
test("getadvancedsearch catch path returns ADVANCED_SEARCH_FETCH_FAILED", async () => {
const mod = loadModule("pages/api/endpoint/getadvancedsearch_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "token" }),
hashAPIPath: () => "&hash=expected",
azureHeadersPaged: () => ({}),
axios: {
get: async () => {
throw new Error("relay failed");
}
},
consoleLogger: () => {}
});
const req = {
query: {
searchstring: encodeURI(
JSON.stringify({ q: "cas", lpa: "l1", apt: "1" })
)
}
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"ADVANCED_SEARCH_FETCH_FAILED"
);
});
const run = async () => {
let passed = 0;
for (const currentTest of tests) {
await currentTest.fn();
passed += 1;
}
console.log(
`Phase 21 endpoint-handler contract tests passed (${passed}/${tests.length}).`
);
};
module.exports = run;
if (require.main === module) {
run().catch((error) => {
console.error(error);
process.exit(1);
});
}