VIBE-221 Subscription Fulfilment Email - #155
Conversation
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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
…s://github.com/hmcts/cath-service into feature/VIBE-221-subscription-fulfilment-email
The remote branch uses 'notifications' (plural) with better structure. Removed the local 'notification' directory to avoid confusion.
WalkthroughAdds a notifications library and end-to-end notification flow: DB migrations and Prisma model for notification audit logs, GOV.UK Notify client and template config, subscription queries and orchestration, triggers from ingestion/manual upload, tests, E2E helpers, TypeScript path/types, CI/env updates, and documentation. Changes
Sequence Diagram(s)sequenceDiagram
participant BlobSvc as Blob Ingestion
participant NotifSvc as Notification Service
participant SubQuery as Subscription Queries
participant AuditDB as Notification Audit Log (DB)
participant EmailClient as GOV.UK Notify
BlobSvc->>BlobSvc: create artefact
BlobSvc->>NotifSvc: sendPublicationNotifications(publicationId, locationId, ...)
NotifSvc->>SubQuery: findActiveSubscriptionsByLocation(locationId)
SubQuery-->>NotifSvc: subscriptions[]
loop per subscriber
NotifSvc->>AuditDB: createNotificationAuditLog(Pending)
NotifSvc->>NotifSvc: validate email & build template params
alt valid email
NotifSvc->>EmailClient: sendEmail(params)
EmailClient-->>NotifSvc: success / failure
alt success
NotifSvc->>AuditDB: updateNotificationStatus(Sent, govNotifyId, sentAt)
else failure
NotifSvc->>AuditDB: updateNotificationStatus(Failed, errorMessage)
end
else invalid/missing
NotifSvc->>AuditDB: updateNotificationStatus(Skipped, reason)
end
end
NotifSvc-->>BlobSvc: aggregated result (total, sent, failed, skipped)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used📓 Path-based instructions (5)**/*.{ts,tsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.{ts,tsx,js}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.{test,spec}.ts📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.ts📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.{ts,tsx,js,mjs}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
🧬 Code graph analysis (1)libs/subscriptions/src/repository/queries.test.ts (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)
🔇 Additional comments (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🎭 Playwright E2E Test Results196 tests 196 ✅ 17m 43s ⏱️ Results for commit 04ddf7d. ♻️ This comment has been updated with latest results. |
Updated admin-pages manual-upload-summary to use the correct package name.
There was a problem hiding this comment.
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/typesin the global typeRoots, the sibling modulelibs/notification/tsconfig.jsondeclares 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_logtable structure is well-designed with appropriate indexes for common query patterns. The indexes onuser_id,publication_id,subscription_id, andcreated_atwill support efficient lookups.Minor observation: Consider whether
location_idshould 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 wordingThe 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 detailA few points around this notification block:
Not truly fire-and-forget
- Because
sendPublicationNotificationsis 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.
Log contents and PII
console.logof the fullnotificationResultobject andconsole.errorwith the rawnotificationErrorrisk 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/messageplus 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.
listTypeName field
- Here you use
listType?.name || \LIST_TYPE_${listTypeId}`whereas the rest of the file usesenglishFriendlyName/welshFriendlyName. Please double‑check thatmockListTypesactually 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 Schemaand 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 assertionThe mocks and expectations line up with the implementation:
createNotificationAuditLogreturns the mocked record and defaults status to "Pending".updateNotificationStatusandfindExistingNotificationassertions match the Prisma calls, including the compositeuserId_publicationIdkey anderrorMessage: 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(ormd) 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 assertionsThe 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
sendEmailrejects or returns a failure, assertingfailedanderrorsare populated andupdateNotificationStatusis called appropriately.- Optionally assert that
findActiveSubscriptionsByLocation/createNotificationAuditLogare 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 valuesThe email validator and
validatePublicationEventcover the basic required fields and are consistent with how the service uses them. If you expectpublicationDateto 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 statusThe Prisma query and returned
SubscriptionWithUsershape look correct and comply with the parameterisation guidelines. As the user model matures, you may want to extend thewhereclause to filter on flags likeemailVerified/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
Stringfor 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
NotifyClientis 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
createNotificationAuditLogto 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
CreateNotificationDatainterface to include an optionalerrorMessagefield.libs/notification/src/repository/queries.ts (3)
4-10: Export the interface for consumer type safety.
CreateNotificationLogParamsis 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") whilenotification-queries.tsuses 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
findNotificationLogsByPublicationIdandfindNotificationLogsByUserIdrely 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
NotificationLoginterface matching the Prisma model.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
docs/VIBE-221/email notification mock up.pngis excluded by!**/*.pngdocs/VIBE-221/email notification template .pngis excluded by!**/*.pngyarn.lockis 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 useis/has/canprefix (e.g.,isActive,hasAccess,canEdit).
Classes and interfaces must use PascalCase (e.g.,UserService,CaseRepository). DO NOT useIprefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g.,MAX_FILE_SIZE,DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add.jsextension 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.tslibs/account/src/repository/query.tslibs/notification/src/index.tslibs/notifications/src/notification/validation.test.tslibs/subscriptions/src/repository/queries.tslibs/notifications/src/notification/notification-service.test.tslibs/notification/src/config.tslibs/notifications/src/config.tslibs/account/src/repository/query.test.tslibs/notifications/src/notifications-node-client.d.tslibs/notifications/src/notification/validation.tslibs/notifications/src/notification/notification-queries.tslibs/notification/src/notification-service.test.tslibs/notifications/src/govnotify/template-config.tslibs/notification/src/repository/queries.test.tslibs/notification/src/notification-service.tslibs/notifications/src/govnotify/govnotify-client.tslibs/notifications/src/notification/subscription-queries.test.tslibs/notifications/types/notifications-node-client.d.tslibs/notification/src/notifications-node-client.d.tslibs/notifications/src/notification/notification-service.tslibs/notifications/src/notification/notification-queries.test.tslibs/notification/types/notifications-node-client.d.tslibs/notifications/src/govnotify/govnotify-client.test.tstypes/notifications-node-client.d.tslibs/notifications/src/notification/subscription-queries.tslibs/admin-pages/src/pages/manual-upload-summary/index.tslibs/notification/src/repository/queries.tsapps/postgres/src/schema-discovery.test.tslibs/notifications/src/index.tslibs/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.tslibs/account/src/repository/query.tslibs/notification/src/index.tslibs/notifications/src/notification/validation.test.tslibs/subscriptions/src/repository/queries.tslibs/notifications/src/notification/notification-service.test.tslibs/notification/src/config.tslibs/notifications/src/config.tslibs/account/src/repository/query.test.tslibs/notifications/src/notifications-node-client.d.tslibs/notifications/src/notification/validation.tslibs/notifications/src/notification/notification-queries.tslibs/notification/src/notification-service.test.tslibs/notifications/src/govnotify/template-config.tslibs/notification/src/repository/queries.test.tslibs/notification/src/notification-service.tslibs/notifications/src/govnotify/govnotify-client.tslibs/notifications/src/notification/subscription-queries.test.tslibs/notifications/types/notifications-node-client.d.tslibs/notification/src/notifications-node-client.d.tslibs/notifications/src/notification/notification-service.tslibs/notifications/src/notification/notification-queries.test.tslibs/notification/types/notifications-node-client.d.tslibs/notifications/src/govnotify/govnotify-client.test.tstypes/notifications-node-client.d.tslibs/notifications/src/notification/subscription-queries.tslibs/admin-pages/src/pages/manual-upload-summary/index.tslibs/notification/src/repository/queries.tsapps/postgres/src/schema-discovery.test.tslibs/notifications/src/index.tslibs/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.tsor*.spec.tsand use Vitest.
Files:
libs/notifications/src/notification/validation.test.tslibs/notifications/src/notification/notification-service.test.tslibs/account/src/repository/query.test.tslibs/notification/src/notification-service.test.tslibs/notification/src/repository/queries.test.tslibs/notifications/src/notification/subscription-queries.test.tslibs/notifications/src/notification/notification-queries.test.tslibs/notifications/src/govnotify/govnotify-client.test.tsapps/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.tsfile to avoid circular dependencies during Prisma client generation. Apps import config using the/configpath.
Files:
libs/notification/src/config.tslibs/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.jsonlibs/notification/tsconfig.json
**/tsconfig.json
📄 CodeRabbit inference engine (CLAUDE.md)
TypeScript must use strict mode with no
anywithout justification.
Files:
libs/notifications/tsconfig.jsonlibs/notification/tsconfig.jsontsconfig.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 includebuild:nunjucksif the module contains Nunjucks templates in thepages/directory.
All test packages must use"test": "vitest run"script to run tests.
Files:
libs/notification/package.jsonlibs/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@@mapand@mapfor aliases.
Files:
libs/notifications/prisma/schema.prismalibs/notification/prisma/schema.prismalibs/subscriptions/prisma/schema.prisma
**/pages/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/pages/**/*.ts: Page controllers must export namedGETand/orPOSTfunctions with Express Request and Response types.
Every page must support both English and Welsh with separateenandcycontent objects in the controller, and Welsh content must be tested with?lng=cyquery 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.tslibs/notification/src/config.tslibs/notifications/tsconfig.jsonlibs/notifications/src/config.tslibs/notifications/src/govnotify/template-config.tslibs/notification/tsconfig.jsontsconfig.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.tslibs/notifications/tsconfig.jsonlibs/account/src/repository/query.test.tslibs/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.tstsconfig.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.tslibs/notifications/tsconfig.jsonlibs/notification/tsconfig.jsontsconfig.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.jsonlibs/notification/tsconfig.jsontsconfig.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.jsonlibs/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.jsonlibs/notification/tsconfig.jsontsconfig.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.jsonlibs/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.jsonlibs/notifications/package.jsontsconfig.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.jsonlibs/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
There was a problem hiding this comment.
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
retryWithBackoffimplementation)- 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
📒 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 useis/has/canprefix (e.g.,isActive,hasAccess,canEdit).
Classes and interfaces must use PascalCase (e.g.,UserService,CaseRepository). DO NOT useIprefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g.,MAX_FILE_SIZE,DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add.jsextension 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.tslibs/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.tslibs/notifications/src/govnotify/govnotify-client.test.ts
**/pages/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/pages/**/*.ts: Page controllers must export namedGETand/orPOSTfunctions with Express Request and Response types.
Every page must support both English and Welsh with separateenandcycontent objects in the controller, and Welsh content must be tested with?lng=cyquery 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.tsor*.spec.tsand 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)
There was a problem hiding this comment.
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()orvi.waitUntil()with proper async conditionsExample 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
getNotificationsByPublicationIdto 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
findExistingNotificationthrows, the test correctly expectsfailed: 1in the results. However, if the exception occurs aftercreateNotificationAuditLogsucceeds (e.g., during email sending), the audit log may remain in "Pending" status. Consider adding a test case that verifiesupdateNotificationStatusis 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
setTimeoutdelays (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
Stringfor 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 extentCreateNotificationData) mirror the Prisma model fields. This will drift if the schema changes (new/renamed columns, nullability tweaks). If@hmcts/postgresexposes the generated Prisma types, consider aliasing those here and derivingCreateNotificationDatafrom them (e.g.Pick<...>), so the compiler catches schema changes.
22-33: Makestatusa well-defined domain type and avoid magic stringsRight now
statusis an untypedstringwith a hard‑coded"Pending"default. It would be safer to introduce a small status domain (string literal union or enum, plusSTATUS_PENDINGconstant) 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 forgovNotifyIdlookupsIf
govNotifyIdis guaranteed unique at the DB level, preferfindUniqueoverfindFirstto encode that invariant and fail fast if it is ever violated. If it is not unique by design, consider adding an explicitorderByhere so the chosen record is deterministic (e.g. most recent bycreatedAt), and document that behaviour in the caller.
72-75: Align publication lookups with deterministic ordering / reuse existing helper
getNotificationsByPublicationIdreturns results without anorderBy, while the e2e helper ate2e-tests/utils/notification-helpers.tsorders bycreatedAt: "asc". For consistency and to avoid tests or callers depending on implicit DB ordering, consider adding the sameorderByhere 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
📒 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 useis/has/canprefix (e.g.,isActive,hasAccess,canEdit).
Classes and interfaces must use PascalCase (e.g.,UserService,CaseRepository). DO NOT useIprefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g.,MAX_FILE_SIZE,DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add.jsextension 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.tse2e-tests/tests/manual-upload.spec.tse2e-tests/utils/api-auth-helpers.tslibs/notifications/src/notification/notification-queries.tse2e-tests/tests/api/blob-ingestion-notifications.spec.tslibs/notifications/src/notification/notification-service.tslibs/notifications/src/notification/notification-service.test.tse2e-tests/utils/notification-helpers.tslibs/admin-pages/src/pages/manual-upload-summary/index.test.tslibs/notifications/src/govnotify/govnotify-client.test.tslibs/api/src/blob-ingestion/repository/service.test.tstypes/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.tse2e-tests/tests/manual-upload.spec.tse2e-tests/utils/api-auth-helpers.tslibs/notifications/src/notification/notification-queries.tse2e-tests/tests/api/blob-ingestion-notifications.spec.tslibs/notifications/src/notification/notification-service.tslibs/notifications/src/notification/notification-service.test.tse2e-tests/utils/notification-helpers.tslibs/admin-pages/src/pages/manual-upload-summary/index.test.tse2e-tests/run-with-credentials.jslibs/notifications/src/govnotify/govnotify-client.test.tslibs/api/src/blob-ingestion/repository/service.test.tstypes/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.tsor*.spec.tsand use Vitest.
Files:
libs/subscriptions/src/repository/queries.test.tse2e-tests/tests/manual-upload.spec.tse2e-tests/tests/api/blob-ingestion-notifications.spec.tslibs/notifications/src/notification/notification-service.test.tslibs/admin-pages/src/pages/manual-upload-summary/index.test.tslibs/notifications/src/govnotify/govnotify-client.test.tslibs/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@@mapand@mapfor aliases.
Files:
libs/notifications/prisma/schema.prisma
**/pages/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/pages/**/*.ts: Page controllers must export namedGETand/orPOSTfunctions with Express Request and Response types.
Every page must support both English and Welsh with separateenandcycontent objects in the controller, and Welsh content must be tested with?lng=cyquery 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
sendPublicationNotificationsis called with the expected payload after a successful upload. The assertions onpublicationId,locationId,locationName,hearingListName, andpublicationDateprovide comprehensive validation.
604-635: Resilience testing for missing location is well-implemented.The test correctly verifies that the upload succeeds even when
getLocationByIdreturnsnull, and that notifications are skipped with an appropriate warning log. The console spy cleanup withmockRestore()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 missingfileName(defaults totrue). 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.tsrequirements 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
findSubscriptionsByLocationIdpasses 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 EXISTSchecks for both the column addition and index creation, ensuring it can be safely re-run. Thegov_notify_idcolumn 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
testDataarrays 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 usingvi.resetModules()inbeforeEachto 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
/.defaultscope 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. TheclearCachedToken()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.mockat the module level followed byvi.mockedto 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.allSettledto 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
@mapand@@mapto maintain snake_case in the database while using camelCase in the Prisma client. The indexes onpublicationId,status, andgovNotifyIdsupport the query patterns innotification-queries.ts.
22-22: No action needed. The Subscription model is properly defined inlibs/subscriptions/prisma/schema.prismaand 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.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (4)
e2e-tests/utils/notification-helpers.ts (1)
65-73: VerifygetNotificationByIdtype definition exists.The code calls
notifyClient.getNotificationById(notificationId)which may not be properly typed intypes/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
updateNotificationStatuswith 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
errorMessagefield to theCreateNotificationDatainterface innotification-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
createNotificationAuditLogsucceeds (line 116) but a subsequent exception occurs beforeupdateNotificationStatus, 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: BroadensendEmailoptions type to cover full GOV.UK Notify API.Right now the
optionsparameter only exposespersonalisation, but Notify also supportsreferenceandemailReplyToIdon 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-clientversion 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_IDis sourced fromGOVUK_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.createwas 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 includeserrorMessageandgovNotifyIdto 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
📒 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 useis/has/canprefix (e.g.,isActive,hasAccess,canEdit).
Classes and interfaces must use PascalCase (e.g.,UserService,CaseRepository). DO NOT useIprefix for interfaces.
Constants must use SCREAMING_SNAKE_CASE (e.g.,MAX_FILE_SIZE,DEFAULT_TIMEOUT).
Module ordering: constants at the top, exported functions next, other functions ordered by usage, interfaces and types at the bottom.
Always add.jsextension 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.tslibs/notifications/src/notification/notification-queries.test.tslibs/notifications/src/notification/notification-queries.tslibs/notifications/src/govnotify/notifications-node-client.d.tse2e-tests/utils/notification-helpers.tslibs/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.tslibs/notifications/src/notification/notification-queries.test.tslibs/notifications/src/notification/notification-queries.tslibs/notifications/src/govnotify/notifications-node-client.d.tse2e-tests/utils/notification-helpers.tslibs/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.tsor*.spec.tsand 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@@mapand@mapfor 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 moduledeclarations exist for "notifications-node-client":
types/notifications-node-client.d.ts— comprehensive but missinggetNotificationById(used in e2e-tests)libs/notifications/src/govnotify/notifications-node-client.d.ts— incomplete and redundantDelete
libs/notifications/src/govnotify/notifications-node-client.d.tsand addgetNotificationByIdto the declaration intypes/notifications-node-client.d.tsto resolve the type inconsistency. Thetypes/location is the proper place for ambient module declarations (it's intypeRoots), 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()andgetApiKey(), and a production URL fallback forCATH_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 (
ListTypein PascalCase,content_dateandstart_page_linkin snake_case,locationsin camelCase). These keys are passed directly aspersonalisationto the Gov.UK Notify API'ssendEmail()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 migration20251201095418_add_notification_audit_logand then explicitly dropped in migration20251202095746_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
errorMessagesupport.
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
useris in the main Prisma client.
37-56: ThedateAddedfield has a database default and does not require an explicit value.The Subscription model in
libs/subscriptions/prisma/schema.prismadefinesdateAddedwith@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.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (1)
e2e-tests/tests/add-jurisdiction.spec.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Use camelCase for TypeScript variables:userId,caseDetails,documentId
Use PascalCase for classes and interfaces:UserService,CaseRepository. NOIprefix.
Use kebab-case for file and directory names:user-service.ts,case-management/
Use boolean variable names withis/has/canprefix:isActive,hasAccess,canEdit
Module ordering: constants at the top, exported functions next, other functions in order of use, interfaces and types at the bottom
Always add.jsextension to relative imports in ES modules (e.g.,import { foo } from "./bar.js")
Use workspace aliases for imports (@hmcts/*) instead of relative paths across packages
Use strict TypeScript mode enabled; noanywithout justification
Use parameterized database queries with Prisma (no raw SQL string concatenation)
Do not include sensitive data in logs
Files:
e2e-tests/tests/add-jurisdiction.spec.ts
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (CLAUDE.md)
Use SCREAMING_SNAKE_CASE for constants:
MAX_FILE_SIZE,DEFAULT_TIMEOUT
Files:
e2e-tests/tests/add-jurisdiction.spec.ts
**/*.{test,spec}.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Test files must be co-located with source code using
*.test.tsor*.spec.tsnaming pattern
Files:
e2e-tests/tests/add-jurisdiction.spec.ts
e2e-tests/**/*.spec.ts
📄 CodeRabbit inference engine (CLAUDE.md)
e2e-tests/**/*.spec.ts: E2E tests must be located ine2e-tests/directory with*.spec.tsnaming pattern
Tag nightly-only E2E tests with@nightlyin the test title
E2E tests should use selectors in priority order: getByRole(), getByLabel(), getByText(), getByTestId()
Accessibility testing (WCAG 2.2 AA) is mandatory - include axe-core checks in E2E tests
Include validation checks, Welsh translation checks, accessibility checks, and keyboard navigation within a single E2E test journey
Do not test visual styling (fonts, colors, margins, padding) in E2E tests
Files:
e2e-tests/tests/add-jurisdiction.spec.ts
**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Input validation must be performed on all endpoints
Files:
e2e-tests/tests/add-jurisdiction.spec.ts
**/*.{ts,tsx,js,mjs}
📄 CodeRabbit inference engine (CLAUDE.md)
Do not use CommonJS - ES modules only with import/export syntax
Files:
e2e-tests/tests/add-jurisdiction.spec.ts
🧠 Learnings (5)
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to e2e-tests/**/*.spec.ts : Include validation checks, Welsh translation checks, accessibility checks, and keyboard navigation within a single E2E test journey
Applied to files:
e2e-tests/tests/add-jurisdiction.spec.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to e2e-tests/**/*.spec.ts : Accessibility testing (WCAG 2.2 AA) is mandatory - include axe-core checks in E2E tests
Applied to files:
e2e-tests/tests/add-jurisdiction.spec.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to e2e-tests/**/*.spec.ts : Do not test visual styling (fonts, colors, margins, padding) in E2E tests
Applied to files:
e2e-tests/tests/add-jurisdiction.spec.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to e2e-tests/**/*.spec.ts : E2E tests must be located in `e2e-tests/` directory with `*.spec.ts` naming pattern
Applied to files:
e2e-tests/tests/add-jurisdiction.spec.ts
📚 Learning: 2025-12-15T13:38:57.497Z
Learnt from: CR
Repo: hmcts/cath-service PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-15T13:38:57.497Z
Learning: Applies to e2e-tests/**/*.spec.ts : E2E tests should use selectors in priority order: getByRole(), getByLabel(), getByText(), getByTestId()
Applied to files:
e2e-tests/tests/add-jurisdiction.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: Test Changed Packages
- GitHub Check: E2E Tests
Replace click() with keyboard.press('Enter') to properly test keyboard-
driven form submission. The test now genuinely verifies that users can
submit the form using only the keyboard, matching the test's comment
"Submit with keyboard".
Changes:
- Focus the save button
- Verify button is focused (accessibility check)
- Press 'Enter' key to submit (actual keyboard interaction)
- Assert navigation to success page
This ensures the test validates real keyboard accessibility rather than
simulating it with a mouse click.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
|



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