Skip to content

Commit e4aad0d

Browse files
review changes
1 parent e0a8d26 commit e4aad0d

7 files changed

Lines changed: 127 additions & 10 deletions

File tree

infrastructure/terraform/components/api/module_lambda_supplier_allocator.tf

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,22 @@ data "aws_iam_policy_document" "supplier_allocator_lambda" {
8383
}
8484

8585
statement {
86-
sid = "AllowDynamoDBAccess"
86+
sid = "AllowConfigDynamoDBAccess"
87+
effect = "Allow"
88+
89+
actions = [
90+
"dynamodb:GetItem",
91+
"dynamodb:Query",
92+
]
93+
94+
resources = [
95+
aws_dynamodb_table.supplier-configuration.arn,
96+
"${aws_dynamodb_table.supplier-configuration.arn}/index/*",
97+
]
98+
}
99+
100+
statement {
101+
sid = "AllowQuotaDynamoDBAccess"
87102
effect = "Allow"
88103

89104
actions = [
@@ -94,9 +109,7 @@ data "aws_iam_policy_document" "supplier_allocator_lambda" {
94109
]
95110

96111
resources = [
97-
aws_dynamodb_table.supplier-configuration.arn,
98112
aws_dynamodb_table.supplier-quotas.arn,
99-
"${aws_dynamodb_table.supplier-configuration.arn}/index/*",
100113
"${aws_dynamodb_table.supplier-quotas.arn}/index/*"
101114
]
102115
}

lambdas/supplier-allocator/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
"@types/aws-lambda": "^8.10.148",
1313
"aws-embedded-metrics": "^4.2.1",
1414
"aws-lambda": "^1.0.7",
15+
"date-fns": "^3.0.0",
16+
"date-fns-tz": "^3.0.0",
1517
"esbuild": "^0.27.2",
1618
"pino": "^9.7.0",
1719
"zod": "^4.1.11"

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,58 @@ describe("createSupplierAllocatorHandler", () => {
577577
},
578578
);
579579

580+
test("returns batch failure when no suppliers are found for pack specification", async () => {
581+
const preparedEvent = createPreparedV2Event();
582+
const evt: SQSEvent = createSQSEvent([
583+
createSqsRecord("msg1", JSON.stringify(preparedEvent)),
584+
]);
585+
586+
process.env.UPSERT_LETTERS_QUEUE_URL = "https://sqs.test.queue";
587+
588+
setupDefaultMocks();
589+
(allocationConfig.suppliersWithValidPack as jest.Mock).mockResolvedValue(
590+
[],
591+
);
592+
593+
const handler = createSupplierAllocatorHandler(mockedDeps);
594+
const result = await handler(evt, {} as any, {} as any);
595+
if (!result) throw new Error("expected BatchResponse, got void");
596+
597+
expect(result.batchItemFailures).toHaveLength(1);
598+
expect(result.batchItemFailures[0].itemIdentifier).toBe("msg1");
599+
expect((mockedDeps.logger.error as jest.Mock).mock.calls).toHaveLength(2);
600+
expect((mockedDeps.logger.error as jest.Mock).mock.calls[0][0]).toEqual(
601+
expect.objectContaining({
602+
description: "Error fetching supplier from config",
603+
err: new Error("No suppliers found for pack specification spec1"),
604+
variantId: "lv1",
605+
}),
606+
);
607+
});
608+
609+
test("does not call selectSupplierByFactor for suppliers with capacity when there are no suppliers with capacity", async () => {
610+
setupDefaultMocks();
611+
(
612+
allocationConfig.filterSuppliersWithCapacity as jest.Mock
613+
).mockResolvedValue([]);
614+
process.env.UPSERT_LETTERS_QUEUE_URL = "https://sqs.test.queue";
615+
616+
const evt: SQSEvent = createSQSEvent([
617+
createSqsRecord("msg1", JSON.stringify(createPreparedV2Event())),
618+
]);
619+
620+
const handler = createSupplierAllocatorHandler(mockedDeps);
621+
const result = await handler(evt, {} as any, {} as any);
622+
if (!result) throw new Error("expected BatchResponse, got void");
623+
624+
expect(result.batchItemFailures).toHaveLength(0);
625+
expect(allocationConfig.selectSupplierByFactor).toHaveBeenCalledWith(
626+
expect.any(Array),
627+
expect.any(Array),
628+
mockedDeps,
629+
);
630+
});
631+
580632
test("falls back to the second selectSupplierByFactor call when the first returns undefined", async () => {
581633
setupDefaultMocks();
582634
(allocationConfig.selectSupplierByFactor as jest.Mock)

lambdas/supplier-allocator/src/handler/__tests__/allocation-config.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,4 +961,14 @@ describe("selectSupplierByFactor", () => {
961961

962962
expect(result).toBe("supplier-5");
963963
});
964+
965+
it("should throw an error if no supplier factors are returned", async () => {
966+
(
967+
supplierQuotasService.calculateSupplierAllocatedFactor as jest.Mock
968+
).mockResolvedValue([]);
969+
970+
await expect(
971+
selectSupplierByFactor(mockSuppliers, mockSupplierAllocations, mockDeps),
972+
).rejects.toThrow("No supplier factors could be calculated for allocation");
973+
});
964974
});

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,25 @@ async function getSupplierFromConfig(
7373
deps,
7474
);
7575

76+
if (allSuppliersForPack.length === 0) {
77+
throw new Error(
78+
`No suppliers found for pack specification ${preferredPack.id}`,
79+
);
80+
}
81+
7682
const suppliersForPackWithCapacity: Supplier[] =
7783
await filterSuppliersWithCapacity(allSuppliersForPack, deps);
7884

79-
// selected supplier id is determined by first calling selectSupplierByFactor for suppliers with capacity and if nothing is returned tryong again with all suppliers for pack
85+
// selected supplier id is determined by first calling selectSupplierByFactor for suppliers with capacity
86+
// and if that returns nothing, try again with all suppliers for the pack
8087
const selectedSupplierId =
81-
(await selectSupplierByFactor(
82-
suppliersForPackWithCapacity,
83-
supplierAllocations,
84-
deps,
85-
)) ??
88+
(suppliersForPackWithCapacity.length > 0
89+
? await selectSupplierByFactor(
90+
suppliersForPackWithCapacity,
91+
supplierAllocations,
92+
deps,
93+
)
94+
: undefined) ??
8695
(await selectSupplierByFactor(
8796
allSuppliersForPack,
8897
supplierAllocations,

lambdas/supplier-allocator/src/handler/allocation-config.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
SupplierPack,
66
VolumeGroup,
77
} from "@nhsdigital/nhs-notify-event-schemas-supplier-config";
8+
import { format } from "date-fns";
9+
import { toZonedTime } from "date-fns-tz";
810
import {
911
filterPacksForLetter,
1012
getPackSpecification,
@@ -14,6 +16,7 @@ import {
1416
getSupplierPacks,
1517
} from "../services/supplier-config";
1618
import { calculateSupplierAllocatedFactor } from "../services/supplier-quotas";
19+
1720
import { Deps } from "../config/deps";
1821
import { PreparedEvents } from "./types";
1922

@@ -79,7 +82,10 @@ export async function filterSuppliersWithCapacity(
7982
suppliers: Supplier[],
8083
deps: Deps,
8184
): Promise<Supplier[]> {
82-
const dailyAllocationDate = new Date().toISOString().split("T")[0]; // Get current date in YYYY-MM-DD format
85+
const dailyAllocationDate = format(
86+
toZonedTime(new Date(), "Europe/London"),
87+
"yyyy-MM-dd",
88+
); // Get current date in YYYY-MM-DD format (London timezone)
8389
const dailyAllocation =
8490
await deps.supplierQuotasRepo.getDailyAllocation(dailyAllocationDate);
8591
if (dailyAllocation) {
@@ -103,6 +109,10 @@ export async function selectSupplierByFactor(
103109
const supplierFactors: { supplierId: string; factor: number }[] =
104110
await calculateSupplierAllocatedFactor(supplierAllocationsForPack, deps);
105111

112+
if (supplierFactors.length === 0) {
113+
throw new Error("No supplier factors could be calculated for allocation");
114+
}
115+
106116
deps.logger.info({
107117
description: "Calculated supplier factors for allocation",
108118
supplierFactors,

package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)