Skip to content

Conversation

@junaidiqbalmoj
Copy link
Contributor

@junaidiqbalmoj junaidiqbalmoj commented Dec 1, 2025

Jira link

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

Change description

Approve Media User Application Journey in Admin Dashboard

Summary by CodeRabbit

  • New Features

    • Admin approval workflow for media account requests with approve/approved pages and dashboard notification banner showing pending counts
    • Upload, view and download proof-of-ID files for applications
    • Email notification sent when an application is approved
    • Full Welsh translations across listing, details, approval and confirmation pages
  • Database

    • Media application schema updated to track applicant name, applied date, proof-of-ID path and review metadata
  • Tests

    • New unit and end-to-end tests covering approval flows, access control, accessibility and Welsh content

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

junaidiqbalmoj and others added 4 commits December 1, 2025 10:17
Resolved merge conflict in MediaApplication schema:
- Kept approval flow fields (reviewedDate, reviewedBy, proofOfIdPath)
- Required for VIBE-228 admin approval functionality

Brings in VIBE-175 media account creation form from master.

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

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

coderabbitai bot commented Dec 1, 2025

Walkthrough

Adds a media-application approval feature: DB schema and migrations, admin pages (list, details, approve, approved), file serving and deletion, Prisma queries/services, GOV.UK Notify integration and types, public upload changes (proof file persistence), tests (unit and e2e), a backfill script, and a new notification library.

Changes

Cohort / File(s) Summary
Database Schema & Migrations
apps/postgres/prisma/schema.prisma, apps/postgres/prisma/migrations/...20251201102249_.../migration.sql, ...20251201160103_remove_reviewed_fields_from_media_application/migration.sql, ...20251203102403_add_proof_of_id_original_name/migration.sql
Refactors MediaApplication: rename fullName→name, requestDate/statusDate→appliedDate; add proof_of_id_path and proof_of_id_original_name; change column types/lengths; drop/add reviewed_by/reviewed_date columns via migrations.
Notification Library & Types
libs/notification/package.json, libs/notification/src/*, libs/notification/tsconfig.json, libs/notification/src/global.d.ts, types/notifications-node-client/index.d.ts, libs/admin-pages/src/global.d.ts, package.json, tsconfig.json
New @hmcts/notification package; implements sendMediaApprovalEmail, adds notifications-node-client dependency, and adds ambient/type declarations and tsconfig/paths updates.
Admin: Data Model, Queries & Service
libs/admin-pages/src/media-application/model.ts, .../queries.ts, .../queries.test.ts, .../service.ts, .../service.test.ts
Adds APPLICATION_STATUS, MediaApplication types, Prisma helpers (getPendingApplications, getApplicationById, updateApplicationStatus, getPendingCount), and service functions approveApplication and deleteProofOfIdFile (path validation + fs deletion).
Admin: List Page & Dashboard Integration
libs/admin-pages/src/pages/media-applications/en.ts, .../cy.ts, .../index.ts, .../index.njk, .../index.test.ts, libs/admin-pages/src/pages/admin-dashboard/en.ts, .../cy.ts, .../index.ts, .../index.njk, .../index.test.ts
Pending applications list with translations; dashboard queries pending count for CTSC admins and conditionally renders a notification banner linking to media applications.
Admin: Details, Proof-of-ID Serving & Approval Flow
libs/admin-pages/src/pages/media-applications/[id]/en.ts, .../cy.ts, .../index.ts, .../index.njk, .../index.test.ts, .../proof-of-id.ts, .../proof-of-id.test.ts, .../approve-en.ts, .../approve-cy.ts, .../approve.njk, .../approve.ts, .../approve.test.ts, .../approved-en.ts, .../approved-cy.ts, .../approved.njk, .../approved.ts, .../approved.test.ts
Details page with proof-of-ID link, file-serving endpoint (content-type detection, existence checks), approve confirmation (GET/POST) that calls approveApplication and triggers Notify email, plus approved success page and tests; role guards enforced.
Public Pages: Create Media Account & Storage
libs/public-pages/src/media-application/repository/model.ts, .../query.ts, .../query.test.ts, .../storage.ts, .../storage.test.ts, libs/public-pages/src/pages/create-media-account/index.ts, .../index.njk, .../index.test.ts, libs/public-pages/src/pages/validation.ts
Rename fullName→name across model/validation/controllers; saveIdProofFile now returns saved path; added updateProofOfIdPath to persist proof file info after upload; tests updated accordingly.
Tests & E2E
e2e-tests/tests/media-application-approval.spec.ts, e2e-tests/tests/create-media-account.spec.ts, many new unit tests under libs/admin-pages/src/pages/media-applications/...
Adds comprehensive Playwright e2e tests for approval workflow and numerous Vitest unit tests for pages, proof serving, approval flows, services, and repository changes.
Utilities & Scripts
apps/web/.env.example, scripts/fix-media-application-file-paths.ts, libs/admin-pages/package.json
Adds GOVUK_NOTIFY env entries to example, a backfill script to populate proofOfIdPath from storage, and admin-pages dependency on @hmcts/notification.
Misc. Tests & Small Fixes
apps/api/src/server.test.ts, libs/api/src/middleware/oauth-middleware.ts, libs/admin-pages/src/pages/manual-upload/index.test.ts, libs/web-core/src/..., libs/system-admin-pages/src/..., libs/verified-pages/src/...
Test adjustments: unused param renames (_prefix), mock async resolutions, type casts, richer mock fixtures, minor typing/path fixes; no production logic changes here.

Sequence Diagram(s)

sequenceDiagram
    participant Admin as Admin User
    participant Browser as Web Browser
    participant Server as Express Server
    participant DB as Database (Prisma)
    participant FS as File System
    participant Notify as GOV.UK Notify

    Admin->>Browser: Open pending applications
    Browser->>Server: GET /media-applications
    Server->>DB: getPendingApplications()
    DB-->>Server: list
    Server-->>Browser: Render list (banner if pendingCount>0)

    Admin->>Browser: View application details
    Browser->>Server: GET /media-applications/:id
    Server->>DB: getApplicationById(id)
    DB-->>Server: application
    Server-->>Browser: Render details with proof link

    Admin->>Browser: Open approve page
    Browser->>Server: GET /media-applications/:id/approve
    Server->>DB: getApplicationById(id)
    DB-->>Server: application
    Server-->>Browser: Render confirmation form

    Admin->>Browser: Submit "Yes"
    Browser->>Server: POST /media-applications/:id/approve
    Server->>DB: updateApplicationStatus(id, APPROVED)
    DB-->>Server: updatedApplication
    Server->>FS: deleteProofOfIdFile(path) (if present)
    FS-->>Server: deletion result
    Server->>Notify: sendMediaApprovalEmail({name,email,employer})
    Notify-->>Server: email result
    Server-->>Browser: Redirect to /approved
    Browser->>Server: GET /media-applications/:id/approved
    Server->>DB: getApplicationById(id)
    DB-->>Server: updatedApplication
    Server-->>Browser: Render approved page
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • Path traversal and file-serving security in .../proof-of-id.ts and deleteProofOfIdFile in service.ts.
  • Migration ordering, data migration/backfill and potential data loss from dropped columns.
  • saveIdProofFile now returning a path and correct use of updateProofOfIdPath in public-pages flow.
  • Notify integration: env validation, error handling, and sendEmail parameter mapping/types.
  • Session augmentation and global/type declarations for notifications-node-client.

Possibly related PRs

Suggested reviewers

  • ChrisS1512
  • KianKwa

Poem

🐰
I hopped through fields, both name and date,
A badge approved, no longer wait,
A nibble, a nudge, the file then cleared,
Notify sent — the good news cheered,
Bilingual carrots for every crate!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main objective: implementing the media application approval feature (VIBE-228). It directly reflects the primary change across the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch vibe-228-approve-media-application

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

Resolved yarn.lock conflict and updated dependencies from master.
@github-actions
Copy link
Contributor

github-actions bot commented Dec 1, 2025

🎭 Playwright E2E Test Results

413 tests   413 ✅  33m 25s ⏱️
 24 suites    0 💤
  1 files      0 ❌

Results for commit 8686700.

♻️ This comment has been updated with latest results.

- Update vitest from 3.2.4 to 4.0.6 to match other packages
- Fix test mocks to use class-based approach for vitest 4.0.6
- All notification tests now passing
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

♻️ Duplicate comments (1)
libs/web-core/src/middleware/govuk-frontend/configure-govuk.test.ts (1)

255-255: Same as any concern as on Line 51.

This repeats the direct access to env.loaders via as any. The same optional refactor suggested for Line 51 (using a helper type or public API) would apply here too.

🧹 Nitpick comments (25)
libs/system-admin-pages/src/pages/reference-data-upload-summary/index.test.ts (1)

127-152: Strengthen assertions to verify enriched data is passed to the view

The enriched mock data (including Welsh names, jurisdiction and region arrays) is set up and returned from enrichLocationData, but the test only asserts fileName and hasErrors. To properly guard this flow, consider also asserting that the rendered view receives the enriched data structure (e.g. checking whichever property carries the rows) so regressions in enrichment wiring are caught by this test. Based on learnings, the chosen subJurisdictionNames / regionNames values also correctly avoid semicolons used as CSV delimiters.

libs/web-core/src/middleware/govuk-frontend/configure-govuk.test.ts (1)

51-51: Minimise as any when asserting on env.loaders.

Even in tests, casting to any loses useful type checking. Consider either asserting behaviour via the public Nunjucks API instead of reaching into loaders, or introducing a narrow helper type (e.g. type EnvWithLoaders = Awaited<ReturnType<typeof configureGovuk>> & { loaders: unknown }) and casting to that instead of any.

libs/web-core/src/assets/js/search-autocomplete.test.ts (1)

34-34: Consider a more specific type for source.

While any is acceptable for test mocks, using a more specific type would improve type safety. Based on the accessible-autocomplete library, source is typically a function.

If desired, apply this diff to use a more precise type:

-const mockAccessibleAutocomplete = vi.fn((config: { element: HTMLElement; id: string; source?: any }) => {
+const mockAccessibleAutocomplete = vi.fn((config: { element: HTMLElement; id: string; source?: (query: string, populateResults: (results: unknown[]) => void) => void }) => {
libs/admin-pages/src/pages/manual-upload/index.test.ts (1)

543-561: Async validateForm mocks are consistent with an async validator

Switching the POST-path expectations to use vi.mocked(validateForm).mockResolvedValue(...) is the right shape if validateForm is now async and ensures the handlers under test see a resolved Promise.

If you want to tighten things further, you could also make the module-level mock async for consistency, e.g.:

vi.mock("../../manual-upload/validation.js", () => ({
  validateForm: vi.fn().mockResolvedValue([])
}));

so that any tests which don’t override validateForm still exercise the async code path.

Also applies to: 601-616, 652-662, 693-705, 739-749, 783-789, 823-829, 861-869

libs/admin-pages/src/pages/media-applications/[id]/index.njk (2)

36-47: Refactor complex HTML concatenation and improve accessibility label.

The proof of ID row uses complex inline concatenation that's hard to read and maintain. Additionally, the aria-label is generic.

Consider refactoring to use Nunjucks conditionals:

           {
             key: { text: tableHeaders.proofOfId },
-            value: {
-              html: (
-                '<span>' + proofOfIdText + ' ' if application.proofOfIdPath else ''
-              ) + (
-                '<a href="/media-applications/' + application.id + '/proof-of-id" class="govuk-link" target="_blank" rel="noopener noreferrer" aria-label="Opens in new window">' + viewProofOfId + '</a>' if application.proofOfIdPath else fileNotAvailable
-              ) + (
-                '</span>' if application.proofOfIdPath else ''
-              )
-            }
+            value: {
+              html: (
+                '<span>' + proofOfIdText + ' <a href="/media-applications/' + application.id + '/proof-of-id" class="govuk-link" target="_blank" rel="noopener noreferrer" aria-label="' + viewProofOfId + ' (opens in new window)">' + viewProofOfId + '</a></span>'
+              ) if application.proofOfIdPath else fileNotAvailable
+            }
           }

This also improves the aria-label to describe what opens in a new window.


51-65: Move inline styles to CSS classes.

The button container uses inline styles for flexbox layout, which couples presentation with markup and makes theming difficult.

Define a CSS class in your stylesheet:

.media-application-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 2rem;
}

Then update the template:

-      <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 2rem;">
+      <div class="media-application-actions">
docs/tickets/VIBE-228/plan.md (2)

34-38: Add language identifier to fenced code block.

The module structure code block (lines 34-38) should specify a language for syntax highlighting.

-```
+```plaintext
 media-application/

Based on learnings, as per static analysis hints.


55-61: Add blank lines around the table.

The page routes table should be surrounded by blank lines for proper Markdown rendering.

Add a blank line before line 55 and after line 61.

Based on learnings, as per static analysis hints.

libs/admin-pages/src/pages/admin-dashboard/index.test.ts (1)

15-22: Prisma mock for mediaApplication.count is appropriate and isolates tests.

Mocking @hmcts/postgres with prisma.mediaApplication.count resolving to 0 matches the query layer shape and keeps these tests independent of a real database. If you later want to assert the notification banner for pending applications, you can override this mock per test to return a non-zero count.

scripts/fix-media-application-file-paths.ts (2)

11-56: Script logic cleanly reconciles DB rows with on-disk files.

The use of prisma.mediaApplication.findMany/update with a fixed set of extensions and fs.access keeps everything parameterised and avoids raw SQL, which matches the database guidelines. As a small tidy-up, you could hoist the extensions array to a module-level constant (e.g. const FILE_EXTENSIONS = [...]) to avoid reallocating it per application and better follow the “constants at the top” convention.


59-66: Prefer process.exitCode over process.exit(1) so Prisma disconnect always runs.

Calling process.exit(1) inside the catch means the subsequent finally block might not complete, so prisma.$disconnect() may be skipped on errors; switching to process.exitCode = 1 preserves a non‑zero exit status while still allowing the finally block to run to completion.

libs/admin-pages/src/pages/admin-dashboard/index.ts (1)

3-23: GET handler correctly wires pending media count and notification content.

getPendingCount is only awaited for INTERNAL_ADMIN_CTSC, keeping DB access scoped to the role that needs it, and the handler now passes pendingCount plus language-specific notificationText/notificationLink into the view while preserving the existing tile-filtering logic. As an optional follow‑up, you could add a dedicated test that sets the mocked count > 0 and asserts the banner content in the rendered context.

libs/admin-pages/src/pages/admin-dashboard/index.njk (1)

10-26: Consider extracting hardcoded strings to content objects.

The notification banner works correctly but has two hardcoded values:

  • Line 15: "Important" title
  • Line 20: "/media-applications" URL

For consistency with i18n best practices and maintainability, consider moving these to the content objects (en.ts/cy.ts).

libs/admin-pages/src/pages/media-applications/[id]/approved.njk (1)

8-14: Consider using govukErrorSummary macro for consistency.

The error display uses a custom div structure. While functional, using the govukErrorSummary macro would be more consistent with GOV.UK Design System patterns.

libs/admin-pages/src/pages/media-applications/[id]/index.ts (1)

8-49: Consider logging the error for debugging purposes.

The error is silently caught and discarded with _error. While the user sees a friendly error message, logging the error would help with debugging in production.

-  } catch (_error) {
+  } catch (error) {
+    console.error("Failed to load media application:", error);
     res.render("media-applications/[id]/index", {
       pageTitle: lang.pageTitle,
       error: lang.errorMessages.loadFailed,
       application: null,
       hideLanguageToggle: true
     });
   }
libs/public-pages/src/pages/create-media-account/index.ts (2)

71-74: Consider handling the case where file upload succeeds but path update fails.

If updateProofOfIdPath throws, the application is created but without the proof of ID path. The file exists on storage but the database reference is lost. Consider wrapping this in a transaction or handling the partial failure gracefully.

     if (file) {
-      const filePath = await saveIdProofFile(applicationId, file.originalname, file.buffer);
-      await updateProofOfIdPath(applicationId, filePath);
+      try {
+        const filePath = await saveIdProofFile(applicationId, file.originalname, file.buffer);
+        await updateProofOfIdPath(applicationId, filePath);
+      } catch (fileError) {
+        console.error("Failed to save proof of ID file:", fileError);
+        // Application created but file not linked - consider cleanup or retry logic
+      }
     }

Alternatively, consider using a database transaction that includes the application creation and path update together.


82-84: Avoid logging the full error object which may contain sensitive information.

The error object could contain user data or internal details. Consider logging only the error message.

As per coding guidelines about not putting sensitive data in logs:

   } catch (error) {
-    console.error("Error creating media application:", error);
+    console.error("Error creating media application:", error instanceof Error ? error.message : "Unknown error");
     res.status(500).render("errors/500", { locale });
   }
libs/admin-pages/src/media-application/queries.test.ts (1)

1-154: Good coverage and accurate Prisma call assertions

The tests exercise all query helpers, including success and not-found/zero-count paths, and assert the exact Prisma where/select/orderBy shapes. Using expect.any(Date) for reviewedDate keeps updateApplicationStatus tests robust. If you find these tests growing, you could consider a small factory/helper for mock applications to avoid repetition, but it’s not necessary right now.

libs/admin-pages/src/media-application/service.test.ts (1)

1-126: Solid coverage of approval flow and file deletion edge cases

These tests exercise all key branches of approveApplication (happy path, no file, not found, already reviewed) and deleteProofOfIdFile (valid path, traversal attempt, ENOENT, other fs errors). The expectations line up with the implementation, including specific error messages and the ENOENT swallow behavior.

If proofOfIdPath can ever be influenced by user-controlled data or external systems, you might later want to harden deleteProofOfIdFile further by constraining deletions to a configured base directory in service.ts (e.g., resolving against a known root) and adding a test for that. As it stands, for internally-generated paths this is sufficient.

libs/notification/src/govuk-notify-service.ts (1)

12-32: Consider validating environment variables at module initialization.

Currently, environment variables are validated when the function is called. Validating at module load time would fail fast at startup rather than during runtime.

Consider moving the validation to module level:

+if (!process.env.GOVUK_NOTIFY_API_KEY) {
+  throw new Error("GOV Notify API key not configured");
+}
+
+if (!process.env.GOVUK_NOTIFY_TEMPLATE_ID_MEDIA_APPROVAL) {
+  throw new Error("GOV Notify approval template ID not configured");
+}
+
 const GOVUK_NOTIFY_API_KEY = process.env.GOVUK_NOTIFY_API_KEY;
 const TEMPLATE_ID_MEDIA_APPROVAL = process.env.GOVUK_NOTIFY_TEMPLATE_ID_MEDIA_APPROVAL;

-export async function sendMediaApprovalEmail(data: MediaApplicationEmailData): Promise<void> {
-  if (!GOVUK_NOTIFY_API_KEY) {
-    throw new Error("GOV Notify API key not configured");
-  }
-
-  if (!TEMPLATE_ID_MEDIA_APPROVAL) {
-    throw new Error("GOV Notify approval template ID not configured");
-  }
-
+export async function sendMediaApprovalEmail(data: MediaApplicationEmailData): Promise<void> {
   const notifyClient = new NotifyClient(GOVUK_NOTIFY_API_KEY);
libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.ts (1)

45-46: Consider streaming files instead of buffering for better memory efficiency.

Currently, the entire file is read into memory before sending. For large proof-of-ID files, this could consume significant memory. Consider using fs.createReadStream() and pipe() for better performance.

-const fileBuffer = await fs.readFile(filePath);
-res.send(fileBuffer);
+const readStream = createReadStream(filePath);
+readStream.pipe(res);

Don't forget to import createReadStream:

-import fs from "node:fs/promises";
+import { createReadStream } from "node:fs";
+import fs from "node:fs/promises";
libs/admin-pages/src/media-application/queries.ts (2)

42-42: Use ApplicationStatus type instead of string for the status parameter.

The status parameter accepts any string, but should be constrained to valid status values. Using the ApplicationStatus type from your model ensures compile-time safety.

+import { APPLICATION_STATUS, type ApplicationStatus, type MediaApplicationDetails, type PendingApplicationSummary } from "./model.js";
-import { APPLICATION_STATUS, type MediaApplicationDetails, type PendingApplicationSummary } from "./model.js";
...
-export async function updateApplicationStatus(id: string, status: string, reviewedBy: string): Promise<MediaApplicationDetails> {
+export async function updateApplicationStatus(id: string, status: ApplicationStatus, reviewedBy: string): Promise<MediaApplicationDetails> {

39-39: Type assertion may mask schema-model drift.

The as MediaApplicationDetails assertion assumes Prisma's return type matches your interface. If the schema changes, this could silently produce incorrect types. Consider using Prisma's generated types or a validation layer.

apps/postgres/prisma/schema.prisma (1)

46-58: Consider adding an index on the status field for query performance.

The getPendingApplications and getPendingCount queries filter by status. As the table grows, an index would improve query performance.

   reviewedBy       String?   @map("reviewed_by") @db.VarChar(255)

+  @@index([status])
   @@map("media_application")
 }
e2e-tests/tests/media-application-approval.spec.ts (1)

208-212: Document the rationale for disabling accessibility rules.

Disabling target-size, link-name, and region rules across all accessibility tests reduces coverage. Consider adding a comment explaining why these rules are disabled (e.g., known GOV.UK Design System limitations) or creating issues to address them.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4ca84a and 12781f0.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (65)
  • apps/api/src/server.test.ts (1 hunks)
  • apps/postgres/prisma/migrations/20251201102249_media_application/migration.sql (1 hunks)
  • apps/postgres/prisma/schema.prisma (1 hunks)
  • apps/web/.env.example (1 hunks)
  • docs/tickets/VIBE-228/plan.md (1 hunks)
  • docs/tickets/VIBE-228/tasks.md (1 hunks)
  • docs/tickets/VIBE-228/ticket.md (1 hunks)
  • e2e-tests/tests/media-application-approval.spec.ts (1 hunks)
  • libs/admin-pages/package.json (1 hunks)
  • libs/admin-pages/src/media-application/model.ts (1 hunks)
  • libs/admin-pages/src/media-application/queries.test.ts (1 hunks)
  • libs/admin-pages/src/media-application/queries.ts (1 hunks)
  • libs/admin-pages/src/media-application/service.test.ts (1 hunks)
  • libs/admin-pages/src/media-application/service.ts (1 hunks)
  • libs/admin-pages/src/pages/admin-dashboard/cy.ts (1 hunks)
  • libs/admin-pages/src/pages/admin-dashboard/en.ts (1 hunks)
  • libs/admin-pages/src/pages/admin-dashboard/index.njk (1 hunks)
  • libs/admin-pages/src/pages/admin-dashboard/index.test.ts (1 hunks)
  • libs/admin-pages/src/pages/admin-dashboard/index.ts (2 hunks)
  • libs/admin-pages/src/pages/manual-upload/index.test.ts (6 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approve-en.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approve.njk (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approved-cy.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approved-en.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approved.njk (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approved.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/cy.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/en.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/index.njk (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/index.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/cy.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/en.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/index.njk (1 hunks)
  • libs/admin-pages/src/pages/media-applications/index.ts (1 hunks)
  • libs/api/src/middleware/oauth-middleware.ts (1 hunks)
  • libs/notification/package.json (1 hunks)
  • libs/notification/src/govuk-notify-service.test.ts (1 hunks)
  • libs/notification/src/govuk-notify-service.ts (1 hunks)
  • libs/notification/src/index.ts (1 hunks)
  • libs/notification/src/notifications-node-client.d.ts (1 hunks)
  • libs/notification/tsconfig.json (1 hunks)
  • libs/public-pages/src/media-application/repository/model.ts (2 hunks)
  • libs/public-pages/src/media-application/repository/query.test.ts (3 hunks)
  • libs/public-pages/src/media-application/repository/query.ts (1 hunks)
  • libs/public-pages/src/media-application/storage.test.ts (1 hunks)
  • libs/public-pages/src/media-application/storage.ts (1 hunks)
  • libs/public-pages/src/pages/create-media-account/index.njk (1 hunks)
  • libs/public-pages/src/pages/create-media-account/index.test.ts (9 hunks)
  • libs/public-pages/src/pages/create-media-account/index.ts (3 hunks)
  • libs/public-pages/src/pages/validation.ts (2 hunks)
  • libs/system-admin-pages/src/pages/reference-data-upload-summary/index.test.ts (2 hunks)
  • libs/system-admin-pages/src/reference-data-upload/repository/upload-repository.test.ts (1 hunks)
  • libs/verified-pages/src/pages/delete-subscription/index.test.ts (5 hunks)
  • libs/verified-pages/src/pages/subscription-management/index.test.ts (1 hunks)
  • libs/verified-pages/src/pages/unsubscribe-confirmation/index.test.ts (1 hunks)
  • libs/web-core/src/assets/js/search-autocomplete.test.ts (2 hunks)
  • libs/web-core/src/middleware/file-upload/file-upload-middleware.test.ts (1 hunks)
  • libs/web-core/src/middleware/govuk-frontend/configure-govuk.test.ts (3 hunks)
  • libs/web-core/src/middleware/helmet/helmet-middleware.test.ts (1 hunks)
  • package.json (1 hunks)
  • scripts/fix-media-application-file-paths.ts (1 hunks)
  • tsconfig.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
libs/*/package.json

📄 CodeRabbit inference engine (CLAUDE.md)

libs/*/package.json: Package names must use @hmcts scope (e.g., @hmcts/auth, @hmcts/case-management).
Module package.json must include both main export (.) and config export (./config) in the exports field, with production and default conditions.
Module package.json build script must include build:nunjucks if the module contains Nunjucks templates in the pages/ directory.
All test packages must use "test": "vitest run" script to run tests.

Files:

  • libs/admin-pages/package.json
  • libs/notification/package.json
**/*.{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.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
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.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. 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.

Files:

  • libs/web-core/src/middleware/govuk-frontend/configure-govuk.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved.ts
  • libs/admin-pages/src/pages/admin-dashboard/index.test.ts
  • libs/verified-pages/src/pages/delete-subscription/index.test.ts
  • libs/admin-pages/src/pages/admin-dashboard/cy.ts
  • apps/api/src/server.test.ts
  • libs/web-core/src/middleware/helmet/helmet-middleware.test.ts
  • libs/web-core/src/assets/js/search-autocomplete.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/cy.ts
  • libs/public-pages/src/pages/validation.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts
  • libs/admin-pages/src/pages/manual-upload/index.test.ts
  • libs/system-admin-pages/src/pages/reference-data-upload-summary/index.test.ts
  • scripts/fix-media-application-file-paths.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved-cy.ts
  • libs/web-core/src/middleware/file-upload/file-upload-middleware.test.ts
  • libs/admin-pages/src/pages/admin-dashboard/en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.ts
  • libs/admin-pages/src/media-application/queries.ts
  • libs/notification/src/govuk-notify-service.ts
  • libs/notification/src/govuk-notify-service.test.ts
  • libs/public-pages/src/media-application/repository/query.ts
  • libs/admin-pages/src/media-application/queries.test.ts
  • libs/verified-pages/src/pages/subscription-management/index.test.ts
  • e2e-tests/tests/media-application-approval.spec.ts
  • libs/notification/src/notifications-node-client.d.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts
  • libs/admin-pages/src/pages/media-applications/cy.ts
  • libs/admin-pages/src/media-application/service.ts
  • libs/admin-pages/src/pages/media-applications/[id]/index.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved-en.ts
  • libs/public-pages/src/media-application/repository/model.ts
  • libs/admin-pages/src/pages/media-applications/[id]/en.ts
  • libs/public-pages/src/pages/create-media-account/index.ts
  • libs/admin-pages/src/media-application/model.ts
  • libs/notification/src/index.ts
  • libs/public-pages/src/media-application/storage.test.ts
  • libs/public-pages/src/pages/create-media-account/index.test.ts
  • libs/admin-pages/src/pages/media-applications/index.ts
  • libs/verified-pages/src/pages/unsubscribe-confirmation/index.test.ts
  • libs/api/src/middleware/oauth-middleware.ts
  • libs/public-pages/src/media-application/storage.ts
  • libs/system-admin-pages/src/reference-data-upload/repository/upload-repository.test.ts
  • libs/public-pages/src/media-application/repository/query.test.ts
  • libs/admin-pages/src/media-application/service.test.ts
  • libs/admin-pages/src/pages/admin-dashboard/index.ts
  • libs/admin-pages/src/pages/media-applications/en.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • libs/web-core/src/middleware/govuk-frontend/configure-govuk.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved.ts
  • libs/admin-pages/src/pages/admin-dashboard/index.test.ts
  • libs/verified-pages/src/pages/delete-subscription/index.test.ts
  • libs/admin-pages/src/pages/admin-dashboard/cy.ts
  • apps/api/src/server.test.ts
  • libs/web-core/src/middleware/helmet/helmet-middleware.test.ts
  • libs/web-core/src/assets/js/search-autocomplete.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/cy.ts
  • libs/public-pages/src/pages/validation.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts
  • libs/admin-pages/src/pages/manual-upload/index.test.ts
  • libs/system-admin-pages/src/pages/reference-data-upload-summary/index.test.ts
  • scripts/fix-media-application-file-paths.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved-cy.ts
  • libs/web-core/src/middleware/file-upload/file-upload-middleware.test.ts
  • libs/admin-pages/src/pages/admin-dashboard/en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.ts
  • libs/admin-pages/src/media-application/queries.ts
  • libs/notification/src/govuk-notify-service.ts
  • libs/notification/src/govuk-notify-service.test.ts
  • libs/public-pages/src/media-application/repository/query.ts
  • libs/admin-pages/src/media-application/queries.test.ts
  • libs/verified-pages/src/pages/subscription-management/index.test.ts
  • e2e-tests/tests/media-application-approval.spec.ts
  • libs/notification/src/notifications-node-client.d.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts
  • libs/admin-pages/src/pages/media-applications/cy.ts
  • libs/admin-pages/src/media-application/service.ts
  • libs/admin-pages/src/pages/media-applications/[id]/index.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved-en.ts
  • libs/public-pages/src/media-application/repository/model.ts
  • libs/admin-pages/src/pages/media-applications/[id]/en.ts
  • libs/public-pages/src/pages/create-media-account/index.ts
  • libs/admin-pages/src/media-application/model.ts
  • libs/notification/src/index.ts
  • libs/public-pages/src/media-application/storage.test.ts
  • libs/public-pages/src/pages/create-media-account/index.test.ts
  • libs/admin-pages/src/pages/media-applications/index.ts
  • libs/verified-pages/src/pages/unsubscribe-confirmation/index.test.ts
  • libs/api/src/middleware/oauth-middleware.ts
  • libs/public-pages/src/media-application/storage.ts
  • libs/system-admin-pages/src/reference-data-upload/repository/upload-repository.test.ts
  • libs/public-pages/src/media-application/repository/query.test.ts
  • libs/admin-pages/src/media-application/service.test.ts
  • libs/admin-pages/src/pages/admin-dashboard/index.ts
  • libs/admin-pages/src/pages/media-applications/en.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • libs/web-core/src/middleware/govuk-frontend/configure-govuk.test.ts
  • libs/admin-pages/src/pages/admin-dashboard/index.test.ts
  • libs/verified-pages/src/pages/delete-subscription/index.test.ts
  • apps/api/src/server.test.ts
  • libs/web-core/src/middleware/helmet/helmet-middleware.test.ts
  • libs/web-core/src/assets/js/search-autocomplete.test.ts
  • libs/admin-pages/src/pages/manual-upload/index.test.ts
  • libs/system-admin-pages/src/pages/reference-data-upload-summary/index.test.ts
  • libs/web-core/src/middleware/file-upload/file-upload-middleware.test.ts
  • libs/notification/src/govuk-notify-service.test.ts
  • libs/admin-pages/src/media-application/queries.test.ts
  • libs/verified-pages/src/pages/subscription-management/index.test.ts
  • e2e-tests/tests/media-application-approval.spec.ts
  • libs/public-pages/src/media-application/storage.test.ts
  • libs/public-pages/src/pages/create-media-account/index.test.ts
  • libs/verified-pages/src/pages/unsubscribe-confirmation/index.test.ts
  • libs/system-admin-pages/src/reference-data-upload/repository/upload-repository.test.ts
  • libs/public-pages/src/media-application/repository/query.test.ts
  • libs/admin-pages/src/media-application/service.test.ts
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/pages/**/*.ts: Page controllers must export named GET and/or POST functions with Express Request and Response types.
Every page must support both English and Welsh with separate en and cy content objects in the controller, and Welsh content must be tested with ?lng=cy query parameter.

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/approved.ts
  • libs/admin-pages/src/pages/admin-dashboard/index.test.ts
  • libs/verified-pages/src/pages/delete-subscription/index.test.ts
  • libs/admin-pages/src/pages/admin-dashboard/cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/cy.ts
  • libs/public-pages/src/pages/validation.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts
  • libs/admin-pages/src/pages/manual-upload/index.test.ts
  • libs/system-admin-pages/src/pages/reference-data-upload-summary/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved-cy.ts
  • libs/admin-pages/src/pages/admin-dashboard/en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.ts
  • libs/verified-pages/src/pages/subscription-management/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts
  • libs/admin-pages/src/pages/media-applications/cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/index.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/en.ts
  • libs/public-pages/src/pages/create-media-account/index.ts
  • libs/public-pages/src/pages/create-media-account/index.test.ts
  • libs/admin-pages/src/pages/media-applications/index.ts
  • libs/verified-pages/src/pages/unsubscribe-confirmation/index.test.ts
  • libs/admin-pages/src/pages/admin-dashboard/index.ts
  • libs/admin-pages/src/pages/media-applications/en.ts
**/pages/**/*.njk

📄 CodeRabbit inference engine (CLAUDE.md)

Nunjucks templates must extend layouts/base-templates.njk and use GOV.UK component macros (govukButton, govukInput, govukErrorSummary, etc.).

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.njk
  • libs/admin-pages/src/pages/media-applications/[id]/index.njk
  • libs/public-pages/src/pages/create-media-account/index.njk
  • libs/admin-pages/src/pages/media-applications/index.njk
  • libs/admin-pages/src/pages/admin-dashboard/index.njk
  • libs/admin-pages/src/pages/media-applications/[id]/approved.njk
**/tsconfig.json

📄 CodeRabbit inference engine (CLAUDE.md)

TypeScript must use strict mode with no any without justification.

Files:

  • tsconfig.json
  • libs/notification/tsconfig.json
package.json

📄 CodeRabbit inference engine (CLAUDE.md)

package.json: All package.json files must use "type": "module" to enforce ES Modules.
Dependencies must be pinned to specific versions (e.g., "express": "5.1.0"), except for peer dependencies.

Files:

  • package.json
libs/*/tsconfig.json

📄 CodeRabbit inference engine (CLAUDE.md)

Module tsconfig.json must extend the root tsconfig.json with outDir, rootDir, and include/exclude declarations.

Files:

  • libs/notification/tsconfig.json
libs/**/*-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/api/src/middleware/oauth-middleware.ts
**/prisma/schema.prisma

📄 CodeRabbit inference engine (CLAUDE.md)

Database tables and fields MUST be singular and snake_case (e.g., user, case, created_at). Use Prisma @@map and @map for aliases.

Files:

  • apps/postgres/prisma/schema.prisma
🧠 Learnings (18)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/package.json : Package names must use hmcts scope (e.g., `hmcts/auth`, `hmcts/case-management`).

Applied to files:

  • libs/admin-pages/package.json
  • tsconfig.json
  • libs/notification/package.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Use workspace aliases (`hmcts/*`) for imports instead of relative paths across packages.

Applied to files:

  • libs/admin-pages/package.json
  • tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.ts : Page controllers must export named `GET` and/or `POST` functions with Express Request and Response types.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approved.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts
  • libs/admin-pages/src/pages/media-applications/[id]/index.ts
  • libs/admin-pages/src/pages/media-applications/index.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/**/locales/**/*.ts : Shared content (common button text, phase banners, service names, common error messages) goes in locale files (`libs/[module]/src/locales/en.ts` and `cy.ts`). Page-specific content goes in controllers.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approved.ts
  • libs/admin-pages/src/pages/admin-dashboard/cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved-cy.ts
  • libs/admin-pages/src/pages/admin-dashboard/en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts
  • libs/admin-pages/src/pages/media-applications/cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/en.ts
  • libs/admin-pages/src/pages/media-applications/en.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.ts : Every page must support both English and Welsh with separate `en` and `cy` content objects in the controller, and Welsh content must be tested with `?lng=cy` query parameter.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved-cy.ts
  • e2e-tests/tests/media-application-approval.spec.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts
  • libs/admin-pages/src/pages/media-applications/cy.ts
📚 Learning: 2025-11-20T09:59:16.776Z
Learnt from: junaidiqbalmoj
Repo: hmcts/cath-service PR: 106
File: libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts:84-160
Timestamp: 2025-11-20T09:59:16.776Z
Learning: In the cath-service repository, Welsh localization (lng=cy) is not required for admin screens (system-admin-pages), so locale preservation in admin screen redirects is not necessary.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts
  • libs/admin-pages/src/pages/media-applications/cy.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Don't skip Welsh translations - required for all user-facing text.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts
  • libs/admin-pages/src/pages/media-applications/cy.ts
📚 Learning: 2025-11-20T10:19:35.873Z
Learnt from: junaidiqbalmoj
Repo: hmcts/cath-service PR: 106
File: libs/system-admin-pages/src/reference-data-upload/services/download-service.ts:25-27
Timestamp: 2025-11-20T10:19:35.873Z
Learning: In the HMCTS cath-service project, region and sub-jurisdiction names are managed manually and must not contain semicolons, as semicolons are used as delimiters when joining multiple values in CSV export/import operations (specifically in libs/system-admin-pages/src/reference-data-upload/services/download-service.ts).

Applied to files:

  • libs/system-admin-pages/src/pages/reference-data-upload-summary/index.test.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Database queries must be parameterized using Prisma (never raw SQL with string concatenation).

Applied to files:

  • scripts/fix-media-application-file-paths.ts
  • libs/admin-pages/src/media-application/queries.ts
  • libs/admin-pages/src/media-application/queries.test.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.njk : Nunjucks templates must extend `layouts/base-templates.njk` and use GOV.UK component macros (govukButton, govukInput, govukErrorSummary, etc.).

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.njk
  • libs/admin-pages/src/pages/media-applications/[id]/index.njk
  • libs/admin-pages/src/pages/media-applications/index.njk
  • libs/admin-pages/src/pages/media-applications/[id]/approved.njk
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/tsconfig.json : Module tsconfig.json must extend the root tsconfig.json with outDir, rootDir, and include/exclude declarations.

Applied to files:

  • tsconfig.json
  • libs/notification/tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/src/config.ts : Config exports (pageRoutes, apiRoutes, prismaSchemas, assets) must be in a separate `config.ts` file to avoid circular dependencies during Prisma client generation. Apps import config using the `/config` path.

Applied to files:

  • tsconfig.json
  • libs/admin-pages/src/media-application/queries.ts
  • libs/notification/tsconfig.json
  • libs/admin-pages/src/pages/media-applications/index.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{test,spec}.ts : Test files must be co-located with source files using the pattern `*.test.ts` or `*.spec.ts` and use Vitest.

Applied to files:

  • tsconfig.json
  • e2e-tests/tests/media-application-approval.spec.ts
  • libs/notification/tsconfig.json
  • libs/public-pages/src/media-application/repository/query.test.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : 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.

Applied to files:

  • libs/notification/tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.

Applied to files:

  • libs/notification/tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/tsconfig.json : TypeScript must use strict mode with no `any` without justification.

Applied to files:

  • libs/notification/tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/package.json : Module package.json must include both main export (`.`) and config export (`./config`) in the exports field, with production and default conditions.

Applied to files:

  • libs/notification/package.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/prisma/schema.prisma : Database tables and fields MUST be singular and snake_case (e.g., `user`, `case`, `created_at`). Use Prisma `@map` and `map` for aliases.

Applied to files:

  • apps/postgres/prisma/schema.prisma
🧬 Code graph analysis (12)
libs/admin-pages/src/pages/media-applications/[id]/approved.ts (5)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-40)
libs/admin-pages/src/pages/media-applications/[id]/approve.ts (1)
  • GET (102-102)
libs/admin-pages/src/pages/admin-dashboard/index.ts (1)
  • GET (27-27)
libs/admin-pages/src/pages/media-applications/[id]/index.ts (1)
  • GET (51-51)
libs/admin-pages/src/pages/media-applications/index.ts (1)
  • GET (31-31)
libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.ts (2)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-40)
libs/admin-pages/src/pages/media-applications/[id]/approve.ts (1)
  • GET (102-102)
libs/admin-pages/src/media-application/queries.ts (1)
libs/admin-pages/src/media-application/model.ts (3)
  • PendingApplicationSummary (21-26)
  • APPLICATION_STATUS (1-5)
  • MediaApplicationDetails (9-19)
libs/notification/src/govuk-notify-service.test.ts (2)
libs/notification/src/govuk-notify-service.ts (1)
  • sendMediaApprovalEmail (12-32)
libs/notification/src/index.ts (1)
  • sendMediaApprovalEmail (1-1)
e2e-tests/tests/media-application-approval.spec.ts (1)
e2e-tests/utils/sso-helpers.ts (1)
  • loginWithSSO (10-46)
libs/admin-pages/src/pages/media-applications/[id]/index.ts (6)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-40)
libs/admin-pages/src/media-application/model.ts (1)
  • APPLICATION_STATUS (1-5)
libs/admin-pages/src/pages/media-applications/[id]/approve.ts (1)
  • GET (102-102)
libs/admin-pages/src/pages/admin-dashboard/index.ts (1)
  • GET (27-27)
libs/admin-pages/src/pages/media-applications/[id]/approved.ts (1)
  • GET (39-39)
libs/admin-pages/src/pages/media-applications/index.ts (1)
  • GET (31-31)
libs/public-pages/src/media-application/storage.test.ts (1)
libs/public-pages/src/media-application/storage.ts (1)
  • saveIdProofFile (6-16)
libs/public-pages/src/pages/create-media-account/index.test.ts (4)
libs/public-pages/src/media-application/storage.ts (1)
  • saveIdProofFile (6-16)
libs/public-pages/src/pages/create-media-account/index.ts (1)
  • POST (39-86)
libs/public-pages/src/media-application/repository/model.ts (1)
  • MulterRequest (30-32)
libs/public-pages/src/media-application/repository/query.ts (2)
  • createMediaApplication (4-15)
  • updateProofOfIdPath (17-22)
libs/admin-pages/src/pages/media-applications/index.ts (1)
libs/admin-pages/src/media-application/queries.ts (1)
  • getPendingApplications (4-21)
libs/public-pages/src/media-application/repository/query.test.ts (2)
libs/public-pages/src/media-application/repository/query.ts (2)
  • createMediaApplication (4-15)
  • updateProofOfIdPath (17-22)
libs/public-pages/src/media-application/repository/model.ts (1)
  • MediaApplicationCreateData (16-20)
libs/admin-pages/src/media-application/service.test.ts (2)
libs/admin-pages/src/media-application/model.ts (1)
  • APPLICATION_STATUS (1-5)
libs/admin-pages/src/media-application/service.ts (2)
  • approveApplication (6-22)
  • deleteProofOfIdFile (24-37)
libs/admin-pages/src/pages/admin-dashboard/index.ts (1)
libs/admin-pages/src/media-application/queries.ts (1)
  • getPendingCount (66-74)
🪛 ast-grep (0.40.0)
e2e-tests/tests/media-application-approval.spec.ts

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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)

🪛 Gitleaks (8.29.1)
libs/notification/src/govuk-notify-service.test.ts

[high] 4-4: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 markdownlint-cli2 (0.18.1)
docs/tickets/VIBE-228/plan.md

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

(MD040, fenced-code-language)


55-55: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


64-64: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


70-70: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ 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

- Create global types directory with notifications-node-client declarations
- Add typeRoots to tsconfig.json to include custom types
- Update notification build script to copy declaration file
- Fixes TypeScript compilation error in admin-pages build
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
libs/notification/src/govuk-notify-service.test.ts (2)

21-110: Consider adding tests for environment variable validation.

The service implementation validates that both GOVUK_NOTIFY_API_KEY and GOVUK_NOTIFY_TEMPLATE_ID_MEDIA_APPROVAL are configured (see libs/notification/src/govuk-notify-service.ts lines 12-17), but there are no tests verifying these error cases.

Consider adding tests like:

it("should throw error when API key is not configured", async () => {
  // Would need to re-import with different env vars or mock differently
  // This demonstrates the intent
});

it("should throw error when template ID is not configured", async () => {
  // Similar test for template ID validation
});

Note: These tests may require restructuring the test file to support different environment configurations, or refactoring the service to accept configuration as a parameter for better testability.


46-50: Optional: Extract duplicated test data.

The test data object is duplicated across all three test cases. Consider extracting it to a constant for better maintainability.

