Skip to content

VIBE-229 Reject Media Application - #161

Merged
junaidiqbalmoj merged 17 commits into
masterfrom
vibe-229-reject-media-application
Dec 23, 2025
Merged

junaidiqbalmoj merged 17 commits into
masterfrom
vibe-229-reject-media-application

Conversation

@junaidiqbalmoj

@junaidiqbalmoj junaidiqbalmoj commented Dec 2, 2025

Copy link
Copy Markdown
Contributor

Jira link

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

Change description

Reject Media Application

Summary by CodeRabbit

  • New Features
    • Media application rejection workflow: select reasons, confirm rejection, and view rejection success pages
    • Rejection notification emails sent to applicants
    • Full Welsh language support across the rejection flow
  • Tests
    • Comprehensive unit and end-to-end tests covering approval and rejection flows, validations, accessibility, and multilingual behavior
  • Chores
    • Added example environment variable for rejection email template ID

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

@coderabbitai

coderabbitai Bot commented Dec 2, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a media application rejection workflow: new pages/templates (EN/CY), route handlers (reject-reasons, reject, rejected), service function to mark applications REJECTED, GOV.UK Notify support for rejection emails, updated types/tests, E2E suite replacement, and an env var for the rejection template ID.

Changes

Cohort / File(s) Summary
Environment & Config
apps/web/.env.example
Added GOVUK_NOTIFY_TEMPLATE_ID_MEDIA_REJECTION.
Service Layer
libs/admin-pages/src/media-application/service.ts, libs/admin-pages/src/media-application/service.test.ts
Added rejectApplication(id) to validate existence/PENDING and set status to REJECTED; tests for success and error cases.
Notification Module
libs/notification/src/govuk-notify-service.ts, libs/notification/src/govuk-notify-service.test.ts, libs/notification/src/index.ts
New MediaApplicationRejectionEmailData and sendMediaRejectionEmail() using the new env var; exported from index; tests updated.
Reject Reasons Page
libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.ts, .../reject-reasons.njk, .../reject-reasons-en.ts, .../reject-reasons-cy.ts, .../reject-reasons.test.ts
New GET/POST handlers, template, EN/CY translations, validation (≥1 reason), session storage of selected reasons, and unit tests.
Reject Confirmation Page
libs/admin-pages/src/pages/media-applications/[id]/reject.ts, .../reject.njk, .../reject-en.ts, .../reject-cy.ts, .../reject.test.ts
New GET/POST handlers (yes/no confirmation), renders email preview, calls rejectApplication, attempts sendMediaRejectionEmail (errors logged), redirects to success; includes validation and tests.
Rejected (Success) Page
libs/admin-pages/src/pages/media-applications/[id]/rejected.ts, .../rejected.njk, .../rejected-en.ts, .../rejected-cy.ts, .../rejected.test.ts
New GET handler and template to show rejection confirmation, injected reasons/next steps, EN/CY content and tests, 404 and load-error handling.
Navigation & Templates
libs/admin-pages/src/pages/media-applications/[id]/index.njk
Updated rejection link/form action to /media-applications/{{ application.id }}/reject-reasons.
Approve Page Tests
libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
Added Welsh validation test for approve POST when no option selected.
E2E Tests
e2e-tests/tests/media-application-approval.spec.ts (deleted), e2e-tests/tests/media-application-management.spec.ts
Removed legacy approval-only E2E; added unified management E2E covering approval & rejection flows, RBAC, accessibility, Welsh, and DB checks.
Notify Types
types/notifications-node-client/index.d.ts
Extended NotifyClient constructor signature, added NotificationEmailOptions and NotificationResponse, refactored sendEmail signature.
Test Infra
libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts, apps/web/src/server.test.ts
Small test adjustments: added timeout and adjusted mocked app structure.
Docs
docs/tickets/VIBE-229/*
Added plan, implementation summary, tasks, and testing documentation for VIBE-229.

Sequence Diagram(s)

sequenceDiagram
    actor Admin as Admin User
    participant UI as Admin UI (Rejection Pages)
    participant App as Web App / Route Handlers
    participant Service as Media Application Service
    participant DB as Database
    participant Notify as GOV.UK Notify

    Admin->>UI: Open application details
    UI->>App: GET /media-applications/:id/reject-reasons
    App->>Service: getApplicationById(id)
    Service->>DB: read application
    DB-->>Service: application (PENDING)
    Service-->>App: render reasons form

    Admin->>UI: Select reasons & submit
    UI->>App: POST /media-applications/:id/reject-reasons
    App->>App: validate ≥1 reason, store in session
    App-->>UI: redirect to /reject

    Admin->>UI: View confirmation, submit "yes"
    UI->>App: POST /media-applications/:id/reject
    App->>Service: rejectApplication(id)
    Service->>DB: update status -> REJECTED
    DB-->>Service: updated
    par Async send email
        App->>Notify: sendMediaRejectionEmail(personalisation)
        Notify-->>App: response / error
    end
    App-->>UI: redirect to /rejected

    UI->>App: GET /media-applications/:id/rejected
    App->>Service: getApplicationById(id)
    Service->>DB: read application (REJECTED)
    Service-->>App: render rejected page
    App-->>UI: display confirmation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • NatashaAlker
  • ChrisS1512
  • KianKwa

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'VIBE-229 Reject Media Application' clearly and specifically describes the main feature implementation - adding rejection functionality for media applications, directly mirroring the PR's primary objective.
✨ 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-229-reject-media-application

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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

❤️ Share

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

junaidiqbalmoj and others added 3 commits December 18, 2025 16:47
… journeys

- Merged media-application-approval.spec.ts and media-application-rejection.spec.ts into single media-application-management.spec.ts
- Reduced test count from 27 separate tests to 5 comprehensive journey tests
- Combined related test cases into complete end-to-end user flows:
  * Approval workflow with validations, Welsh, and accessibility checks
  * Rejection workflow with validations, Welsh, and accessibility checks
  * Role-based access control verification
  * Comprehensive accessibility testing across all pages
  * Welsh language support verification across all pages
- Optimized test execution by removing redundant beforeEach hooks in single-test sections
- Fixed validation error message assertions to match actual implementation
- All tests passing (5/5)

This follows testing guidelines to minimize test count while ensuring complete coverage through comprehensive user journey tests.

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

Co-Authored-By: Claude <noreply@anthropic.com>
@junaidiqbalmoj
junaidiqbalmoj changed the base branch from vibe-228-approve-media-application to master December 23, 2025 13:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

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

67-157: Comprehensive approval journey test with good coverage.

The test covers validation errors, Welsh translations, accessibility checks, and database state verification. It follows the coding guidelines by using semantic selectors like getByRole and getByText.

Note: Based on learnings, consider adding keyboard navigation verification (e.g., Tab through form fields, Enter to submit) within this journey test for more complete coverage.

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

28-38: Add language specifier to fenced code block.

The static analysis correctly identifies that the code block should have a language specified for proper syntax highlighting.

🔎 Proposed fix
-```
+```text
 pages/media-applications/[id]/
 ├── reject.ts                      # GET/POST: Rejection confirmation form
libs/admin-pages/src/media-application/service.ts (1)

24-36: LGTM!

The rejectApplication function correctly implements the rejection flow, mirroring the approval logic while intentionally preserving the proof of ID file for audit purposes.

Optional: Extract common validation logic

The validation logic (lines 25-33) is identical to approveApplication (lines 7-15). Consider extracting a shared helper to reduce duplication:

+async function validateApplicationForReview(id: string): Promise<MediaApplicationDetails> {
+  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");
+  }
+
+  return application;
+}
+
 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");
-  }
+  const application = await validateApplicationForReview(id);

   await updateApplicationStatus(id, APPLICATION_STATUS.APPROVED);

   if (application.proofOfIdPath) {
     await deleteProofOfIdFile(application.proofOfIdPath);
   }
 }

 export async function rejectApplication(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 validateApplicationForReview(id);

   await updateApplicationStatus(id, APPLICATION_STATUS.REJECTED);
 }
libs/admin-pages/src/pages/media-applications/[id]/rejected.ts (1)

20-24: Consider validating reason keys before mapping.

Line 23 maps selectedReasons keys to localized text without validation. If the session contains an invalid key (e.g., due to tampering or code changes), the mapped value will be undefined. This could lead to display issues in the template.

Recommended: Add key validation
 // Get rejection reasons from session
 const sessionReasons = req.session?.rejectionReasons || {};
 const selectedReasons = sessionReasons.selectedReasons || [];
-const reasonsList = selectedReasons.map((key: string) => lang.reasons[key as keyof typeof lang.reasons]);
+const validReasonKeys = Object.keys(lang.reasons);
+const reasonsList = selectedReasons
+  .filter((key: string) => validReasonKeys.includes(key))
+  .map((key: string) => lang.reasons[key as keyof typeof lang.reasons]);
libs/admin-pages/src/pages/media-applications/[id]/rejected.test.ts (1)

41-50: Mock application data contains fields not returned by getApplicationById.

The mock mockApplication includes phoneNumber and createdAt, but according to getApplicationById in queries.ts, the query returns appliedDate (not createdAt) and does not include phoneNumber. While this doesn't break the tests since the mocked function returns whatever you specify, aligning mock data with the actual data shape improves test accuracy and maintainability.

🔎 Suggested mock data alignment
       const mockApplication = {
         id: "app-123",
         name: "John Smith",
         employer: "BBC",
         email: "john@bbc.co.uk",
-        phoneNumber: "07700900123",
         proofOfIdPath: "/uploads/proof-app-123.pdf",
+        proofOfIdOriginalName: "proof-app-123.pdf",
         status: "REJECTED" as const,
-        createdAt: new Date("2024-01-01")
+        appliedDate: new Date("2024-01-01")
       };

Also applies to: 81-90

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

48-51: Manual checkbox collection could be more maintainable.

The current approach manually checks each reason. Consider using a data-driven approach for easier maintenance if more reasons are added.

🔎 Alternative approach
-  // Collect selected reasons
-  const selectedReasons: string[] = [];
-  if (req.body.notAccredited) selectedReasons.push("notAccredited");
-  if (req.body.invalidId) selectedReasons.push("invalidId");
-  if (req.body.detailsMismatch) selectedReasons.push("detailsMismatch");
+  // Collect selected reasons
+  const REASON_KEYS = ["notAccredited", "invalidId", "detailsMismatch"] as const;
+  const selectedReasons = REASON_KEYS.filter((key) => req.body[key]);
libs/admin-pages/src/pages/media-applications/[id]/reject.test.ts (1)

60-69: Mock application data contains fields not returned by getApplicationById.

Similar to rejected.test.ts, the mock includes phoneNumber and createdAt instead of appliedDate. Consider aligning with the actual query return shape.

Also applies to: 113-122

libs/admin-pages/src/pages/media-applications/[id]/reject.ts (2)

96-118: Consider structured logging instead of console.error with emoji.

Using console.error with an emoji prefix may not integrate well with structured logging systems. Consider using a proper logger that the application likely already uses.

Additionally, the BASE_URL fallback to https://localhost:8080 could inadvertently appear in production emails if the environment variable is missing. Consider failing explicitly or using a more appropriate default.

🔎 Suggested improvements
-      const linkToService = process.env.BASE_URL || "https://localhost:8080";
+      const linkToService = process.env.BASE_URL;
+      if (!linkToService) {
+        console.error("BASE_URL environment variable is not set");
+      }

       await sendMediaRejectionEmail({
         fullName: application.name,
         email: application.email,
         rejectReasons,
-        linkToService
+        linkToService: linkToService || ""
       });
     } catch (error) {
-      console.error("❌ Failed to send rejection email:", error);
+      console.error("Failed to send rejection email:", error);
     }

101-107: Type annotation for the map callback could be more precise.

The r parameter is typed as string[], but the mapping at line 99 produces values from lang.reasons[key] which are indeed arrays. However, this implicit knowledge could be clearer with explicit typing in the language file interface.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81fb0bd and 1a44c61.

📒 Files selected for processing (30)
  • apps/web/.env.example
  • apps/web/src/server.test.ts
  • docs/tickets/VIBE-229/implementation-summary.md
  • docs/tickets/VIBE-229/plan.md
  • docs/tickets/VIBE-229/tasks.md
  • e2e-tests/tests/media-application-approval.spec.ts
  • e2e-tests/tests/media-application-management.spec.ts
  • libs/admin-pages/src/media-application/service.test.ts
  • libs/admin-pages/src/media-application/service.ts
  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/index.njk
  • libs/admin-pages/src/pages/media-applications/[id]/reject-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.njk
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.njk
  • libs/admin-pages/src/pages/media-applications/[id]/reject.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.njk
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.ts
  • libs/notification/src/govuk-notify-service.test.ts
  • libs/notification/src/govuk-notify-service.ts
  • libs/notification/src/index.ts
  • libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts
  • types/notifications-node-client/index.d.ts
💤 Files with no reviewable changes (1)
  • e2e-tests/tests/media-application-approval.spec.ts
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons-cy.ts
  • libs/admin-pages/src/media-application/service.ts
  • libs/notification/src/govuk-notify-service.ts
  • libs/notification/src/index.ts
  • libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.test.ts
  • libs/admin-pages/src/media-application/service.test.ts
  • libs/notification/src/govuk-notify-service.test.ts
  • apps/web/src/server.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-cy.ts
  • types/notifications-node-client/index.d.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-en.ts
  • e2e-tests/tests/media-application-management.spec.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.test.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use SCREAMING_SNAKE_CASE for constants: MAX_FILE_SIZE, DEFAULT_TIMEOUT

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons-cy.ts
  • libs/admin-pages/src/media-application/service.ts
  • libs/notification/src/govuk-notify-service.ts
  • libs/notification/src/index.ts
  • libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.test.ts
  • libs/admin-pages/src/media-application/service.test.ts
  • libs/notification/src/govuk-notify-service.test.ts
  • apps/web/src/server.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-cy.ts
  • types/notifications-node-client/index.d.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-en.ts
  • e2e-tests/tests/media-application-management.spec.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.test.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.test.ts
  • libs/admin-pages/src/media-application/service.test.ts
  • libs/notification/src/govuk-notify-service.test.ts
  • apps/web/src/server.test.ts
  • e2e-tests/tests/media-application-management.spec.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.test.ts
libs/*/src/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons-cy.ts
  • libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.test.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Input validation must be performed on all endpoints

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons-cy.ts
  • libs/admin-pages/src/media-application/service.ts
  • libs/notification/src/govuk-notify-service.ts
  • libs/notification/src/index.ts
  • libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.test.ts
  • libs/admin-pages/src/media-application/service.test.ts
  • libs/notification/src/govuk-notify-service.test.ts
  • apps/web/src/server.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-cy.ts
  • types/notifications-node-client/index.d.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-en.ts
  • e2e-tests/tests/media-application-management.spec.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.test.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons-cy.ts
  • libs/admin-pages/src/media-application/service.ts
  • libs/notification/src/govuk-notify-service.ts
  • libs/notification/src/index.ts
  • libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.test.ts
  • libs/admin-pages/src/media-application/service.test.ts
  • libs/notification/src/govuk-notify-service.test.ts
  • apps/web/src/server.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-cy.ts
  • types/notifications-node-client/index.d.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-en.ts
  • e2e-tests/tests/media-application-management.spec.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.test.ts
libs/*/src/pages/**/*.njk

📄 CodeRabbit inference engine (CLAUDE.md)

Nunjucks templates must extend 'layouts/base-templates.njk' and use GOV.UK Frontend component macros

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/index.njk
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.njk
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.njk
  • libs/admin-pages/src/pages/media-applications/[id]/reject.njk
e2e-tests/**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.test.ts
  • e2e-tests/tests/media-application-management.spec.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.test.ts
  • docs/tickets/VIBE-229/tasks.md
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to libs/*/src/pages/**/*.ts : Provide both `en` and `cy` language objects in page controllers for English and Welsh support

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject-en.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.test.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Welsh translations are required for all user-facing text

Applied to files:

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

Applied to files:

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

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject-cy.ts
  • libs/admin-pages/src/pages/media-applications/[id]/rejected-cy.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to libs/*/src/pages/**/*.njk : Nunjucks templates must extend 'layouts/base-templates.njk' and use GOV.UK Frontend component macros

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.njk
  • libs/admin-pages/src/pages/media-applications/[id]/rejected.njk
  • libs/admin-pages/src/pages/media-applications/[id]/reject.njk
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to **/package.json : All packages must use `"test": "vitest run"` script in package.json

Applied to files:

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

Applied to files:

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

Applied to files:

  • e2e-tests/tests/media-application-management.spec.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to e2e-tests/**/*.spec.ts : E2E tests should use selectors in priority order: getByRole(), getByLabel(), getByText(), getByTestId()

Applied to files:

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

Applied to files:

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

Applied to files:

  • e2e-tests/tests/media-application-management.spec.ts
🧬 Code graph analysis (9)
libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts (2)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-39)
libs/admin-pages/src/pages/media-applications/[id]/approve.ts (1)
  • POST (110-110)
libs/admin-pages/src/media-application/service.ts (2)
libs/admin-pages/src/media-application/queries.ts (2)
  • getApplicationById (23-39)
  • updateApplicationStatus (41-60)
libs/admin-pages/src/media-application/model.ts (1)
  • APPLICATION_STATUS (1-5)
libs/notification/src/govuk-notify-service.ts (4)
libs/notification/src/index.ts (1)
  • sendMediaRejectionEmail (1-1)
types/notifications-node-client/index.d.ts (1)
  • NotifyClient (25-33)
libs/admin-pages/src/global.d.ts (1)
  • NotifyClient (2-14)
libs/notification/src/global.d.ts (1)
  • NotifyClient (2-14)
libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.ts (3)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-39)
libs/admin-pages/src/pages/media-applications/[id]/reject.ts (2)
  • GET (131-131)
  • POST (132-132)
libs/auth/src/middleware/authorise.ts (1)
  • requireRole (12-39)
libs/admin-pages/src/pages/media-applications/[id]/reject.test.ts (6)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-39)
libs/admin-pages/src/pages/media-applications/[id]/reject.ts (2)
  • GET (131-131)
  • POST (132-132)
libs/admin-pages/src/pages/media-applications/[id]/rejected.ts (1)
  • GET (47-47)
libs/admin-pages/src/media-application/service.ts (1)
  • rejectApplication (24-36)
libs/notification/src/govuk-notify-service.ts (1)
  • sendMediaRejectionEmail (40-59)
libs/notification/src/index.ts (1)
  • sendMediaRejectionEmail (1-1)
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 (1)
  • rejectApplication (24-36)
libs/notification/src/govuk-notify-service.test.ts (1)
libs/notification/src/govuk-notify-service.ts (1)
  • sendMediaRejectionEmail (40-59)
libs/admin-pages/src/pages/media-applications/[id]/rejected.ts (2)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-39)
libs/auth/src/middleware/authorise.ts (1)
  • requireRole (12-39)
libs/admin-pages/src/pages/media-applications/[id]/rejected.test.ts (3)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-39)
libs/admin-pages/src/pages/media-applications/[id]/reject.ts (1)
  • GET (131-131)
libs/admin-pages/src/pages/media-applications/[id]/rejected.ts (1)
  • GET (47-47)
🪛 ast-grep (0.40.3)
e2e-tests/tests/media-application-management.spec.ts

[warning] 86-86: 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/${approvalApplicationId})
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/${approvalApplicationId}/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] 107-107: 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/${approvalApplicationId}$)
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/${approvalApplicationId}/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] 149-149: 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] 177-177: 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/${rejectionApplicationId}/reject-reasons)
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] 190-190: 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/${rejectionApplicationId}/reject)
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] 201-201: 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/${rejectionApplicationId}$)
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] 210-210: 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/${rejectionApplicationId}/reject)
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] 213-213: 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/${rejectionApplicationId}/rejected)
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] 283-283: 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/${rejectionApplicationId}/reject)
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] 314-314: 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/${rejectionApplicationId}/reject)
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)

🪛 GitHub Actions: Test
libs/notification/src/govuk-notify-service.test.ts

[error] 1-1: AssertionError in GOV Notify Service tests: should send email with correct parameters. The mock was called with different personalisation keys: expected 'employer' and 'name' but received 'Employer' and 'full name'.

🪛 GitHub Check: Test Changed Packages
libs/notification/src/govuk-notify-service.test.ts

[failure] 55-55: src/govuk-notify-service.test.ts > GOV Notify Service > sendMediaApprovalEmail > should send email with correct parameters
AssertionError: expected "vi.fn()" to be called with arguments: [ 'test-template-id-approval', …(2) ]

