Skip to content

Conversation

@ChrisS1512
Copy link
Contributor

@ChrisS1512 ChrisS1512 commented Dec 12, 2025

Jira link

https://tools.hmcts.net/jira/browse/VIBE-310

Change description

Blob Explorer page for both JSON and Flat files.

Re-submission calls a mock endpoint at present while VIBE-221 is being worked on

Summary by CodeRabbit

  • New Features

    • Blob Explorer for System Admins: browse locations → publications, view JSON and flat-file metadata/content, download files, trigger re-submission with confirmation and success pages; dashboard tile updated.
  • Internationalisation

    • English and Welsh translations; locale-aware date/time and safe HTML display.
  • Documentation

    • Added plan and specification for Blob Explorer.
  • Tests

    • Expanded unit and end-to-end tests covering workflows, accessibility, locale handling, file serving, and cleanup.
  • Chores

    • Deterministic dependency resolution added.

✏️ Tip: You can customize this high-level summary in your review settings.

github-actions bot and others added 2 commits December 5, 2025 15:06
Generated specification and implementation plan for blob explorer feature allowing system admins to view publication metadata and manually trigger re-submissions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Dec 12, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds a System Admin "Blob Explorer" feature: multi‑page UI (locations → publications → JSON/flat-file → confirm → success), publication repository/service helpers for artefact data and file access, protected file-serving API, session augmentation, notification service, translations/templates, unit/E2E tests, and documentation.

Changes

Cohort / File(s) Summary
Documentation
docs/tickets/VIBE-310/plan.md, docs/tickets/VIBE-310/specification.md
New plan and specification describing Blob Explorer flows, pages, architecture, testing strategy and DoD.
End-to-end tests
e2e-tests/tests/blob-explorer.spec.ts, e2e-tests/tests/system-admin-dashboard.spec.ts
New E2E suite for the Blob Explorer flow; updated dashboard test to expect /blob-explorer-locations.
Publication library — API & repo
libs/publication/src/index.ts, libs/publication/src/repository/queries.ts, .../queries.test.ts, libs/publication/src/repository/service.ts, .../service.test.ts
Added public types and query helpers (ArtefactSummary, ArtefactMetadata, LocationWithPublicationCount, getArtefactSummariesByLocation, getArtefactMetadata, getArtefactType, getLocationsWithPublicationCount, getArtefactListTypeId) and storage utilities (getJsonContent, getRenderedTemplateUrl, getFlatFileUrl) with tests.
System admin pages — exports & config
libs/system-admin-pages/src/index.ts, libs/system-admin-pages/src/config.ts
Re-exported service module and added apiRoutes config to expose API routes.
System admin service & types
libs/system-admin-pages/src/services/service.ts, .../service.test.ts, libs/system-admin-pages/src/types/session.ts
Added sendPublicationNotifications(artefactId) (mock impl), session augmentation resubmissionArtefactId, and tests.
Blob Explorer — Locations
libs/system-admin-pages/src/pages/blob-explorer-locations/{en.ts,cy.ts,index.ts,index.njk,index.test.ts}
New Locations page: translations, GET handler, template and unit tests listing locations with publication counts.
Blob Explorer — Publications
libs/system-admin-pages/src/pages/blob-explorer-publications/{en.ts,cy.ts,index.ts,index.njk,index.test.ts}
Publications page: translations, GET handler, template and tests; builds artefact links by type.
Blob Explorer — JSON file view
libs/system-admin-pages/src/pages/blob-explorer-json-file/{en.ts,cy.ts,index.ts,index.njk,index.test.ts}
JSON file page: translations, GET/POST handlers, template (accordion for pretty JSON), uses getJsonContent/getRenderedTemplateUrl, and tests.
Blob Explorer — Flat file view
libs/system-admin-pages/src/pages/blob-explorer-flat-file/{en.ts,cy.ts,index.ts,index.njk,index.test.ts}
Flat-file page: translations, GET/POST handlers, template with file link (getFlatFileUrl), and tests.
Blob Explorer — Confirm resubmission
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/{en.ts,cy.ts,index.ts,index.njk,index.test.ts}
Confirmation page: GET/POST (SYSTEM_ADMIN guard), template, translations, tests; triggers sendPublicationNotifications and clears session on success.
Blob Explorer — Resubmission success
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/{en.ts,cy.ts,index.ts,index.njk,index.test.ts}
Success page: translations, GET handler, template, and tests.
File serving route
libs/system-admin-pages/src/routes/files/[filename].ts, .../[filename].test.ts
Protected API route to serve temp-storage files with filename validation, safe path resolution, content-type handling, and tests.
Formatting & escaping utilities
libs/system-admin-pages/src/services/formatting.ts
New helpers: formatDateTime and escapeHtml used by handlers/templates.
Localization & Templates
libs/system-admin-pages/src/pages/*/{en.ts,cy.ts}, libs/system-admin-pages/src/pages/*/index.njk
Added English/Welsh translation modules and Nunjucks templates for Blob Explorer pages.
Application wiring & tests
apps/web/src/app.ts, apps/web/src/app.test.ts, libs/system-admin-pages/src/pages/system-admin-dashboard/en.ts, .../index.njk.test.ts
Registered new system-admin API routes in app; adjusted tests and dashboard tile link to /blob-explorer-locations.
Package resolutions
package.json
Updated resolutions to pin jws to 4.0.1.
sequenceDiagram
    autonumber
    participant Admin as System Admin
    participant Dashboard as Dashboard
    participant Locations as Locations Page
    participant Publications as Publications Page
    participant FilePage as File Page (JSON/Flat)
    participant Confirm as Confirm Page
    participant PublicationSvc as Publication Service
    participant Storage as Temp Storage / FS
    participant Session as Session Store

    Admin->>Dashboard: open dashboard
    Dashboard->>Locations: click Blob Explorer tile
    Locations->>PublicationSvc: getLocationsWithPublicationCount()
    PublicationSvc-->>Locations: locations[]
    Locations->>Admin: render locations table

    Admin->>Publications: click location link
    Publications->>PublicationSvc: getArtefactSummariesByLocation(locationId)
    PublicationSvc-->>Publications: artefact summaries
    Publications->>Admin: render artefacts list

    Admin->>FilePage: click artefact link
    FilePage->>PublicationSvc: getArtefactMetadata(artefactId)
    PublicationSvc-->>FilePage: metadata
    FilePage->>Storage: getJsonContent / getFlatFileUrl / getRenderedTemplateUrl
    Storage-->>FilePage: content or URL
    FilePage->>Admin: display metadata + content/link

    Admin->>FilePage: POST resubmit
    FilePage->>Session: set resubmissionArtefactId
    FilePage->>Confirm: redirect to confirm page
    Confirm->>PublicationSvc: getArtefactMetadata(artefactId)
    PublicationSvc-->>Confirm: metadata
    Admin->>Confirm: click Confirm
    Confirm->>PublicationSvc: sendPublicationNotifications(artefactId)
    PublicationSvc-->>Confirm: resolved
    Confirm->>Admin: redirect to success page
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Areas needing extra attention:
    • libs/publication/src/repository/queries.ts — raw SQL, bigint → number conversion, provenance/list-type resolution.
    • libs/publication/src/repository/service.ts — filesystem path validation, artefactId sanitization, URL encoding.
    • Route handlers/templates/session lifecycle — consistent i18n, role guards, redirects and error rendering.
    • E2E tests — Prisma setup/teardown and temporary file lifecycle to avoid flakiness.

Possibly related PRs

Suggested reviewers

  • junaidiqbalmoj
  • KianKwa
  • NatashaAlker

Poem

🐰 I hopped through en and cy with cheer,

locations, pubs, and files appear.
Safe paths, a confirm, a tiny test-run,
notifications sent — the job is done.
— a rabbit, nibbling a merged carrot 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'VIBE-310 - Blob Explorer' directly corresponds to the main feature introduced in this PR—a comprehensive Blob Explorer implementation for system admins. It clearly identifies the ticket and feature.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libs/publication/src/repository/queries.test.ts (1)

59-193: Fix create/update expectations: createArtefact now sends noMatch, so these tests will fail.
libs/publication/src/repository/queries.ts includes noMatch: data.noMatch in both prisma.artefact.create({...}) and prisma.artefact.update({...}), but the assertions here expect an exact payload without noMatch.

   it("should create a new artefact when no existing artefact is found", async () => {
     const artefactData = {
       artefactId: "550e8400-e29b-41d4-a716-446655440000",
       locationId: "1",
       listTypeId: 6,
       contentDate: new Date("2025-10-23"),
       sensitivity: "PUBLIC",
       language: "ENGLISH",
       displayFrom: new Date("2025-10-20"),
       displayTo: new Date("2025-10-30"),
       isFlatFile: true,
-      provenance: "MANUAL_UPLOAD"
+      provenance: "MANUAL_UPLOAD",
+      noMatch: false
     };

     ...
     expect(prisma.artefact.create).toHaveBeenCalledWith({
       data: {
         artefactId: artefactData.artefactId,
         locationId: artefactData.locationId,
         listTypeId: artefactData.listTypeId,
         contentDate: artefactData.contentDate,
         sensitivity: artefactData.sensitivity,
         language: artefactData.language,
         displayFrom: artefactData.displayFrom,
         displayTo: artefactData.displayTo,
         isFlatFile: true,
-        provenance: "MANUAL_UPLOAD"
+        provenance: "MANUAL_UPLOAD",
+        noMatch: false
       }
     });
   });

   it("should update an existing artefact when a match is found", async () => {
     ...
     const artefactData = {
       ...
       isFlatFile: true,
-      provenance: "MANUAL_UPLOAD"
+      provenance: "MANUAL_UPLOAD",
+      noMatch: false
     };

     ...
     expect(prisma.artefact.update).toHaveBeenCalledWith({
       where: { artefactId: existingArtefactId },
       data: {
         sensitivity: artefactData.sensitivity,
         displayFrom: artefactData.displayFrom,
         displayTo: artefactData.displayTo,
         isFlatFile: true,
         provenance: "MANUAL_UPLOAD",
+        noMatch: false,
         lastReceivedDate: expect.any(Date),
         supersededCount: {
           increment: 1
         }
       }
     });
♻️ Duplicate comments (3)
libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts (1)

1-20: Welsh locale file contains English text instead of Welsh translations.

The cy.ts file contains English text rather than Welsh translations. This is the same issue as in blob-explorer-locations/cy.ts. Please ensure all Welsh locale files either contain actual Welsh translations or are removed if Welsh support is not required for system admin pages.

libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts (1)

1-7: Welsh locale file contains English text instead of Welsh translations.

Same issue as the other Welsh locale files in this PR. The cy.ts file should contain Welsh translations, not English text.

libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts (1)

1-19: Welsh locale file contains English text instead of Welsh translations.

Same issue as the other Welsh locale files in this PR. The cy.ts file should contain Welsh translations, not English text.

🧹 Nitpick comments (29)
libs/system-admin-pages/src/services/service.ts (1)

1-4: Consider using a proper logging framework instead of console.log.

While this is a mock implementation, using a structured logging framework (if available in the project) would be more maintainable and production-ready.

docs/tickets/VIBE-310/plan.md (1)

1-230: Consider simplifying the code fence formatting.
The document uses ```` fences; if you’re not nesting code blocks, standard ``` tends to render more reliably across viewers.

libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk (1)

73-96: Guard date formatting if fields can be null/undefined.
If metadata.contentDate/displayFrom/displayTo can be absent, ensure formatDateTime handles it or add conditional rendering.

libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts (1)

33-35: Avoid hard-coding GET[1] in tests (brittle to middleware changes).
Consider importing/exposing the underlying handler for tests, or locating the non-auth handler more explicitly.

Also applies to: 52-54, 76-78, 100-102, 130-132, 150-152, 175-177

libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.ts (1)

19-22: Replace session: {} as any with a narrow typed stub.
Even in tests, prefer a minimal type SessionStub = { resubmissionArtefactId?: string } and cast to unknown as Request["session"] (or the project’s session type) to avoid any.

libs/system-admin-pages/src/pages/blob-explorer-locations/index.ts (1)

32-40: Prefer structured logging over console.error (if the app has a logger).
Not blocking, but consistency/observability will be better if you use the project’s logger (and avoid logging sensitive payloads).

libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts (1)

1-7: Remove or clarify the intent of cy.ts file.

The file contains English strings identical to en.ts. If Welsh support is not intended for admin screens (per established practice), remove the Welsh locale files and the locale-switching logic from the page controller. If Welsh support is planned, provide actual Welsh translations. The current state (identical English in both files with active locale selection) is misleading.

libs/system-admin-pages/src/services/service.test.ts (1)

5-6: Consider restoring spy after each test.

vi.clearAllMocks() clears call history but doesn't restore the original console.log. Since vi.spyOn is called in each test, you may want to add vi.restoreAllMocks() in an afterEach or use vi.spyOn(...).mockImplementation(...) patterns to avoid potential interference between tests.

   beforeEach(() => {
     vi.clearAllMocks();
   });
+
+  afterEach(() => {
+    vi.restoreAllMocks();
+  });
e2e-tests/tests/blob-explorer.spec.ts (2)

114-114: Non-null assertions on environment variables could cause cryptic failures.

If SSO_TEST_SYSTEM_ADMIN_EMAIL or SSO_TEST_SYSTEM_ADMIN_PASSWORD are not set, the test will fail with unclear errors. Consider validating these at the start of the test or in a setup hook.

+test.beforeAll(() => {
+  if (!process.env.SSO_TEST_SYSTEM_ADMIN_EMAIL || !process.env.SSO_TEST_SYSTEM_ADMIN_PASSWORD) {
+    throw new Error("SSO_TEST_SYSTEM_ADMIN_EMAIL and SSO_TEST_SYSTEM_ADMIN_PASSWORD must be set");
+  }
+});

146-150: Document why the "region" accessibility rule is disabled.

The region rule is disabled across all accessibility checks. If this is intentional (e.g., the app layout doesn't use landmark regions), add a brief comment explaining why to avoid future confusion.

libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts (2)

10-19: Date formatting ignores user locale.

formatDateTime always uses "en-GB" locale regardless of whether the user selected Welsh (cy). Consider passing the locale parameter to provide localized date/time formatting.

-function formatDateTime(isoString: string): string {
+function formatDateTime(isoString: string, locale: "en" | "cy"): string {
   const date = new Date(isoString);
-  return date.toLocaleString("en-GB", {
+  return date.toLocaleString(locale === "cy" ? "cy-GB" : "en-GB", {
     day: "2-digit",
     month: "2-digit",
     year: "numeric",
     hour: "2-digit",
     minute: "2-digit"
   });
 }

3-3: Side-effect import without explanation.

The @hmcts/web-core import doesn't use any exports. If this is an intentional side-effect import (e.g., for Express extensions), add a comment explaining why it's needed.

libs/system-admin-pages/src/pages/blob-explorer-locations/index.test.ts (1)

36-38: GET[1] indexing is a bit brittle—consider a helper to select the “last” handler. If middleware order changes (e.g., auth added/removed), these tests will silently start exercising the wrong function.

Also applies to: 58-60, 72-74, 89-91

libs/system-admin-pages/src/pages/blob-explorer-locations/index.njk (1)

13-40: Conditional flow should avoid showing empty-state when error is present. Suggest if error … elif tableRows.length > 0 … else ….

-    {% if error %}
+    {% if error %}
       {{ govukErrorSummary({
-        titleText: "There is a problem",
+        titleText: errorSummaryTitle,
         errorList: [
           {
             text: error
           }
         ]
       }) }}
-    {% endif %}
-
-    {% if tableRows.length > 0 %}
+    {% elif tableRows.length > 0 %}
       {{ govukTable({
         head: [
           {
             text: locationsTableHeadingLocation
           },
           {
             text: locationsTableHeadingPublications,
             format: "numeric"
           }
         ],
         rows: tableRows
       }) }}
     {% else %}
-      <p class="govuk-body">No publications found. Publications need to be ingested before they can be viewed here.</p>
+      <p class="govuk-body">{{ noPublicationsMessage }}</p>
     {% endif %}
libs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.ts (1)

20-23: Avoid as any for session to comply with “no any” guideline. Use unknown/a minimal session type instead. As per coding guidelines.

-    mockRequest = {
-      query: { artefactId: "abc-123" },
-      session: {} as any
-    };
+    mockRequest = {
+      query: { artefactId: "abc-123" },
+      session: {} as unknown as Request["session"]
+    };
libs/publication/src/repository/service.test.ts (1)

124-125: Drop as any on fs.readdir mock return values. Keep these as string[] for cleaner typing. As per coding guidelines.

-      vi.mocked(fs.readdir).mockResolvedValue(["test-artefact-id.pdf", "other-file.txt"] as any);
+      vi.mocked(fs.readdir).mockResolvedValue(["test-artefact-id.pdf", "other-file.txt"]);

Also applies to: 133-134, 149-150, 157-158

libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.ts (2)

1-15: Avoid as any for session in tests; use a minimal typed stub instead.

session: {} as any (Line 25) violates the “no any without justification” guideline and can hide real session-shape regressions.

-    mockRequest = {
-      query: { artefactId: "abc-123" },
-      session: {} as any
-    };
+    mockRequest = {
+      query: { artefactId: "abc-123" },
+      session: {} as unknown as Request["session"]
+    };

If you have a local SessionData type augmentation, even better to use that explicitly.

Also applies to: 20-32


34-40: Don’t hardcode GET[1] / POST[1]; select the last handler to reduce brittleness.

This test suite assumes exactly one auth middleware before the handler. If you add another middleware later, tests will silently start targeting the wrong function.

-      const handler = GET[1];
+      const handler = GET[GET.length - 1];
...
-      const handler = POST[1];
+      const handler = POST[POST.length - 1];

Also applies to: 120-126

libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (2)

22-30: Parse query params safely instead of as string casts.

-  const artefactId = req.query.artefactId as string;
+  const artefactId = typeof req.query.artefactId === "string" ? req.query.artefactId : undefined;
...
-  const artefactId = req.query.artefactId as string;
+  const artefactId = typeof req.query.artefactId === "string" ? req.query.artefactId : undefined;

Also applies to: 62-67


11-20: Consider centralising formatDateTime to avoid copy/paste across pages.

libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts (2)

22-30: Same as JSON page: avoid req.query.artefactId as string casts; parse safely.

Also applies to: 60-65


11-20: Consider a shared date formatting helper (duplicate implementation).

libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts (2)

23-31: Parse artefactId safely; avoid as string on req.query.

Also applies to: 60-66


60-85: Optional: enforce resubmission target via session (or validate query matches session) to prevent tampering.

Right now, POST resubmits whatever artefactId is in the URL, even if it doesn’t match req.session.resubmissionArtefactId.

libs/publication/src/repository/queries.test.ts (2)

2-26: Move vi.mock(...) blocks above module imports (or use vi.hoisted) to avoid hoisting/ordering footguns.
Right now ./queries.js is imported before the mocks; this usually works due to Vitest hoisting, but it’s brittle and non-obvious (especially once conditions/variables enter the mock factories).


593-1032: Nice coverage for the new query helpers; add 1–2 “query contract” assertions for raw SQL and list-type/location lookups.
For getLocationsWithPublicationCount, consider also asserting $queryRaw was called once (and optionally that it’s called with a SQL template), to catch accidental regressions where the mapper is tested but the query isn’t invoked. For getArtefactMetadata, you’re already asserting getLocationById(123)—good.

libs/publication/src/repository/queries.ts (3)

1-33: Guidelines mismatch: move export interface ... declarations to the bottom of the module.
Repo guidelines specify “interfaces and types at the bottom”; this file currently puts them at the top.


147-167: Precompute list-type lookup (Map) instead of mockListTypes.find(...) per row.
This is minor now, but it’s an easy win and keeps the function predictable if list types grow.


215-233: Potential perf/overflow concerns in $queryRaw mapping (CAST join + bigint->number).

  • CAST(l.location_id AS VARCHAR) = a.location_id can prevent index usage; consider aligning column types or changing the join to avoid casting on the indexed side.
  • Number(row.publication_count) can overflow past Number.MAX_SAFE_INTEGER (unlikely for counts here, but it’s a correctness edge).
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52d5f46 and dc4a197.

📒 Files selected for processing (44)
  • docs/tickets/VIBE-310/plan.md (1 hunks)
  • docs/tickets/VIBE-310/specification.md (1 hunks)
  • e2e-tests/tests/blob-explorer.spec.ts (1 hunks)
  • e2e-tests/tests/system-admin-dashboard.spec.ts (1 hunks)
  • libs/publication/src/index.ts (1 hunks)
  • libs/publication/src/repository/queries.test.ts (3 hunks)
  • libs/publication/src/repository/queries.ts (2 hunks)
  • libs/publication/src/repository/service.test.ts (1 hunks)
  • libs/publication/src/repository/service.ts (1 hunks)
  • libs/system-admin-pages/src/index.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.njk (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.njk (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.njk (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.test.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.njk (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.njk (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.ts (1 hunks)
  • libs/system-admin-pages/src/pages/system-admin-dashboard/en.ts (1 hunks)
  • libs/system-admin-pages/src/services/service.test.ts (1 hunks)
  • libs/system-admin-pages/src/services/service.ts (1 hunks)
  • libs/system-admin-pages/src/types/session.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans must use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants outside function scope at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
TypeScript strict mode must be enabled. No any type without justification. Use explicit types for all variables and function parameters.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"), even when importing TypeScript files. This is required for ESM with Node.js 'nodenext' module resolution.
Use workspace aliases (@hmcts/*) for imports between packages instead of relative paths.
Only export functions that are intended to be used outside the module. Do not export functions solely for testing purposes.
Only add comments when they provide meaningful explanation of why something is done, not what is done. Code should be self-documenting.
Favor functional style. Don't use classes unless you have shared state.
Data should be immutable by default. Use const and avoid mutations to ensure predictable state.
Functions should have no side effects. Avoid modifying external state or relying on mutable data.

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/pages/system-admin-dashboard/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts
  • libs/system-admin-pages/src/services/service.test.ts
  • e2e-tests/tests/blob-explorer.spec.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts
  • libs/system-admin-pages/src/services/service.ts
  • e2e-tests/tests/system-admin-dashboard.spec.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts
  • libs/system-admin-pages/src/types/session.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.ts
  • libs/publication/src/repository/service.test.ts
  • libs/system-admin-pages/src/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts
  • libs/publication/src/repository/service.ts
  • libs/publication/src/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.ts
  • libs/publication/src/repository/queries.ts
  • libs/publication/src/repository/queries.test.ts
**/src/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/src/pages/**/*.ts: Pages are registered through explicit imports in apps/web/src/app.ts. Routes are created based on file names within the pages/ directory (e.g., my-page.ts becomes /my-page, nested routes via subdirectories).
Page controller files must export GET and/or POST functions that accept Express Request and Response, render using res.render(), and handle form submissions.

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/pages/system-admin-dashboard/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.ts
**/src/pages/**/*.{ts,njk}

📄 CodeRabbit inference engine (CLAUDE.md)

**/src/pages/**/*.{ts,njk}: Every page must support both English and Welsh. Controllers must provide both en and cy objects with page content.
Welsh translations are required for all user-facing text. Do not skip Welsh support.

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/pages/system-admin-dashboard/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

DO NOT use CommonJS. Use import/export, never require()/module.exports. Only ES modules are allowed.

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/pages/system-admin-dashboard/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts
  • libs/system-admin-pages/src/services/service.test.ts
  • e2e-tests/tests/blob-explorer.spec.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts
  • libs/system-admin-pages/src/services/service.ts
  • e2e-tests/tests/system-admin-dashboard.spec.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts
  • libs/system-admin-pages/src/types/session.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.ts
  • libs/publication/src/repository/service.test.ts
  • libs/system-admin-pages/src/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts
  • libs/publication/src/repository/service.ts
  • libs/publication/src/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.ts
  • libs/publication/src/repository/queries.ts
  • libs/publication/src/repository/queries.test.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Do not create generic types.ts files. Colocate types with the appropriate code file where they are used.
Do not create generic files like utils.ts. Be specific with naming (e.g., object-properties.ts, date-formatting.ts).

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/pages/system-admin-dashboard/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts
  • libs/system-admin-pages/src/services/service.test.ts
  • e2e-tests/tests/blob-explorer.spec.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts
  • libs/system-admin-pages/src/services/service.ts
  • e2e-tests/tests/system-admin-dashboard.spec.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts
  • libs/system-admin-pages/src/types/session.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.ts
  • libs/publication/src/repository/service.test.ts
  • libs/system-admin-pages/src/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts
  • libs/publication/src/repository/service.ts
  • libs/publication/src/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.ts
  • libs/publication/src/repository/queries.ts
  • libs/publication/src/repository/queries.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Unit/integration test files must be co-located with source files as *.test.ts and use Vitest with describe, it, and expect.

Files:

  • libs/system-admin-pages/src/services/service.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.ts
  • libs/publication/src/repository/service.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.ts
  • libs/publication/src/repository/queries.test.ts
e2e-tests/**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

e2e-tests/**/*.spec.ts: E2E test files must be in e2e-tests/ directory named *.spec.ts, use Playwright, include complete user journeys with validations, Welsh translations, accessibility checks, and keyboard navigation all within a single test.
WCAG 2.2 AA accessibility compliance is mandatory. Include accessibility testing in E2E tests using Axe-core.

Files:

  • e2e-tests/tests/blob-explorer.spec.ts
  • e2e-tests/tests/system-admin-dashboard.spec.ts
**/src/pages/**/*.njk

📄 CodeRabbit inference engine (CLAUDE.md)

Nunjucks templates must extend layouts/base-templates.njk, use govuk macros for components, include error summaries, and support conditional rendering based on language variables.

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.njk
🧠 Learnings (12)
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/src/pages/**/*.{ts,njk} : Welsh translations are required for all user-facing text. Do not skip Welsh support.

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/src/pages/**/*.{ts,njk} : Every page must support both English and Welsh. Controllers must provide both `en` and `cy` objects with page content.

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts
📚 Learning: 2025-11-20T09:59:16.776Z
Learnt from: junaidiqbalmoj
Repo: hmcts/cath-service PR: 106
File: libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts:84-160
Timestamp: 2025-11-20T09:59:16.776Z
Learning: In the cath-service repository, Welsh localization (lng=cy) is not required for admin screens (system-admin-pages), so locale preservation in admin screen redirects is not necessary.

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/*.test.ts : Unit/integration test files must be co-located with source files as `*.test.ts` and use Vitest with `describe`, `it`, and `expect`.

Applied to files:

  • libs/system-admin-pages/src/services/service.test.ts
  • e2e-tests/tests/blob-explorer.spec.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.test.ts
  • libs/publication/src/repository/service.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.ts
  • libs/publication/src/repository/queries.test.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to e2e-tests/**/*.spec.ts : E2E test files must be in `e2e-tests/` directory named `*.spec.ts`, use Playwright, include complete user journeys with validations, Welsh translations, accessibility checks, and keyboard navigation all within a single test.

Applied to files:

  • e2e-tests/tests/blob-explorer.spec.ts
  • e2e-tests/tests/system-admin-dashboard.spec.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to e2e-tests/**/*.spec.ts : WCAG 2.2 AA accessibility compliance is mandatory. Include accessibility testing in E2E tests using Axe-core.

Applied to files:

  • e2e-tests/tests/blob-explorer.spec.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/src/pages/**/*.ts : Page controller files must export `GET` and/or `POST` functions that accept Express Request and Response, render using `res.render()`, and handle form submissions.

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/src/pages/**/*.ts : Pages are registered through explicit imports in `apps/web/src/app.ts`. Routes are created based on file names within the `pages/` directory (e.g., `my-page.ts` becomes `/my-page`, nested routes via subdirectories).

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
  • libs/system-admin-pages/src/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/libs/*/src/config.ts : Config exports (pageRoutes, apiRoutes, prismaSchemas, assets) must be in a separate `config.ts` file to avoid circular dependencies during Prisma client generation. Apps import config using the `/config` path.

Applied to files:

  • libs/system-admin-pages/src/index.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/*.{ts,tsx} : Only export functions that are intended to be used outside the module. Do not export functions solely for testing purposes.

Applied to files:

  • libs/system-admin-pages/src/index.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/src/pages/**/*.njk : Nunjucks templates must extend `layouts/base-templates.njk`, use govuk macros for components, include error summaries, and support conditional rendering based on language variables.

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-locations/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.njk
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/package.json : All test packages must use `"test": "vitest run"` script. Tests should achieve >80% coverage on business logic.

Applied to files:

  • libs/publication/src/repository/queries.test.ts
🧬 Code graph analysis (20)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts (5)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts (1)
  • cy (1-7)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts (1)
  • cy (1-19)
libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts (1)
  • cy (1-20)
libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts (1)
  • cy (1-8)
libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts (1)
  • cy (1-10)
libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts (5)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts (1)
  • cy (1-7)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts (1)
  • cy (1-19)
libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts (1)
  • cy (1-8)
libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts (1)
  • cy (1-10)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts (1)
  • cy (1-7)
libs/system-admin-pages/src/services/service.test.ts (1)
libs/system-admin-pages/src/services/service.ts (1)
  • sendPublicationNotifications (1-4)
e2e-tests/tests/blob-explorer.spec.ts (1)
e2e-tests/utils/sso-helpers.ts (1)
  • loginWithSSO (10-46)
libs/system-admin-pages/src/pages/blob-explorer-locations/index.ts (4)
libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts (1)
  • cy (1-8)
libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts (1)
  • en (1-8)
libs/publication/src/repository/queries.ts (1)
  • getLocationsWithPublicationCount (215-233)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts (1)
  • GET (88-88)
libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts (6)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts (1)
  • en (1-7)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts (1)
  • en (1-19)
libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts (1)
  • en (1-8)
libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts (1)
  • en (1-10)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts (1)
  • en (1-7)
libs/system-admin-pages/src/pages/system-admin-dashboard/en.ts (1)
  • en (1-45)
libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts (6)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts (1)
  • en (1-7)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts (1)
  • en (1-19)
libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts (1)
  • en (1-20)
libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts (1)
  • en (1-10)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts (1)
  • en (1-7)
libs/system-admin-pages/src/pages/system-admin-dashboard/en.ts (1)
  • en (1-45)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts (6)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts (1)
  • en (1-19)
libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts (1)
  • en (1-20)
libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts (1)
  • en (1-8)
libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts (1)
  • en (1-10)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts (1)
  • en (1-7)
libs/system-admin-pages/src/pages/system-admin-dashboard/en.ts (1)
  • en (1-45)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.ts (1)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts (2)
  • GET (88-88)
  • POST (89-89)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.ts (7)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts (1)
  • cy (1-7)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts (1)
  • en (1-7)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts (1)
  • GET (88-88)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts (1)
  • GET (73-73)
libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (1)
  • GET (75-75)
libs/system-admin-pages/src/pages/blob-explorer-locations/index.ts (1)
  • GET (43-43)
libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts (1)
  • GET (74-74)
libs/publication/src/repository/service.test.ts (2)
libs/publication/src/index.ts (3)
  • getJsonContent (20-20)
  • getRenderedTemplateUrl (20-20)
  • getFlatFileUrl (20-20)
libs/publication/src/repository/service.ts (3)
  • getJsonContent (12-20)
  • getRenderedTemplateUrl (22-35)
  • getFlatFileUrl (37-50)
libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts (5)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts (1)
  • cy (1-7)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts (1)
  • cy (1-19)
libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts (1)
  • cy (1-20)
libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts (1)
  • cy (1-8)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts (1)
  • cy (1-7)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts (4)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts (1)
  • cy (1-7)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts (1)
  • en (1-7)
libs/publication/src/repository/queries.ts (1)
  • getArtefactMetadata (169-196)
libs/system-admin-pages/src/services/service.ts (1)
  • sendPublicationNotifications (1-4)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts (4)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts (1)
  • cy (1-19)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts (1)
  • en (1-19)
libs/publication/src/repository/queries.ts (1)
  • getArtefactMetadata (169-196)
libs/publication/src/repository/service.ts (1)
  • getFlatFileUrl (37-50)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.ts (1)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.ts (1)
  • GET (19-19)
libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (2)
libs/publication/src/repository/queries.ts (1)
  • getArtefactMetadata (169-196)
libs/publication/src/repository/service.ts (2)
  • getJsonContent (12-20)
  • getRenderedTemplateUrl (22-35)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts (3)
libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts (1)
  • en (1-20)
libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts (1)
  • en (1-8)
libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts (1)
  • en (1-10)
libs/publication/src/repository/service.ts (1)
libs/publication/src/repository/queries.ts (1)
  • getArtefactListTypeId (235-246)
libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts (7)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts (1)
  • GET (88-88)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts (1)
  • GET (73-73)
libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (1)
  • GET (75-75)
libs/system-admin-pages/src/pages/blob-explorer-locations/index.ts (1)
  • GET (43-43)
libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts (1)
  • GET (74-74)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.ts (1)
  • GET (19-19)
libs/publication/src/index.ts (1)
  • mockPublications (3-3)
libs/publication/src/repository/queries.test.ts (4)
libs/publication/src/index.ts (5)
  • getArtefactSummariesByLocation (13-13)
  • getArtefactMetadata (12-12)
  • getArtefactType (16-16)
  • getLocationsWithPublicationCount (17-17)
  • getArtefactListTypeId (11-11)
libs/publication/src/repository/queries.ts (5)
  • getArtefactSummariesByLocation (147-167)
  • getArtefactMetadata (169-196)
  • getArtefactType (198-213)
  • getLocationsWithPublicationCount (215-233)
  • getArtefactListTypeId (235-246)
libs/location/src/repository/queries.ts (1)
  • getLocationById (37-65)
libs/location/src/index.ts (1)
  • getLocationById (17-17)
🪛 ast-grep (0.40.0)
e2e-tests/tests/blob-explorer.spec.ts

[warning] 164-164: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(/blob-explorer/publications.*locationId=${publicationData.locationId})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 189-189: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(/blob-explorer/json-file.*artefactId=${publicationData.artefactId})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 233-233: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(/blob-explorer/confirm-resubmission.*artefactId=${publicationData.artefactId})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: E2E Tests

Comment on lines +169 to +196
export async function getArtefactMetadata(artefactId: string): Promise<ArtefactMetadata | null> {
const artefact = await prisma.artefact.findUnique({
where: {
artefactId
}
});

if (!artefact) {
return null;
}

const listType = mockListTypes.find((lt) => lt.id === artefact.listTypeId);
const location = await getLocationById(Number.parseInt(artefact.locationId));

return {
artefactId: artefact.artefactId,
locationId: artefact.locationId,
locationName: location?.name || "Unknown",
publicationType: artefact.isFlatFile ? "Flat File" : "JSON",
listType: listType?.englishFriendlyName || "Unknown",
provenance: PROVENANCE_LABELS[artefact.provenance] || artefact.provenance,
language: artefact.language,
sensitivity: artefact.sensitivity,
contentDate: artefact.contentDate.toISOString(),
displayFrom: artefact.displayFrom.toISOString(),
displayTo: artefact.displayTo.toISOString()
};
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard Number.parseInt(artefact.locationId) to avoid passing NaN into getLocationById.
If artefact.locationId is ever non-numeric, getLocationById(NaN) is a silent failure mode and can mask data issues.

 export async function getArtefactMetadata(artefactId: string): Promise<ArtefactMetadata | null> {
   const artefact = await prisma.artefact.findUnique({
     where: {
       artefactId
     }
   });

   if (!artefact) {
     return null;
   }

   const listType = mockListTypes.find((lt) => lt.id === artefact.listTypeId);
-  const location = await getLocationById(Number.parseInt(artefact.locationId));
+  const locationIdNum = Number.parseInt(artefact.locationId, 10);
+  const location = Number.isFinite(locationIdNum) ? await getLocationById(locationIdNum) : undefined;

   return {
     artefactId: artefact.artefactId,
     locationId: artefact.locationId,
     locationName: location?.name || "Unknown",
     publicationType: artefact.isFlatFile ? "Flat File" : "JSON",
     listType: listType?.englishFriendlyName || "Unknown",
     provenance: PROVENANCE_LABELS[artefact.provenance] || artefact.provenance,
     language: artefact.language,
     sensitivity: artefact.sensitivity,
     contentDate: artefact.contentDate.toISOString(),
     displayFrom: artefact.displayFrom.toISOString(),
     displayTo: artefact.displayTo.toISOString()
   };
 }

Comment on lines +7 to +11
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const MONOREPO_ROOT = path.join(__dirname, "..", "..", "..", "..");
const TEMP_STORAGE_BASE = path.join(MONOREPO_ROOT, "storage", "temp", "uploads");

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

MONOREPO_ROOT/TEMP_STORAGE_BASE derivation is likely brittle in runtime builds—prefer config/env.

If this code runs from a built dist/ path, walking .. four times may point somewhere else. Consider injecting the base directory via env/config (and keep this relative logic only in tests).

🤖 Prompt for AI Agents
In libs/publication/src/repository/service.ts around lines 7 to 11, the
MONOREPO_ROOT/TEMP_STORAGE_BASE derivation by walking up from import.meta.url is
brittle for built runtimes; change the code to read the base storage path from
configuration or an environment variable (e.g.
process.env.PUBLICATION_TEMP_STORAGE_BASE or a config module) and fall back to
the current relative logic only for local/dev/tests, also allow injection of the
base directory into the service (constructor or factory) so production builds
supply an absolute path via env/config and tests can keep the relative
resolution.

Comment on lines +132 to +137
{% if jsonContent %}
{{ govukDetails({
summaryText: jsonFileAccordionTitle,
html: '<pre class="govuk-body" style="white-space: pre-wrap; word-wrap: break-word; max-height: 400px; overflow-y: auto; background-color: #f3f2f1; padding: 15px; border: 1px solid #b1b4b6;">' + jsonContent + '</pre>'
}) }}
{% endif %}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

XSS risk: jsonContent is injected into html without escaping. If JSON contains </&, it will be interpreted as markup inside the <pre>. Escape it (or render via text) before passing to govukDetails.

-      {% if jsonContent %}
-        {{ govukDetails({
-          summaryText: jsonFileAccordionTitle,
-          html: '<pre class="govuk-body" style="white-space: pre-wrap; word-wrap: break-word; max-height: 400px; overflow-y: auto; background-color: #f3f2f1; padding: 15px; border: 1px solid #b1b4b6;">' + jsonContent + '</pre>'
-        }) }}
-      {% endif %}
+      {% if jsonContent %}
+        {% set jsonPreHtml %}
+          <pre class="govuk-body" style="white-space: pre-wrap; word-wrap: break-word; max-height: 400px; overflow-y: auto; background-color: #f3f2f1; padding: 15px; border: 1px solid #b1b4b6;">{{ jsonContent | escape }}</pre>
+        {% endset %}
+        {{ govukDetails({
+          summaryText: jsonFileAccordionTitle,
+          html: jsonPreHtml
+        }) }}
+      {% endif %}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{% if jsonContent %}
{{ govukDetails({
summaryText: jsonFileAccordionTitle,
html: '<pre class="govuk-body" style="white-space: pre-wrap; word-wrap: break-word; max-height: 400px; overflow-y: auto; background-color: #f3f2f1; padding: 15px; border: 1px solid #b1b4b6;">' + jsonContent + '</pre>'
}) }}
{% endif %}
{% if jsonContent %}
{% set jsonPreHtml %}
<pre class="govuk-body" style="white-space: pre-wrap; word-wrap: break-word; max-height: 400px; overflow-y: auto; background-color: #f3f2f1; padding: 15px; border: 1px solid #b1b4b6;">{{ jsonContent | escape }}</pre>
{% endset %}
{{ govukDetails({
summaryText: jsonFileAccordionTitle,
html: jsonPreHtml
}) }}
{% endif %}
🤖 Prompt for AI Agents
In libs/system-admin-pages/src/pages/blob-explorer-json-file/index.njk around
lines 132 to 137, jsonContent is injected directly into the govukDetails html
string causing an XSS risk; escape the JSON before insertion (e.g. use Nunjucks'
escape filter jsonContent | escape('html') or equivalent) or pass it through a
safe text-rendering param instead of raw HTML concatenation so any '<', '&'
characters are encoded and cannot be interpreted as markup.

@@ -0,0 +1,43 @@
{% extends "layouts/base-template.njk" %}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Base layout filename doesn’t match guideline (layouts/base-templates.njk). Please confirm and align to the required base template name. As per coding guidelines/learnings.

🤖 Prompt for AI Agents
In libs/system-admin-pages/src/pages/blob-explorer-locations/index.njk around
line 1, the template currently extends "layouts/base-template.njk" but the
project guideline requires the base layout filename
"layouts/base-templates.njk"; update the extends line to reference the correct
filename, ensure the target file exists at layouts/base-templates.njk, and run a
quick search/replace across the repo to align any other templates to the
required base template name.

Comment on lines +17 to +25
const tableRows = locations.map((location) => [
{
html: `<a href="/blob-explorer-publications?locationId=${location.locationId}" class="govuk-link">${location.locationName}</a>`
},
{
text: location.publicationCount.toString(),
format: "numeric"
}
]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: XSS risk from interpolating locationName into raw HTML.
location.locationName comes from the database and is injected into an html: string without escaping. Also, locationId should be URL-encoded. Prefer passing href + text to the template (so Nunjucks escapes), or escape/encode here.

-    const tableRows = locations.map((location) => [
-      {
-        html: `<a href="/blob-explorer-publications?locationId=${location.locationId}" class="govuk-link">${location.locationName}</a>`
-      },
+    const tableRows = locations.map((location) => [
+      {
+        // Prefer rendering link in Nunjucks so text is escaped by default.
+        // If you must build HTML here, at least URL-encode and escape text.
+        html: `<a href="/blob-explorer-publications?locationId=${encodeURIComponent(location.locationId)}" class="govuk-link">${location.locationName}</a>`
+      },
       {
         text: location.publicationCount.toString(),
         format: "numeric"
       }
     ]);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In libs/system-admin-pages/src/pages/blob-explorer-locations/index.ts around
lines 17 to 25, the code injects location.locationName directly into an html
string and interpolates locationId into the URL, creating an XSS risk; change
the row data to avoid raw HTML by supplying a safe href and text to the template
(i.e., use href:
`/blob-explorer-publications?locationId=${encodeURIComponent(location.locationId)}`
and text: location.locationName so the templating engine escapes it), or if you
must build a string here ensure you URL-encode the locationId with
encodeURIComponent and escape the locationName before inserting it into html.

Comment on lines +26 to +28
if (!locationId) {
return res.redirect("/blob-explorer-locations");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Redirect path inconsistency.

The redirect goes to /blob-explorer-locations, but the E2E test (blob-explorer.spec.ts line 299) and other navigation expects /blob-explorer as the locations page URL. This mismatch will cause the redirect to fail.

   if (!locationId) {
-    return res.redirect("/blob-explorer-locations");
+    return res.redirect("/blob-explorer");
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!locationId) {
return res.redirect("/blob-explorer-locations");
}
if (!locationId) {
return res.redirect("/blob-explorer");
}
🤖 Prompt for AI Agents
In libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts around
lines 26 to 28, the redirect for missing locationId points to
/blob-explorer-locations while tests and other navigation expect /blob-explorer;
change the redirect path to /blob-explorer so it matches the canonical locations
page route used by blob-explorer.spec.ts and other callers.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 12, 2025

🎭 Playwright E2E Test Results

181 tests   181 ✅  15m 38s ⏱️
 24 suites    0 💤
  1 files      0 ❌

Results for commit def4f2c.

♻️ This comment has been updated with latest results.

@@ -0,0 +1,86 @@
import fs from "node:fs/promises";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a placeholder for now. Once VIBE-215 has been merged, this should be replaced with flat file display functionality

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

♻️ Duplicate comments (3)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk (1)

1-1: Fix base layout extends path (base-templatebase-templates) or this may fail at render-time.

Guidelines require layouts/base-templates.njk, and the current extends is likely a template-not-found.

-{% extends "layouts/base-template.njk" %}
+{% extends "layouts/base-templates.njk" %}

Based on learnings, templates must extend layouts/base-templates.njk.

libs/publication/src/repository/service.ts (1)

7-10: MONOREPO_ROOT / TEMP_STORAGE_BASE derivation is brittle for built/runtime layouts—prefer config/env injection.
This was already flagged in earlier review comments; still applies here (walking .. from import.meta.url can point to the wrong place in dist/ or different deploy layouts).

libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts (1)

37-39: Verify canonical redirect for “locations” page (/blob-explorer vs /blob-explorer-locations).
This was flagged previously; the redirect is still /blob-explorer-locations, so please confirm it matches the actual registered route and the Playwright journey.

🧹 Nitpick comments (12)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk (2)

13-20: Harden error rendering (ensure plain text / escaped).

If error can ever contain markup from upstream, explicitly escape it before passing into govukErrorSummary.

-            text: error
+            text: (error | escape)

73-96: Defensive fallback for missing/invalid date fields to avoid “Invalid date”.

If any of metadata.contentDate, metadata.displayFrom, metadata.displayTo can be null/undefined, consider a fallback:

-              text: formatDateTime(metadata.contentDate)
+              text: metadata.contentDate ? formatDateTime(metadata.contentDate) : "-"

(same pattern for displayFrom / displayTo)

libs/publication/src/repository/service.ts (3)

12-37: Good containment check; consider simplifying with path.relative and remove the “resolvedPath === resolvedBase” allowance.
getSafeFilePath() is already doing the right kind of thing (resolve + ensure within base). Minor hardening/readability: path.relative(resolvedBase, resolvedPath) tends to be less error-prone than startsWith, and you shouldn’t need to allow equality with the base dir when you expect a file path.

 function getSafeFilePath(artefactId: string, filename: string): string | null {
@@
-  // Resolve both paths to absolute and normalize them
-  const resolvedPath = path.resolve(filePath);
-  const resolvedBase = path.resolve(TEMP_STORAGE_BASE);
-
-  // Ensure the resolved path is within the base directory
-  if (!resolvedPath.startsWith(resolvedBase + path.sep) && resolvedPath !== resolvedBase) {
-    return null;
-  }
+  const resolvedPath = path.resolve(filePath);
+  const resolvedBase = path.resolve(TEMP_STORAGE_BASE);
+  const relative = path.relative(resolvedBase, resolvedPath);
+  if (relative.startsWith("..") || path.isAbsolute(relative)) return null;
 
   return resolvedPath;
 }

39-51: getJsonContent return type is likely wrong (JSON.parse can return arrays/primitives); prefer unknown | null or validate to a known schema.
Right now it promises object | null, but JSON.parse() is not guaranteed to be an object. If callers expect specific shape, consider runtime validation (e.g., zod/ajv) and return a typed value.

-export async function getJsonContent(artefactId: string): Promise<object | null> {
+export async function getJsonContent(artefactId: string): Promise<unknown | null> {

68-91: getFlatFileUrl validation looks sensible; consider making the “..” filename rule match your actual threat model.
Blocking any .. anywhere in the filename is conservative (and may reject legitimate names like id..pdf). If that’s intended, fine; otherwise consider checking for path separators only and relying on “base name only” constraints from readdir.

e2e-tests/tests/blob-explorer.spec.ts (4)

132-136: Fail fast if required SSO env vars are missing (avoid opaque ! crashes)
Non-null assertions on process.env.*! will fail with less actionable errors.

 test.beforeEach(async ({ page }) => {
+  const email = process.env.SSO_TEST_SYSTEM_ADMIN_EMAIL;
+  const password = process.env.SSO_TEST_SYSTEM_ADMIN_PASSWORD;
+  if (!email || !password) {
+    throw new Error("Missing SSO_TEST_SYSTEM_ADMIN_EMAIL / SSO_TEST_SYSTEM_ADMIN_PASSWORD");
+  }
   await page.goto("/system-admin-dashboard");
-  await loginWithSSO(page, process.env.SSO_TEST_SYSTEM_ADMIN_EMAIL!, process.env.SSO_TEST_SYSTEM_ADMIN_PASSWORD!);
+  await loginWithSSO(page, email, password);
   await page.waitForURL("/system-admin-dashboard");
 });

166-170: Axe: avoid blanket disabling rules unless there’s a tracked reason
Disabling "region" everywhere can hide real regressions; if it’s a known acceptable violation, add a short “why” comment and scope it to only the affected pages/components.

Also applies to: 195-200, 228-233, 268-273, 304-309


184-186: Avoid interpolated new RegExp(...) in URL assertions (simpler + removes ReDoS lint noise)
In tests, this is low-risk, but you can avoid the pattern entirely by asserting via URL parsing.

-      await expect(page).toHaveURL(new RegExp(`/blob-explorer-publications.*locationId=${publicationData.locationId}`));
+      await expect(page).toHaveURL(/\/blob-explorer-publications/);
+      expect(new URL(page.url()).searchParams.get("locationId")).toBe(String(publicationData.locationId));

-      await expect(page).toHaveURL(new RegExp(`/blob-explorer-json-file.*artefactId=${publicationData.artefactId}`));
+      await expect(page).toHaveURL(/\/blob-explorer-json-file/);
+      expect(new URL(page.url()).searchParams.get("artefactId")).toBe(publicationData.artefactId);

-      await expect(page).toHaveURL(new RegExp(`/blob-explorer-confirm-resubmission.*artefactId=${publicationData.artefactId}`));
+      await expect(page).toHaveURL(/\/blob-explorer-confirm-resubmission/);
+      expect(new URL(page.url()).searchParams.get("artefactId")).toBe(publicationData.artefactId);

Also applies to: 209-211, 253-255


172-174: Keyboard navigation: assert focus target, not just Tab presses
Right now it “does something” but doesn’t prove keyboard navigability. Consider asserting toBeFocused() on the expected link/button after tabbing.

Also applies to: 278-281

libs/system-admin-pages/src/routes/files/[filename].ts (1)

77-83: Avoid loading entire file into memory; stream it instead.
fs.readFile() can spike memory for large uploads and slows down the event loop.

-import fs from "node:fs/promises";
+import fs from "node:fs/promises";
+import { createReadStream } from "node:fs";

   // Stream the file
-  const fileContent = await fs.readFile(filePath);
-  res.send(fileContent);
+  createReadStream(filePath).pipe(res);
libs/system-admin-pages/src/routes/files/[filename].test.ts (1)

13-13: Test brittleness: indexing GET[1] couples to middleware order.
Consider invoking the last handler (GET.at(-1)) so adding middleware doesn’t break tests.

-  const handler = GET[1];
+  const handler = GET.at(-1)!;

Also applies to: 37-39

apps/web/src/app.test.ts (1)

134-146: Brittle assertion: prefer verifying the specific router registration over call counts.
Call counts will churn as routes grow; asserting the presence/order of systemAdminApiRoutes is more stable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 148a5a7 and 2016c5c.

📒 Files selected for processing (19)
  • apps/web/src/app.test.ts (1 hunks)
  • apps/web/src/app.ts (2 hunks)
  • e2e-tests/tests/blob-explorer.spec.ts (1 hunks)
  • libs/publication/src/repository/service.test.ts (1 hunks)
  • libs/publication/src/repository/service.ts (1 hunks)
  • libs/system-admin-pages/src/config.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.njk (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.njk (1 hunks)
  • libs/system-admin-pages/src/routes/files/[filename].test.ts (1 hunks)
  • libs/system-admin-pages/src/routes/files/[filename].ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.njk
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans must use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants outside function scope at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
TypeScript strict mode must be enabled. No any type without justification. Use explicit types for all variables and function parameters.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"), even when importing TypeScript files. This is required for ESM with Node.js 'nodenext' module resolution.
Use workspace aliases (@hmcts/*) for imports between packages instead of relative paths.
Only export functions that are intended to be used outside the module. Do not export functions solely for testing purposes.
Only add comments when they provide meaningful explanation of why something is done, not what is done. Code should be self-documenting.
Favor functional style. Don't use classes unless you have shared state.
Data should be immutable by default. Use const and avoid mutations to ensure predictable state.
Functions should have no side effects. Avoid modifying external state or relying on mutable data.

Files:

  • libs/system-admin-pages/src/config.ts
  • libs/system-admin-pages/src/routes/files/[filename].test.ts
  • apps/web/src/app.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/routes/files/[filename].ts
  • libs/publication/src/repository/service.test.ts
  • apps/web/src/app.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
  • libs/publication/src/repository/service.ts
  • e2e-tests/tests/blob-explorer.spec.ts
**/libs/*/src/config.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Config exports (pageRoutes, apiRoutes, prismaSchemas, assets) must be in a separate config.ts file to avoid circular dependencies during Prisma client generation. Apps import config using the /config path.

Files:

  • libs/system-admin-pages/src/config.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

DO NOT use CommonJS. Use import/export, never require()/module.exports. Only ES modules are allowed.

Files:

  • libs/system-admin-pages/src/config.ts
  • libs/system-admin-pages/src/routes/files/[filename].test.ts
  • apps/web/src/app.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/routes/files/[filename].ts
  • libs/publication/src/repository/service.test.ts
  • apps/web/src/app.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
  • libs/publication/src/repository/service.ts
  • e2e-tests/tests/blob-explorer.spec.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Do not create generic types.ts files. Colocate types with the appropriate code file where they are used.
Do not create generic files like utils.ts. Be specific with naming (e.g., object-properties.ts, date-formatting.ts).

Files:

  • libs/system-admin-pages/src/config.ts
  • libs/system-admin-pages/src/routes/files/[filename].test.ts
  • apps/web/src/app.test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/routes/files/[filename].ts
  • libs/publication/src/repository/service.test.ts
  • apps/web/src/app.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
  • libs/publication/src/repository/service.ts
  • e2e-tests/tests/blob-explorer.spec.ts
**/routes/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

API endpoints must use plural for collections (/api/cases, /api/users), singular for specific resources (/api/case/:id), and singular for creation (POST /api/case).

Files:

  • libs/system-admin-pages/src/routes/files/[filename].test.ts
  • libs/system-admin-pages/src/routes/files/[filename].ts
**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Unit/integration test files must be co-located with source files as *.test.ts and use Vitest with describe, it, and expect.

Files:

  • libs/system-admin-pages/src/routes/files/[filename].test.ts
  • apps/web/src/app.test.ts
  • libs/publication/src/repository/service.test.ts
**/src/routes/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Validate all user inputs on endpoints. Use parameterized database queries (Prisma). Never include sensitive data in logs.

Files:

  • libs/system-admin-pages/src/routes/files/[filename].test.ts
  • libs/system-admin-pages/src/routes/files/[filename].ts
**/src/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/src/pages/**/*.ts: Pages are registered through explicit imports in apps/web/src/app.ts. Routes are created based on file names within the pages/ directory (e.g., my-page.ts becomes /my-page, nested routes via subdirectories).
Page controller files must export GET and/or POST functions that accept Express Request and Response, render using res.render(), and handle form submissions.

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
**/src/pages/**/*.{ts,njk}

📄 CodeRabbit inference engine (CLAUDE.md)

**/src/pages/**/*.{ts,njk}: Every page must support both English and Welsh. Controllers must provide both en and cy objects with page content.
Welsh translations are required for all user-facing text. Do not skip Welsh support.

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk
**/src/pages/**/*.njk

📄 CodeRabbit inference engine (CLAUDE.md)

Nunjucks templates must extend layouts/base-templates.njk, use govuk macros for components, include error summaries, and support conditional rendering based on language variables.

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk
e2e-tests/**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

e2e-tests/**/*.spec.ts: E2E test files must be in e2e-tests/ directory named *.spec.ts, use Playwright, include complete user journeys with validations, Welsh translations, accessibility checks, and keyboard navigation all within a single test.
WCAG 2.2 AA accessibility compliance is mandatory. Include accessibility testing in E2E tests using Axe-core.

Files:

  • e2e-tests/tests/blob-explorer.spec.ts
🧠 Learnings (11)
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/libs/*/src/config.ts : Config exports (pageRoutes, apiRoutes, prismaSchemas, assets) must be in a separate `config.ts` file to avoid circular dependencies during Prisma client generation. Apps import config using the `/config` path.

Applied to files:

  • libs/system-admin-pages/src/config.ts
  • libs/system-admin-pages/src/routes/files/[filename].ts
  • apps/web/src/app.ts
  • libs/publication/src/repository/service.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/src/pages/**/*.ts : Pages are registered through explicit imports in `apps/web/src/app.ts`. Routes are created based on file names within the `pages/` directory (e.g., `my-page.ts` becomes `/my-page`, nested routes via subdirectories).

Applied to files:

  • libs/system-admin-pages/src/config.ts
  • apps/web/src/app.test.ts
  • libs/system-admin-pages/src/routes/files/[filename].ts
  • apps/web/src/app.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to e2e-tests/**/*.spec.ts : E2E test files must be in `e2e-tests/` directory named `*.spec.ts`, use Playwright, include complete user journeys with validations, Welsh translations, accessibility checks, and keyboard navigation all within a single test.

Applied to files:

  • libs/system-admin-pages/src/routes/files/[filename].test.ts
  • apps/web/src/app.test.ts
  • e2e-tests/tests/blob-explorer.spec.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/*.test.ts : Unit/integration test files must be co-located with source files as `*.test.ts` and use Vitest with `describe`, `it`, and `expect`.

Applied to files:

  • libs/system-admin-pages/src/routes/files/[filename].test.ts
  • libs/publication/src/repository/service.test.ts
  • e2e-tests/tests/blob-explorer.spec.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/src/pages/**/*.ts : Page controller files must export `GET` and/or `POST` functions that accept Express Request and Response, render using `res.render()`, and handle form submissions.

Applied to files:

  • libs/system-admin-pages/src/routes/files/[filename].test.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/routes/files/[filename].ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/src/routes/**/*.ts : Validate all user inputs on endpoints. Use parameterized database queries (Prisma). Never include sensitive data in logs.

Applied to files:

  • libs/system-admin-pages/src/routes/files/[filename].ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/*.{ts,tsx} : Use workspace aliases (`hmcts/*`) for imports between packages instead of relative paths.

Applied to files:

  • apps/web/src/app.ts
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/src/pages/**/*.njk : Nunjucks templates must extend `layouts/base-templates.njk`, use govuk macros for components, include error summaries, and support conditional rendering based on language variables.

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk
📚 Learning: 2025-11-27T14:18:22.932Z
Learnt from: junaidiqbalmoj
Repo: hmcts/cath-service PR: 137
File: e2e-tests/tests/create-media-account.spec.ts:51-64
Timestamp: 2025-11-27T14:18:22.932Z
Learning: For the create-media-account form in libs/public-pages, the English email validation error message (errorEmailInvalid) should be: "There is a problem - Enter a valid email address, e.g. nameexample.com" to match the Welsh translation and clearly indicate the format requirement rather than suggesting the field is empty.

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to **/src/pages/**/*.{ts,njk} : Welsh translations are required for all user-facing text. Do not skip Welsh support.

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk
📚 Learning: 2025-12-03T13:55:34.702Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.702Z
Learning: Applies to e2e-tests/**/*.spec.ts : WCAG 2.2 AA accessibility compliance is mandatory. Include accessibility testing in E2E tests using Axe-core.

Applied to files:

  • e2e-tests/tests/blob-explorer.spec.ts
🧬 Code graph analysis (5)
libs/system-admin-pages/src/routes/files/[filename].test.ts (3)
libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (1)
  • GET (86-86)
libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts (1)
  • GET (87-87)
libs/system-admin-pages/src/routes/files/[filename].ts (1)
  • GET (86-86)
libs/system-admin-pages/src/routes/files/[filename].ts (2)
libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (1)
  • GET (86-86)
libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts (1)
  • GET (87-87)
libs/publication/src/repository/service.test.ts (1)
libs/publication/src/repository/service.ts (3)
  • getJsonContent (39-51)
  • getRenderedTemplateUrl (53-66)
  • getFlatFileUrl (68-91)
libs/publication/src/repository/service.ts (2)
libs/publication/src/index.ts (5)
  • getJsonContent (20-20)
  • getRenderedTemplateUrl (20-20)
  • getArtefactListTypeId (11-11)
  • mockListTypes (1-1)
  • getFlatFileUrl (20-20)
libs/publication/src/repository/queries.ts (1)
  • getArtefactListTypeId (235-246)
e2e-tests/tests/blob-explorer.spec.ts (1)
e2e-tests/utils/sso-helpers.ts (1)
  • loginWithSSO (10-46)
🪛 ast-grep (0.40.0)
e2e-tests/tests/blob-explorer.spec.ts

[warning] 184-184: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(/blob-explorer-publications.*locationId=${publicationData.locationId})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 209-209: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(/blob-explorer-json-file.*artefactId=${publicationData.artefactId})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 253-253: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(/blob-explorer-confirm-resubmission.*artefactId=${publicationData.artefactId})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (6)
libs/publication/src/repository/service.ts (1)

53-66: getRenderedTemplateUrl looks good; encoding is correctly applied.
Good: encodeURIComponent(artefactId) prevents query-string breakage/injection.

libs/publication/src/repository/service.test.ts (1)

1-43: Test/mocking setup is clean and consistent with Vitest.
Mocks are scoped and vi.clearAllMocks() is applied per test.

e2e-tests/tests/blob-explorer.spec.ts (1)

92-129: Cleanup now deletes subscription + user (good) — consider making cleanup failures visible in Playwright output
Nice improvement vs the earlier orphaning risk. Consider using testInfo.attach(...) / testInfo.annotations instead of console.log, so cleanup issues are easier to spot in CI artifacts.

libs/system-admin-pages/src/config.ts (1)

7-10: Good addition of apiRoutes to config (keeps routing config centralized).
This matches the repo pattern of exporting route roots via libs/*/src/config.ts. (Based on retrieved learnings.)

libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts (1)

52-55: Good: artefact link is URL-encoded and link text is HTML-escaped.
This mitigates the earlier XSS concern while keeping the GOV.UK table link rendering.

apps/web/src/app.ts (1)

101-106: System-admin API routes are already role-protected; namespace clarity is optional.

The /files/:filename route is protected by requireRole([USER_ROLES.SYSTEM_ADMIN]) and currently has no collisions with other routes in the codebase. While the path is generic, the role-based access control mitigates collision risk. That said, mounting under a clearer prefix like /system-admin/files/:filename would improve API organization—this is supported by the createSimpleRouter prefix configuration but is not strictly necessary.

Comment on lines 17 to 90
async function createTestPublication(): Promise<TestPublicationData> {
// Get first location for test
const location = await prisma.location.findFirst();
if (!location) {
throw new Error("No location found in database");
}

// Create test artefact (JSON publication) - let Prisma generate UUID
const artefact = await prisma.artefact.create({
data: {
locationId: location.locationId.toString(),
provenance: "MANUAL_UPLOAD",
displayFrom: new Date("2024-01-01"),
displayTo: new Date("2024-12-31"),
language: "ENGLISH",
listTypeId: 1, // Family Daily Cause List
sensitivity: "PUBLIC",
contentDate: new Date("2024-06-01"),
isFlatFile: false,
},
});

const artefactId = artefact.artefactId;

// Create test JSON file in storage
const storageDir = path.join(process.cwd(), "storage", "temp", "uploads");
await fs.mkdir(storageDir, { recursive: true });

const testJsonContent = {
document: {
publicationDate: "2024-06-01",
locationName: location.name,
language: "ENGLISH",
listType: "CIVIL_DAILY_CAUSE_LIST",
courtLists: [
{
courtHouse: { courtHouseName: location.name },
courtListings: [
{ case: { caseName: "Test Case 123" } },
],
},
],
},
};

await fs.writeFile(
path.join(storageDir, `${artefactId}.json`),
JSON.stringify(testJsonContent, null, 2)
);

// Create a test user and subscription for resubmission test
const testUser = await prisma.user.create({
data: {
email: "[email protected]",
userProvenance: "SSO",
userProvenanceId: `test-${Date.now()}`,
role: "MEDIA",
},
});

await prisma.subscription.create({
data: {
userId: testUser.userId,
locationId: location.locationId,
},
});

return {
artefactId,
locationId: location.locationId,
locationName: location.name,
userId: testUser.userId,
};
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix time-bound test data (2024 dates) + make test user email unique to avoid flaky/failing runs
As of December 12, 2025, displayTo: new Date("2024-12-31") is already expired, so the artefact may be filtered out of the Blob Explorer UI. Also, the hard-coded "[email protected]" is prone to unique-constraint collisions and parallel-run failures.

 async function createTestPublication(): Promise<TestPublicationData> {
+  const now = new Date();
+  const displayFrom = new Date(now);
+  displayFrom.setDate(displayFrom.getDate() - 1);
+  const displayTo = new Date(now);
+  displayTo.setFullYear(displayTo.getFullYear() + 1);
+
   // Get first location for test
   const location = await prisma.location.findFirst();
   if (!location) {
     throw new Error("No location found in database");
   }

   // Create test artefact (JSON publication) - let Prisma generate UUID
   const artefact = await prisma.artefact.create({
     data: {
       locationId: location.locationId.toString(),
       provenance: "MANUAL_UPLOAD",
-      displayFrom: new Date("2024-01-01"),
-      displayTo: new Date("2024-12-31"),
+      displayFrom,
+      displayTo,
       language: "ENGLISH",
       listTypeId: 1, // Family Daily Cause List
       sensitivity: "PUBLIC",
-      contentDate: new Date("2024-06-01"),
+      contentDate: now,
       isFlatFile: false,
     },
   });

   const artefactId = artefact.artefactId;

   // Create test JSON file in storage
   const storageDir = path.join(process.cwd(), "storage", "temp", "uploads");
   await fs.mkdir(storageDir, { recursive: true });

   const testJsonContent = {
     document: {
-      publicationDate: "2024-06-01",
+      publicationDate: now.toISOString().slice(0, 10),
       locationName: location.name,
       language: "ENGLISH",
       listType: "CIVIL_DAILY_CAUSE_LIST",
       courtLists: [
         {
           courtHouse: { courtHouseName: location.name },
           courtListings: [
             { case: { caseName: "Test Case 123" } },
           ],
         },
       ],
     },
   };

   await fs.writeFile(
     path.join(storageDir, `${artefactId}.json`),
     JSON.stringify(testJsonContent, null, 2)
   );

   // Create a test user and subscription for resubmission test
   const testUser = await prisma.user.create({
     data: {
-      email: "[email protected]",
+      email: `test-subscriber+${artefactId}@example.com`,
       userProvenance: "SSO",
       userProvenanceId: `test-${Date.now()}`,
       role: "MEDIA",
     },
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function createTestPublication(): Promise<TestPublicationData> {
// Get first location for test
const location = await prisma.location.findFirst();
if (!location) {
throw new Error("No location found in database");
}
// Create test artefact (JSON publication) - let Prisma generate UUID
const artefact = await prisma.artefact.create({
data: {
locationId: location.locationId.toString(),
provenance: "MANUAL_UPLOAD",
displayFrom: new Date("2024-01-01"),
displayTo: new Date("2024-12-31"),
language: "ENGLISH",
listTypeId: 1, // Family Daily Cause List
sensitivity: "PUBLIC",
contentDate: new Date("2024-06-01"),
isFlatFile: false,
},
});
const artefactId = artefact.artefactId;
// Create test JSON file in storage
const storageDir = path.join(process.cwd(), "storage", "temp", "uploads");
await fs.mkdir(storageDir, { recursive: true });
const testJsonContent = {
document: {
publicationDate: "2024-06-01",
locationName: location.name,
language: "ENGLISH",
listType: "CIVIL_DAILY_CAUSE_LIST",
courtLists: [
{
courtHouse: { courtHouseName: location.name },
courtListings: [
{ case: { caseName: "Test Case 123" } },
],
},
],
},
};
await fs.writeFile(
path.join(storageDir, `${artefactId}.json`),
JSON.stringify(testJsonContent, null, 2)
);
// Create a test user and subscription for resubmission test
const testUser = await prisma.user.create({
data: {
email: "[email protected]",
userProvenance: "SSO",
userProvenanceId: `test-${Date.now()}`,
role: "MEDIA",
},
});
await prisma.subscription.create({
data: {
userId: testUser.userId,
locationId: location.locationId,
},
});
return {
artefactId,
locationId: location.locationId,
locationName: location.name,
userId: testUser.userId,
};
}
async function createTestPublication(): Promise<TestPublicationData> {
const now = new Date();
const displayFrom = new Date(now);
displayFrom.setDate(displayFrom.getDate() - 1);
const displayTo = new Date(now);
displayTo.setFullYear(displayTo.getFullYear() + 1);
// Get first location for test
const location = await prisma.location.findFirst();
if (!location) {
throw new Error("No location found in database");
}
// Create test artefact (JSON publication) - let Prisma generate UUID
const artefact = await prisma.artefact.create({
data: {
locationId: location.locationId.toString(),
provenance: "MANUAL_UPLOAD",
displayFrom,
displayTo,
language: "ENGLISH",
listTypeId: 1, // Family Daily Cause List
sensitivity: "PUBLIC",
contentDate: now,
isFlatFile: false,
},
});
const artefactId = artefact.artefactId;
// Create test JSON file in storage
const storageDir = path.join(process.cwd(), "storage", "temp", "uploads");
await fs.mkdir(storageDir, { recursive: true });
const testJsonContent = {
document: {
publicationDate: now.toISOString().slice(0, 10),
locationName: location.name,
language: "ENGLISH",
listType: "CIVIL_DAILY_CAUSE_LIST",
courtLists: [
{
courtHouse: { courtHouseName: location.name },
courtListings: [
{ case: { caseName: "Test Case 123" } },
],
},
],
},
};
await fs.writeFile(
path.join(storageDir, `${artefactId}.json`),
JSON.stringify(testJsonContent, null, 2)
);
// Create a test user and subscription for resubmission test
const testUser = await prisma.user.create({
data: {
email: `test-subscriber+${artefactId}@example.com`,
userProvenance: "SSO",
userProvenanceId: `test-${Date.now()}`,
role: "MEDIA",
},
});
await prisma.subscription.create({
data: {
userId: testUser.userId,
locationId: location.locationId,
},
});
return {
artefactId,
locationId: location.locationId,
locationName: location.name,
userId: testUser.userId,
};
}
🤖 Prompt for AI Agents
In e2e-tests/tests/blob-explorer.spec.ts around lines 17 to 90, the test creates
time-bound artefact dates fixed to 2024 (displayFrom/displayTo/contentDate) and
a hard-coded test user email which leads to expired artefacts and
unique-constraint collisions in current/parallel runs; update the artefact dates
to be dynamic (e.g., set displayFrom to now or today minus a small delta,
displayTo to now plus a year, and contentDate to a recent date) so the artefact
remains active, and make the test user email unique by appending a timestamp or
UUID (e.g., `test-subscriber+${Date.now()}@example.com`) to avoid collisions in
repeated/parallel test runs.

Comment on lines +310 to +313
// STEP 13: Test POST/Redirect/GET pattern - browser back should not re-submit
await page.goBack();
await expect(page).toHaveURL("/blob-explorer-resubmission-success");
// Should still be on success page, not trigger another submission
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

page.goBack() expectation may be brittle; verify intended PRG behavior
Depending on history entries, goBack() may legitimately return to the confirm page. If the goal is “back doesn’t re-POST”, consider asserting no additional request (route spy) or that UI shows a safe state rather than asserting the URL is unchanged.

🤖 Prompt for AI Agents
In e2e-tests/tests/blob-explorer.spec.ts around lines 310 to 313, the test uses
page.goBack() and asserts the URL stays on
"/blob-explorer-resubmission-success", which is brittle because browser history
may validly return to the confirm page; replace the brittle URL assertion with a
robust check that POST is not re-submitted: add a network/route spy or request
counter before triggering the POST, then after page.goBack() assert the route
spy saw zero additional POST requests (or assert a UI element/state that only
appears when no resubmission occurred), ensuring the test verifies "back does
not re-POST" rather than a specific URL.

Comment on lines 258 to 266
it("should reject matched filenames with path traversal", async () => {
vi.mocked(fs.readdir).mockResolvedValue(["../../../malicious.pdf", "test-artefact-id.pdf"] as any);

// Use valid artefactId but readdir returns malicious filename
const result = await getFlatFileUrl("../../../malicious");

expect(result).toBeNull();
});

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test case is internally inconsistent; it never exercises “malicious filename matched” because artefactId is invalid.
getFlatFileUrl("../../../malicious") fails validation early, so the test doesn’t cover the “matched filename is malicious” branch it claims to.

   it("should reject matched filenames with path traversal", async () => {
-    vi.mocked(fs.readdir).mockResolvedValue(["../../../malicious.pdf", "test-artefact-id.pdf"] as any);
-
-    // Use valid artefactId but readdir returns malicious filename
-    const result = await getFlatFileUrl("../../../malicious");
+    // Use a valid artefactId but have readdir return a malicious *matched* filename
+    vi.mocked(fs.readdir).mockResolvedValue(["test-artefact-id..pdf"] as unknown as string[]);
+    const result = await getFlatFileUrl("test-artefact-id");
 
     expect(result).toBeNull();
   });

(If you apply the earlier “remove as any” change, you can likely also drop the unknown as string[] cast here and keep it as a plain array—depending on your Vitest/TS typings.)

🤖 Prompt for AI Agents
In libs/publication/src/repository/service.test.ts around lines 258 to 266, the
test claims to exercise a “malicious filename matched” branch but passes an
invalid artefactId ("../../../malicious") so validation fails early; change the
call to use a valid artefactId that matches the mocked filename (for example
"malicious"), update the fs.readdir mock to be a properly typed string[] (remove
the "as any" and any "unknown as string[]" casts if present), and keep the
expectation that the result is null so the test actually exercises the
matched-malicious-filename branch.

Comment on lines +12 to +21
{% if error %}
{{ govukErrorSummary({
titleText: errorSummaryTitle,
errorList: [
{
text: error
}
]
}) }}
{% else %}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Error state leaves the user with no recovery path (no confirm action, no cancel/back).

When error is present, the template hides the metadata table, the POST button, and the cancel link—so users can’t retry or navigate away.

Consider always rendering the metadata + form + cancel link, and show the error summary above them (or at least keep the cancel/back link in the error branch).

Also applies to: 100-111

🤖 Prompt for AI Agents
In
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njk
around lines 12-21 (and similarly for 100-111), the current template hides the
metadata table, POST button and cancel link when `error` is present, leaving
users no recovery path; update the template so the error summary is rendered
above the form but the metadata table, the form submit button (POST) and the
cancel/back link are always rendered regardless of `error` (or at minimum ensure
the cancel/back link is present in the `if error` branch), so users can retry or
navigate away while seeing the error message.

Comment on lines +44 to +67
const tableRows = await Promise.all(
publications.map(async (pub) => {
const type = await getArtefactType(pub.artefactId);
const path = type === "flat-file" ? "flat-file" : "json-file";
const encodedArtefactId = encodeURIComponent(pub.artefactId);
const link = `/blob-explorer-${path}?artefactId=${encodedArtefactId}`;
const escapedArtefactId = escapeHtml(pub.artefactId);

return [
{
html: `<a href="${link}" class="govuk-link">${escapedArtefactId}</a>`
},
{
text: pub.listType
},
{
text: formatDateTime(pub.displayFrom)
},
{
text: formatDateTime(pub.displayTo)
}
];
})
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential N+1 performance issue: getArtefactType() per row.
If a location has many publications, this will fan out into many calls; consider returning type in getArtefactSummariesByLocation() or adding a batched API.

🤖 Prompt for AI Agents
In libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts around
lines 44 to 67 the code calls getArtefactType() inside the publications.map loop
causing an N+1 request pattern; instead either (A) modify
getArtefactSummariesByLocation() to include the artefact type in each
publication summary so you can use pub.type directly, or (B) fetch all needed
types in one call before mapping: collect unique artefactIds, call a batched API
like getArtefactTypesBatch(ids) that returns a map id->type, and then look up
type from that map inside the loop; update the component types/usages
accordingly and remove the per-row getArtefactType() call.

Comment on lines +75 to +83
} catch (error) {
console.error("Error loading publications:", error);
res.render("blob-explorer-publications/index", {
...t,
error: t.publicationsError,
tableRows: [],
locationId,
locale
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid logging raw errors for publication loads.
Prefer redacted/structured logging to prevent leaking details in logs. (Based on retrieved learnings.)

🤖 Prompt for AI Agents
In libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts around
lines 75 to 83, remove the raw console.error(error) call and replace it with a
structured, redacted log entry: call the app's logger (e.g., logger.error) with
a clear message and only non-sensitive context (locationId, locale, and an error
code or sanitized message), or pass a redacted error helper that strips
stack/PII; do not include the full error object or stack in logs, and ensure the
rest of the catch block behavior (rendering the page with t.publicationsError
and empty tableRows) remains unchanged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts (1)

1-6: Mirror copy tweaks from English version for consistency

This locale object is structurally fine and matches the en keys. The same optional text tweaks as in en.ts apply here:

  • “Submission re-submitted” → likely “Subscription re-submitted”, to align with other Blob Explorer pages.
  • “Blob explorer – Locations” → consider “Blob Explorer Locations” to match the locations page title.
libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (3)

10-10: Factor out shared translation selector for blob explorer pages

getTranslations here is the same “lng === "cy" ? cy : en” pattern used in other blob‑explorer controllers. To keep things DRY, consider extracting a shared getBlobExplorerTranslations(lng) helper that all these pages can reuse.


42-49: Avoid logging raw error objects; log a redacted summary instead

console.error("Error loading JSON file:", error); can leak stack traces, file paths or other sensitive details into logs, which conflicts with the “no sensitive data in logs” guideline. Prefer logging a minimal, structured summary (e.g. artefactId + error.message) via your standard logger, without serialising the full error object. For example:

-  } catch (error) {
-    console.error("Error loading JSON file:", error);
+  } catch (error) {
+    console.warn("Error loading JSON file", {
+      artefactId,
+      message: error instanceof Error ? error.message : String(error)
+    });
     res.render("blob-explorer-json-file/index", {
       ...t,
       error: t.jsonFileError,
       locale
     });
   }

As per coding guidelines, this keeps operational context while avoiding sensitive detail in logs.


52-63: URL‑encode artefactId when building the redirect query string

The redirect currently interpolates the raw artefactId into the query string. If artefactId ever contains characters like & or =, it can break the querystring or allow parameter injection. Encoding it keeps the URL well‑formed:

-  return res.redirect(`/blob-explorer-confirm-resubmission?artefactId=${artefactId}`);
+  return res.redirect(
+    `/blob-explorer-confirm-resubmission?artefactId=${encodeURIComponent(artefactId)}`
+  );

You can still store the raw value in req.session.resubmissionArtefactId; only the URL parameter needs encoding.

🧹 Nitpick comments (4)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts (1)

1-18: Flat file localisation looks consistent; consider de-duplicating shared metadata labels

The en dictionary is well-structured and consistent with the other Blob Explorer pages (titles, error copy, metadata keys). No issues from a correctness or i18n standpoint.

If this pattern continues to grow, consider extracting the repeated metadata* labels into a shared localisation fragment to reduce duplication across Blob Explorer pages.

libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts (1)

1-6: Minor copy consistency: "submission" vs "subscription" and locations label

The structure and keys look good. Two small text nits you might want to align with the rest of the blob‑explorer flow:

  • Other pages talk about “subscription re‑submission” / “Re‑submit subscription” (see blob-explorer-confirm-resubmission/en.ts and blob-explorer-*-file/en.ts), but this success page says “Submission re-submitted”. Consider changing both successTitle and successBanner to “Subscription re-submitted” for consistency.
  • successLinkToLocations is “Blob explorer – Locations” while the locations page title is “Blob Explorer Locations” (no hyphen, capital “Explorer”). Consider reusing that wording for the link label for a more consistent UI.
libs/system-admin-pages/src/types/session.ts (1)

1-5: SessionData augmentation matches usage; optional pattern tweak

The resubmissionArtefactId?: string extension is minimal and lines up with how req.session.resubmissionArtefactId is used in the blob explorer flows. If you ever see TS not picking this up in other packages, a low-friction tweak is to follow the common augmentation pattern by adding an import "express-session"; at the top so the compiler definitely loads this module’s declaration.

libs/system-admin-pages/src/services/formatting.ts (1)

1-21: Formatting helpers look good; consider hoisting the HTML escape map

Both formatDateTime and escapeHtml do what the blob explorer pages need and keep concerns nicely separated. As a tiny tidy‑up, you could move the map in escapeHtml to module scope as a const HTML_ESCAPE_MAP to avoid re‑allocating it per call and to align with the constant naming convention.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2016c5c and b4d627f.

📒 Files selected for processing (18)
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts (1 hunks)
  • libs/system-admin-pages/src/services/formatting.ts (1 hunks)
  • libs/system-admin-pages/src/types/session.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use camelCase for TypeScript variables: userId, caseDetails, documentId
Use PascalCase for classes and interfaces: UserService, CaseRepository. NO I prefix.
Use kebab-case for file and directory names: user-service.ts, case-management/
Use boolean variable names with is/has/can prefix: isActive, hasAccess, canEdit
Module ordering: constants at the top, exported functions next, other functions in order of use, interfaces and types at the bottom
Always add .js extension to relative imports in ES modules (e.g., import { foo } from "./bar.js")
Use workspace aliases for imports (@hmcts/*) instead of relative paths across packages
Use strict TypeScript mode enabled; no any without justification
Use parameterized database queries with Prisma (no raw SQL string concatenation)
Do not include sensitive data in logs

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts
  • libs/system-admin-pages/src/services/formatting.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/types/session.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use SCREAMING_SNAKE_CASE for constants: MAX_FILE_SIZE, DEFAULT_TIMEOUT

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts
  • libs/system-admin-pages/src/services/formatting.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/types/session.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts
libs/*/src/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

libs/*/src/pages/**/*.ts: Create page controller files with GET and POST exports following the pattern: export const GET = async (req, res) => { ... }
Provide both en and cy language objects in page controllers for English and Welsh support

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Input validation must be performed on all endpoints

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts
  • libs/system-admin-pages/src/services/formatting.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/types/session.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not use CommonJS - ES modules only with import/export syntax

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts
  • libs/system-admin-pages/src/services/formatting.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/types/session.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts
🧠 Learnings (5)
📚 Learning: 2025-12-15T13:38:57.483Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.483Z
Learning: Applies to libs/*/src/pages/**/*.ts : Provide both `en` and `cy` language objects in page controllers for English and Welsh support

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
  • libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts
📚 Learning: 2025-12-15T13:38:57.483Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.483Z
Learning: Applies to **/*-{formatting,properties,helpers,utilities}.ts : Do not create generic utility files - Be specific with file names (e.g., `object-properties.ts`, `date-formatting.ts` not `utils.ts`)

Applied to files:

  • libs/system-admin-pages/src/services/formatting.ts
📚 Learning: 2025-12-15T13:38:57.483Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.483Z
Learning: Applies to libs/*/src/pages/**/*.ts : Create page controller files with GET and POST exports following the pattern: `export const GET = async (req, res) => { ... }`

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
📚 Learning: 2025-12-15T13:38:57.483Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.483Z
Learning: Applies to **/*.{ts,tsx} : Do not include sensitive data in logs

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
📚 Learning: 2025-11-20T09:59:16.776Z
Learnt from: junaidiqbalmoj
Repo: hmcts/cath-service PR: 106
File: libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts:84-160
Timestamp: 2025-11-20T09:59:16.776Z
Learning: In the cath-service repository, Welsh localization (lng=cy) is not required for admin screens (system-admin-pages), so locale preservation in admin screen redirects is not necessary.

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts
🧬 Code graph analysis (4)
libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (17)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts (1)
  • cy (1-16)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts (1)
  • cy (1-18)
libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts (1)
  • cy (1-20)
libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts (1)
  • cy (1-7)
libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts (1)
  • cy (1-9)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts (1)
  • cy (1-6)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts (1)
  • en (1-16)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts (1)
  • en (1-18)
libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts (1)
  • en (1-20)
libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts (1)
  • en (1-7)
libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts (1)
  • en (1-9)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts (1)
  • en (1-6)
libs/publication/src/repository/queries.ts (1)
  • getArtefactMetadata (169-196)
libs/publication/src/repository/service.ts (2)
  • getJsonContent (39-51)
  • getRenderedTemplateUrl (53-66)
libs/system-admin-pages/src/services/formatting.ts (1)
  • escapeHtml (12-21)
libs/system-admin-pages/src/routes/files/[filename].ts (1)
  • GET (86-86)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.ts (1)
  • GET (19-19)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts (5)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts (1)
  • en (1-16)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts (1)
  • en (1-18)
libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts (1)
  • en (1-20)
libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts (1)
  • en (1-7)
libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts (1)
  • en (1-9)
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.ts (5)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.ts (1)
  • cy (1-16)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.ts (1)
  • cy (1-18)
libs/system-admin-pages/src/pages/blob-explorer-json-file/cy.ts (1)
  • cy (1-20)
libs/system-admin-pages/src/pages/blob-explorer-locations/cy.ts (1)
  • cy (1-7)
libs/system-admin-pages/src/pages/blob-explorer-publications/cy.ts (1)
  • cy (1-9)
libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts (4)
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts (1)
  • en (1-16)
libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts (1)
  • en (1-20)
libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts (1)
  • en (1-7)
libs/system-admin-pages/src/pages/blob-explorer-publications/en.ts (1)
  • en (1-9)
🔇 Additional comments (3)
libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts (1)

1-7: LGTM!

The English locale file follows all coding guidelines and i18n best practices. The translation strings are clear and grammatically correct, property names use camelCase, and the ES module syntax is correct. The lowercase en identifier aligns with standard i18n conventions and the project's bilingual support pattern.

libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts (1)

1-20: Translation keys are consistent and complete for the JSON file view

The en object covers all JSON-file page strings (title, resubmit CTA, metadata labels, error summary) and aligns with the corresponding Welsh file and controller usage. Nothing further needed here.

libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (1)

1-8: Controller structure and access control look solid

GET/POST are exported as RequestHandler[] with the SYSTEM_ADMIN role guard first, and the controller wires in both en and cy translation modules via a simple locale picker. This matches the page‑controller and i18n patterns described in the guidelines.

Also applies to: 65-66

@@ -0,0 +1,16 @@
export const en = {
confirmTitle: "Confirm subscription re-submission",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix typo: "subscription" should be "publication".

The title uses "subscription" but should be "publication" to match the rest of the codebase and the PR context. Line 5 correctly uses "publication".

Apply this diff to fix the typo:

-  confirmTitle: "Confirm subscription re-submission",
+  confirmTitle: "Confirm publication re-submission",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
confirmTitle: "Confirm subscription re-submission",
confirmTitle: "Confirm publication re-submission",
🤖 Prompt for AI Agents
In libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
around line 2, the confirmTitle string uses the word "subscription" but should
read "publication"; update the value from "Confirm subscription re-submission"
to "Confirm publication re-submission" so it matches the rest of the codebase
and PR context.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
apps/web/src/app.test.ts (2)

182-186: Hard-coded router count is correct but a bit brittle

The toHaveBeenCalledTimes(9) expectation matches the current list in the comment, but any future router addition will force this test to change. Consider deriving the expected count (e.g. from a small helper or from mocked config) so the assertion stays in sync with the actual registrations.


188-194: Strengthen the system-admin route registration assertion

expect(calls.length).toBeGreaterThanOrEqual(9) doesn’t actually prove that system-admin API/page routes were registered; it only checks a minimum total. Since you already mock @hmcts/system-admin-pages/config, you could assert that at least one call to createSimpleRouter uses /mock/system-admin/api or /mock/system-admin/pages as the path, which would better match the test name and comment.

Example refactor:

-    const calls = vi.mocked(createSimpleRouter).mock.calls;
-
-    // Verify system-admin routes were registered (should have 9 total calls)
-    expect(calls.length).toBeGreaterThanOrEqual(9);
+    const calls = vi.mocked(createSimpleRouter).mock.calls;
+    expect(calls).toEqual(
+      expect.arrayContaining([
+        [expect.objectContaining({ path: "/mock/system-admin/api" })],
+        [expect.objectContaining({ path: "/mock/system-admin/pages" }), expect.anything()]
+      ])
+    );

This keeps the focus on system-admin wiring rather than the global call count.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4d627f and b526633.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • apps/web/src/app.test.ts (2 hunks)
  • apps/web/src/app.ts (2 hunks)
  • libs/system-admin-pages/src/config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/system-admin-pages/src/config.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use camelCase for TypeScript variables: userId, caseDetails, documentId
Use PascalCase for classes and interfaces: UserService, CaseRepository. NO I prefix.
Use kebab-case for file and directory names: user-service.ts, case-management/
Use boolean variable names with is/has/can prefix: isActive, hasAccess, canEdit
Module ordering: constants at the top, exported functions next, other functions in order of use, interfaces and types at the bottom
Always add .js extension to relative imports in ES modules (e.g., import { foo } from "./bar.js")
Use workspace aliases for imports (@hmcts/*) instead of relative paths across packages
Use strict TypeScript mode enabled; no any without justification
Use parameterized database queries with Prisma (no raw SQL string concatenation)
Do not include sensitive data in logs

Files:

  • apps/web/src/app.test.ts
  • apps/web/src/app.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use SCREAMING_SNAKE_CASE for constants: MAX_FILE_SIZE, DEFAULT_TIMEOUT

Files:

  • apps/web/src/app.test.ts
  • apps/web/src/app.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source code using *.test.ts or *.spec.ts naming pattern

Files:

  • apps/web/src/app.test.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Input validation must be performed on all endpoints

Files:

  • apps/web/src/app.test.ts
  • apps/web/src/app.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not use CommonJS - ES modules only with import/export syntax

Files:

  • apps/web/src/app.test.ts
  • apps/web/src/app.ts
🧠 Learnings (2)
📚 Learning: 2025-12-15T13:38:57.483Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.483Z
Learning: Applies to e2e-tests/**/*.spec.ts : Include validation checks, Welsh translation checks, accessibility checks, and keyboard navigation within a single E2E test journey

Applied to files:

  • apps/web/src/app.test.ts
📚 Learning: 2025-12-15T13:38:57.483Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.483Z
Learning: Applies to **/config.ts : Module config.ts must export standardized interfaces: pageRoutes, apiRoutes, prismaSchemas, assets

Applied to files:

  • apps/web/src/app.test.ts
  • apps/web/src/app.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: E2E Tests
🔇 Additional comments (3)
apps/web/src/app.test.ts (1)

85-90: System-admin config mock correctly extended with apiRoutes

Adding apiRoutes here keeps the test double in sync with the system-admin config used in app.ts; no issues spotted.

apps/web/src/app.ts (2)

17-22: System-admin imports look consistent and idiomatic

The additional systemAdminApiRoutes/systemAdminFileUploadRoutes/systemAdminModuleRoot/systemAdminPageRoutes imports follow existing patterns and naming conventions; no changes needed.


110-112: New system-admin API router registration is wired correctly; confirm protection/validation

Mounting systemAdminApiRoutes via createSimpleRouter in line with locationApiRoutes looks consistent, and matches the updated tests expecting 9 router registrations. Please double-check that the underlying system-admin API route definitions enforce authentication/authorization and perform input validation, as these endpoints appear to be used for file serving.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (1)

42-49: Avoid logging raw errors for admin artefact fetch failures.

console.error(..., error) may leak sensitive details. Use structured logging that redacts sensitive fields.

Based on retrieved learnings, sensitive data should not be included in logs.

e2e-tests/tests/blob-explorer.spec.ts (1)

17-90: Fix expired test dates (2024 → dynamic dates).

The artefact uses displayTo: new Date("2024-12-31") which expired a year ago (current date: December 15, 2025). This causes the artefact to be filtered from the Blob Explorer UI, breaking the test.

Apply this diff to use dynamic dates:

 async function createTestPublication(): Promise<TestPublicationData> {
+  const now = new Date();
+  const displayFrom = new Date(now.getFullYear(), 0, 1); // Jan 1 of current year
+  const displayTo = new Date(now.getFullYear() + 1, 11, 31); // Dec 31 of next year
+  const contentDate = new Date(now.getFullYear(), now.getMonth(), 1); // First of current month
+
   // Get first location for test
   const location = await prisma.location.findFirst();
   if (!location) {
     throw new Error("No location found in database");
   }

   // Create test artefact (JSON publication) - let Prisma generate UUID
   const artefact = await prisma.artefact.create({
     data: {
       locationId: location.locationId.toString(),
       provenance: "MANUAL_UPLOAD",
-      displayFrom: new Date("2024-01-01"),
-      displayTo: new Date("2024-12-31"),
+      displayFrom,
+      displayTo,
       language: "ENGLISH",
       listTypeId: 1, // Family Daily Cause List
       sensitivity: "PUBLIC",
-      contentDate: new Date("2024-06-01"),
+      contentDate,
       isFlatFile: false,
     },
   });

   const artefactId = artefact.artefactId;

   // Create test JSON file in storage
   const storageDir = path.join(process.cwd(), "storage", "temp", "uploads");
   await fs.mkdir(storageDir, { recursive: true });

   const testJsonContent = {
     document: {
-      publicationDate: "2024-06-01",
+      publicationDate: contentDate.toISOString().slice(0, 10),
       locationName: location.name,

Note: A past comment claimed this was addressed, but the 2024 dates remain in the current code.

🧹 Nitpick comments (1)
e2e-tests/tests/blob-explorer.spec.ts (1)

310-313: Consider more robust PRG verification than goBack() URL assertion.

The test verifies POST/Redirect/GET by checking the URL remains unchanged after goBack(). However, browser history behavior can vary. A more reliable approach would verify no duplicate POST request occurs.

Consider adding a request counter or route spy to verify POST is not re-executed:

+      // Set up request monitoring before POST
+      let postCount = 0;
+      await page.route('**/blob-explorer-confirm-resubmission', (route) => {
+        if (route.request().method() === 'POST') {
+          postCount++;
+        }
+        route.continue();
+      });
+
       await confirmButton.click();

       // STEP 12: Verify success page
       await expect(page).toHaveURL("/blob-explorer-resubmission-success");
+      expect(postCount).toBe(1);

       // STEP 13: Test POST/Redirect/GET pattern - browser back should not re-submit
       await page.goBack();
-      await expect(page).toHaveURL("/blob-explorer-resubmission-success");
+      expect(postCount).toBe(1); // POST count should still be 1, not 2

This verifies the PRG pattern intent (no duplicate POST) rather than relying on history navigation behavior.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b526633 and 2e870b2.

📒 Files selected for processing (5)
  • e2e-tests/tests/blob-explorer.spec.ts (1 hunks)
  • libs/publication/src/repository/service.test.ts (1 hunks)
  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (1 hunks)
  • libs/system-admin-pages/src/routes/files/[filename].test.ts (1 hunks)
  • libs/system-admin-pages/src/routes/files/[filename].ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • libs/publication/src/repository/service.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/system-admin-pages/src/routes/files/[filename].ts
  • libs/system-admin-pages/src/routes/files/[filename].test.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use camelCase for TypeScript variables: userId, caseDetails, documentId
Use PascalCase for classes and interfaces: UserService, CaseRepository. NO I prefix.
Use kebab-case for file and directory names: user-service.ts, case-management/
Use boolean variable names with is/has/can prefix: isActive, hasAccess, canEdit
Module ordering: constants at the top, exported functions next, other functions in order of use, interfaces and types at the bottom
Always add .js extension to relative imports in ES modules (e.g., import { foo } from "./bar.js")
Use workspace aliases for imports (@hmcts/*) instead of relative paths across packages
Use strict TypeScript mode enabled; no any without justification
Use parameterized database queries with Prisma (no raw SQL string concatenation)
Do not include sensitive data in logs

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • e2e-tests/tests/blob-explorer.spec.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use SCREAMING_SNAKE_CASE for constants: MAX_FILE_SIZE, DEFAULT_TIMEOUT

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • e2e-tests/tests/blob-explorer.spec.ts
libs/*/src/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

libs/*/src/pages/**/*.ts: Create page controller files with GET and POST exports following the pattern: export const GET = async (req, res) => { ... }
Provide both en and cy language objects in page controllers for English and Welsh support

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Input validation must be performed on all endpoints

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • e2e-tests/tests/blob-explorer.spec.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not use CommonJS - ES modules only with import/export syntax

Files:

  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
  • e2e-tests/tests/blob-explorer.spec.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source code using *.test.ts or *.spec.ts naming pattern

Files:

  • e2e-tests/tests/blob-explorer.spec.ts
e2e-tests/**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

e2e-tests/**/*.spec.ts: E2E tests must be located in e2e-tests/ directory with *.spec.ts naming pattern
Tag nightly-only E2E tests with @nightly in the test title
E2E tests should use selectors in priority order: getByRole(), getByLabel(), getByText(), getByTestId()
Accessibility testing (WCAG 2.2 AA) is mandatory - include axe-core checks in E2E tests
Include validation checks, Welsh translation checks, accessibility checks, and keyboard navigation within a single E2E test journey
Do not test visual styling (fonts, colors, margins, padding) in E2E tests

Files:

  • e2e-tests/tests/blob-explorer.spec.ts
🧠 Learnings (8)
📚 Learning: 2025-12-15T13:38:57.483Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.483Z
Learning: Applies to libs/*/src/pages/**/*.ts : Create page controller files with GET and POST exports following the pattern: `export const GET = async (req, res) => { ... }`

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
📚 Learning: 2025-12-15T13:38:57.483Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.483Z
Learning: Applies to **/*.{ts,tsx} : Do not include sensitive data in logs

Applied to files:

  • libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
📚 Learning: 2025-12-15T13:38:57.483Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.483Z
Learning: Applies to e2e-tests/**/*.spec.ts : Include validation checks, Welsh translation checks, accessibility checks, and keyboard navigation within a single E2E test journey

Applied to files:

  • e2e-tests/tests/blob-explorer.spec.ts
📚 Learning: 2025-12-15T13:38:57.483Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.483Z
Learning: Applies to e2e-tests/**/*.spec.ts : Accessibility testing (WCAG 2.2 AA) is mandatory - include axe-core checks in E2E tests

Applied to files:

  • e2e-tests/tests/blob-explorer.spec.ts
📚 Learning: 2025-12-15T13:38:57.483Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.483Z
Learning: Applies to e2e-tests/**/*.spec.ts : E2E tests must be located in `e2e-tests/` directory with `*.spec.ts` naming pattern

Applied to files:

  • e2e-tests/tests/blob-explorer.spec.ts
📚 Learning: 2025-12-15T13:38:57.483Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.483Z
Learning: Applies to e2e-tests/**/*.spec.ts : Tag nightly-only E2E tests with `nightly` in the test title

Applied to files:

  • e2e-tests/tests/blob-explorer.spec.ts
📚 Learning: 2025-12-15T13:38:57.483Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.483Z
Learning: Applies to e2e-tests/**/*.spec.ts : Do not test visual styling (fonts, colors, margins, padding) in E2E tests

Applied to files:

  • e2e-tests/tests/blob-explorer.spec.ts
📚 Learning: 2025-11-27T09:48:13.010Z
Learnt from: junaidiqbalmoj
Repo: hmcts/cath-service PR: 136
File: libs/api/src/blob-ingestion/validation.ts:156-163
Timestamp: 2025-11-27T09:48:13.010Z
Learning: In libs/api/src/blob-ingestion/validation.ts, the permissive date validation in isValidISODate and isValidISODateTime functions is expected behavior and should not be flagged for stricter validation.

Applied to files:

  • e2e-tests/tests/blob-explorer.spec.ts
🧬 Code graph analysis (1)
e2e-tests/tests/blob-explorer.spec.ts (1)
e2e-tests/utils/sso-helpers.ts (1)
  • loginWithSSO (10-46)
🪛 ast-grep (0.40.0)
e2e-tests/tests/blob-explorer.spec.ts

[warning] 184-184: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(/blob-explorer-publications.*locationId=${publicationData.locationId})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 209-209: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(/blob-explorer-json-file.*artefactId=${publicationData.artefactId})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 253-253: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(/blob-explorer-confirm-resubmission.*artefactId=${publicationData.artefactId})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (3)
libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts (1)

52-63: LGTM! URL encoding properly applied.

The redirect now uses encodeURIComponent(artefactId), which prevents query string injection and properly handles unexpected values.

e2e-tests/tests/blob-explorer.spec.ts (2)

92-129: LGTM! Cleanup properly handles subscription and user deletion.

The function now deletes subscription and user before the artefact, respecting foreign key constraints. Error handling with try-catch ensures cleanup doesn't fail silently.


138-330: Comprehensive test coverage with excellent accessibility and i18n checks.

The test properly includes:

  • @nightly tag for long-running tests
  • Axe accessibility checks on all pages (WCAG 2.2 AA)
  • Welsh translation validation throughout the journey
  • Keyboard navigation tests
  • Semantic selectors (getByRole, getByLabel)
  • Full workflow validation from dashboard to success page

As per coding guidelines, the test includes validation checks, Welsh translation checks, accessibility checks, and keyboard navigation within a single E2E test journey.

@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants