Skip to content

VIBE-215 Add flat file viewing feature for publications - #141

Merged
ChrisS1512 merged 33 commits into
masterfrom
feature/VIBE-215-view-publication-flat-files
Jan 29, 2026
Merged

VIBE-215 Add flat file viewing feature for publications#141
ChrisS1512 merged 33 commits into
masterfrom
feature/VIBE-215-view-publication-flat-files

Conversation

@alexbottenberg

@alexbottenberg alexbottenberg commented Nov 27, 2025

Copy link
Copy Markdown
Contributor
  • Add file storage and retrieval service
  • Implement flat file page with embedded PDF viewer
  • Add download API endpoint for flat files
  • Update summary of publications to link to flat files
  • Configure Helm for single pod deployment (ephemeral storage)
  • Update CSP to allow embedded content
  • Add E2E tests for flat file viewing

🤖 Generated with Claude Code

Jira link

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

Change description

  • Add file storage and retrieval service
  • Implement flat file page with embedded PDF viewer
  • Add download API endpoint for flat files
  • Update summary of publications to link to flat files
  • Configure Helm for single pod deployment (ephemeral storage)
  • Update CSP to allow embedded content
  • Add E2E tests for flat file viewing

Testing done

Security Vulnerability Assessment

CVE Suppression: Are there any CVEs present in the codebase (either newly introduced or pre-existing) that are being intentionally suppressed or ignored by this commit?

  • Yes
  • No

Checklist

  • commit messages are meaningful and follow good commit message guidelines
  • README and other documentation has been updated / added (if needed)
  • tests have been updated / new tests has been added (if needed)
  • Does this PR introduce a breaking change

Summary by CodeRabbit

  • New Features

    • PDF viewer page and dedicated download endpoint for hearing‑list files; public pages now served by the API.
  • Infrastructure

    • Deployment temporarily set to a single pod with autoscaling disabled.
  • Localization

    • Welsh translations added for viewer and error messages.
  • Security

    • Content Security Policy expanded to improve iframe/object handling.
  • Documentation & Tests

    • Comprehensive design docs and extensive unit and end‑to‑end test coverage.

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

- Add file storage and retrieval service
- Implement flat file page with embedded PDF viewer
- Add download API endpoint for flat files
- Update summary of publications to link to flat files
- Configure Helm for single pod deployment (ephemeral storage)
- Update CSP to allow embedded content
- Add E2E tests for flat file viewing

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

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 27, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Integrates a flat-file viewer and download API into public-pages, adds file-retrieval and content-type utilities, registers public-pages API routes in apps, introduces Helm single‑replica/disabled autoscaling values, updates CSP defaults, adds a DB migration for ingestion_log, and includes extensive tests and documentation for VIBE‑215.

Changes

Cohort / File(s) Summary
Public Pages — Viewer & Routes
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].*, libs/public-pages/src/pages/publication-not-found.*, libs/public-pages/src/routes/flat-file/[artefactId]/download.*
New GET handlers, Nunjucks viewer and publication-not-found templates, download route with error mappings and cache headers; en/cy localisation modules; unit tests for controllers and routes.
Flat-file Service & Exports
libs/public-pages/src/flat-file/flat-file-service.*, libs/public-pages/src/index.ts, libs/public-pages/src/flat-file/flat-file-service.test.ts
Added getFlatFileForDisplay and getFileForDownload, exported result types and re-exports from index; comprehensive unit tests.
File-storage Utilities & Publication API
libs/publication/src/file-storage/*, libs/publication/src/file-storage/content-type.*, libs/publication/src/index.ts, libs/publication/src/repository/queries.*
New file lookup helpers (findFileByArtefactId, getFileBuffer, getFileExtension, getFileName), content-type mapping (getContentTypeFromExtension), and getArtefactById query; tests and exports updated.
Summary/Listings UI
libs/public-pages/src/pages/summary-of-publications/*
Added isFlatFile and locationId to artefact mapping; template branch linking flat-file entries to /hearing-lists/:locationId/:artefactId opening in a new tab.
App Integration & Tests
apps/api/src/app.ts, apps/web/src/app.ts, apps/web/src/app.test.ts
Public-pages API routes exported and mounted (API and web apps); web registers public pages API routes under /api; tests updated to mock/register additional routes.
E2E Tests
e2e-tests/tests/flat-file-viewing.spec.ts
New end‑to‑end suite covering viewer flow, downloads, error cases, Welsh locale, accessibility checks, and cleanup helpers.
Build & Packaging
libs/public-pages/package.json
Build pipeline extended: build:routes script and updated build chain to copy route files into dist.
CSP / Security Middleware
libs/web-core/src/middleware/helmet/helmet-middleware.*, libs/web-core/src/middleware/helmet/helmet-middleware.test.ts
CSP now always includes 'self' in frame-src, adds object-src and form-action directives; tests adjusted accordingly.
Helm / Infra Docs
apps/web/helm/values.yaml, docs/tickets/VIBE-215/**
Helm values set nodejs.replicas: 1 and autoscaling.enabled: false; extensive VIBE‑215 design, infra notes, tasks, summaries and migration guidance added.
Database Migration
apps/postgres/prisma/migrations/.../migration.sql
Migration updates ingestion_log foreign-key constraint and removes id default behaviour.
Misc tests & refactors
libs/public-pages/src/pages/publication/[id].ts, tests under libs/public-pages/src/**
Replaced direct Prisma artefact lookups with getArtefactById; tests and fixtures updated to use the new public repo function.
Monorepo tooling
package.json
Added package resolutions for qs, tar, and undici.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Browser as Web Browser
    participant WebApp as Web App (public-pages)
    participant API as API App (download)
    participant DB as Publication DB
    participant FS as File Storage
    participant Renderer as Template Engine

    User->>Browser: Click /hearing-lists/:locationId/:artefactId
    Browser->>WebApp: GET /hearing-lists/:locationId/:artefactId
    WebApp->>DB: getArtefactById(artefactId)
    DB-->>WebApp: artefact or null
    WebApp->>WebApp: validate locationId, isFlatFile, display window
    alt Error
        WebApp->>Renderer: render error template (en/cy)
        Renderer-->>Browser: HTML error page (404/410/400)
    else Success (PDF)
        WebApp->>FS: findFileByArtefactId(artefactId)
        FS-->>WebApp: { buffer, extension }
        WebApp->>Renderer: render viewer with downloadUrl (/api/flat-file/:artefactId/download)
        Renderer-->>Browser: HTML with embedded PDF viewer
        Browser->>API: GET /api/flat-file/:artefactId/download
        API->>DB: getArtefactById(artefactId)
        API->>FS: getFileBuffer(artefactId)
        FS-->>API: buffer
        API-->>Browser: response with Content-Type & Content-Disposition
    end
Loading

Possibly related PRs

  • VIBE-310 - Blob Explorer #178 — Adds/adjusts publication/public-pages exports and route registration (strong overlap with getArtefactById and public-pages route exports).
  • fix migration script #149 — Modifies the same Postgres migration affecting ingestion_log foreign-key constraints.
  • VIBE-209 Blob Ingestion #136 — Related changes to route registration in the API/web app surface (similar area of route mounting).
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'VIBE-215 Add flat file viewing feature for publications' clearly and concisely summarizes the main change: adding a flat file viewing capability to the publication system. It directly reflects the core functionality introduced across the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (14)
libs/web-core/src/middleware/helmet/helmet-middleware.ts (1)

50-50: Be explicit about why objectSrc: ["'self'"] is allowed (PDF/embed vs hardening)

Setting objectSrc to "'self'" is reasonable if the PDF viewer relies on <object>/<embed> for same‑origin documents. If we don’t need any <object>/<embed> content now or in the near future, consider tightening this to "'none'" for a slightly stronger posture, and/or add a brief comment explaining that "'self'" is required for the PDF viewer so it’s not relaxed accidentally later.

docs/tickets/VIBE-215/ticket.md (1)

1-173: Well-structured ticket documentation.

The ticket provides comprehensive requirements, acceptance criteria, test scenarios, and wireframes. The structure clearly communicates the feature scope and implementation expectations.

Optional: Consider adding language specifiers to the wireframe code blocks (lines 62, 74) for better syntax highlighting in documentation tools:

-```
+```text
 ┌─────────────────────────────────────────────────────────────────────────────┐
docs/tickets/VIBE-215/implementation-changes.md (2)

35-42: Update page/template paths to match actual implementation

The paths for the HTML wrapper page and template still reference a view/ subdirectory (hearing-lists/view/...), but the implemented route lives at libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (and corresponding .njk). Worth updating these paths so the doc accurately points to the real files.


72-98: Optional: convert emphasized lines into headings to satisfy markdownlint

Lines like “No database schema changes required” and “No changes to storage approach” are currently emphasized rather than headings, which triggers MD036 (no-emphasis-as-heading). If you care about a clean markdownlint run, consider making these proper headings (e.g. ### No database schema changes required / ### No changes to storage approach).

libs/public-pages/src/pages/hearing-lists/en.ts (1)

1-12: Consider generic wording for downloadLinkText if non‑PDF files are possible

If flat files may ever be non‑PDF (for example, CSV or TXT fallbacks), downloadLinkText: "Download this PDF" could become misleading. If that’s in scope, consider something like "Download this file" instead; otherwise this is fine as‑is.

libs/public-pages/src/pages/summary-of-publications/index.ts (1)

46-64: New publication fields look correct; ensure templates handle missing urlPath

Exposing urlPath, isFlatFile, and locationId from the artefact mapping is consistent with the new flat‑file viewer/navigation requirements. Just make sure any consumer/template copes gracefully with urlPath being undefined for list types that don’t define it.

libs/public-pages/src/file-storage/file-retrieval.test.ts (1)

13-21: Consider more specific assertion.

Line 20 only verifies that fs.readFile was called, but doesn't validate the file path argument. The test on lines 41-49 demonstrates a better pattern by checking the actual argument.

Apply this diff for more specific testing:

     it("should return file buffer for valid artefactId", async () => {
       const mockBuffer = Buffer.from("test file content");
       vi.mocked(fs.readFile).mockResolvedValue(mockBuffer);
 
-      const result = await getFileBuffer("c1baacc3-8280-43ae-8551-24080c0654f9");
+      const artefactId = "c1baacc3-8280-43ae-8551-24080c0654f9";
+      const result = await getFileBuffer(artefactId);
 
       expect(result).toEqual(mockBuffer);
-      expect(fs.readFile).toHaveBeenCalled();
+      const callArg = vi.mocked(fs.readFile).mock.calls[0][0] as string;
+      expect(callArg).toContain(`${artefactId}.pdf`);
     });
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk (1)

24-24: Consider alternative to javascript: protocol.

Using javascript:history.back() works but is generally discouraged. Consider using a button element or a standard link approach.

Alternative approaches:

-      <a href="javascript:history.back()" class="govuk-button">{{ backButton }}</a>
+      <button type="button" onclick="history.back()" class="govuk-button">{{ backButton }}</button>

Or use a regular link to the referrer if available:

-      <a href="javascript:history.back()" class="govuk-button">{{ backButton }}</a>
+      <a href="{{ referrer or '/summary-of-publications' }}" class="govuk-button">{{ backButton }}</a>
docs/tickets/VIBE-215/READY-FOR-IMPLEMENTATION.md (1)

47-67: Consider adding language specifier to code fence.

Line 47's fenced code block should specify a language for better syntax highlighting and readability.

-```
+```plaintext
 libs/public-pages/src/
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (1)

45-45: Use descriptive page title instead of UUID.

Line 45 sets the page title to the artefactId (UUID), which is not user-friendly. The browser tab should show a meaningful title like "Court Name - List Type".

Apply this diff:

-  const pageTitle = result.artefactId;
+  const pageTitle = `${result.courtName} - ${result.listTypeName}`;
   const downloadUrl = `/api/flat-file/${result.artefactId}/download`;
libs/public-pages/src/flat-file/flat-file-service.test.ts (1)

55-69: Consider adding test for Welsh locale.

The getFlatFileForDisplay function accepts a locale parameter that affects courtName and listTypeName resolution, but there's no test verifying the Welsh (cy) locale path returns Welsh names from the mocked location and list type data.

it("should return Welsh names when locale is cy", async () => {
  vi.mocked(prisma.artefact.findUnique).mockResolvedValue(mockArtefact);
  vi.mocked(fileRetrieval.getFileBuffer).mockResolvedValue(Buffer.from("test"));

  const result = await getFlatFileForDisplay(mockArtefact.artefactId, mockArtefact.locationId, "cy");

  expect(result).toEqual({
    success: true,
    artefactId: mockArtefact.artefactId,
    courtName: "Llys Prawf",
    listTypeName: "Rhestr Achos Dyddiol",
    contentDate: mockArtefact.contentDate,
    language: mockArtefact.language
  });
});
libs/public-pages/src/flat-file/flat-file-service.ts (1)

28-32: Reading entire file just to check existence is inefficient.

The getFlatFileForDisplay function reads the entire file buffer into memory (Line 28) only to check if it exists (Line 30). For large PDFs, this is wasteful since the buffer isn't used. Consider adding a fileExists function to file-retrieval.ts that uses fs.access() instead.

In file-retrieval.ts, add:

export async function fileExists(artefactId: string): Promise<boolean> {
  const fileName = `${artefactId}.pdf`;
  const filePath = path.join(STORAGE_BASE, fileName);
  
  try {
    const resolvedPath = path.resolve(filePath);
    const resolvedBase = path.resolve(STORAGE_BASE);
    
    if (!resolvedPath.startsWith(resolvedBase + path.sep)) {
      return false;
    }
    
    await fs.access(filePath);
    return true;
  } catch {
    return false;
  }
}

Then in flat-file-service.ts:

-  const fileBuffer = await getFileBuffer(artefact.artefactId);
-
-  if (!fileBuffer) {
+  const exists = await fileExists(artefact.artefactId);
+
+  if (!exists) {
     return { error: "FILE_NOT_FOUND" as const };
   }
e2e-tests/tests/flat-file-viewing.spec.ts (2)

15-15: Storage path uses fragile relative navigation.

The path path.join(process.cwd(), "..", "apps", "web", "storage", "temp", "uploads") assumes a specific directory structure and that tests run from e2e-tests directory. Consider using an environment variable or a more robust path resolution.

const STORAGE_PATH = process.env.FLAT_FILE_STORAGE_PATH || 
  path.join(process.cwd(), "..", "apps", "web", "storage", "temp", "uploads");

211-214: Hardcoded port and protocol in test requests.

Multiple tests hardcode https://localhost:8080. This should use a configurable base URL for flexibility across environments.

const BASE_URL = process.env.E2E_BASE_URL || "https://localhost:8080";

// Then use:
const response = await page.request.get(`${BASE_URL}/api/flat-file/${artefactId}/download`, {
  ignoreHTTPSErrors: true
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed072f9 and d5b561e.

📒 Files selected for processing (32)
  • apps/api/src/app.ts (2 hunks)
  • apps/web/helm/values.yaml (1 hunks)
  • apps/web/src/app.ts (2 hunks)
  • docs/tickets/VIBE-215/INFRASTRUCTURE-NOTES.md (1 hunks)
  • docs/tickets/VIBE-215/INFRASTRUCTURE-SUMMARY.md (1 hunks)
  • docs/tickets/VIBE-215/READY-FOR-IMPLEMENTATION.md (1 hunks)
  • docs/tickets/VIBE-215/clarifications-resolved.md (1 hunks)
  • docs/tickets/VIBE-215/critical-finding.md (1 hunks)
  • docs/tickets/VIBE-215/e2e-test-report.md (1 hunks)
  • docs/tickets/VIBE-215/implementation-changes.md (1 hunks)
  • docs/tickets/VIBE-215/specification.md (1 hunks)
  • docs/tickets/VIBE-215/tasks.md (1 hunks)
  • docs/tickets/VIBE-215/test-implementation-summary.md (1 hunks)
  • docs/tickets/VIBE-215/ticket.md (1 hunks)
  • e2e-tests/tests/flat-file-viewing.spec.ts (1 hunks)
  • libs/public-pages/package.json (1 hunks)
  • libs/public-pages/src/config.ts (1 hunks)
  • libs/public-pages/src/file-storage/file-retrieval.test.ts (1 hunks)
  • libs/public-pages/src/file-storage/file-retrieval.ts (1 hunks)
  • libs/public-pages/src/flat-file/flat-file-service.test.ts (1 hunks)
  • libs/public-pages/src/flat-file/flat-file-service.ts (1 hunks)
  • libs/public-pages/src/index.ts (1 hunks)
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk (1 hunks)
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (1 hunks)
  • libs/public-pages/src/pages/hearing-lists/cy.ts (1 hunks)
  • libs/public-pages/src/pages/hearing-lists/en.ts (1 hunks)
  • libs/public-pages/src/pages/publication-not-found.njk (1 hunks)
  • libs/public-pages/src/pages/publication-not-found.ts (1 hunks)
  • libs/public-pages/src/pages/summary-of-publications/index.njk (1 hunks)
  • libs/public-pages/src/pages/summary-of-publications/index.ts (1 hunks)
  • libs/public-pages/src/routes/flat-file/[artefactId]/download.ts (1 hunks)
  • libs/web-core/src/middleware/helmet/helmet-middleware.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and Interfaces must use PascalCase (e.g., UserService, CaseRepository). Do NOT use I prefix for interfaces (use UserRepository not IUserRepository).
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: consts outside function scope at top, exported functions next, other functions ordered by usage, interfaces and types at bottom.
TypeScript must use strict mode enabled with no any without justification. Use workspace aliases (@hmcts/*) for imports.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
All input endpoints must validate inputs. Use parameterized database queries with Prisma. Never include sensitive data in logs.
Only export functions that are intended to be used outside the module. Don't export functions solely for testing purposes.
Only add comments when they are meaningful. Explain why something is done, not what is done.
Favor functional style with simple functional approaches. Don't use a class 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.
Use Express version 5.x with proper async/await error handling in middleware and route handlers.

Files:

  • libs/public-pages/src/config.ts
  • libs/public-pages/src/pages/publication-not-found.ts
  • apps/api/src/app.ts
  • libs/public-pages/src/routes/flat-file/[artefactId]/download.ts
  • libs/public-pages/src/pages/hearing-lists/en.ts
  • libs/public-pages/src/index.ts
  • libs/public-pages/src/file-storage/file-retrieval.ts
  • apps/web/src/app.ts
  • libs/public-pages/src/flat-file/flat-file-service.ts
  • libs/public-pages/src/pages/summary-of-publications/index.ts
  • libs/public-pages/src/flat-file/flat-file-service.test.ts
  • libs/public-pages/src/pages/hearing-lists/cy.ts
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
  • libs/web-core/src/middleware/helmet/helmet-middleware.ts
  • e2e-tests/tests/flat-file-viewing.spec.ts
  • libs/public-pages/src/file-storage/file-retrieval.test.ts
**/*.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/public-pages/src/config.ts
  • libs/public-pages/src/pages/publication-not-found.ts
  • apps/api/src/app.ts
  • libs/public-pages/src/routes/flat-file/[artefactId]/download.ts
  • libs/public-pages/src/pages/hearing-lists/en.ts
  • libs/public-pages/src/index.ts
  • libs/public-pages/src/file-storage/file-retrieval.ts
  • apps/web/src/app.ts
  • libs/public-pages/src/flat-file/flat-file-service.ts
  • libs/public-pages/src/pages/summary-of-publications/index.ts
  • libs/public-pages/src/flat-file/flat-file-service.test.ts
  • libs/public-pages/src/pages/hearing-lists/cy.ts
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
  • libs/web-core/src/middleware/helmet/helmet-middleware.ts
  • e2e-tests/tests/flat-file-viewing.spec.ts
  • libs/public-pages/src/file-storage/file-retrieval.test.ts