Received:

1st vi.fn() call:

[
"test-template-id-approval",
"john@example.com",
{
"personalisation": {

  •   "employer": "BBC",
    
  •   "name": "John Smith",
    
  •   "Employer": "BBC",
    
  •   "full name": "John Smith",
    },
    
  • "reference": StringContaining "media-approval-",
    
  • "reference": "media-approval-1766495064088",
    
    },
    ]

Number of calls: 1

❯ src/govuk-notify-service.test.ts:55:29

🪛 LanguageTool
docs/tickets/VIBE-229/implementation-summary.md

[style] ~17-~17: ‘with success’ might be wordy. Consider a shorter alternative.
Context: ...lications/[id]/rejected.njk- Template with success banner -libs/admin-pages/src/pages...

(EN_WORDINESS_PREMIUM_WITH_SUCCESS)

docs/tickets/VIBE-229/tasks.md

[style] ~28-~28: ‘with success’ might be wordy. Consider a shorter alternative.
Context: ...pplications/[id]/rejected.njk` template with success banner - [x] Verify "Reject application...

(EN_WORDINESS_PREMIUM_WITH_SUCCESS)

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

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

(MD040, fenced-code-language)

⏰ 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 (40)
libs/admin-pages/src/pages/media-applications/[id]/approve.test.ts (1)

216-259: Good addition of Welsh language validation test coverage.

This test correctly validates the Welsh error message ("Rhaid dewis opsiwn") and Welsh UI strings when no radio option is selected. The test structure follows the existing patterns established for English validation.

Note: The mockApplication here omits proofOfIdOriginalName (present in other tests at line 179), resulting in proofOfIdFilename: undefined at line 245. This appears intentional to test the undefined case, but verify this matches the expected behavior when the original filename is not set.

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

55-55: Route update aligns with the new rejection reasons workflow.

The form action correctly points to the new /reject-reasons endpoint, introducing an intermediate step for selecting rejection reasons before the final confirmation page. This matches the broader rejection flow implementation described in the PR.

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

26-43: Improved Express app mocking accuracy.

The updated mock correctly represents an Express app as a function with an attached listen method, rather than a plain object. This is a more accurate representation of Express's actual API and improves test reliability.

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

108-191: New rejection email test suite is well-structured.

The sendMediaRejectionEmail test suite correctly covers:

  • Success path with proper personalisation keys (full-name, reject-reasons, link-to-service)
  • API error handling
  • Network error handling

The personalisation keys here align with the implementation shown in the relevant code snippet (using "full-name", "reject-reasons", "link-to-service").

e2e-tests/tests/media-application-management.spec.ts (4)

9-58: Solid test setup with Prisma-based fixture management.

The beforeAll/afterAll hooks properly create and clean up test data using Prisma. The .catch(() => {}) pattern in cleanup is appropriate to prevent test failures if records are already deleted during the test.


86-87: ReDoS static analysis warnings are false positives here.

The static analyzer flags new RegExp() with variable input as a potential ReDoS risk. However, in this E2E test context:

  • The approvalApplicationId and rejectionApplicationId are UUIDs generated by Prisma in beforeAll
  • These are not user-controlled inputs
  • The patterns are simple path matches without complex regex features

The warnings can be safely ignored as the variable content is controlled test data.

Also applies to: 96-97, 107-108, 114-115, 149-150


238-252: Good RBAC validation for Local Admin access restrictions.

The test correctly verifies that Local Admin users cannot see the media applications tile and cannot access the feature directly. Consider also verifying the response when accessing /media-applications directly (e.g., checking for 403 or redirect to an error page).


255-287: Thorough accessibility testing across multiple pages.

Good coverage of axe-core checks on all media application pages. The disabled rules (target-size, link-name, region) should be documented or addressed in follow-up work if they represent known issues versus intentional exclusions.

Are the disabled axe rules (target-size, link-name, region) documented technical debt, or should they be addressed as part of accessibility compliance?

docs/tickets/VIBE-229/plan.md (2)

74-82: Plan signature differs from actual implementation.

The planned sendMediaRejectionEmail signature shows:

{ name, email, employer }

However, the actual implementation (from govuk-notify-service.ts) uses:

{ fullName, email, rejectReasons, linkToService }

Consider updating this planning document to reflect the implemented API, or marking it as superseded if the implementation intentionally diverged.


190-223: Clarification questions provide good traceability.

These questions are valuable for documenting assumptions and open items. Consider moving answered questions to a separate "Resolved" section as the implementation progresses, keeping the document as a living record.

apps/web/.env.example (1)

39-39: Environment variable follows established patterns.

The new GOVUK_NOTIFY_TEMPLATE_ID_MEDIA_REJECTION variable is correctly placed alongside the approval template ID and follows the SCREAMING_SNAKE_CASE naming convention per coding guidelines.

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

1-16: LGTM!

The Welsh localization structure is consistent with the English version and follows the required pattern for bilingual support.

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

1-1: LGTM!

The export correctly adds sendMediaRejectionEmail to the public API, following ES module conventions with the .js extension.

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

1-16: LGTM!

The English localization is well-structured and consistent with the Welsh translation file.

libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.njk (3)

1-4: LGTM!

The template correctly extends the base layout and imports the necessary GOV.UK Frontend macros.


41-60: LGTM!

The checkbox items are correctly configured with appropriate values, text, names, and checked states.


40-40: The conditional syntax if ... else is valid in Nunjucks and works correctly inside object literals within macro parameters. This pattern is used consistently throughout the codebase (in reference-data-upload, add-region, add-jurisdiction, add-sub-jurisdiction, manual-upload, non-strategic-upload, and public-pages files). No change is needed.

Likely an incorrect or invalid review comment.

libs/admin-pages/src/pages/media-applications/[id]/rejected.ts (2)

7-18: LGTM!

The language selection and application retrieval logic is correct, with appropriate 404 handling.


25-45: LGTM!

The template rendering logic correctly passes all required data with proper error handling.

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

89-135: LGTM!

The test coverage for rejectApplication is comprehensive, covering success, not-found, and already-reviewed scenarios. The tests correctly verify that file deletion does NOT occur during rejection, which aligns with the intended audit trail behavior.

libs/admin-pages/src/pages/media-applications/[id]/rejected.njk (3)

1-3: LGTM!

The template correctly extends the base layout and imports the necessary GOV.UK Frontend macros.


36-64: LGTM!

The summary list construction correctly builds the application details rows with proper date formatting and conditional reasons display.


66-68: LGTM!

The "What happens next" section appropriately uses the safe filter to render the mailto link HTML and dynamically inserts the applicant's email address.

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

1-148: Well-structured test suite with good coverage.

The tests cover English/Welsh rendering, 404 handling, database errors, and middleware verification. The structure follows the established patterns in the codebase.

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

40-59: Well-structured rejection email function.

The function follows the same pattern as sendMediaApprovalEmail, with proper validation of environment variables before use and descriptive error messages.

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

1-27: Welsh localization looks complete and well-structured.

The file provides all required Welsh translations matching the English counterpart structure, including page title, table headers, reasons with HTML formatting, and error messages. This satisfies the requirement for Welsh language support in page controllers.

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

1-7: Template correctly extends base layout and uses GOV.UK components.

The template follows the required pattern of extending layouts/base-template.njk and properly imports GOV.UK Frontend component macros.

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

80-81: Exports follow the required pattern.

Both GET and POST handlers are properly exported with requireRole middleware protection, following the established pattern for page controllers.

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

1-27: English localization is well-structured.

The file provides all required English translations with a clear structure matching the Welsh counterpart. The error messages are user-friendly and the content is appropriate for the rejection flow.

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

1-43: Comprehensive Welsh localization for rejection confirmation.

The file provides thorough Welsh translations including page content, form labels, email preview content, and error messages. This enables full Welsh language support for the rejection confirmation flow.

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

348-370: Good resilience testing for email failures.

Testing that rejection still completes and redirects even when email notification fails is an important edge case. This ensures the core business logic isn't blocked by notification failures.


1-410: Comprehensive test suite with excellent coverage.

The tests thoroughly cover:

  • GET/POST handlers in both English and Welsh
  • Validation error scenarios
  • Success and failure paths for rejection
  • Email notification resilience
  • 404 and database error handling
  • Middleware verification

This provides strong confidence in the rejection flow implementation.


338-344: No action needed—test and implementation are correctly aligned.

The rejection email format with the caret character (^) as a line continuation marker is correctly implemented in reject.ts (line 105) and properly validated by the test expectation. The implementation strips HTML tags and formats each rejection reason as ${index + 1}. ${reason}\n^${explanation}, which matches the test assertion exactly.

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

1-9: Imports follow coding guidelines.

The imports correctly use workspace aliases (@hmcts/*) and .js extensions for relative imports, following ES module conventions.


11-51: GET handler is well-structured with proper error handling.

The handler correctly implements language switching, session retrieval, and error handling. Authorization is properly delegated to the middleware. The optional chaining on req.session?.rejectionReasons is a good defensive pattern.


131-132: Role-based authorization correctly applied.

The GET and POST exports properly use requireRole middleware to restrict access to INTERNAL_ADMIN_CTSC users, following the established security pattern.

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

1-54: Comprehensive task tracking documentation.

The task list provides excellent traceability for the implementation, covering service layer, notifications, page controllers, templates, and thorough testing requirements. All items are marked complete, indicating a well-planned and executed feature.

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

13-20: Verify HTML in translation strings is safely rendered.

The reasons object contains HTML markup including <strong> tags and anchor elements. Ensure the template renders these using safe HTML output (e.g., Nunjucks | safe filter) and that user input is never interpolated into these strings to prevent XSS.

The hardcoded URL in notAccredited (line 16) may need to be configurable if it changes across environments.


1-43: English translations structure looks good.

The translation object provides comprehensive coverage for the rejection page including page metadata, table headers, rejection reasons, email preview content, radio options, and error messages. This follows the established pattern for i18n support.

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

1-152: Excellent implementation documentation.

This summary provides comprehensive documentation covering:

  • Files created with clear organization
  • Test coverage details with specific counts
  • Key implementation notes (file retention, email resilience, authorization)
  • Environment variables and routes
  • Acceptance criteria checklist
  • Actionable next steps for deployment

The documentation clearly distinguishes the rejection flow from the approval flow (e.g., file retention behavior) and provides good guidance for deployment.

@@ -0,0 +1,81 @@
import { requireRole, USER_ROLES } from "@hmcts/auth";
import "@hmcts/web-core";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all instances of @hmcts/web-core imports in TypeScript files
echo "=== Searching for @hmcts/web-core imports ==="
rg 'import.*@hmcts/web-core' --type=ts -n

echo -e "\n=== Check the reject-reasons.ts file content ==="
cat -n libs/admin-pages/src/pages/media-applications/\[id\]/reject-reasons.ts

echo -e "\n=== Check what @hmcts/web-core provides ==="
fd -e d.ts -e ts 'web-core' | head -20

Repository: hmcts/cath-service

Length of output: 6748


Add explanatory comment to the side-effect import for consistency.

The import "@hmcts/web-core"; on line 2 is necessary for Express type augmentation (required for the req.session usage on lines 22 and 69). However, similar files in the codebase (non-strategic-upload/index.ts, manual-upload/index.ts, app.ts) include an explicit comment explaining this purpose. Add the comment // Import for Express type augmentation for clarity and consistency.

🤖 Prompt for AI Agents
In libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.ts around
line 2, the side-effect import `import "@hmcts/web-core";` is missing an
explanatory comment; add the comment `// Import for Express type augmentation`
immediately after that import to document that it is required for Express
typings (e.g., `req.session`) and to match the style used in other files.

<p>{{ error }}</p>
</div>
</div>
<p><a href="/media-applications" class="govuk-link">Back to applications list</a></p>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded English text in error fallback.

The "Back to applications list" text should use a localized variable to support Welsh users.

🔎 Suggested fix
-      <p><a href="/media-applications" class="govuk-link">Back to applications list</a></p>
+      <p><a href="/media-applications" class="govuk-link">{{ returnLink }}</a></p>
📝 Committable suggestion

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

Suggested change
<p><a href="/media-applications" class="govuk-link">Back to applications list</a></p>
<p><a href="/media-applications" class="govuk-link">{{ returnLink }}</a></p>
🤖 Prompt for AI Agents
In libs/admin-pages/src/pages/media-applications/[id]/reject.njk around line 19
the link text "Back to applications list" is hardcoded in English; replace it
with a localized variable/translation key from the template context (e.g. use
the existing i18n/translation helper or a page-specific key) and update the
route handler that renders this template to pass the translation or i18n helper
so Welsh users see the localized string; ensure the translation key is added to
the relevant locale files.

Comment on lines +22 to +25
{{ govukErrorSummary({
titleText: "There is a problem",
errorList: errors
}) }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded English in error summary title.

The error summary titleText: "There is a problem" should be a localized variable for Welsh support.

🔎 Suggested fix
       {{ govukErrorSummary({
-          titleText: "There is a problem",
+          titleText: errorSummaryTitle,
           errorList: errors
         }) }}

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

🤖 Prompt for AI Agents
In libs/admin-pages/src/pages/media-applications/[id]/reject.njk around lines 22
to 25, the error summary titleText is hardcoded to English ("There is a
problem"); replace this hardcoded string with a localized variable (e.g., pass a
translated title from the template context or use the existing i18n helper) so
the titleText uses the appropriate Welsh/English translation at render time;
ensure the template receives the localized key (like t('errorSummary.title') or
context.errorSummaryTitle) and use that variable in place of the hardcoded
string.

Comment on lines +78 to +81
href: '/media-applications/' + application.id + '/proof-of-id',
text: viewLinkText,
visuallyHiddenText: 'proof of ID',
attributes: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded English in visually hidden text.

The visuallyHiddenText: 'proof of ID' is hardcoded English and won't be accessible to Welsh screen reader users. Consider using a localized variable.

🔎 Suggested fix
                 text: viewLinkText,
-                visuallyHiddenText: 'proof of ID',
+                visuallyHiddenText: visuallyHiddenProofOfId,
                 attributes: {

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

🤖 Prompt for AI Agents
In libs/admin-pages/src/pages/media-applications/[id]/reject.njk around lines 78
to 81, the visuallyHiddenText is hardcoded as 'proof of ID' which breaks
localization; replace the hardcoded string with the appropriate localized
variable (e.g., use the page/view translation helper or injected i18n key) and
pass the translation key for "proof of ID" so Welsh screen readers receive the
localized text; ensure the translation key exists in locale files and use the
same translation method used elsewhere in this template.

Comment on lines +21 to +34
{% set reasonsHtml = '' %}
{% if reasonsList and reasonsList.length > 0 %}
{% set reasonsHtml = '<ol class="govuk-list govuk-list--number">' %}
{% for reason in reasonsList %}
{% if reason[0] %}
{% set reasonsHtml = reasonsHtml + '<li>' + reason[0] %}
{% if reason[1] %}
{% set reasonsHtml = reasonsHtml + '<br>' + reason[1] %}
{% endif %}
{% set reasonsHtml = reasonsHtml + '</li>' %}
{% endif %}
{% endfor %}
{% set reasonsHtml = reasonsHtml + '</ol>' %}
{% endif %}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Incorrect array access on string values.

The template treats each reason in reasonsList as an array (accessing reason[0] and reason[1]), but rejected.ts line 23 constructs reasonsList as an array of strings:

const reasonsList = selectedReasons.map((key: string) => lang.reasons[key as keyof typeof lang.reasons]);

This produces ["Full reason text 1", "Full reason text 2", ...], not an array of arrays.

In JavaScript/Nunjucks, strings are indexable by character position, so reason[0] returns the first character (e.g., "T"), and reason[1] returns the second character (e.g., "h"). This would display <li>T<br>h</li> instead of the full rejection reason text.

🔎 Proposed fix

Option 1: Fix the template (recommended)

-{% set reasonsHtml = '<ol class="govuk-list govuk-list--number">' %}
-{% for reason in reasonsList %}
-  {% if reason[0] %}
-    {% set reasonsHtml = reasonsHtml + '<li>' + reason[0] %}
-    {% if reason[1] %}
-      {% set reasonsHtml = reasonsHtml + '<br>' + reason[1] %}
-    {% endif %}
-    {% set reasonsHtml = reasonsHtml + '</li>' %}
-  {% endif %}
-{% endfor %}
-{% set reasonsHtml = reasonsHtml + '</ol>' %}
+{% set reasonsHtml = '<ol class="govuk-list govuk-list--number">' %}
+{% for reason in reasonsList %}
+  {% if reason %}
+    {% set reasonsHtml = reasonsHtml + '<li>' + reason + '</li>' %}
+  {% endif %}
+{% endfor %}
+{% set reasonsHtml = reasonsHtml + '</ol>' %}

Option 2: Change the data structure in rejected.ts

If multi-line reasons are needed, modify rejected.ts line 23 to return tuples:

-const reasonsList = selectedReasons.map((key: string) => lang.reasons[key as keyof typeof lang.reasons]);
+const reasonsList = selectedReasons.map((key: string) => {
+  const reasonText = lang.reasons[key as keyof typeof lang.reasons];
+  // Return as array if multi-line support needed
+  return Array.isArray(reasonText) ? reasonText : [reasonText];
+});
🤖 Prompt for AI Agents
In libs/admin-pages/src/pages/media-applications/[id]/rejected.njk around lines
21 to 34, the template treats each reason as an array (using reason[0] and
reason[1]) but rejected.ts supplies an array of strings, so you should use the
full string instead of indexing characters: replace the reason[0]/reason[1]
logic with inserting the whole reason string into the <li>; if you need
preserved line breaks convert newline characters to <br> (e.g. replace newlines
with <br> before marking safe) or simply output the reason with the appropriate
Nunjucks filters (replace + safe) so the full text displays correctly;
alternatively, if you want to keep the current template, change rejected.ts to
provide arrays/tuples per reason.

Comment thread libs/notification/src/govuk-notify-service.test.ts
Comment thread libs/notification/src/govuk-notify-service.test.ts
Comment on lines +32 to +35
personalisation: {
"full name": data.name,
Employer: data.employer
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n libs/notification/src/govuk-notify-service.ts | head -80

Repository: hmcts/cath-service

Length of output: 2249


🏁 Script executed:

# Search for test files and any references to these personalisation keys
find libs/notification -type f -name "*.spec.ts" -o -name "*.test.ts" | head -20

Repository: hmcts/cath-service

Length of output: 112


🏁 Script executed:

# Search for any references to the personalisation keys or templates
rg "full.name|full-name|reject-reasons|link-to-service|TEMPLATE_ID_MEDIA" libs/notification --type=ts -B2 -A2

Repository: hmcts/cath-service

Length of output: 4436


🏁 Script executed:

# Check if there are any mock files or fixtures
find libs/notification -type f \( -name "*mock*" -o -name "*fixture*" -o -name "*stub*" \) | head -20

Repository: hmcts/cath-service

Length of output: 44


🏁 Script executed:

cat -n libs/notification/src/govuk-notify-service.test.ts | head -150

Repository: hmcts/cath-service

Length of output: 5823


Fix approval email personalisation keys to match template expectations.

The approval email is sending "full name" and Employer keys, but the test expects name and employer. This mismatch will cause the email to fail at runtime. Update lines 32-35 to send the correct personalisation keys that the template expects.

🤖 Prompt for AI Agents
In libs/notification/src/govuk-notify-service.ts around lines 32 to 35, the
personalisation object uses keys "full name" and Employer which don't match the
template/tests; change the keys to name and employer so the personalisation
payload uses { name: data.name, employer: data.employer } when building the
approval email.

})
);
});
}, 10000);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the test file around line 52 to understand the full context
cat -n libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts | sed -n '40,70p'

Repository: hmcts/cath-service

Length of output: 1378


🏁 Script executed:

# Check if this file exists and get its full content to understand the test structure
wc -l libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts

Repository: hmcts/cath-service

Length of output: 135


🏁 Script executed:

# Search for other tests with extended timeouts in the codebase
rg -n 'it\([^,]+,[^,]+,\s*\d+\s*\)' --type ts libs --max-count 20

Repository: hmcts/cath-service

Length of output: 44


🏁 Script executed:

# Also check for Vitest timeout syntax (using vi.setConfig or test timeout)
rg -n 'timeout|setConfig' --type ts -g '*.test.ts' -g '*.spec.ts' libs | head -30

Repository: hmcts/cath-service

Length of output: 682


🏁 Script executed:

# Check if other page controllers use similar dynamic import patterns in tests
rg -n 'await import' --type ts -g '*.test.ts' -g '*.spec.ts' libs | head -20

Repository: hmcts/cath-service

Length of output: 2251


🏁 Script executed:

# Look at the actual page controller being tested to understand why dynamic import is needed
cat -n libs/system-admin-pages/src/pages/reference-data-upload/index.ts | head -30

Repository: hmcts/cath-service

Length of output: 1217


🏁 Script executed:

# Check if there are any related changes to the imports or dependencies in index.ts
git diff HEAD -- libs/system-admin-pages/src/pages/reference-data-upload/index.ts 2>/dev/null || echo "Git diff not available, checking file size"

Repository: hmcts/cath-service

Length of output: 44


🏁 Script executed:

# Check how many tests total exist in this file and their pattern
grep -c "it(" libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts
grep "it(" libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts | wc -l

Repository: hmcts/cath-service

Length of output: 65


🏁 Script executed:

# Let's see all the tests in this file to confirm none have other timeout overrides
grep -n "it(" libs/system-admin-pages/src/pages/reference-data-upload/index.test.ts

Repository: hmcts/cath-service

Length of output: 656


Investigate the root cause of the test timeout requirement.

This test is the only one in the file using an explicit timeout override. All seven other tests in the same file use the identical await import("./index.js") pattern without extended timeouts, and no other tests across the codebase employ explicit timeout configurations. This suggests the timeout is masking an underlying performance issue with module loading rather than a legitimate test duration requirement.

If this test is timing out intermittently at the default limit, the root cause—likely slow imports or module initialization—should be addressed rather than working around it with a timeout increase.

Comment on lines +2 to +32
export interface NotificationEmailOptions {
personalisation?: Record<string, string | number | boolean>;
reference?: string;
emailReplyToId?: string;
oneClickUnsubscribeURL?: string;
}

export interface NotificationResponse {
id: string;
reference?: string;
uri: string;
template: {
id: string;
version: number;
uri: string;
};
content: {
subject: string;
body: string;
from_email: string;
};
}

export class NotifyClient {
constructor(apiKey: string);
constructor(apiKey: string, baseUrl?: string);

sendEmail(
templateId: string,
emailAddress: string,
personalisation?: Record<string, string>,
reference?: string,
emailReplyToId?: string
): Promise<{
id: string;
reference?: string;
uri: string;
template: {
id: string;
version: number;
uri: string;
};
content: {
subject: string;
body: string;
from_email: string;
};
}>;
options?: NotificationEmailOptions
): Promise<NotificationResponse>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

notifications-node-client npm v8.2.1 sendEmail method signature API

💡 Result:

For notifications-node-client v8.2.1 the sendEmail method has two common forms shown in the package docs/usage:

  • sendEmail(templateId, emailAddress, options) — where options is an object that may contain personalisation (object), reference (string) and emailReplyToId (string). [1][2]

Example:
notifyClient.sendEmail(
'template-id',
'user@example.com',
{ personalisation: { name: 'Jane' }, reference: 'ref-123', emailReplyToId: '465' }
).then(...).catch(...);. [1][2]

Sources: npm package README and package analysis. [1] [2]


Remove the undocumented oneClickUnsubscribeURL property or verify it against the actual package version.

The type definitions for NotificationEmailOptions and NotificationResponse follow the correct naming conventions (PascalCase for interfaces, camelCase for properties). However, the oneClickUnsubscribeURL property is not documented in the official notifications-node-client v8.2.1 API—only personalisation, reference, and emailReplyToId are documented. Either remove this property, verify it exists in a later version, or add a comment explaining its purpose.

🤖 Prompt for AI Agents
In types/notifications-node-client/index.d.ts around lines 2 to 32, the
NotificationEmailOptions interface contains an undocumented property
oneClickUnsubscribeURL; remove this property from the interface unless you can
verify it exists in the exact notifications-node-client version we target
(v8.2.1) — if it does exist in a later, intentionally targeted version, update
the package version in package.json and add a brief comment above the property
explaining its purpose and linking to the authoritative docs/change log.

@github-actions

github-actions Bot commented Dec 23, 2025

Copy link
Copy Markdown
Contributor

🎭 Playwright E2E Test Results

201 tests   201 ✅  20m 28s ⏱️
 27 suites    0 💤
  1 files      0 ❌

Results for commit 8b8eda9.

♻️ This comment has been updated with latest results.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a44c61 and e844c6e.

📒 Files selected for processing (3)
  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
  • libs/notification/src/govuk-notify-service.test.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
  • libs/notification/src/govuk-notify-service.test.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use SCREAMING_SNAKE_CASE for constants: MAX_FILE_SIZE, DEFAULT_TIMEOUT

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.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 code using *.test.ts or *.spec.ts naming pattern

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.test.ts
  • libs/notification/src/govuk-notify-service.test.ts
libs/*/src/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Input validation must be performed on all endpoints

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
  • libs/notification/src/govuk-notify-service.test.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.test.ts
  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
  • libs/notification/src/govuk-notify-service.test.ts
🧠 Learnings (4)
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to e2e-tests/**/*.spec.ts : Include validation checks, Welsh translation checks, accessibility checks, and keyboard navigation within a single E2E test journey

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.test.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to libs/*/src/pages/**/*.ts : Provide both `en` and `cy` language objects in page controllers for English and Welsh support

Applied to files:

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

Applied to files:

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

Applied to files:

  • libs/notification/src/govuk-notify-service.test.ts
🧬 Code graph analysis (3)
libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.test.ts (2)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-39)
libs/admin-pages/src/pages/media-applications/[id]/reject.ts (2)
  • GET (141-141)
  • POST (142-142)
libs/admin-pages/src/pages/media-applications/[id]/reject.ts (4)
libs/admin-pages/src/media-application/queries.ts (1)
  • getApplicationById (23-39)
libs/admin-pages/src/media-application/service.ts (1)
  • rejectApplication (24-36)
libs/notification/src/govuk-notify-service.ts (1)
  • sendMediaRejectionEmail (40-59)
libs/notification/src/index.ts (1)
  • sendMediaRejectionEmail (1-1)
libs/notification/src/govuk-notify-service.test.ts (1)
libs/notification/src/govuk-notify-service.ts (1)
  • sendMediaRejectionEmail (40-59)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (3)
libs/admin-pages/src/pages/media-applications/[id]/reject-reasons.test.ts (1)

1-299: Excellent test coverage for the reject-reasons page.

The test suite comprehensively covers:

  • GET/POST handlers in both English and Welsh
  • Session state management for rejection reasons
  • Error scenarios (404, database failures)
  • Validation edge cases (no selection, single/multiple selections)
  • Language preservation in redirects

The tests are well-structured and follow established patterns from the codebase.

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

1-270: Comprehensive test coverage for rejection email flow.

The new sendMediaRejectionEmail test suite provides thorough coverage including:

  • Environment variable validation (API key and rejection template ID)
  • Successful email sending with correct personalisation keys ("full-name", "reject-reasons", "link-to-service")
  • API error handling with appropriate error messages
  • Network error handling

The tests mirror the existing approval email test structure, maintaining consistency across the notification service.

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

111-117: No action required — rejection reason language files correctly provide array structures.

The reject-en.ts and reject-cy.ts language files both define reasons with arrays matching the expected structure. Each reason (e.g., notAccredited, invalidId, detailsMismatch) is a 2-element array containing [reason, explanation], which aligns with the destructuring pattern at lines 113-114. Both English and Welsh language objects are present as required.

return `${index + 1}. ${reason}\n^${explanation}`;
})
.join("\n");
const linkToService = process.env.BASE_URL || "https://localhost:8080";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

BASE_URL fallback to localhost will break email links in production.

If the BASE_URL environment variable is not configured in production, rejection emails will contain links to https://localhost:8080, rendering them unusable for applicants.

The fallback should either throw an error during startup if BASE_URL is missing, or use a production-appropriate default.

🔎 Proposed fix
-      const linkToService = process.env.BASE_URL || "https://localhost:8080";
+      if (!process.env.BASE_URL) {
+        throw new Error("BASE_URL environment variable not configured");
+      }
+      const linkToService = process.env.BASE_URL;

Alternatively, validate BASE_URL at application startup to fail fast rather than at runtime during rejection.

📝 Committable suggestion

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

Suggested change
const linkToService = process.env.BASE_URL || "https://localhost:8080";
if (!process.env.BASE_URL) {
throw new Error("BASE_URL environment variable not configured");
}
const linkToService = process.env.BASE_URL;
🤖 Prompt for AI Agents
In libs/admin-pages/src/pages/media-applications/[id]/reject.ts around line 118,
the current fallback to "https://localhost:8080" for process.env.BASE_URL will
produce invalid links in production; remove the localhost fallback and instead
require BASE_URL in production (throw a clear error if NODE_ENV === "production"
and BASE_URL is missing) or validate BASE_URL at application startup and fail
fast; update the code to read BASE_URL without a non-production fallback and add
an initialization-time check that logs/throws a descriptive error when BASE_URL
is unset in production so emails never contain localhost links.

linkToService
});
} catch (error) {
console.error("❌ Failed to send rejection email:", error);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid logging sensitive data in error messages.

The error object logged here may contain sensitive information from the application object (name, email) and rejection reasons. According to coding guidelines, sensitive data should not be included in logs.

🔎 Proposed fix
-      console.error("❌ Failed to send rejection email:", error);
+      console.error("❌ Failed to send rejection email:", { 
+        message: error instanceof Error ? error.message : "Unknown error",
+        applicationId: id 
+      });

This logs only the error message and application ID, omitting PII like names and email addresses.

📝 Committable suggestion

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

Suggested change
console.error("❌ Failed to send rejection email:", error);
console.error("❌ Failed to send rejection email:", {
message: error instanceof Error ? error.message : "Unknown error",
applicationId: id
});
🤖 Prompt for AI Agents
In libs/admin-pages/src/pages/media-applications/[id]/reject.ts around line 127,
the code logs the entire error object which may contain sensitive PII from the
application (name, email, rejection reasons); change the logging to avoid
sensitive data by logging only non-PII context (e.g., a short message,
application ID) and the error message or a safe error code, and remove or redact
any application fields from the logged output so only minimal diagnostic info is
recorded.

@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

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

133-133: BASE_URL fallback to localhost will break email links in production.

This issue was already flagged in a previous review. The fallback to https://localhost:8080 will produce invalid links in production rejection emails if the BASE_URL environment variable is not configured.


142-142: Avoid logging sensitive data in error messages.

This issue was already flagged in a previous review. The error object may contain sensitive PII from the application (name, email) and rejection reasons. According to coding guidelines, sensitive data should not be included in logs.

🧹 Nitpick comments (2)
libs/admin-pages/src/pages/media-applications/[id]/reject.ts (2)

13-34: Prefer a well-known library for HTML stripping.

The manual stripHtmlTags implementation may miss edge cases like malformed HTML, HTML entities, or nested script tags. Consider using a mature library such as striptags or strip-html instead.

🔎 Example using striptags library

First, install the library:

npm install striptags

Then replace the function:

+import striptags from 'striptags';
+
-const MAX_REASON_LENGTH = 10000;
-
-function stripHtmlTags(input: string): string {
-  if (input.length > MAX_REASON_LENGTH) {
-    throw new Error("Input exceeds maximum allowed length");
-  }
-
-  let result = "";
-  let insideTag = false;
-
-  for (let i = 0; i < input.length; i++) {
-    const char = input[i];
-
-    if (char === "<") {
-      insideTag = true;
-    } else if (char === ">") {
-      insideTag = false;
-    } else if (!insideTag) {
-      result += char;
-    }
-  }
-
-  return result;
-}

Then use striptags(input) at lines 128-129 instead of calling stripHtmlTags.


122-132: Add defensive checks for session data structure.

The code assumes rejectionReasons exists in session and that each reason in reasonsList is a two-element string array. If a user navigates directly to this page, or if the session data structure changes, the mapping on line 127 could throw a runtime error.

Consider adding validation:

const sessionReasons = req.session?.rejectionReasons || {};
const selectedReasons = sessionReasons.selectedReasons || [];

if (selectedReasons.length === 0) {
  // Redirect back to reject-reasons selection
  return res.redirect(`/media-applications/${id}/reject-reasons`);
}

const reasonsList = selectedReasons
  .map((key: string) => lang.reasons[key as keyof typeof lang.reasons])
  .filter((r): r is [string, string] => Array.isArray(r) && r.length === 2);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e844c6e and 8b8eda9.

📒 Files selected for processing (1)
  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use SCREAMING_SNAKE_CASE for constants: MAX_FILE_SIZE, DEFAULT_TIMEOUT

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
libs/*/src/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Input validation must be performed on all endpoints

Files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to **/*.{ts,tsx} : Do not include sensitive data in logs

Applied to files:

  • libs/admin-pages/src/pages/media-applications/[id]/reject.ts
🔇 Additional comments (2)
libs/admin-pages/src/pages/media-applications/[id]/reject.ts (2)

156-157: LGTM!

The exports follow the required pattern for page controllers with proper role-based access control using INTERNAL_ADMIN_CTSC.


38-41: Add input validation for the id parameter before querying the database.

The id parameter from req.params is used directly without validation. According to coding guidelines, input validation must be performed on all endpoints. Add validation to check that id is not empty or null before passing it to getApplicationById(). This applies to both GET and POST handlers (lines 38 and 80).

@junaidiqbalmoj
junaidiqbalmoj merged commit a5d58d0 into master Dec 23, 2025
10 checks passed
@junaidiqbalmoj
junaidiqbalmoj deleted the vibe-229-reject-media-application branch December 24, 2025 14:49
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.

3 participants