+const TEST_MEDIA_APPLICATION_DATA = {
+  name: "John Smith",
+  email: "[email protected]",
+  employer: "BBC"
+};
+
 describe("sendMediaApprovalEmail", () => {
   it("should send email with correct parameters", async () => {
     // ...
-    const testData = {
-      name: "John Smith",
-      email: "[email protected]",
-      employer: "BBC"
-    };
-
-    await sendMediaApprovalEmail(testData);
+    await sendMediaApprovalEmail(TEST_MEDIA_APPLICATION_DATA);

Apply similar changes to the other test cases.

Also applies to: 86-90, 101-105

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12781f0 and 882fbf5.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • libs/notification/package.json (1 hunks)
  • libs/notification/src/govuk-notify-service.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/notification/package.json
🧰 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.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
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.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. 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.

Files:

  • libs/notification/src/govuk-notify-service.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • libs/notification/src/govuk-notify-service.test.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • libs/notification/src/govuk-notify-service.test.ts
🧬 Code graph analysis (1)
libs/notification/src/govuk-notify-service.test.ts (1)
libs/notification/src/govuk-notify-service.ts (1)
  • sendMediaApprovalEmail (12-32)
🪛 Gitleaks (8.29.1)
libs/notification/src/govuk-notify-service.test.ts

[high] 4-4: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (4)
libs/notification/src/govuk-notify-service.test.ts (4)

1-19: LGTM! Proper test setup and mocking strategy.

The environment variable configuration before import, mock setup, and dynamic import sequence correctly ensure that the module under test uses the mocked dependencies and test configuration.


21-24: LGTM! Clean test suite structure.

The beforeEach hook properly clears mocks between tests to ensure test isolation.


27-65: LGTM! Comprehensive happy path test.

The test correctly verifies that sendMediaApprovalEmail calls the Notify API with the expected template ID, recipient email, personalisation data, and reference format.


67-108: LGTM! Good error handling coverage.

Both API errors and network errors are properly tested to ensure they propagate correctly from the Notify client.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
libs/notification/package.json (1)

26-26: Consider using semantic versioning for vitest.

The vitest dependency is pinned to an exact version (4.0.6) instead of using semantic versioning (^4.0.6), which is inconsistent with the other devDependencies. While exact pinning can ensure reproducibility, consider aligning with the versioning strategy used for other dependencies unless there's a specific reason for the pin.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 882fbf5 and 9da207d.

📒 Files selected for processing (3)
  • libs/notification/package.json (1 hunks)
  • tsconfig.json (2 hunks)
  • types/notifications-node-client/index.d.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 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.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
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.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. 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.

Files:

  • types/notifications-node-client/index.d.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • types/notifications-node-client/index.d.ts
libs/*/package.json

📄 CodeRabbit inference engine (CLAUDE.md)

libs/*/package.json: Package names must use @hmcts scope (e.g., @hmcts/auth, @hmcts/case-management).
Module package.json must include both main export (.) and config export (./config) in the exports field, with production and default conditions.
Module package.json build script must include build:nunjucks if the module contains Nunjucks templates in the pages/ directory.
All test packages must use "test": "vitest run" script to run tests.

Files:

  • libs/notification/package.json
**/tsconfig.json

📄 CodeRabbit inference engine (CLAUDE.md)

TypeScript must use strict mode with no any without justification.

Files:

  • tsconfig.json
🧠 Learnings (8)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/package.json : Package names must use hmcts scope (e.g., `hmcts/auth`, `hmcts/case-management`).

Applied to files:

  • libs/notification/package.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Use workspace aliases (`hmcts/*`) for imports instead of relative paths across packages.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/tsconfig.json : Module tsconfig.json must extend the root tsconfig.json with outDir, rootDir, and include/exclude declarations.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/tsconfig.json : TypeScript must use strict mode with no `any` without justification.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : 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.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to package.json : All package.json files must use `"type": "module"` to enforce ES Modules.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{test,spec}.ts : Test files must be co-located with source files using the pattern `*.test.ts` or `*.spec.ts` and use Vitest.

Applied to files:

  • tsconfig.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (3)
tsconfig.json (1)

1-40: Configuration changes are well-structured and compliant.

The additions to tsconfig.json follow TypeScript best practices and align with the PR objectives:

  • Strict mode enabled (line 35): Complies with the coding guideline requiring strict mode with no any without justification.
  • Path mappings (lines 30–31): The new @hmcts/subscriptions and @hmcts/notification aliases follow the established workspace pattern and enable consistent imports across packages.
  • Type roots (line 6): Adds support for custom types in ./types alongside node_modules, enabling declaration files from the new notification library to be resolved correctly.
  • Build exclusion (line 39): Appropriately excludes vitest.config.ts from the TypeScript build output.
types/notifications-node-client/index.d.ts (2)

1-31: Well-structured type declaration.

The ambient module declaration follows TypeScript conventions correctly. Naming uses PascalCase for the class, camelCase for methods and parameters, and all types are properly defined.


2-30: No action required unless SMS or letter notifications are planned.

The type declaration currently includes only the sendEmail method. While the notifications-node-client library does provide sendSms, sendLetter, and prepareUpload, the codebase only uses sendEmail (verified in libs/notification/src/govuk-notify-service.ts). The incomplete type declaration matches actual project usage and poses no issues. If SMS or letter notifications are added to the project in the future, add their corresponding type declarations at that time.

- Add tests for media-applications list page (5 tests)
- Add tests for application details page (7 tests)
- Add tests for approve confirmation page (13 tests)
- Add tests for approval success page (5 tests)
- Add tests for proof-of-id file download (13 tests)

Coverage improved from 42.8% to 96.48% (exceeds 80% requirement)
All 290 tests passing

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

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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (2)
libs/admin-pages/src/pages/media-applications/[id]/index.test.ts (2)

79-88: Fix mock data to match the actual query return type.

Same issue as the English test: the mock includes phoneNumber (line 84) and uses createdAt (line 87) instead of appliedDate.

Apply this diff:

     const mockApplication = {
       id: "app-123",
       name: "John Smith",
       employer: "BBC",
       email: "[email protected]",
-      phoneNumber: "07700900123",
       proofOfIdPath: "/uploads/proof-app-123.pdf",
       status: "PENDING" as const,
-      createdAt: new Date("2024-01-01")
+      appliedDate: new Date("2024-01-01")
     };

127-138: Fix mock data to match the actual query return type.

Both test cases have the same mock data inconsistencies as the earlier tests:

  • phoneNumber is included but not returned by the query
  • createdAt should be appliedDate

Apply this diff to fix both mocks:

     const mockApplication = {
       id: "app-123",
       name: "John Smith",
       employer: "BBC",
       email: "[email protected]",
-      phoneNumber: "07700900123",
       proofOfIdPath: "/uploads/proof-app-123.pdf",
       status: "APPROVED" as const,  // or "REJECTED" for the second test
-      createdAt: new Date("2024-01-01"),
+      appliedDate: new Date("2024-01-01"),
       reviewedBy: "[email protected]",
       reviewedDate: new Date("2024-01-02")
     };

Also applies to: 154-165

🧹 Nitpick comments (14)
libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.test.ts (2)

51-255: Reduce duplication with a mock factory function.

The mockApplication object is repeated across 9 tests with minor variations. Extract to a factory function for better maintainability.

Apply this pattern at the top of the describe block:

+  const createMockApplication = (overrides = {}) => ({
+    id: "app-123",
+    name: "John Smith",
+    employer: "BBC",
+    email: "[email protected]",
+    proofOfIdPath: "/uploads/proof-app-123.pdf",
+    status: "PENDING" as const,
+    appliedDate: new Date("2024-01-01"),
+    ...overrides
+  });
+
   beforeEach(() => {

Then use it in tests:

     it("should serve PDF file successfully", async () => {
-      const mockApplication = {
-        id: "app-123",
-        name: "John Smith",
-        employer: "BBC",
-        email: "[email protected]",
-        phoneNumber: "07700900123",
-        proofOfIdPath: "/uploads/proof-app-123.pdf",
-        status: "PENDING" as const,
-        createdAt: new Date("2024-01-01")
-      };
+      const mockApplication = createMockApplication();

267-270: Consider more explicit middleware verification.

The test verifies that GET has 2 elements and GET[0] exists, but doesn't confirm GET[0] is the requireRole middleware. For stronger validation, you could verify the middleware was called with the correct role.

+import { requireRole, USER_ROLES } from "@hmcts/auth";
+
 // ... in the test:
     it("should use requireRole middleware with INTERNAL_ADMIN_CTSC role", () => {
       expect(GET).toHaveLength(2);
-      expect(GET[0]).toBeDefined();
+      expect(requireRole).toHaveBeenCalledWith(USER_ROLES.INTERNAL_ADMIN_CTSC);
     });
libs/admin-pages/src/pages/media-applications/[id]/index.test.ts (2)

92-93: Add assertion to verify getApplicationById was called.

For consistency with the English test (line 56), add an assertion to verify that getApplicationById was called with the correct ID.

     const handler = GET[1];
     await handler(mockRequest as Request, mockResponse as Response, vi.fn());
 
+    expect(getApplicationById).toHaveBeenCalledWith("app-123");
     expect(renderSpy).toHaveBeenCalledWith("media-applications/[id]/index", {

194-197: Consider verifying requireRole was called with the correct role.

The current test only checks the structure of the GET export. You could make it more robust by verifying that the requireRole mock was called with INTERNAL_ADMIN_CTSC.

+    it("should use requireRole middleware with INTERNAL_ADMIN_CTSC role", async () => {
+      const { requireRole, USER_ROLES } = await import("@hmcts/auth");
+      
       expect(GET).toHaveLength(2);
-      expect(GET[0]).toBeDefined();
+      expect(requireRole).toHaveBeenCalledWith([USER_ROLES.INTERNAL_ADMIN_CTSC]);
     });
libs/admin-pages/src/pages/media-applications/[id]/approved.test.ts (1)

4-15: Strengthen the requireRole middleware assertions

The last test only checks that GET has two entries and that GET[0] is defined, which doesn’t actually prove that requireRole is used with the INTERNAL_ADMIN_CTSC role or that GET[0] is the middleware returned by requireRole.

You can tighten this by importing the mocked requireRole and asserting both the role argument and identity of the middleware function, for example:

 vi.mock("@hmcts/auth", () => ({
   requireRole: vi.fn(() => (_req: Request, _res: Response, next: () => void) => next()),
   USER_ROLES: { INTERNAL_ADMIN_CTSC: "INTERNAL_ADMIN_CTSC" }
 }));
 
 vi.mock("../../../media-application/queries.js", () => ({
   getApplicationById: vi.fn()
 }));
 
+const { requireRole } = await import("@hmcts/auth");
 const { getApplicationById } = await import("../../../media-application/queries.js");
 const { GET } = await import("./approved.js");
@@
-    it("should use requireRole middleware with INTERNAL_ADMIN_CTSC role", () => {
-      expect(GET).toHaveLength(2);
-      expect(GET[0]).toBeDefined();
-    });
+    it("should use requireRole middleware with INTERNAL_ADMIN_CTSC role", () => {
+      expect(requireRole).toHaveBeenCalledWith("INTERNAL_ADMIN_CTSC");
+      expect(GET).toHaveLength(2);
+      expect(GET[0]).toBe(requireRole.mock.results[0].value);
+    });

This keeps the test resilient while explicitly verifying both the correct role and the wiring of the middleware array.

Please confirm this matches how GET is constructed in approved.ts under your current Vitest/ESM setup.

Also applies to: 140-143

docs/tickets/VIBE-228/review.md (4)

211-227: Database index suggestion should consider elevation to high-priority for performance.

The suggestions section appropriately identifies non-blocking improvements. However, the database index recommendation (lines 212-227) for the status and email columns may warrant higher priority consideration:

  • The media_application table is queried by status frequently (filtering for pending applications)
  • As the application list grows, unindexed status queries will degrade significantly
  • This is a low-effort fix (2-3 lines in schema) with high long-term benefit

Consider elevating this to the High Priority section or ensuring it's included in the first post-deployment iteration.


458-497: Next steps are well-organized but some testing items duplicate fix categories.

The clear categorization by severity (Critical → High Priority → Recommended) with estimated effort makes this actionable. However, lines 480-485 (Testing section) list items that logically belong within the fix categories (e.g., "Verify path traversal fix" is inherent to fixing the critical issue, not a separate task). Consider consolidating testing as part of each priority level's completion criteria rather than a standalone section. Also, per-item time estimates (in addition to category totals) would improve planning accuracy.


55-55: Fix grammar and markdown formatting issues identified by static analysis.

Three formatting issues detract from the document's polish:

  1. Line 55: Change "HIGH PRIORITY Issues" to "HIGH-PRIORITY Issues" (compound adjective requires hyphen)
  2. Line 193: Remove spaces inside emphasis markers (change " text " to "text" as per markdown linting rules)
  3. Line 334 context: "new windows" should be "new Windows" (proper noun for the operating system)
  4. Line 496: Same as line 55 — change "High Priority Items" to "High-Priority Items"

These are minor but improve readability and meet style guidelines.

Also applies to: 193-193, 334-334, 496-496


487-496: Overall assessment appropriately balances severity, timing, and next steps.

The "NEEDS CHANGES" status with clear guidance that deployment is possible after critical and high-priority fixes is the right call. The assessment correctly identifies that this is good work with a critical blocker (path traversal), not a systemic quality issue. The 2-4 hour critical / 4-8 hour high-priority time estimates provide realistic expectations for remediation effort.

One suggestion: Add a note about regression testing scope — specifically, which existing test suites should be re-run after the path traversal fix to ensure no unexpected side effects.

libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts (3)

71-89: Relax full render-context equality to make tests less brittle

Several tests assert the entire render context object with toHaveBeenCalledWith. Any future addition of template params will break these tests even if behaviour is unchanged. You can focus the assertions on the important bits using expect.objectContaining for the second argument:

-      expect(renderSpy).toHaveBeenCalledWith("media-applications/[id]/approve", {
-        pageTitle: "Are you sure you want to approve this application?",
-        subheading: "Applicant's details",
-        tableHeaders: {
-          name: "Name",
-          email: "Email",
-          employer: "Employer",
-          dateApplied: "Date applied"
-        },
-        radioLegend: "Confirm approval",
-        radioOptions: {
-          yes: "Yes",
-          no: "No"
-        },
-        continueButton: "Continue",
-        application: mockApplication,
-        hideLanguageToggle: true
-      });
+      expect(renderSpy).toHaveBeenCalledWith(
+        "media-applications/[id]/approve",
+        expect.objectContaining({
+          pageTitle: "Are you sure you want to approve this application?",
+          subheading: "Applicant's details",
+          tableHeaders: {
+            name: "Name",
+            email: "Email",
+            employer: "Employer",
+            dateApplied: "Date applied"
+          },
+          radioLegend: "Confirm approval",
+          radioOptions: {
+            yes: "Yes",
+            no: "No"
+          },
+          continueButton: "Continue",
+          application: mockApplication,
+          hideLanguageToggle: true
+        })
+      );

Same pattern could be applied to the Welsh GET test, the POST validation-error render, and the DB-error renders.

Also applies to: 111-129, 177-195, 317-322


55-64: Deduplicate mockApplication construction and align shape with real query type

Each test builds a near-identical mockApplication object. That’s fine functionally, but it’s a little noisy and easy for the mocked shape to drift from the real MediaApplicationDetails (e.g. appliedDate vs createdAt, extra fields, etc., per getApplicationById in media-application/queries.ts). Pulling this into a small factory would tighten things up and keep the test doubles closer to reality:

-import type { Request, Response } from "express";
+import type { Request, Response } from "express";
+import type { MediaApplicationDetails } from "../../../media-application/model.js";
@@
 describe("media-application approve page", () => {
@@
-  beforeEach(() => {
+  const createMockApplication = (
+    overrides: Partial<MediaApplicationDetails> = {}
+  ): MediaApplicationDetails => ({
+    id: "app-123",
+    name: "John Smith",
+    employer: "BBC",
+    email: "[email protected]",
+    proofOfIdPath: "/uploads/proof-app-123.pdf",
+    status: "PENDING",
+    appliedDate: new Date("2024-01-01"),
+    ...overrides
+  });
+
+  beforeEach(() => {
@@
-      const mockApplication = {
-        id: "app-123",
-        name: "John Smith",
-        employer: "BBC",
-        email: "[email protected]",
-        phoneNumber: "07700900123",
-        proofOfIdPath: "/uploads/proof-app-123.pdf",
-        status: "PENDING" as const,
-        createdAt: new Date("2024-01-01")
-      };
+      const mockApplication = createMockApplication();

…and similarly in the other tests. This keeps the mock consistent with what getApplicationById really returns while making the tests easier to read.

Also applies to: 95-104, 160-169, 199-208, 221-230, 250-259, 274-283


326-334: Auth-middleware tests don’t currently verify role usage as described

The test names say “use requireRole middleware with INTERNAL_ADMIN_CTSC role”, but the assertions only check that GET/POST are arrays of length 2 with a first element defined. That will catch the absence of any middleware, but not an incorrect role or a different auth wrapper.

Either relax the descriptions to match what’s asserted:

-  it("should use requireRole middleware with INTERNAL_ADMIN_CTSC role for GET", () => {
+  it("should expose GET as [authMiddleware, handler]", () => {
@@
-  it("should use requireRole middleware with INTERNAL_ADMIN_CTSC role for POST", () => {
+  it("should expose POST as [authMiddleware, handler]", () => {

or, if you want to assert the role explicitly, you’d need to avoid clearing the @hmcts/auth mock calls made at module initialisation (or rework the mocking strategy) so you can inspect requireRole’s arguments.

libs/admin-pages/src/pages/media-applications/index.test.ts (2)

35-72: Align mocked application shape with getPendingApplications’ real return type

getPendingApplications returns an appliedDate field (per libs/admin-pages/src/media-application/queries.ts), but the mocks here use createdAt. That means the test won’t catch mapping bugs between the query result and the view model. Consider either:

  • Mocking appliedDate and asserting that the handler maps it to whatever the template expects, or
  • Updating the query/handler so the field names genuinely align and the mock mirrors that contract.

This will make the test a better guard against schema drift between the DB layer and the page.


121-133: Consider asserting HTTP status in the error-path test (if applicable)

If the intended behaviour on a database failure is to return a non‑200 status (for example 500), it’s worth extending this test to also assert the status code in addition to the rendered view and model. That would tighten coverage of the error path and prevent regressions where the page continues to render but the status silently changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9da207d and 102cfba.

📒 Files selected for processing (6)
  • docs/tickets/VIBE-228/review.md (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approved.test.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/index.test.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.test.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/index.test.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 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.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
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.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. 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.

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved.test.ts
  • libs/admin-pages/src/pages/media-applications/index.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved.test.ts
  • libs/admin-pages/src/pages/media-applications/index.test.ts
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/pages/**/*.ts: Page controllers must export named GET and/or POST functions with Express Request and Response types.
Every page must support both English and Welsh with separate en and cy content objects in the controller, and Welsh content must be tested with ?lng=cy query parameter.

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved.test.ts
  • libs/admin-pages/src/pages/media-applications/index.test.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved.test.ts
  • libs/admin-pages/src/pages/media-applications/index.test.ts
🧠 Learnings (3)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.ts : Page controllers must export named `GET` and/or `POST` functions with Express Request and Response types.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved.test.ts
  • libs/admin-pages/src/pages/media-applications/index.test.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.ts : Every page must support both English and Welsh with separate `en` and `cy` content objects in the controller, and Welsh content must be tested with `?lng=cy` query parameter.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approved.test.ts
  • libs/admin-pages/src/pages/media-applications/index.test.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{test,spec}.ts : Test files must be co-located with source files using the pattern `*.test.ts` or `*.spec.ts` and use Vitest.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.test.ts
  • libs/admin-pages/src/pages/media-applications/index.test.ts
🧬 Code graph analysis (5)
libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts (2)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-40)
libs/admin-pages/src/media-application/service.ts (1)
  • approveApplication (6-22)
libs/admin-pages/src/pages/media-applications/[id]/index.test.ts (1)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-40)
libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.test.ts (1)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-40)
libs/admin-pages/src/pages/media-applications/[id]/approved.test.ts (1)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-40)
libs/admin-pages/src/pages/media-applications/index.test.ts (1)
libs/admin-pages/src/media-application/queries.ts (1)
  • getPendingApplications (4-21)
🪛 LanguageTool
docs/tickets/VIBE-228/review.md

[uncategorized] ~55-~55: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...send("Invalid file path"); } ``` ## ⚠️ HIGH PRIORITY Issues ### 1. Inconsistent Logging Pra...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[uncategorized] ~334-~334: “Windows” (operating system by Microsoft) is a proper noun and needs to be capitalized.
Context: ...- ARIA labels on links that open in new windows - Error summary component for validatio...

(A_WINDOWS)


[uncategorized] ~496-~496: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...ssues**: 2-4 hours Estimated Time for High Priority Items: 4-8 hours --- **Next Review ...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

🪛 markdownlint-cli2 (0.18.1)
docs/tickets/VIBE-228/review.md

193-193: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)

⏰ 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: E2E Tests
  • GitHub Check: Test Changed Packages
🔇 Additional comments (6)
libs/admin-pages/src/pages/media-applications/[id]/proof-of-id.test.ts (1)

1-24: LGTM!

The mock setup properly isolates external dependencies and follows Vitest patterns. The .js extensions on imports comply with the ESM requirements.

libs/admin-pages/src/pages/media-applications/[id]/approved.test.ts (1)

16-139: Solid coverage of GET handler behaviour and localisation

The tests for the GET handler cover the main paths well: happy-path English, Welsh via ?lng=cy, not-found (404), and DB failure with an error message. The mocking of getApplicationById and the render/status spies are set up cleanly and keep the tests focused on controller behaviour rather than implementation details. This aligns nicely with the requirement to exercise Welsh content using ?lng=cy.

Based on learnings and coding guidelines, this file meets the Vitest and bilingual coverage expectations.

docs/tickets/VIBE-228/review.md (2)

389-456: Acceptance criteria verification is thorough and well-structured.

The mapping of all 8 acceptance criteria to implementation, with traceability to specific files and test evidence, demonstrates comprehensive validation. The document appropriately flags the inline styles issue identified in AC4 as connected to the high-priority concerns. The honest acknowledgment that AC7 (Reject flow) is a placeholder is appropriate for the current scope.

One minor enhancement: Consider adding a column or notation for "Verified by" test files to make the E2E/unit test traceability even more explicit.


304-356: Positive feedback section is well-balanced and appropriately detailed.

The comprehensive positive feedback across eight dimensions (database design, type safety, testing, security, accessibility, GOV.UK standards, code organization, and DX) demonstrates thorough appreciation for solid engineering practices. Specific examples (e.g., "good use of const assertions for status enum," "comprehensive unit tests with 100% coverage") are motivating while maintaining professional credibility. This balance against identified issues is appropriate and helpful.

libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts (1)

53-324: Good end-to-end coverage of approve page GET/POST flows

The suite exercises the key happy paths and failure modes (en/cy rendering, validation, “no” vs “yes”, reviewer email fallback, notification failure, 404 and DB errors) via the actual GET[1]/POST[1] handlers, which is a solid level of coverage for this page. I don’t see correctness issues in the current assertions.

libs/admin-pages/src/pages/media-applications/index.test.ts (1)

74-105: Welsh rendering test covers the bilingual requirement well

This test explicitly sets ?lng=cy and asserts the Welsh copy and hideLanguageToggle flag, which satisfies the requirement that pages support and test Welsh content via the query parameter. No changes needed here; just keep these strings in sync with the page content definitions as they evolve.

Based on learnings, this correctly exercises the Welsh content path.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
e2e-tests/tests/media-application-approval.spec.ts (1)

121-159: Decouple approval tests from shared state to reduce flakiness

Both should complete approval workflow and should not show approved application in pending list currently approve the same applicationId, and they share the same global fixture row. This creates cross-test coupling and potential flakiness if tests are ever reordered or run in parallel.

Consider making the “not show approved application” test self-contained by creating and cleaning up its own temporary PENDING application inside the test (similar to your pattern in the accessibility suite), instead of reusing and re-approving the shared one.

🧹 Nitpick comments (2)
e2e-tests/tests/media-application-approval.spec.ts (2)

81-81: Prefer simple URL or predicate matching over dynamic RegExp

For the waitForURL(new RegExp(...)) calls that only need to match a concrete path (e.g. /media-applications/${applicationId}/approved), you can simplify to either the string form or a small predicate:

await page.waitForURL(`/media-applications/${applicationId}/approved`);
// or
await page.waitForURL(url => url.includes(`/media-applications/${applicationId}/approved`));

This keeps the intent clearer and avoids static-analysis noise around constructing regular expressions from variables.

Also applies to: 97-97, 117-117, 129-129, 151-151, 250-250


263-293: Welsh-language tests align with bilingual page guidelines

The ?lng=cy tests for list, details, and approval confirmation headers directly exercise the Welsh content paths for these pages and match the bilingual-support guideline from CLAUDE.md.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 102cfba and 0f6df13.

📒 Files selected for processing (1)
  • e2e-tests/tests/media-application-approval.spec.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.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
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.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. 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.

Files:

  • e2e-tests/tests/media-application-approval.spec.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • e2e-tests/tests/media-application-approval.spec.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • e2e-tests/tests/media-application-approval.spec.ts
🧠 Learnings (2)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{test,spec}.ts : Test files must be co-located with source files using the pattern `*.test.ts` or `*.spec.ts` and use Vitest.

Applied to files:

  • e2e-tests/tests/media-application-approval.spec.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.ts : Every page must support both English and Welsh with separate `en` and `cy` content objects in the controller, and Welsh content must be tested with `?lng=cy` query parameter.

Applied to files:

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

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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (6)
e2e-tests/tests/media-application-approval.spec.ts (6)

5-36: Test data setup/teardown looks solid

Creating a single fixture media application in beforeAll and cleaning it up in afterAll via Prisma is clear and keeps DB noise low. The defensive delete with .catch(() => {}) is appropriate for tests where intermediate steps may remove the row already.


38-61: CTSC admin login and basic navigation are well covered

The CTSC admin beforeEach plus the dashboard tile and navigation checks give good smoke coverage of the new media applications entry point without over-constraining UI details.


62-88: Pending list and details flow are reasonably exercised

Using the truncated applicationId.substring(0, 8) to find the row and then driving into the details page verifies the end-to-end wiring from DB → list → details. The specific applicant fields asserted are a good minimal set and help catch regression in the mapping.


90-119: Approval entry, validation, and “No” branch are covered well

The tests around navigating to /approve, triggering the validation error with no selection, and asserting the “No” path returns to the details page provide solid coverage for the main branches of the confirmation step.


162-192: RBAC scenarios for Local Admin are appropriately asserted

The two Local Admin tests cover both absence of the tile and blocked direct navigation to /media-applications, which should be enough to catch regressions in access control without relying on specific error wording.


194-261: Accessibility coverage is strong and uses Axe appropriately

Running Axe scans across list, details, approve, and approved pages (with a fresh, disposable application for the final case) gives good a11y confidence. The targeted disableRules list is small and clearly focused on known GOV.UK patterns.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
e2e-tests/tests/media-application-approval.spec.ts (2)

185-195: Consider asserting a more specific RBAC outcome for Local Admin

Right now this test only asserts that the <h1> does not contain “Select application to assess”. It would be stronger to also assert the expected unauthorized experience (for example, a specific “not authorised” heading or redirect target), so failures are easier to diagnose and less likely to pass on an unrelated page.


41-45: Optional: factor repeated CTSC admin login into a small helper

The CTSC admin login flow (goto("/admin-dashboard") + loginWithSSO + waitForURL("/admin-dashboard")) is repeated in three beforeEach blocks. You could wrap this in a local helper like async function loginAsCTSCAdmin(page, context?) to reduce duplication and keep future changes to the login flow in one place, while still reusing the shared loginWithSSO utility.

Also applies to: 199-203, 256-260

libs/admin-pages/src/pages/media-applications/index.njk (1)

18-36: Row-building logic is fine but could be simplified or moved server‑side

The rows accumulation using rows.push([...]) inside the loop is valid Nunjucks/JS and will produce the correct table rows, but it’s quite imperative and mixes data shaping with presentation.

If you want to simplify, you could either:

  • Build the rows array in the route/controller and pass it directly into the template, or
  • Use a more declarative structure (e.g. compute rows in code, or at least keep the loop but avoid the push/comma pattern).

Not urgent, but it would make the template a bit easier to read and 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 0f6df13 and 0b4a272.

📒 Files selected for processing (3)
  • e2e-tests/tests/create-media-account.spec.ts (1 hunks)
  • e2e-tests/tests/media-application-approval.spec.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/index.njk (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/pages/**/*.njk

📄 CodeRabbit inference engine (CLAUDE.md)

Nunjucks templates must extend layouts/base-templates.njk and use GOV.UK component macros (govukButton, govukInput, govukErrorSummary, etc.).

Files:

  • libs/admin-pages/src/pages/media-applications/index.njk
**/*.{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.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
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.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. 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.

Files:

  • e2e-tests/tests/media-application-approval.spec.ts
  • e2e-tests/tests/create-media-account.spec.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • e2e-tests/tests/media-application-approval.spec.ts
  • e2e-tests/tests/create-media-account.spec.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • e2e-tests/tests/media-application-approval.spec.ts
  • e2e-tests/tests/create-media-account.spec.ts
🧠 Learnings (2)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.njk : Nunjucks templates must extend `layouts/base-templates.njk` and use GOV.UK component macros (govukButton, govukInput, govukErrorSummary, etc.).

Applied to files:

  • libs/admin-pages/src/pages/media-applications/index.njk
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.ts : Every page must support both English and Welsh with separate `en` and `cy` content objects in the controller, and Welsh content must be tested with `?lng=cy` query parameter.

Applied to files:

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

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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (4)
e2e-tests/tests/create-media-account.spec.ts (1)

188-197: Assertion update to name is correct and consistent with the model rename

Using application?.name here matches the underlying Prisma field rename from fullName to name and is consistent with how the test populates the form and verifies other fields. No further changes needed in this block.

e2e-tests/tests/media-application-approval.spec.ts (3)

8-38: Test data setup/teardown with Prisma looks solid

Creating a single shared mediaApplication in beforeAll and cleaning it up in afterAll gives all scenarios a stable fixture while keeping DB noise low. The defensive catch on delete is reasonable for test cleanup, so this block looks good as-is.


144-171: Good isolation for “approved not in pending list” scenario

Creating a fresh mediaApplication inside this test, driving it through approval, asserting absence from the pending list, and then deleting it keeps this scenario isolated from the shared applicationId and avoids inter-test coupling. This directly addresses the earlier concern about reusing the same application across approval tests.


255-279: Welsh coverage aligns with bilingual page requirements

The ?lng=cy tests for list, details, and approval confirmation pages validate the expected Welsh headings, which matches the requirement that admin pages support both English and Welsh content and that Welsh is explicitly tested.

Based on learnings, this is exactly the kind of coverage we want to see.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b4a272 and 22734a9.

📒 Files selected for processing (5)
  • libs/notification/src/govuk-notify-service.test.ts (1 hunks)
  • libs/notification/src/govuk-notify-service.ts (1 hunks)
  • libs/notification/tsconfig.json (1 hunks)
  • libs/web-core/src/assets/js/search-autocomplete.test.ts (2 hunks)
  • types/notifications-node-client/index.d.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • libs/web-core/src/assets/js/search-autocomplete.test.ts
  • libs/notification/tsconfig.json
  • libs/notification/src/govuk-notify-service.ts
  • types/notifications-node-client/index.d.ts
🧰 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.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
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.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. 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.

Files:

  • libs/notification/src/govuk-notify-service.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • libs/notification/src/govuk-notify-service.test.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • libs/notification/src/govuk-notify-service.test.ts
🧬 Code graph analysis (1)
libs/notification/src/govuk-notify-service.test.ts (1)
libs/notification/src/govuk-notify-service.ts (1)
  • sendMediaApprovalEmail (12-29)
🪛 Gitleaks (8.29.1)
libs/notification/src/govuk-notify-service.test.ts

[high] 4-4: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (1)
libs/notification/src/govuk-notify-service.test.ts (1)

14-107: Tests correctly exercise happy path and error propagation for GOV.UK Notify integration

The mocking pattern (vi.mock + dynamic import) looks sound, and the tests cover:

  • Correct template ID, recipient, personalisation, and media-approval- reference on success.
  • Propagation of API errors with the original message.
  • Propagation of network errors without re‑wrapping.

This aligns well with the current sendMediaApprovalEmail implementation and gives good confidence around Notify integration behaviour.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
libs/admin-pages/src/pages/media-applications/index.ts (1)

8-27: Optional: harden lng handling and de-duplicate the view name

You might want to (a) handle lng being an array more defensively, and (b) extract "media-applications/index" into a top-level SCREAMING_SNAKE_CASE constant to avoid duplication and follow the constants-at-top guideline.

-import { getPendingApplications } from "../../media-application/queries.js";
-import cy from "./cy.js";
-import en from "./en.js";
-
-const getHandler = async (req: Request, res: Response) => {
-  const lang = req.query.lng === "cy" ? cy : en; 
+import { getPendingApplications } from "../../media-application/queries.js";
+import cy from "./cy.js";
+import en from "./en.js";
+
+const MEDIA_APPLICATIONS_INDEX_VIEW = "media-applications/index";
+
+const getHandler = async (req: Request, res: Response) => {
+  const lngParam = req.query.lng;
+  const isWelsh = Array.isArray(lngParam) ? lngParam.includes("cy") : lngParam === "cy";
+  const lang = isWelsh ? cy : en;
@@
-    res.render("media-applications/index", {
+    res.render(MEDIA_APPLICATIONS_INDEX_VIEW, {
@@
-    res.render("media-applications/index", {
+    res.render(MEDIA_APPLICATIONS_INDEX_VIEW, {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 56f853e and dc693ce.

📒 Files selected for processing (1)
  • libs/admin-pages/src/pages/media-applications/index.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.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
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.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. 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.

Files:

  • libs/admin-pages/src/pages/media-applications/index.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • libs/admin-pages/src/pages/media-applications/index.ts
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/pages/**/*.ts: Page controllers must export named GET and/or POST functions with Express Request and Response types.
Every page must support both English and Welsh with separate en and cy content objects in the controller, and Welsh content must be tested with ?lng=cy query parameter.

Files:

  • libs/admin-pages/src/pages/media-applications/index.ts
🧠 Learnings (2)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.ts : Page controllers must export named `GET` and/or `POST` functions with Express Request and Response types.

Applied to files:

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

Applied to files:

  • libs/admin-pages/src/pages/media-applications/index.ts
🧬 Code graph analysis (1)
libs/admin-pages/src/pages/media-applications/index.ts (1)
libs/admin-pages/src/media-application/queries.ts (1)
  • getPendingApplications (4-21)
🔇 Additional comments (1)
libs/admin-pages/src/pages/media-applications/index.ts (1)

1-31: Route structure and localisation look solid

This page controller follows the project conventions: named GET export, role guard in front of the handler, correct use of .js relative imports, and clean en/cy content selection with a safe default to English. The success and error render paths are coherent and avoid leaking error details. Based on learnings, this aligns with the page-controller requirements for **/pages/**/*.ts.

@junaidiqbalmoj junaidiqbalmoj force-pushed the vibe-228-approve-media-application branch from 1fae8a0 to dc693ce Compare December 1, 2025 15:09
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
libs/admin-pages/src/pages/media-applications/index.ts (1)

21-28: Consider logging the error for observability.

The error is silently discarded. While the user sees a friendly message, consider adding structured logging (without sensitive data) to aid debugging in production.

-  } catch (_error) {
+  } catch (error) {
+    console.error("Failed to load pending applications", { error: (error as Error).message });
     res.render("media-applications/index", {
libs/admin-pages/src/media-application/service.ts (1)

6-22: Race condition: concurrent approvals could bypass status check.

The check-then-update pattern creates a TOCTOU window. Two concurrent requests could both read PENDING and proceed with approval, potentially causing duplicate notifications.

Consider using an atomic update with a where clause that includes the status check:

 export async function approveApplication(id: string): Promise<void> {
-  const application = await getApplicationById(id);
-
-  if (!application) {
-    throw new Error("Application not found");
-  }
-
-  if (application.status !== APPLICATION_STATUS.PENDING) {
-    throw new Error("Application has already been reviewed");
-  }
-
-  await updateApplicationStatus(id, APPLICATION_STATUS.APPROVED);
+  // Atomic update with status check - returns null if not found or not PENDING
+  const application = await prisma.mediaApplication.update({
+    where: { id, status: APPLICATION_STATUS.PENDING },
+    data: { status: APPLICATION_STATUS.APPROVED },
+    select: { proofOfIdPath: true }
+  }).catch((e) => {
+    if (e.code === 'P2025') return null; // Record not found
+    throw e;
+  });
+
+  if (!application) {
+    throw new Error("Application not found or already reviewed");
+  }

   if (application.proofOfIdPath) {
     await deleteProofOfIdFile(application.proofOfIdPath);
   }
 }
apps/postgres/prisma/schema.prisma (1)

52-52: Consider adding an index on status for query performance.

The getPendingApplications query filters by status. As data grows, an index would help.

   appliedDate      DateTime  @default(now()) @map("applied_date")

+  @@index([status])
   @@map("media_application")
 }
libs/admin-pages/src/media-application/queries.ts (1)

40-40: Strengthen type safety for the status parameter.

The status parameter is typed as string instead of the more specific ApplicationStatus type. This weakens type safety and could allow invalid status values at compile time.

Apply this diff:

-export async function updateApplicationStatus(id: string, status: string): Promise<MediaApplicationDetails> {
+export async function updateApplicationStatus(id: string, status: ApplicationStatus): Promise<MediaApplicationDetails> {

Don't forget to import ApplicationStatus from ./model.js on line 2:

-import { APPLICATION_STATUS, type MediaApplicationDetails, type PendingApplicationSummary } from "./model.js";
+import { APPLICATION_STATUS, type ApplicationStatus, type MediaApplicationDetails, type PendingApplicationSummary } from "./model.js";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 17383c6 and 9885fb2.

📒 Files selected for processing (11)
  • apps/postgres/prisma/migrations/20251201160103_remove_reviewed_fields_from_media_application/migration.sql (1 hunks)
  • apps/postgres/prisma/schema.prisma (1 hunks)
  • e2e-tests/tests/media-application-approval.spec.ts (1 hunks)
  • libs/admin-pages/src/media-application/model.ts (1 hunks)
  • libs/admin-pages/src/media-application/queries.test.ts (1 hunks)
  • libs/admin-pages/src/media-application/queries.ts (1 hunks)
  • libs/admin-pages/src/media-application/service.test.ts (1 hunks)
  • libs/admin-pages/src/media-application/service.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • libs/admin-pages/src/media-application/queries.test.ts
  • libs/admin-pages/src/media-application/service.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
🧰 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.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
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.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. 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.

Files:

  • libs/admin-pages/src/pages/media-applications/index.ts
  • e2e-tests/tests/media-application-approval.spec.ts
  • libs/admin-pages/src/media-application/model.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts
  • libs/admin-pages/src/media-application/queries.ts
  • libs/admin-pages/src/media-application/service.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • libs/admin-pages/src/pages/media-applications/index.ts
  • e2e-tests/tests/media-application-approval.spec.ts
  • libs/admin-pages/src/media-application/model.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts
  • libs/admin-pages/src/media-application/queries.ts
  • libs/admin-pages/src/media-application/service.ts
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/pages/**/*.ts: Page controllers must export named GET and/or POST functions with Express Request and Response types.
Every page must support both English and Welsh with separate en and cy content objects in the controller, and Welsh content must be tested with ?lng=cy query parameter.

Files:

  • libs/admin-pages/src/pages/media-applications/index.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • e2e-tests/tests/media-application-approval.spec.ts
**/prisma/schema.prisma

📄 CodeRabbit inference engine (CLAUDE.md)

Database tables and fields MUST be singular and snake_case (e.g., user, case, created_at). Use Prisma @@map and @map for aliases.

Files:

  • apps/postgres/prisma/schema.prisma
🧠 Learnings (7)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.ts : Page controllers must export named `GET` and/or `POST` functions with Express Request and Response types.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/index.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/src/config.ts : Config exports (pageRoutes, apiRoutes, prismaSchemas, assets) must be in a separate `config.ts` file to avoid circular dependencies during Prisma client generation. Apps import config using the `/config` path.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/index.ts
  • libs/admin-pages/src/media-application/queries.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.ts : Every page must support both English and Welsh with separate `en` and `cy` content objects in the controller, and Welsh content must be tested with `?lng=cy` query parameter.

Applied to files:

  • e2e-tests/tests/media-application-approval.spec.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/**/locales/**/*.ts : Shared content (common button text, phase banners, service names, common error messages) goes in locale files (`libs/[module]/src/locales/en.ts` and `cy.ts`). Page-specific content goes in controllers.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Never put sensitive data in logs.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Database queries must be parameterized using Prisma (never raw SQL with string concatenation).

Applied to files:

  • libs/admin-pages/src/media-application/queries.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/prisma/schema.prisma : Database tables and fields MUST be singular and snake_case (e.g., `user`, `case`, `created_at`). Use Prisma `@map` and `map` for aliases.

Applied to files:

  • apps/postgres/prisma/schema.prisma
🧬 Code graph analysis (5)
libs/admin-pages/src/pages/media-applications/index.ts (5)
libs/admin-pages/src/media-application/queries.ts (1)
  • getPendingApplications (4-21)
libs/admin-pages/src/pages/media-applications/[id]/approve.ts (1)
  • GET (101-101)
libs/admin-pages/src/pages/admin-dashboard/index.ts (1)
  • GET (27-27)
libs/admin-pages/src/pages/media-applications/[id]/approved.ts (1)
  • GET (39-39)
libs/admin-pages/src/pages/media-applications/[id]/index.ts (1)
  • GET (51-51)
e2e-tests/tests/media-application-approval.spec.ts (1)
e2e-tests/utils/sso-helpers.ts (1)
  • loginWithSSO (10-46)
libs/admin-pages/src/pages/media-applications/[id]/approve.ts (4)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-38)
libs/admin-pages/src/media-application/service.ts (1)
  • approveApplication (6-22)
libs/notification/src/govuk-notify-service.ts (1)
  • sendMediaApprovalEmail (12-29)
libs/admin-pages/src/pages/media-applications/index.ts (1)
  • GET (31-31)
libs/admin-pages/src/media-application/queries.ts (1)
libs/admin-pages/src/media-application/model.ts (3)
  • PendingApplicationSummary (19-24)
  • APPLICATION_STATUS (1-5)
  • MediaApplicationDetails (9-17)
libs/admin-pages/src/media-application/service.ts (2)
libs/admin-pages/src/media-application/queries.ts (2)
  • getApplicationById (23-38)
  • updateApplicationStatus (40-58)
libs/admin-pages/src/media-application/model.ts (1)
  • APPLICATION_STATUS (1-5)
🪛 ast-grep (0.40.0)
e2e-tests/tests/media-application-approval.spec.ts

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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)

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

1-31: LGTM!

The implementation follows established patterns: correct role guard, language handling via query param, proper error recovery with empty state, and ESM imports with .js extensions. Structure aligns with related page controllers in libs/admin-pages/src/pages/media-applications/[id]/.

apps/postgres/prisma/schema.prisma (1)

46-56: LGTM! Schema follows conventions.

Fields correctly use snake_case mapping (proof_of_id_path, applied_date), table is singular (media_application), and VarChar constraints are appropriate.

libs/admin-pages/src/media-application/model.ts (1)

1-33: LGTM!

Well-structured type definitions:

  • APPLICATION_STATUS uses SCREAMING_SNAKE_CASE with as const for type safety
  • ApplicationStatus correctly derives from the const
  • Interfaces use PascalCase without I prefix
  • Session augmentation is properly scoped for the approval flow
libs/admin-pages/src/pages/media-applications/[id]/approve.ts (4)

1-9: LGTM!

Imports follow coding guidelines correctly: workspace aliases for cross-package imports and .js extensions on relative imports.


11-42: LGTM!

GET handler follows best practices: language support via lng query parameter, proper 404 handling, and graceful error rendering without exposing details.


44-99: LGTM!

POST handler implements the approval workflow correctly: validates user confirmation, handles yes/no choices, calls the service layer for approval, and sends a notification email without failing the approval if email delivery fails. The non-fatal email behavior is appropriate for this use case.


101-102: LGTM!

Exports follow the established pattern with proper role-based access control using requireRole middleware for INTERNAL_ADMIN_CTSC.

libs/admin-pages/src/media-application/queries.ts (4)

1-2: LGTM!

Imports follow coding guidelines: workspace alias for Prisma and .js extension on relative import.


4-21: LGTM!

Proper Prisma query with type-safe select, filtering by PENDING status, and sensible descending date ordering.


23-38: LGTM!

Proper Prisma query with explicit field selection matching MediaApplicationDetails. The type assertion on line 37 aligns the Prisma return type with the interface.


60-68: LGTM!

Straightforward Prisma count query filtering by PENDING status.

e2e-tests/tests/media-application-approval.spec.ts (5)

1-38: LGTM!

Test setup and teardown follow good practices: dynamic Prisma import, proper cleanup with error handling, and storage of test data ID for use across tests.


40-170: LGTM!

Comprehensive test coverage of the CTSC admin approval workflow: dashboard integration, navigation, form validation, approval paths (yes/no), database verification, and test isolation. The test at lines 142-169 properly creates fresh test data to avoid dependencies between tests.

Note: Static analysis warnings about RegExp from variables (lines 78, 94, 114, 126, 157) are false positives—the IDs are controlled UUIDs from Prisma, not user input.


172-194: LGTM!

Role-based access control tests properly verify that Local Admin role cannot access media applications, testing both UI visibility and direct navigation attempts.


196-251: LGTM!

Thorough accessibility coverage using Axe across all workflow pages. Properly creates and cleans up fresh test data for the success page test.


253-277: LGTM!

Welsh language support tests properly validate localized content across all workflow pages using the lng=cy query parameter, as per coding guidelines.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
libs/notification/src/govuk-notify-service.test.ts (1)

3-5: Test API key literal still trips secret scanners and leaks across suites (non‑blocking)

The hard‑coded GOVUK_NOTIFY_API_KEY ("test-api-key-12345") continues to look realistic enough for tools like Gitleaks to flag it, and it’s never restored after tests. If you decide to revisit this later, consider:

  • Using a more obviously dummy value (e.g. "dummy-govuk-notify-key"), and
  • Capturing the original env values and restoring them in an afterAll to avoid leaking config into other suites.

Not blocking, just something to be aware of if secret‑scanning noise becomes an issue.

🧹 Nitpick comments (1)
libs/notification/src/govuk-notify-service.ts (1)

3-12: Minor style: move MediaApplicationEmailData below exports to match module ordering guideline

Current order is:

  1. Constants (GOVUK_NOTIFY_API_KEY, TEMPLATE_ID_MEDIA_APPROVAL)
  2. MediaApplicationEmailData interface
  3. Exported sendMediaApprovalEmail function

Per the stated guideline, the preferred order is: constants → exported functions → types/interfaces at the bottom. Consider moving MediaApplicationEmailData below the function:

-const GOVUK_NOTIFY_API_KEY = process.env.GOVUK_NOTIFY_API_KEY;
-const TEMPLATE_ID_MEDIA_APPROVAL = process.env.GOVUK_NOTIFY_TEMPLATE_ID_MEDIA_APPROVAL;
-
-interface MediaApplicationEmailData {
-  name: string;
-  email: string;
-  employer: string;
-}
-
-export async function sendMediaApprovalEmail(data: MediaApplicationEmailData): Promise<void> {
+const GOVUK_NOTIFY_API_KEY = process.env.GOVUK_NOTIFY_API_KEY;
+const TEMPLATE_ID_MEDIA_APPROVAL = process.env.GOVUK_NOTIFY_TEMPLATE_ID_MEDIA_APPROVAL;
+
+export async function sendMediaApprovalEmail(data: MediaApplicationEmailData): Promise<void> {
   // ...
 }
+
+interface MediaApplicationEmailData {
+  name: string;
+  email: string;
+  employer: string;
+}

Purely organisational, but keeps this module consistent with the documented conventions.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb4d214 and dcd1a5b.

📒 Files selected for processing (2)
  • libs/notification/src/govuk-notify-service.test.ts (1 hunks)
  • libs/notification/src/govuk-notify-service.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.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
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.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. 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.

Files:

  • libs/notification/src/govuk-notify-service.ts
  • libs/notification/src/govuk-notify-service.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • libs/notification/src/govuk-notify-service.ts
  • libs/notification/src/govuk-notify-service.test.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • libs/notification/src/govuk-notify-service.test.ts
🧬 Code graph analysis (2)
libs/notification/src/govuk-notify-service.ts (2)
libs/notification/src/index.ts (1)
  • sendMediaApprovalEmail (1-1)
types/notifications-node-client/index.d.ts (1)
  • NotifyClient (2-26)
libs/notification/src/govuk-notify-service.test.ts (1)
libs/notification/src/govuk-notify-service.ts (1)
  • sendMediaApprovalEmail (12-32)
🪛 GitHub Actions: Test
libs/notification/src/govuk-notify-service.ts

[error] 29-29: Type '{ "full name": string; Employer: string; }' is not assignable to type 'string'.

🪛 Gitleaks (8.30.0)
libs/notification/src/govuk-notify-service.test.ts

[high] 4-4: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ 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 (2)
libs/notification/src/govuk-notify-service.ts (1)

28-31: Unable to verify the review comment. The repository could not be accessed to inspect the actual code at libs/notification/src/govuk-notify-service.ts lines 28-31 or the type definitions in types/notifications-node-client/index.d.ts. Manual verification by the developer is required to confirm:

  • Whether the current sendEmail call signature matches the actual notifications-node-client library API
  • If the proposed fix (changing from an options object to positional arguments) aligns with the library's type definition
  • Whether applying the suggested change resolves the reported type error

Please run tsc after making any changes to confirm the pipeline passes.

libs/notification/src/govuk-notify-service.test.ts (1)

54-60: Test expectation is correct and requires no changes

The test assertion at lines 54–60 correctly matches both the implementation and the local type definitions. The implementation in govuk-notify-service.ts (line 28) calls notifyClient.sendEmail() with three arguments: templateId, email, and an options object containing personalisation and reference.

This aligns with the local type definition in libs/notification/src/notifications-node-client.d.ts (lines 5–13), which defines sendEmail(templateId: string, emailAddress: string, options?: {...}).

Note: The workspace types/notifications-node-client/index.d.ts defines a different signature with five separate parameters, but this appears to be stale or incorrect relative to the actual local implementation and should be updated to match libs/notification/src/notifications-node-client.d.ts.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
libs/notification/src/govuk-notify-service.ts (1)

3-4: Narrow env vars via function-scope consts for better TS safety.

With strict: true enabled, the module-level const assignments from process.env have type string | undefined. The runtime guards at lines 13–19 don't narrow the type of module-level consts in TypeScript, so the direct uses of GOVUK_NOTIFY_API_KEY and TEMPLATE_ID_MEDIA_APPROVAL at lines 21 and 28 remain typed as string | undefined.

To keep runtime behaviour identical while improving type safety and readability, assign to function-local consts and guard there:

-export async function sendMediaApprovalEmail(data: MediaApplicationEmailData): Promise<void> {
-  if (!GOVUK_NOTIFY_API_KEY) {
-    throw new Error("GOV Notify API key not configured");
-  }
-
-  if (!TEMPLATE_ID_MEDIA_APPROVAL) {
-    throw new Error("GOV Notify approval template ID not configured");
-  }
-
-  const notifyClient = new NotifyClient(GOVUK_NOTIFY_API_KEY);
+export async function sendMediaApprovalEmail(data: MediaApplicationEmailData): Promise<void> {
+  const apiKey = GOVUK_NOTIFY_API_KEY;
+  if (!apiKey) {
+    throw new Error("GOV Notify API key not configured");
+  }
+
+  const templateId = TEMPLATE_ID_MEDIA_APPROVAL;
+  if (!templateId) {
+    throw new Error("GOV Notify approval template ID not configured");
+  }
+
+  const notifyClient = new NotifyClient(apiKey);
@@
-  await notifyClient.sendEmail(TEMPLATE_ID_MEDIA_APPROVAL, data.email, {
+  await notifyClient.sendEmail(templateId, data.email, {
     personalisation,
     reference: `media-approval-${Date.now()}`
   });
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5071e52 and bbe7888.

📒 Files selected for processing (3)
  • libs/admin-pages/src/global.d.ts (1 hunks)
  • libs/notification/src/global.d.ts (1 hunks)
  • libs/notification/src/govuk-notify-service.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.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
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.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. 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.

Files:

  • libs/notification/src/govuk-notify-service.ts
  • libs/admin-pages/src/global.d.ts
  • libs/notification/src/global.d.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • libs/notification/src/govuk-notify-service.ts
  • libs/admin-pages/src/global.d.ts
  • libs/notification/src/global.d.ts
🧬 Code graph analysis (3)
libs/notification/src/govuk-notify-service.ts (3)
libs/admin-pages/src/global.d.ts (1)
  • NotifyClient (2-14)
libs/notification/src/global.d.ts (1)
  • NotifyClient (2-14)
types/notifications-node-client/index.d.ts (1)
  • NotifyClient (2-26)
libs/admin-pages/src/global.d.ts (1)
libs/notification/src/global.d.ts (1)
  • NotifyClient (2-14)
libs/notification/src/global.d.ts (1)
libs/admin-pages/src/global.d.ts (1)
  • NotifyClient (2-14)
⏰ 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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
libs/admin-pages/src/pages/media-applications/[id]/approve.njk (1)

12-18: Previous feedback acknowledged.

The hand-coded error summary was previously discussed and marked as acceptable by the developer.

🧹 Nitpick comments (7)
libs/web-core/src/middleware/govuk-frontend/filters/date.test.ts (1)

42-56: Good coverage for new compact format; optional refactor to table-driven tests

The added "compact" tests exercise Date vs string inputs, single‑digit days with leading zeros, and multiple months, which is solid coverage and aligns with the implementation. If you want to reduce repetition later, you could convert the month and single‑day cases into a small table‑driven test, but that’s purely optional.

libs/admin-pages/src/pages/media-applications/[id]/approve.njk (1)

47-55: Consider extracting complex inline HTML to a separate macro or variable.

The inline HTML string for proof of ID (line 51) is complex and harder to maintain. While functional, extracting this to a Nunjucks variable or macro would improve readability.

+      {% set proofOfIdHtml %}
+        <div style="display: flex; justify-content: space-between; align-items: center;">
+          <span>{{ proofOfIdFilename }} {{ proofOfIdText }}</span>
+          <a href="/media-applications/{{ application.id }}/proof-of-id" class="govuk-link" target="_blank" rel="noopener noreferrer" aria-label="Opens in new window">{{ viewProofOfId }}</a>
+        </div>
+      {% endset %}
+
       {{ govukSummaryList({
         rows: [
           ...
           {
             key: { text: tableHeaders.proofOfId },
             value: {
-              html: (
-                '<div style="display: flex; justify-content: space-between; align-items: center;"><span>' + proofOfIdFilename + ' ' + proofOfIdText + '</span><a href="/media-applications/' + application.id + '/proof-of-id" class="govuk-link" target="_blank" rel="noopener noreferrer" aria-label="Opens in new window">' + viewProofOfId + '</a></div>' if application.proofOfIdPath else fileNotAvailable
-              )
+              html: proofOfIdHtml if application.proofOfIdPath else fileNotAvailable
             }
           }
         ]
       }) }}
libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts (2)

55-65: Mock application objects contain extraneous properties.

The mock objects include phoneNumber and createdAt properties, but looking at the relevant code snippet for getApplicationById, the actual query only selects: id, name, email, employer, proofOfIdPath, proofOfIdOriginalName, status, appliedDate. The mock should use appliedDate instead of createdAt and exclude phoneNumber to accurately reflect the real data shape.

 const mockApplication = {
   id: "app-123",
   name: "John Smith",
   employer: "BBC",
   email: "[email protected]",
-  phoneNumber: "07700900123",
   proofOfIdPath: "/uploads/proof-app-123.pdf",
   proofOfIdOriginalName: "IACListPublishPreviewed.pdf",
   status: "PENDING" as const,
-  createdAt: new Date("2024-01-01")
+  appliedDate: new Date("2024-01-01")
 };

Also applies to: 101-111, 172-182, 217-227, 240-250, 270-280


323-331: Middleware assertions could be more explicit.

The tests verify that GET and POST arrays have length 2, but don't actually verify that requireRole was called with INTERNAL_ADMIN_CTSC. Since requireRole is mocked, you could assert on its invocation.

+import { requireRole } from "@hmcts/auth";
+
 it("should use requireRole middleware with INTERNAL_ADMIN_CTSC role for GET", () => {
   expect(GET).toHaveLength(2);
   expect(GET[0]).toBeDefined();
+  expect(requireRole).toHaveBeenCalledWith(["INTERNAL_ADMIN_CTSC"]);
 });

Note: This would require importing the mocked requireRole after the dynamic import pattern you're using.

libs/admin-pages/src/pages/media-applications/[id]/index.njk (2)

40-40: Refactor complex inline HTML for maintainability.

The inline HTML concatenation is difficult to read and maintain. Consider using Nunjucks template blocks or a separate macro to construct this content.

Example refactor using Nunjucks syntax:

-              html: (
-                '<div style="display: flex; justify-content: space-between; align-items: center;"><span>' + proofOfIdFilename + ' ' + proofOfIdText + '</span><a href="/media-applications/' + application.id + '/proof-of-id" class="govuk-link" target="_blank" rel="noopener noreferrer" aria-label="Opens in new window">' + viewProofOfId + '</a></div>' if application.proofOfIdPath else fileNotAvailable
-              )
+              html: (
+                ('<div class="proof-of-id-row"><span>' + proofOfIdFilename + ' ' + proofOfIdText + '</span><a href="/media-applications/' + application.id + '/proof-of-id" class="govuk-link" target="_blank" rel="noopener noreferrer" aria-label="Opens in new window">' + viewProofOfId + '</a></div>') if application.proofOfIdPath else fileNotAvailable
+              )

Then add CSS class .proof-of-id-row to your stylesheet instead of inline styles.


47-47: Replace inline styles with CSS classes.

Inline styles should be avoided in favor of reusable CSS classes for better maintainability.

Apply this diff to use a CSS class:

-      <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 2rem;">
+      <div class="action-buttons">

Then define .action-buttons in your stylesheet with the appropriate flexbox properties.

libs/public-pages/src/pages/create-media-account/index.test.ts (1)

263-273: Consider asserting updateProofOfIdPath is not called when file is missing

In the “should handle missing file when validation passes” test, you already assert that saveIdProofFile is not called. Adding a similar assertion for updateProofOfIdPath would close the loop on ensuring repository updates are strictly gated on the presence of a file.

-      expect(saveIdProofFile).not.toHaveBeenCalled();
+      expect(saveIdProofFile).not.toHaveBeenCalled();
+      expect(updateProofOfIdPath).not.toHaveBeenCalled();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbe7888 and 4a8e1f7.

📒 Files selected for processing (21)
  • apps/postgres/prisma/migrations/20251203102403_add_proof_of_id_original_name/migration.sql (1 hunks)
  • apps/postgres/prisma/schema.prisma (1 hunks)
  • libs/admin-pages/src/media-application/model.ts (1 hunks)
  • libs/admin-pages/src/media-application/queries.test.ts (1 hunks)
  • libs/admin-pages/src/media-application/queries.ts (1 hunks)
  • libs/admin-pages/src/media-application/service.test.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approve-en.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approve.njk (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/approved.njk (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/index.njk (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/index.test.ts (1 hunks)
  • libs/admin-pages/src/pages/media-applications/[id]/index.ts (1 hunks)
  • libs/public-pages/src/media-application/repository/query.test.ts (3 hunks)
  • libs/public-pages/src/media-application/repository/query.ts (1 hunks)
  • libs/public-pages/src/pages/create-media-account/index.test.ts (9 hunks)
  • libs/public-pages/src/pages/create-media-account/index.ts (3 hunks)
  • libs/web-core/src/middleware/govuk-frontend/filters/date.test.ts (1 hunks)
  • libs/web-core/src/middleware/govuk-frontend/filters/date.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • libs/admin-pages/src/pages/media-applications/[id]/approved.njk
  • libs/admin-pages/src/media-application/queries.ts
  • libs/admin-pages/src/pages/media-applications/[id]/index.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.ts
  • libs/public-pages/src/media-application/repository/query.ts
  • libs/admin-pages/src/media-application/model.ts
  • libs/admin-pages/src/media-application/service.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/index.test.ts
🧰 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.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
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.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. 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.

Files:

  • libs/web-core/src/middleware/govuk-frontend/filters/date.test.ts
  • libs/public-pages/src/pages/create-media-account/index.test.ts
  • libs/admin-pages/src/media-application/queries.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/public-pages/src/pages/create-media-account/index.ts
  • libs/web-core/src/middleware/govuk-frontend/filters/date.ts
  • libs/public-pages/src/media-application/repository/query.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • libs/web-core/src/middleware/govuk-frontend/filters/date.test.ts
  • libs/public-pages/src/pages/create-media-account/index.test.ts
  • libs/admin-pages/src/media-application/queries.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/public-pages/src/pages/create-media-account/index.ts
  • libs/web-core/src/middleware/govuk-frontend/filters/date.ts
  • libs/public-pages/src/media-application/repository/query.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • libs/web-core/src/middleware/govuk-frontend/filters/date.test.ts
  • libs/public-pages/src/pages/create-media-account/index.test.ts
  • libs/admin-pages/src/media-application/queries.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/public-pages/src/media-application/repository/query.test.ts
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/pages/**/*.ts: Page controllers must export named GET and/or POST functions with Express Request and Response types.
Every page must support both English and Welsh with separate en and cy content objects in the controller, and Welsh content must be tested with ?lng=cy query parameter.

Files:

  • libs/public-pages/src/pages/create-media-account/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/public-pages/src/pages/create-media-account/index.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts
**/prisma/schema.prisma

📄 CodeRabbit inference engine (CLAUDE.md)

Database tables and fields MUST be singular and snake_case (e.g., user, case, created_at). Use Prisma @@map and @map for aliases.

Files:

  • apps/postgres/prisma/schema.prisma
**/pages/**/*.njk

📄 CodeRabbit inference engine (CLAUDE.md)

Nunjucks templates must extend layouts/base-templates.njk and use GOV.UK component macros (govukButton, govukInput, govukErrorSummary, etc.).

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.njk
  • libs/admin-pages/src/pages/media-applications/[id]/index.njk
🧠 Learnings (9)
📚 Learning: 2025-11-27T09:48:13.010Z
Learnt from: junaidiqbalmoj
Repo: hmcts/cath-service PR: 136
File: libs/api/src/blob-ingestion/validation.ts:156-163
Timestamp: 2025-11-27T09:48:13.010Z
Learning: In libs/api/src/blob-ingestion/validation.ts, the permissive date validation in isValidISODate and isValidISODateTime functions is expected behavior and should not be flagged for stricter validation.

Applied to files:

  • libs/web-core/src/middleware/govuk-frontend/filters/date.test.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{test,spec}.ts : Test files must be co-located with source files using the pattern `*.test.ts` or `*.spec.ts` and use Vitest.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/public-pages/src/media-application/repository/query.test.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.ts : Page controllers must export named `GET` and/or `POST` functions with Express Request and Response types.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/prisma/schema.prisma : Database tables and fields MUST be singular and snake_case (e.g., `user`, `case`, `created_at`). Use Prisma `@map` and `map` for aliases.

Applied to files:

  • apps/postgres/prisma/schema.prisma
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.njk : Nunjucks templates must extend `layouts/base-templates.njk` and use GOV.UK component macros (govukButton, govukInput, govukErrorSummary, etc.).

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.njk
  • libs/admin-pages/src/pages/media-applications/[id]/index.njk
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/**/locales/**/*.ts : Shared content (common button text, phase banners, service names, common error messages) goes in locale files (`libs/[module]/src/locales/en.ts` and `cy.ts`). Page-specific content goes in controllers.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.ts : Every page must support both English and Welsh with separate `en` and `cy` content objects in the controller, and Welsh content must be tested with `?lng=cy` query parameter.

Applied to files:

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

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Don't skip Welsh translations - required for all user-facing text.

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts
🧬 Code graph analysis (3)
libs/public-pages/src/pages/create-media-account/index.test.ts (4)
libs/public-pages/src/media-application/storage.ts (1)
  • saveIdProofFile (6-16)
libs/public-pages/src/pages/create-media-account/index.ts (1)
  • POST (39-86)
libs/public-pages/src/media-application/repository/model.ts (1)
  • MulterRequest (30-32)
libs/public-pages/src/media-application/repository/query.ts (2)
  • createMediaApplication (4-15)
  • updateProofOfIdPath (17-25)
libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts (4)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-39)
libs/admin-pages/src/pages/media-applications/[id]/approve.ts (2)
  • GET (109-109)
  • POST (110-110)
libs/admin-pages/src/pages/media-applications/[id]/index.ts (1)
  • GET (52-52)
libs/admin-pages/src/media-application/service.ts (1)
  • approveApplication (6-22)
libs/public-pages/src/pages/create-media-account/index.ts (3)
libs/public-pages/src/pages/validation.ts (1)
  • validateForm (15-96)
libs/public-pages/src/media-application/storage.ts (1)
  • saveIdProofFile (6-16)
libs/public-pages/src/media-application/repository/query.ts (1)
  • updateProofOfIdPath (17-25)
⏰ 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: E2E Tests
  • GitHub Check: Test Changed Packages
🔇 Additional comments (25)
libs/web-core/src/middleware/govuk-frontend/filters/date.ts (1)

7-12: Compact date format branch is correct and side‑effect free

The new "compact" branch cleanly reuses the existing Date instance, produces the expected "DD Mon YYYY" output with a zero‑padded day, and doesn’t alter prior "short"/default behaviours. Implementation is consistent with the functional style in this module.

libs/admin-pages/src/pages/media-applications/[id]/approve-cy.ts (1)

1-25: LGTM!

The Welsh translation module is well-structured, with all keys matching the English counterpart. The translations provide complete coverage for the approval page UI.

libs/admin-pages/src/pages/media-applications/[id]/approve-en.ts (1)

1-25: LGTM!

The English translation module is well-structured with all necessary UI strings for the approval page. The structure correctly mirrors the Welsh counterpart for consistency.

libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts (1)

1-51: LGTM on test structure and coverage.

The test file is well-organized with comprehensive coverage of:

  • GET handler (English/Welsh rendering, 404, error states)
  • POST handler (validation, redirect flows, approval + notification, error handling)
  • Middleware presence checks

The use of Vitest, dynamic imports after mocking, and .js extensions for relative imports all follow project conventions.

libs/admin-pages/src/pages/media-applications/[id]/index.njk (1)

1-1: The base template path is correct. The actual template file in the codebase is layouts/base-template.njk (singular), and all 20+ Nunjucks templates consistently extend this same file. The coding guideline in the retrieved learning appears to be outdated or incorrect.

Likely an incorrect or invalid review comment.

libs/admin-pages/src/media-application/queries.test.ts (4)

1-15: LGTM! Proper import extensions and comprehensive mock setup.

The imports correctly use .js extensions as required by ESM with Node.js "nodenext" module resolution, and the Prisma client mock covers all necessary methods for the test suite.


17-47: LGTM! Proper test isolation and comprehensive assertions.

The beforeEach hook ensures test isolation, and the getPendingApplications test correctly verifies both the Prisma method invocation with exact arguments and the return value. The mock data structure properly matches the select clause.


49-89: LGTM! Comprehensive test coverage with proper edge case handling.

The tests cover both the success case (application found) and the edge case (application not found), with mock data that accurately matches the select clause structure.


127-146: LGTM! Good coverage of edge cases.

The getPendingCount tests properly cover both the case with pending applications and the case with no pending applications, with clear assertions for each scenario.

libs/public-pages/src/pages/create-media-account/index.test.ts (6)

6-9: Mocks for repository and storage functions are correctly wired

The vi.mock setup and subsequent dynamic imports cleanly cover createMediaApplication, updateProofOfIdPath, saveIdProofFile, and validateForm, so the tests are exercising the same surfaces the controller uses without leaking real implementations.

Based on learnings, the Vitest usage and co-located test file layout align with the repo’s testing guidelines.

Also applies to: 19-21


93-101: Session form data uses name consistently

Using name in mediaApplicationForm and in the GET “wasSubmitted” flow lines up with the controller and validation expectations, keeping the session shape consistent after the fullName→name refactor.

Also applies to: 113-120


165-177: Validation error path correctly preserves user input and boolean conversion

The POST error-path assertions (including name instead of fullName and termsAccepted converted to boolean) accurately reflect the controller logic and help guard against regressions in how form data is stored in the session.


192-206: Success path test now verifies file save and DB update contract

Stubbing saveIdProofFile to return a path and asserting that updateProofOfIdPath is called with (applicationId, filePath, originalname) ensures the controller-to-repository contract for proof-of-ID handling is well covered.


208-231: Success redirects and session cleanup are well covered for both locales

The success tests (with and without lng=cy) verify session flags, cleanup, and redirects, and they correctly factor in the new storage interaction without over-coupling assertions to implementation internals.


275-292: Checkbox boolean handling tests remain accurate after refactor

The tests around termsAccepted still assert boolean conversion correctly in both checked and unchecked scenarios, which is important given the session object shape changed alongside other form fields.

apps/postgres/prisma/migrations/20251203102403_add_proof_of_id_original_name/migration.sql (1)

1-2: Migration cleanly adds the new original-name column

Adding proof_of_id_original_name VARCHAR(255) as a new column on media_application is a straightforward, backward-compatible change and matches the Prisma mapping in the MediaApplication model.

libs/public-pages/src/pages/create-media-account/index.ts (4)

3-5: Repository and helper imports remain consistent with ESM and alias rules

Importing updateProofOfIdPath alongside createMediaApplication, and keeping .js extensions on local imports, respects the nodenext/ESM setup and matches the way the repository API is exposed.


43-51: name propagation through validation, session, and creation is coherent

Reading fullName from the request into name, then using name in validateForm, in mediaApplicationForm, and in the createMediaApplication payload keeps the public API and persistence model aligned after the fullName→name change. This matches the updated tests and Prisma MediaApplication definition.

Also applies to: 55-59, 66-69


71-74: File save and proof-of-ID update are correctly sequenced

Conditionally calling saveIdProofFile and then updateProofOfIdPath(applicationId, filePath, file.originalname) only when a file is present is a sensible flow and aligns with the repository’s contract (storing both path and original name). Errors in either step are caught by the surrounding try/catch and surfaced as a 500, which is reasonable for this admin-facing journey.


76-85: Session cleanup and error handling still behave as expected

The success branch still sets mediaApplicationSubmitted, clears form and error state, and redirects with the locale, while the catch block returns a 500 error page. This behavior remains consistent with the tests and the previous implementation, with only the new file-path update added into the happy path.

libs/public-pages/src/media-application/repository/query.test.ts (4)

3-4: Repository tests correctly mock Prisma and cover both create and update

Extending the Prisma mock with update and importing updateProofOfIdPath ensures both repository functions are tested against the same mocked client, with no leakage to the real database. The setup is clear and focused.

Based on learnings, the test remains co-located with its source and uses Vitest as required.

Also applies to: 5-12


21-52: createMediaApplication tests align with new schema fields

Using name, asserting lowercase normalization of email, and checking that status: "PENDING" and appliedDate: expect.any(Date) are passed to prisma.mediaApplication.create keeps the test tightly coupled to the intended persistence behavior after the schema refactor.


55-82: Email normalization behavior is well isolated

The second test focusing on lowercasing the email while keeping other fields intact is a good, minimal check that avoids over-specifying unrelated data and makes the intent of createMediaApplication clear.


84-111: updateProofOfIdPath test verifies the critical update payload

The new test for updateProofOfIdPath correctly asserts that prisma.mediaApplication.update is called with the expected where clause and with both proofOfIdPath and proofOfIdOriginalName set, which directly mirrors the implementation in query.ts and the Prisma model.

apps/postgres/prisma/schema.prisma (1)

46-57: MediaApplication model matches naming guidelines and migrations

The MediaApplication model uses @@map("media_application") and snake_case column mappings (e.g. proof_of_id_path, proof_of_id_original_name, applied_date), with types and lengths consistent with the corresponding migrations. The new proofOfIdOriginalName field is correctly optional and mapped to VARCHAR(255), as in the SQL migration.

As per coding guidelines, this keeps the Prisma schema aligned with singular, snake_case database naming while exposing idiomatic camelCase fields in TypeScript.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 3, 2025

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
e2e-tests/tests/media-application-approval.spec.ts (1)

227-250: Consider using try/finally for test cleanup robustness.

If the test fails before line 249, the created test record won't be cleaned up. This is a minor issue but can lead to orphaned test data accumulating over time.

     test("approved success page should pass accessibility checks", async ({ page }) => {
       // Create a fresh application for this test
       const { prisma } = await import("@hmcts/postgres");
       const testApp = await prisma.mediaApplication.create({
         data: {
           name: "Test User For Accessibility",
           email: "[email protected]",
           employer: "Test Corp",
           status: "PENDING"
         }
       });

-      await page.goto(`/media-applications/${testApp.id}/approve`);
-      await page.check('input[value="yes"]');
-      await page.getByRole("button", { name: /continue/i }).click();
-      await page.waitForURL(new RegExp(`/media-applications/${testApp.id}/approved`));
-
-      const accessibilityScanResults = await new AxeBuilder({ page }).disableRules(["target-size", "link-name", "region"]).analyze();
-
-      expect(accessibilityScanResults.violations).toEqual([]);
-
-      // Cleanup
-      await prisma.mediaApplication.delete({ where: { id: testApp.id } });
+      try {
+        await page.goto(`/media-applications/${testApp.id}/approve`);
+        await page.check('input[value="yes"]');
+        await page.getByRole("button", { name: /continue/i }).click();
+        await page.waitForURL(new RegExp(`/media-applications/${testApp.id}/approved`));
+
+        const accessibilityScanResults = await new AxeBuilder({ page }).disableRules(["target-size", "link-name", "region"]).analyze();
+
+        expect(accessibilityScanResults.violations).toEqual([]);
+      } finally {
+        await prisma.mediaApplication.delete({ where: { id: testApp.id } }).catch(() => {});
+      }
     });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a8e1f7 and 8686700.

📒 Files selected for processing (1)
  • e2e-tests/tests/media-application-approval.spec.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.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
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.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. 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.

Files:

  • e2e-tests/tests/media-application-approval.spec.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Files and directories must use kebab-case (e.g., user-service.ts, case-management/).
Never use CommonJS (require(), module.exports). Use ES modules (import/export) exclusively.

Files:

  • e2e-tests/tests/media-application-approval.spec.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must be co-located with source files using the pattern *.test.ts or *.spec.ts and use Vitest.

Files:

  • e2e-tests/tests/media-application-approval.spec.ts
🧠 Learnings (1)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/pages/**/*.ts : Every page must support both English and Welsh with separate `en` and `cy` content objects in the controller, and Welsh content must be tested with `?lng=cy` query parameter.

Applied to files:

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

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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)


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

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (5)
e2e-tests/tests/media-application-approval.spec.ts (5)

1-4: LGTM!

Imports are correctly structured with .js extension for local imports as required for ESM with Node.js "nodenext" module resolution.


8-38: LGTM!

Test setup and teardown are well-structured with proper error handling for cleanup.


172-194: LGTM!

Role-based access control tests are well-structured with proper session isolation using clearCookies(). The tests correctly verify both tile visibility and direct URL access restrictions.


253-277: LGTM! Welsh language support correctly tested.

Tests properly use the ?lng=cy query parameter to verify Welsh translations across the media applications pages, as required by the coding guidelines. Based on learnings, this aligns with the requirement that Welsh content must be tested with the ?lng=cy query parameter.


78-79: Static analysis ReDoS warnings are false positives in this context.

The applicationId is a Prisma-generated UUID containing only hex characters and hyphens, which are not regex metacharacters. The ReDoS risk flagged by static analysis does not apply here since the variable content is controlled test data.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants