Skip to content

Commit df865a9

Browse files
Initial idempotency test
1 parent 500e7af commit df865a9

3 files changed

Lines changed: 178 additions & 0 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { expect, test } from "@playwright/test";
2+
import {
3+
getTotalAllocationForVolumeGroup,
4+
getTotalDailyAllocation,
5+
} from "tests/helpers/supplier-quotas-helper";
6+
import { supplierIdFromSupplierAllocatorLog } from "tests/helpers/aws-cloudwatch-helper";
7+
import { logger } from "tests/helpers/pino-logger";
8+
import { format } from "date-fns";
9+
import { toZonedTime } from "date-fns-tz";
10+
import { randomUUID } from "node:crypto";
11+
import { createPreparedV1Event } from "tests/helpers/event-fixtures";
12+
import { sendSnsBatchEvent } from "tests/helpers/send-sns-event";
13+
import { domain } from "zod/v4/core/regexes.cjs";
14+
15+
test.describe("Supplier Allocation Tests", () => {
16+
test("Verify that supplier allocations are correctly updated for a volume group", async () => {
17+
test.setTimeout(180_000);
18+
const volumeGroupId = "volumeGroup-test3";
19+
const originalTotalAllocation =
20+
await getTotalAllocationForVolumeGroup(volumeGroupId);
21+
logger.info(
22+
`Total allocation for volume group ${volumeGroupId}: ${originalTotalAllocation}`,
23+
);
24+
25+
const allocationDate = format(
26+
toZonedTime(new Date(), "Europe/London"),
27+
"yyyy-MM-dd",
28+
);
29+
const originalTotalDailyAllocation =
30+
await getTotalDailyAllocation(allocationDate);
31+
logger.info(
32+
`Total daily allocation for date ${allocationDate}: ${originalTotalDailyAllocation}`,
33+
);
34+
35+
// Create 2 messages with same domain id
36+
const domainId = randomUUID();
37+
38+
const message1 = createPreparedV1Event({ domainId });
39+
const message2 = createPreparedV1Event({ domainId });
40+
41+
const eventBatch = [message1, message2];
42+
const response = await sendSnsBatchEvent(
43+
eventBatch.map((event) => ({ id: event.id, message: event })),
44+
);
45+
expect(response.Successful).toHaveLength(eventBatch.length);
46+
47+
await supplierIdFromSupplierAllocatorLog(domainId);
48+
49+
const newTotalAllocation =
50+
await getTotalAllocationForVolumeGroup(volumeGroupId);
51+
logger.info(
52+
`New total allocation for volume group ${volumeGroupId}: ${newTotalAllocation}`,
53+
);
54+
expect(newTotalAllocation).toBe(originalTotalAllocation + 1);
55+
56+
const newTotalDailyAllocation =
57+
await getTotalDailyAllocation(allocationDate);
58+
logger.info(
59+
`New total daily allocation for date ${allocationDate}: ${newTotalDailyAllocation}`,
60+
);
61+
expect(newTotalDailyAllocation).toBe(originalTotalDailyAllocation + 1);
62+
});
63+
});

tests/constants/api-constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export const EVENT_SUBSCRIPTION_TOPIC_ARN =
1616
process.env.EVENT_SUBSCRIPTION_TOPIC_ARN ??
1717
`arn:aws:sns:${AWS_REGION}:${AWS_ACCOUNT_ID}:${EVENT_SUBSCRIPTION_TOPIC_NAME}`;
1818
export const LETTERQUEUE_TABLENAME = `nhs-${envName}-supapi-letter-queue`;
19+
export const SUPPLIER_QUOTAS_TABLENAME = `nhs-${envName}-supapi-supplier-quotas`;
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
2+
import {
3+
GetCommand,
4+
DynamoDBDocumentClient,
5+
QueryCommand,
6+
} from "@aws-sdk/lib-dynamodb";
7+
import {
8+
$Supplier,
9+
$VolumeGroup,
10+
} from "@nhsdigital/nhs-notify-event-schemas-supplier-config";
11+
import z from "zod";
12+
import { SUPPLIER_QUOTAS_TABLENAME, envName } from "../constants/api-constants";
13+
import { logger } from "./pino-logger";
14+
import { id } from "zod/v4/locales";
15+
16+
const ddb = new DynamoDBClient({});
17+
const docClient = DynamoDBDocumentClient.from(ddb);
18+
19+
export const overallAllocationSchema = z.object({
20+
id: z.string(),
21+
volumeGroup: z.string(),
22+
allocations: z.record(z.string(), z.number()),
23+
});
24+
25+
export const dailyAllocationSchema = z.object({
26+
id: z.string(),
27+
date: z.string(),
28+
allocations: z.record(z.string(), z.number()),
29+
});
30+
31+
export async function getTotalAllocationForVolumeGroup(
32+
volumeGroupId: string,
33+
): Promise<number> {
34+
try {
35+
const result = await docClient.send(
36+
new GetCommand({
37+
TableName: SUPPLIER_QUOTAS_TABLENAME,
38+
Key: { pk: "ENTITY#overall-allocation", sk: `ID#${volumeGroupId}` },
39+
}),
40+
);
41+
logger.info(`Selecting from table name: ${SUPPLIER_QUOTAS_TABLENAME}`);
42+
43+
if (!result.Item) {
44+
logger.warn(
45+
`No overall allocation found for volume group ${volumeGroupId}`,
46+
);
47+
return 0; // Default to 0 if no allocation record exists
48+
}
49+
// Strip DynamoDB keys before parsing
50+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
51+
const { pk, sk, ...item } = result.Item;
52+
const overallAllocation = overallAllocationSchema.parse(item);
53+
54+
logger.info(
55+
`Fetched overall allocation for volume group ${volumeGroupId}: ${JSON.stringify(overallAllocation)}`,
56+
);
57+
const { allocations } = overallAllocation;
58+
59+
const totalAllocation = Object.values(allocations).reduce(
60+
(sum, allocation) => sum + allocation,
61+
0,
62+
);
63+
logger.info(
64+
`Fetched overall allocation for volume group ${volumeGroupId}: ${totalAllocation}`,
65+
);
66+
return totalAllocation;
67+
} catch (error) {
68+
logger.error(
69+
`Error fetching overall allocation for volume group ${volumeGroupId}: ${error}`,
70+
);
71+
throw error;
72+
}
73+
}
74+
export async function getTotalDailyAllocation(
75+
allocationDate: string,
76+
): Promise<number> {
77+
try {
78+
const result = await docClient.send(
79+
new GetCommand({
80+
TableName: SUPPLIER_QUOTAS_TABLENAME,
81+
Key: { pk: "ENTITY#daily-allocation", sk: `ID#${allocationDate}` },
82+
}),
83+
);
84+
logger.info(`Selecting from table name: ${SUPPLIER_QUOTAS_TABLENAME}`);
85+
86+
if (!result.Item) {
87+
logger.warn(`No daily allocation found for date ${allocationDate}`);
88+
return 0; // Default to 0 if no allocation record exists
89+
}
90+
// Strip DynamoDB keys before parsing
91+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
92+
const { pk, sk, ...item } = result.Item;
93+
const dailyAllocation = dailyAllocationSchema.parse(item);
94+
95+
logger.info(
96+
`Fetched daily allocation for date ${allocationDate}: ${JSON.stringify(dailyAllocation)}`,
97+
);
98+
const { allocations } = dailyAllocation;
99+
100+
const totalAllocation = Object.values(allocations).reduce(
101+
(sum, allocation) => sum + allocation,
102+
0,
103+
);
104+
logger.info(
105+
`Fetched daily allocation for date ${allocationDate}: ${totalAllocation}`,
106+
);
107+
return totalAllocation;
108+
} catch (error) {
109+
logger.error(
110+
`Error fetching daily allocation for date ${allocationDate}: ${error}`,
111+
);
112+
throw error;
113+
}
114+
}

0 commit comments

Comments
 (0)