Skip to content

Commit 869cd24

Browse files
authored
Relax source field regex and request ids in event payload (#29)
* Enhance source validation to accept digital-letters domain; bump version to 1.1.3 * Make request ID fields optional in letter request schema; add tests for validation
1 parent ec935a5 commit 869cd24

File tree

8 files changed

+205
-23
lines changed

8 files changed

+205
-23
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/events/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,5 @@
4040
"test:unit": "jest",
4141
"prepare": "npm run build"
4242
},
43-
"version": "1.1.2"
43+
"version": "1.1.3"
4444
}

packages/events/src/domain/letter-request.ts

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,33 @@ export const $LetterRequest = DomainBase("LetterRequest")
2727
"Reference to the letter variant which should be used to select a letter pack and supplier for this request",
2828
examples: ["1y3q9v1zzzz"],
2929
}),
30-
requestId: z.string().meta({
31-
title: "Request ID",
32-
description:
33-
"Identifier for the request which this letter request is part of",
34-
examples: ["1y3q9v1zzzy"],
35-
}),
36-
requestItemId: z.string().meta({
37-
title: "Request Item ID",
38-
description:
39-
"Identifier for the request item which this letter request is part of",
40-
examples: ["1y3q9v1zzyx"],
41-
}),
42-
requestItemPlanId: z.string().meta({
43-
title: "Request Item Plan ID",
44-
description:
45-
"Identifier for the request item plan which associated with this letter request",
46-
examples: ["1y3q9v1zzzz"],
47-
}),
30+
requestId: z
31+
.string()
32+
.optional()
33+
.meta({
34+
title: "Request ID",
35+
description:
36+
"Identifier for the request which this letter request is part of",
37+
examples: ["1y3q9v1zzzy"],
38+
}),
39+
requestItemId: z
40+
.string()
41+
.optional()
42+
.meta({
43+
title: "Request Item ID",
44+
description:
45+
"Identifier for the request item which this letter request is part of",
46+
examples: ["1y3q9v1zzyx"],
47+
}),
48+
requestItemPlanId: z
49+
.string()
50+
.optional()
51+
.meta({
52+
title: "Request Item Plan ID",
53+
description:
54+
"Identifier for the request item plan which associated with this letter request",
55+
examples: ["1y3q9v1zzzz"],
56+
}),
4857
templateId: z
4958
.string()
5059
.optional()

packages/events/src/events/__tests__/event-envelope.test.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,51 @@ describe("EventEnvelope schema validation", () => {
216216
});
217217
});
218218

219-
describe("edge cases", () => {
219+
describe("source validation", () => {
220+
it("should accept source with letter-rendering plane", () => {
221+
const envelope = {
222+
...baseValidEnvelope,
223+
source: "/data-plane/letter-rendering/ordering",
224+
};
225+
226+
const result = $Envelope.safeParse(envelope);
227+
expect(result.error).toBeUndefined();
228+
expect(result.success).toBe(true);
229+
});
230+
231+
it("should accept source with digital-letters domain", () => {
232+
const envelope = {
233+
...baseValidEnvelope,
234+
source: "/data-plane/digital-letters/ordering",
235+
};
236+
237+
const result = $Envelope.safeParse(envelope);
238+
expect(result.error).toBeUndefined();
239+
expect(result.success).toBe(true);
240+
});
241+
242+
it("should accept source with digital-letters domain and additional path", () => {
243+
const envelope = {
244+
...baseValidEnvelope,
245+
source: "/data-plane/digital-letters/ordering/sub-path",
246+
};
247+
248+
const result = $Envelope.safeParse(envelope);
249+
expect(result.error).toBeUndefined();
250+
expect(result.success).toBe(true);
251+
});
252+
253+
it("should accept source with letter-rendering plane and additional path", () => {
254+
const envelope = {
255+
...baseValidEnvelope,
256+
source: "/data-plane/letter-rendering/ordering/sub-path/more",
257+
};
258+
259+
const result = $Envelope.safeParse(envelope);
260+
expect(result.error).toBeUndefined();
261+
expect(result.success).toBe(true);
262+
});
263+
220264
it("should reject invalid source pattern", () => {
221265
const envelope = {
222266
...baseValidEnvelope,
@@ -226,6 +270,16 @@ describe("EventEnvelope schema validation", () => {
226270
const result = $Envelope.safeParse(envelope);
227271
expect(result.success).toBe(false);
228272
});
273+
274+
it("should reject source without data-plane prefix", () => {
275+
const envelope = {
276+
...baseValidEnvelope,
277+
source: "/digital-letters/ordering",
278+
};
279+
280+
const result = $Envelope.safeParse(envelope);
281+
expect(result.success).toBe(false);
282+
});
229283
});
230284

231285
describe("subject prefix validation", () => {

packages/events/src/events/__tests__/letter-request-prepared.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,73 @@ describe("LetterRequestPreparedEvent validations", () => {
4949

5050
expect(() => $LetterRequestPreparedEvent.parse(json)).toThrow("dataschema");
5151
});
52+
53+
describe("optional request ID fields", () => {
54+
it("should validate event without any optional request ID fields", () => {
55+
const json = readJson(
56+
"letter-request-prepared-without-optional-fields.json",
57+
);
58+
59+
const { data: event, error } =
60+
$LetterRequestPreparedEvent.safeParse(json);
61+
expect(error).toBeUndefined();
62+
expect(event).toEqual(
63+
expect.objectContaining({
64+
type: "uk.nhs.notify.letter-rendering.letter-request.prepared.v1",
65+
specversion: "1.0",
66+
data: expect.objectContaining({
67+
domainId: "0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5",
68+
letterVariantId: "standard_economy",
69+
clientId: "00f3b388-bbe9-41c9-9e76-052d37ee8988",
70+
}),
71+
}),
72+
);
73+
expect(event?.data.requestId).toBeUndefined();
74+
expect(event?.data.requestItemId).toBeUndefined();
75+
expect(event?.data.requestItemPlanId).toBeUndefined();
76+
expect(event?.data.campaignId).toBeUndefined();
77+
expect(event?.data.templateId).toBeUndefined();
78+
});
79+
80+
it("should validate event with partial optional request ID fields", () => {
81+
const json = readJson(
82+
"letter-request-prepared-with-partial-optional-fields.json",
83+
);
84+
85+
const { data: event, error } =
86+
$LetterRequestPreparedEvent.safeParse(json);
87+
expect(error).toBeUndefined();
88+
expect(event).toEqual(
89+
expect.objectContaining({
90+
type: "uk.nhs.notify.letter-rendering.letter-request.prepared.v1",
91+
specversion: "1.0",
92+
data: expect.objectContaining({
93+
domainId: "0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5",
94+
letterVariantId: "standard_economy",
95+
clientId: "00f3b388-bbe9-41c9-9e76-052d37ee8988",
96+
requestId: "0o5Fs0EELR0fUjHjbCnEtdUwQe3",
97+
templateId: "template_123",
98+
}),
99+
}),
100+
);
101+
expect(event?.data.requestId).toBe("0o5Fs0EELR0fUjHjbCnEtdUwQe3");
102+
expect(event?.data.templateId).toBe("template_123");
103+
expect(event?.data.requestItemId).toBeUndefined();
104+
expect(event?.data.requestItemPlanId).toBeUndefined();
105+
expect(event?.data.campaignId).toBeUndefined();
106+
});
107+
108+
it("should validate event with all optional request ID fields present", () => {
109+
const json = readJson("letter-request-prepared-valid.json");
110+
111+
const { data: event, error } =
112+
$LetterRequestPreparedEvent.safeParse(json);
113+
expect(error).toBeUndefined();
114+
expect(event?.data.requestId).toBe("0o5Fs0EELR0fUjHjbCnEtdUwQe3");
115+
expect(event?.data.requestItemId).toBe("0o5Fs0EELR0fUjHjbCnEtdUwQe4");
116+
expect(event?.data.requestItemPlanId).toBe("0o5Fs0EELR0fUjHjbCnEtdUwQe5");
117+
expect(event?.data.campaignId).toBe("campaign_456");
118+
expect(event?.data.templateId).toBe("template_123");
119+
});
120+
});
52121
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"data": {
3+
"clientId": "00f3b388-bbe9-41c9-9e76-052d37ee8988",
4+
"createdAt": "2025-08-28T08:45:00.000Z",
5+
"domainId": "0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5",
6+
"letterVariantId": "standard_economy",
7+
"pageCount": 2,
8+
"requestId": "0o5Fs0EELR0fUjHjbCnEtdUwQe3",
9+
"sha256Hash": "3a7bd3e2360a3d29eea436fcfb7e44c735d117c8f2f1d2d1e4f6e8f7e6e8f7e6",
10+
"status": "PREPARED",
11+
"templateId": "template_123",
12+
"url": "https://s3.eu-west-2.amazonaws.com/notify-letters-dev/letters/f47ac10b-58cc-4372-a567-0e02b2c3d479.pdf"
13+
},
14+
"datacontenttype": "application/json",
15+
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/letter-rendering/letter-request.prepared.1.0.0.schema.json",
16+
"id": "23f1f09c-a555-4d9b-8405-0b33490bc920",
17+
"recordedtime": "2025-08-28T08:45:00.000Z",
18+
"severitynumber": 2,
19+
"severitytext": "INFO",
20+
"source": "/data-plane/letter-rendering/prod/render-pdf",
21+
"specversion": "1.0",
22+
"subject": "client/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5",
23+
"time": "2025-08-28T08:45:00.000Z",
24+
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
25+
"type": "uk.nhs.notify.letter-rendering.letter-request.prepared.v1"
26+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"data": {
3+
"clientId": "00f3b388-bbe9-41c9-9e76-052d37ee8988",
4+
"createdAt": "2025-08-28T08:45:00.000Z",
5+
"domainId": "0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5",
6+
"letterVariantId": "standard_economy",
7+
"pageCount": 2,
8+
"sha256Hash": "3a7bd3e2360a3d29eea436fcfb7e44c735d117c8f2f1d2d1e4f6e8f7e6e8f7e6",
9+
"status": "PREPARED",
10+
"url": "https://s3.eu-west-2.amazonaws.com/notify-letters-dev/letters/f47ac10b-58cc-4372-a567-0e02b2c3d479.pdf"
11+
},
12+
"datacontenttype": "application/json",
13+
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/letter-rendering/letter-request.prepared.1.0.0.schema.json",
14+
"id": "23f1f09c-a555-4d9b-8405-0b33490bc920",
15+
"recordedtime": "2025-08-28T08:45:00.000Z",
16+
"severitynumber": 2,
17+
"severitytext": "INFO",
18+
"source": "/data-plane/letter-rendering/prod/render-pdf",
19+
"specversion": "1.0",
20+
"subject": "client/00f3b388-bbe9-41c9-9e76-052d37ee8988/letter-request/0o5Fs0EELR0fUjHjbCnEtdUwQe4_0o5Fs0EELR0fUjHjbCnEtdUwQe5",
21+
"time": "2025-08-28T08:45:00.000Z",
22+
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01",
23+
"type": "uk.nhs.notify.letter-rendering.letter-request.prepared.v1"
24+
}

packages/events/src/events/event-envelope.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ export function EventEnvelope<TData extends z.ZodTypeAny>(
6464

6565
source: z
6666
.string()
67-
.regex(/^\/data-plane\/letter-rendering(?:\/.*)?$/)
67+
.regex(/^\/data-plane\/(letter-rendering|digital-letters)(?:\/.*)?$/)
6868
.meta({
6969
title: "Event Source",
7070
description:
71-
"Logical event producer path within the letter-rendering domain",
71+
"Logical event producer path within the letter-rendering or digital-letters domains",
7272
}),
7373

7474
subject: z

0 commit comments

Comments
 (0)