-
Notifications
You must be signed in to change notification settings - Fork 2
VIBE-310 - Blob Explorer #178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
VIBE-310 - Blob Explorer #178
Conversation
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]>
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including You can disable this status message by setting the WalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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. Comment |
There was a problem hiding this 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:createArtefactnow sendsnoMatch, so these tests will fail.
libs/publication/src/repository/queries.tsincludesnoMatch: data.noMatchin bothprisma.artefact.create({...})andprisma.artefact.update({...}), but the assertions here expect an exact payload withoutnoMatch.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.tsfile contains English text rather than Welsh translations. This is the same issue as inblob-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.tsfile 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.tsfile 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.
Ifmetadata.contentDate/displayFrom/displayTocan be absent, ensureformatDateTimehandles it or add conditional rendering.libs/system-admin-pages/src/pages/blob-explorer-publications/index.test.ts (1)
33-35: Avoid hard-codingGET[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: Replacesession: {} as anywith a narrow typed stub.
Even in tests, prefer a minimaltype SessionStub = { resubmissionArtefactId?: string }and cast tounknown as Request["session"](or the project’s session type) to avoidany.libs/system-admin-pages/src/pages/blob-explorer-locations/index.ts (1)
32-40: Prefer structured logging overconsole.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 ofcy.tsfile.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 originalconsole.log. Sincevi.spyOnis called in each test, you may want to addvi.restoreAllMocks()in anafterEachor usevi.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_EMAILorSSO_TEST_SYSTEM_ADMIN_PASSWORDare 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
regionrule 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.
formatDateTimealways 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-coreimport 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 whenerroris present. Suggestif 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: Avoidas anyforsessionto comply with “noany” guideline. Useunknown/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: Dropas anyonfs.readdirmock return values. Keep these asstring[]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: Avoidas anyforsessionin tests; use a minimal typed stub instead.
session: {} as any(Line 25) violates the “noanywithout 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
SessionDatatype augmentation, even better to use that explicitly.Also applies to: 20-32
34-40: Don’t hardcodeGET[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 ofas stringcasts.- 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 centralisingformatDateTimeto avoid copy/paste across pages.libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts (2)
22-30: Same as JSON page: avoidreq.query.artefactId as stringcasts; 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: ParseartefactIdsafely; avoidas stringonreq.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
artefactIdis in the URL, even if it doesn’t matchreq.session.resubmissionArtefactId.libs/publication/src/repository/queries.test.ts (2)
2-26: Movevi.mock(...)blocks above module imports (or usevi.hoisted) to avoid hoisting/ordering footguns.
Right now./queries.jsis 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.
ForgetLocationsWithPublicationCount, consider also asserting$queryRawwas 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. ForgetArtefactMetadata, you’re already assertinggetLocationById(123)—good.libs/publication/src/repository/queries.ts (3)
1-33: Guidelines mismatch: moveexport 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 ofmockListTypes.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$queryRawmapping (CAST join + bigint->number).
CAST(l.location_id AS VARCHAR) = a.location_idcan prevent index usage; consider aligning column types or changing the join to avoid casting on the indexed side.Number(row.publication_count)can overflow pastNumber.MAX_SAFE_INTEGER(unlikely for counts here, but it’s a correctness edge).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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 useis/has/canprefix (e.g.,isActive,hasAccess,canEdit).
Classes and interfaces must use PascalCase (e.g.,UserService,CaseRepository). DO NOT useIprefix 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. Noanytype without justification. Use explicit types for all variables and function parameters.
Always add.jsextension 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.tslibs/system-admin-pages/src/pages/blob-explorer-locations/cy.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/pages/system-admin-dashboard/en.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/cy.tslibs/system-admin-pages/src/services/service.test.tse2e-tests/tests/blob-explorer.spec.tslibs/system-admin-pages/src/pages/blob-explorer-publications/en.tslibs/system-admin-pages/src/services/service.tse2e-tests/tests/system-admin-dashboard.spec.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.tslibs/system-admin-pages/src/pages/blob-explorer-locations/en.tslibs/system-admin-pages/src/types/session.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.tslibs/publication/src/repository/service.test.tslibs/system-admin-pages/src/index.tslibs/system-admin-pages/src/pages/blob-explorer-publications/cy.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/en.tslibs/publication/src/repository/service.tslibs/publication/src/index.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.tslibs/publication/src/repository/queries.tslibs/publication/src/repository/queries.test.ts
**/src/pages/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/src/pages/**/*.ts: Pages are registered through explicit imports inapps/web/src/app.ts. Routes are created based on file names within thepages/directory (e.g.,my-page.tsbecomes/my-page, nested routes via subdirectories).
Page controller files must exportGETand/orPOSTfunctions that accept Express Request and Response, render usingres.render(), and handle form submissions.
Files:
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.tslibs/system-admin-pages/src/pages/blob-explorer-locations/cy.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/pages/system-admin-dashboard/en.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/cy.tslibs/system-admin-pages/src/pages/blob-explorer-publications/en.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.tslibs/system-admin-pages/src/pages/blob-explorer-locations/en.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.tslibs/system-admin-pages/src/pages/blob-explorer-publications/cy.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/en.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.tslibs/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 bothenandcyobjects 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.tslibs/system-admin-pages/src/pages/blob-explorer-locations/cy.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/pages/system-admin-dashboard/en.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/cy.tslibs/system-admin-pages/src/pages/blob-explorer-publications/en.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.tslibs/system-admin-pages/src/pages/blob-explorer-locations/en.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.njklibs/system-admin-pages/src/pages/blob-explorer-publications/cy.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.njklibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.njklibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njklibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/en.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.njklibs/system-admin-pages/src/pages/blob-explorer-publications/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.njklibs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.tslibs/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, neverrequire()/module.exports. Only ES modules are allowed.
Files:
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.tslibs/system-admin-pages/src/pages/blob-explorer-locations/cy.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/pages/system-admin-dashboard/en.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/cy.tslibs/system-admin-pages/src/services/service.test.tse2e-tests/tests/blob-explorer.spec.tslibs/system-admin-pages/src/pages/blob-explorer-publications/en.tslibs/system-admin-pages/src/services/service.tse2e-tests/tests/system-admin-dashboard.spec.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.tslibs/system-admin-pages/src/pages/blob-explorer-locations/en.tslibs/system-admin-pages/src/types/session.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.tslibs/publication/src/repository/service.test.tslibs/system-admin-pages/src/index.tslibs/system-admin-pages/src/pages/blob-explorer-publications/cy.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/en.tslibs/publication/src/repository/service.tslibs/publication/src/index.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.tslibs/publication/src/repository/queries.tslibs/publication/src/repository/queries.test.ts
**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.ts: Do not create generictypes.tsfiles. Colocate types with the appropriate code file where they are used.
Do not create generic files likeutils.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.tslibs/system-admin-pages/src/pages/blob-explorer-locations/cy.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/pages/system-admin-dashboard/en.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/cy.tslibs/system-admin-pages/src/services/service.test.tse2e-tests/tests/blob-explorer.spec.tslibs/system-admin-pages/src/pages/blob-explorer-publications/en.tslibs/system-admin-pages/src/services/service.tse2e-tests/tests/system-admin-dashboard.spec.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.tslibs/system-admin-pages/src/pages/blob-explorer-locations/en.tslibs/system-admin-pages/src/types/session.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.tslibs/publication/src/repository/service.test.tslibs/system-admin-pages/src/index.tslibs/system-admin-pages/src/pages/blob-explorer-publications/cy.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/en.tslibs/publication/src/repository/service.tslibs/publication/src/index.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/cy.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.tslibs/publication/src/repository/queries.tslibs/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.tsand use Vitest withdescribe,it, andexpect.
Files:
libs/system-admin-pages/src/services/service.test.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.tslibs/publication/src/repository/service.test.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.tslibs/publication/src/repository/queries.test.ts
e2e-tests/**/*.spec.ts
📄 CodeRabbit inference engine (CLAUDE.md)
e2e-tests/**/*.spec.ts: E2E test files must be ine2e-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.tse2e-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.njklibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.njklibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.njklibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njklibs/system-admin-pages/src/pages/blob-explorer-locations/index.njklibs/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.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/cy.tslibs/system-admin-pages/src/pages/blob-explorer-publications/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.tslibs/system-admin-pages/src/pages/blob-explorer-locations/en.tslibs/system-admin-pages/src/pages/blob-explorer-publications/cy.tslibs/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.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/cy.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.tslibs/system-admin-pages/src/pages/blob-explorer-publications/cy.tslibs/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.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/cy.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/cy.tslibs/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.tse2e-tests/tests/blob-explorer.spec.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.test.tslibs/publication/src/repository/service.test.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.test.tslibs/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.tse2e-tests/tests/system-admin-dashboard.spec.tslibs/system-admin-pages/src/pages/blob-explorer-locations/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.tslibs/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.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.test.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.test.tslibs/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.tslibs/system-admin-pages/src/index.tslibs/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.njklibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/index.njklibs/system-admin-pages/src/pages/blob-explorer-flat-file/index.njklibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.njklibs/system-admin-pages/src/pages/blob-explorer-locations/index.njklibs/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
| 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() | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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()
};
}| 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"); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| {% 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 %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| {% 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" %} | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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" | ||
| } | ||
| ]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| if (!locationId) { | ||
| return res.redirect("/blob-explorer-locations"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
🎭 Playwright E2E Test Results181 tests 181 ✅ 15m 38s ⏱️ Results for commit def4f2c. ♻️ This comment has been updated with latest results. |
| @@ -0,0 +1,86 @@ | |||
| import fs from "node:fs/promises"; | |||
There was a problem hiding this comment.
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
There was a problem hiding this 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-template→base-templates) or this may fail at render-time.Guidelines require
layouts/base-templates.njk, and the currentextendsis 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_BASEderivation is brittle for built/runtime layouts—prefer config/env injection.
This was already flagged in earlier review comments; still applies here (walking..fromimport.meta.urlcan point to the wrong place indist/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-explorervs/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
errorcan ever contain markup from upstream, explicitly escape it before passing intogovukErrorSummary.- 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.displayTocan 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 withpath.relativeand 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 thanstartsWith, 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:getJsonContentreturn type is likely wrong (JSON.parsecan return arrays/primitives); preferunknown | nullor validate to a known schema.
Right now it promisesobject | null, butJSON.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:getFlatFileUrlvalidation 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 likeid..pdf). If that’s intended, fine; otherwise consider checking for path separators only and relying on “base name only” constraints fromreaddir.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 onprocess.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 interpolatednew RegExp(...)in URL assertions (simpler + removes ReDoS lint noise)
In tests, this is low-risk, but you can avoid the pattern entirely by asserting viaURLparsing.- 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 justTabpresses
Right now it “does something” but doesn’t prove keyboard navigability. Consider assertingtoBeFocused()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: indexingGET[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 ofsystemAdminApiRoutesis more stable.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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 useis/has/canprefix (e.g.,isActive,hasAccess,canEdit).
Classes and interfaces must use PascalCase (e.g.,UserService,CaseRepository). DO NOT useIprefix 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. Noanytype without justification. Use explicit types for all variables and function parameters.
Always add.jsextension 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.tslibs/system-admin-pages/src/routes/files/[filename].test.tsapps/web/src/app.test.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/routes/files/[filename].tslibs/publication/src/repository/service.test.tsapps/web/src/app.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.tslibs/publication/src/repository/service.tse2e-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.tsfile to avoid circular dependencies during Prisma client generation. Apps import config using the/configpath.
Files:
libs/system-admin-pages/src/config.ts
**/*.{ts,tsx,js,mjs}
📄 CodeRabbit inference engine (CLAUDE.md)
DO NOT use CommonJS. Use
import/export, neverrequire()/module.exports. Only ES modules are allowed.
Files:
libs/system-admin-pages/src/config.tslibs/system-admin-pages/src/routes/files/[filename].test.tsapps/web/src/app.test.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/routes/files/[filename].tslibs/publication/src/repository/service.test.tsapps/web/src/app.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.tslibs/publication/src/repository/service.tse2e-tests/tests/blob-explorer.spec.ts
**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.ts: Do not create generictypes.tsfiles. Colocate types with the appropriate code file where they are used.
Do not create generic files likeutils.ts. Be specific with naming (e.g.,object-properties.ts,date-formatting.ts).
Files:
libs/system-admin-pages/src/config.tslibs/system-admin-pages/src/routes/files/[filename].test.tsapps/web/src/app.test.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/routes/files/[filename].tslibs/publication/src/repository/service.test.tsapps/web/src/app.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.tslibs/publication/src/repository/service.tse2e-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.tslibs/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.tsand use Vitest withdescribe,it, andexpect.
Files:
libs/system-admin-pages/src/routes/files/[filename].test.tsapps/web/src/app.test.tslibs/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.tslibs/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 inapps/web/src/app.ts. Routes are created based on file names within thepages/directory (e.g.,my-page.tsbecomes/my-page, nested routes via subdirectories).
Page controller files must exportGETand/orPOSTfunctions that accept Express Request and Response, render usingres.render(), and handle form submissions.
Files:
libs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/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 bothenandcyobjects 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.tslibs/system-admin-pages/src/pages/blob-explorer-publications/index.tslibs/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 ine2e-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.tslibs/system-admin-pages/src/routes/files/[filename].tsapps/web/src/app.tslibs/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.tsapps/web/src/app.test.tslibs/system-admin-pages/src/routes/files/[filename].tsapps/web/src/app.tslibs/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.tsapps/web/src/app.test.tse2e-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.tslibs/publication/src/repository/service.test.tse2e-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.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/routes/files/[filename].tslibs/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:getRenderedTemplateUrllooks 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 andvi.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 usingtestInfo.attach(...)/testInfo.annotationsinstead ofconsole.log, so cleanup issues are easier to spot in CI artifacts.libs/system-admin-pages/src/config.ts (1)
7-10: Good addition ofapiRoutesto config (keeps routing config centralized).
This matches the repo pattern of exporting route roots vialibs/*/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/:filenameroute is protected byrequireRole([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/:filenamewould improve API organization—this is supported by thecreateSimpleRouterprefix configuration but is not strictly necessary.
| 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, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| // 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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(); | ||
| }); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| {% if error %} | ||
| {{ govukErrorSummary({ | ||
| titleText: errorSummaryTitle, | ||
| errorList: [ | ||
| { | ||
| text: error | ||
| } | ||
| ] | ||
| }) }} | ||
| {% else %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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) | ||
| } | ||
| ]; | ||
| }) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| } catch (error) { | ||
| console.error("Error loading publications:", error); | ||
| res.render("blob-explorer-publications/index", { | ||
| ...t, | ||
| error: t.publicationsError, | ||
| tableRows: [], | ||
| locationId, | ||
| locale | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.ts
Outdated
Show resolved
Hide resolved
libs/system-admin-pages/src/pages/blob-explorer-flat-file/en.ts
Outdated
Show resolved
Hide resolved
libs/system-admin-pages/src/pages/blob-explorer-json-file/en.ts
Outdated
Show resolved
Hide resolved
libs/system-admin-pages/src/pages/blob-explorer-locations/en.ts
Outdated
Show resolved
Hide resolved
libs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.ts
Outdated
Show resolved
Hide resolved
libs/system-admin-pages/src/pages/blob-explorer-publications/index.ts
Outdated
Show resolved
Hide resolved
libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
Outdated
Show resolved
Hide resolved
libs/system-admin-pages/src/pages/blob-explorer-json-file/index.ts
Outdated
Show resolved
Hide resolved
libs/system-admin-pages/src/pages/blob-explorer-flat-file/index.ts
Outdated
Show resolved
Hide resolved
libs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/index.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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 consistencyThis locale object is structurally fine and matches the
enkeys. The same optional text tweaks as inen.tsapply 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
getTranslationshere is the same “lng === "cy" ? cy : en” pattern used in other blob‑explorer controllers. To keep things DRY, consider extracting a sharedgetBlobExplorerTranslations(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‑encodeartefactIdwhen building the redirect query stringThe redirect currently interpolates the raw
artefactIdinto the query string. IfartefactIdever 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 labelsThe
endictionary 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 labelThe 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.tsandblob-explorer-*-file/en.ts), but this success page says “Submission re-submitted”. Consider changing bothsuccessTitleandsuccessBannerto “Subscription re-submitted” for consistency.successLinkToLocationsis “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 tweakThe
resubmissionArtefactId?: stringextension is minimal and lines up with howreq.session.resubmissionArtefactIdis 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 animport "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 mapBoth
formatDateTimeandescapeHtmldo what the blob explorer pages need and keep concerns nicely separated. As a tiny tidy‑up, you could move themapinescapeHtmlto module scope as aconst HTML_ESCAPE_MAPto 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
📒 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. NOIprefix.
Use kebab-case for file and directory names:user-service.ts,case-management/
Use boolean variable names withis/has/canprefix: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.jsextension 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; noanywithout 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.tslibs/system-admin-pages/src/services/formatting.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/types/session.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/en.tslibs/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.tslibs/system-admin-pages/src/services/formatting.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/types/session.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/en.tslibs/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 bothenandcylanguage objects in page controllers for English and Welsh support
Files:
libs/system-admin-pages/src/pages/blob-explorer-locations/en.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/en.tslibs/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.tslibs/system-admin-pages/src/services/formatting.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/types/session.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/en.tslibs/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.tslibs/system-admin-pages/src/services/formatting.tslibs/system-admin-pages/src/pages/blob-explorer-json-file/index.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/types/session.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/en.tslibs/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.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/en.tslibs/system-admin-pages/src/pages/blob-explorer-confirm-resubmission/en.tslibs/system-admin-pages/src/pages/blob-explorer-resubmission-success/cy.tslibs/system-admin-pages/src/pages/blob-explorer-flat-file/en.tslibs/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
enidentifier 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 viewThe
enobject 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 solidGET/POST are exported as
RequestHandler[]with the SYSTEM_ADMIN role guard first, and the controller wires in bothenandcytranslation 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", | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
There was a problem hiding this 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 brittleThe
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 tocreateSimpleRouteruses/mock/system-admin/apior/mock/system-admin/pagesas 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
⛔ Files ignored due to path filters (1)
yarn.lockis 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. NOIprefix.
Use kebab-case for file and directory names:user-service.ts,case-management/
Use boolean variable names withis/has/canprefix: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.jsextension 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; noanywithout 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.tsapps/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.tsapps/web/src/app.ts
**/*.{test,spec}.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Test files must be co-located with source code using
*.test.tsor*.spec.tsnaming 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.tsapps/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.tsapps/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.tsapps/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 withapiRoutesAdding
apiRouteshere keeps the test double in sync with the system-admin config used inapp.ts; no issues spotted.apps/web/src/app.ts (2)
17-22: System-admin imports look consistent and idiomaticThe additional
systemAdminApiRoutes/systemAdminFileUploadRoutes/systemAdminModuleRoot/systemAdminPageRoutesimports follow existing patterns and naming conventions; no changes needed.
110-112: New system-admin API router registration is wired correctly; confirm protection/validationMounting
systemAdminApiRoutesviacreateSimpleRouterin line withlocationApiRouteslooks 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.
There was a problem hiding this 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 thangoBack()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 2This 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
📒 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. NOIprefix.
Use kebab-case for file and directory names:user-service.ts,case-management/
Use boolean variable names withis/has/canprefix: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.jsextension 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; noanywithout 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.tse2e-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.tse2e-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 bothenandcylanguage 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.tse2e-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.tse2e-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.tsor*.spec.tsnaming 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 ine2e-tests/directory with*.spec.tsnaming pattern
Tag nightly-only E2E tests with@nightlyin 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:
@nightlytag 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.
|



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
Internationalisation
Documentation
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.