Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/events/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@
"test:unit": "jest",
"prepare": "npm run build"
},
"version": "1.1.2"
"version": "1.1.3"
}
45 changes: 27 additions & 18 deletions packages/events/src/domain/letter-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
56 changes: 55 additions & 1 deletion packages/events/src/events/__tests__/event-envelope.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});
});
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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"
}
4 changes: 2 additions & 2 deletions packages/events/src/events/event-envelope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ export function EventEnvelope<TData extends z.ZodTypeAny>(

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
Expand Down