From fc12e5d09b79ce39aa59987da3f287b24e6025a6 Mon Sep 17 00:00:00 2001 From: Mike Houston Date: Thu, 27 Nov 2025 17:10:03 +0000 Subject: [PATCH 1/2] Enhance source validation to accept digital-letters domain; bump version to 1.1.3 --- package-lock.json | 2 +- packages/events/package.json | 2 +- .../events/__tests__/event-envelope.test.ts | 56 ++++++++++++++++++- packages/events/src/events/event-envelope.ts | 4 +- 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index dafd6c4..0c19333 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11129,7 +11129,7 @@ }, "packages/events": { "name": "@nhsdigital/nhs-notify-event-schemas-letter-rendering", - "version": "1.1.2", + "version": "1.1.3", "dependencies": { "@asyncapi/bundler": "^0.6.4", "zod": "^4.1.11" diff --git a/packages/events/package.json b/packages/events/package.json index 0f74898..dc63dd2 100644 --- a/packages/events/package.json +++ b/packages/events/package.json @@ -40,5 +40,5 @@ "test:unit": "jest", "prepare": "npm run build" }, - "version": "1.1.2" + "version": "1.1.3" } diff --git a/packages/events/src/events/__tests__/event-envelope.test.ts b/packages/events/src/events/__tests__/event-envelope.test.ts index ed4510f..620df91 100644 --- a/packages/events/src/events/__tests__/event-envelope.test.ts +++ b/packages/events/src/events/__tests__/event-envelope.test.ts @@ -216,7 +216,51 @@ describe("EventEnvelope schema validation", () => { }); }); - describe("edge cases", () => { + describe("source validation", () => { + it("should accept source with letter-rendering plane", () => { + const envelope = { + ...baseValidEnvelope, + source: "/data-plane/letter-rendering/ordering", + }; + + const result = $Envelope.safeParse(envelope); + expect(result.error).toBeUndefined(); + expect(result.success).toBe(true); + }); + + it("should accept source with digital-letters domain", () => { + const envelope = { + ...baseValidEnvelope, + source: "/data-plane/digital-letters/ordering", + }; + + const result = $Envelope.safeParse(envelope); + expect(result.error).toBeUndefined(); + expect(result.success).toBe(true); + }); + + it("should accept source with digital-letters domain and additional path", () => { + const envelope = { + ...baseValidEnvelope, + source: "/data-plane/digital-letters/ordering/sub-path", + }; + + const result = $Envelope.safeParse(envelope); + expect(result.error).toBeUndefined(); + expect(result.success).toBe(true); + }); + + it("should accept source with letter-rendering plane and additional path", () => { + const envelope = { + ...baseValidEnvelope, + source: "/data-plane/letter-rendering/ordering/sub-path/more", + }; + + const result = $Envelope.safeParse(envelope); + expect(result.error).toBeUndefined(); + expect(result.success).toBe(true); + }); + it("should reject invalid source pattern", () => { const envelope = { ...baseValidEnvelope, @@ -226,6 +270,16 @@ describe("EventEnvelope schema validation", () => { const result = $Envelope.safeParse(envelope); expect(result.success).toBe(false); }); + + it("should reject source without data-plane prefix", () => { + const envelope = { + ...baseValidEnvelope, + source: "/digital-letters/ordering", + }; + + const result = $Envelope.safeParse(envelope); + expect(result.success).toBe(false); + }); }); describe("subject prefix validation", () => { diff --git a/packages/events/src/events/event-envelope.ts b/packages/events/src/events/event-envelope.ts index 5e1f61d..233193f 100644 --- a/packages/events/src/events/event-envelope.ts +++ b/packages/events/src/events/event-envelope.ts @@ -64,11 +64,11 @@ export function EventEnvelope( source: z .string() - .regex(/^\/data-plane\/letter-rendering(?:\/.*)?$/) + .regex(/^\/data-plane\/(letter-rendering|digital-letters)(?:\/.*)?$/) .meta({ title: "Event Source", description: - "Logical event producer path within the letter-rendering domain", + "Logical event producer path within the letter-rendering or digital-letters domains", }), subject: z From 877b161af01239178f155b9f1c56b858db245c17 Mon Sep 17 00:00:00 2001 From: Mike Houston Date: Thu, 27 Nov 2025 17:21:03 +0000 Subject: [PATCH 2/2] Make request ID fields optional in letter request schema; add tests for validation --- packages/events/src/domain/letter-request.ts | 45 +++++++----- .../__tests__/letter-request-prepared.test.ts | 69 +++++++++++++++++++ ...prepared-with-partial-optional-fields.json | 26 +++++++ ...uest-prepared-without-optional-fields.json | 24 +++++++ 4 files changed, 146 insertions(+), 18 deletions(-) create mode 100644 packages/events/src/events/__tests__/testData/letter-request-prepared-with-partial-optional-fields.json create mode 100644 packages/events/src/events/__tests__/testData/letter-request-prepared-without-optional-fields.json diff --git a/packages/events/src/domain/letter-request.ts b/packages/events/src/domain/letter-request.ts index 06b4715..ca9ba7e 100644 --- a/packages/events/src/domain/letter-request.ts +++ b/packages/events/src/domain/letter-request.ts @@ -27,24 +27,33 @@ export const $LetterRequest = DomainBase("LetterRequest") "Reference to the letter variant which should be used to select a letter pack and supplier for this request", examples: ["1y3q9v1zzzz"], }), - requestId: z.string().meta({ - title: "Request ID", - description: - "Identifier for the request which this letter request is part of", - examples: ["1y3q9v1zzzy"], - }), - requestItemId: z.string().meta({ - title: "Request Item ID", - description: - "Identifier for the request item which this letter request is part of", - examples: ["1y3q9v1zzyx"], - }), - requestItemPlanId: z.string().meta({ - title: "Request Item Plan ID", - description: - "Identifier for the request item plan which associated with this letter request", - examples: ["1y3q9v1zzzz"], - }), + requestId: z + .string() + .optional() + .meta({ + title: "Request ID", + description: + "Identifier for the request which this letter request is part of", + examples: ["1y3q9v1zzzy"], + }), + requestItemId: z + .string() + .optional() + .meta({ + title: "Request Item ID", + description: + "Identifier for the request item which this letter request is part of", + examples: ["1y3q9v1zzyx"], + }), + requestItemPlanId: z + .string() + .optional() + .meta({ + title: "Request Item Plan ID", + description: + "Identifier for the request item plan which associated with this letter request", + examples: ["1y3q9v1zzzz"], + }), templateId: z .string() .optional() diff --git a/packages/events/src/events/__tests__/letter-request-prepared.test.ts b/packages/events/src/events/__tests__/letter-request-prepared.test.ts index 1d62f18..f9055f4 100644 --- a/packages/events/src/events/__tests__/letter-request-prepared.test.ts +++ b/packages/events/src/events/__tests__/letter-request-prepared.test.ts @@ -49,4 +49,73 @@ describe("LetterRequestPreparedEvent validations", () => { expect(() => $LetterRequestPreparedEvent.parse(json)).toThrow("dataschema"); }); + + describe("optional request ID fields", () => { + it("should validate event without any optional request ID fields", () => { + const json = readJson( + "letter-request-prepared-without-optional-fields.json", + ); + + const { data: event, error } = + $LetterRequestPreparedEvent.safeParse(json); + expect(error).toBeUndefined(); + expect(event).toEqual( + expect.objectContaining({ + type: "uk.nhs.notify.letter-rendering.letter-request.prepared.v1", + specversion: "1.0", + data: expect.objectContaining({ + domainId: "0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", + letterVariantId: "standard_economy", + clientId: "00f3b388-bbe9-41c9-9e76-052d37ee8988", + }), + }), + ); + expect(event?.data.requestId).toBeUndefined(); + expect(event?.data.requestItemId).toBeUndefined(); + expect(event?.data.requestItemPlanId).toBeUndefined(); + expect(event?.data.campaignId).toBeUndefined(); + expect(event?.data.templateId).toBeUndefined(); + }); + + it("should validate event with partial optional request ID fields", () => { + const json = readJson( + "letter-request-prepared-with-partial-optional-fields.json", + ); + + const { data: event, error } = + $LetterRequestPreparedEvent.safeParse(json); + expect(error).toBeUndefined(); + expect(event).toEqual( + expect.objectContaining({ + type: "uk.nhs.notify.letter-rendering.letter-request.prepared.v1", + specversion: "1.0", + data: expect.objectContaining({ + domainId: "0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", + letterVariantId: "standard_economy", + clientId: "00f3b388-bbe9-41c9-9e76-052d37ee8988", + requestId: "0o5Fs0EELR0fUjHjbCnEtdUwQe3", + templateId: "template_123", + }), + }), + ); + expect(event?.data.requestId).toBe("0o5Fs0EELR0fUjHjbCnEtdUwQe3"); + expect(event?.data.templateId).toBe("template_123"); + expect(event?.data.requestItemId).toBeUndefined(); + expect(event?.data.requestItemPlanId).toBeUndefined(); + expect(event?.data.campaignId).toBeUndefined(); + }); + + it("should validate event with all optional request ID fields present", () => { + const json = readJson("letter-request-prepared-valid.json"); + + const { data: event, error } = + $LetterRequestPreparedEvent.safeParse(json); + expect(error).toBeUndefined(); + expect(event?.data.requestId).toBe("0o5Fs0EELR0fUjHjbCnEtdUwQe3"); + expect(event?.data.requestItemId).toBe("0o5Fs0EELR0fUjHjbCnEtdUwQe4"); + expect(event?.data.requestItemPlanId).toBe("0o5Fs0EELR0fUjHjbCnEtdUwQe5"); + expect(event?.data.campaignId).toBe("campaign_456"); + expect(event?.data.templateId).toBe("template_123"); + }); + }); }); diff --git a/packages/events/src/events/__tests__/testData/letter-request-prepared-with-partial-optional-fields.json b/packages/events/src/events/__tests__/testData/letter-request-prepared-with-partial-optional-fields.json new file mode 100644 index 0000000..c0160af --- /dev/null +++ b/packages/events/src/events/__tests__/testData/letter-request-prepared-with-partial-optional-fields.json @@ -0,0 +1,26 @@ +{ + "data": { + "clientId": "00f3b388-bbe9-41c9-9e76-052d37ee8988", + "createdAt": "2025-08-28T08:45:00.000Z", + "domainId": "0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", + "letterVariantId": "standard_economy", + "pageCount": 2, + "requestId": "0o5Fs0EELR0fUjHjbCnEtdUwQe3", + "sha256Hash": "3a7bd3e2360a3d29eea436fcfb7e44c735d117c8f2f1d2d1e4f6e8f7e6e8f7e6", + "status": "PREPARED", + "templateId": "template_123", + "url": "https://s3.eu-west-2.amazonaws.com/notify-letters-dev/letters/f47ac10b-58cc-4372-a567-0e02b2c3d479.pdf" + }, + "datacontenttype": "application/json", + "dataschema": "https://notify.nhs.uk/cloudevents/schemas/letter-rendering/letter-request.prepared.1.0.0.schema.json", + "id": "23f1f09c-a555-4d9b-8405-0b33490bc920", + "recordedtime": "2025-08-28T08:45:00.000Z", + "severitynumber": 2, + "severitytext": "INFO", + "source": "/data-plane/letter-rendering/prod/render-pdf", + "specversion": "1.0", + "subject": "client/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", + "time": "2025-08-28T08:45:00.000Z", + "traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "type": "uk.nhs.notify.letter-rendering.letter-request.prepared.v1" +} diff --git a/packages/events/src/events/__tests__/testData/letter-request-prepared-without-optional-fields.json b/packages/events/src/events/__tests__/testData/letter-request-prepared-without-optional-fields.json new file mode 100644 index 0000000..cae682f --- /dev/null +++ b/packages/events/src/events/__tests__/testData/letter-request-prepared-without-optional-fields.json @@ -0,0 +1,24 @@ +{ + "data": { + "clientId": "00f3b388-bbe9-41c9-9e76-052d37ee8988", + "createdAt": "2025-08-28T08:45:00.000Z", + "domainId": "0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", + "letterVariantId": "standard_economy", + "pageCount": 2, + "sha256Hash": "3a7bd3e2360a3d29eea436fcfb7e44c735d117c8f2f1d2d1e4f6e8f7e6e8f7e6", + "status": "PREPARED", + "url": "https://s3.eu-west-2.amazonaws.com/notify-letters-dev/letters/f47ac10b-58cc-4372-a567-0e02b2c3d479.pdf" + }, + "datacontenttype": "application/json", + "dataschema": "https://notify.nhs.uk/cloudevents/schemas/letter-rendering/letter-request.prepared.1.0.0.schema.json", + "id": "23f1f09c-a555-4d9b-8405-0b33490bc920", + "recordedtime": "2025-08-28T08:45:00.000Z", + "severitynumber": 2, + "severitytext": "INFO", + "source": "/data-plane/letter-rendering/prod/render-pdf", + "specversion": "1.0", + "subject": "client/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5", + "time": "2025-08-28T08:45:00.000Z", + "traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "type": "uk.nhs.notify.letter-rendering.letter-request.prepared.v1" +}