@@ -7,12 +7,12 @@ import {
77 setupDynamoDBContainer ,
88} from "./db" ;
99import LetterQueueRepository from "../letter-queue-repository" ;
10- import { InsertPendingLetter } from "../types" ;
10+ import { PendingLetterBase } from "../types" ;
1111import { LetterAlreadyExistsError } from "../letter-already-exists-error" ;
1212import { createTestLogger } from "./logs" ;
1313import { LetterDoesNotExistError } from "../letter-does-not-exist-error" ;
1414
15- function createLetter ( letterId = "letter1" ) : InsertPendingLetter {
15+ function createLetter ( letterId = "letter1" ) : PendingLetterBase {
1616 return {
1717 letterId,
1818 supplierId : "supplier1" ,
@@ -47,38 +47,22 @@ describe("LetterQueueRepository", () => {
4747 afterEach ( async ( ) => {
4848 await deleteTables ( db ) ;
4949 jest . useRealTimers ( ) ;
50+ jest . restoreAllMocks ( ) ;
5051 } ) ;
5152
5253 afterAll ( async ( ) => {
5354 await db . container . stop ( ) ;
5455 } ) ;
5556
56- function assertTtl ( ttl : number , before : number , after : number ) {
57- const expectedLower = Math . floor (
58- before / 1000 + 60 * 60 * db . config . letterQueueTtlHours ,
59- ) ;
60- const expectedUpper = Math . floor (
61- after / 1000 + 60 * 60 * db . config . lettersTtlHours ,
62- ) ;
63- expect ( ttl ) . toBeGreaterThanOrEqual ( expectedLower ) ;
64- expect ( ttl ) . toBeLessThanOrEqual ( expectedUpper ) ;
65- }
66-
6757 describe ( "putLetter" , ( ) => {
6858 it ( "adds a letter to the database" , async ( ) => {
69- const before = Date . now ( ) ;
59+ jest . useFakeTimers ( ) . setSystemTime ( new Date ( "2026-03-04T13:15:45.000Z" ) ) ;
7060
7161 const pendingLetter =
7262 await letterQueueRepository . putLetter ( createLetter ( ) ) ;
7363
74- const after = Date . now ( ) ;
75-
76- const timestampInMillis = new Date (
77- pendingLetter . queueTimestamp ,
78- ) . valueOf ( ) ;
79- expect ( timestampInMillis ) . toBeGreaterThanOrEqual ( before ) ;
80- expect ( timestampInMillis ) . toBeLessThanOrEqual ( after ) ;
81- assertTtl ( pendingLetter . ttl , before , after ) ;
64+ expect ( pendingLetter . queueTimestamp ) . toBe ( "2026-03-04T13:15:45.000Z" ) ;
65+ expect ( pendingLetter . ttl ) . toBe ( 1_772_633_745 ) ;
8266 expect ( await letterExists ( db , "supplier1" , "letter1" ) ) . toBe ( true ) ;
8367 } ) ;
8468
@@ -134,18 +118,112 @@ describe("LetterQueueRepository", () => {
134118 ) . rejects . toThrow ( "Cannot do operations on a non-existent table" ) ;
135119 } ) ;
136120 } ) ;
121+
122+ describe ( "getLetters" , ( ) => {
123+ it ( "filters by supplierId" , async ( ) => {
124+ await letterQueueRepository . putLetter ( createLetter ( ) ) ;
125+
126+ const letters = await letterQueueRepository . getLetters ( "supplier2" , 1 ) ;
127+
128+ expect ( letters ) . toHaveLength ( 0 ) ;
129+ } ) ;
130+
131+ it ( "filters by queueTimestamp" , async ( ) => {
132+ jest . useFakeTimers ( ) . setSystemTime ( new Date ( "2026-03-04T13:25:45.000Z" ) ) ;
133+ await letterQueueRepository . putLetter ( createLetter ( ) ) ;
134+ jest . useFakeTimers ( ) . setSystemTime ( new Date ( "2026-03-04T13:23:45.000Z" ) ) ;
135+
136+ const letters = await letterQueueRepository . getLetters ( "supplier1" , 1 ) ;
137+
138+ expect ( letters ) . toHaveLength ( 0 ) ;
139+ } ) ;
140+
141+ it ( "returns letters in timestamp order" , async ( ) => {
142+ await letterQueueRepository . putLetter ( createLetter ( "first-letter" ) ) ;
143+ await letterQueueRepository . putLetter ( createLetter ( "second-letter" ) ) ;
144+ await letterQueueRepository . putLetter ( createLetter ( "third-letter" ) ) ;
145+ await letterQueueRepository . putLetter ( createLetter ( "fourth-letter" ) ) ;
146+ await letterQueueRepository . putLetter ( createLetter ( "fifth-letter" ) ) ;
147+
148+ const letters = await letterQueueRepository . getLetters ( "supplier1" , 5 ) ;
149+
150+ expect ( letters [ 0 ] . letterId ) . toBe ( "first-letter" ) ;
151+ expect ( letters [ 1 ] . letterId ) . toBe ( "second-letter" ) ;
152+ expect ( letters [ 2 ] . letterId ) . toBe ( "third-letter" ) ;
153+ expect ( letters [ 3 ] . letterId ) . toBe ( "fourth-letter" ) ;
154+ expect ( letters [ 4 ] . letterId ) . toBe ( "fifth-letter" ) ;
155+ } ) ;
156+
157+ it ( "limits results to the supplied number" , async ( ) => {
158+ await letterQueueRepository . putLetter ( createLetter ( "first-letter" ) ) ;
159+ await letterQueueRepository . putLetter ( createLetter ( "second-letter" ) ) ;
160+ await letterQueueRepository . putLetter ( createLetter ( "third-letter" ) ) ;
161+ await letterQueueRepository . putLetter ( createLetter ( "fourth-letter" ) ) ;
162+
163+ const letters = await letterQueueRepository . getLetters ( "supplier1" , 3 ) ;
164+
165+ expect ( letters ) . toHaveLength ( 3 ) ;
166+ expect ( letters [ 2 ] . letterId ) . toBe ( "third-letter" ) ;
167+ } ) ;
168+ } ) ;
169+
170+ describe ( "updateLetterTimestamp" , ( ) => {
171+ it ( "updates the queueTimestamp on an existing letter" , async ( ) => {
172+ const pendingLetter =
173+ await letterQueueRepository . putLetter ( createLetter ( ) ) ;
174+
175+ await letterQueueRepository . updateLetterTimestamp (
176+ pendingLetter ,
177+ new Date ( "2026-03-04T13:15:45.000Z" ) ,
178+ ) ;
179+
180+ const letter = await getLetter ( db , "supplier1" , "letter1" ) ;
181+ expect ( letter ?. queueTimestamp ) . toBe ( "2026-03-04T13:15:45.000Z" ) ;
182+ } ) ;
183+
184+ it ( "does nothing when the letter does not exist" , async ( ) => {
185+ await letterQueueRepository . updateLetterTimestamp (
186+ createLetter ( ) ,
187+ new Date ( ) ,
188+ ) ;
189+
190+ expect ( await letterExists ( db , "supplier1" , "letter1" ) ) . toBe ( false ) ;
191+ } ) ;
192+
193+ it ( "rethrows errors from DynamoDB when updating the letter" , async ( ) => {
194+ const misconfiguredRepository = new LetterQueueRepository (
195+ db . docClient ,
196+ logger ,
197+ {
198+ ...db . config ,
199+ letterQueueTableName : "nonexistent-table" ,
200+ } ,
201+ ) ;
202+ await expect (
203+ misconfiguredRepository . updateLetterTimestamp (
204+ createLetter ( ) ,
205+ new Date ( ) ,
206+ ) ,
207+ ) . rejects . toThrow ( "Cannot do operations on a non-existent table" ) ;
208+ } ) ;
209+ } ) ;
137210} ) ;
138211
139- async function letterExists (
140- db : DBContext ,
141- supplierId : string ,
142- letterId : string ,
143- ) : Promise < boolean > {
212+ async function getLetter ( db : DBContext , supplierId : string , letterId : string ) {
144213 const result = await db . docClient . send (
145214 new GetCommand ( {
146215 TableName : db . config . letterQueueTableName ,
147216 Key : { supplierId, letterId } ,
148217 } ) ,
149218 ) ;
150- return result . Item !== undefined ;
219+ return result . Item ;
220+ }
221+
222+ async function letterExists (
223+ db : DBContext ,
224+ supplierId : string ,
225+ letterId : string ,
226+ ) : Promise < boolean > {
227+ const letter = await getLetter ( db , supplierId , letterId ) ;
228+ return letter !== undefined ;
151229}
0 commit comments