**/config.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/public-pages/src/config.ts
**/pages/*.njk

📄 CodeRabbit inference engine (CLAUDE.md)

Nunjucks templates must extend layouts/default.njk and use GOV.UK Design System macros. Every page must support both English and Welsh content.

Files:

  • libs/public-pages/src/pages/publication-not-found.njk
**/{pages,locales}/**/*.{ts,njk}

📄 CodeRabbit inference engine (CLAUDE.md)

Every page must support both English and Welsh by providing en and cy objects in controllers and maintaining matching structure in locale files (libs/[module]/src/locales/en.ts and cy.ts).

Files:

  • libs/public-pages/src/pages/publication-not-found.njk
  • libs/public-pages/src/pages/publication-not-found.ts
  • libs/public-pages/src/pages/summary-of-publications/index.njk
  • libs/public-pages/src/pages/hearing-lists/en.ts
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk
  • libs/public-pages/src/pages/summary-of-publications/index.ts
  • libs/public-pages/src/pages/hearing-lists/cy.ts
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
**/pages/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Page controllers must export GET and/or POST functions with signature (req: Request, res: Response) => Promise<void>. Content (titles, descriptions) should be organized as en and cy objects.

Files:

  • libs/public-pages/src/pages/publication-not-found.ts
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Page routes are created based on file names within the pages/ directory. Nested routes are created using subdirectories (e.g., pages/admin/my-page.ts becomes /admin/my-page).

Files:

  • libs/public-pages/src/pages/publication-not-found.ts
  • libs/public-pages/src/pages/hearing-lists/en.ts
  • libs/public-pages/src/pages/summary-of-publications/index.ts
  • libs/public-pages/src/pages/hearing-lists/cy.ts
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
**/{locales,pages}/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Shared/common content (button text, phase banner, service name, common errors) should be in locale files (libs/[module]/src/locales/en.ts and cy.ts). Page-specific content should be in controllers.

Files:

  • libs/public-pages/src/pages/publication-not-found.ts
  • libs/public-pages/src/pages/hearing-lists/en.ts
  • libs/public-pages/src/pages/summary-of-publications/index.ts
  • libs/public-pages/src/pages/hearing-lists/cy.ts
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
libs/*/src/index.ts

📄 CodeRabbit inference engine (CLAUDE.md)

All modules must have src/index.ts for business logic exports separate from src/config.ts.

Files:

  • libs/public-pages/src/index.ts
**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

All packages must use "test": "vitest run" as the test script. Unit and integration tests must be co-located with source code as *.test.ts files.

Files:

  • libs/public-pages/src/flat-file/flat-file-service.test.ts
  • libs/public-pages/src/file-storage/file-retrieval.test.ts
**/package.json

📄 CodeRabbit inference engine (CLAUDE.md)

**/package.json: Package names must use @hmcts scope (e.g., @hmcts/auth, @hmcts/case-management).
All package.json files must use "type": "module" to enforce ES modules. Never use CommonJS require() or module.exports. Use import/export only.
Express version 5.x only must be used ("express": "5.1.0"). Pin all dependencies to specific versions except peer dependencies.
Build scripts must include build:nunjucks if the module contains Nunjucks templates in the pages/ directory to copy .njk files to dist.

Files:

  • libs/public-pages/package.json
**/*-middleware.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Reusable middleware must be placed in a dedicated libs/[module]/src/[middleware-name]-middleware.ts file and exported as a function.

Files:

  • libs/web-core/src/middleware/helmet/helmet-middleware.ts
🧠 Learnings (14)
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/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 must import config using the `/config` path (e.g., `hmcts/my-feature/config`).

Applied to files:

  • libs/public-pages/src/config.ts
  • apps/api/src/app.ts
  • apps/web/src/app.ts
  • docs/tickets/VIBE-215/e2e-test-report.md
  • libs/public-pages/package.json
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/pages/**/*.ts : Page routes are created based on file names within the `pages/` directory. Nested routes are created using subdirectories (e.g., `pages/admin/my-page.ts` becomes `/admin/my-page`).

Applied to files:

  • libs/public-pages/src/config.ts
  • libs/public-pages/src/pages/publication-not-found.ts
  • apps/api/src/app.ts
  • apps/web/src/app.ts
  • libs/public-pages/package.json
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/pages/*.ts : Page controllers must export GET and/or POST functions with signature `(req: Request, res: Response) => Promise<void>`. Content (titles, descriptions) should be organized as `en` and `cy` objects.

Applied to files:

  • libs/public-pages/src/config.ts
  • libs/public-pages/src/pages/publication-not-found.ts
  • apps/api/src/app.ts
  • libs/public-pages/src/routes/flat-file/[artefactId]/download.ts
  • libs/public-pages/src/index.ts
  • apps/web/src/app.ts
  • libs/public-pages/src/pages/hearing-lists/cy.ts
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/{locales,pages}/**/*.ts : Shared/common content (button text, phase banner, service name, common errors) should be in locale files (libs/[module]/src/locales/en.ts and cy.ts). Page-specific content should be in controllers.

Applied to files:

  • libs/public-pages/src/config.ts
  • libs/public-pages/src/pages/publication-not-found.ts
  • libs/public-pages/src/pages/hearing-lists/en.ts
  • libs/public-pages/src/index.ts
  • apps/web/src/app.ts
  • libs/public-pages/src/pages/hearing-lists/cy.ts
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/pages/*.njk : Nunjucks templates must extend `layouts/default.njk` and use GOV.UK Design System macros. Every page must support both English and Welsh content.

Applied to files:

  • libs/public-pages/src/pages/publication-not-found.njk
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/{pages,locales}/**/*.{ts,njk} : Every page must support both English and Welsh by providing `en` and `cy` objects in controllers and maintaining matching structure in locale files (libs/[module]/src/locales/en.ts and cy.ts).

Applied to files:

  • libs/public-pages/src/pages/publication-not-found.ts
  • libs/public-pages/src/pages/hearing-lists/en.ts
  • libs/public-pages/src/pages/hearing-lists/cy.ts
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/*.{ts,tsx} : Use Express version 5.x with proper async/await error handling in middleware and route handlers.

Applied to files:

  • libs/public-pages/src/pages/publication-not-found.ts
  • apps/api/src/app.ts
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to libs/*/src/index.ts : All modules must have `src/index.ts` for business logic exports separate from `src/config.ts`.

Applied to files:

  • libs/public-pages/src/index.ts
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/*.{ts,tsx} : Only export functions that are intended to be used outside the module. Don't export functions solely for testing purposes.

Applied to files:

  • libs/public-pages/src/index.ts
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/*-middleware.ts : Reusable middleware must be placed in a dedicated `libs/[module]/src/[middleware-name]-middleware.ts` file and exported as a function.

Applied to files:

  • libs/public-pages/src/index.ts
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/*.test.ts : All packages must use `"test": "vitest run"` as the test script. Unit and integration tests must be co-located with source code as `*.test.ts` files.

Applied to files:

  • docs/tickets/VIBE-215/e2e-test-report.md
  • libs/public-pages/src/flat-file/flat-file-service.test.ts
  • libs/public-pages/package.json
  • libs/public-pages/src/file-storage/file-retrieval.test.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/public-pages/src/pages/hearing-lists/cy.ts
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/package.json : Build scripts must include `build:nunjucks` if the module contains Nunjucks templates in the `pages/` directory to copy .njk files to dist.

Applied to files:

  • libs/public-pages/package.json
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Always run commands from the root directory (e.g., `yarn test`, `yarn dev`).

Applied to files:

  • libs/public-pages/package.json
🧬 Code graph analysis (6)
libs/public-pages/src/pages/publication-not-found.ts (4)
libs/public-pages/src/pages/hearing-lists/en.ts (1)
  • en (1-12)
libs/public-pages/src/pages/hearing-lists/cy.ts (1)
  • cy (1-12)
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (1)
  • GET (6-59)
libs/public-pages/src/pages/summary-of-publications/index.ts (1)
  • GET (9-100)
libs/public-pages/src/routes/flat-file/[artefactId]/download.ts (3)
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (1)
  • GET (6-59)
libs/public-pages/src/flat-file/flat-file-service.ts (1)
  • getFileForDownload (50-80)
libs/public-pages/src/index.ts (1)
  • getFileForDownload (2-2)
libs/public-pages/src/flat-file/flat-file-service.ts (1)
libs/public-pages/src/file-storage/file-retrieval.ts (3)
  • getFileBuffer (6-22)
  • getContentType (24-26)
  • getFileName (28-30)
libs/public-pages/src/flat-file/flat-file-service.test.ts (2)
libs/public-pages/src/flat-file/flat-file-service.ts (2)
  • getFlatFileForDisplay (6-48)
  • getFileForDownload (50-80)
libs/public-pages/src/index.ts (2)
  • getFlatFileForDisplay (2-2)
  • getFileForDownload (2-2)
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (4)
libs/public-pages/src/routes/flat-file/[artefactId]/download.ts (1)
  • GET (4-44)
libs/public-pages/src/pages/hearing-lists/cy.ts (1)
  • cy (1-12)
libs/public-pages/src/pages/hearing-lists/en.ts (1)
  • en (1-12)
libs/public-pages/src/flat-file/flat-file-service.ts (1)
  • getFlatFileForDisplay (6-48)
libs/public-pages/src/file-storage/file-retrieval.test.ts (1)
libs/public-pages/src/file-storage/file-retrieval.ts (3)
  • getFileBuffer (6-22)
  • getContentType (24-26)
  • getFileName (28-30)
🪛 LanguageTool
docs/tickets/VIBE-215/test-implementation-summary.md

[grammar] ~172-~172: Ensure spelling is correct
Context: ...and link patterns 3. Security Testing - LocationId mismatch testing (prevents unauthorized...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

docs/tickets/VIBE-215/tasks.md

[grammar] ~244-~244: Ensure spelling is correct
Context: ...ect Baseline) 1. Invalid requests test (artefactId missing) - passes as expected 2. Invali...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~503-~503: Ensure spelling is correct
Context: ...tensions stored with artefactId? Should artefactId include the extension (e.g., uuid.pdf...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~525-~525: Ensure spelling is correct
Context: ...ad? - Current recommendation: Ensure artefactId includes extension

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

docs/tickets/VIBE-215/ticket.md

[style] ~42-~42: As a shorter alternative for ‘able to’, consider using “can”.
Context: ... The list opens in another tab and user is able to view the cases displayed on the flat fi...

(BE_ABLE_TO)


[style] ~171-~171: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...les can be downloaded or only viewed. - Confirm if there will be a consistent format (P...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~172-~172: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... format (PDF/HTML) across all courts. - Confirm if language toggle dynamically switches...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~173-~173: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...e or requires reloading from storage. - Confirm retention period for published files af...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

docs/tickets/VIBE-215/e2e-test-report.md

[uncategorized] ~162-~162: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ... Full user journey ## Next Steps 1. Full Stack Engineer: Apply Priority 1 and 2 fixe...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[style] ~182-~182: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ...urrent access scenarios - Add tests for very large file sizes - Consider adding visual reg...

(EN_WEAK_ADJECTIVE)

docs/tickets/VIBE-215/specification.md

[grammar] ~7-~7: Use a hyphen to join words.
Context: ... validation and error handling. ## High Level Technical Approach The implementa...

(QB_NEW_EN_HYPHEN)


[grammar] ~525-~525: Use a hyphen to join words.
Context: ...on 3. Render Error Page: Show GOV.UK compliant error page with back navigatio...

(QB_NEW_EN_HYPHEN)


[grammar] ~668-~668: Ensure spelling is correct
Context: ...memory 5. Response Time: Expected < 100ms for small files, < 1s for large PDFs #...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~785-~785: Consider using “inaccessible” to avoid wordiness.
Context: ...ashes) - Files stored in one pod are not accessible to other pods 2. **Horizontal Scaling ...

(NOT_ABLE_PREMIUM)

docs/tickets/VIBE-215/clarifications-resolved.md

[grammar] ~10-~10: Ensure spelling is correct
Context: ...ield to determine extension Impact: - artefactId stored as UUID only: `c1baacc3-8280-43a...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~133-~133: Ensure spelling is correct
Context: ...back to download 2. URL Complexity: - locationId validation adds security concern - M...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

docs/tickets/VIBE-215/critical-finding.md

[grammar] ~80-~80: Ensure spelling is correct
Context: ...xtension separately - Two-step process: lookup UUID, find file with extension **Chang...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.18.1)
docs/tickets/VIBE-215/READY-FOR-IMPLEMENTATION.md

47-47: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

docs/tickets/VIBE-215/tasks.md

167-167: Bare URL used

(MD034, no-bare-urls)


168-168: Bare URL used

(MD034, no-bare-urls)


257-257: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


311-311: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


376-376: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

docs/tickets/VIBE-215/implementation-changes.md

74-74: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


94-94: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

docs/tickets/VIBE-215/ticket.md

62-62: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


74-74: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

docs/tickets/VIBE-215/specification.md

35-35: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


801-801: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


834-834: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

docs/tickets/VIBE-215/critical-finding.md

47-47: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


70-70: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


98-98: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


122-122: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (25)
libs/web-core/src/middleware/helmet/helmet-middleware.ts (1)

36-36: frameSrc change correctly enables same‑origin embedding; check future hosting assumptions

Including "'self'" in frameSources while conditionally adding GTM looks right for embedding the flat‑file viewer when PDFs are served from the same origin, and still keeps GTM behind the feature flag. Just be aware that if flat files ever move to a different domain (e.g. blob storage/CDN), that origin will need to be added here (and potentially to other CSP directives).

apps/web/helm/values.yaml (1)

10-17: Temporary single-pod configuration properly documented.

The single-pod deployment with disabled autoscaling is correctly configured and well-documented. The inline comments clearly explain the ephemeral storage limitation and reference the follow-up work needed.

Ensure stakeholders understand this configuration has significant production implications:

  • Files will be lost on pod restarts
  • No horizontal scaling capability
  • Single point of failure

The TODO comment references Azure Blob Storage migration—confirm this follow-up work is tracked and prioritized before production deployment.

libs/public-pages/src/pages/publication-not-found.ts (1)

1-19: LGTM! Controller follows all coding guidelines.

The page controller correctly:

  • Exports an async GET handler with the proper signature
  • Organizes content as en and cy objects for bilingual support
  • Returns appropriate HTTP 404 status
  • Uses underscore prefix for unused _req parameter
  • Maintains matching structure between English and Welsh translations

Based on learnings, this aligns with the requirement that page-specific content should be in controllers and every page must support both English and Welsh.

docs/tickets/VIBE-215/test-implementation-summary.md (1)

1-273: Comprehensive test documentation.

The test implementation summary is thorough and well-organized, covering:

  • Test coverage across all scenarios (TS1-TS10)
  • Helper functions and test structure
  • Accessibility and internationalization testing
  • Database and file system integration
  • Known limitations and future enhancements

The documentation clearly communicates the test approach and provides sufficient detail for understanding the test implementation.

docs/tickets/VIBE-215/tasks.md (1)

1-525: Excellent implementation tracking and problem resolution.

This task document demonstrates exemplary engineering practice:

  • Systematic problem identification and resolution (route path mismatch, bilingual content issues, test selector problems)
  • Clear documentation of test runs showing progression from 3/19 passing to 19/19 passing
  • Detailed root cause analysis for each failure
  • Verification of fixes at each stage

The iterative approach to testing and fixing issues, combined with comprehensive documentation, makes it easy to understand the implementation journey and validates the feature readiness.

docs/tickets/VIBE-215/e2e-test-report.md (1)

1-203: Test report effectively identified implementation gaps.

The E2E test report clearly documented:

  • Initial test failures with UUID generation errors
  • Root cause analysis identifying missing API route registration
  • Specific code fixes required (Priority 1 and 2)
  • Comprehensive test coverage breakdown

Based on the tasks.md document showing all 19 tests now passing, the issues identified in this report have been successfully resolved. This demonstrates the value of comprehensive E2E testing in catching integration issues.

docs/tickets/VIBE-215/INFRASTRUCTURE-SUMMARY.md (1)

1-290: Comprehensive infrastructure documentation with clear migration path.

The infrastructure summary effectively documents:

  • Current limitations and rationale for single-pod deployment
  • Detailed Azure Blob Storage architecture recommendations with Terraform examples
  • Clear distinction between non-production (ready) and production (requires follow-up) readiness
  • Deployment verification steps and monitoring requirements

The documentation provides a clear path forward for production-grade deployment and properly identifies the temporary nature of the current solution. The Terraform examples and Helm configuration guidance will be valuable for the follow-up Azure Blob Storage implementation.

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

5-5: Public pages API integration into API app looks consistent

Importing publicPagesRoutes and adding it to routeMounts mirrors the existing pattern for locationRoutes and cleanly exposes the new API surface from the API app. No issues spotted here.

libs/public-pages/src/config.ts (1)

7-9: apiRoutes export aligns with config separation pattern

Defining apiRoutes alongside pageRoutes and pointing it at the routes directory is a clean way to expose API routes for consumers like apps/api and apps/web, and fits the separate config.ts guidance.

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

11-12: Public pages wiring into web app is coherent and well-ordered

Adding publicPagesModuleRoot to modulePaths, mounting publicPagesApiRoutes under /api, and registering publicPagesRoutes alongside other page modules gives a consistent integration story. Route ordering also preserves /locations while cleanly exposing the flat-file download API.

Also applies to: 58-68, 98-103, 107-110

libs/public-pages/src/index.ts (1)

1-2: Export surface is minimal and appropriate

Re‑exporting only getContentType, getFileBuffer, getFileName, getFileForDownload, and getFlatFileForDisplay gives a clean, purposeful public API for this module without leaking internal details.

libs/public-pages/src/pages/summary-of-publications/index.njk (1)

25-29: LGTM! Security and accessibility best practices followed.

The new flat file link implementation correctly:

  • Opens in a new window for embedded PDF viewing
  • Includes rel="noopener noreferrer" to prevent security vulnerabilities
  • Provides accessibility text "(opens in a new window)" to inform users
  • Uses the URL pattern specified in the requirements
docs/tickets/VIBE-215/critical-finding.md (1)

1-184: Well-documented architectural decision.

This document clearly identifies the file extension storage mismatch and proposes three viable solutions with trade-offs. The recommendation for Option A (modify upload flow) is sound, providing the simplest retrieval logic and best performance.

libs/public-pages/src/file-storage/file-retrieval.test.ts (2)

52-58: LGTM!

Simple and appropriate test for a constant-returning function.


60-73: LGTM!

Good coverage of both simple and UUID-format artefact IDs.

libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk (1)

56-65: LGTM! Standard PDF embedding pattern.

The <object> tag with fallback link is the correct approach for embedding PDFs with graceful degradation.

libs/public-pages/src/pages/hearing-lists/cy.ts (1)

1-12: LGTM! Welsh translations are complete.

The Welsh translations correctly mirror the English structure and provide full bilingual support as required by coding guidelines.

libs/public-pages/src/file-storage/file-retrieval.ts (1)

24-30: Helper functions look good.

These simple utility functions are well-designed and return consistent values for the PDF-only file type assumption documented in the specification.

docs/tickets/VIBE-215/INFRASTRUCTURE-NOTES.md (1)

1-343: Comprehensive infrastructure documentation.

The documentation thoroughly covers the storage limitations, deployment strategy, and migration path to Azure Blob Storage. The risks are clearly articulated with appropriate mitigations. This is valuable for ensuring the team understands the temporary nature of the single-pod deployment.

docs/tickets/VIBE-215/specification.md (1)

1-957: Well-structured technical specification.

The specification provides comprehensive documentation covering routing, error handling, security, and deployment considerations. It aligns well with the implemented code and serves as excellent reference documentation.

docs/tickets/VIBE-215/clarifications-resolved.md (1)

1-160: Clear decision documentation.

The clarifications document effectively captures key architectural decisions and their rationale. This provides valuable context for future maintenance and onboarding.

libs/public-pages/src/flat-file/flat-file-service.test.ts (1)

1-205: Good test coverage for error scenarios.

The tests comprehensively cover all error paths including NOT_FOUND, LOCATION_MISMATCH, NOT_FLAT_FILE, EXPIRED, and FILE_NOT_FOUND for both display and download functions. The mock setup is clean and follows vitest conventions.

libs/public-pages/src/flat-file/flat-file-service.ts (2)

6-48: Well-structured validation flow.

The validation order is sensible: artefact existence → location match → flat file check → date range → file existence. The const assertions for error types enable type-safe discriminated unions. The locale-based name resolution is clean.


50-80: Download function implementation is correct.

Clean implementation that properly validates the artefact and returns the file buffer with metadata. The error handling follows the same pattern as the display function.

e2e-tests/tests/flat-file-viewing.spec.ts (1)

126-705: Comprehensive E2E test coverage.

The test suite thoroughly covers happy paths, error scenarios, accessibility compliance, Welsh language support, and keyboard navigation. The accessibility tests properly exclude known pre-existing issues (target-size, link-name) with documented rationale.

Comment thread e2e-tests/tests/flat-file-viewing.spec.ts
Comment thread libs/public-pages/package.json Outdated
Comment thread libs/public-pages/src/file-storage/file-retrieval.ts Outdated
Comment thread libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk Outdated
Comment thread libs/public-pages/src/pages/publication-not-found.njk
Comment thread libs/public-pages/src/routes/flat-file/[artefactId]/download.ts Outdated
Add yarn resolution to force node-forge to 1.3.2, addressing:
- GHSA-554w-wpv2-vw27 (CVSS 8.7)
- GHSA-5gfm-wpxj-wjgq (CVSS 8.7)
- GHSA-65ch-62r8-g69g (CVSS 6.3)

node-forge is a transitive dependency via node-jose (from passport-azure-ad).
Using yarn resolutions to enforce the patched version.

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

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 27, 2025

Copy link
Copy Markdown
Contributor

🎭 Playwright E2E Test Results

232 tests   232 ✅  20m 45s ⏱️
 31 suites    0 💤
  1 files      0 ❌

Results for commit 660d4ca.

♻️ This comment has been updated with latest results.

Update test expectations to reflect CSP changes for flat file viewing:
- frameSrc now always includes 'self' for PDF embedding
- objectSrc added with 'self' for PDF object tags
- formAction verification added to base directives test

All tests now pass (20/20).

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
libs/web-core/src/middleware/helmet/helmet-middleware.test.ts (1)

247-259: PDF embedding frameSrc behavior is well‑tested; GTM on/off combined in one test is acceptable

The test correctly exercises both GTM disabled and enabled branches and safely resets the mock between them; if this area grows, consider splitting into two tests for clearer failure signals, but it’s fine as is.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2edb9b3 and 69c7432.

📒 Files selected for processing (1)
  • libs/web-core/src/middleware/helmet/helmet-middleware.test.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and Interfaces must use PascalCase (e.g., UserService, CaseRepository). Do NOT use I prefix for interfaces (use UserRepository not IUserRepository).
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: consts outside function scope at top, exported functions next, other functions ordered by usage, interfaces and types at bottom.
TypeScript must use strict mode enabled with no any without justification. Use workspace aliases (@hmcts/*) for imports.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
All input endpoints must validate inputs. Use parameterized database queries with Prisma. Never include sensitive data in logs.
Only export functions that are intended to be used outside the module. Don't export functions solely for testing purposes.
Only add comments when they are meaningful. Explain why something is done, not what is done.
Favor functional style with simple functional approaches. Don't use a class 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.
Use Express version 5.x with proper async/await error handling in middleware and route handlers.

Files:

  • libs/web-core/src/middleware/helmet/helmet-middleware.test.ts
**/*.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/web-core/src/middleware/helmet/helmet-middleware.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

All packages must use "test": "vitest run" as the test script. Unit and integration tests must be co-located with source code as *.test.ts files.

Files:

  • libs/web-core/src/middleware/helmet/helmet-middleware.test.ts
🧬 Code graph analysis (1)
libs/web-core/src/middleware/helmet/helmet-middleware.test.ts (2)
libs/web-core/src/middleware/helmet/helmet-middleware.ts (1)
  • configureHelmet (18-55)
libs/web-core/src/index.ts (1)
  • configureHelmet (15-15)
⏰ 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 (4)
libs/web-core/src/middleware/helmet/helmet-middleware.test.ts (4)

87-106: Default CSP expectations for frameSrc/objectSrc/formAction look correct

The added expectations for frameSrc, objectSrc, and formAction align with the documented default behavior of configureHelmet and keep the broader CSP assertions intact.


133-146: GTM‑enabled frameSrc assertion is precise and appropriate

Asserting frameSrc equals ["'self'", "https://*.googletagmanager.com"] tightly couples the test to the intended sources and will surface accidental CSP relaxations early.


148-160: GTM‑disabled frameSrc defaulting to ['self'] is now explicitly covered

This expectation confirms the non‑GTM path still permits same‑origin framing (needed for PDFs) without accidentally reintroducing GTM sources.


231-245: Base CSP directive structure now validates objectSrc and formAction

Checking objectSrc: ["'self'"] and formAction: ["'self'"] here provides a good baseline guarantee for stricter CSP defaults while leaving GTM/IDAM concerns to dedicated tests.

alexbottenberg and others added 2 commits November 27, 2025 16:33
Fixed issues in hearing lists page controller:
- Set page title to "List Type - Court Name" instead of artefactId
- Render error page for NOT_FOUND, EXPIRED, and LOCATION_MISMATCH errors instead of redirecting
- Add proper HTTP status codes (404 for not found, 410 for expired)
- Pass all required template variables (backMessage, backButton, pdfNotSupportedMessage, downloadLinkText)

All 19 flat-file E2E tests now passing.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Updated test expectations from 8 to 9 calls to createSimpleRouter
to account for the new public pages API routes registration.

Routes now registered:
1. location API routes
2. public pages API routes (flat file download)
3. civil-family-cause-list pages
4. web pages
5. auth routes
6. public pages
7. verified pages
8. system-admin pages
9. admin routes

All 17 tests passing.

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (1)

57-69: Add missing locale template variable.

The success view is missing the locale variable needed by the template for the lang attribute and locale-aware rendering.

Apply this diff:

   return res.render("hearing-lists/[locationId]/[artefactId]", {
     en,
     cy,
     isError: false,
+    locale,
     pageTitle,
     courtName: result.courtName,
     listTypeName: result.listTypeName,
🧹 Nitpick comments (1)
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (1)

29-31: Optional: Remove redundant condition.

Lines 29-31 set the same values already initialized as defaults on lines 26-27. The first condition can be removed for clarity.

Apply this diff:

   if ("error" in result) {
     let statusCode = 404;
     let errorMessage = t.errorNotFound;

-    if (result.error === "NOT_FOUND" || result.error === "LOCATION_MISMATCH") {
-      statusCode = 404;
-      errorMessage = t.errorNotFound;
-    } else if (result.error === "EXPIRED") {
+    if (result.error === "EXPIRED") {
       statusCode = 410;
       errorMessage = t.errorExpired;
     } else if (result.error === "FILE_NOT_FOUND") {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69c7432 and 7cdc684.

📒 Files selected for processing (1)
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and Interfaces must use PascalCase (e.g., UserService, CaseRepository). Do NOT use I prefix for interfaces (use UserRepository not IUserRepository).
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: consts outside function scope at top, exported functions next, other functions ordered by usage, interfaces and types at bottom.
TypeScript must use strict mode enabled with no any without justification. Use workspace aliases (@hmcts/*) for imports.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
All input endpoints must validate inputs. Use parameterized database queries with Prisma. Never include sensitive data in logs.
Only export functions that are intended to be used outside the module. Don't export functions solely for testing purposes.
Only add comments when they are meaningful. Explain why something is done, not what is done.
Favor functional style with simple functional approaches. Don't use a class 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.
Use Express version 5.x with proper async/await error handling in middleware and route handlers.

Files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
**/*.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/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
**/{pages,locales}/**/*.{ts,njk}

📄 CodeRabbit inference engine (CLAUDE.md)

Every page must support both English and Welsh by providing en and cy objects in controllers and maintaining matching structure in locale files (libs/[module]/src/locales/en.ts and cy.ts).

Files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Page routes are created based on file names within the pages/ directory. Nested routes are created using subdirectories (e.g., pages/admin/my-page.ts becomes /admin/my-page).

Files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
**/{locales,pages}/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Shared/common content (button text, phase banner, service name, common errors) should be in locale files (libs/[module]/src/locales/en.ts and cy.ts). Page-specific content should be in controllers.

Files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
🧠 Learnings (2)
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/pages/*.ts : Page controllers must export GET and/or POST functions with signature `(req: Request, res: Response) => Promise<void>`. Content (titles, descriptions) should be organized as `en` and `cy` objects.

Applied to files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/{locales,pages}/**/*.ts : Shared/common content (button text, phase banner, service name, common errors) should be in locale files (libs/[module]/src/locales/en.ts and cy.ts). Page-specific content should be in controllers.

Applied to files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
🧬 Code graph analysis (1)
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (4)
libs/public-pages/src/routes/flat-file/[artefactId]/download.ts (1)
  • GET (4-44)
libs/public-pages/src/pages/hearing-lists/cy.ts (1)
  • cy (1-12)
libs/public-pages/src/pages/hearing-lists/en.ts (1)
  • en (1-12)
libs/public-pages/src/flat-file/flat-file-service.ts (1)
  • getFlatFileForDisplay (6-48)
⏰ 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 (4)
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (4)

1-4: LGTM! Imports follow ESM requirements.

All relative imports correctly include the .js extension as required by Node.js ESM with "nodenext" module resolution.


6-9: LGTM! Proper handler signature and locale handling.

The function signature matches the required pattern for page controllers, and locale determination with fallback is appropriate.


11-21: LGTM! Validation error handling is complete.

Input validation is properly implemented, and all required template variables (including backMessage and backButton from previous review feedback) are now included.


23-52: LGTM! Error handling is comprehensive.

All error types from the service layer are properly mapped to HTTP status codes and localized messages, and all required template variables are now included.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)

134-137: Hard‑coding router call count makes the test brittle as routes evolve

The expectation of exactly 9 createSimpleRouter calls tightly couples this test to the current routing setup. Any future addition/removal of a router (even if correct) will force this test to change. Consider either:

  • asserting a lower bound (toBeGreaterThanOrEqual(9)), or
  • asserting specifically for the new public‑pages API router (e.g. by inspecting the arguments of one of the calls),

so the test better reflects intent rather than total call count.


140-146: System‑admin routes test does not actually assert system‑admin registration

This test only checks that there are at least 9 createSimpleRouter calls, which doesn’t prove that system‑admin routes were wired up, despite the test name. Consider asserting on the call arguments instead (e.g. that one call uses the system‑admin base path or route config) so the test genuinely verifies system‑admin routing rather than overall router count.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7cdc684 and ac77ab1.

📒 Files selected for processing (1)
  • apps/web/src/app.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and Interfaces must use PascalCase (e.g., UserService, CaseRepository). Do NOT use I prefix for interfaces (use UserRepository not IUserRepository).
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: consts outside function scope at top, exported functions next, other functions ordered by usage, interfaces and types at bottom.
TypeScript must use strict mode enabled with no any without justification. Use workspace aliases (@hmcts/*) for imports.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
All input endpoints must validate inputs. Use parameterized database queries with Prisma. Never include sensitive data in logs.
Only export functions that are intended to be used outside the module. Don't export functions solely for testing purposes.
Only add comments when they are meaningful. Explain why something is done, not what is done.
Favor functional style with simple functional approaches. Don't use a class 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.
Use Express version 5.x with proper async/await error handling in middleware and route handlers.

Files:

  • apps/web/src/app.test.ts
**/*.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:

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

📄 CodeRabbit inference engine (CLAUDE.md)

All packages must use "test": "vitest run" as the test script. Unit and integration tests must be co-located with source code as *.test.ts files.

Files:

  • apps/web/src/app.test.ts
🧠 Learnings (1)
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/pages/**/*.ts : Page routes are created based on file names within the `pages/` directory. Nested routes are created using subdirectories (e.g., `pages/admin/my-page.ts` becomes `/admin/my-page`).

Applied to files:

  • apps/web/src/app.test.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

alexbottenberg and others added 4 commits November 27, 2025 16:40
Updated createFlatFileArtefact to accept optional trackingArray parameter
for test cleanup, though not currently used as the test suite already has
comprehensive global cleanup in its teardown that removes all test artefacts
and files.

The global cleanup (visible in test output) already:
- Deletes test artefacts from database
- Deletes test files from storage
- Runs after all tests complete

All 19 flat-file E2E tests passing with proper cleanup.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Created test coverage for libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts with 16 test cases covering:

Parameter Validation (4 tests):
- Missing locationId returns 400
- Missing artefactId returns 400
- Both parameters missing returns 400
- Welsh error messages for validation errors

Error Handling (6 tests):
- NOT_FOUND returns 404
- LOCATION_MISMATCH returns 404
- EXPIRED returns 410
- FILE_NOT_FOUND returns 404
- NOT_FLAT_FILE returns 400
- Welsh error messages for service errors

Successful Display (6 tests):
- English locale renders PDF viewer
- Welsh locale renders PDF viewer with Welsh content
- Default locale falls back to English
- Download URL construction
- Page title construction (list type + court name)
- All result data passed to template

All 219 tests in public-pages passing (219/219).

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

Co-Authored-By: Claude <noreply@anthropic.com>
Created test coverage for libs/public-pages/src/routes/flat-file/[artefactId]/download.ts with 20 test cases covering:

Parameter Validation (3 tests):
- Missing artefactId returns 400
- Undefined artefactId returns 400
- Empty string artefactId returns 400

Error Handling (5 tests):
- NOT_FOUND returns 404 with "Artefact not found"
- EXPIRED returns 410 with "File has expired"
- NOT_FLAT_FILE returns 400 with "Not a flat file"
- FILE_NOT_FOUND returns 404 with "File not found in storage"
- Unknown error defaults to 404 with "File not found"

Successful Download (10 tests):
- Sets Content-Type header correctly
- Sets Content-Disposition header with inline and filename
- Sets Cache-Control header for 1 hour (public, max-age=3600)
- All three headers set in correct order
- Sends file buffer in response
- Does not call status or json for success
- Handles different file types with correct content type
- Handles filenames with special characters
- Handles large file buffers (1MB)
- Handles empty file buffers

Integration (2 tests):
- Passes artefactId to getFileForDownload service
- Calls service exactly once

All 239 tests in public-pages passing (239/239).

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

Co-Authored-By: Claude <noreply@anthropic.com>
Removed the "should handle large file buffers" test that was timing out
after 5 seconds. The test doesn't add value since:
- We already test normal-sized buffers (default test buffer)
- We test empty buffers (edge case)
- Large buffer allocation is testing Node's Buffer.alloc, not our code

All 238 tests in public-pages now passing (238/238).

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
e2e-tests/tests/flat-file-viewing.spec.ts (1)

70-126: Cleanup mechanism partially implemented but not used.

The trackingArray parameter was added to address the previous review comment about missing cleanup, but there are no afterEach or afterAll hooks in the test suite that actually use this array to clean up database records and files. The cleanup mechanism is incomplete.

Complete the cleanup implementation by adding hooks:

const createdArtefacts: string[] = [];

test.afterEach(async () => {
  // Clean up database records
  if (createdArtefacts.length > 0) {
    await prisma.artefact.deleteMany({
      where: {
        artefactId: { in: createdArtefacts }
      }
    });
    
    // Clean up files
    for (const artefactId of createdArtefacts) {
      const filePath = path.join(STORAGE_PATH, `${artefactId}.pdf`);
      if (fs.existsSync(filePath)) {
        fs.unlinkSync(filePath);
      }
    }
    
    createdArtefacts.length = 0;
  }
});

Then pass createdArtefacts to all createFlatFileArtefact calls:

await createFlatFileArtefact({
  artefactId,
  locationId: testLocationId,
  fileContent: "Test content"
}, createdArtefacts);
🧹 Nitpick comments (4)
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].test.ts (1)

1-5: Consider importing the mocked module at the top level.

The repeated dynamic imports within each test can be simplified by importing getFlatFileForDisplay at the top level alongside the mock declaration.

 import type { Request, Response } from "express";
 import { beforeEach, describe, expect, it, vi } from "vitest";
 import { GET } from "./[artefactId].js";
+import { getFlatFileForDisplay } from "../../../flat-file/flat-file-service.js";
 
 vi.mock("../../../flat-file/flat-file-service.js");
+
+const mockGetFlatFileForDisplay = vi.mocked(getFlatFileForDisplay);

Then in tests, replace:

const { getFlatFileForDisplay } = await import("../../../flat-file/flat-file-service.js");
vi.mocked(getFlatFileForDisplay).mockResolvedValue({ error: "NOT_FOUND" });

With:

mockGetFlatFileForDisplay.mockResolvedValue({ error: "NOT_FOUND" });
libs/public-pages/src/routes/flat-file/[artefactId]/download.test.ts (1)

71-274: Consider consolidating the import pattern.

Each test within the error handling, success, and integration sections imports getFileForDownload individually. While this works correctly, it's verbose and repetitive.

Consider importing once at the top level and configuring the mock in each test:

+import { getFileForDownload } from "../../../flat-file/flat-file-service.js";
+
 vi.mock("../../../flat-file/flat-file-service.js");

 describe("Flat File Download Route", () => {
   // ... existing setup ...

   describe("Error Handling", () => {
     // ... existing beforeEach ...

     it("should return 404 for NOT_FOUND error", async () => {
-      const { getFileForDownload } = await import("../../../flat-file/flat-file-service.js");
       vi.mocked(getFileForDownload).mockResolvedValue({ error: "NOT_FOUND" });
       // ... rest of test
     });
   });
 });

This reduces boilerplate while maintaining the same test behavior.

e2e-tests/tests/flat-file-viewing.spec.ts (2)

15-15: Consider using environment variable for storage path.

The hardcoded relative path assumes a specific directory structure and working directory. This may break in different environments (CI/CD, local dev with different setup).

Consider using an environment variable:

-const STORAGE_PATH = path.join(process.cwd(), "..", "apps", "web", "storage", "temp", "uploads");
+const STORAGE_PATH = process.env.TEST_STORAGE_PATH || path.join(process.cwd(), "..", "apps", "web", "storage", "temp", "uploads");

219-219: Use Playwright's baseURL configuration instead of hardcoded URLs.

Hardcoded https://localhost:8080 URLs appear in multiple tests, making them less portable. Playwright supports a baseURL configuration option that should be used for making requests.

Configure baseURL in your Playwright config (if not already done):

// playwright.config.ts
export default defineConfig({
  use: {
    baseURL: process.env.BASE_URL || 'https://localhost:8080',
  },
});

Then update requests to use relative URLs:

-const response = await page.request.get(`https://localhost:8080/api/flat-file/${artefactId}/download`, {
+const response = await page.request.get(`/api/flat-file/${artefactId}/download`, {
   ignoreHTTPSErrors: true
 });

Also applies to: 248-248, 258-258, 654-654

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac77ab1 and 510543d.

📒 Files selected for processing (3)
  • e2e-tests/tests/flat-file-viewing.spec.ts (1 hunks)
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].test.ts (1 hunks)
  • libs/public-pages/src/routes/flat-file/[artefactId]/download.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and Interfaces must use PascalCase (e.g., UserService, CaseRepository). Do NOT use I prefix for interfaces (use UserRepository not IUserRepository).
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: consts outside function scope at top, exported functions next, other functions ordered by usage, interfaces and types at bottom.
TypeScript must use strict mode enabled with no any without justification. Use workspace aliases (@hmcts/*) for imports.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
All input endpoints must validate inputs. Use parameterized database queries with Prisma. Never include sensitive data in logs.
Only export functions that are intended to be used outside the module. Don't export functions solely for testing purposes.
Only add comments when they are meaningful. Explain why something is done, not what is done.
Favor functional style with simple functional approaches. Don't use a class 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.
Use Express version 5.x with proper async/await error handling in middleware and route handlers.

Files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].test.ts
  • e2e-tests/tests/flat-file-viewing.spec.ts
  • libs/public-pages/src/routes/flat-file/[artefactId]/download.test.ts
**/*.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/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].test.ts
  • e2e-tests/tests/flat-file-viewing.spec.ts
  • libs/public-pages/src/routes/flat-file/[artefactId]/download.test.ts
**/{pages,locales}/**/*.{ts,njk}

📄 CodeRabbit inference engine (CLAUDE.md)

Every page must support both English and Welsh by providing en and cy objects in controllers and maintaining matching structure in locale files (libs/[module]/src/locales/en.ts and cy.ts).

Files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].test.ts
**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

All packages must use "test": "vitest run" as the test script. Unit and integration tests must be co-located with source code as *.test.ts files.

Files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].test.ts
  • libs/public-pages/src/routes/flat-file/[artefactId]/download.test.ts
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Page routes are created based on file names within the pages/ directory. Nested routes are created using subdirectories (e.g., pages/admin/my-page.ts becomes /admin/my-page).

Files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].test.ts
**/{locales,pages}/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Shared/common content (button text, phase banner, service name, common errors) should be in locale files (libs/[module]/src/locales/en.ts and cy.ts). Page-specific content should be in controllers.

Files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].test.ts
🧠 Learnings (2)
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/pages/*.ts : Page controllers must export GET and/or POST functions with signature `(req: Request, res: Response) => Promise<void>`. Content (titles, descriptions) should be organized as `en` and `cy` objects.

Applied to files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].test.ts
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/*.test.ts : All packages must use `"test": "vitest run"` as the test script. Unit and integration tests must be co-located with source code as `*.test.ts` files.

Applied to files:

  • libs/public-pages/src/routes/flat-file/[artefactId]/download.test.ts
🪛 GitHub Actions: Test
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].test.ts

[error] 1-1: Command failed with exit code 1: yarn run test --coverage in libs/public-pages.

libs/public-pages/src/routes/flat-file/[artefactId]/download.test.ts

[error] 228-228: Test timed out in 5000ms for 'should handle large file buffers'.


[error] 1-1: Command failed with exit code 1: yarn run test --coverage in libs/public-pages.

⏰ 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 (13)
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].test.ts (4)

7-28: LGTM!

Test setup correctly initializes mock request/response objects and properly clears mocks between tests. The chained res.status().render() pattern is correctly mocked.


30-91: LGTM!

Comprehensive parameter validation tests covering missing parameters and Welsh locale support. The tests properly verify both the HTTP status codes and localized error messages.


93-206: LGTM!

Error handling tests comprehensively cover all error types with appropriate HTTP status codes. Good coverage of Welsh locale error messages.


208-337: All test assertions are correctly aligned with the implementation; no issues found.

The test expectations in lines 208-337 precisely match the controller implementation and locale file definitions:

  • Locale strings verified: Both downloadLinkText and pdfNotSupportedMessage are properly defined in libs/public-pages/src/pages/hearing-lists/en.ts and cy.ts
  • English test (line 228-246): Expects pdfNotSupportedMessage as any string and downloadLinkText as any string → controller passes t.pdfNotSupportedMessage ("Your browser does not support PDF viewing.") and t.downloadLinkText ("Download this PDF")
  • Welsh test (line 248-266): Expects strings containing "eich porwr" and "Lawrlwytho" → controller correctly passes Welsh values ("Nid yw eich porwr yn cefnogi gwylio PDF." and "Lawrlwytho'r PDF hwn")
  • Default locale test (line 285-310): Expects strings containing "browser" and "Download" → correctly defaults to English locale values

All render calls provide both en and cy objects as required by the coding guidelines. The test structure properly verifies both locale support and data passthrough. If pipeline tests are failing, the issue is likely environmental or related to test execution, not the test assertions themselves.

libs/public-pages/src/routes/flat-file/[artefactId]/download.test.ts (4)

1-6: LGTM!

The imports and mock setup follow the coding guidelines correctly, including the required .js extension for relative imports.


7-33: LGTM!

The test setup with beforeEach properly initializes mocks and spies, ensuring test isolation.


35-129: LGTM!

Comprehensive test coverage for parameter validation and error handling. The tests properly verify that error responses don't set headers and return appropriate status codes for each error scenario.


255-275: LGTM!

The integration tests properly verify that the service is called with the correct parameters and only once per request.

e2e-tests/tests/flat-file-viewing.spec.ts (5)

18-68: LGTM!

The PDF generation helper creates a valid minimal PDF structure suitable for testing.


129-132: LGTM!

Simple navigation helper is clear and appropriate.


187-204: LGTM!

Accessibility testing implementation is comprehensive with appropriate WCAG tags and helpful violation logging for debugging.

Also applies to: 538-555


476-521: LGTM!

Welsh language support tests provide good i18n coverage with appropriate verification of translated content.


664-712: LGTM!

Comprehensive end-to-end journey test validates the complete user flow from landing page to flat file viewer.

Comment thread e2e-tests/tests/flat-file-viewing.spec.ts
Comment thread e2e-tests/tests/flat-file-viewing.spec.ts
Comment thread libs/public-pages/src/routes/flat-file/[artefactId]/download.test.ts Outdated
alexbottenberg and others added 5 commits November 27, 2025 17:02
Created test coverage for libs/public-pages/src/pages/publication-not-found.ts with 20 test cases covering:

Response Status (2 tests):
- Returns 404 status code
- Calls status before render

Template Rendering (2 tests):
- Renders publication-not-found template
- Passes both en and cy locale objects

English Locale Content (5 tests):
- Page title: "Page not found"
- Heading: "Page not found"
- Body text with expiry message
- Button text: "Find a court or tribunal"
- All required properties present

Welsh Locale Content (6 tests):
- Page title: "Ni chanfuwyd y dudalen"
- Heading: "Ni chanfuwyd y dudalen"
- Body text with expiry message in Welsh
- Button text: "Dod o hyd i lys neu dribiwnlys"
- All required properties present
- Same structure as English locale

Request Independence (3 tests):
- Does not read from request object
- Works with empty request object
- Produces same output regardless of request

Error Handling (2 tests):
- Render called exactly once
- Status called exactly once

All 258 tests in public-pages passing (258/258).

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (3)
e2e-tests/tests/flat-file-viewing.spec.ts (3)

559-595: Keyboard navigation test doesn’t actually assert focus reaches the back button

This test presses Tab four times and only checks that some element has focus, then manually focuses the back button. That doesn’t verify that keyboard tabbing can reach the button (same issue as the prior review).

Stronger options:

  • Assert that locator(":focus") after tabbing corresponds specifically to the back button (e.g., compare handles or text).
  • Or skip the Tab presses and instead assert await backButton.focus(); await expect(backButton).toBeFocused(); plus an assertion that it has a valid href (which you already check in another test).

As-is, the Tab sequence adds flakiness potential without improving coverage.


70-126: Add proper DB/file teardown and wire the tracking array

createFlatFileArtefact inserts artefacts and (optionally) writes PDFs into STORAGE_PATH, but this file still has no test.afterEach/test.afterAll cleanup. The new trackingArray parameter is never used, so artefact IDs and files are not tracked or removed, which can pollute the test DB and filesystem over time (same concern as the previous review).

Consider:

  • Adding a top‑level const createdArtefactIds: string[] = [];.
  • Passing this into every createFlatFileArtefact call so IDs are collected.
  • Using test.afterEach to delete artefacts by ID and clear the array.
  • Using test.afterAll to remove any remaining test PDFs in STORAGE_PATH.

Example sketch:

+const createdArtefactIds: string[] = [];
+
 async function createFlatFileArtefact(
   options: { /* ... */ },
   trackingArray?: string[]
 ): Promise<string> {
   // ...
   if (trackingArray) {
     trackingArray.push(artefactId);
   }
   return artefactId;
 }
+
+test.afterEach(async () => {
+  if (createdArtefactIds.length > 0) {
+    await prisma.artefact.deleteMany({
+      where: { artefactId: { in: createdArtefactIds } }
+    });
+    for (const id of createdArtefactIds) {
+      const filePath = path.join(STORAGE_PATH, `${id}.pdf`);
+      if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
+    }
+    createdArtefactIds.length = 0;
+  }
+});
+
+test.afterAll(async () => {
+  if (fs.existsSync(STORAGE_PATH)) {
+    for (const file of fs.readdirSync(STORAGE_PATH)) {
+      if (file.endsWith(".pdf")) {
+        fs.unlinkSync(path.join(STORAGE_PATH, file));
+      }
+    }
+  }
+});

…and update each call site to pass createdArtefactIds.

This keeps the environment clean and makes the new trackingArray parameter actually useful.


619-641: Strengthen invalid-request tests with real error checks

Both “invalid requests” tests only assert that document.title is truthy, which doesn’t meaningfully validate error handling (this matches the earlier review feedback).

Consider instead:

  • Capturing the page.goto response and asserting an expected status (e.g., 404), or
  • Asserting the presence of a specific error element / heading (e.g., .govuk-error-summary or an H1 with “not found”) consistent with the app’s error page.

Example sketch:

-      const statusText = await page.evaluate(() => document.title);
-      expect(statusText).toBeTruthy();
+      const errorSummary = page.locator(".govuk-error-summary");
+      const notFoundHeading = page.locator("h1", { hasText: /not found/i });
+      const hasError = await errorSummary.isVisible().catch(() => false);
+      const hasNotFound = await notFoundHeading.isVisible().catch(() => false);
+      expect(hasError || hasNotFound).toBeTruthy();

This will better protect against regressions in invalid-URL handling.

🧹 Nitpick comments (2)
e2e-tests/tests/flat-file-viewing.spec.ts (2)

128-132: Remove or use the unused navigateToSummaryPage helper

navigateToSummaryPage is currently never called; tests inline page.goto("/summary-of-publications?...") instead. Either start using this helper to DRY those calls or remove it to avoid dead code in the spec.


433-472: Back-button test should click the button instead of using page.goBack()

The test asserts the back button’s href is javascript:history.back(), but actual navigation back to /summary-of-publications is done with page.goBack(), so it doesn’t prove the button itself works.

Consider asserting navigation via the button click:

-      // Verify it works by going back
-      await page.goBack();
-      await expect(page).toHaveURL(/\/summary-of-publications/);
+      // Verify it works by clicking the back button
+      await Promise.all([
+        page.waitForURL(/\/summary-of-publications/),
+        backButton.click()
+      ]);

This ties the behavior directly to the control under test.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22ce1b6 and 39a8f15.

📒 Files selected for processing (1)
  • e2e-tests/tests/flat-file-viewing.spec.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and Interfaces must use PascalCase (e.g., UserService, CaseRepository). Do NOT use I prefix for interfaces (use UserRepository not IUserRepository).
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: consts outside function scope at top, exported functions next, other functions ordered by usage, interfaces and types at bottom.
TypeScript must use strict mode enabled with no any without justification. Use workspace aliases (@hmcts/*) for imports.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
All input endpoints must validate inputs. Use parameterized database queries with Prisma. Never include sensitive data in logs.
Only export functions that are intended to be used outside the module. Don't export functions solely for testing purposes.
Only add comments when they are meaningful. Explain why something is done, not what is done.
Favor functional style with simple functional approaches. Don't use a class 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.
Use Express version 5.x with proper async/await error handling in middleware and route handlers.

Files:

  • e2e-tests/tests/flat-file-viewing.spec.ts
**/*.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:

  • e2e-tests/tests/flat-file-viewing.spec.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). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (5)
e2e-tests/tests/flat-file-viewing.spec.ts (5)

134-267: Flat-file happy-path, headers, and download coverage looks solid

The main “Flat File Viewing” tests exercise inline viewing, opening in a new tab, title content, download link presence, response headers, and direct download via page.request. This is a good breadth of coverage for the core feature.


269-405: Error-handling scenarios are well covered

The tests for expired, not-yet-available, missing-file, missing-artefact, and mismatched-location scenarios cover both content and key messages, and they avoid leaking artefact existence for wrong-location access. This is a solid set of negative-path checks for the flat-file feature.


475-521: Welsh-language behavior is exercised appropriately

The Welsh tests verify localized headings, error summary text, and back button / download link labels with lng=cy, which is exactly what we want to guard against i18n regressions on both error and viewer pages.


523-557: Accessibility checks on the error page are a good addition

Running Axe with WCAG 2.2 tags and explicitly documenting the two disabled rules (footer issues) is clear and pragmatic. The logging block on violations is also helpful for debugging failing runs.


664-712: End-to-end “full user journey” test provides strong regression protection

The full-flow test from landing page through view-option selection, summary-of-publications, and finally opening the flat-file viewer in a new tab is valuable high-level coverage for VIBE-215 and should catch most wiring or routing regressions.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
e2e-tests/tests/flat-file-viewing.spec.ts (2)

616-633: Keyboard navigation test doesn't verify focus order.

The test presses Tab four times but never asserts which element receives focus. The manual backButton.focus() call bypasses the actual keyboard navigation path being tested.

Consider verifying the focused element after tabbing:

       // Test Tab navigation to back button
       await page.keyboard.press("Tab");
       await page.keyboard.press("Tab");
       await page.keyboard.press("Tab");
       await page.keyboard.press("Tab");

-      // Find focused element
-      const focusedElement = page.locator(":focus");
-      await expect(focusedElement).toBeVisible();
-
-      // Verify back button can be activated with keyboard
+      // Verify back button is keyboard accessible
       await backButton.focus();
       await expect(backButton).toBeFocused();
+
+      // Verify error summary link is also keyboard accessible
+      const errorSummaryLink = page.locator(".govuk-error-summary__list a").first();
+      await errorSummaryLink.focus();
+      await expect(errorSummaryLink).toBeFocused();

658-680: Invalid request tests have weak assertions.

These tests only verify document.title is truthy, which doesn't meaningfully validate error handling. They should check for specific error responses or page elements.

Consider stronger assertions:

     test("should show error message when artefactId is missing", async ({ page }) => {
       await page.goto(`/hearing-lists/${testLocationId}/`);
       await page.waitForLoadState("domcontentloaded");

-      const statusText = await page.evaluate(() => document.title);
-      expect(statusText).toBeTruthy();
+      // Verify 404 or error page is shown
+      const errorSummary = page.locator(".govuk-error-summary");
+      const notFoundHeading = page.locator("h1", { hasText: /not found/i });
+      const hasError = await errorSummary.isVisible().catch(() => false);
+      const hasNotFound = await notFoundHeading.isVisible().catch(() => false);
+      expect(hasError || hasNotFound).toBeTruthy();
     });
🧹 Nitpick comments (2)
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk (1)

29-68: Standalone HTML viewer approach is appropriate for full-screen PDF display.

The success path intentionally creates a minimal standalone HTML document optimized for PDF viewing. The dynamic {{ locale }} on line 30 properly supports Welsh/English per coding guidelines.

Consider adding a fallback for the locale variable to handle edge cases:

-<html lang="{{ locale }}">
+<html lang="{{ locale or 'en' }}">
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (1)

26-42: Consider using a switch statement for cleaner error mapping.

The if-else chain duplicates the switch pattern used in the download route. A switch would improve consistency and readability.

-    if (result.error === "NOT_FOUND" || result.error === "LOCATION_MISMATCH") {
-      statusCode = 404;
-      errorMessage = t.errorNotFound;
-    } else if (result.error === "EXPIRED") {
-      statusCode = 410;
-      errorMessage = t.errorExpired;
-    } else if (result.error === "FILE_NOT_FOUND") {
-      statusCode = 404;
-      errorMessage = t.errorFileNotFound;
-    } else if (result.error === "NOT_FLAT_FILE") {
-      statusCode = 400;
-      errorMessage = t.errorNotFlatFile;
-    }
+    switch (result.error) {
+      case "NOT_FOUND":
+      case "LOCATION_MISMATCH":
+        statusCode = 404;
+        errorMessage = t.errorNotFound;
+        break;
+      case "EXPIRED":
+        statusCode = 410;
+        errorMessage = t.errorExpired;
+        break;
+      case "FILE_NOT_FOUND":
+        statusCode = 404;
+        errorMessage = t.errorFileNotFound;
+        break;
+      case "NOT_FLAT_FILE":
+        statusCode = 400;
+        errorMessage = t.errorNotFlatFile;
+        break;
+    }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39a8f15 and cc44634.

📒 Files selected for processing (6)
  • e2e-tests/tests/flat-file-viewing.spec.ts (1 hunks)
  • libs/public-pages/package.json (1 hunks)
  • libs/public-pages/src/file-storage/file-retrieval.ts (1 hunks)
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk (1 hunks)
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (1 hunks)
  • libs/public-pages/src/routes/flat-file/[artefactId]/download.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/public-pages/package.json
  • libs/public-pages/src/file-storage/file-retrieval.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/{pages,locales}/**/*.{ts,njk}

📄 CodeRabbit inference engine (CLAUDE.md)

Every page must support both English and Welsh by providing en and cy objects in controllers and maintaining matching structure in locale files (libs/[module]/src/locales/en.ts and cy.ts).

Files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and Interfaces must use PascalCase (e.g., UserService, CaseRepository). Do NOT use I prefix for interfaces (use UserRepository not IUserRepository).
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: consts outside function scope at top, exported functions next, other functions ordered by usage, interfaces and types at bottom.
TypeScript must use strict mode enabled with no any without justification. Use workspace aliases (@hmcts/*) for imports.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
All input endpoints must validate inputs. Use parameterized database queries with Prisma. Never include sensitive data in logs.
Only export functions that are intended to be used outside the module. Don't export functions solely for testing purposes.
Only add comments when they are meaningful. Explain why something is done, not what is done.
Favor functional style with simple functional approaches. Don't use a class 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.
Use Express version 5.x with proper async/await error handling in middleware and route handlers.

Files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
  • libs/public-pages/src/routes/flat-file/[artefactId]/download.ts
  • e2e-tests/tests/flat-file-viewing.spec.ts
**/*.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/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
  • libs/public-pages/src/routes/flat-file/[artefactId]/download.ts
  • e2e-tests/tests/flat-file-viewing.spec.ts
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Page routes are created based on file names within the pages/ directory. Nested routes are created using subdirectories (e.g., pages/admin/my-page.ts becomes /admin/my-page).

Files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
**/{locales,pages}/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Shared/common content (button text, phase banner, service name, common errors) should be in locale files (libs/[module]/src/locales/en.ts and cy.ts). Page-specific content should be in controllers.

Files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
🧠 Learnings (6)
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/{pages,locales}/**/*.{ts,njk} : Every page must support both English and Welsh by providing `en` and `cy` objects in controllers and maintaining matching structure in locale files (libs/[module]/src/locales/en.ts and cy.ts).

Applied to files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/{locales,pages}/**/*.ts : Shared/common content (button text, phase banner, service name, common errors) should be in locale files (libs/[module]/src/locales/en.ts and cy.ts). Page-specific content should be in controllers.

Applied to files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].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/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk
📚 Learning: 2025-11-27T14:18:22.922Z
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.922Z
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/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/pages/*.njk : Nunjucks templates must extend `layouts/default.njk` and use GOV.UK Design System macros. Every page must support both English and Welsh content.

Applied to files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk
📚 Learning: 2025-11-25T06:40:24.099Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T06:40:24.099Z
Learning: Applies to **/pages/*.ts : Page controllers must export GET and/or POST functions with signature `(req: Request, res: Response) => Promise<void>`. Content (titles, descriptions) should be organized as `en` and `cy` objects.

Applied to files:

  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
  • libs/public-pages/src/routes/flat-file/[artefactId]/download.ts
🧬 Code graph analysis (1)
libs/public-pages/src/routes/flat-file/[artefactId]/download.ts (3)
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (1)
  • GET (6-73)
libs/public-pages/src/flat-file/flat-file-service.ts (1)
  • getFileForDownload (50-80)
libs/public-pages/src/index.ts (1)
  • getFileForDownload (2-2)
⏰ 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 (8)
libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk (1)

1-27: Well-structured error handling with proper GOV.UK components.

The error state correctly extends the base template, uses the govukErrorSummary macro, and provides all required localized strings. The back button implementation is appropriate for this use case.

libs/public-pages/src/routes/flat-file/[artefactId]/download.ts (2)

1-8: Good UUID validation implementation.

The regex-based UUID validation at the route level prevents unnecessary database lookups for malformed artefact IDs. The .js extension on the import follows ESM guidelines.


10-52: Clean implementation with proper error handling and cache headers.

The handler correctly:

  • Validates input before service call
  • Maps service errors to appropriate HTTP status codes (404, 410, 400)
  • Uses restrictive cache headers (private, no-store) for time-sensitive artefacts
  • Sets Content-Disposition: inline for browser viewing

One minor observation: the error responses use JSON format while the page controller renders HTML. This is appropriate for an API endpoint.

libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (2)

1-9: Proper locale handling and imports.

The controller correctly:

  • Uses .js extension for ESM imports per guidelines
  • Determines locale from res.locals.locale with English fallback
  • Selects appropriate translation object based on locale

56-72: Success rendering includes all required template variables.

The render call correctly passes all variables needed by the Nunjucks template: locale, pageTitle, downloadUrl, pdfNotSupportedMessage, and downloadLinkText. This addresses the previous review comments.

e2e-tests/tests/flat-file-viewing.spec.ts (3)

140-174: Excellent test cleanup implementation.

The afterEach and afterAll hooks properly address test pollution concerns by:

  • Tracking created artefact IDs
  • Deleting database records and corresponding files after each test
  • Cleaning up any residual PDFs after all tests

This follows the recommendations from the previous review.


176-306: Comprehensive happy path tests with proper coverage.

The tests cover:

  • PDF viewing in new tabs (TS1, TS2)
  • Response headers verification (TS2, TS8)
  • Download functionality (TS3)
  • Accessibility compliance (TS9)

Good use of Playwright's multi-page context for new tab testing.


514-560: Welsh language support properly tested.

Both error page and viewer page are tested with ?lng=cy parameter, verifying Welsh translations appear correctly. This aligns with the coding guidelines requiring English and Welsh support.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
e2e-tests/tests/flat-file-viewing.spec.ts (1)

128-132: Remove unused helper function.

navigateToSummaryPage is never called in this test file—all tests navigate directly using page.goto() or through complete user journeys.

Apply this diff:

-// Helper function to create a flat file link in the summary of publications page
-async function navigateToSummaryPage(page: Page, locationId: string) {
-  await page.goto(`/summary-of-publications?locationId=${locationId}`);
-  await page.waitForLoadState("domcontentloaded");
-}
🧹 Nitpick comments (1)
e2e-tests/tests/flat-file-viewing.spec.ts (1)

258-260: Avoid hardcoding base URL in test requests.

The hardcoded https://localhost:8080 reduces test portability. If the test environment uses a different port or host, these tests will fail.

Use Playwright's baseURL configuration or an environment variable:

-const response = await page.request.get(`https://localhost:8080/api/flat-file/${artefactId}/download`, {
+const response = await page.request.get(`/api/flat-file/${artefactId}/download`, {
-  ignoreHTTPSErrors: true
 });

Then configure baseURL in your Playwright config file (e.g., playwright.config.ts):

export default defineConfig({
  use: {
    baseURL: process.env.BASE_URL || 'https://localhost:8080',
    ignoreHTTPSErrors: true,
  },
});

Also applies to: 287-289, 297-299, 704-706

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82c8c6a and 7d19d90.

📒 Files selected for processing (1)
  • e2e-tests/tests/flat-file-viewing.spec.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/tests/flat-file-viewing.spec.ts
e2e-tests/**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/tests/flat-file-viewing.spec.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/tests/flat-file-viewing.spec.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/tests/flat-file-viewing.spec.ts
🧠 Learnings (4)
📓 Common learnings
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).
📚 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/flat-file-viewing.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 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/flat-file-viewing.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:

  • e2e-tests/tests/flat-file-viewing.spec.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). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (1)
e2e-tests/tests/flat-file-viewing.spec.ts (1)

140-174: Excellent cleanup implementation.

The cleanup hooks properly address the previously flagged concern about test pollution. Tracked artefacts are removed from both database and filesystem, with appropriate error handling for missing files.

Comment thread e2e-tests/tests/flat-file-viewing.spec.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
e2e-tests/tests/flat-file-viewing.spec.ts (2)

714-763: Full journey test should include accessibility, Welsh, and keyboard checks.

Per coding guidelines, "E2E tests must include complete user journeys with validations, Welsh translations, accessibility checks, and keyboard navigation all within a single test." The current test validates the happy path but omits these required checks.

Expand the test after Step 6 to include:

// Step 7: Run accessibility checks on viewer
const accessibilityScanResults = await new AxeBuilder({ page })
  .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"])
  .disableRules(["target-size", "link-name"])
  .analyze();
expect(accessibilityScanResults.violations).toEqual([]);

// Step 8: Verify keyboard navigation to download link
await downloadLink.focus();
await expect(downloadLink).toBeFocused();

// Step 9: Test Welsh language support
await page.goto(`/hearing-lists/${testLocationId}/${artefactId}?lng=cy`);
const welshDownloadLink = page.locator(`a[href="/api/flat-file/${artefactId}/download"]`);
await expect(welshDownloadLink).toContainText(/lawrlwytho/i);

Based on learnings, E2E tests should be comprehensive within each journey.


128-132: Remove unused helper function.

navigateToSummaryPage is defined but never called in this test file. All tests navigate directly using page.goto().

-// Helper function to create a flat file link in the summary of publications page
-async function navigateToSummaryPage(page: Page, locationId: string) {
-  await page.goto(`/summary-of-publications?locationId=${locationId}`);
-  await page.waitForLoadState("domcontentloaded");
-}
🧹 Nitpick comments (2)
e2e-tests/tests/flat-file-viewing.spec.ts (2)

18-50: Potential PDF corruption with special characters in content.

The content parameter is directly embedded into the PDF stream without escaping. If content contains characters like ), \, or non-ASCII, the PDF structure will break. Since this is test-only code with controlled inputs, this is low risk but worth noting.

Consider escaping or validating content if this helper will be reused:

 function createTestPDFBuffer(content: string): Buffer {
+  // Escape special PDF characters
+  const escapedContent = content.replace(/[()\\]/g, '\\$&');
   return Buffer.from(`%PDF-1.4
   ...
-  (${content}) Tj
+  (${escapedContent}) Tj

258-261: Hardcoded base URL may cause test failures in different environments.

The test uses hardcoded https://localhost:8080 for API requests, but page.goto() elsewhere uses relative URLs (presumably resolved by Playwright's baseURL config). This inconsistency could cause failures if the test runs against a different host/port.

Consider using page.request.get() with a relative URL or derive the base URL from Playwright config:

-      const response = await page.request.get(`https://localhost:8080/api/flat-file/${artefactId}/download`, {
-        ignoreHTTPSErrors: true
-      });
+      const response = await page.request.get(`/api/flat-file/${artefactId}/download`);

Playwright's APIRequestContext respects the configured baseURL when using relative paths.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f37454b and a78adfb.

📒 Files selected for processing (4)
  • e2e-tests/tests/flat-file-viewing.spec.ts (1 hunks)
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk (1 hunks)
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].test.ts (1 hunks)
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].test.ts
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].ts
  • libs/public-pages/src/pages/hearing-lists/[locationId]/[artefactId].njk
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/tests/flat-file-viewing.spec.ts
e2e-tests/**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/tests/flat-file-viewing.spec.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/tests/flat-file-viewing.spec.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/tests/flat-file-viewing.spec.ts
🧠 Learnings (6)
📓 Common learnings
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).
📚 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/flat-file-viewing.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 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/flat-file-viewing.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:

  • e2e-tests/tests/flat-file-viewing.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,njk} : Welsh translations are required for all user-facing text. Do not skip Welsh support.

Applied to files:

  • e2e-tests/tests/flat-file-viewing.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,njk} : Every page must support both English and Welsh. Controllers must provide both `en` and `cy` objects with page content.

Applied to files:

  • e2e-tests/tests/flat-file-viewing.spec.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). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (10)
e2e-tests/tests/flat-file-viewing.spec.ts (10)

1-16: LGTM!

Imports are well-organized, using workspace aliases correctly. The documented explanation for disabled accessibility rules with a reference to a tracking ticket is good practice.


94-108: LGTM!

The helper properly handles artefact creation with sensible defaults and cleanup tracking. The test data setup is well-structured.

Consider adding a comment for the magic number listTypeId: 6 to clarify it represents "Crown Daily List" as mentioned in the full journey test assertions.


140-174: LGTM!

The cleanup mechanism properly tracks and removes test artefacts from both the database and file storage. The use of afterEach for tracked items and afterAll for residual cleanup is a solid pattern.


308-371: LGTM!

Good coverage of temporal edge cases (expired files and future availability). The error message assertions are appropriately flexible with regex matching.


422-443: LGTM!

Good security-conscious test - verifying that location mismatch returns a generic error without revealing whether the artefact exists at the correct location.


472-512: LGTM!

Well-structured navigation test that properly establishes a session context before testing the back button behavior. The specific selector with filter { hasText: /^back/i } correctly avoids matching "feedback" links.


514-560: LGTM!

Welsh language support is properly tested for both error pages and the successful viewer page. The tests verify key Welsh translations as per coding guidelines. Based on learnings, Welsh translations are required for all user-facing text.


562-596: LGTM!

Accessibility testing meets WCAG 2.2 AA requirements using Axe-core with appropriate tag coverage. The disabled rules are documented with a reference to the tracking ticket.


646-666: LGTM!

Keyboard navigation test for the viewer page appropriately verifies that the download link can receive focus.


669-691: Acknowledged: Weak assertions in invalid request tests.

These tests only verify that document.title is truthy, which doesn't meaningfully validate error handling. This was previously flagged and intentionally skipped as a minor concern.

@ChrisS1512 ChrisS1512 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some merge conflicts to fix

import { getContentType, getFileBuffer, getFileExtension, getFileName } from "../file-storage/file-retrieval.js";

export async function getFlatFileForDisplay(artefactId: string, locationId: string, locale: string = "en") {
const artefact = await prisma.artefact.findUnique({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can this be moved to a query class instead? (if it does not already exist)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Similar comment for queries below

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

done. move this prisma statement to query.

const MONOREPO_ROOT = path.join(__dirname, "..", "..", "..", "..");
const STORAGE_BASE = path.join(MONOREPO_ROOT, "storage", "temp", "uploads");

async function findFileByArtefactId(artefactId: string): Promise<{ buffer: Buffer; extension: string } | null> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if this sits better in the publication lib, as this is going to be used in many places in the future like subscriptions fulfilment, deletion of artefacts etc

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

done.

junaidiqbalmoj and others added 2 commits December 18, 2025 10:52
Resolved merge conflicts in:
- apps/web/src/app.ts: Combined public pages API routes and list type routes
- apps/web/src/app.test.ts: Updated router call count expectations and added care-standards-tribunal mock
- yarn.lock: Regenerated with current dependencies

Updated test expectations to reflect 10 router registrations (added public pages API routes).

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

Co-Authored-By: Claude <noreply@anthropic.com>
Resolved merge conflicts in:
- apps/web/src/app.ts: Combined public pages API routes and care-standards-tribunal routes
- apps/web/src/app.test.ts: Updated test expectations for 10 router calls and added mock for care-standards-tribunal

Changes:
- Added apiRoutes import from @hmcts/public-pages/config
- Registered public pages API routes with /api prefix
- Added care-standards-tribunal-weekly-hearing-list route registration
- Updated test mock for public-pages/config to include apiRoutes
- Added test mock for care-standards-tribunal-weekly-hearing-list/config
- Updated createSimpleRouter call count from 9 to 10

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)

187-191: Consider renaming test to reflect total route registration.

The test is named "should register public pages routes" but actually validates that createSimpleRouter is called 10 times across all route sources (location API, public pages API, civil-family pages, care-standards-tribunal pages, etc.). The test name could be more descriptive of its actual behavior.

🔎 Consider this rename for clarity:
-    it("should register public pages routes", async () => {
+    it("should register all route sources", async () => {
       const { createSimpleRouter } = await import("@hmcts/simple-router");
-      // Should be called 10 times: location API routes, public pages API routes, civil-family-cause-list pages, care-standards-tribunal pages, web pages, auth routes, public pages, verified pages, system-admin pages, admin routes
+      // Should be called 10 times total: location API routes, public pages API routes, civil-family-cause-list pages, care-standards-tribunal pages, web pages, auth routes, public pages, verified pages, system-admin pages, admin routes
       expect(createSimpleRouter).toHaveBeenCalledTimes(10);
     });

197-198: Use exact assertion count for better test precision.

The assertion uses toBeGreaterThanOrEqual(10), which will pass even if more than 10 routes are registered. This is less precise than the assertion on line 190 and could mask unexpected route additions. The comment also states "should have 10 total calls," suggesting an exact count is expected.

🔎 Apply this diff for more precise testing:
-      // Verify system-admin routes were registered (should have 10 total calls)
-      expect(calls.length).toBeGreaterThanOrEqual(10);
+      // Verify system-admin routes were registered (should have 10 total calls)
+      expect(calls.length).toBe(10);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a78adfb and 67dda3d.

📒 Files selected for processing (3)
  • apps/web/src/app.test.ts (3 hunks)
  • apps/web/src/app.ts (2 hunks)
  • libs/public-pages/src/config.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/public-pages/src/config.ts
  • apps/web/src/app.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

Use SCREAMING_SNAKE_CASE for constants: MAX_FILE_SIZE, DEFAULT_TIMEOUT

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

Input validation must be performed on all endpoints

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • apps/web/src/app.test.ts
🧠 Learnings (2)
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
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.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to **/config.ts : Module config.ts must export standardized interfaces: pageRoutes, apiRoutes, prismaSchemas, assets

Applied to files:

  • apps/web/src/app.test.ts
⏰ 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 (2)
apps/web/src/app.test.ts (2)

66-69: LGTM: Mock follows established pattern.

The new mock for the care-standards-tribunal-weekly-hearing-list module follows the same structure as other page route mocks in the test suite.


85-85: LGTM: API routes mock added appropriately.

The addition of apiRoutes to the public-pages mock aligns with the PR's objective to add a download API endpoint for flat files.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
libs/publication/src/repository/queries.ts (1)

107-129: LGTM! The new getArtefactById function is correctly implemented.

The function follows the established pattern in this module and properly handles the not-found case by returning null.

Consider extracting the repeated mapping logic into a helper function to reduce duplication across getArtefactsByLocation, getArtefactsByIds, and getArtefactById:

🔎 Optional refactor to reduce duplication
+function mapToArtefact(artefact: {
+  artefactId: string;
+  locationId: string;
+  listTypeId: number;
+  contentDate: Date;
+  sensitivity: string;
+  language: string;
+  displayFrom: Date;
+  displayTo: Date;
+  isFlatFile: boolean;
+  provenance: string;
+  noMatch: boolean;
+}): Artefact {
+  return {
+    artefactId: artefact.artefactId,
+    locationId: artefact.locationId,
+    listTypeId: artefact.listTypeId,
+    contentDate: artefact.contentDate,
+    sensitivity: artefact.sensitivity,
+    language: artefact.language,
+    displayFrom: artefact.displayFrom,
+    displayTo: artefact.displayTo,
+    isFlatFile: artefact.isFlatFile,
+    provenance: artefact.provenance,
+    noMatch: artefact.noMatch
+  };
+}

Then use it in the retrieval functions:

return artefacts.map(mapToArtefact);
// or
return artefact ? mapToArtefact(artefact) : null;
libs/public-pages/src/pages/publication/[id].ts (1)

24-26: Consider limiting what is logged in the error handler.

Logging the full error object could expose sensitive database query details or stack traces. As per coding guidelines, avoid including sensitive data in logs.

🔎 Suggested improvement
   } catch (error) {
-    console.error("Error loading publication:", error);
+    console.error("Error loading publication:", error instanceof Error ? error.message : "Unknown error");
     return res.redirect("/500");
   }
libs/publication/src/file-storage/content-type.ts (1)

1-17: LGTM! Clean implementation of content type resolution.

The normalization logic correctly handles extensions with or without leading dots, and the case-insensitive comparison is appropriate. The default to "application/pdf" for missing extensions aligns with the PR's focus on PDF viewing.

Consider adding support for additional common file types if they may be encountered:

🔎 Optional: Extend CONTENT_TYPE_MAP
 const CONTENT_TYPE_MAP: Record<string, string> = {
   ".pdf": "application/pdf",
   ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
   ".doc": "application/msword",
   ".html": "text/html",
   ".htm": "text/html",
-  ".csv": "text/csv"
+  ".csv": "text/csv",
+  ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+  ".xls": "application/vnd.ms-excel",
+  ".txt": "text/plain",
+  ".json": "application/json"
 };
libs/publication/src/file-storage/file-retrieval.ts (1)

9-11: Consider making the storage path configurable.

