Skip to content

Commit 850296b

Browse files
committed
Add MI schema validation tests and example event JSON files
1 parent 70e6cf8 commit 850296b

File tree

8 files changed

+410
-22
lines changed

8 files changed

+410
-22
lines changed

internal/events/jest.config.ts

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,49 @@
1-
export default {
1+
import type { Config } from "jest";
2+
3+
export const baseJestConfig: Config = {
24
preset: "ts-jest",
3-
testEnvironment: "node",
4-
testMatch: ["**/__tests__/**/*.ts", "**/?(*.)+(spec|test).ts"],
5-
testPathIgnorePatterns: ["<rootDir>/dist/"],
6-
moduleFileExtensions: ["ts", "js", "json", "node"],
7-
transform: {
8-
"^.+\\.ts$": ["ts-jest", { tsconfig: "<rootDir>/tsconfig.jest.json" }],
5+
6+
// Automatically clear mock calls, instances, contexts and results before every test
7+
clearMocks: true,
8+
9+
// Indicates whether the coverage information should be collected while executing the test
10+
collectCoverage: true,
11+
12+
// The directory where Jest should output its coverage files
13+
coverageDirectory: "./.reports/unit/coverage",
14+
15+
// Indicates which provider should be used to instrument code for coverage
16+
coverageProvider: "babel",
17+
18+
coverageThreshold: {
19+
global: {
20+
branches: 100,
21+
functions: 100,
22+
lines: 100,
23+
statements: -10,
24+
},
925
},
26+
27+
coveragePathIgnorePatterns: ["/__tests__/"],
28+
transform: { "^.+\\.ts$": "ts-jest" },
29+
testPathIgnorePatterns: [".build"],
30+
testMatch: ["**/?(*.)+(spec|test).[jt]s?(x)"],
31+
32+
// Use this configuration option to add custom reporters to Jest
33+
reporters: [
34+
"default",
35+
[
36+
"jest-html-reporter",
37+
{
38+
pageTitle: "Test Report",
39+
outputPath: "./.reports/unit/test-report.html",
40+
includeFailureMsg: true,
41+
},
42+
],
43+
],
44+
45+
// The test environment that will be used for testing
46+
testEnvironment: "jsdom",
1047
};
48+
49+
export default baseJestConfig;
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import {
2+
$MI,
3+
MI,
4+
} from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/domain/mi";
5+
6+
describe("MI schema validation", () => {
7+
const validMIEvent: MI = {
8+
id: "mi-001",
9+
lineItem: "LETTER_PRINT_A4",
10+
timestamp: "2025-11-16T10:30:00.000Z",
11+
quantity: 150,
12+
specificationId: "spec-123",
13+
groupId: "group-456",
14+
stockRemaining: 1000,
15+
supplierId: "supplier-789",
16+
createdAt: "2025-11-16T10:30:00.000Z",
17+
updatedAt: "2025-11-16T10:30:00.000Z",
18+
};
19+
20+
describe("basic validation", () => {
21+
it("should validate a valid MI event with all fields", () => {
22+
const result = $MI.safeParse(validMIEvent);
23+
expect(result.success).toBe(true);
24+
expect(result.error).toBeUndefined();
25+
expect(result.data).toEqual(validMIEvent);
26+
});
27+
28+
it("should validate an MI event without optional fields", () => {
29+
const minimalMI = {
30+
id: "mi-002",
31+
lineItem: "LETTER_PRINT_A5",
32+
timestamp: "2025-11-16T11:00:00.000Z",
33+
quantity: 75,
34+
supplierId: "supplier-101",
35+
createdAt: "2025-11-16T11:00:00.000Z",
36+
updatedAt: "2025-11-16T11:00:00.000Z",
37+
};
38+
39+
const result = $MI.safeParse(minimalMI);
40+
expect(result.success).toBe(true);
41+
const data = result.data as MI;
42+
expect(data.specificationId).toBeUndefined();
43+
expect(data.groupId).toBeUndefined();
44+
expect(data.stockRemaining).toBeUndefined();
45+
});
46+
});
47+
48+
describe("field validation", () => {
49+
it("should reject MI event missing required field 'id'", () => {
50+
const invalidMI = { ...validMIEvent };
51+
delete (invalidMI as any).id;
52+
53+
const result = $MI.safeParse(invalidMI);
54+
expect(result.success).toBe(false);
55+
});
56+
57+
it("should reject MI event missing required field 'lineItem'", () => {
58+
const invalidMI = { ...validMIEvent };
59+
delete (invalidMI as any).lineItem;
60+
61+
const result = $MI.safeParse(invalidMI);
62+
expect(result.success).toBe(false);
63+
});
64+
65+
it("should reject MI event missing required field 'timestamp'", () => {
66+
const invalidMI = { ...validMIEvent };
67+
delete (invalidMI as any).timestamp;
68+
69+
const result = $MI.safeParse(invalidMI);
70+
expect(result.success).toBe(false);
71+
});
72+
73+
it("should reject MI event missing required field 'quantity'", () => {
74+
const invalidMI = { ...validMIEvent };
75+
delete (invalidMI as any).quantity;
76+
77+
const result = $MI.safeParse(invalidMI);
78+
expect(result.success).toBe(false);
79+
});
80+
81+
it("should reject MI event missing required field 'supplierId'", () => {
82+
const invalidMI = { ...validMIEvent };
83+
delete (invalidMI as any).supplierId;
84+
85+
const result = $MI.safeParse(invalidMI);
86+
expect(result.success).toBe(false);
87+
});
88+
89+
it("should reject MI event with invalid quantity type", () => {
90+
const invalidMI = {
91+
...validMIEvent,
92+
quantity: "not-a-number",
93+
};
94+
95+
const result = $MI.safeParse(invalidMI);
96+
expect(result.success).toBe(false);
97+
});
98+
99+
it("should reject MI event with invalid stockRemaining type", () => {
100+
const invalidMI = {
101+
...validMIEvent,
102+
stockRemaining: "not-a-number",
103+
};
104+
105+
const result = $MI.safeParse(invalidMI);
106+
expect(result.success).toBe(false);
107+
});
108+
});
109+
110+
describe("testData examples", () => {
111+
it("should parse a letter print MI event", () => {
112+
const letterPrintMI = {
113+
id: "mi-letter-001",
114+
lineItem: "LETTER_PRINT_A4_COLOR",
115+
timestamp: "2025-11-16T14:00:00.000Z",
116+
quantity: 250,
117+
specificationId: "letter-spec-001",
118+
groupId: "batch-001",
119+
supplierId: "supplier-abc",
120+
createdAt: "2025-11-16T14:00:00.000Z",
121+
updatedAt: "2025-11-16T14:00:00.000Z",
122+
};
123+
124+
const result = $MI.safeParse(letterPrintMI);
125+
expect(result.success).toBe(true);
126+
});
127+
128+
it("should parse an envelope usage MI event", () => {
129+
const envelopeMI = {
130+
id: "mi-envelope-001",
131+
lineItem: "ENVELOPE_DL",
132+
timestamp: "2025-11-16T15:00:00.000Z",
133+
quantity: 300,
134+
stockRemaining: 2500,
135+
supplierId: "supplier-xyz",
136+
createdAt: "2025-11-16T15:00:00.000Z",
137+
updatedAt: "2025-11-16T15:00:00.000Z",
138+
};
139+
140+
const result = $MI.safeParse(envelopeMI);
141+
expect(result.success).toBe(true);
142+
const data = result.data as MI;
143+
expect(data.stockRemaining).toBe(2500);
144+
});
145+
146+
it("should parse a postage MI event", () => {
147+
const postageMI = {
148+
id: "mi-postage-001",
149+
lineItem: "POSTAGE_FIRST_CLASS",
150+
timestamp: "2025-11-16T16:00:00.000Z",
151+
quantity: 500,
152+
groupId: "daily-batch-16-11-2025",
153+
supplierId: "supplier-123",
154+
createdAt: "2025-11-16T16:00:00.000Z",
155+
updatedAt: "2025-11-16T16:00:00.000Z",
156+
};
157+
158+
const result = $MI.safeParse(postageMI);
159+
expect(result.success).toBe(true);
160+
const data = result.data as MI;
161+
expect(data.lineItem).toBe("POSTAGE_FIRST_CLASS");
162+
expect(data.groupId).toBe("daily-batch-16-11-2025");
163+
});
164+
});
165+
});
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import fs from "node:fs";
2+
import path from "node:path";
3+
import { $MISubmittedEvent } from "@nhsdigital/nhs-notify-event-schemas-supplier-api/src/events/mi-events";
4+
5+
function readJson(filename: string): unknown {
6+
const filePath = path.resolve(__dirname, "./testData/", filename);
7+
8+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
9+
}
10+
11+
describe("MI event validations", () => {
12+
it("should parse mi.SUBMITTED event successfully", () => {
13+
const json = readJson("mi.SUBMITTED.json");
14+
15+
const { data: event, error } = $MISubmittedEvent.safeParse(json);
16+
expect(error).toBeUndefined();
17+
expect(event).toBeDefined();
18+
expect(event).toEqual(
19+
expect.objectContaining({
20+
type: "uk.nhs.notify.supplier-api.mi.SUBMITTED.v1",
21+
specversion: "1.0",
22+
source: "/data-plane/supplier-api/prod/submit-mi",
23+
id: "8f2c3b44-4e65-5b1b-a678-1f0bf3d4d222",
24+
time: "2025-11-16T10:30:00.000Z",
25+
datacontenttype: "application/json",
26+
dataschema:
27+
"https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.1.0.0.schema.json",
28+
subject: "mi/mi-test-001",
29+
data: expect.objectContaining({
30+
id: "mi-test-001",
31+
lineItem: "LETTER_PRINT_A4",
32+
timestamp: "2025-11-16T10:30:00.000Z",
33+
quantity: 150,
34+
specificationId: "spec-123",
35+
groupId: "group-456",
36+
stockRemaining: 1000,
37+
supplierId: "supplier-789",
38+
}),
39+
}),
40+
);
41+
});
42+
43+
it("should parse minimal mi.SUBMITTED event successfully", () => {
44+
const json = readJson("mi.SUBMITTED-minimal.json");
45+
46+
const event = $MISubmittedEvent.parse(json);
47+
expect(event).toBeDefined();
48+
expect(event.data).toEqual(
49+
expect.objectContaining({
50+
id: "mi-envelope-001",
51+
lineItem: "ENVELOPE_DL",
52+
quantity: 300,
53+
stockRemaining: 2500,
54+
supplierId: "supplier-xyz",
55+
}),
56+
);
57+
expect(event.data.specificationId).toBeUndefined();
58+
expect(event.data.groupId).toBeUndefined();
59+
});
60+
61+
it("should parse MI data fields correctly", () => {
62+
const json = readJson("mi.SUBMITTED.json");
63+
64+
const event = $MISubmittedEvent.parse(json);
65+
expect(event).toBeDefined();
66+
expect(event.data.id).toBe("mi-test-001");
67+
expect(event.data.lineItem).toBe("LETTER_PRINT_A4");
68+
expect(event.data.quantity).toBe(150);
69+
expect(event.data.stockRemaining).toBe(1000);
70+
expect(event.data.supplierId).toBe("supplier-789");
71+
expect(event.data.specificationId).toBe("spec-123");
72+
expect(event.data.groupId).toBe("group-456");
73+
});
74+
75+
it("should throw error for mi.SUBMITTED event with missing subject", () => {
76+
const json = readJson("mi.SUBMITTED-with-missing-subject.json");
77+
78+
expect(() => $MISubmittedEvent.parse(json)).toThrow("subject");
79+
});
80+
81+
it("should throw error for mi.SUBMITTED event with invalid major schema version", () => {
82+
const json = readJson("mi.SUBMITTED-with-invalid-major-version.json");
83+
84+
expect(() => $MISubmittedEvent.parse(json)).toThrow("dataschema");
85+
});
86+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"data": {
3+
"createdAt": "2025-11-16T15:00:00.000Z",
4+
"id": "mi-envelope-001",
5+
"lineItem": "ENVELOPE_DL",
6+
"quantity": 300,
7+
"stockRemaining": 2500,
8+
"supplierId": "supplier-xyz",
9+
"timestamp": "2025-11-16T15:00:00.000Z",
10+
"updatedAt": "2025-11-16T15:00:00.000Z"
11+
},
12+
"datacontenttype": "application/json",
13+
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.1.0.0.schema.json",
14+
"id": "9a3d4c55-5f76-6c2c-b789-2f1cf4e5e333",
15+
"recordedtime": "2025-11-16T15:00:00.250Z",
16+
"severitynumber": 2,
17+
"severitytext": "INFO",
18+
"source": "/data-plane/supplier-api/prod/submit-mi",
19+
"specversion": "1.0",
20+
"subject": "mi/mi-envelope-001",
21+
"time": "2025-11-16T15:00:00.000Z",
22+
"traceparent": "00-2cf9873938ef65ff0660fd433e02541e-d9cf8d9380415553-01",
23+
"type": "uk.nhs.notify.supplier-api.mi.SUBMITTED.v1"
24+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"data": {
3+
"createdAt": "2025-11-16T10:30:00.000Z",
4+
"groupId": "group-456",
5+
"id": "mi-test-001",
6+
"lineItem": "LETTER_PRINT_A4",
7+
"quantity": 150,
8+
"specificationId": "spec-123",
9+
"stockRemaining": 1000,
10+
"supplierId": "supplier-789",
11+
"timestamp": "2025-11-16T10:30:00.000Z",
12+
"updatedAt": "2025-11-16T10:30:00.000Z"
13+
},
14+
"datacontenttype": "application/json",
15+
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.2.0.0.schema.json",
16+
"id": "8f2c3b44-4e65-5b1b-a678-1f0bf3d4d222",
17+
"recordedtime": "2025-11-16T10:30:00.250Z",
18+
"severitynumber": 2,
19+
"severitytext": "INFO",
20+
"source": "/data-plane/supplier-api/prod/submit-mi",
21+
"specversion": "1.0",
22+
"subject": "mi/mi-test-001",
23+
"time": "2025-11-16T10:30:00.000Z",
24+
"traceparent": "00-1bf8762827de54ee9559fc322d91430d-c8be7c8279304442-01",
25+
"type": "uk.nhs.notify.supplier-api.mi.SUBMITTED.v1"
26+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"data": {
3+
"createdAt": "2025-11-16T10:30:00.000Z",
4+
"id": "mi-invalid-001",
5+
"lineItem": "LETTER_PRINT_A4",
6+
"quantity": 150,
7+
"supplierId": "supplier-789",
8+
"timestamp": "2025-11-16T10:30:00.000Z",
9+
"updatedAt": "2025-11-16T10:30:00.000Z"
10+
},
11+
"datacontenttype": "application/json",
12+
"dataschema": "https://notify.nhs.uk/cloudevents/schemas/supplier-api/mi.SUBMITTED.1.0.0.schema.json",
13+
"id": "8f2c3b44-4e65-5b1b-a678-1f0bf3d4d222",
14+
"recordedtime": "2025-11-16T10:30:00.250Z",
15+
"severitynumber": 2,
16+
"severitytext": "INFO",
17+
"source": "/data-plane/supplier-api/prod/submit-mi",
18+
"specversion": "1.0",
19+
"time": "2025-11-16T10:30:00.000Z",
20+
"traceparent": "00-1bf8762827de54ee9559fc322d91430d-c8be7c8279304442-01",
21+
"type": "uk.nhs.notify.supplier-api.mi.SUBMITTED.v1"
22+
}

0 commit comments

Comments
 (0)