Skip to content

Conversation

@junaidiqbalmoj
Copy link
Contributor

@junaidiqbalmoj junaidiqbalmoj commented Dec 1, 2025

Jira link

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

Change description

Add gov notifier notification email for both api and manual upload functionality

Summary by CodeRabbit

  • New Features

    • Added email notification system for hearing list publications that automatically sends emails to subscribed users when hearing lists are published for their subscribed locations.
    • Notifications include audit logging to track delivery status, sent timestamps, and any errors.
    • Manual file uploads now trigger notifications to eligible subscribers.
  • Documentation

    • Added comprehensive technical specifications and implementation guides for the notification system.
    • Added end-to-end test documentation and GitHub secrets setup guide for running notification tests.
  • Tests

    • Added end-to-end tests for notification delivery and manual upload workflows.

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

github-actions bot and others added 10 commits November 21, 2025 16:09
Created specification and implementation plan for event-driven GOV.UK Notify
email notification system with deduplication, retry logic, and audit logging.

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

Co-Authored-By: Claude <[email protected]>
- Created specification document with requirements and acceptance criteria
- Created tasks document with implementation checklist
- Includes database schema, Gov.Notify integration, notification service, and testing tasks

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

Co-Authored-By: Claude <[email protected]>
- Specification document with data models and Gov.Notify integration
- Technical implementation plan with 10-phase approach
- Detailed task breakdown with 70+ actionable tasks
- Estimated effort: 4-6 days (27-43 hours)

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

Co-Authored-By: Claude <[email protected]>
- specification.md: Comprehensive system architecture and requirements
- plan.md: 7-phase implementation plan (13 hours)
- tasks.md: 30+ granular tasks with acceptance criteria
- Downloaded email notification mockup images

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

Co-Authored-By: Claude <[email protected]>
Created comprehensive technical implementation plan (plan.md) for subscription fulfilment:
- Event-driven trigger system for publication notifications
- Integration with VIBE-192 subscription infrastructure
- Gov.Notify client integration with bilingual templates
- Deduplication logic and error handling
- Non-blocking batch processing architecture

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

Co-Authored-By: Claude <[email protected]>
The remote branch uses 'notifications' (plural) with better structure.
Removed the local 'notification' directory to avoid confusion.
@coderabbitai
Copy link

coderabbitai bot commented Dec 1, 2025

Walkthrough

A notification system for hearing list publications is implemented via a new @hmcts/notifications library. It includes Gov.Notify email integration, Prisma-backed audit logging, subscription-based recipient queries, deduplication logic, and comprehensive E2E test coverage. Database schema changes track notifications and add subscription indexing. Multiple dependencies are updated to express 5.2.0, and Azure API credentials are renamed for consistency.

Changes

Cohort / File(s) Summary
Notifications Library (Core)
libs/notifications/package.json, libs/notifications/tsconfig.json, libs/notifications/src/index.ts, libs/notifications/src/config.ts, libs/notifications/src/config.test.ts
New @hmcts/notifications package with workspace dependencies, TypeScript config, Prisma schema path export, and test coverage for schema resolution.
Notifications Data & Queries
libs/notifications/prisma/schema.prisma, libs/notifications/src/notification/subscription-queries.ts, libs/notifications/src/notification/subscription-queries.test.ts, libs/notifications/src/notification/notification-queries.ts, libs/notifications/src/notification/notification-queries.test.ts
Prisma models for NotificationAuditLog, query functions for subscriptions and notifications with tests covering retrieval and data transformation.
Notifications Gov.Notify Integration
libs/notifications/src/govnotify/govnotify-client.ts, libs/notifications/src/govnotify/govnotify-client.test.ts, libs/notifications/src/govnotify/template-config.ts
Gov.Notify email client with retry/backoff logic, template parameter builders, environment accessors, and comprehensive test coverage for send success/failure scenarios.
Notifications Validation & Service
libs/notifications/src/notification/validation.ts, libs/notifications/src/notification/validation.test.ts, libs/notifications/src/notification/notification-service.ts, libs/notifications/src/notification/notification-service.test.ts
Email and event validation, core notification orchestration service with deduplication, subscription fetching, Gov.Notify dispatch, and audit log updates; extensive test coverage.
Notifications Type Definitions
libs/notifications/types/email/index.d.ts
TypeScript ambient module declarations for notifications-node-client interfaces (NotifyClient, EmailOptions, NotificationResponse).
Database Migrations & Subscriptions Schema
apps/postgres/prisma/migrations/20251201095418_add_notification_audit_log/migration.sql, apps/postgres/prisma/migrations/20251201155125_add_gov_notify_id/migration.sql, apps/postgres/prisma/migrations/20251202095746_remove_notification_unique_constraint/migration.sql, libs/subscriptions/prisma/schema.prisma
SQL migrations for notification audit log creation and index management, Prisma schema updates adding subscription index and notification audit log relations.
Integration: Blob Ingestion & Admin Pages
libs/api/src/blob-ingestion/repository/service.ts, libs/api/src/blob-ingestion/repository/service.test.ts, libs/admin-pages/src/pages/manual-upload-summary/index.ts, libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
Fire-and-forget notification trigger after artefact ingestion with location enrichment, manual upload notification dispatch, error handling, and test mocking.
Account & Subscription Queries
libs/account/src/repository/query.ts, libs/account/src/repository/query.test.ts, libs/subscriptions/src/repository/queries.ts, libs/subscriptions/src/repository/queries.test.ts
New findUserById and findSubscriptionsByLocationId query functions with test coverage.
Schema Discovery
apps/postgres/src/schema-discovery.ts, apps/postgres/src/schema-discovery.test.ts
Addition of notificationsSchemas to getPrismaSchemas() return array with updated test expectations.
E2E Testing Infrastructure
e2e-tests/utils/api-auth-helpers.ts, e2e-tests/utils/notification-helpers.ts, e2e-tests/tests/api/blob-ingestion-notifications.spec.ts, e2e-tests/tests/manual-upload.spec.ts
Azure token fetching for API auth, notification query/creation/cleanup utilities, test specs for blob ingestion and manual upload notification flows.
E2E Configuration & Documentation
e2e-tests/README-NOTIFICATIONS.md, e2e-tests/run-with-credentials.js, e2e-tests/package.json, e2e-tests/playwright.config.ts
E2E test documentation, environment loading from .env, dotenv dependency, playwright config updates for Gov.Notify variables.
OAuth & API Middleware
libs/api/src/middleware/oauth-middleware.ts, libs/api/src/middleware/oauth-middleware.test.ts
Rename AZURE_CLIENT_ID to AZURE_API_CLIENT_ID for consistency with Azure credential management.
Express Dependency Updates
apps/api/package.json, apps/web/package.json, libs/admin-pages/package.json, libs/api/package.json, libs/auth/package.json, libs/cloud-native-platform/package.json, libs/list-types/civil-and-family-daily-cause-list/package.json, libs/public-pages/package.json, libs/simple-router/package.json, libs/subscriptions/package.json, libs/system-admin-pages/package.json, libs/verified-pages/package.json, libs/web-core/package.json
Express dependency/peer dependency version bumps from 5.1.0 to 5.2.0 across multiple packages.
TypeScript Path Mappings
tsconfig.json
Added @hmcts/notifications path alias and typeRoots entry for notifications types.
GitHub & Documentation
docs/tickets/VIBE-221/plan.md, docs/tickets/VIBE-221/specification.md, docs/tickets/VIBE-221/tasks.md, docs/GITHUB_SECRETS_SETUP.md, .github/workflows/e2e.yml
Comprehensive technical plan, specification, and task tracking for VIBE-221; GitHub secrets setup documentation; E2E workflow environment variable updates.

Sequence Diagram

sequenceDiagram
    participant Admin as Admin/Upload
    participant Ingestion as Blob Ingestion Service
    participant NotifSvc as Notification Service
    participant Subscriptions as Subscription Queries
    participant GovNotify as Gov.Notify Client
    participant AuditLog as Notification Audit Log
    participant DB as Database

    Admin->>Ingestion: Upload hearing list artefact
    Ingestion->>DB: Save artefact
    DB-->>Ingestion: artefact_id
    
    Ingestion->>NotifSvc: sendPublicationNotifications(publicationId, locationId, ...)
    activate NotifSvc
    
    NotifSvc->>Subscriptions: findActiveSubscriptionsByLocation(locationId)
    Subscriptions->>DB: Query subscriptions
    DB-->>Subscriptions: [subscriptions with users]
    Subscriptions-->>NotifSvc: subscriptions[]
    
    par Process Each Subscription
        rect rgb(200, 220, 255)
            Note over NotifSvc,AuditLog: Per-user notification flow
            NotifSvc->>AuditLog: createNotificationAuditLog(subscriptionId, userId, publicationId)
            AuditLog->>DB: Insert notification_audit_log (Pending)
            DB-->>AuditLog: notificationId
            AuditLog-->>NotifSvc: notification record
            
            alt Duplicate Check (user, publication already sent)
                NotifSvc-->>NotifSvc: Mark as Skipped
            else New User/Publication
                NotifSvc->>GovNotify: sendEmail(templateId, email, templateParams)
                alt Send Success
                    GovNotify-->>NotifSvc: { success, notificationId: govNotifyId }
                    NotifSvc->>AuditLog: updateNotificationStatus(notificationId, Sent, sentAt, govNotifyId)
                    AuditLog->>DB: Update status=Sent, gov_notify_id
                else Send Failure
                    GovNotify-->>NotifSvc: { success: false, error }
                    NotifSvc->>AuditLog: updateNotificationStatus(notificationId, Failed, errorMessage)
                    AuditLog->>DB: Update status=Failed, error_message
                end
            end
        end
    end
    
    deactivate NotifSvc
    NotifSvc-->>Ingestion: { totalSubscriptions, sent, failed, skipped, errors }
    Ingestion-->>Admin: success (notifications sent async)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • Gov.Notify integration & retry logic (libs/notifications/src/govnotify/govnotify-client.ts): Verify exponential backoff implementation, error handling for non-Error exceptions, and template parameter mapping correctness.
  • Notification service deduplication (libs/notifications/src/notification/notification-service.ts): Confirm the deduplication check against existing audit logs works correctly in concurrent scenarios and handles edge cases (missing emails, invalid addresses).
  • Database migrations & schema consistency (apps/postgres/prisma/migrations/*, libs/subscriptions/prisma/schema.prisma, libs/notifications/prisma/schema.prisma): Verify foreign key constraints, indexes, cascade behavior, and that the removal of unique constraint (20251202095746) doesn't introduce duplicate audit log issues.
  • E2E test utilities & polling logic (e2e-tests/utils/notification-helpers.ts): Check retry/polling delays, Gov.Notify client usage, and cleanup ordering (notifications before subscriptions) to avoid FK violations.
  • Azure credential migration (libs/api/src/middleware/oauth-middleware.ts): Ensure all references to AZURE_CLIENT_ID are properly updated and backward compatibility is maintained where needed.

Possibly related PRs

Suggested reviewers

  • KianKwa
  • ChrisS1512
  • linusnorton

Poem

🐰 Hear the whispers of the warren,
Gov.Notify hops through the day,
Subscriptions bloom, deduped with care,
Audit logs keep watch along the way,
Email wings carry justice's prayer! ✨📬

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.26% 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 accurately summarizes the main change: adding a subscription fulfilment email notification system using GOV.UK Notify, covering both API and manual upload paths as described in the PR objectives.
✨ 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 feature/VIBE-221-subscription-fulfilment-email

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.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 1, 2025

🎭 Playwright E2E Test Results

151 tests   151 ✅  10m 53s ⏱️
 23 suites    0 💤
  1 files      0 ❌

Results for commit ce6dbca.

♻️ This comment has been updated with latest results.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (24)
libs/notifications/tsconfig.json (1)

1-11: Consider adding typeRoots for consistency.

While the root tsconfig.json includes ./libs/notifications/types in the global typeRoots, the sibling module libs/notification/tsconfig.json declares typeRoots locally. For consistency and explicit type resolution, consider adding:

 {
   "extends": "../../tsconfig.json",
   "compilerOptions": {
     "outDir": "./dist",
     "rootDir": "./src",
     "declaration": true,
-    "declarationMap": true
+    "declarationMap": true,
+    "typeRoots": ["../../node_modules/@types", "./types"]
   },
   "include": ["src/**/*", "types/**/*"],
   "exclude": ["**/*.test.ts", "**/*.spec.ts", "dist", "node_modules"]
 }
apps/postgres/prisma/migrations/20251201115015_govnotifier_notification/migration.sql (1)

7-32: LGTM with minor observation.

The notification_log table structure is well-designed with appropriate indexes for common query patterns. The indexes on user_id, publication_id, subscription_id, and created_at will support efficient lookups.

Minor observation: Consider whether location_id should also have an index if queries will filter or join by location.

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

64-116: Markdown lint nits: bare URL + minor wording

The content is clear; only minor markdown/grammar nits if you care about passing markdownlint/LanguageTool cleanly:

  • Line 106: wrap the bare URL in <...> or markdown link to satisfy MD034.
  • Line 37: “10 second timeout” → “10-second timeout” if you want to follow the hyphenation suggestion.
  • Line 105: “populated correctly … correctly” – consider dropping one “correctly” to avoid repetition.

All optional and non-blocking.

libs/admin-pages/src/pages/manual-upload-summary/index.ts (1)

111-137: Notification trigger is awaited, and logging may expose too much detail

A few points around this notification block:

  1. Not truly fire-and-forget

    • Because sendPublicationNotifications is awaited, the POST response is delayed until the whole notification run completes. That’s at odds with the “fire-and-forget” / non‑blocking requirement described in the VIBE-221 docs and could hurt UX if there are many subscribers or Notify is slow.
    • Consider kicking this off without awaiting the result, e.g.:
      • void sendPublicationNotifications(...).then(/* log summary */).catch(/* log error */);
      • This keeps the upload flow latency bound to artefact creation and file save, not email sending.
  2. Log contents and PII

    • console.log of the full notificationResult object and console.error with the raw notificationError risk leaking PII (email addresses, full Notify payloads, etc.) depending on how the notification module structures those objects.
    • Safer pattern is to log only high‑level, non‑PII fields (artefactId, locationId, counts, maybe a correlation/reference ID) and, for errors, name/message plus a sanitized error code instead of the full error object.
    • Suggest constraining logs here to a small, explicit shape and relying on the notification module itself for any deeper debug logging.
  3. listTypeName field

    • Here you use listType?.name || \LIST_TYPE_${listTypeId}`whereas the rest of the file usesenglishFriendlyName/welshFriendlyName. Please double‑check that mockListTypesactually exposes aname` field and that it’s the intended value for the email template; otherwise you may want to reuse the same friendly name logic for consistency.

These are mostly design/privacy points rather than correctness, but they’re worth tightening before release.

docs/VIBE-221/tasks.md (1)

771-815: Markdown headings vs emphasis (MD036)

In the “Progress Tracking” section the phase labels are bolded (**Phase 1: Database Schema**, etc.). markdownlint flags this as “emphasis used instead of a heading” (MD036). If you want a clean lint run, consider turning these into proper headings, e.g.:

### Phase 1: Database Schema

and similarly for the other phases. Content is otherwise fine.

libs/notifications/src/notification/notification-queries.test.ts (1)

1-97: Tests correctly assert Prisma interaction; consider one extra assertion

The mocks and expectations line up with the implementation:

  • createNotificationAuditLog returns the mocked record and defaults status to "Pending".
  • updateNotificationStatus and findExistingNotification assertions match the Prisma calls, including the composite userId_publicationId key and errorMessage: undefined.

If you want slightly stronger coverage, you could also assert on the arguments passed to prisma.notificationAuditLog.create (status defaulting) in the first test, but as is this is already adequate.

VIBE-221-plan.md (1)

238-262: Add language to fenced block for markdownlint (MD040)

markdownlint flags the email‑template block for lacking a language (MD040). Since this is prose, using text (or md) is enough to satisfy it:

```text
Subject: New hearing list published: ((hearing_list_name))
...

Purely a documentation nit; behavior is unaffected.

</blockquote></details>
<details>
<summary>libs/notification/src/notifications-node-client.d.ts (1)</summary><blockquote>

`1-26`: **Duplicate NotifyClient typings and default export shape**  

Two small points on this declaration:

1. **Duplication**  
   - There are already NotifyClient typings elsewhere (`types/notifications-node-client.d.ts` / `libs/notification/types/notifications-node-client.d.ts`). To avoid these drifting out of sync, consider having a single canonical definition and re‑using it (e.g. via `declare module "notifications-node-client"` that re‑exports that type), rather than maintaining multiple copies.

2. **Default export shape**  
   - `export default { NotifyClient };` declares the default as an object with a `NotifyClient` property. If the real package exports a class as its default (or only uses a named export), this may not accurately match the runtime shape, and could mislead consumers using `import NotifyClient from "notifications-node-client";`.  
   - If the project only ever uses `import { NotifyClient } ...`, you can probably drop the default export from this declaration to keep types closer to reality.

Neither is breaking right now, but consolidating the typings and aligning the default export with actual usage will reduce confusion later.

</blockquote></details>
<details>
<summary>apps/postgres/prisma/migrations/20251201095418_add_notification_audit_log/migration.sql (1)</summary><blockquote>

`8-34`: **Notification audit table design looks consistent; consider status tightening**

The `notification_audit_log` shape (UUID PK, timestamps, unique `(user_id, publication_id)` index, FK to `subscription`) looks aligned with a per-user-per-publication audit trail. If you want to prevent typos or drift in `status`, consider normalising it (enum / CHECK constraint), otherwise this is fine as-is.

</blockquote></details>
<details>
<summary>libs/notification/prisma/schema.prisma (1)</summary><blockquote>

`1-18`: **NotificationLog schema matches conventions; indexes look sensible**

Model naming and `@map` / `@@map` usage align with the Prisma/DB naming guidelines, and the chosen indexes support common access patterns by user, subscription, publication, and time. If you anticipate frequent queries by status (e.g. failed/pending retries), adding an index on `status` later may help, but not required now.

</blockquote></details>
<details>
<summary>docs/VIBE-221/plan.md (1)</summary><blockquote>

`37-47`: **Add a language to the project-structure code fence**

The file tree block is fenced with ``` but no language, which markdownlint (MD040) flags. Using something like:

```markdown
```text
libs/notification/
├── package.json
…

will keep the rendering the same while satisfying the linter.

</blockquote></details>
<details>
<summary>docs/VIBE-221/specification.md (1)</summary><blockquote>

`41-48`: **Tidy fenced blocks and bare URL to satisfy markdownlint**

A few small tweaks will make this spec pass markdownlint:

- Architecture diagram and GOV.UK Notify template snippet: add an explicit language, e.g.

  ```markdown
  ```text
  Publication Event → …

and

```markdown
```text
Your subscription has been triggered…

- GOV.UK Notify docs link: wrap as a Markdown link instead of a bare URL, e.g.

```markdown
- Documentation: [GOV.UK Notify Node client](https://docs.notifications.service.gov.uk/node.html)

These keep the content unchanged but clear MD040/MD034.

Also applies to: 509-509, 648-657

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

1-193: Good coverage of happy-path and edge cases; consider adding failure-path assertions

The suite exercises the main flows (multi-subscriber send, duplicate suppression, invalid email skip, no-subscribers, invalid event) with a clean mocking pattern. To harden it further, you could:

  • Add a test where sendEmail rejects or returns a failure, asserting failed and errors are populated and updateNotificationStatus is called appropriately.
  • Optionally assert that findActiveSubscriptionsByLocation / createNotificationAuditLog are invoked with the expected arguments.

Not mandatory, but would make regressions in error handling more visible.

libs/notifications/src/notification/validation.ts (1)

1-50: Validation logic is sound; optional check for invalid Date values

The email validator and validatePublicationEvent cover the basic required fields and are consistent with how the service uses them. If you expect publicationDate to sometimes be an invalid Date object (e.g. new Date("bad")), you could strengthen that check with:

if (!event.publicationDate || Number.isNaN(event.publicationDate.getTime())) {
  errors.push("Publication date is required");
}

Otherwise this is fine as-is.

libs/notifications/src/notification/subscription-queries.ts (1)

1-31: Subscription query is correct; consider future filters for email status

The Prisma query and returned SubscriptionWithUser shape look correct and comply with the parameterisation guidelines. As the user model matures, you may want to extend the where clause to filter on flags like emailVerified / isActive (as outlined in the spec’s optimised SQL example) so invalid or inactive accounts are excluded at source.

VIBE-221-specification.md (1)

1-342: LGTM! Comprehensive specification document.

The specification is well-structured and provides clear technical requirements, event flow, error handling scenarios, and test cases. The document will serve as a valuable reference for implementation and testing.

Optional: Add language specifiers to fenced code blocks.

For improved markdown rendering and syntax highlighting, consider adding language specifiers to fenced code blocks (e.g., ```sql, ```typescript, ```text). The bare URL on line 70 could also be wrapped in angle brackets or a markdown link for consistency.

Apply static analysis suggestions if desired:

  • Lines 49, 60, 66, 107, 157, 263: Add language specifiers
  • Line 70: Wrap URL in markdown link format
libs/notification/src/notification-service.ts (1)

131-154: Consider extracting template parameter building logic.

The inline date formatting and list type lookup logic makes the function harder to read. Consider extracting this into a dedicated helper function like buildTemplateParameters.

Extract template parameter construction into a separate helper function to improve readability and testability.

function buildTemplateParameters(user: User, hearingListName: string, publicationDate: string, location: Location, baseUrl: string) {
  const listType = mockListTypes.find((lt) => lt.name === hearingListName);
  const listTypeFriendlyName = listType?.englishFriendlyName || hearingListName;

  const date = new Date(publicationDate);
  const day = String(date.getDate()).padStart(2, "0");
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const year = date.getFullYear();
  const formattedDate = `${day}/${month}/${year}`;

  return {
    ListType: listTypeFriendlyName,
    content_date: formattedDate,
    locations: location.name,
    start_page_link: baseUrl,
    subscription_page_link: `${baseUrl}/account-home`
  };
}

Then use it in the main function:

         const baseUrl = process.env.BASE_URL || "https://localhost:8080";
-
-        // Convert list type name to friendly name
-        const listType = mockListTypes.find((lt) => lt.name === hearingListName);
-        const listTypeFriendlyName = listType?.englishFriendlyName || hearingListName;
-
-        // Format date to dd/mm/yyyy
-        const date = new Date(publicationDate);
-        const day = String(date.getDate()).padStart(2, "0");
-        const month = String(date.getMonth() + 1).padStart(2, "0");
-        const year = date.getFullYear();
-        const formattedDate = `${day}/${month}/${year}`;
-
+        const personalisation = buildTemplateParameters(user, hearingListName, publicationDate, location, baseUrl);
         const client = await getNotifyClient();
         const response = await client.sendEmail(GOVUK_NOTIFY_TEMPLATE_ID, user.email, {
-          personalisation: {
-            ListType: listTypeFriendlyName,
-            content_date: formattedDate,
-            locations: location.name,
-            start_page_link: baseUrl,
-            subscription_page_link: `${baseUrl}/account-home`
-          },
+          personalisation,
           reference: `${publicationId}-${subscription.subscriptionId}`
         });
docs/tickets/VIBE-221/plan.md (1)

1-544: LGTM! Comprehensive technical plan.

The plan provides:

  • Clear architecture overview with module structure
  • Detailed database schema with proper snake_case naming
  • Well-defined component responsibilities
  • Comprehensive error handling scenarios
  • Thorough testing strategy
  • Security and compliance considerations
  • Performance optimization guidelines
  • Open questions that need clarification

The plan aligns well with the specification and provides solid guidance for implementation.

Optional: Address markdown linting issues.

Similar to the specification document, consider adding language specifiers to fenced code blocks and wrapping bare URLs in markdown links for better rendering.

libs/notifications/prisma/schema.prisma (1)

11-27: Consider using enum for status field.

Line 16 uses String for the status field, but the specification defines specific status values ("Pending", "Sent", "Failed", "Skipped"). Using a Prisma enum would provide better type safety and prevent invalid status values.

Define a Prisma enum for notification status to improve type safety:

+enum NotificationStatus {
+  PENDING
+  SENT
+  FAILED
+  SKIPPED
+}
+
 model NotificationAuditLog {
   notificationId String    @id @default(uuid()) @map("notification_id") @db.Uuid
   subscriptionId String    @map("subscription_id") @db.Uuid
   userId         String    @map("user_id") @db.Uuid
   publicationId  String    @map("publication_id") @db.Uuid
-  status         String    @default("Pending")
+  status         NotificationStatus @default(PENDING)
   errorMessage   String?   @map("error_message")
   createdAt      DateTime  @default(now()) @map("created_at")
   sentAt         DateTime? @map("sent_at")

This aligns with the VIBE-221 specification which defines an enum for NotificationStatus.

libs/notifications/src/govnotify/govnotify-client.ts (1)

23-43: Consider caching the NotifyClient instance.

A new NotifyClient is instantiated on every email send. While acceptable for low-volume usage, consider extracting client creation if this becomes a performance concern at scale.

libs/notifications/src/notification/notification-service.ts (1)

98-112: Redundant database operations when skipping notifications.

The code creates an audit log with status "Skipped" then immediately updates the same record to "Skipped" with an error message. This results in two unnecessary database round-trips.

Consider passing the error message during creation, or modifying createNotificationAuditLog to accept an optional error message:

     if (!subscription.user.email) {
-      const notification = await createNotificationAuditLog({
+      await createNotificationAuditLog({
         subscriptionId: subscription.subscriptionId,
         userId: subscription.userId,
         publicationId: event.publicationId,
-        status: "Skipped"
+        status: "Skipped",
+        errorMessage: "No email address"
       });
-
-      await updateNotificationStatus(notification.notificationId, "Skipped", undefined, "No email address");

       return {
         status: "skipped",
         error: `User ${subscription.userId}: No email address`
       };
     }

This would require updating CreateNotificationData interface to include an optional errorMessage field.

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

4-10: Export the interface for consumer type safety.

CreateNotificationLogParams is not exported, which means callers cannot reference this type when constructing parameters.

-interface CreateNotificationLogParams {
+export interface CreateNotificationLogParams {
   subscriptionId: string;
   userId: string;
   publicationId: string;
   locationId: string;
   status: string;
 }

30-49: Inconsistent status string casing between modules.

This file uses uppercase status values ("SENT", "FAILED") while notification-queries.ts uses title case ("Sent", "Failed", "Pending", "Skipped"). If these feed into the same reporting or query system, this inconsistency could cause issues.

Consider extracting status constants to a shared module:

// notification-status.ts
export const NotificationStatus = {
  PENDING: "Pending",
  SENT: "Sent",
  FAILED: "Failed",
  SKIPPED: "Skipped"
} as const;

51-63: Add explicit return types for public API functions.

Both findNotificationLogsByPublicationId and findNotificationLogsByUserId rely on inferred return types. Adding explicit types improves API clarity and catches accidental changes.

-export async function findNotificationLogsByPublicationId(publicationId: string) {
+export async function findNotificationLogsByPublicationId(publicationId: string): Promise<NotificationLog[]> {
   return prisma.notificationLog.findMany({
     where: { publicationId },
     orderBy: { createdAt: "desc" }
   });
 }

-export async function findNotificationLogsByUserId(userId: string) {
+export async function findNotificationLogsByUserId(userId: string): Promise<NotificationLog[]> {
   return prisma.notificationLog.findMany({
     where: { userId },
     orderBy: { createdAt: "desc" }
   });
 }

You'll need to define or import a NotificationLog interface matching the Prisma model.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc9c436 and 0f83f5e.

⛔ Files ignored due to path filters (3)
  • docs/VIBE-221/email notification mock up.png is excluded by !**/*.png
  • docs/VIBE-221/email notification template .png is excluded by !**/*.png
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (52)
  • .ai/plans/VIBE-221/plan.md (1 hunks)
  • .ai/plans/VIBE-221/specification.md (1 hunks)
  • .ai/plans/VIBE-221/tasks.md (1 hunks)
  • VIBE-221-plan.md (1 hunks)
  • VIBE-221-specification.md (1 hunks)
  • apps/postgres/prisma/migrations/20251201095418_add_notification_audit_log/migration.sql (1 hunks)
  • apps/postgres/prisma/migrations/20251201115015_govnotifier_notification/migration.sql (1 hunks)
  • apps/postgres/src/schema-discovery.test.ts (1 hunks)
  • apps/postgres/src/schema-discovery.ts (1 hunks)
  • docs/VIBE-221/plan.md (1 hunks)
  • docs/VIBE-221/specification.md (1 hunks)
  • docs/VIBE-221/tasks.md (1 hunks)
  • docs/tickets/VIBE-221/plan.md (1 hunks)
  • docs/tickets/VIBE-221/specification.md (1 hunks)
  • docs/tickets/VIBE-221/tasks.md (1 hunks)
  • libs/account/src/repository/query.test.ts (2 hunks)
  • libs/account/src/repository/query.ts (1 hunks)
  • libs/admin-pages/src/pages/manual-upload-summary/index.ts (2 hunks)
  • libs/api/src/blob-ingestion/repository/service.ts (3 hunks)
  • libs/notification/package.json (1 hunks)
  • libs/notification/prisma/schema.prisma (1 hunks)
  • libs/notification/src/config.ts (1 hunks)
  • libs/notification/src/index.ts (1 hunks)
  • libs/notification/src/notification-service.test.ts (1 hunks)
  • libs/notification/src/notification-service.ts (1 hunks)
  • libs/notification/src/notifications-node-client.d.ts (1 hunks)
  • libs/notification/src/repository/queries.test.ts (1 hunks)
  • libs/notification/src/repository/queries.ts (1 hunks)
  • libs/notification/tsconfig.json (1 hunks)
  • libs/notification/types/notifications-node-client.d.ts (1 hunks)
  • libs/notifications/package.json (1 hunks)
  • libs/notifications/prisma/schema.prisma (1 hunks)
  • libs/notifications/src/config.ts (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.test.ts (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.ts (1 hunks)
  • libs/notifications/src/govnotify/template-config.ts (1 hunks)
  • libs/notifications/src/index.ts (1 hunks)
  • libs/notifications/src/notification/notification-queries.test.ts (1 hunks)
  • libs/notifications/src/notification/notification-queries.ts (1 hunks)
  • libs/notifications/src/notification/notification-service.test.ts (1 hunks)
  • libs/notifications/src/notification/notification-service.ts (1 hunks)
  • libs/notifications/src/notification/subscription-queries.test.ts (1 hunks)
  • libs/notifications/src/notification/subscription-queries.ts (1 hunks)
  • libs/notifications/src/notification/validation.test.ts (1 hunks)
  • libs/notifications/src/notification/validation.ts (1 hunks)
  • libs/notifications/src/notifications-node-client.d.ts (1 hunks)
  • libs/notifications/tsconfig.json (1 hunks)
  • libs/notifications/types/notifications-node-client.d.ts (1 hunks)
  • libs/subscriptions/prisma/schema.prisma (1 hunks)
  • libs/subscriptions/src/repository/queries.ts (1 hunks)
  • tsconfig.json (2 hunks)
  • types/notifications-node-client.d.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • apps/postgres/src/schema-discovery.ts
  • libs/account/src/repository/query.ts
  • libs/notification/src/index.ts
  • libs/notifications/src/notification/validation.test.ts
  • libs/subscriptions/src/repository/queries.ts
  • libs/notifications/src/notification/notification-service.test.ts
  • libs/notification/src/config.ts
  • libs/notifications/src/config.ts
  • libs/account/src/repository/query.test.ts
  • libs/notifications/src/notifications-node-client.d.ts
  • libs/notifications/src/notification/validation.ts
  • libs/notifications/src/notification/notification-queries.ts
  • libs/notification/src/notification-service.test.ts
  • libs/notifications/src/govnotify/template-config.ts
  • libs/notification/src/repository/queries.test.ts
  • libs/notification/src/notification-service.ts
  • libs/notifications/src/govnotify/govnotify-client.ts
  • libs/notifications/src/notification/subscription-queries.test.ts
  • libs/notifications/types/notifications-node-client.d.ts
  • libs/notification/src/notifications-node-client.d.ts
  • libs/notifications/src/notification/notification-service.ts
  • libs/notifications/src/notification/notification-queries.test.ts
  • libs/notification/types/notifications-node-client.d.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • types/notifications-node-client.d.ts
  • libs/notifications/src/notification/subscription-queries.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.ts
  • libs/notification/src/repository/queries.ts
  • apps/postgres/src/schema-discovery.test.ts
  • libs/notifications/src/index.ts
  • libs/api/src/blob-ingestion/repository/service.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • apps/postgres/src/schema-discovery.ts
  • libs/account/src/repository/query.ts
  • libs/notification/src/index.ts
  • libs/notifications/src/notification/validation.test.ts
  • libs/subscriptions/src/repository/queries.ts
  • libs/notifications/src/notification/notification-service.test.ts
  • libs/notification/src/config.ts
  • libs/notifications/src/config.ts
  • libs/account/src/repository/query.test.ts
  • libs/notifications/src/notifications-node-client.d.ts
  • libs/notifications/src/notification/validation.ts
  • libs/notifications/src/notification/notification-queries.ts
  • libs/notification/src/notification-service.test.ts
  • libs/notifications/src/govnotify/template-config.ts
  • libs/notification/src/repository/queries.test.ts
  • libs/notification/src/notification-service.ts
  • libs/notifications/src/govnotify/govnotify-client.ts
  • libs/notifications/src/notification/subscription-queries.test.ts
  • libs/notifications/types/notifications-node-client.d.ts
  • libs/notification/src/notifications-node-client.d.ts
  • libs/notifications/src/notification/notification-service.ts
  • libs/notifications/src/notification/notification-queries.test.ts
  • libs/notification/types/notifications-node-client.d.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • types/notifications-node-client.d.ts
  • libs/notifications/src/notification/subscription-queries.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.ts
  • libs/notification/src/repository/queries.ts
  • apps/postgres/src/schema-discovery.test.ts
  • libs/notifications/src/index.ts
  • libs/api/src/blob-ingestion/repository/service.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/src/notification/validation.test.ts
  • libs/notifications/src/notification/notification-service.test.ts
  • libs/account/src/repository/query.test.ts
  • libs/notification/src/notification-service.test.ts
  • libs/notification/src/repository/queries.test.ts
  • libs/notifications/src/notification/subscription-queries.test.ts
  • libs/notifications/src/notification/notification-queries.test.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • apps/postgres/src/schema-discovery.test.ts
libs/*/src/config.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notification/src/config.ts
  • libs/notifications/src/config.ts
libs/*/tsconfig.json

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

TypeScript must use strict mode with no any without justification.

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notification/package.json
  • libs/notifications/package.json
**/prisma/schema.prisma

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/prisma/schema.prisma
  • libs/notification/prisma/schema.prisma
  • libs/subscriptions/prisma/schema.prisma
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

Applied to files:

  • apps/postgres/src/schema-discovery.ts
  • libs/notification/src/config.ts
  • libs/notifications/tsconfig.json
  • libs/notifications/src/config.ts
  • libs/notifications/src/govnotify/template-config.ts
  • libs/notification/tsconfig.json
  • tsconfig.json
📚 Learning: 2025-11-27T09:48:13.010Z
Learnt from: junaidiqbalmoj
Repo: hmcts/cath-service PR: 136
File: libs/api/src/blob-ingestion/validation.ts:156-163
Timestamp: 2025-11-27T09:48:13.010Z
Learning: In libs/api/src/blob-ingestion/validation.ts, the permissive date validation in isValidISODate and isValidISODateTime functions is expected behavior and should not be flagged for stricter validation.

Applied to files:

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

Applied to files:

  • libs/notifications/src/notification/validation.test.ts
  • libs/notifications/tsconfig.json
  • libs/account/src/repository/query.test.ts
  • libs/notification/tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/package.json : All test packages must use `"test": "vitest run"` script to run tests.

Applied to files:

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

Applied to files:

  • libs/notification/src/config.ts
  • tsconfig.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Always add `.js` extension to relative imports (e.g., `import { foo } from "./bar.js"`). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • libs/notification/package.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/package.json : Module package.json build script must include `build:nunjucks` if the module contains Nunjucks templates in the `pages/` directory.

Applied to files:

  • libs/notification/package.json
  • libs/notifications/package.json
🧬 Code graph analysis (15)
libs/notifications/src/notification/validation.test.ts (1)
libs/notifications/src/notification/validation.ts (2)
  • isValidEmail (3-8)
  • validatePublicationEvent (23-50)
libs/notifications/src/notification/notification-service.test.ts (4)
libs/notifications/src/notification/subscription-queries.ts (1)
  • findActiveSubscriptionsByLocation (14-31)
libs/notifications/src/notification/notification-queries.ts (2)
  • findExistingNotification (45-56)
  • createNotificationAuditLog (21-32)
libs/notifications/src/index.ts (1)
  • sendPublicationNotifications (1-1)
libs/notifications/src/notification/notification-service.ts (1)
  • sendPublicationNotifications (21-86)
libs/account/src/repository/query.test.ts (1)
libs/account/src/repository/query.ts (1)
  • findUserById (18-22)
libs/notifications/src/notifications-node-client.d.ts (2)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notification/src/repository/queries.test.ts (1)
libs/notification/src/repository/queries.ts (3)
  • createNotificationLog (12-28)
  • updateNotificationLogSent (30-38)
  • updateNotificationLogFailed (40-49)
libs/notifications/src/govnotify/govnotify-client.ts (2)
libs/notifications/src/govnotify/template-config.ts (3)
  • TemplateParameters (32-39)
  • getApiKey (12-17)
  • getTemplateId (5-10)
libs/notifications/src/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/src/notification/subscription-queries.test.ts (1)
libs/notifications/src/notification/subscription-queries.ts (1)
  • findActiveSubscriptionsByLocation (14-31)
libs/notifications/types/notifications-node-client.d.ts (2)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/src/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notification/src/notifications-node-client.d.ts (2)
libs/notification/types/notifications-node-client.d.ts (1)
  • NotifyClient (2-23)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/src/notification/notification-service.ts (5)
libs/notifications/src/notification/validation.ts (3)
  • PublicationEvent (10-16)
  • validatePublicationEvent (23-50)
  • isValidEmail (3-8)
libs/notifications/src/notification/subscription-queries.ts (1)
  • SubscriptionWithUser (3-12)
libs/notifications/src/notification/notification-queries.ts (3)
  • findExistingNotification (45-56)
  • createNotificationAuditLog (21-32)
  • updateNotificationStatus (34-43)
libs/notifications/src/govnotify/template-config.ts (1)
  • buildTemplateParameters (41-54)
libs/notifications/src/govnotify/govnotify-client.ts (1)
  • sendEmail (19-21)
libs/notifications/src/notification/notification-queries.test.ts (1)
libs/notifications/src/notification/notification-queries.ts (3)
  • createNotificationAuditLog (21-32)
  • updateNotificationStatus (34-43)
  • findExistingNotification (45-56)
libs/notifications/src/govnotify/govnotify-client.test.ts (1)
libs/notifications/src/govnotify/govnotify-client.ts (1)
  • sendEmail (19-21)
types/notifications-node-client.d.ts (3)
libs/notification/src/notifications-node-client.d.ts (1)
  • NotifyClient (2-23)
libs/notifications/src/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/admin-pages/src/pages/manual-upload-summary/index.ts (3)
libs/publication/src/index.ts (1)
  • mockListTypes (1-1)
libs/list-types/common/src/index.ts (1)
  • mockListTypes (2-2)
libs/notification/src/notification-service.ts (1)
  • sendPublicationNotifications (38-236)
apps/postgres/src/schema-discovery.test.ts (1)
apps/postgres/src/schema-discovery.ts (1)
  • getPrismaSchemas (6-8)
🪛 LanguageTool
docs/VIBE-221/tasks.md

[grammar] ~158-~158: Ensure spelling is correct
Context: ...NotificationEmail()function 4. DefineEmailPersonalisationinterface 5. DefineSendEmailResult` i...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

docs/tickets/VIBE-221/tasks.md

[grammar] ~37-~37: Use a hyphen to join words.
Context: ...] Add timeout handling for API calls (10 second timeout) - [x] Create template co...

(QB_NEW_EN_HYPHEN)


[style] ~105-~105: This adverb was used twice in the sentence. Consider removing one of them or replacing them with a synonym.
Context: ...erify all template parameters populated correctly (user_name, hearing_list_name, publicat...

(ADVERB_REPETITION_PREMIUM)

docs/tickets/VIBE-221/plan.md

[style] ~484-~484: It’s more common nowadays to write this noun as one word.
Context: ... table with email field?) - Where is user name stored for template personalization? ...

(RECOMMENDED_COMPOUNDS)


[uncategorized] ~484-~484: Do not mix variants of the same word (‘personalization’ and ‘personalisation’) within a single text.
Context: ... Where is user name stored for template personalization? - Do we need to fetch user data fro...

(EN_WORD_COHERENCY)

VIBE-221-plan.md

[grammar] ~132-~132: Use a hyphen to join words.
Context: ...Retry Policy Decision: Retry once, 5 second delay Rationale: - Most trans...

(QB_NEW_EN_HYPHEN)


[uncategorized] ~151-~151: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...for parallelism - GOV.UK Notify handles rate limiting - Better visibility into individual fai...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

docs/VIBE-221/specification.md

[uncategorized] ~354-~354: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...Errors**: - Count by error type - Rate limiting occurrences - Invalid email addresse...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[uncategorized] ~396-~396: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ... Client**: - Mock API responses - Rate limiting handling - Error scenarios 3. **Aud...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[uncategorized] ~525-~525: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ... Mitigations ### Risk 1: GOV.UK Notify Rate Limiting Impact: High volume of notification...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

docs/tickets/VIBE-221/specification.md

[style] ~7-~7: Try moving the adverb to make the sentence clearer.
Context: ...rigger-based mechanism in the CaTH back end to automatically send email notifications to subscribed users through Gov.Notify ...

(SPLIT_INFINITIVE)

.ai/plans/VIBE-221/plan.md

[style] ~475-~475: Consider using a different verb for a more formal wording.
Context: ...on flow continues normally 4. Debug and fix issues 5. Redeploy with fix ## Perform...

(FIX_RESOLVE)


[uncategorized] ~525-~525: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...ses - Validate template parameters ### Rate Limiting - Respect Gov.Notify rate limits - Moni...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

🪛 markdownlint-cli2 (0.18.1)
docs/VIBE-221/tasks.md

771-771: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


778-778: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


784-784: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


790-790: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


796-796: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


802-802: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


807-807: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


812-812: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

docs/VIBE-221/plan.md

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

(MD040, fenced-code-language)

docs/tickets/VIBE-221/tasks.md

106-106: Bare URL used

(MD034, no-bare-urls)

docs/tickets/VIBE-221/plan.md

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

(MD040, fenced-code-language)


156-156: Bare URL used

(MD034, no-bare-urls)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


191-191: Bare URL used

(MD034, no-bare-urls)


541-541: Bare URL used

(MD034, no-bare-urls)

VIBE-221-plan.md

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

(MD040, fenced-code-language)

docs/VIBE-221/specification.md

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

(MD040, fenced-code-language)


202-202: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


509-509: Bare URL used

(MD034, no-bare-urls)


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

(MD040, fenced-code-language)

VIBE-221-specification.md

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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


70-70: Bare URL used

(MD034, no-bare-urls)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)

.ai/plans/VIBE-221/specification.md

38-38: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


60-60: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)

- Set environment variables before module imports to ensure they're available
- Fixed vi.fn() mock to use function declaration instead of arrow function
- All 17 notifications tests now passing
- Pass Date object instead of ISO string for publicationDate
- Include locationName parameter (fetch from database)
- Use friendlyName instead of internal name for hearingListName
- Properly handle missing location case
- Fixed indentation and closing braces
- Added IF EXISTS to DROP CONSTRAINT statements
- Prevents migration failures when constraint doesn't exist
- Fixes GitHub Actions build error
- Removed 20251201115015_govnotifier_notification migration
- This migration created 'notification_log' table (incorrect)
- Keeping 20251201095418 which creates 'notification_audit_log' (correct)
- Prevents table name mismatch with schema definition
- Added IF NOT EXISTS to CREATE TABLE statement
- Added IF NOT EXISTS to all CREATE INDEX statements
- Wrapped ADD CONSTRAINT in DO blocks with existence checks
- Wrapped DROP DEFAULT in conditional check for existing default
- Migration can now be safely re-run without errors
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
libs/notifications/src/govnotify/govnotify-client.test.ts (1)

29-54: Expand test coverage to include error scenarios and retry logic.

The test suite currently only covers the happy path. Consider adding test cases for:

  • Email sending failures and error handling
  • Retry logic with backoff (based on the retryWithBackoff implementation)
  • Missing or invalid environment variables
  • Invalid email addresses or missing template parameters

This will help prevent regressions and ensure robust error handling.

libs/admin-pages/src/pages/manual-upload-summary/index.ts (1)

111-122: Remove redundant comments that describe "what" rather than "why".

The comments on lines 111, 113, and 120 describe what the code does rather than why. Per the coding guidelines, comments should explain why something is done when the code itself is not self-explanatory. These comments can be safely removed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f83f5e and 54f3093.

📒 Files selected for processing (3)
  • apps/postgres/prisma/migrations/20251201095418_add_notification_audit_log/migration.sql (1 hunks)
  • libs/admin-pages/src/pages/manual-upload-summary/index.ts (2 hunks)
  • libs/notifications/src/govnotify/govnotify-client.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/postgres/prisma/migrations/20251201095418_add_notification_audit_log/migration.sql
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • libs/admin-pages/src/pages/manual-upload-summary/index.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/admin-pages/src/pages/manual-upload-summary/index.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/admin-pages/src/pages/manual-upload-summary/index.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/src/govnotify/govnotify-client.test.ts
🧬 Code graph analysis (2)
libs/admin-pages/src/pages/manual-upload-summary/index.ts (3)
libs/location/src/repository/queries.ts (1)
  • getLocationById (37-65)
libs/publication/src/index.ts (1)
  • mockListTypes (1-1)
libs/list-types/common/src/index.ts (1)
  • mockListTypes (2-2)
libs/notifications/src/govnotify/govnotify-client.test.ts (4)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/types/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/src/notifications-node-client.d.ts (1)
  • NotifyClient (2-24)
libs/notifications/src/govnotify/govnotify-client.ts (1)
  • sendEmail (19-21)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (12)
libs/api/src/blob-ingestion/repository/service.test.ts (1)

301-517: Refactor tests to avoid setTimeout pattern for async operations.

All notification-related tests use await new Promise((resolve) => setTimeout(resolve, 50)) to wait for fire-and-forget async operations. This pattern is problematic because:

  • It's non-deterministic: the async operation might not complete within 50ms on slower environments
  • It adds artificial delays that slow down test execution
  • It can cause flaky test failures

Consider refactoring the production code to expose the notification promise for testing, either through:

  • A test-mode flag that makes the operation awaitable
  • Dependency injection that allows tests to intercept and await the async flow
  • Using Vitest's vi.waitFor() or vi.waitUntil() with proper async conditions

Example using vi.waitFor():

-    // Wait for async notification processing
-    await new Promise((resolve) => setTimeout(resolve, 50));
+    // Wait for the mock to be called
+    await vi.waitFor(() => {
+      expect(sendPublicationNotifications).toHaveBeenCalled();
+    });

Note: This still requires the production code to complete quickly, but provides better error messages and can have configurable timeouts.

e2e-tests/utils/notification-helpers.ts (1)

35-55: Consider extracting common Prisma type casting.

Multiple functions use (prisma as any) to access models not typed in the shared client. While this works, it reduces type safety. If this pattern is used elsewhere, consider creating typed wrappers or extending the shared Prisma client to include these models.

e2e-tests/tests/manual-upload.spec.ts (2)

5-12: Unused import: getNotificationsByPublicationId.

This helper is imported but never used in the tests. The tests only verify the success panel is visible, not that notification records were created. Consider adding assertions using getNotificationsByPublicationId to verify the notification audit trail, or remove the unused import.


839-839: Fixed delay may cause test flakiness.

The setTimeout(resolve, 2000) delays assume notifications complete within 2 seconds. Under load or with slower environments, this could cause intermittent failures. Consider polling for notification records with a timeout instead:

// Example: Poll for notification completion
const maxWait = 10000;
const pollInterval = 500;
let elapsed = 0;
while (elapsed < maxWait) {
  const notifications = await getNotificationsByPublicationId(publicationId);
  if (notifications.length > 0) break;
  await new Promise(r => setTimeout(r, pollInterval));
  elapsed += pollInterval;
}

Also applies to: 883-883

libs/notifications/src/govnotify/govnotify-client.test.ts (1)

146-182: Verify exponential backoff test assertion.

The test checks that delays[0] equals 1000ms (default delay), but it only verifies the first retry delay. For true exponential backoff verification, consider testing with more retry attempts to confirm the delay increases (e.g., 1000, 2000, 4000ms). Currently this only confirms a delay was applied, not that it's exponential.

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

300-332: Consider adding a test for partial audit log cleanup on exceptions.

When findExistingNotification throws, the test correctly expects failed: 1 in the results. However, if the exception occurs after createNotificationAuditLog succeeds (e.g., during email sending), the audit log may remain in "Pending" status. Consider adding a test case that verifies updateNotificationStatus is called with "Failed" when an exception occurs after audit log creation.

e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1)

78-78: Hardcoded delays are fragile for async notification processing.

Using fixed setTimeout delays (2000ms) to wait for notification processing is brittle. Under load or slower environments, tests may fail spuriously. Consider implementing a polling mechanism:

async function waitForNotifications(publicationId: string, expectedCount: number, timeoutMs = 10000) {
  const startTime = Date.now();
  while (Date.now() - startTime < timeoutMs) {
    const notifications = await getNotificationsByPublicationId(publicationId);
    if (notifications.length >= expectedCount && 
        notifications.every(n => n.status !== "Pending")) {
      return notifications;
    }
    await new Promise(r => setTimeout(r, 500));
  }
  throw new Error(`Timed out waiting for ${expectedCount} notifications`);
}

This same issue applies to lines 105, 134, 158, 176, and 198.

libs/notifications/prisma/schema.prisma (1)

17-17: Consider using an enum for status field.

Using String for status allows any value. Define a Prisma enum to restrict to valid statuses and improve type safety:

enum NotificationStatus {
  Pending
  Sent
  Failed
  Skipped
}

model NotificationAuditLog {
  // ...
  status NotificationStatus @default(Pending)
  // ...
}
libs/notifications/src/notification/notification-queries.ts (4)

3-20: Avoid duplicating the Prisma model shape in local interfaces

NotificationAuditLog (and to a lesser extent CreateNotificationData) mirror the Prisma model fields. This will drift if the schema changes (new/renamed columns, nullability tweaks). If @hmcts/postgres exposes the generated Prisma types, consider aliasing those here and deriving CreateNotificationData from them (e.g. Pick<...>), so the compiler catches schema changes.


22-33: Make status a well-defined domain type and avoid magic strings

Right now status is an untyped string with a hard‑coded "Pending" default. It would be safer to introduce a small status domain (string literal union or enum, plus STATUS_PENDING constant) and use nullish coalescing for the default, e.g. status ?? STATUS_PENDING, so callers and future refactors can’t silently pass invalid values.


66-70: Clarify uniqueness / determinism for govNotifyId lookups

If govNotifyId is guaranteed unique at the DB level, prefer findUnique over findFirst to encode that invariant and fail fast if it is ever violated. If it is not unique by design, consider adding an explicit orderBy here so the chosen record is deterministic (e.g. most recent by createdAt), and document that behaviour in the caller.


72-75: Align publication lookups with deterministic ordering / reuse existing helper

getNotificationsByPublicationId returns results without an orderBy, while the e2e helper at e2e-tests/utils/notification-helpers.ts orders by createdAt: "asc". For consistency and to avoid tests or callers depending on implicit DB ordering, consider adding the same orderBy here and, if practical, having the e2e helper call this function instead of duplicating the query.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54f3093 and 96271e3.

📒 Files selected for processing (18)
  • apps/postgres/prisma/migrations/20251201155125_add_gov_notify_id/migration.sql (1 hunks)
  • e2e-tests/README-NOTIFICATIONS.md (1 hunks)
  • e2e-tests/run-with-credentials.js (1 hunks)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1 hunks)
  • e2e-tests/tests/manual-upload.spec.ts (2 hunks)
  • e2e-tests/utils/api-auth-helpers.ts (1 hunks)
  • e2e-tests/utils/notification-helpers.ts (1 hunks)
  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts (2 hunks)
  • libs/api/src/blob-ingestion/repository/service.test.ts (3 hunks)
  • libs/notifications/prisma/schema.prisma (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.test.ts (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.ts (1 hunks)
  • libs/notifications/src/notification/notification-queries.ts (1 hunks)
  • libs/notifications/src/notification/notification-service.test.ts (1 hunks)
  • libs/notifications/src/notification/notification-service.ts (1 hunks)
  • libs/notifications/src/notification/validation.ts (1 hunks)
  • libs/subscriptions/src/repository/queries.test.ts (2 hunks)
  • types/notifications-node-client.d.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • e2e-tests/README-NOTIFICATIONS.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/notifications/src/notification/validation.ts
  • libs/notifications/src/govnotify/govnotify-client.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • libs/subscriptions/src/repository/queries.test.ts
  • e2e-tests/tests/manual-upload.spec.ts
  • e2e-tests/utils/api-auth-helpers.ts
  • libs/notifications/src/notification/notification-queries.ts
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
  • libs/notifications/src/notification/notification-service.ts
  • libs/notifications/src/notification/notification-service.test.ts
  • e2e-tests/utils/notification-helpers.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • libs/api/src/blob-ingestion/repository/service.test.ts
  • types/notifications-node-client.d.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/subscriptions/src/repository/queries.test.ts
  • e2e-tests/tests/manual-upload.spec.ts
  • e2e-tests/utils/api-auth-helpers.ts
  • libs/notifications/src/notification/notification-queries.ts
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
  • libs/notifications/src/notification/notification-service.ts
  • libs/notifications/src/notification/notification-service.test.ts
  • e2e-tests/utils/notification-helpers.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
  • e2e-tests/run-with-credentials.js
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • libs/api/src/blob-ingestion/repository/service.test.ts
  • types/notifications-node-client.d.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/subscriptions/src/repository/queries.test.ts
  • e2e-tests/tests/manual-upload.spec.ts
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
  • libs/notifications/src/notification/notification-service.test.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • libs/api/src/blob-ingestion/repository/service.test.ts
**/prisma/schema.prisma

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/prisma/schema.prisma
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
🧠 Learnings (4)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/tsconfig.json : Module tsconfig.json must extend the root tsconfig.json with outDir, rootDir, and include/exclude declarations.

Applied to files:

  • types/notifications-node-client.d.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Don't ignore TypeScript errors - fix or justify with comments.

Applied to files:

  • types/notifications-node-client.d.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Don't create types.ts files - colocate types with the appropriate code.

Applied to files:

  • types/notifications-node-client.d.ts
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Don't duplicate dependencies - check root package.json first.

Applied to files:

  • types/notifications-node-client.d.ts
🧬 Code graph analysis (9)
libs/subscriptions/src/repository/queries.test.ts (1)
libs/subscriptions/src/repository/queries.ts (1)
  • findSubscriptionsByLocationId (14-23)
e2e-tests/tests/manual-upload.spec.ts (1)
e2e-tests/utils/notification-helpers.ts (4)
  • cleanupTestSubscriptions (84-92)
  • cleanupTestUsers (94-102)
  • createTestUser (19-33)
  • createTestSubscription (35-55)
libs/notifications/src/notification/notification-queries.ts (1)
e2e-tests/utils/notification-helpers.ts (1)
  • getNotificationsByPublicationId (57-62)
e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (3)
e2e-tests/utils/notification-helpers.ts (7)
  • cleanupTestNotifications (74-82)
  • cleanupTestSubscriptions (84-92)
  • cleanupTestUsers (94-102)
  • createTestUser (19-33)
  • createTestSubscription (35-55)
  • getNotificationsByPublicationId (57-62)
  • getGovNotifyEmail (64-72)
e2e-tests/utils/api-auth-helpers.ts (1)
  • getApiAuthToken (20-70)
libs/notifications/src/notification/notification-queries.ts (1)
  • getNotificationsByPublicationId (72-76)
libs/notifications/src/notification/notification-service.ts (5)
libs/notifications/src/notification/validation.ts (3)
  • PublicationEvent (13-19)
  • validatePublicationEvent (26-53)
  • isValidEmail (6-11)
libs/notifications/src/notification/subscription-queries.ts (2)
  • findActiveSubscriptionsByLocation (14-31)
  • SubscriptionWithUser (3-12)
libs/notifications/src/notification/notification-queries.ts (3)
  • findExistingNotification (53-64)
  • createNotificationAuditLog (22-33)
  • updateNotificationStatus (35-51)
libs/notifications/src/govnotify/template-config.ts (1)
  • buildTemplateParameters (41-54)
libs/notifications/src/govnotify/govnotify-client.ts (1)
  • sendEmail (18-29)
e2e-tests/utils/notification-helpers.ts (2)
libs/notifications/src/notification/notification-queries.ts (1)
  • getNotificationsByPublicationId (72-76)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-30)
libs/admin-pages/src/pages/manual-upload-summary/index.test.ts (7)
libs/admin-pages/src/manual-upload/storage.ts (1)
  • getManualUpload (46-68)
libs/admin-pages/src/manual-upload/file-storage.ts (1)
  • saveUploadedFile (12-23)
libs/publication/src/repository/queries.ts (1)
  • createArtefact (4-52)
libs/publication/src/index.ts (1)
  • createArtefact (6-6)
libs/admin-pages/src/pages/manual-upload-summary/index.ts (1)
  • POST (212-212)
libs/api/src/routes/v1/publication.ts (1)
  • POST (26-72)
libs/location/src/repository/queries.ts (1)
  • getLocationById (37-65)
libs/notifications/src/govnotify/govnotify-client.test.ts (2)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-30)
libs/notifications/src/govnotify/govnotify-client.ts (1)
  • sendEmail (18-29)
libs/api/src/blob-ingestion/repository/service.test.ts (3)
libs/api/src/blob-ingestion/validation.ts (1)
  • validateBlobRequest (9-158)
libs/api/src/blob-ingestion/repository/service.ts (1)
  • processBlobIngestion (17-128)
libs/api/src/blob-ingestion/repository/queries.ts (1)
  • createIngestionLog (4-16)
🔇 Additional comments (25)
libs/api/src/blob-ingestion/repository/service.test.ts (4)

6-40: LGTM!

The mock additions follow the existing pattern and provide appropriate test doubles for the new dependencies.


43-48: LGTM!

Import updates correctly reference the newly added mocks and mockListTypes.


220-299: LGTM!

These tests provide thorough coverage of edge cases including missing fields, system errors, and non-Error exceptions with appropriate fallback behaviors.


301-336: LGTM!

The notification test coverage is comprehensive, including:

  • Successful notification flow with proper mock data
  • Skipping notifications when location is not matched
  • Graceful error handling that doesn't fail ingestion
  • Edge cases for invalid/missing location and list type data
  • Detailed logging verification for both success and error paths

The test logic and assertions are appropriate, with proper setup and teardown of console spies.

Also applies to: 338-354, 356-390, 392-414, 416-438, 440-465, 467-517

libs/admin-pages/src/pages/manual-upload-summary/index.test.ts (4)

566-602: Good test coverage for notification flow.

The test properly verifies that sendPublicationNotifications is called with the expected payload after a successful upload. The assertions on publicationId, locationId, locationName, hearingListName, and publicationDate provide comprehensive validation.


604-635: Resilience testing for missing location is well-implemented.

The test correctly verifies that the upload succeeds even when getLocationById returns null, and that notifications are skipped with an appropriate warning log. The console spy cleanup with mockRestore() is properly handled.


637-675: Good fire-and-forget resilience test.

This test validates that notification failures don't block the upload flow, which aligns with the async notification architecture described in the AI summary. The error logging verification ensures observability is maintained.


740-856: Comprehensive isFlatFile logic coverage.

The tests cover JSON files (isFlatFile: false), non-JSON files (isFlatFile: true), and the edge case of missing fileName (defaults to true). This matches the expected behavior for flat file determination.

e2e-tests/run-with-credentials.js (1)

23-25: LGTM - Azure credentials added for API E2E tests.

The new secret mappings align with the api-auth-helpers.ts requirements and follow the existing naming conventions. Good that these credentials are not logged (unlike SSO/CFT emails), maintaining security best practices.

libs/subscriptions/src/repository/queries.test.ts (1)

78-120: Well-structured tests for new location-based subscription query.

The tests follow the established patterns in the file and properly verify that findSubscriptionsByLocationId passes the correct parameters to Prisma (where.locationId, orderBy.dateAdded: "desc"). Coverage for both the happy path and empty result case is appropriate.

apps/postgres/prisma/migrations/20251201155125_add_gov_notify_id/migration.sql (1)

1-15: Well-structured idempotent migration.

The migration properly uses IF NOT EXISTS checks for both the column addition and index creation, ensuring it can be safely re-run. The gov_notify_id column and index support the notification audit functionality introduced in this PR.

e2e-tests/tests/manual-upload.spec.ts (1)

795-801: Good cleanup order respects foreign key constraints.

The cleanup correctly deletes subscriptions before users, which avoids FK constraint violations. The tracking pattern with testData arrays ensures reliable cleanup even if tests fail mid-execution.

libs/notifications/src/govnotify/govnotify-client.test.ts (3)

29-54: Comprehensive test for successful email sending.

The test properly verifies the template ID, email address, and personalisation parameters are passed correctly to the NotifyClient. The assertion structure clearly validates the expected behavior.


77-101: Good retry behavior coverage.

Testing that the client retries on first failure and succeeds on the second attempt validates the retry logic works correctly with NOTIFICATION_RETRY_ATTEMPTS=1 (initial + 1 retry = 2 calls).


29-30: Dynamic imports may cause module caching issues.

Using await import("./govnotify-client.js") in each test could lead to the module being cached after the first import, meaning subsequent tests might not get fresh module instances. If tests start failing intermittently, consider using vi.resetModules() in beforeEach to ensure each test gets a fresh module instance.

Also applies to: 56-57, 77-78, 103-104, 125-126, 146-147

e2e-tests/utils/api-auth-helpers.ts (2)

20-70: Well-implemented token caching with proper error handling.

The implementation correctly:

  • Caches tokens with a 5-minute safety buffer before expiry
  • Provides clear error messages when credentials are missing
  • Handles both HTTP errors and exceptions appropriately
  • Avoids logging sensitive data (tokens/secrets)

The client credentials flow with /.default scope is the correct pattern for Azure AD service-to-service authentication.


12-13: Module-level state is acceptable for E2E test helpers.

While module-level mutable state (cachedToken, tokenExpiry) is generally discouraged, it's appropriate here for E2E test utilities where token reuse across tests improves performance. The clearCachedToken() function provides a way to reset state when needed.

libs/notifications/src/notification/notification-service.test.ts (2)

1-79: Well-structured test setup and coverage.

The mock setup and first test case correctly verify the notification flow for subscribed users. The use of vi.mock at the module level followed by vi.mocked to configure return values is appropriate for Vitest.


182-204: LGTM - validation error tests are correctly structured.

The toThrow() assertions use substring matching, which will correctly match the actual error messages from the implementation (e.g., "Invalid publication event: Publication ID is required, ..." will match "Invalid publication event").

libs/notifications/src/notification/notification-service.ts (2)

21-86: Good use of Promise.allSettled for concurrent notification processing.

The implementation correctly uses Promise.allSettled to handle per-user notifications independently, preventing a single failure from affecting other users. The aggregation logic properly handles both fulfilled and rejected promises.


171-174: LGTM - buildUserName helper is concise and handles edge cases.

Correctly filters out null/undefined values and provides a sensible fallback.

e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (2)

39-46: Good use of test.skip for conditional test execution.

The beforeAll hook appropriately skips tests when Azure AD OAuth tokens aren't available, with a clear explanation message directing users to alternative test suites.


48-56: Clean test data cleanup in afterEach.

The cleanup order (notifications → subscriptions → users) respects foreign key constraints. Good pattern for test isolation.

libs/notifications/prisma/schema.prisma (2)

11-29: Schema follows naming conventions correctly.

Good use of @map and @@map to maintain snake_case in the database while using camelCase in the Prisma client. The indexes on publicationId, status, and govNotifyId support the query patterns in notification-queries.ts.


22-22: No action needed. The Subscription model is properly defined in libs/subscriptions/prisma/schema.prisma and the relation reference on line 22 is valid. This multi-file Prisma schema setup allows cross-module model references, which is correctly configured in this codebase.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
e2e-tests/utils/notification-helpers.ts (1)

65-73: Verify getNotificationById type definition exists.

The code calls notifyClient.getNotificationById(notificationId) which may not be properly typed in types/notifications-node-client.d.ts. If TypeScript compilation succeeds, the type definition exists. Otherwise, you'll need to extend the type definition.

#!/bin/bash
# Check if getNotificationById is defined in the type definition
fd -t f "notifications-node-client.d.ts" | xargs rg "getNotificationById"
libs/notifications/src/notification/notification-service.ts (3)

84-98: Redundant double-write persists: audit log created then immediately updated with same status.

The code creates a notification audit log with status "Skipped" (line 89), then immediately calls updateNotificationStatus with the same "Skipped" status and error message (line 92). This is an unnecessary database round-trip.

Per previous review feedback, pass the error message during creation:

     if (!subscription.user.email) {
-      const notification = await createNotificationAuditLog({
+      await createNotificationAuditLog({
         subscriptionId: subscription.subscriptionId,
         userId: subscription.userId,
         publicationId: event.publicationId,
-        status: "Skipped"
+        status: "Skipped",
+        errorMessage: "No email address"
       });

-      await updateNotificationStatus(notification.notificationId, "Skipped", undefined, "No email address");
-
       return {
         status: "skipped",
         error: `User ${subscription.userId}: No email address`
       };
     }

This requires adding an optional errorMessage field to the CreateNotificationData interface in notification-queries.ts.


100-114: Same double-write issue for invalid email case.

Same pattern as the no-email case above - creates audit log then immediately updates it with the same status.

     if (!isValidEmail(subscription.user.email)) {
-      const notification = await createNotificationAuditLog({
+      await createNotificationAuditLog({
         subscriptionId: subscription.subscriptionId,
         userId: subscription.userId,
         publicationId: event.publicationId,
-        status: "Skipped"
+        status: "Skipped",
+        errorMessage: "Invalid email format"
       });

-      await updateNotificationStatus(notification.notificationId, "Skipped", undefined, "Invalid email format");
-
       return {
         status: "skipped",
         error: `User ${subscription.userId}: Invalid email format`
       };
     }

148-155: Exception handling doesn't update audit log status on failure.

If createNotificationAuditLog succeeds (line 116) but a subsequent exception occurs before updateNotificationStatus, the audit log remains in "Pending" status indefinitely. This leaves orphaned records in the database.

Refactor to ensure the audit log is updated even on exceptions:

+    let notification;
     try {
-      const notification = await createNotificationAuditLog({
+      notification = await createNotificationAuditLog({
         subscriptionId: subscription.subscriptionId,
         userId: subscription.userId,
         publicationId: event.publicationId,
         status: "Pending"
       });
+    } catch (error) {
+      const errorMessage = error instanceof Error ? error.message : String(error);
+      return {
+        status: "failed",
+        error: `User ${subscription.userId}: ${errorMessage}`
+      };
+    }

+    try {
       const userName = buildUserName(subscription.user.firstName, subscription.user.surname);
       const templateParameters = buildTemplateParameters({
         userName,
         hearingListName: event.hearingListName,
         publicationDate: event.publicationDate,
         locationName: event.locationName
       });

       const emailResult = await sendEmail({
         emailAddress: subscription.user.email,
         templateParameters
       });

       if (emailResult.success) {
         await updateNotificationStatus(notification.notificationId, "Sent", new Date(), undefined, emailResult.notificationId);
         return {
           status: "sent"
         };
       }

       await updateNotificationStatus(notification.notificationId, "Failed", undefined, emailResult.error);
       return {
         status: "failed",
         error: `User ${subscription.userId}: ${emailResult.error}`
       };
     } catch (error) {
       const errorMessage = error instanceof Error ? error.message : String(error);
+      await updateNotificationStatus(notification.notificationId, "Failed", undefined, errorMessage);
       return {
         status: "failed",
         error: `User ${subscription.userId}: ${errorMessage}`
       };
     }
🧹 Nitpick comments (4)
libs/notifications/src/govnotify/notifications-node-client.d.ts (1)

4-4: Broaden sendEmail options type to cover full GOV.UK Notify API.

Right now the options parameter only exposes personalisation, but Notify also supports reference and emailReplyToId on this options object.(npmjs.com) This is not wrong for current usage but will block you from typing those fields later.

Consider expanding the signature to more closely match the upstream API:

-    sendEmail(templateId: string, emailAddress: string, options?: { personalisation?: Record<string, string> }): Promise<any>;
+    sendEmail(
+      templateId: string,
+      emailAddress: string,
+      options?: {
+        personalisation?: Record<string, string>;
+        reference?: string;
+        emailReplyToId?: string;
+      }
+    ): Promise<any>;

Please double‑check against the exact notifications-node-client version you depend on to ensure this matches its documented options shape.

libs/notifications/src/govnotify/template-config.ts (1)

5-10: Variable name doesn't match the environment variable source.

The constant GOVUK_NOTIFY_TEMPLATE_ID is sourced from GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION (line 2), but the error message correctly references the full name. Consider renaming the constant to match the environment variable for clarity:

-const GOVUK_NOTIFY_TEMPLATE_ID = process.env.GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION || "";
+const GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION = process.env.GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION || "";

 export function getTemplateId(): string {
-  if (!GOVUK_NOTIFY_TEMPLATE_ID) {
+  if (!GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION) {
     throw new Error("GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION environment variable is not set");
   }
-  return GOVUK_NOTIFY_TEMPLATE_ID;
+  return GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION;
 }
libs/notifications/src/notification/notification-queries.test.ts (2)

18-41: Add assertion to verify the create method was called with correct arguments.

The test validates the returned result but doesn't verify that prisma.notificationAuditLog.create was called with the expected data. This would catch regressions if the function incorrectly transforms the input.

   const result = await createNotificationAuditLog({
     subscriptionId: "sub-1",
     userId: "user-1",
     publicationId: "pub-1"
   });

+  expect(prisma.notificationAuditLog.create).toHaveBeenCalledWith({
+    data: {
+      subscriptionId: "sub-1",
+      userId: "user-1",
+      publicationId: "pub-1",
+      status: "Pending"
+    }
+  });
   expect(result.notificationId).toBe("notif-1");
   expect(result.status).toBe("Pending");

43-57: Add test case with all optional parameters.

The current test only exercises sentAt. Add a test case that includes errorMessage and govNotifyId to ensure all optional parameters are correctly passed through:

it("should update notification status with all optional parameters", async () => {
  const { prisma } = await import("@hmcts/postgres");
  const sentAt = new Date();

  await updateNotificationStatus("notif-1", "Failed", sentAt, "Send failed", "gov-notify-123");

  expect(prisma.notificationAuditLog.update).toHaveBeenCalledWith({
    where: { notificationId: "notif-1" },
    data: {
      status: "Failed",
      sentAt,
      errorMessage: "Send failed",
      govNotifyId: "gov-notify-123"
    }
  });
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96271e3 and b7dca26.

📒 Files selected for processing (15)
  • apps/postgres/prisma/migrations/20251202095746_remove_notification_unique_constraint/migration.sql (1 hunks)
  • docs/tickets/VIBE-221/plan.md (1 hunks)
  • e2e-tests/README-NOTIFICATIONS.md (1 hunks)
  • e2e-tests/run-with-credentials.js (1 hunks)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1 hunks)
  • e2e-tests/utils/notification-helpers.ts (1 hunks)
  • libs/notifications/prisma/schema.prisma (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.test.ts (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.ts (1 hunks)
  • libs/notifications/src/govnotify/notifications-node-client.d.ts (1 hunks)
  • libs/notifications/src/govnotify/template-config.ts (1 hunks)
  • libs/notifications/src/notification/notification-queries.test.ts (1 hunks)
  • libs/notifications/src/notification/notification-queries.ts (1 hunks)
  • libs/notifications/src/notification/notification-service.test.ts (1 hunks)
  • libs/notifications/src/notification/notification-service.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • libs/notifications/src/govnotify/govnotify-client.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
  • e2e-tests/run-with-credentials.js
  • libs/notifications/src/notification/notification-service.test.ts
  • e2e-tests/README-NOTIFICATIONS.md
  • libs/notifications/src/govnotify/govnotify-client.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • libs/notifications/src/notification/notification-service.ts
  • libs/notifications/src/notification/notification-queries.test.ts
  • libs/notifications/src/notification/notification-queries.ts
  • libs/notifications/src/govnotify/notifications-node-client.d.ts
  • e2e-tests/utils/notification-helpers.ts
  • libs/notifications/src/govnotify/template-config.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/src/notification/notification-service.ts
  • libs/notifications/src/notification/notification-queries.test.ts
  • libs/notifications/src/notification/notification-queries.ts
  • libs/notifications/src/govnotify/notifications-node-client.d.ts
  • e2e-tests/utils/notification-helpers.ts
  • libs/notifications/src/govnotify/template-config.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/src/notification/notification-queries.test.ts
**/prisma/schema.prisma

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

Applied to files:

  • libs/notifications/src/govnotify/template-config.ts
🧬 Code graph analysis (2)
libs/notifications/src/notification/notification-queries.test.ts (1)
libs/notifications/src/notification/notification-queries.ts (2)
  • createNotificationAuditLog (22-33)
  • updateNotificationStatus (35-51)
libs/notifications/src/notification/notification-queries.ts (1)
e2e-tests/utils/notification-helpers.ts (1)
  • getNotificationsByPublicationId (58-63)
🪛 LanguageTool
docs/tickets/VIBE-221/plan.md

[style] ~484-~484: It’s more common nowadays to write this noun as one word.
Context: ... table with email field?) - Where is user name stored for template personalization? ...

(RECOMMENDED_COMPOUNDS)


[uncategorized] ~484-~484: Do not mix variants of the same word (‘personalization’ and ‘personalisation’) within a single text.
Context: ... Where is user name stored for template personalization? - Do we need to fetch user data fro...

(EN_WORD_COHERENCY)

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

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

(MD040, fenced-code-language)


156-156: Bare URL used

(MD034, no-bare-urls)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)


191-191: Bare URL used

(MD034, no-bare-urls)


541-541: Bare URL used

(MD034, no-bare-urls)

⏰ 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 (24)
apps/postgres/prisma/migrations/20251202095746_remove_notification_unique_constraint/migration.sql (1)

1-2: Add documentation explaining the business rationale for removing the unique constraint.

The removal of the unique index on (user_id, publication_id) lacks context. While the current notification service creates one notification per subscription (and subscriptions enforce uniqueness on (userId, locationId)), the constraint removal suggests either a bug fix for existing duplicates or a future design change.

Clarify:

  • Why was the constraint needed initially, then removed one day later?
  • Are there code paths outside the subscription model that create notifications?
  • Does existing data contain duplicates that this migration needed to accommodate?
  • Is the vague commit message "fix bug" referring to a specific issue?

Add a comment in the migration explaining the rationale to prevent future confusion.

libs/notifications/src/govnotify/notifications-node-client.d.ts (1)

1-7: Remove duplicate and incomplete ambient module declaration; update types/notifications-node-client.d.ts instead.

Two conflicting declare module declarations exist for "notifications-node-client":

  1. types/notifications-node-client.d.ts — comprehensive but missing getNotificationById (used in e2e-tests)
  2. libs/notifications/src/govnotify/notifications-node-client.d.ts — incomplete and redundant

Delete libs/notifications/src/govnotify/notifications-node-client.d.ts and add getNotificationById to the declaration in types/notifications-node-client.d.ts to resolve the type inconsistency. The types/ location is the proper place for ambient module declarations (it's in typeRoots), and having both creates conflicting type information across the codebase.

⛔ Skipped due to learnings
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/tsconfig.json : Module tsconfig.json must extend the root tsconfig.json with outDir, rootDir, and include/exclude declarations.
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Don't create types.ts files - colocate types with the appropriate code.
libs/notifications/src/govnotify/template-config.ts (4)

1-3: LGTM!

The constants use appropriate defaults, with empty strings for required credentials that are validated by getTemplateId() and getApiKey(), and a production URL fallback for CATH_SERVICE_URL.


12-17: LGTM!

Proper validation with clear error message.


19-21: LGTM!

Returns the service URL with a sensible production default.


32-39: Confirm if template parameter keys are constrained by Gov.UK Notify template requirements.

The interface uses mixed naming conventions (ListType in PascalCase, content_date and start_page_link in snake_case, locations in camelCase). These keys are passed directly as personalisation to the Gov.UK Notify API's sendEmail() method, suggesting they may be required to match exact template placeholder names.

If these exact names are dictated by the Gov.UK Notify template, the mixed casing is acceptable and should be documented. Otherwise, standardize to camelCase per coding guidelines.

libs/notifications/src/notification/notification-queries.test.ts (1)

4-11: LGTM!

The mock properly isolates the Prisma client for unit testing.

libs/notifications/src/notification/notification-service.ts (3)

20-80: LGTM!

The orchestration logic properly validates input, handles partial failures with Promise.allSettled, and aggregates results correctly. The error handling for both fulfilled and rejected promises is comprehensive.


116-147: LGTM!

The email sending logic correctly creates an audit log with "Pending" status, attempts the send, and updates to the final status ("Sent" or "Failed") based on the result. The status transitions are appropriate.


157-160: LGTM!

The helper correctly handles null values and provides a sensible fallback.

libs/notifications/prisma/schema.prisma (2)

1-9: LGTM!

Standard Prisma configuration with shared client output.


11-27: Unique constraint was intentionally removed—do not re-add without clarification.

The schema currently lacks the @@unique([userId, publicationId]) constraint, but this is not an oversight. The constraint was created in migration 20251201095418_add_notification_audit_log and then explicitly dropped in migration 20251202095746_remove_notification_unique_constraint.

The plan (docs/tickets/VIBE-221/plan.md lines 92-93) requires this constraint for deduplication, but the codebase has since removed it. Before re-adding the constraint to the schema, clarify why it was removed and whether that decision should be reversed or the plan should be updated.

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

1-552: Planning document - no code review required.

This is a technical planning document. The static analysis hints are markdown formatting suggestions (e.g., adding language identifiers to code blocks, handling bare URLs) that don't impact functionality. The document provides valuable context for the implementation.

libs/notifications/src/notification/notification-queries.ts (4)

22-33: LGTM with enhancement suggested above.

The function correctly creates an audit log entry with a sensible default status. See the previous comment about adding errorMessage support.


35-51: LGTM!

The function correctly updates the notification status with all optional fields.


53-57: LGTM!

Appropriate query with null handling for not-found cases.


59-63: LGTM!

Appropriate query for retrieving all notifications for a publication.

e2e-tests/utils/notification-helpers.ts (7)

5-17: LGTM!

Interfaces follow camelCase naming convention as per coding guidelines.


19-35: LGTM!

Test user creation logic is correct with proper typing and sensible defaults.


58-63: LGTM!

Query logic is correct and consistent with the main implementation in notification-queries.ts.


75-84: LGTM!

Defensive filtering and early return prevent errors with empty arrays. Good practice.


86-94: LGTM!

Consistent cleanup pattern with appropriate guards.


96-104: LGTM!

Proper typing with no cast needed since user is in the main Prisma client.


37-56: The dateAdded field has a database default and does not require an explicit value.

The Subscription model in libs/subscriptions/prisma/schema.prisma defines dateAdded with @default(now()), so the field will be automatically populated by the database when the record is created. The current implementation at lines 40-46 is correct. The (prisma as any) cast is acceptable for test helpers.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
libs/api/src/blob-ingestion/repository/service.ts (1)

95-116: Fire-and-forget trigger looks good; consider reusing parsed content date

The fire-and-forget pattern with an explicit .catch keeps ingestion latency isolated from notification failures, which is a good trade-off here. To avoid parsing request.content_date multiple times and keep publication date usage consistent, consider parsing it once earlier in processBlobIngestion and reusing it when triggering notifications:

// earlier near artefact creation
const contentDate = new Date(request.content_date);
// use contentDate both for createArtefact and notifications
-      triggerPublicationNotifications(artefactId, request.court_id, validation.listTypeId, new Date(request.content_date)).catch((error) => {
+      triggerPublicationNotifications(artefactId, request.court_id, validation.listTypeId, contentDate).catch((error) => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7dca26 and 8137cbf.

📒 Files selected for processing (4)
  • .github/workflows/e2e.yml (1 hunks)
  • docs/GITHUB_SECRETS_SETUP.md (1 hunks)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1 hunks)
  • libs/api/src/blob-ingestion/repository/service.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • libs/api/src/blob-ingestion/repository/service.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/api/src/blob-ingestion/repository/service.ts
🪛 LanguageTool
docs/GITHUB_SECRETS_SETUP.md

[uncategorized] ~62-~62: The official name of this software platform is spelled with a capital “H”.
Context: ... Configuration The E2E tests workflow (.github/workflows/e2e.yml) automatically uses ...

(GITHUB)

⏰ 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 (5)
docs/GITHUB_SECRETS_SETUP.md (3)

1-104: Well-structured and comprehensive documentation.

The documentation provides clear guidance on required secrets, workflow configuration, local development setup, and troubleshooting. The tables are well-organized, and the explanations are thorough. The inclusion of environment variable mappings and notification test behavior helps users understand the full picture.


68-79: Local development credentials flow is correctly implemented.

The run-with-credentials.js script exists and properly implements the documented behavior. It detects CI mode via process.env.CI === 'true' and conditionally loads credentials from either Azure Key Vault (local development) or relies on GitHub Secrets (CI environment). The script uses @azure/identity with DefaultAzureCredential for Key Vault authentication and correctly passes loaded secrets to Playwright tests as environment variables. No discrepancies between documentation and implementation found.


97-103: Documentation accurately reflects test implementation.

The test implementation in blob-ingestion-notifications.spec.ts (line 68) correctly implements the documented behavior: the "should verify GOV.UK Notify email content" test uses test.skip(!process.env.GOVUK_NOTIFY_API_KEY, "Skipping: GOVUK_NOTIFY_API_KEY not set") to conditionally skip when the API key is not set. Other notification tests (email validation, skipped notifications for invalid emails) proceed without this API key dependency. The documentation at lines 97-103 is accurate.

libs/api/src/blob-ingestion/repository/service.ts (2)

2-4: Imports align with alias/ESM conventions

Using @hmcts/* aliases and non-relative imports here is consistent with the workspace and ESM guidelines; no changes needed.


141-181: Verify notification result shape and avoid logging any sensitive data

The defensive checks around courtId, location, and listType are solid, and the summary log of counts is useful. Two things to verify with the @hmcts/notifications API and data contract:

  1. Ensure result.errors is always defined as an array so result.errors.length cannot throw; if it can be undefined, guard with optional chaining or a default.
  2. Confirm that result.errors does not contain PII (e.g. email addresses or subscriber identifiers); if it does, consider only logging aggregate information or sanitized error codes instead of the raw array:
if (result.errors?.length) {
  console.error("Notification errors:", result.errors.map((e) => e.code ?? e.message));
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (3)
libs/auth/package.json (1)

32-32: Pin the @hmcts/account dependency to a specific version.

The @hmcts/account dependency is unpinned ("*"), which violates the coding guideline that dependencies must be pinned to specific versions (except for peer dependencies). This can lead to unexpected breaking changes or inconsistent behavior across environments.

Replace the wildcard with a specific version, for example:

-    "@hmcts/account": "*",
+    "@hmcts/account": "1.0.0",

Please ensure the version number matches an available release on your package registry.

libs/simple-router/package.json (1)

6-11: Add missing ./config export to align with package.json guidelines.

The exports field is missing the required ./config export. Per the coding guidelines, all @hmcts modules must include both main (.) and config (./config) exports with production and default conditions.

Apply this diff to add the missing config export:

 "exports": {
   ".": {
     "production": "./dist/index.js",
     "default": "./src/index.ts"
   }
+ },
+ "./config": {
+   "production": "./dist/config.js",
+   "default": "./src/config.ts"
 }
libs/cloud-native-platform/package.json (1)

6-11: Add missing ./config export to comply with module requirements.

Per coding guidelines, module package.json files must include both main export (.) and config export (./config) in the exports field with production and default conditions. The current exports field is missing the ./config export.

Apply this diff to add the config export:

  "exports": {
    ".": {
      "production": "./dist/index.js",
      "default": "./src/index.ts"
    }
+   },
+   "./config": {
+     "production": "./dist/config.js",
+     "default": "./src/config.ts"
+   }
  }

Note: Adjust the config paths as needed to match your actual config file locations.

🧹 Nitpick comments (3)
libs/notifications/src/govnotify/govnotify-client.ts (1)

13-22: Align boolean naming and interface placement with guidelines (optional)

Two minor style nits against the stated guidelines:

  • SendEmailResult.success is a boolean but doesn’t use an is/has/can prefix.
  • Interfaces are defined near the top rather than at the bottom after functions.

If it’s still easy to change within this PR, consider:

  • Renaming success to something like isSuccess (and updating call‑sites).
  • Moving EmailResponse, SendEmailParams, and SendEmailResult below the function implementations.
libs/api/src/blob-ingestion/repository/service.test.ts (2)

12-19: Keep mockListTypes shape aligned with production module

The added mockListTypes only expose id, englishFriendlyName, and welshFriendlyName. In validateBlobRequest the implementation looks up by lt.name, which suggests the real mockListTypes objects may also include a name field. To avoid tests drifting from production behaviour (and to keep any future tests that rely on name consistent), consider mirroring the full structure exported by @hmcts/publication here, including name if it exists.

Also applies to: 43-46


34-40: Notification tests are strong, but async contract of getLocationById and timers should be verified

The new notification-path tests cover success, no‑match, invalid location ID, missing location, and missing list type thoroughly, and the mocks for @hmcts/location / @hmcts/notifications look correct. Two things to sanity‑check:

  • In validation.ts getLocationById is used synchronously, but here it’s mocked with mockResolvedValue and used as if it were async. Please confirm the real @hmcts/location.getLocationById is actually async; if it’s sync, switch to mockReturnValue here to keep the contract consistent.
  • The await new Promise((resolve) => setTimeout(resolve, 50)); waits make the tests rely on wall‑clock timing for the fire‑and‑forget notifications. For long‑term stability it would be safer to either (a) expose a Promise from the notification helper that tests can await, or (b) use fake timers / a waitFor‑style assertion instead of fixed sleeps.

Functionally these tests look good; this is mainly about making them more robust and aligned with the real APIs.

Also applies to: 47-48, 301-391, 393-466

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8137cbf and 698e4c3.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (17)
  • apps/api/package.json (1 hunks)
  • apps/web/package.json (1 hunks)
  • libs/admin-pages/package.json (1 hunks)
  • libs/api/package.json (1 hunks)
  • libs/api/src/blob-ingestion/repository/service.test.ts (3 hunks)
  • libs/auth/package.json (1 hunks)
  • libs/cloud-native-platform/package.json (1 hunks)
  • libs/list-types/civil-and-family-daily-cause-list/package.json (1 hunks)
  • libs/notifications/package.json (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.ts (1 hunks)
  • libs/notifications/types/notifications-node-client/index.d.ts (1 hunks)
  • libs/public-pages/package.json (1 hunks)
  • libs/simple-router/package.json (1 hunks)
  • libs/subscriptions/package.json (1 hunks)
  • libs/system-admin-pages/package.json (1 hunks)
  • libs/verified-pages/package.json (1 hunks)
  • libs/web-core/package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/notifications/package.json
🧰 Additional context used
📓 Path-based instructions (4)
libs/*/package.json

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/web-core/package.json
  • libs/api/package.json
  • libs/verified-pages/package.json
  • libs/simple-router/package.json
  • libs/admin-pages/package.json
  • libs/system-admin-pages/package.json
  • libs/public-pages/package.json
  • libs/auth/package.json
  • libs/cloud-native-platform/package.json
  • libs/subscriptions/package.json
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • libs/notifications/types/notifications-node-client/index.d.ts
  • libs/api/src/blob-ingestion/repository/service.test.ts
  • libs/notifications/src/govnotify/govnotify-client.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/types/notifications-node-client/index.d.ts
  • libs/api/src/blob-ingestion/repository/service.test.ts
  • libs/notifications/src/govnotify/govnotify-client.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/api/src/blob-ingestion/repository/service.test.ts
🧠 Learnings (4)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to package.json : Dependencies must be pinned to specific versions (e.g., `"express": "5.1.0"`), except for peer dependencies.

Applied to files:

  • libs/web-core/package.json
  • libs/api/package.json
  • libs/verified-pages/package.json
  • apps/web/package.json
  • apps/api/package.json
  • libs/simple-router/package.json
  • libs/admin-pages/package.json
  • libs/system-admin-pages/package.json
  • libs/public-pages/package.json
  • libs/auth/package.json
  • libs/list-types/civil-and-family-daily-cause-list/package.json
  • libs/cloud-native-platform/package.json
  • libs/subscriptions/package.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/package.json : Package names must use hmcts scope (e.g., `hmcts/auth`, `hmcts/case-management`).

Applied to files:

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

Applied to files:

  • libs/api/package.json
  • libs/subscriptions/package.json
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to libs/*/package.json : All test packages must use `"test": "vitest run"` script to run tests.

Applied to files:

  • libs/list-types/civil-and-family-daily-cause-list/package.json
🧬 Code graph analysis (1)
libs/api/src/blob-ingestion/repository/service.test.ts (3)
libs/api/src/blob-ingestion/validation.ts (1)
  • validateBlobRequest (9-158)
libs/api/src/blob-ingestion/repository/service.ts (1)
  • processBlobIngestion (17-140)
libs/api/src/blob-ingestion/repository/queries.ts (1)
  • createIngestionLog (4-16)
⏰ 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 (18)
libs/verified-pages/package.json (1)

32-32: Express peer dependency alignment looks good.

The update to ^5.2.0 properly aligns this library with the workspace-wide Express upgrade, ensuring compatibility with the newly integrated notifications library while maintaining flexible peer dependency versioning.

libs/auth/package.json (1)

27-27: Express peer dependency bump aligns with monorepo strategy.

The peer dependency upgrade from ^5.1.0 to ^5.2.0 is consistent with the repository-wide express upgrade across multiple packages (apps/api, apps/web, libs/notifications, etc.). Peer dependencies can use semantic versioning ranges, so this follows best practices.

libs/list-types/civil-and-family-daily-cause-list/package.json (1)

39-39: Peer dependency version bump is appropriate.

The Express peer dependency update from ^5.1.0 to ^5.2.0 follows the learnings that peer dependencies can use caret ranges for flexibility, and is consistent with the coordinated Express upgrades across the monorepo mentioned in the PR. The caret range allows dependent packages to consume compatible minor/patch versions.

Verify that Express 5.2.0 introduces no breaking changes affecting this package's Express integration (e.g., middleware patterns, routing, configuration).

libs/simple-router/package.json (1)

22-22: Express peer dependency version bump is appropriate.

The upgrade from ^5.1.0 to ^5.2.0 aligns with the broader express version bump across the monorepo (part of the notifications integration changes) and is acceptable for peer dependencies. No concerns with this change.

libs/cloud-native-platform/package.json (1)

23-23: Express peerDependency version bump looks good.

The minor version update from ^5.1.0 to ^5.2.0 is appropriate for a peerDependency, and version ranges are acceptable per the learnings.

libs/web-core/package.json (1)

56-56: Express 5.2.0 peer dependency update compliant.

The peer dependency version constraint is correctly expressed as a caret range. Ensure that consuming packages using this peer dependency are compatible with Express 5.2.0.

libs/system-admin-pages/package.json (1)

28-28: Package structure and express peer dependency update compliant.

All coding guidelines are satisfied: package name uses @hmcts scope, exports include both . and ./config with production/default conditions, build:nunjucks script is present, test script uses vitest run, and express peer dependency is properly versioned.

apps/api/package.json (1)

24-24: Express dependency properly pinned to specific version.

The dependency is correctly pinned to 5.2.0 following the guideline for regular (non-peer) dependencies.

libs/public-pages/package.json (1)

26-26: Package structure and express peer dependency update compliant.

All coding guidelines are satisfied: package name uses @hmcts scope, exports include both . and ./config with production/default conditions, build:nunjucks script is present, test script uses vitest run, and express peer dependency is properly versioned.

libs/admin-pages/package.json (1)

30-30: Package structure and express peer dependency update compliant.

All coding guidelines are satisfied: package name uses @hmcts scope, exports include both . and ./config with production/default conditions, build:nunjucks script is present, test script uses vitest run, and express peer dependency is properly versioned.

libs/subscriptions/package.json (1)

30-30: Package structure and express peer dependency update compliant.

All coding guidelines are satisfied: package name uses @hmcts scope, exports include both . and ./config with production/default conditions, test script uses vitest run, and express peer dependency is properly versioned.

libs/api/package.json (1)

2-2: No action required. The package.json file at libs/api/package.json fully complies with coding guidelines: the package name correctly uses the @hmcts scope, exports include both main and config exports with proper production and default conditions, and the test script is configured correctly. The directory name (api) differs from the package name (blob-ingestion), which is a standard monorepo pattern and not a concern.

libs/notifications/types/notifications-node-client/index.d.ts (3)

30-34: LGTM! NotifyClient class structure is well-defined.

The class declaration follows coding guidelines with appropriate naming conventions and method signatures that return promises.


24-28: No issues found. The interface correctly uses the British spelling "personalisation" to match the Gov.UK Notify API and the official notifications-node-client library specifications.


2-16: No action required. The from_email property correctly reflects the actual notifications-node-client library API response structure and is appropriately named in snake_case to match the JSON payload from Gov.Notify. Ambient type declarations for third-party libraries must mirror the external API exactly, making this an acceptable exception to camelCase naming conventions.

libs/notifications/src/govnotify/govnotify-client.ts (1)

24-35: sendEmail wrapper structure looks good

The public sendEmail API cleanly wraps sendEmailInternal with retryWithBackoff and normalises failures into a SendEmailResult instead of throwing, which is a nice, predictable contract for callers.

apps/web/package.json (1)

38-38: Remove or correct invalid express version 5.2.0.

Express version 5.2.0 does not exist in the npm registry; the latest express 5.x release is 5.1.0. This invalid version will cause npm install to fail. Change to "5.1.0" or the intended version.

⛔ Skipped due to learnings
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to package.json : Dependencies must be pinned to specific versions (e.g., `"express": "5.1.0"`), except for peer dependencies.
libs/api/src/blob-ingestion/repository/service.test.ts (1)

220-299: Good coverage of validation logging and system‑error edge cases

The new tests for missing provenance / court_id, missing listTypeId after a “valid” validation result, and non‑Error rejections from createArtefact accurately mirror the behaviour in processBlobIngestion and its error logging. They ensure we don’t regress logging of UNKNOWN provenance/court IDs and that odd failure modes are captured as SYSTEM_ERROR with the expected messages.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
libs/api/src/blob-ingestion/repository/service.test.ts (1)

508-511: Update test expectation to align with sanitized error logging.

This test expects email addresses to be logged in the errors array, which validates PII logging behavior. When the production code is updated to sanitize error logs (per the suggestion in service.ts), update this expectation accordingly.

After applying the fix in service.ts, update this expectation:

-    expect(consoleErrorSpy).toHaveBeenCalledWith("Notification errors:", [
-      { email: "[email protected]", error: "Invalid email" },
-      { email: "[email protected]", error: "Service unavailable" }
-    ]);
+    expect(consoleErrorSpy).toHaveBeenCalledWith("Notification errors:", {
+      count: 2,
+      errorMessages: ["Invalid email", "Service unavailable"]
+    });
🧹 Nitpick comments (2)
libs/admin-pages/src/pages/manual-upload-summary/index.test.ts (1)

707-736: Verify if double mock is necessary.

Line 713-714 chains two mockResolvedValueOnce calls, but the test only calls the POST handler once. Other error tests in this file (e.g., lines 504-534, 536-564) only mock getManualUpload once.

Consider simplifying to a single mock unless the POST handler genuinely calls getManualUpload twice during error rendering:

-      vi.mocked(getManualUpload).mockResolvedValueOnce(invalidDateUploadData).mockResolvedValueOnce(invalidDateUploadData);
+      vi.mocked(getManualUpload).mockResolvedValue(invalidDateUploadData);
libs/api/src/blob-ingestion/repository/service.test.ts (1)

325-326: Consider using vi.waitFor for more robust async assertions.

Using setTimeout for waiting on fire-and-forget operations can lead to flaky tests. Vitest's vi.waitFor or waitFor from testing utilities provides a more reliable approach.

Example pattern:

await vi.waitFor(() => {
  expect(sendPublicationNotifications).toHaveBeenCalled();
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 698e4c3 and c5f21bd.

📒 Files selected for processing (3)
  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts (2 hunks)
  • libs/api/src/blob-ingestion/repository/service.test.ts (3 hunks)
  • libs/api/src/blob-ingestion/repository/service.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • libs/api/src/blob-ingestion/repository/service.test.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
  • libs/api/src/blob-ingestion/repository/service.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/api/src/blob-ingestion/repository/service.test.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
  • libs/api/src/blob-ingestion/repository/service.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/api/src/blob-ingestion/repository/service.test.ts
  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
**/pages/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/admin-pages/src/pages/manual-upload-summary/index.test.ts
🧠 Learnings (1)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Never put sensitive data in logs.

Applied to files:

  • libs/api/src/blob-ingestion/repository/service.test.ts
🧬 Code graph analysis (2)
libs/admin-pages/src/pages/manual-upload-summary/index.test.ts (5)
libs/api/src/blob-ingestion/file-storage.ts (1)
  • saveUploadedFile (12-23)
libs/admin-pages/src/manual-upload/file-storage.ts (1)
  • saveUploadedFile (12-23)
libs/publication/src/index.ts (1)
  • createArtefact (6-6)
libs/admin-pages/src/pages/manual-upload-summary/index.ts (1)
  • POST (212-212)
libs/location/src/index.ts (1)
  • getLocationById (17-17)
libs/api/src/blob-ingestion/repository/service.ts (2)
libs/notifications/src/notification/notification-service.ts (1)
  • sendPublicationNotifications (20-80)
libs/notifications/src/index.ts (1)
  • sendPublicationNotifications (1-1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (10)
libs/admin-pages/src/pages/manual-upload-summary/index.test.ts (3)

75-80: LGTM: Notification mock and imports properly configured.

The mock setup and imports follow the established pattern in the test file and properly support the new notification integration tests.


566-674: Excellent test coverage for fire-and-forget notification pattern.

These tests properly verify that:

  • Notifications are sent with correct parameters on successful upload
  • Upload succeeds even when location lookup fails for notifications
  • Upload succeeds even when notification sending fails
  • Appropriate warnings/errors are logged for troubleshooting

The resilience testing ensures the main upload flow is not blocked by notification failures, which is the correct behavior for a fire-and-forget pattern.


676-705: LGTM: Comprehensive edge case and i18n coverage.

These tests properly verify:

  • Welsh locale preservation in redirects (coding guideline compliance for i18n)
  • isFlatFile determination for JSON vs non-JSON files
  • Graceful handling of missing fileName (defensive programming)

The tests follow established patterns and provide good coverage of edge cases.

Also applies to: 738-851

libs/api/src/blob-ingestion/repository/service.ts (3)

1-8: LGTM!

Imports are well-organized with workspace aliases (@hmcts/*) for cross-package imports and .js extensions for relative imports, following the coding guidelines.


95-116: LGTM!

The fire-and-forget pattern is correctly implemented. The notification trigger runs asynchronously without blocking the ingestion response, and errors are caught and logged without affecting the main flow. The conditional skip for noMatch=true is appropriate.


155-159: The identifier mockListTypes is production reference data, not mock/test data. It's a shared catalog of list types exported from @hmcts/list-types-common and used consistently across multiple production modules (validation, page handlers, notifications) to map list type IDs to bilingual display names. The usage in this file is appropriate.

Likely an incorrect or invalid review comment.

libs/api/src/blob-ingestion/repository/service.test.ts (4)

6-20: LGTM!

Mock setup is comprehensive and correctly structured. The mockListTypes array provides test data that aligns with the production mock data used in the service.


34-40: LGTM!

New mock registrations for @hmcts/location and @hmcts/notifications follow the established pattern and are correctly placed.


392-465: LGTM!

Excellent edge case coverage for the triggerPublicationNotifications helper. Tests properly verify handling of invalid location IDs, missing locations, and missing list types. The console spy cleanup pattern is consistently applied.


220-299: LGTM!

Solid test coverage for edge cases including missing provenance/court_id fallbacks, missing list type ID after validation, and non-Error exception handling. These tests validate the defensive coding in the service implementation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1)

87-93: Add defensive checks before accessing notification data.

The code assumes notifications[0] exists and has a valid govNotifyId, but if notification processing hasn't completed or failed, this will throw an error rather than providing a clear test failure message.

Add assertions before accessing nested properties:

     const notifications = await getNotificationsByPublicationId(result.artefact_id);
+    expect(notifications).toHaveLength(1);
+    expect(notifications[0].govNotifyId).toBeTruthy();
+
     const govNotifyEmail = await getGovNotifyEmail(notifications[0].govNotifyId);
+    expect(govNotifyEmail).toBeTruthy();

     expect(govNotifyEmail.email_address).toBe(process.env.CFT_VALID_TEST_ACCOUNT!);

Based on learnings, this issue was previously flagged in past reviews.

🧹 Nitpick comments (2)
e2e-tests/tests/manual-upload.spec.ts (1)

839-839: Replace hard-coded delays with polling for notification processing.

The fixed 2-second delays are arbitrary and don't verify that notification processing has completed. This can lead to flaky tests (too short) or unnecessarily slow tests (too long).

Once you add notification verification (as suggested in the previous comment), replace the fixed delay with retry/polling logic similar to the blob-ingestion tests (lines 114-118 in blob-ingestion-notifications.spec.ts):

-    await new Promise((resolve) => setTimeout(resolve, 2000));
+    // Wait for async notification processing with retry logic
+    let notifications = [];
+    for (let i = 0; i < 10; i++) {
+      await new Promise((resolve) => setTimeout(resolve, 1000));
+      notifications = await getNotificationsByPublicationId(publicationId);
+      if (notifications.length > 0) break;
+    }

Better yet, extract this polling logic into a shared helper function in notification-helpers.ts to avoid duplication across test files.

Also applies to: 883-883

e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1)

85-85: Use consistent wait times or replace with polling logic.

The first test uses a 2-second delay while the third test uses 1 second. These arbitrary fixed delays should either be consistent or replaced with the polling logic used in tests 2 and 4.

For consistency and reliability, apply the polling helper (suggested in the previous comment) to all tests:

-    await new Promise((resolve) => setTimeout(resolve, 2000));
-
     const notifications = await getNotificationsByPublicationId(result.artefact_id);
+    // Or use the polling helper:
+    const notifications = await waitForNotifications(result.artefact_id);

Also applies to: 138-138

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5f21bd and 728987e.

📒 Files selected for processing (2)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1 hunks)
  • e2e-tests/tests/manual-upload.spec.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • e2e-tests/tests/manual-upload.spec.ts
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/tests/manual-upload.spec.ts
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/tests/manual-upload.spec.ts
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
🧬 Code graph analysis (1)
e2e-tests/tests/manual-upload.spec.ts (1)
e2e-tests/utils/notification-helpers.ts (4)
  • cleanupTestSubscriptions (86-94)
  • cleanupTestUsers (96-104)
  • createTestUser (19-35)
  • createTestSubscription (37-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (4)
e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (4)

68-70: Verify both required environment variables before running the test.

The test only checks GOVUK_NOTIFY_API_KEY but also requires CFT_VALID_TEST_ACCOUNT on line 70 with a non-null assertion that will throw if unset.


87-93: Add defensive checks before accessing notification properties.

If notifications[0] is undefined or govNotifyId is null, the subsequent calls will fail without clear error messages.


113-118: Extract duplicate polling logic into a shared helper.

This retry/polling pattern is duplicated across multiple tests and violates DRY principles.


162-167: Extract duplicate polling logic into a shared helper.

This is the same retry/polling pattern duplicated from lines 113-118.

🧹 Nitpick comments (1)
e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1)

138-138: Use retry logic instead of fixed delay for reliability.

Like the first test, this uses a fixed 1-second delay. If notification processing is slow or the system is under load, this could lead to false negatives where notifications exist but haven't been processed yet.

Consider using the retry pattern from tests 2 and 4:

-    await new Promise((resolve) => setTimeout(resolve, 1000));
-
-    const notifications = await getNotificationsByPublicationId(result.artefact_id);
+    // Wait for async notification processing (with retry logic)
+    let notifications = [];
+    for (let i = 0; i < 10; i++) {
+      await new Promise((resolve) => setTimeout(resolve, 1000));
+      notifications = await getNotificationsByPublicationId(result.artefact_id);
+      // Break early if we're sure no notifications will be created
+      if (i > 5) break;
+    }
+
     expect(notifications).toHaveLength(0);

Or better yet, implement the shared helper function suggested for lines 113-118 and use it consistently across all tests.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 728987e and 62993b1.

⛔ Files ignored due to path filters (1)
  • .claude/.DS_Store is excluded by !**/.DS_Store
📒 Files selected for processing (1)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
e2e-tests/utils/notification-helpers.ts (1)

40-46: Avoid (prisma as any) to retain Prisma type‑safety.

Casting prisma to any for subscription and notificationAuditLog operations removes compile‑time checks and can hide schema mismatches (as seen with the earlier createdAt vs dateAdded issue). You can call the typed client directly and let Prisma enforce the correct field names and shapes.

For example:

-  await (prisma as any).subscription.create({
+  await prisma.subscription.create({
     data: {
       subscriptionId,
       userId,
-      locationId
+      locationId
+      // If `dateAdded` is a required field without a default, also include:
+      // dateAdded: new Date(),
     }
   });

And similarly for the other calls:

-  return await (prisma as any).notificationAuditLog.findMany({
+  return await prisma.notificationAuditLog.findMany({

-  await (prisma as any).notificationAuditLog.deleteMany({
+  await prisma.notificationAuditLog.deleteMany({

-  await (prisma as any).subscription.deleteMany({
+  await prisma.subscription.deleteMany({

This keeps the helpers aligned with the Prisma schema and lets TS catch future model changes early.

Also applies to: 59-62, 79-83, 90-101

🧹 Nitpick comments (2)
e2e-tests/utils/notification-helpers.ts (2)

5-17: Optional: move interfaces to bottom of the module for consistency.

Coding guidelines say interfaces/types should live at the bottom after exported functions; consider moving TestSubscription and TestUser below the helpers to match that pattern.


58-63: Consider reusing the shared notification query helper.

There’s already a getNotificationsByPublicationId in libs/notifications/src/notification/notification-queries.ts; reusing that (via the @hmcts/* alias) instead of an ad‑hoc Prisma call here would reduce duplication and keep behaviour (e.g., filters/order) in one place.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62993b1 and 3f79d49.

📒 Files selected for processing (2)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1 hunks)
  • e2e-tests/utils/notification-helpers.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • e2e-tests/utils/notification-helpers.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/utils/notification-helpers.ts
🧬 Code graph analysis (1)
e2e-tests/utils/notification-helpers.ts (1)
libs/notifications/src/notification/notification-queries.ts (1)
  • getNotificationsByPublicationId (59-63)
⏰ 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)
e2e-tests/utils/notification-helpers.ts (3)

19-35: createTestUser helper looks sound for e2e usage.

User creation logic (UUID id, provenance fields, fixed role) is simple and aligned with test helper intent; returning the basic shape is sufficient.


65-73: GovNotify helper is straightforward and fails fast on missing API key.

The env check and direct NotifyClient call are appropriate for e2e tests; as long as GOVUK_NOTIFY_API_KEY is wired in the test environment, this looks fine.


104-112: User cleanup helper is clear and safe.

Guarding on empty userIds and using a single deleteMany on userId in [...] is a clean approach for test teardown.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
e2e-tests/utils/notification-helpers.ts (1)

37-56: Avoid (prisma as any) and tighten notification helper typings

These helpers lean heavily on Prisma models, but the (prisma as any) casts for subscription and notificationAuditLog and the any[] return types mean schema/field mistakes will only surface at runtime.

You already use strongly‑typed Prisma access elsewhere (e.g. prisma.notificationAuditLog.findMany(...) in libs/notifications/src/notification/notification-queries.ts), so it would be safer and more consistent to:

  • Drop the as any casts and call typed models directly:
  • await (prisma as any).subscription.create({
  • await prisma.subscription.create({
    data: {
    subscriptionId,
    userId,
    locationId
    }
    });
and similarly for all `notificationAuditLog` usages and `subscription.deleteMany`.

- Optionally, return a concrete notification type instead of `any[]` from the `get*Notifications` / `waitForNotifications` helpers (e.g. reuse the same `NotificationAuditLog` type you use in your notifications library) so tests can rely on fields like `status`, `govNotifyId`, and `publicationId` with type safety.

This keeps the test utilities aligned with your schema and makes refactors (like field renames) fail fast at compile time rather than in CI runs.  





Also applies to: 58-63, 79-84, 96-105, 107-123

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>libs/notifications/src/notification/notification-queries.ts (1)</summary><blockquote>

`61-65`: **Consider adding chronological ordering for predictability.**

For audit logs, returning notifications in creation order is typically expected. The e2e test helper (`e2e-tests/utils/notification-helpers.ts` lines 57-62) includes `orderBy: { createdAt: "asc" }` for consistency.



Apply this diff to add ordering:

```diff
export async function getNotificationsByPublicationId(publicationId: string): Promise<NotificationAuditLog[]> {
 return await prisma.notificationAuditLog.findMany({
-    where: { publicationId }
+    where: { publicationId },
+    orderBy: { createdAt: "asc" }
 });
}
libs/api/src/blob-ingestion/repository/service.ts (1)

155-159: Consider renaming mockListTypes to clarify it contains production reference data, not test data.

The mockListTypes constant in @hmcts/list-types-common contains actual production reference data for court list types (Civil Daily Cause List, Family Daily Cause List, Crime Daily List, Magistrates Public List, Crown Warned List), not mock data. The "mock" prefix is misleading—rename to listTypesData or listTypesReference to better reflect its purpose. This would require updates across multiple packages (blob-ingestion, admin-pages, public-pages) where it's currently imported and used.

libs/api/src/middleware/oauth-middleware.ts (1)

59-72: Config/env resolution for AZURE_API_CLIENT_ID looks sound; consider per-key fallback as an optional improvement

The switch to AZURE_API_CLIENT_ID with AZURE_API_CLIENT_ID || AZURE_CLIENT_ID env fallback and the updated error message are consistent and keep local dev working while enforcing the new config key.

One nuance: wrapping both config.get calls in a single try means a missing AZURE_API_CLIENT_ID in Key Vault will also discard a valid AZURE_TENANT_ID from config and force both to come from env. If you ever expect mixed configuration (e.g. tenant in config, client ID via env), you might optionally split the lookups and fall back per-key.

libs/api/src/middleware/oauth-middleware.test.ts (1)

94-200: Update remaining config mocks from AZURE_CLIENT_ID to AZURE_API_CLIENT_ID to exercise intended test branches

The tests in this section correctly mock AZURE_API_CLIENT_ID, ensuring the happy-path and "missing role" scenarios reach their intended token validation logic. However, five additional tests still mock:

if (key === "AZURE_CLIENT_ID") return "test-client-id";

Since the middleware now calls config.get("AZURE_API_CLIENT_ID") (line 61 of oauth-middleware.ts), these tests never reach their intended branches. The clientId becomes undefined, validateToken throws a config error, and the middleware's catch-all error handler returns 401 with "Invalid or expired token"—masking the fact that tests for token format validation, missing kid, JWKS failures, and JWT verification errors are no longer exercising those specific code paths.

Update the mocks at lines 269, 297, 329, 370, and 418 to use "AZURE_API_CLIENT_ID" so each test covers its intended failure scenario.

e2e-tests/tests/manual-upload.spec.ts (2)

783-806: Shared testData + nested beforeEach may bite if tests go parallel

The shared mutable testData object and inner beforeEach work as long as tests in this file run serially. If fullyParallel is ever enabled for the file, concurrent tests would race on testData and cleanup calls, and the double authenticateSystemAdmin (outer beforeEach at Line 69 and inner one here) will run twice per test.

Consider:

  • Marking this describe as serial (test.describe.serial(...)) or switching to per‑test fixtures instead of shared testData.
  • Dropping the inner beforeEach and relying on the outer one for authentication to avoid redundant navigation.

808-860: Use polling instead of fixed 2‑second sleeps for notification assertions

Both notification tests rely on a hardcoded setTimeout(…, 2000) before querying for notifications. If notification processing is slower under load, this will cause intermittent flakes even though the system is behaving correctly.

Since you already have DB helpers in notification-helpers.ts, consider introducing a small polling helper (e.g. waitForSubscriptionNotifications(subscriptionId, maxRetries, delayMs)) that repeatedly calls getNotificationsBySubscriptionId until records appear or a timeout is hit, and then use it in both tests instead of the fixed sleep.

For example inside these tests:

-      await new Promise((resolve) => setTimeout(resolve, 2000));
-
-      // Verify notifications were created
-      const myNotifications = await getNotificationsBySubscriptionId(subscription.subscriptionId);
+      const myNotifications = await waitForSubscriptionNotifications(subscription.subscriptionId);

(with waitForSubscriptionNotifications implemented alongside your other helpers). This keeps the tests fast when notifications are quick and more resilient when they are slow.

Also applies to: 900-923

types/notifications-node-client.d.ts (1)

4-29: sendEmail signature and response typing look consistent and practical

The options and response body types for sendEmail are coherent and give good type-safety for common fields while still allowing the caller to pass structured personalisation. This looks fine as-is; if these shapes start being reused elsewhere, you could later extract shared Template/Content interfaces to reduce duplication, but that’s not necessary right now.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f79d49 and 56bbb97.

📒 Files selected for processing (8)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1 hunks)
  • e2e-tests/tests/manual-upload.spec.ts (2 hunks)
  • e2e-tests/utils/notification-helpers.ts (1 hunks)
  • libs/api/src/blob-ingestion/repository/service.ts (3 hunks)
  • libs/api/src/middleware/oauth-middleware.test.ts (4 hunks)
  • libs/api/src/middleware/oauth-middleware.ts (1 hunks)
  • libs/notifications/src/notification/notification-queries.ts (1 hunks)
  • types/notifications-node-client.d.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • libs/api/src/middleware/oauth-middleware.ts
  • libs/api/src/middleware/oauth-middleware.test.ts
  • types/notifications-node-client.d.ts
  • e2e-tests/tests/manual-upload.spec.ts
  • libs/api/src/blob-ingestion/repository/service.ts
  • libs/notifications/src/notification/notification-queries.ts
  • e2e-tests/utils/notification-helpers.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/api/src/middleware/oauth-middleware.ts
  • libs/api/src/middleware/oauth-middleware.test.ts
  • types/notifications-node-client.d.ts
  • e2e-tests/tests/manual-upload.spec.ts
  • libs/api/src/blob-ingestion/repository/service.ts
  • libs/notifications/src/notification/notification-queries.ts
  • e2e-tests/utils/notification-helpers.ts
libs/**/*-middleware.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/api/src/middleware/oauth-middleware.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/api/src/middleware/oauth-middleware.test.ts
  • e2e-tests/tests/manual-upload.spec.ts
🧠 Learnings (1)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Never put sensitive data in logs.

Applied to files:

  • libs/api/src/blob-ingestion/repository/service.ts
🧬 Code graph analysis (4)
e2e-tests/tests/manual-upload.spec.ts (1)
e2e-tests/utils/notification-helpers.ts (6)
  • cleanupTestNotifications (96-105)
  • cleanupTestSubscriptions (107-123)
  • cleanupTestUsers (125-133)
  • createTestUser (19-35)
  • createTestSubscription (37-56)
  • getNotificationsBySubscriptionId (79-84)
libs/api/src/blob-ingestion/repository/service.ts (2)
libs/notifications/src/notification/notification-service.ts (1)
  • sendPublicationNotifications (20-80)
libs/notifications/src/index.ts (1)
  • sendPublicationNotifications (1-1)
libs/notifications/src/notification/notification-queries.ts (1)
e2e-tests/utils/notification-helpers.ts (1)
  • getNotificationsByPublicationId (58-63)
e2e-tests/utils/notification-helpers.ts (2)
libs/notifications/src/notification/notification-queries.ts (1)
  • getNotificationsByPublicationId (61-65)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: E2E Tests
🔇 Additional comments (12)
libs/notifications/src/notification/notification-queries.ts (5)

3-9: LGTM! Past feedback addressed.

The addition of the optional errorMessage field enables efficient single-write audit log creation, eliminating the create-then-update pattern.


11-21: LGTM!

The interface correctly represents the Prisma model with appropriate nullable types for optional fields.


23-35: LGTM!

The create function correctly defaults status to "Pending" and persists the optional errorMessage, enabling efficient single-write audit log creation.


37-53: LGTM!

The update function correctly handles optional parameters. Prisma ignores undefined values in updates, making this pattern safe and idiomatic.


55-59: LGTM!

The query correctly retrieves a notification by Gov.UK Notify ID using a parameterized Prisma query.

libs/api/src/blob-ingestion/repository/service.ts (3)

2-4: LGTM - imports follow guidelines.

The workspace aliases are correctly used per coding guidelines, and the imports are properly structured.


95-116: Fire-and-forget pattern appropriately implemented.

The notification trigger correctly uses a fire-and-forget pattern with .catch(), ensuring that notification failures don't block the ingestion flow. The logging contains only non-PII identifiers.


177-181: Good improvement - PII sanitization now in place.

The email sanitization regex addresses the previous review concern about logging PII. The pattern redacts email addresses while preserving error counts and non-PII diagnostics for debugging.

Based on learnings: "Never put sensitive data in logs."

libs/api/src/middleware/oauth-middleware.test.ts (1)

202-259: Env‑fallback test correctly aligned with AZURE_API_CLIENT_ID

The env‑fallback scenario now matches the implementation:

  • config.get throws, driving the catch‑block path.
  • process.env.AZURE_TENANT_ID and process.env.AZURE_API_CLIENT_ID are set and then cleaned up.
  • This exercises the AZURE_API_CLIENT_ID || AZURE_CLIENT_ID logic while keeping test isolation intact.

Looks good.

e2e-tests/tests/manual-upload.spec.ts (2)

5-13: Notification helpers import is well‑factored

Reusing the shared notification helpers here keeps user/subscription/notification setup and cleanup consistent across E2E tests, and the .js extension on the relative import matches the nodenext module resolution guidelines.


849-859: Notification assertions and cleanup look robust

The tests now assert on real notification records (status "Sent" and non‑empty govNotifyId) and verify that multiple subscribers share the same publicationId, while also tracking that publicationId for cleanup via cleanupTestNotifications. This closes the previous gap where only the UI success panel was checked.

Also applies to: 905-923

types/notifications-node-client.d.ts (1)

30-52: No action needed. The type declaration correctly reflects how this CJS package is used in your ESM codebase. With esModuleInterop: true and module: nodenext in your tsconfig, TypeScript synthesizes a named export from the package's default export, making import { NotifyClient } both type-safe and runtime-safe. The dual export in the .d.ts (named class + default object) appropriately documents both the intended ESM import pattern and the underlying CJS structure.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
libs/api/src/blob-ingestion/repository/service.test.ts (1)

301-465: Notification-flow tests thoroughly cover success/skip/error paths

The notification tests exercise: successful send with location match, no-op when noMatch is true, failure of the notification service while keeping ingestion successful, invalid court_id, missing location, and unknown listTypeId. This gives good confidence that the fire‑and‑forget path won’t break ingestion and that diagnostics are emitted in the right scenarios. If you find the repeated await new Promise((resolve) => setTimeout(resolve, 50)); noisy over time, you could factor it into a small helper (e.g. await waitForAsyncNotifications()) or move to fake timers to make the intent clearer, but that’s optional.

libs/api/src/blob-ingestion/repository/service.ts (1)

142-185: Defensive notification helper and PII-safe logging align with guidelines

triggerPublicationNotifications defensively bails out (with clear logs) on invalid courtId, missing location, or unknown listTypeId, and then logs a concise summary of notification outcomes. The error logging path builds sanitized strings and uses a regex to redact email addresses before logging, so subscriber emails never hit logs while still preserving useful diagnostics like counts and messages; this is in line with the “never put sensitive data in logs” guidance. If you want to further tidy things up, you could extract the email-redaction regex into a named constant or utility for reuse across the codebase, but that’s purely a nicety.

e2e-tests/utils/notification-helpers.ts (3)

59-64: Add explicit return type annotation.

The function returns an implicit any type. For better type safety and developer experience, add an explicit return type (e.g., Promise<NotificationAuditLog[]> or at minimum document the return shape).


66-78: Add explicit return type annotation.

Similar to getNotificationsByPublicationId, this function returns any[] implicitly. Consider adding an explicit return type for better type safety.


108-124: Remove unnecessary cast for subscription operations.

Line 119 uses (prisma as any).subscription.deleteMany, but line 40 uses prisma.subscription.create without a cast. Since subscription is accessible on the typed Prisma client for create operations, it should also work for delete operations. The cast bypasses type safety unnecessarily.

Apply this diff to remove the cast:

-  await (prisma as any).subscription.deleteMany({
+  await prisma.subscription.deleteMany({
     where: {
       subscriptionId: { in: subscriptionIds }
     }
   });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 56bbb97 and 72a0d7b.

📒 Files selected for processing (7)
  • e2e-tests/utils/notification-helpers.ts (1 hunks)
  • libs/api/src/blob-ingestion/repository/service.test.ts (3 hunks)
  • libs/api/src/blob-ingestion/repository/service.ts (3 hunks)
  • libs/api/src/middleware/oauth-middleware.test.ts (9 hunks)
  • libs/notifications/src/govnotify/govnotify-client.test.ts (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.ts (1 hunks)
  • libs/notifications/types/notifications-node-client/index.d.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • libs/api/src/middleware/oauth-middleware.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • libs/api/src/blob-ingestion/repository/service.ts
  • libs/api/src/blob-ingestion/repository/service.test.ts
  • libs/notifications/types/notifications-node-client/index.d.ts
  • libs/notifications/src/govnotify/govnotify-client.ts
  • e2e-tests/utils/notification-helpers.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/api/src/blob-ingestion/repository/service.ts
  • libs/api/src/blob-ingestion/repository/service.test.ts
  • libs/notifications/types/notifications-node-client/index.d.ts
  • libs/notifications/src/govnotify/govnotify-client.ts
  • e2e-tests/utils/notification-helpers.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/api/src/blob-ingestion/repository/service.test.ts
🧠 Learnings (1)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Never put sensitive data in logs.

Applied to files:

  • libs/api/src/blob-ingestion/repository/service.ts
  • libs/api/src/blob-ingestion/repository/service.test.ts
  • libs/notifications/src/govnotify/govnotify-client.ts
🧬 Code graph analysis (3)
libs/api/src/blob-ingestion/repository/service.ts (1)
libs/notifications/src/notification/notification-service.ts (1)
  • sendPublicationNotifications (20-80)
libs/api/src/blob-ingestion/repository/service.test.ts (3)
libs/api/src/blob-ingestion/validation.ts (1)
  • validateBlobRequest (9-158)
libs/api/src/blob-ingestion/repository/service.ts (1)
  • processBlobIngestion (17-140)
libs/api/src/blob-ingestion/repository/queries.ts (1)
  • createIngestionLog (4-16)
e2e-tests/utils/notification-helpers.ts (3)
libs/notifications/src/notification/notification-queries.ts (1)
  • getNotificationsByPublicationId (61-65)
libs/notifications/types/notifications-node-client/index.d.ts (1)
  • NotifyClient (45-49)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (13)
libs/api/src/blob-ingestion/repository/service.test.ts (4)

12-19: Publication Provenance and list-type mocks look consistent

Extending the Provenance mock with SJP/SNL/COMMON_PLATFORM and adding a minimal mockListTypes fixture gives the tests just enough surface to exercise the new notification logic and keep expectations readable; this looks consistent with how the service uses these values.


34-40: Location and notifications mocks are wired correctly for dynamic imports

Mocking @hmcts/location and @hmcts/notifications at the module level and then destructuring them via await import(...) inside the describe keeps the tests aligned with the production import pattern and makes vi.mocked(...) usage type-safe and predictable.

Also applies to: 42-48


220-299: Good coverage for validation and system-error edge cases

The new tests for missing provenance, missing court_id, absent listTypeId after a “successful” validation, and non-Error exceptions during artefact creation align tightly with the service behavior and give solid regression protection for these failure paths.


467-515: PII-safe notification error logging is verified correctly

The final test asserts that the notification summary log omits per-user details and that error logs contain redacted email addresses via the [REDACTED_EMAIL] placeholder, matching the sanitization behavior in the service. This directly enforces the “no sensitive data in logs” guideline at test level, which is a nice improvement over earlier behavior. Based on learnings, this now aligns with the privacy logging rule.

libs/api/src/blob-ingestion/repository/service.ts (1)

2-4: Non-blocking notification trigger is correctly isolated from ingestion success

Importing getLocationById/sendPublicationNotifications and then invoking triggerPublicationNotifications only when noMatch is false, without awaiting it, keeps the main ingestion response path independent of notification latency or failures. The attached .catch ensures any rejection from the helper is logged with artefact and court context but doesn’t leak raw error objects or affect the ingestion result.

Also applies to: 95-117

libs/notifications/src/govnotify/govnotify-client.ts (1)

19-30: LGTM! Retry logic is now correct.

The refactored implementation properly enables retries: sendEmailInternal now throws on failure (line 73), allowing retryWithBackoff to retry the operation. The outer try-catch converts the final thrown error into a { success: false, error } result for the caller.

libs/notifications/types/notifications-node-client/index.d.ts (3)

2-16: LGTM!

The NotificationBody interface correctly models the GOV.UK Notify email response structure.


18-20: LGTM! EmailResponse interface is now correct.

The interface correctly defines the GOV.UK Notify response structure with a single required body property, addressing the previous issue with redundant and incorrect properties.


45-49: LGTM!

The NotifyClient class definition correctly includes all necessary methods including getNotificationById, which addresses the previous concern.

e2e-tests/utils/notification-helpers.ts (4)

1-17: LGTM!

Imports follow the coding guidelines, using workspace aliases (@hmcts/*) and proper module resolution. Interface definitions use correct PascalCase naming.


19-35: LGTM!

The function correctly creates a test user with proper type safety using the typed Prisma client.


37-57: LGTM! Field name corrected.

The function now correctly uses dateAdded field and maintains type safety without requiring a cast, addressing the previous review concern.


87-95: LGTM!

The function correctly uses the getNotificationById method (now properly defined in the type definitions) and includes appropriate validation for the required API key.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
libs/notifications/src/govnotify/govnotify-client.ts (2)

4-5: Guard against invalid retry configuration to avoid unbounded retries

If NOTIFICATION_RETRY_ATTEMPTS or NOTIFICATION_RETRY_DELAY_MS are set to non‑numeric or negative values, retries/delay can become NaN or stay negative. With the current if (retries === 0) check, this means the base case is never hit and you can end up with effectively unbounded retries / recursion with 0ms delay.

It’s safer to sanitise the env values and tighten the stop condition, e.g.:

-const NOTIFICATION_RETRY_ATTEMPTS = Number.parseInt(process.env.NOTIFICATION_RETRY_ATTEMPTS || "1", 10);
-const NOTIFICATION_RETRY_DELAY_MS = Number.parseInt(process.env.NOTIFICATION_RETRY_DELAY_MS || "1000", 10);
+const NOTIFICATION_RETRY_ATTEMPTS = (() => {
+  const parsed = Number.parseInt(process.env.NOTIFICATION_RETRY_ATTEMPTS ?? "", 10);
+  return Number.isNaN(parsed) || parsed < 0 ? 1 : parsed;
+})();
+
+const NOTIFICATION_RETRY_DELAY_MS = (() => {
+  const parsed = Number.parseInt(process.env.NOTIFICATION_RETRY_DELAY_MS ?? "", 10);
+  return Number.isNaN(parsed) || parsed < 0 ? 1000 : parsed;
+})();
@@
-  } catch (error) {
-    if (retries === 0) {
+  } catch (error) {
+    if (retries <= 0) {
       throw error;
     }

Also applies to: 96-106


55-60: Strip PII and raw payloads from logs to comply with logging guidelines

Current logging includes emailAddress, full templateParameters, and error.response?.data / response.body, which can contain PII and other sensitive data. This conflicts with the “never put sensitive data in logs” guideline for TypeScript files. Consider logging only non‑identifying metadata and avoiding raw Notify payloads, e.g.:

-    console.log("[govnotify-client] Sending email:", {
-      templateId,
-      emailAddress: params.emailAddress,
-      templateParameters: params.templateParameters
-    });
+    console.log("[govnotify-client] Sending GOV.UK Notify email", {
+      templateId
+    });
@@
-    console.log("[govnotify-client] Response keys:", Object.keys(response || {}));
-    console.log("[govnotify-client] Response.body:", response?.body);
+    console.log("[govnotify-client] GOV.UK Notify sendEmail call succeeded");
@@
-    console.error("[govnotify-client] Error details:", {
-      message: error.message,
-      response: error.response?.data,
-      status: error.response?.status
-    });
+    console.error("[govnotify-client] Error when sending GOV.UK Notify email", {
+      message: error?.message,
+      status: error?.response?.status
+    });

You can then optionally log notificationId in a separate success log if that’s acceptable. As per coding guidelines / learnings, this keeps logs free of email addresses and template personalisation.

Also applies to: 66-68, 72-75, 77-88, 90-92

🧹 Nitpick comments (1)
libs/notifications/src/govnotify/govnotify-client.ts (1)

7-25: Clean up unused local types and align interface placement with module-order guideline

NotificationBody and EmailResponse are declared but not referenced in this file, and their shapes duplicate what’s already defined in the notifications-node-client typings. They can be safely removed to avoid a second source of truth.

Also, the coding guidelines state that interfaces/types should live at the bottom of the module. You may want to move SendEmailParams and SendEmailResult below the functions to match that convention.

This is purely a tidiness/consistency change, but it will make the file easier to maintain.

Also applies to: 27-36

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72a0d7b and b1b3958.

📒 Files selected for processing (1)
  • libs/notifications/src/govnotify/govnotify-client.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: TypeScript variables must use camelCase (e.g., userId, caseDetails, documentId). Booleans should use is/has/can prefix (e.g., isActive, hasAccess, canEdit).
Classes and interfaces must use PascalCase (e.g., UserService, CaseRepository). DO NOT use I prefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g., MAX_FILE_SIZE, DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add .js extension to relative imports (e.g., import { foo } from "./bar.js"). This is required for ESM with Node.js "nodenext" module resolution, even when importing TypeScript files.
Use workspace aliases (@hmcts/*) for imports instead of relative paths across packages.
Database queries must be parameterized using Prisma (never raw SQL with string concatenation).
Never put sensitive data in logs.
Don't add comments unless meaningful - explain why something is done, not what is done.
Favour functional style - use simple functions. Don't use a class unless you have shared state.
Data should be immutable by default - use const and avoid mutations to ensure predictable state.
Functions should have no side effects - avoid modifying external state or relying on mutable data.

Files:

  • libs/notifications/src/govnotify/govnotify-client.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/src/govnotify/govnotify-client.ts
🧠 Learnings (1)
📚 Learning: 2025-12-01T11:31:12.342Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-01T11:31:12.342Z
Learning: Applies to **/*.{ts,tsx} : Never put sensitive data in logs.

Applied to files:

  • libs/notifications/src/govnotify/govnotify-client.ts
🧬 Code graph analysis (1)
libs/notifications/src/govnotify/govnotify-client.ts (3)
libs/notifications/types/notifications-node-client/index.d.ts (3)
  • NotificationBody (2-16)
  • EmailResponse (18-20)
  • NotifyClient (45-49)
libs/notifications/src/govnotify/template-config.ts (3)
  • TemplateParameters (32-39)
  • getApiKey (12-17)
  • getTemplateId (5-10)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-48)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
libs/notifications/src/govnotify/govnotify-client.test.ts (1)

176-180: Reduce use of any types.

The setTimeout mock uses any for the callback parameter and return type. While this is a test mock, you can be more specific about types.

-    vi.spyOn(global, "setTimeout").mockImplementation((callback: any, delay?: number) => {
+    vi.spyOn(global, "setTimeout").mockImplementation((callback: () => void, delay?: number) => {
       if (delay !== undefined) {
         delays.push(delay);
       }
-      return originalSetTimeout(callback, 0) as any;
+      return originalSetTimeout(callback, 0) as NodeJS.Timeout;
     });
e2e-tests/utils/notification-helpers.ts (1)

59-88: Tighten typings and avoid any / (prisma as any) in notification helpers.

The helpers currently bypass type safety:

  • getNotificationsByPublicationId / getNotificationsBySubscriptionId use (prisma as any).
  • waitForNotifications returns Promise<any[]>.
  • getGovNotifyEmail returns an untyped any.

As per coding guidelines, avoiding any where possible will help catch schema or API changes early. Consider:

  • Returning a concrete type (e.g. Prisma’s NotificationAuditLog[] or a local interface) from the getNotifications* and waitForNotifications functions.
  • Using typed prisma.notificationAuditLog / prisma.subscription instead of (prisma as any).
  • Giving getGovNotifyEmail a proper return type that reflects the Notify payload you assert on in tests.

This keeps the test utilities aligned with strict TypeScript and makes refactors in the notifications schema safer.

Also applies to: 90-123, 125-152

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aafca23 and 9fe6e4a.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (7)
  • e2e-tests/package.json (1 hunks)
  • e2e-tests/playwright.config.ts (1 hunks)
  • e2e-tests/run-with-credentials.js (2 hunks)
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1 hunks)
  • e2e-tests/utils/notification-helpers.ts (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.test.ts (1 hunks)
  • libs/notifications/src/govnotify/govnotify-client.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • libs/notifications/src/govnotify/govnotify-client.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/package.json

📄 CodeRabbit inference engine (CLAUDE.md)

**/package.json: Package names must use @hmcts scope (e.g., @hmcts/auth, @hmcts/case-management).
Package.json must include "type": "module" and exports field with proper ESM paths.
Pin all dependency versions to specific versions (e.g., "express": "5.1.0"), except for peer dependencies.
All test packages must use "test": "vitest run" script. Tests should achieve >80% coverage on business logic.

Files:

  • e2e-tests/package.json
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/playwright.config.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
  • e2e-tests/utils/notification-helpers.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/playwright.config.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
  • e2e-tests/run-with-credentials.js
  • e2e-tests/utils/notification-helpers.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/playwright.config.ts
  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
  • e2e-tests/utils/notification-helpers.ts
**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/src/govnotify/govnotify-client.test.ts
e2e-tests/**/*.spec.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
🧠 Learnings (3)
📚 Learning: 2025-12-03T13:55:34.675Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.675Z
Learning: Applies to **/*.test.ts : Unit/integration test files must be co-located with source files as `*.test.ts` and use Vitest with `describe`, `it`, and `expect`.

Applied to files:

  • libs/notifications/src/govnotify/govnotify-client.test.ts
📚 Learning: 2025-12-03T13:55:34.675Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.675Z
Learning: Applies to e2e-tests/**/*.spec.ts : E2E test files must be in `e2e-tests/` directory named `*.spec.ts`, use Playwright, include complete user journeys with validations, Welsh translations, accessibility checks, and keyboard navigation all within a single test.

Applied to files:

  • libs/notifications/src/govnotify/govnotify-client.test.ts
  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
📚 Learning: 2025-12-03T13:55:34.675Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.675Z
Learning: Applies to **/*.{ts,tsx} : Constants must use SCREAMING_SNAKE_CASE (e.g., `MAX_FILE_SIZE`, `DEFAULT_TIMEOUT`).

Applied to files:

  • e2e-tests/tests/api/blob-ingestion-notifications.spec.ts
🧬 Code graph analysis (3)
libs/notifications/src/govnotify/govnotify-client.test.ts (3)
libs/notifications/types/notifications-node-client/index.d.ts (1)
  • NotifyClient (45-49)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-48)
libs/notifications/src/govnotify/govnotify-client.ts (1)
  • sendEmail (43-54)
e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (3)
e2e-tests/utils/notification-helpers.ts (8)
  • cleanupTestNotifications (125-134)
  • cleanupTestSubscriptions (136-152)
  • cleanupTestUsers (154-162)
  • createTestUser (19-35)
  • createTestSubscription (37-57)
  • waitForNotifications (66-88)
  • getGovNotifyEmail (97-123)
  • getNotificationsByPublicationId (59-64)
e2e-tests/utils/api-auth-helpers.ts (1)
  • getApiAuthToken (20-70)
libs/notifications/src/notification/notification-queries.ts (1)
  • getNotificationsByPublicationId (61-65)
e2e-tests/utils/notification-helpers.ts (3)
libs/notifications/src/notification/notification-queries.ts (1)
  • getNotificationsByPublicationId (61-65)
libs/notifications/types/notifications-node-client/index.d.ts (1)
  • NotifyClient (45-49)
types/notifications-node-client.d.ts (1)
  • NotifyClient (2-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (8)
libs/notifications/src/govnotify/govnotify-client.test.ts (4)

41-66: LGTM!

The test correctly validates the happy path: dynamic import, proper parameter passing, and assertion of success result with notification ID.


68-87: LGTM!

The error handling test correctly validates failure scenarios and error message formatting.


89-147: LGTM!

The retry tests correctly validate both successful retry after failure and exhausted retry attempts, including proper call count verification.


149-168: LGTM!

The test correctly validates handling of non-Error exceptions, ensuring robust error handling.

e2e-tests/playwright.config.ts (1)

37-42: GOV.UK Notify env wiring into the dev server looks good.

Injecting GOVUK_NOTIFY_API_KEY and GOVUK_NOTIFY_TEMPLATE_ID_SUBSCRIPTION into the webServer.command for both CI and local aligns with the new notification tests and keeps the pattern consistent with the existing SSO/CFT flags. No further changes needed here.

e2e-tests/run-with-credentials.js (1)

6-15: Dotenv + Azure credential mappings integrate cleanly with existing flow.

Loading ../.env via dotenv in ESM and extending SECRET_MAPPINGS to populate AZURE_TENANT_ID, AZURE_API_CLIENT_ID, and AZURE_API_CLIENT_SECRET fits well with getApiAuthToken and the non‑CI workflow. This should make local notification E2Es much easier to run without extra manual setup.

Also applies to: 32-34

e2e-tests/package.json (1)

21-22: Pin dependency versions in e2e-tests/package.json.

Using "*" for @hmcts/postgres and "^17.2.3" for dotenv violates the requirement to pin dependency versions and can make e2e runs non-reproducible as upstream packages change. Replace both with concrete versions (no range specifiers):

-    "@hmcts/postgres": "*",
-    "dotenv": "^17.2.3"
+    "@hmcts/postgres": "X.Y.Z",
+    "dotenv": "17.2.3"
e2e-tests/tests/api/blob-ingestion-notifications.spec.ts (1)

47-66: Run this notification suite in serial mode to avoid shared‑state flakiness.

With fullyParallel: true in the Playwright config, tests in the same file can execute in parallel. This suite shares mutable testData and performs DB cleanup in test.afterEach, so one test can delete another test's users/subscriptions/notifications while it is still running. Switch this block to serial mode:

-test.describe("Blob Ingestion - Notification E2E Tests", () => {
+test.describe.serial("Blob Ingestion - Notification E2E Tests", () => {

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
libs/notifications/src/govnotify/govnotify-client.ts (1)

67-69: Replace custom type assertion with proper AxiosResponse<T> from the axios library.

The notifications-node-client v8.2.1 uses axios under the hood and returns standard Axios responses. Rather than a custom AxiosEmailResponse interface with as unknown as AxiosEmailResponse assertion, import and use AxiosResponse<T> directly:

import { AxiosResponse } from 'axios';

const response: AxiosResponse<{ id: string }> = await notifyClient.sendEmail(
  templateId,
  params.emailAddress,
  { personalisation: params.templateParameters }
);
const notificationId = response.data.id;

This eliminates the double assertion, removes unnecessary any types (config, request), and provides proper type safety for the response structure.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fe6e4a and 61ad3b3.

📒 Files selected for processing (1)
  • libs/notifications/src/govnotify/govnotify-client.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/src/govnotify/govnotify-client.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/src/govnotify/govnotify-client.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/src/govnotify/govnotify-client.ts
🧠 Learnings (1)
📚 Learning: 2025-12-03T13:55:34.675Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.675Z
Learning: Applies to **/src/routes/**/*.ts : Validate all user inputs on endpoints. Use parameterized database queries (Prisma). Never include sensitive data in logs.

Applied to files:

  • libs/notifications/src/govnotify/govnotify-client.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests

No need of this file in Github. commit by mistake
@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 3, 2025

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
libs/notifications/src/config.test.ts (1)

25-27: Make the “ends with prisma” assertion OS‑agnostic

expect(prismaSchemas).toMatch(/\/prisma$/); assumes POSIX-style / separators and will fail on Windows, where paths end with \\prisma.

You can assert the folder name instead of raw string shape to keep the test portable:

-    it("should resolve to a path ending with /prisma", () => {
-      expect(prismaSchemas).toMatch(/\/prisma$/);
-    });
+    it("should resolve to a path ending with prisma", () => {
+      expect(path.basename(prismaSchemas)).toBe("prisma");
+    });

This still validates the intent without depending on platform-specific separators.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61ad3b3 and ce6dbca.

📒 Files selected for processing (3)
  • libs/notifications/src/config.test.ts (1 hunks)
  • libs/notifications/types/email/index.d.ts (1 hunks)
  • tsconfig.json (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/src/config.test.ts
  • libs/notifications/types/email/index.d.ts
**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/src/config.test.ts
**/*.{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • libs/notifications/src/config.test.ts
  • libs/notifications/types/email/index.d.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

Applied to files:

  • libs/notifications/src/config.test.ts
  • tsconfig.json
📚 Learning: 2025-12-03T13:55:34.675Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.675Z
Learning: Applies to **/*.test.ts : Unit/integration test files must be co-located with source files as `*.test.ts` and use Vitest with `describe`, `it`, and `expect`.

Applied to files:

  • libs/notifications/src/config.test.ts
📚 Learning: 2025-12-03T13:55:34.675Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.675Z
Learning: Applies to e2e-tests/**/*.spec.ts : E2E test files must be in `e2e-tests/` directory named `*.spec.ts`, use Playwright, include complete user journeys with validations, Welsh translations, accessibility checks, and keyboard navigation all within a single test.

Applied to files:

  • libs/notifications/src/config.test.ts
📚 Learning: 2025-12-03T13:55:34.675Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.675Z
Learning: Applies to **/package.json : All test packages must use `"test": "vitest run"` script. Tests should achieve >80% coverage on business logic.

Applied to files:

  • libs/notifications/src/config.test.ts
📚 Learning: 2025-12-03T13:55:34.675Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.675Z
Learning: Applies to **/*.{ts,tsx} : Use workspace aliases (`hmcts/*`) for imports between packages instead of relative paths.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-03T13:55:34.675Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.675Z
Learning: Applies to **/*.ts : Do not create generic `types.ts` files. Colocate types with the appropriate code file where they are used.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-03T13:55:34.675Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.675Z
Learning: Applies to **/*.{ts,tsx} : TypeScript strict mode must be enabled. No `any` type without justification. Use explicit types for all variables and function parameters.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-03T13:55:34.675Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.675Z
Learning: Applies to **/*.{ts,tsx} : Always add `.js` extension to relative imports (e.g., `import { foo } from "./bar.js"`), even when importing TypeScript files. This is required for ESM with Node.js 'nodenext' module resolution.

Applied to files:

  • tsconfig.json
📚 Learning: 2025-12-03T13:55:34.675Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-03T13:55:34.675Z
Learning: Applies to **/package.json : Package.json must include `"type": "module"` and exports field with proper ESM paths.

Applied to files:

  • tsconfig.json
🧬 Code graph analysis (1)
libs/notifications/src/config.test.ts (1)
e2e-tests/run-with-credentials.js (2)
  • __filename (10-10)
  • __dirname (11-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: SonarQube Analysis
  • GitHub Check: E2E Tests
🔇 Additional comments (5)
libs/notifications/src/config.test.ts (1)

1-24: Overall test structure and config import look good

  • Vitest usage with describe/it/expect and co-location next to config.ts align with the project testing guidelines.
  • Deriving __filename/__dirname via fileURLToPath(import.meta.url) matches the existing pattern in e2e-tests/run-with-credentials.js and avoids relying on CJS globals.
  • Importing prismaSchemas from "./config.js" with the .js extension is consistent with the ESM + nodenext convention.
    As per coding guidelines and retrieved learnings, this setup is correct and consistent.
tsconfig.json (1)

6-6: The typeRoots path is properly configured and the type declarations directory exists.

The ./libs/notifications/types directory is correctly set up with type declarations at libs/notifications/types/email/index.d.ts. The path alias @hmcts/notificationslibs/notifications/src is also properly configured in the paths section. No additional action required.

libs/notifications/types/email/index.d.ts (3)

1-16: LGTM!

The NotificationBody interface is well-structured with explicit types, proper nullability handling, and follows naming conventions.


18-20: LGTM!

The EmailResponse interface correctly wraps NotificationBody.


45-49: LGTM!

The NotifyClient class is well-typed with explicit parameter and return types. Method signatures correctly reference the defined interfaces.

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.

1 participant