Skip to content

Commit cd9799c

Browse files
committed
Refactor error handling and callback logging
1 parent 8acf5a0 commit cd9799c

File tree

5 files changed

+405
-132
lines changed

5 files changed

+405
-132
lines changed
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
import { logCallbackGenerated } from "services/callback-logger";
2+
import type { Logger } from "services/logger";
3+
import type { ClientCallbackPayload } from "models/client-callback-payload";
4+
import { EventTypes } from "models/status-transition-event";
5+
6+
describe("callback-logger", () => {
7+
let mockLogger: jest.Mocked<Logger>;
8+
9+
beforeEach(() => {
10+
mockLogger = {
11+
info: jest.fn(),
12+
warn: jest.fn(),
13+
error: jest.fn(),
14+
debug: jest.fn(),
15+
child: jest.fn(),
16+
addContext: jest.fn(),
17+
clearContext: jest.fn(),
18+
} as unknown as jest.Mocked<Logger>;
19+
});
20+
21+
describe("logCallbackGenerated", () => {
22+
describe("MESSAGE_STATUS_TRANSITIONED events", () => {
23+
const messageStatusPayload: ClientCallbackPayload = {
24+
data: [
25+
{
26+
type: "MessageStatus",
27+
attributes: {
28+
messageId: "msg-123",
29+
messageReference: "ref-456",
30+
messageStatus: "delivered",
31+
messageStatusDescription: "Message successfully delivered",
32+
messageFailureReasonCode: undefined,
33+
channels: [
34+
{
35+
type: "nhsapp",
36+
channelStatus: "delivered",
37+
},
38+
],
39+
timestamp: "2026-02-05T14:29:55Z",
40+
routingPlan: {
41+
id: "routing-plan-123",
42+
name: "NHS App with SMS fallback",
43+
version: "v1",
44+
createdDate: "2023-11-17T14:27:51.413Z",
45+
},
46+
},
47+
links: {
48+
message: "/v1/message-batches/messages/msg-123",
49+
},
50+
meta: {
51+
idempotencyKey: "661f9510-f39c-52e5-b827-557766551111",
52+
},
53+
},
54+
],
55+
};
56+
57+
it("should log message status callback with all fields", () => {
58+
logCallbackGenerated(
59+
mockLogger,
60+
messageStatusPayload,
61+
EventTypes.MESSAGE_STATUS_TRANSITIONED,
62+
"corr-123",
63+
"client-abc",
64+
);
65+
66+
expect(mockLogger.info).toHaveBeenCalledWith("Callback generated", {
67+
correlationId: "corr-123",
68+
callbackType: "MessageStatus",
69+
clientId: "client-abc",
70+
messageId: "msg-123",
71+
messageReference: "ref-456",
72+
messageStatus: "delivered",
73+
messageStatusDescription: "Message successfully delivered",
74+
messageFailureReasonCode: undefined,
75+
channels: [
76+
{
77+
type: "nhsapp",
78+
channelStatus: "delivered",
79+
},
80+
],
81+
});
82+
});
83+
84+
it("should log message status callback with failure reason code", () => {
85+
const failedPayload: ClientCallbackPayload = {
86+
data: [
87+
{
88+
...messageStatusPayload.data[0],
89+
attributes: {
90+
...messageStatusPayload.data[0].attributes,
91+
messageStatus: "failed",
92+
messageStatusDescription: "All channels failed",
93+
messageFailureReasonCode: "ERR_INVALID_RECIPIENT",
94+
},
95+
},
96+
],
97+
};
98+
99+
logCallbackGenerated(
100+
mockLogger,
101+
failedPayload,
102+
EventTypes.MESSAGE_STATUS_TRANSITIONED,
103+
"corr-456",
104+
"client-xyz",
105+
);
106+
107+
expect(mockLogger.info).toHaveBeenCalledWith(
108+
"Callback generated",
109+
expect.objectContaining({
110+
messageStatus: "failed",
111+
messageFailureReasonCode: "ERR_INVALID_RECIPIENT",
112+
}),
113+
);
114+
});
115+
116+
it("should handle undefined correlationId", () => {
117+
logCallbackGenerated(
118+
mockLogger,
119+
messageStatusPayload,
120+
EventTypes.MESSAGE_STATUS_TRANSITIONED,
121+
undefined,
122+
"client-abc",
123+
);
124+
125+
expect(mockLogger.info).toHaveBeenCalledWith(
126+
"Callback generated",
127+
expect.objectContaining({
128+
correlationId: undefined,
129+
}),
130+
);
131+
});
132+
});
133+
134+
describe("CHANNEL_STATUS_TRANSITIONED events", () => {
135+
const channelStatusPayload: ClientCallbackPayload = {
136+
data: [
137+
{
138+
type: "ChannelStatus",
139+
attributes: {
140+
messageId: "msg-456",
141+
messageReference: "ref-789",
142+
cascadeType: "primary",
143+
cascadeOrder: 1,
144+
channel: "sms",
145+
channelStatus: "delivered",
146+
channelStatusDescription: "SMS delivered successfully",
147+
channelFailureReasonCode: undefined,
148+
supplierStatus: "delivered",
149+
timestamp: "2026-02-05T14:30:00Z",
150+
retryCount: 0,
151+
},
152+
links: {
153+
message: "/v1/message-batches/messages/msg-456",
154+
},
155+
meta: {
156+
idempotencyKey: "762f9510-f39c-52e5-b827-557766552222",
157+
},
158+
},
159+
],
160+
};
161+
162+
it("should log channel status callback with all fields", () => {
163+
logCallbackGenerated(
164+
mockLogger,
165+
channelStatusPayload,
166+
EventTypes.CHANNEL_STATUS_TRANSITIONED,
167+
"corr-789",
168+
"client-def",
169+
);
170+
171+
expect(mockLogger.info).toHaveBeenCalledWith("Callback generated", {
172+
correlationId: "corr-789",
173+
callbackType: "ChannelStatus",
174+
clientId: "client-def",
175+
messageId: "msg-456",
176+
messageReference: "ref-789",
177+
channel: "sms",
178+
channelStatus: "delivered",
179+
channelStatusDescription: "SMS delivered successfully",
180+
channelFailureReasonCode: undefined,
181+
supplierStatus: "delivered",
182+
});
183+
});
184+
185+
it("should log channel status callback with failure reason code", () => {
186+
const failedPayload: ClientCallbackPayload = {
187+
data: [
188+
{
189+
...channelStatusPayload.data[0],
190+
attributes: {
191+
...channelStatusPayload.data[0].attributes,
192+
channelStatus: "failed",
193+
channelStatusDescription: "Invalid phone number",
194+
channelFailureReasonCode: "ERR_INVALID_PHONE_NUMBER",
195+
supplierStatus: "permanent_failure",
196+
},
197+
},
198+
],
199+
};
200+
201+
logCallbackGenerated(
202+
mockLogger,
203+
failedPayload,
204+
EventTypes.CHANNEL_STATUS_TRANSITIONED,
205+
"corr-999",
206+
"client-ghi",
207+
);
208+
209+
expect(mockLogger.info).toHaveBeenCalledWith(
210+
"Callback generated",
211+
expect.objectContaining({
212+
channelStatus: "failed",
213+
channelFailureReasonCode: "ERR_INVALID_PHONE_NUMBER",
214+
supplierStatus: "permanent_failure",
215+
}),
216+
);
217+
});
218+
});
219+
220+
describe("unsupported event types", () => {
221+
const genericPayload: ClientCallbackPayload = {
222+
data: [
223+
{
224+
type: "MessageStatus",
225+
attributes: {
226+
messageId: "msg-123",
227+
messageReference: "ref-456",
228+
messageStatus: "delivered",
229+
messageStatusDescription: "Message successfully delivered",
230+
messageFailureReasonCode: undefined,
231+
channels: [],
232+
timestamp: "2026-02-05T14:29:55Z",
233+
routingPlan: {
234+
id: "routing-plan-123",
235+
name: "Test",
236+
version: "v1",
237+
createdDate: "2023-11-17T14:27:51.413Z",
238+
},
239+
},
240+
links: {
241+
message: "/v1/message-batches/messages/msg-123",
242+
},
243+
meta: {
244+
idempotencyKey: "661f9510-f39c-52e5-b827-557766551111",
245+
},
246+
},
247+
],
248+
};
249+
250+
it("should log with common fields only for unknown event type", () => {
251+
logCallbackGenerated(
252+
mockLogger,
253+
genericPayload,
254+
"uk.nhs.notify.unknown.event.type",
255+
"corr-000",
256+
"client-zzz",
257+
);
258+
259+
expect(mockLogger.info).toHaveBeenCalledWith("Callback generated", {
260+
correlationId: "corr-000",
261+
callbackType: "MessageStatus",
262+
clientId: "client-zzz",
263+
messageId: "msg-123",
264+
messageReference: "ref-456",
265+
});
266+
});
267+
});
268+
});
269+
});

lambdas/client-transform-filter-lambda/src/__tests__/services/error-handler.test.ts

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -50,38 +50,27 @@ describe("LambdaError", () => {
5050
expect(error.stack).toContain("LambdaError");
5151
});
5252

53-
it("should serialize to JSON correctly", () => {
53+
it("should have correct properties", () => {
5454
const error = new LambdaError(
5555
ErrorType.VALIDATION_ERROR,
5656
"Invalid schema",
5757
"corr-789",
5858
false,
5959
);
6060

61-
const json = error.toJSON();
62-
63-
expect(json).toEqual({
64-
errorType: ErrorType.VALIDATION_ERROR,
65-
message: "Invalid schema",
66-
correlationId: "corr-789",
67-
retryable: false,
68-
originalError: "Invalid schema",
69-
});
61+
expect(error.errorType).toBe(ErrorType.VALIDATION_ERROR);
62+
expect(error.message).toBe("Invalid schema");
63+
expect(error.correlationId).toBe("corr-789");
64+
expect(error.retryable).toBe(false);
7065
});
7166

72-
it("should serialize to JSON without optional fields", () => {
67+
it("should have correct properties without optional fields", () => {
7368
const error = new LambdaError(ErrorType.UNKNOWN_ERROR, "Test error");
7469

75-
const json = error.toJSON();
76-
77-
expect(json).toEqual({
78-
errorType: ErrorType.UNKNOWN_ERROR,
79-
message: "Test error",
80-
correlationId: undefined,
81-
82-
retryable: false,
83-
originalError: "Test error",
84-
});
70+
expect(error.errorType).toBe(ErrorType.UNKNOWN_ERROR);
71+
expect(error.message).toBe("Test error");
72+
expect(error.correlationId).toBeUndefined();
73+
expect(error.retryable).toBe(false);
8574
});
8675
});
8776

0 commit comments

Comments
 (0)