Skip to content

Commit 7ec7e85

Browse files
new unit tests
1 parent 1ba029f commit 7ec7e85

8 files changed

Lines changed: 369 additions & 116 deletions

File tree

internal/datastore/jest.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ export const baseJestConfig: Config = {
3131

3232
coveragePathIgnorePatterns: ["/__tests__/"],
3333
transform: { "^.+\\.ts$": "ts-jest" },
34+
transformIgnorePatterns: [
35+
"node_modules/(?!(@nhsdigital/nhs-notify-event-schemas-supplier-config)/)",
36+
],
3437
testPathIgnorePatterns: [".build"],
3538
testMatch: ["**/?(*.)+(spec|test).[jt]s?(x)"],
3639

internal/datastore/src/__test__/db.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export async function setupDynamoDBContainer() {
3636
lettersTtlHours: 1,
3737
letterQueueTtlHours: 1,
3838
miTtlHours: 1,
39+
supplierConfigTableName: "supplier-config",
3940
};
4041

4142
return {
@@ -145,6 +146,31 @@ const createLetterQueueTableCommand = new CreateTableCommand({
145146
{ AttributeName: "queueTimestamp", AttributeType: "S" },
146147
],
147148
});
149+
const createSupplierConfigTableCommand = new CreateTableCommand({
150+
TableName: "supplier-config",
151+
BillingMode: "PAY_PER_REQUEST",
152+
KeySchema: [
153+
{ AttributeName: "PK", KeyType: "HASH" }, // Partition key
154+
{ AttributeName: "SK", KeyType: "RANGE" }, // Sort key
155+
],
156+
GlobalSecondaryIndexes: [
157+
{
158+
IndexName: "volumeGroup-index",
159+
KeySchema: [
160+
{ AttributeName: "PK", KeyType: "HASH" }, // Partition key for GSI
161+
{ AttributeName: "volumeGroup", KeyType: "RANGE" }, // Sort key for GSI
162+
],
163+
Projection: {
164+
ProjectionType: "ALL",
165+
},
166+
},
167+
],
168+
AttributeDefinitions: [
169+
{ AttributeName: "PK", AttributeType: "S" },
170+
{ AttributeName: "SK", AttributeType: "S" },
171+
{ AttributeName: "volumeGroup", AttributeType: "S" },
172+
],
173+
});
148174

149175
export async function createTables(context: DBContext) {
150176
const { ddbClient } = context;
@@ -155,6 +181,7 @@ export async function createTables(context: DBContext) {
155181
await ddbClient.send(createMITableCommand);
156182
await ddbClient.send(createSupplierTableCommand);
157183
await ddbClient.send(createLetterQueueTableCommand);
184+
await ddbClient.send(createSupplierConfigTableCommand);
158185
}
159186

160187
export async function deleteTables(context: DBContext) {
@@ -165,6 +192,7 @@ export async function deleteTables(context: DBContext) {
165192
"management-info",
166193
"suppliers",
167194
"letter-queue",
195+
"supplier-config",
168196
]) {
169197
await ddbClient.send(
170198
new DeleteTableCommand({
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
import { Logger } from "pino";
2+
import { PutCommand } from "@aws-sdk/lib-dynamodb";
3+
import {
4+
DBContext,
5+
createTables,
6+
deleteTables,
7+
setupDynamoDBContainer,
8+
} from "./db";
9+
import { LogStream, createTestLogger } from "./logs";
10+
import { SupplierConfigRepository } from "../supplier-config-repository";
11+
12+
function createLetterVariantItem(variantId: string) {
13+
return {
14+
PK: "LETTER_VARIANT",
15+
SK: variantId,
16+
id: variantId,
17+
name: `Variant ${variantId}`,
18+
description: `Description for variant ${variantId}`,
19+
type: "STANDARD",
20+
status: "PROD",
21+
volumeGroupId: `group-${variantId}`,
22+
packSpecificationIds: [`pack-spec-${variantId}`],
23+
};
24+
}
25+
26+
function createVolumeGroupItem(groupId: string, status = "PROD") {
27+
const startDate = new Date(Date.now() - 24 * 1000 * 60 * 60)
28+
.toISOString()
29+
.split("T")[0]; // Started an hour ago to ensure it's active based on start date. Tests can override this if needed.
30+
const endDate = new Date(Date.now() + 24 * 1000 * 60 * 60)
31+
.toISOString()
32+
.split("T")[0]; // Ends in an hour to ensure it's active based on end date. Tests can override this if needed.
33+
return {
34+
PK: "VOLUME_GROUP",
35+
SK: groupId,
36+
id: groupId,
37+
name: `Volume Group ${groupId}`,
38+
description: `Description for volume group ${groupId}`,
39+
status,
40+
startDate,
41+
endDate,
42+
};
43+
}
44+
45+
function createSupplierAllocationItem(
46+
allocationId: string,
47+
groupId: string,
48+
supplier: string,
49+
) {
50+
return {
51+
PK: `SUPPLIER_ALLOCATION`,
52+
SK: allocationId,
53+
id: allocationId,
54+
status: "PROD",
55+
volumeGroup: groupId,
56+
supplier,
57+
allocationPercentage: 50,
58+
};
59+
}
60+
61+
function createSupplierItem(supplierId: string) {
62+
return {
63+
PK: "SUPPLIER",
64+
SK: supplierId,
65+
id: supplierId,
66+
name: `Supplier ${supplierId}`,
67+
channelType: "LETTER",
68+
dailyCapacity: 1000,
69+
status: "PROD",
70+
};
71+
}
72+
73+
jest.setTimeout(30_000);
74+
75+
describe("SupplierConfigRepository", () => {
76+
let dbContext: DBContext;
77+
let repository: SupplierConfigRepository;
78+
let logStream: LogStream;
79+
let logger: Logger;
80+
81+
// Database tests can take longer, especially with setup and teardown
82+
beforeAll(async () => {
83+
dbContext = await setupDynamoDBContainer();
84+
});
85+
86+
beforeEach(async () => {
87+
await createTables(dbContext);
88+
({ logStream, logger } = createTestLogger());
89+
repository = new SupplierConfigRepository(
90+
dbContext.docClient,
91+
logger,
92+
dbContext.config,
93+
);
94+
});
95+
96+
afterEach(async () => {
97+
await deleteTables(dbContext);
98+
jest.useRealTimers();
99+
});
100+
101+
afterAll(async () => {
102+
await dbContext.container.stop();
103+
});
104+
105+
test("getLetterVariant returns correct details for existing variant", async () => {
106+
const variantId = "variant-123";
107+
await dbContext.docClient.send(
108+
new PutCommand({
109+
TableName: dbContext.config.supplierConfigTableName,
110+
Item: createLetterVariantItem(variantId),
111+
}),
112+
);
113+
114+
const result = await repository.getLetterVariant(variantId);
115+
116+
expect(result.id).toBe(variantId);
117+
expect(result.name).toBe(`Variant ${variantId}`);
118+
expect(result.description).toBe(`Description for variant ${variantId}`);
119+
expect(result.type).toBe("STANDARD");
120+
expect(result.status).toBe("PROD");
121+
expect(result.volumeGroupId).toBe(`group-${variantId}`);
122+
expect(result.packSpecificationIds).toEqual([`pack-spec-${variantId}`]);
123+
124+
expect(logStream.logs).toEqual([]);
125+
});
126+
127+
test("getLetterVariant throws error for non-existent variant", async () => {
128+
const variantId = "non-existent-variant";
129+
130+
await expect(repository.getLetterVariant(variantId)).rejects.toThrow(
131+
`No letter variant details found for id ${variantId}`,
132+
);
133+
134+
expect(
135+
logStream.logs.some((log) =>
136+
log.includes("No letter variant found for id"),
137+
),
138+
).toBe(true);
139+
});
140+
141+
test("getVolumeGroup returns correct details for existing group", async () => {
142+
const groupId = "group-123";
143+
await dbContext.docClient.send(
144+
new PutCommand({
145+
TableName: dbContext.config.supplierConfigTableName,
146+
Item: createVolumeGroupItem(groupId),
147+
}),
148+
);
149+
150+
const result = await repository.getVolumeGroup(groupId);
151+
152+
expect(result.id).toBe(groupId);
153+
expect(result.name).toBe(`Volume Group ${groupId}`);
154+
expect(result.description).toBe(`Description for volume group ${groupId}`);
155+
expect(result.status).toBe("PROD");
156+
expect(new Date(result.startDate).getTime()).toBeLessThan(Date.now());
157+
expect(new Date(result.endDate!).getTime()).toBeGreaterThan(Date.now());
158+
159+
expect(logStream.logs).toEqual([]);
160+
});
161+
162+
test("getVolumeGroup throws error for non-existent group", async () => {
163+
const groupId = "non-existent-group";
164+
165+
await expect(repository.getVolumeGroup(groupId)).rejects.toThrow(
166+
`No volume group details found for id ${groupId}`,
167+
);
168+
169+
expect(
170+
logStream.logs.some((log) =>
171+
log.includes("No volume group found for id"),
172+
),
173+
).toBe(true);
174+
});
175+
176+
test("getSupplierAllocationsForVolumeGroup returns correct allocations", async () => {
177+
const allocationId = "allocation-123";
178+
const groupId = "group-123";
179+
const supplierId = "supplier-123";
180+
181+
await dbContext.docClient.send(
182+
new PutCommand({
183+
TableName: dbContext.config.supplierConfigTableName,
184+
Item: createSupplierAllocationItem(allocationId, groupId, supplierId),
185+
}),
186+
);
187+
188+
const result =
189+
await repository.getSupplierAllocationsForVolumeGroup(groupId);
190+
191+
expect(result).toEqual([
192+
{
193+
id: allocationId,
194+
status: "PROD",
195+
volumeGroup: groupId,
196+
supplier: supplierId,
197+
allocationPercentage: 50,
198+
},
199+
]);
200+
201+
expect(logStream.logs).toEqual([]);
202+
});
203+
204+
test("getSupplierAllocationsForVolumeGroup returns multiple allocations", async () => {
205+
const allocationId1 = "allocation-123";
206+
const allocationId2 = "allocation-456";
207+
const groupId = "group-123";
208+
const supplierId1 = "supplier-123";
209+
const supplierId2 = "supplier-456";
210+
211+
await dbContext.docClient.send(
212+
new PutCommand({
213+
TableName: dbContext.config.supplierConfigTableName,
214+
Item: createSupplierAllocationItem(allocationId1, groupId, supplierId1),
215+
}),
216+
);
217+
218+
await dbContext.docClient.send(
219+
new PutCommand({
220+
TableName: dbContext.config.supplierConfigTableName,
221+
Item: createSupplierAllocationItem(allocationId2, groupId, supplierId2),
222+
}),
223+
);
224+
225+
const result =
226+
await repository.getSupplierAllocationsForVolumeGroup(groupId);
227+
228+
// order of allocations should not matter, just that both are present
229+
expect(result).toHaveLength(2);
230+
expect(result).toEqual(
231+
expect.arrayContaining([
232+
{
233+
id: allocationId1,
234+
status: "PROD",
235+
volumeGroup: groupId,
236+
supplier: supplierId1,
237+
allocationPercentage: 50,
238+
},
239+
{
240+
id: allocationId2,
241+
status: "PROD",
242+
volumeGroup: groupId,
243+
supplier: supplierId2,
244+
allocationPercentage: 50,
245+
},
246+
]),
247+
);
248+
expect(logStream.logs).toEqual([]);
249+
});
250+
251+
test("getSupplierAllocationsForVolumeGroup throws error for non-existent group", async () => {
252+
const groupId = "non-existent-group";
253+
254+
await expect(
255+
repository.getSupplierAllocationsForVolumeGroup(groupId),
256+
).rejects.toThrow(
257+
`No active supplier allocations found for volume group id ${groupId}`,
258+
);
259+
260+
expect(
261+
logStream.logs.some((log) =>
262+
log.includes("No supplier allocations found for volume group id"),
263+
),
264+
).toBe(true);
265+
});
266+
267+
test("getSuppliersDetails returns correct supplier details", async () => {
268+
const supplierId = "supplier-123";
269+
270+
await dbContext.docClient.send(
271+
new PutCommand({
272+
TableName: dbContext.config.supplierConfigTableName,
273+
Item: createSupplierItem(supplierId),
274+
}),
275+
);
276+
277+
const result = await repository.getSuppliersDetails([supplierId]);
278+
279+
expect(result).toEqual([
280+
{
281+
id: supplierId,
282+
name: `Supplier ${supplierId}`,
283+
channelType: "LETTER",
284+
dailyCapacity: 1000,
285+
status: "PROD",
286+
},
287+
]);
288+
expect(logStream.logs).toEqual([]);
289+
});
290+
291+
test("getSuppliersDetails throws error for non-existent supplier", async () => {
292+
const supplierId = "non-existent-supplier";
293+
294+
await expect(repository.getSuppliersDetails([supplierId])).rejects.toThrow(
295+
`Supplier with id ${supplierId} not found`,
296+
);
297+
298+
expect(
299+
logStream.logs.some((log) => log.includes("No supplier found for id")),
300+
).toBe(true);
301+
});
302+
});

internal/datastore/src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export type DatastoreConfig = {
88
lettersTtlHours: number;
99
letterQueueTtlHours: number;
1010
miTtlHours: number;
11+
supplierConfigTableName: string;
1112
};

0 commit comments

Comments
 (0)