Skip to content

Commit a9d2fae

Browse files
committed
CCM-15185 List Letters uses new queue
1 parent 0aa2f41 commit a9d2fae

16 files changed

Lines changed: 327 additions & 422 deletions

File tree

infrastructure/terraform/components/api/locals.tf

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,19 @@ locals {
2020
destination_arn = "arn:aws:logs:${var.region}:${var.shared_infra_account_id}:destination:nhs-main-obs-firehose-logs"
2121

2222
common_lambda_env_vars = {
23-
APIM_CORRELATION_HEADER = "nhsd-correlation-id",
24-
DOWNLOAD_URL_TTL_SECONDS = 60
25-
EVENT_SOURCE = "/data-plane/supplier-api/${var.group}/${var.environment}/letters"
26-
LETTER_TTL_HOURS = 12960, # 18 months * 30 days * 24 hours
27-
LETTERS_TABLE_NAME = aws_dynamodb_table.letters.name,
28-
MI_TABLE_NAME = aws_dynamodb_table.mi.name,
29-
MI_TTL_HOURS = 2160 # 90 days * 24 hours
30-
SNS_TOPIC_ARN = "${module.eventsub.sns_topic.arn}",
31-
SUPPLIER_CONFIG_TABLE_NAME = aws_dynamodb_table.supplier-configuration.name
32-
SUPPLIER_ID_HEADER = "nhsd-supplier-id",
23+
APIM_CORRELATION_HEADER = "nhsd-correlation-id",
24+
DOWNLOAD_URL_TTL_SECONDS = 60
25+
EVENT_SOURCE = "/data-plane/supplier-api/${var.group}/${var.environment}/letters"
26+
LETTER_TTL_HOURS = 12960, # 18 months * 30 days * 24 hours
27+
LETTER_QUEUE_TABLE_NAME = aws_dynamodb_table.letter_queue.name,
28+
LETTER_QUEUE_TTL_HOURS = 168 # 7 days * 24 hours
29+
LETTER_QUEUE_VISIBILITY_TIMEOUT = 600, # 10 minutes * 60 seconds
30+
LETTERS_TABLE_NAME = aws_dynamodb_table.letters.name,
31+
MI_TABLE_NAME = aws_dynamodb_table.mi.name,
32+
MI_TTL_HOURS = 2160 # 90 days * 24 hours
33+
SNS_TOPIC_ARN = "${module.eventsub.sns_topic.arn}",
34+
SUPPLIER_CONFIG_TABLE_NAME = aws_dynamodb_table.supplier-configuration.name
35+
SUPPLIER_ID_HEADER = "nhsd-supplier-id",
3336
}
3437

3538
core_pdf_bucket_arn = "arn:aws:s3:::comms-${var.core_account_id}-eu-west-2-${var.core_environment}-api-stg-pdf-pipeline"

infrastructure/terraform/components/api/module_lambda_get_letters.tf

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,12 @@ data "aws_iam_policy_document" "get_letters_lambda" {
6363
"dynamodb:GetItem",
6464
"dynamodb:Query",
6565
"dynamodb:Scan",
66+
"dynamodb:UpdateItem",
6667
]
6768

6869
resources = [
69-
aws_dynamodb_table.letters.arn,
70-
"${aws_dynamodb_table.letters.arn}/index/supplierStatus-index"
70+
aws_dynamodb_table.letter_queue.arn,
71+
"${aws_dynamodb_table.letter_queue.arn}/index/queueSortOrder-index"
7172
]
7273
}
7374
}

internal/datastore/src/__test__/letter-queue-repository.test.ts

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ import {
77
setupDynamoDBContainer,
88
} from "./db";
99
import LetterQueueRepository from "../letter-queue-repository";
10-
import { InsertPendingLetter } from "../types";
10+
import { PendingLetterBase } from "../types";
1111
import { LetterAlreadyExistsError } from "../letter-already-exists-error";
1212
import { createTestLogger } from "./logs";
1313
import { LetterDoesNotExistError } from "../letter-does-not-exist-error";
1414

15+
type PendingLetterWithPriority = PendingLetterBase & { priority: number };
16+
1517
function createLetter(
16-
overrides: Partial<InsertPendingLetter> = {},
17-
): InsertPendingLetter {
18+
overrides: Partial<PendingLetterWithPriority> = {},
19+
): PendingLetterBase {
1820
return {
1921
letterId: "letter1",
2022
supplierId: "supplier1",
@@ -51,6 +53,7 @@ describe("LetterQueueRepository", () => {
5153
afterEach(async () => {
5254
await deleteTables(db);
5355
jest.useRealTimers();
56+
jest.restoreAllMocks();
5457
});
5558

5659
afterAll(async () => {
@@ -149,6 +152,116 @@ describe("LetterQueueRepository", () => {
149152
).rejects.toThrow("Cannot do operations on a non-existent table");
150153
});
151154
});
155+
156+
describe("getLetters", () => {
157+
it("filters by supplierId", async () => {
158+
await letterQueueRepository.putLetter(createLetter());
159+
160+
const letters = await letterQueueRepository.getLetters("supplier2", 1);
161+
162+
expect(letters).toHaveLength(0);
163+
});
164+
165+
it("filters by visibilityTimestamp", async () => {
166+
const pendingLetter = createLetter();
167+
await letterQueueRepository.putLetter(createLetter());
168+
await letterQueueRepository.updateVisibilityTimestamp(
169+
pendingLetter,
170+
new Date(Date.now() + 600_000),
171+
);
172+
173+
const letters = await letterQueueRepository.getLetters("supplier1", 1);
174+
175+
expect(letters).toHaveLength(0);
176+
});
177+
178+
it("returns letters in timestamp order", async () => {
179+
await letterQueueRepository.putLetter(
180+
createLetter({ letterId: "first-letter" }),
181+
);
182+
await letterQueueRepository.putLetter(
183+
createLetter({ letterId: "second-letter" }),
184+
);
185+
await letterQueueRepository.putLetter(
186+
createLetter({ letterId: "third-letter" }),
187+
);
188+
await letterQueueRepository.putLetter(
189+
createLetter({ letterId: "fourth-letter" }),
190+
);
191+
await letterQueueRepository.putLetter(
192+
createLetter({ letterId: "fifth-letter" }),
193+
);
194+
195+
const letters = await letterQueueRepository.getLetters("supplier1", 5);
196+
197+
expect(letters[0].letterId).toBe("first-letter");
198+
expect(letters[1].letterId).toBe("second-letter");
199+
expect(letters[2].letterId).toBe("third-letter");
200+
expect(letters[3].letterId).toBe("fourth-letter");
201+
expect(letters[4].letterId).toBe("fifth-letter");
202+
});
203+
204+
it("limits results to the supplied number", async () => {
205+
await letterQueueRepository.putLetter(
206+
createLetter({ letterId: "first-letter" }),
207+
);
208+
await letterQueueRepository.putLetter(
209+
createLetter({ letterId: "second-letter" }),
210+
);
211+
await letterQueueRepository.putLetter(
212+
createLetter({ letterId: "third-letter" }),
213+
);
214+
await letterQueueRepository.putLetter(
215+
createLetter({ letterId: "fourth-letter" }),
216+
);
217+
218+
const letters = await letterQueueRepository.getLetters("supplier1", 3);
219+
220+
expect(letters).toHaveLength(3);
221+
expect(letters[2].letterId).toBe("third-letter");
222+
});
223+
});
224+
225+
describe("updateVisibilityTimestamp", () => {
226+
it("updates the visibilityTimestamp on an existing letter", async () => {
227+
const pendingLetter =
228+
await letterQueueRepository.putLetter(createLetter());
229+
230+
await letterQueueRepository.updateVisibilityTimestamp(
231+
pendingLetter,
232+
new Date("2026-03-04T13:15:45.000Z"),
233+
);
234+
235+
const letter = await getLetter(db, "supplier1", "letter1");
236+
expect(letter?.visibilityTimestamp).toBe("2026-03-04T13:15:45.000Z");
237+
});
238+
239+
it("does nothing when the letter does not exist", async () => {
240+
await letterQueueRepository.updateVisibilityTimestamp(
241+
createLetter(),
242+
new Date(),
243+
);
244+
245+
expect(await letterExists(db, "supplier1", "letter1")).toBe(false);
246+
});
247+
248+
it("rethrows errors from DynamoDB when updating the letter", async () => {
249+
const misconfiguredRepository = new LetterQueueRepository(
250+
db.docClient,
251+
logger,
252+
{
253+
...db.config,
254+
letterQueueTableName: "nonexistent-table",
255+
},
256+
);
257+
await expect(
258+
misconfiguredRepository.updateVisibilityTimestamp(
259+
createLetter(),
260+
new Date(),
261+
),
262+
).rejects.toThrow("Cannot do operations on a non-existent table");
263+
});
264+
});
152265
});
153266

154267
async function getLetter(db: DBContext, supplierId: string, letterId: string) {

0 commit comments

Comments
 (0)