Skip to content

Commit 0bddf56

Browse files
CCM-13378: Handle No Supplier Available
1 parent e4aad0d commit 0bddf56

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
@@ -112,11 +112,16 @@ async function getSupplierFromConfig(
112112
});
113113

114114
const supplierDetails: SupplierDetails = {
115-
supplierSpec: {
116-
supplierId: selectedSupplierId,
117-
specId: preferredPack.id,
118-
priority: letterVariant.priority,
119-
billingId: preferredPack.billingId,
115+
allocationDetails: {
116+
supplierSpec: {
117+
supplierId: selectedSupplierId,
118+
specId: preferredPack.id,
119+
priority: letterVariant.priority,
120+
billingId: preferredPack.billingId,
121+
},
122+
allocationStatus: {
123+
status: "PENDING",
124+
},
120125
},
121126
volumeGroupId: volumeGroup.id,
122127
};
@@ -127,7 +132,23 @@ async function getSupplierFromConfig(
127132
err: error,
128133
variantId: letterEvent.data.letterVariantId,
129134
});
130-
throw error;
135+
const supplierDetails: SupplierDetails = {
136+
allocationDetails: {
137+
supplierSpec: {
138+
supplierId: "unknown",
139+
specId: "unknown",
140+
priority: 0,
141+
billingId: "unknown",
142+
},
143+
allocationStatus: {
144+
status: "REJECTED",
145+
reasonCode: "NO_SUPPLIERS_AVAILABLE",
146+
reasonText: error instanceof Error ? error.message : "Unknown error",
147+
},
148+
},
149+
volumeGroupId: "unknown",
150+
};
151+
return supplierDetails;
131152
}
132153
}
133154

@@ -224,28 +245,32 @@ export default function createSupplierAllocatorHandler(deps: Deps): SQSHandler {
224245
letterEvent as PreparedEvents,
225246
deps,
226247
);
227-
const supplierSpec = supplierDetails?.supplierSpec;
228248

229249
deps.logger.info({
230250
description: "Resolved supplier details from config",
231251
supplierDetails,
232252
});
233-
234-
incrementAllocation(
235-
volumeGroupAllocations,
236-
supplierDetails.volumeGroupId,
237-
supplierDetails?.supplierSpec.supplierId,
238-
1,
239-
deps,
240-
);
253+
const supplierSpec = supplierDetails?.allocationDetails?.supplierSpec;
241254

242255
supplier = supplierSpec.supplierId;
243256
priority = String(supplierSpec.priority);
244257

245-
deps.logger.info({
246-
description: "Resolved supplier spec",
247-
supplierSpec,
248-
});
258+
if (
259+
supplierDetails.allocationDetails.allocationStatus.status ===
260+
"PENDING"
261+
) {
262+
incrementMetric(perAllocationSuccess, supplier, priority);
263+
264+
incrementAllocation(
265+
volumeGroupAllocations,
266+
supplierDetails.volumeGroupId,
267+
supplier,
268+
1,
269+
deps,
270+
);
271+
} else {
272+
incrementMetric(perAllocationFailure, supplier, priority);
273+
}
249274

250275
// Send to allocated letters queue
251276
const queueUrl = process.env.UPSERT_LETTERS_QUEUE_URL;
@@ -255,7 +280,7 @@ export default function createSupplierAllocatorHandler(deps: Deps): SQSHandler {
255280

256281
const queueMessage = {
257282
letterEvent,
258-
supplierSpec,
283+
allocationDetails: supplierDetails.allocationDetails,
259284
};
260285

261286
deps.logger.info({
@@ -270,8 +295,6 @@ export default function createSupplierAllocatorHandler(deps: Deps): SQSHandler {
270295
MessageBody: JSON.stringify(queueMessage),
271296
}),
272297
);
273-
274-
incrementMetric(perAllocationSuccess, supplier, priority);
275298
} catch (error) {
276299
deps.logger.error({
277300
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)