The monorepo root calculation uses hardcoded relative path traversal ("../../..") which assumes this file remains at libs/publication/src/file-storage/. The storage base path ("storage/temp/uploads") is also hardcoded. Consider using an environment variable or configuration file to specify the storage location, which would improve flexibility for different deployment environments and make the code more maintainable if the file structure changes.

Example configuration approach:
const STORAGE_BASE = process.env.FILE_STORAGE_PATH || path.join(MONOREPO_ROOT, "storage", "temp", "uploads");
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67dda3d and daffe9e.

📒 Files selected for processing (10)
  • libs/public-pages/src/flat-file/flat-file-service.test.ts (1 hunks)
  • libs/public-pages/src/flat-file/flat-file-service.ts (1 hunks)
  • libs/public-pages/src/index.ts (1 hunks)
  • libs/public-pages/src/pages/publication/[id].test.ts (7 hunks)
  • libs/public-pages/src/pages/publication/[id].ts (2 hunks)
  • libs/publication/src/file-storage/content-type.ts (1 hunks)
  • libs/publication/src/file-storage/file-retrieval.ts (1 hunks)
  • libs/publication/src/index.ts (1 hunks)
  • libs/publication/src/repository/queries.test.ts (2 hunks)
  • libs/publication/src/repository/queries.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • libs/public-pages/src/index.ts
  • libs/public-pages/src/flat-file/flat-file-service.ts
  • libs/public-pages/src/flat-file/flat-file-service.test.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/publication/src/repository/queries.ts
  • libs/publication/src/file-storage/content-type.ts
  • libs/publication/src/index.ts
  • libs/public-pages/src/pages/publication/[id].ts
  • libs/publication/src/repository/queries.test.ts
  • libs/public-pages/src/pages/publication/[id].test.ts
  • libs/publication/src/file-storage/file-retrieval.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use SCREAMING_SNAKE_CASE for constants: MAX_FILE_SIZE, DEFAULT_TIMEOUT

Files:

  • libs/publication/src/repository/queries.ts
  • libs/publication/src/file-storage/content-type.ts
  • libs/publication/src/index.ts
  • libs/public-pages/src/pages/publication/[id].ts
  • libs/publication/src/repository/queries.test.ts
  • libs/public-pages/src/pages/publication/[id].test.ts
  • libs/publication/src/file-storage/file-retrieval.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Input validation must be performed on all endpoints

Files:

  • libs/publication/src/repository/queries.ts
  • libs/publication/src/file-storage/content-type.ts
  • libs/publication/src/index.ts
  • libs/public-pages/src/pages/publication/[id].ts
  • libs/publication/src/repository/queries.test.ts
  • libs/public-pages/src/pages/publication/[id].test.ts
  • libs/publication/src/file-storage/file-retrieval.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/publication/src/repository/queries.ts
  • libs/publication/src/file-storage/content-type.ts
  • libs/publication/src/index.ts
  • libs/public-pages/src/pages/publication/[id].ts
  • libs/publication/src/repository/queries.test.ts
  • libs/public-pages/src/pages/publication/[id].test.ts
  • libs/publication/src/file-storage/file-retrieval.ts
libs/*/src/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/public-pages/src/pages/publication/[id].ts
  • libs/public-pages/src/pages/publication/[id].test.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/publication/src/repository/queries.test.ts
  • libs/public-pages/src/pages/publication/[id].test.ts
🧠 Learnings (2)
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
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/public-pages/src/pages/publication/[id].ts
  • libs/public-pages/src/pages/publication/[id].test.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to **/package.json : All packages must use `"test": "vitest run"` script in package.json

Applied to files:

  • libs/publication/src/repository/queries.test.ts
🧬 Code graph analysis (5)
libs/publication/src/repository/queries.ts (2)
libs/publication/src/index.ts (2)
  • getArtefactById (8-8)
  • Artefact (7-7)
libs/publication/src/repository/model.ts (1)
  • Artefact (1-13)
libs/public-pages/src/pages/publication/[id].ts (2)
libs/publication/src/index.ts (1)
  • getArtefactById (8-8)
libs/publication/src/repository/queries.ts (1)
  • getArtefactById (107-129)
libs/publication/src/repository/queries.test.ts (2)
libs/publication/src/index.ts (1)
  • getArtefactById (8-8)
libs/publication/src/repository/queries.ts (1)
  • getArtefactById (107-129)
libs/public-pages/src/pages/publication/[id].test.ts (3)
libs/publication/src/index.ts (1)
  • getArtefactById (8-8)
libs/publication/src/repository/queries.ts (1)
  • getArtefactById (107-129)
libs/public-pages/src/pages/publication/[id].ts (1)
  • GET (4-28)
libs/publication/src/file-storage/file-retrieval.ts (1)
libs/publication/src/file-storage/content-type.ts (1)
  • getContentTypeFromExtension (10-17)
⏰ 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/queries.test.ts (1)

557-648: LGTM! Comprehensive test coverage for the new getArtefactById function.

The tests cover the essential scenarios:

  • Artefact found and correctly mapped
  • Artefact not found returns null
  • Database fields are mapped correctly to the domain model
libs/public-pages/src/pages/publication/[id].ts (1)

1-1: Good refactor to use the repository abstraction.

Replacing direct Prisma access with getArtefactById from @hmcts/publication improves maintainability and aligns with proper layered architecture.

libs/publication/src/index.ts (1)

1-10: LGTM! Public API exports are correctly updated.

The new file-storage utilities and getArtefactById are properly exposed, following ES module conventions with .js extensions in imports as per coding guidelines.

libs/public-pages/src/pages/publication/[id].test.ts (1)

5-170: LGTM! Test migration properly executed.

The test file has been correctly updated to use the new getArtefactById function from @hmcts/publication. All test cases maintain their original coverage while adapting to the new data access pattern. The mock setup follows Vitest best practices, and the updated artefact fixtures align with the return shape from getArtefactById.

libs/publication/src/file-storage/file-retrieval.ts (2)

48-65: LGTM! Helper functions are well-structured.

The wrapper functions provide a clean API for different file retrieval needs. The use of sensible defaults (.pdf extension) and proper delegation to getContentTypeFromExtension ensures consistent behavior across the codebase.


21-21: The file matching approach using startsWith is safe for this implementation.

ArtefactIds are UUIDs (36-character unique identifiers), and files are named with an explicit dot delimiter before the extension (e.g., 550e8400-e29b-41d4-a716-446655440000.pdf). Since UUIDs are globally unique, the startsWith check will never match multiple files unintentionally—the next character after the UUID will always be the dot from the extension. This design prevents the prefix collision scenario described in the original comment.

Likely an incorrect or invalid review comment.

Comment thread libs/publication/src/file-storage/file-retrieval.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
libs/publication/src/file-storage/file-retrieval.test.ts (1)

25-25: Consider type-safe mocking approach for fs.readdir.

The as any assertions throughout the tests (lines 25, 41, 64, 77, 93, 109, 123, 136, 147, 170, 181, 192) bypass TypeScript's type checking. While common in filesystem mocking, consider using a properly-typed mock that accepts string[] or creating lightweight Dirent-like objects to maintain type safety.

🔎 Example: Type-safe approach

You could create a helper function at the top of the test file:

function createMockDirents(filenames: string[]): fs.Dirent[] {
  return filenames.map(name => ({
    name,
    isFile: () => true,
    isDirectory: () => false,
    isBlockDevice: () => false,
    isCharacterDevice: () => false,
    isSymbolicLink: () => false,
    isFIFO: () => false,
    isSocket: () => false,
  })) as fs.Dirent[];
}

Then use it in tests:

-vi.mocked(fs.readdir).mockResolvedValue([fileName, "other-file.pdf"] as any);
+vi.mocked(fs.readdir).mockResolvedValue(createMockDirents([fileName, "other-file.pdf"]));

Note: You'd need to verify that file-retrieval.ts accesses only the name property of Dirent objects for this to work correctly.

libs/publication/src/file-storage/content-type.test.ts (1)

94-96: Consider trimming whitespace for robustness.

The function currently rejects extensions with trailing spaces. While this strict behavior is valid, trimming whitespace could make the function more robust against common input errors without compromising security or correctness.

Example implementation in content-type.ts:
// Normalize by trimming and converting to lowercase
const normalized = extension?.trim().toLowerCase();
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between daffe9e and 27af3b7.

📒 Files selected for processing (2)
  • libs/publication/src/file-storage/content-type.test.ts (1 hunks)
  • libs/publication/src/file-storage/file-retrieval.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/publication/src/file-storage/content-type.test.ts
  • libs/publication/src/file-storage/file-retrieval.test.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use SCREAMING_SNAKE_CASE for constants: MAX_FILE_SIZE, DEFAULT_TIMEOUT

Files:

  • libs/publication/src/file-storage/content-type.test.ts
  • libs/publication/src/file-storage/file-retrieval.test.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/publication/src/file-storage/content-type.test.ts
  • libs/publication/src/file-storage/file-retrieval.test.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Input validation must be performed on all endpoints

Files:

  • libs/publication/src/file-storage/content-type.test.ts
  • libs/publication/src/file-storage/file-retrieval.test.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/publication/src/file-storage/content-type.test.ts
  • libs/publication/src/file-storage/file-retrieval.test.ts
🧠 Learnings (2)
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to **/*.{test,spec}.ts : Test files must be co-located with source code using `*.test.ts` or `*.spec.ts` naming pattern

Applied to files:

  • libs/publication/src/file-storage/content-type.test.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to e2e-tests/**/*.spec.ts : E2E tests must be located in `e2e-tests/` directory with `*.spec.ts` naming pattern

Applied to files:

  • libs/publication/src/file-storage/content-type.test.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). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (9)
libs/publication/src/file-storage/file-retrieval.test.ts (4)

1-12: LGTM! Clean module setup.

The imports follow ES module conventions with proper .js extensions, and the mock setup provides appropriate test isolation for filesystem operations and content-type resolution.


19-129: Excellent test coverage for findFileByArtefactId.

The test suite comprehensively covers:

  • Successful retrieval with various extensions
  • Null returns for not-found scenarios
  • Error handling for both directory and file operations
  • Prefix matching behavior
  • Edge cases (empty directories, multiple matches)

All tests follow clear arrange-act-assert patterns with appropriate assertions.


131-235: Well-structured tests with proper edge case handling.

The test suites for getFileBuffer, getFileExtension, and getContentType appropriately cover:

  • Success paths and expected return values
  • Null/undefined parameter handling with sensible defaults (.pdf extension, application/octet-stream content type)
  • Error scenarios with appropriate fallback behavior
  • Different file types and extensions

237-280: Thorough testing of filename construction with proper default handling.

The getFileName test suite validates:

  • Correct filename assembly with provided extensions
  • Sensible .pdf default for null, undefined, and empty string extensions
  • Support for various file extensions

The explicit testing of falsy values ensures robust defensive behavior.

libs/publication/src/file-storage/content-type.test.ts (5)

1-2: LGTM!

Imports follow ES module conventions correctly, including the .js extension for relative imports.


4-37: LGTM!

Comprehensive coverage of supported file types with both dot and non-dot variations. The MIME types are correct and tests are well-structured.


39-55: LGTM!

Case insensitivity tests properly validate normalization across different case variations and file types.


75-83: No changes needed. The function signature already explicitly declares string | null | undefined as the parameter type, correctly maintaining type safety in strict TypeScript mode. The tests appropriately cover null and undefined cases.


86-88: Test correctly validates intentional behavior. The getContentTypeFromExtension function performs exact-match lookups against the extension map without parsing compound extensions—any unknown extension (single or compound) returns "application/octet-stream". This is consistent with the implementation design.

Comment thread libs/publication/src/file-storage/content-type.test.ts
junaidiqbalmoj and others added 2 commits December 24, 2025 15:11
…ew-publication-flat-files

# Conflicts:
#	apps/web/src/app.test.ts
#	apps/web/src/app.ts
#	libs/public-pages/src/pages/publication/[id].test.ts
#	libs/public-pages/src/pages/publication/[id].ts
#	libs/publication/src/index.ts
#	libs/publication/src/repository/queries.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
libs/publication/src/repository/queries.test.ts (1)

600-614: Avoid as any in new mocks to keep strict typing.

The new tests use as any for mock artefacts. Please switch to a typed mock (e.g., a shared mock factory or a proper Artefact/Prisma model type) to align with strict TS rules. As per coding guidelines, avoid any in TypeScript.

Also applies to: 650-664

Comment thread apps/web/src/app.test.ts
Comment on lines +190 to +191
// Should be called 12 times: location API routes, public pages API routes, system-admin API routes, civil-family-cause-list pages, care-standards-tribunal pages, web pages, auth routes, public pages, verified pages, system-admin pages, admin routes
expect(createSimpleRouter).toHaveBeenCalledTimes(11);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Comment/assertion mismatch.

The comment states "12 times" but the assertion expects 11. The assertion appears correct based on the enumerated routes.

Proposed fix
-      // Should be called 12 times: location API routes, public pages API routes, system-admin API routes, civil-family-cause-list pages, care-standards-tribunal pages, web pages, auth routes, public pages, verified pages, system-admin pages, admin routes
+      // Should be called 11 times: location API routes, public pages API routes, system-admin API routes, civil-family-cause-list pages, care-standards-tribunal pages, web pages, auth routes, public pages, verified pages, system-admin pages, admin routes
       expect(createSimpleRouter).toHaveBeenCalledTimes(11);
📝 Committable suggestion

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

Suggested change
// Should be called 12 times: location API routes, public pages API routes, system-admin API routes, civil-family-cause-list pages, care-standards-tribunal pages, web pages, auth routes, public pages, verified pages, system-admin pages, admin routes
expect(createSimpleRouter).toHaveBeenCalledTimes(11);
// Should be called 11 times: location API routes, public pages API routes, system-admin API routes, civil-family-cause-list pages, care-standards-tribunal pages, web pages, auth routes, public pages, verified pages, system-admin pages, admin routes
expect(createSimpleRouter).toHaveBeenCalledTimes(11);

@sonarqubecloud

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants