Skip to content

Commit a993388

Browse files
CCM-13378: Handle No Supplier Available
1 parent 23be770 commit a993388

9 files changed

Lines changed: 368 additions & 116 deletions

File tree

lambdas/supplier-allocator/src/handler/__tests__/allocate-handler.test.ts

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,15 @@ describe("createSupplierAllocatorHandler", () => {
253253

254254
const messageBody = JSON.parse(sendCall.input.MessageBody);
255255
expect(messageBody.letterEvent).toEqual(preparedEvent);
256-
expect(messageBody.supplierSpec).toEqual({
256+
expect(messageBody.allocationDetails.supplierSpec).toEqual({
257257
supplierId: "supplier1",
258258
specId: "spec1",
259259
priority: 1,
260260
billingId: "billing1",
261261
});
262+
expect(messageBody.allocationDetails.allocationStatus).toEqual({
263+
status: "PENDING",
264+
});
262265
});
263266

264267
test("parses SNS notification and sends message to SQS queue for v1 event", async () => {
@@ -281,12 +284,15 @@ describe("createSupplierAllocatorHandler", () => {
281284
expect(mockSqsClient.send).toHaveBeenCalledTimes(1);
282285
const sendCall = (mockSqsClient.send as jest.Mock).mock.calls[0][0];
283286
const messageBody = JSON.parse(sendCall.input.MessageBody);
284-
expect(messageBody.supplierSpec).toEqual({
287+
expect(messageBody.allocationDetails.supplierSpec).toEqual({
285288
supplierId: "supplier1",
286289
specId: "spec1",
287290
priority: 1,
288291
billingId: "billing1",
289292
});
293+
expect(messageBody.allocationDetails.allocationStatus).toEqual({
294+
status: "PENDING",
295+
});
290296
});
291297

292298
test("returns batch failure for Update event", async () => {
@@ -486,74 +492,105 @@ describe("createSupplierAllocatorHandler", () => {
486492
const handler = createSupplierAllocatorHandler(mockedDeps);
487493
const result = await handler(evt, {} as any, {} as any);
488494
if (!result) throw new Error("expected BatchResponse, got void");
489-
expect(result.batchItemFailures).toHaveLength(1);
490-
expect((mockedDeps.logger.error as jest.Mock).mock.calls).toHaveLength(2);
495+
expect((mockedDeps.logger.error as jest.Mock).mock.calls).toHaveLength(1);
491496
expect((mockedDeps.logger.error as jest.Mock).mock.calls[0][0]).toEqual(
492497
expect.objectContaining({
493498
description: "Error fetching supplier from config",
494499
err: configError,
495500
variantId: "lv1",
496501
}),
497502
);
503+
expect(mockSqsClient.send).toHaveBeenCalledTimes(1);
504+
const sendCall = (mockSqsClient.send as jest.Mock).mock.calls[0][0];
505+
expect(sendCall).toBeInstanceOf(SendMessageCommand);
506+
507+
const messageBody = JSON.parse(sendCall.input.MessageBody);
508+
expect(messageBody.letterEvent).toEqual(preparedEvent);
509+
expect(messageBody.allocationDetails.supplierSpec).toEqual({
510+
supplierId: "unknown",
511+
specId: "unknown",
512+
priority: 0,
513+
billingId: "unknown",
514+
});
515+
expect(messageBody.allocationDetails.allocationStatus).toEqual({
516+
status: "REJECTED",
517+
reasonCode: "NO_SUPPLIERS_AVAILABLE",
518+
reasonText: "Failed to retrieve supplier config",
519+
});
498520
});
499521

500-
const rejectWith = (mock: jest.Mock, error: Error) =>
501-
mock.mockRejectedValueOnce(error);
522+
const rejectWith = (mock: jest.Mock, errorMessage: string) =>
523+
mock.mockRejectedValueOnce(new Error(errorMessage));
524+
525+
const throwAny = (mock: jest.Mock) =>
526+
mock.mockRejectedValueOnce("anything that is not an Error");
502527

503528
const supplierConfigErrorCases = [
504529
{
505530
name: "getVolumeGroupDetails",
531+
errorMessage: "Volume group retrieval failed",
506532
setup: () =>
507533
rejectWith(
508534
supplierConfig.getVolumeGroupDetails as jest.Mock,
509-
new Error("Volume group retrieval failed"),
535+
"Volume group retrieval failed",
510536
),
511537
},
512538
{
513539
name: "eligibleSuppliers",
540+
errorMessage: "Eligible suppliers retrieval failed",
514541
setup: () =>
515542
rejectWith(
516543
allocationConfig.eligibleSuppliers as jest.Mock,
517-
new Error("Eligible suppliers retrieval failed"),
544+
"Eligible suppliers retrieval failed",
518545
),
519546
},
520547
{
521548
name: "preferredSupplierPack",
549+
errorMessage: "Preferred supplier pack retrieval failed",
522550
setup: () =>
523551
rejectWith(
524552
allocationConfig.preferredSupplierPack as jest.Mock,
525-
new Error("Preferred supplier pack retrieval failed"),
553+
"Preferred supplier pack retrieval failed",
526554
),
527555
},
528556
{
529557
name: "suppliersWithValidPack",
558+
errorMessage: "Suppliers with valid pack retrieval failed",
530559
setup: () =>
531560
rejectWith(
532561
allocationConfig.suppliersWithValidPack as jest.Mock,
533-
new Error("Suppliers with valid pack retrieval failed"),
562+
"Suppliers with valid pack retrieval failed",
534563
),
535564
},
536565
{
537566
name: "filterSuppliersWithCapacity",
567+
errorMessage: "Filter suppliers with capacity failed",
538568
setup: () =>
539569
rejectWith(
540570
allocationConfig.filterSuppliersWithCapacity as jest.Mock,
541-
new Error("Filter suppliers with capacity failed"),
571+
"Filter suppliers with capacity failed",
542572
),
543573
},
544574
{
545575
name: "selectSupplierByFactor",
576+
errorMessage: "Select supplier by factor failed",
546577
setup: () =>
547578
rejectWith(
548579
allocationConfig.selectSupplierByFactor as jest.Mock,
549-
new Error("Select supplier by factor failed"),
580+
"Select supplier by factor failed",
550581
),
551582
},
583+
{
584+
name: "unexpectedError",
585+
errorMessage: "Unknown error",
586+
setup: () =>
587+
throwAny(allocationConfig.selectSupplierByFactor as jest.Mock),
588+
},
552589
];
553590

554591
test.each(supplierConfigErrorCases)(
555592
"logs error when %s rejects during supplier config resolution",
556-
async ({ setup }) => {
593+
async ({ errorMessage, setup }) => {
557594
const preparedEvent = createPreparedV2Event();
558595
const evt: SQSEvent = createSQSEvent([
559596
createSqsRecord("msg1", JSON.stringify(preparedEvent)),
@@ -566,14 +603,30 @@ describe("createSupplierAllocatorHandler", () => {
566603
const result = await handler(evt, {} as any, {} as any);
567604
if (!result) throw new Error("expected BatchResponse, got void");
568605

569-
expect(result.batchItemFailures).toHaveLength(1);
570-
expect((mockedDeps.logger.error as jest.Mock).mock.calls).toHaveLength(2);
606+
expect((mockedDeps.logger.error as jest.Mock).mock.calls).toHaveLength(1);
571607
expect((mockedDeps.logger.error as jest.Mock).mock.calls[0][0]).toEqual(
572608
expect.objectContaining({
573609
description: "Error fetching supplier from config",
574610
variantId: "lv1",
575611
}),
576612
);
613+
expect(mockSqsClient.send).toHaveBeenCalledTimes(1);
614+
const sendCall = (mockSqsClient.send as jest.Mock).mock.calls[0][0];
615+
expect(sendCall).toBeInstanceOf(SendMessageCommand);
616+
617+
const messageBody = JSON.parse(sendCall.input.MessageBody);
618+
expect(messageBody.letterEvent).toEqual(preparedEvent);
619+
expect(messageBody.allocationDetails.supplierSpec).toEqual({
620+
supplierId: "unknown",
621+
specId: "unknown",
622+
priority: 0,
623+
billingId: "unknown",
624+
});
625+
expect(messageBody.allocationDetails.allocationStatus).toEqual({
626+
status: "REJECTED",
627+
reasonCode: "NO_SUPPLIERS_AVAILABLE",
628+
reasonText: errorMessage,
629+
});
577630
},
578631
);
579632

lambdas/supplier-allocator/src/handler/allocate-handler.ts

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,16 @@ async function getSupplierFromConfig(
103103
});
104104

105105
const supplierDetails: SupplierDetails = {
106-
supplierSpec: {
107-
supplierId: selectedSupplierId,
108-
specId: preferredPack.id,
109-
priority: letterVariant.priority,
110-
billingId: preferredPack.billingId,
106+
allocationDetails: {
107+
supplierSpec: {
108+
supplierId: selectedSupplierId,
109+
specId: preferredPack.id,
110+
priority: letterVariant.priority,
111+
billingId: preferredPack.billingId,
112+
},
113+
allocationStatus: {
114+
status: "PENDING",
115+
},
111116
},
112117
volumeGroupId: volumeGroup.id,
113118
};
@@ -118,7 +123,23 @@ async function getSupplierFromConfig(
118123
err: error,
119124
variantId: letterEvent.data.letterVariantId,
120125
});
121-
throw error;
126+
const supplierDetails: SupplierDetails = {
127+
allocationDetails: {
128+
supplierSpec: {
129+
supplierId: "unknown",
130+
specId: "unknown",
131+
priority: 0,
132+
billingId: "unknown",
133+
},
134+
allocationStatus: {
135+
status: "REJECTED",
136+
reasonCode: "NO_SUPPLIERS_AVAILABLE",
137+
reasonText: error instanceof Error ? error.message : "Unknown error",
138+
},
139+
},
140+
volumeGroupId: "unknown",
141+
};
142+
return supplierDetails;
122143
}
123144
}
124145

@@ -215,28 +236,32 @@ export default function createSupplierAllocatorHandler(deps: Deps): SQSHandler {
215236
letterEvent as PreparedEvents,
216237
deps,
217238
);
218-
const supplierSpec = supplierDetails?.supplierSpec;
219239

220240
deps.logger.info({
221241
description: "Resolved supplier details from config",
222242
supplierDetails,
223243
});
224-
225-
incrementAllocation(
226-
volumeGroupAllocations,
227-
supplierDetails.volumeGroupId,
228-
supplierDetails?.supplierSpec.supplierId,
229-
1,
230-
deps,
231-
);
244+
const supplierSpec = supplierDetails?.allocationDetails?.supplierSpec;
232245

233246
supplier = supplierSpec.supplierId;
234247
priority = String(supplierSpec.priority);
235248

236-
deps.logger.info({
237-
description: "Resolved supplier spec",
238-
supplierSpec,
239-
});
249+
if (
250+
supplierDetails.allocationDetails.allocationStatus.status ===
251+
"PENDING"
252+
) {
253+
incrementMetric(perAllocationSuccess, supplier, priority);
254+
255+
incrementAllocation(
256+
volumeGroupAllocations,
257+
supplierDetails.volumeGroupId,
258+
supplier,
259+
1,
260+
deps,
261+
);
262+
} else {
263+
incrementMetric(perAllocationFailure, supplier, priority);
264+
}
240265

241266
// Send to allocated letters queue
242267
const queueUrl = process.env.UPSERT_LETTERS_QUEUE_URL;
@@ -246,7 +271,7 @@ export default function createSupplierAllocatorHandler(deps: Deps): SQSHandler {
246271

247272
const queueMessage = {
248273
letterEvent,
249-
supplierSpec,
274+
allocationDetails: supplierDetails.allocationDetails,
250275
};
251276

252277
deps.logger.info({
@@ -261,8 +286,6 @@ export default function createSupplierAllocatorHandler(deps: Deps): SQSHandler {
261286
MessageBody: JSON.stringify(queueMessage),
262287
}),
263288
);
264-
265-
incrementMetric(perAllocationSuccess, supplier, priority);
266289
} catch (error) {
267290
deps.logger.error({
268291
description: "Error processing allocation of record",

lambdas/supplier-allocator/src/handler/types.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,19 @@ export type SupplierSpec = {
88
billingId: string;
99
};
1010

11-
export type SupplierDetails = {
11+
export type AllocationStatus = {
12+
status: string;
13+
reasonCode?: string;
14+
reasonText?: string;
15+
};
16+
17+
export type AllocationDetails = {
1218
supplierSpec: SupplierSpec;
19+
allocationStatus: AllocationStatus;
20+
};
21+
22+
export type SupplierDetails = {
23+
allocationDetails: AllocationDetails;
1324
volumeGroupId: string;
1425
};
1526

0 commit comments

Comments
 (0)