@@ -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,134 @@ 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 ( "returns letters in timestamp order" , async ( ) => {
132+ await letterQueueRepository . putLetter ( createLetter ( "first-letter" ) ) ;
133+ await letterQueueRepository . putLetter ( createLetter ( "second-letter" ) ) ;
134+ await letterQueueRepository . putLetter ( createLetter ( "third-letter" ) ) ;
135+ await letterQueueRepository . putLetter ( createLetter ( "fourth-letter" ) ) ;
136+ await letterQueueRepository . putLetter ( createLetter ( "fifth-letter" ) ) ;
137+
138+ const letters = await letterQueueRepository . getLetters ( "supplier1" , 5 ) ;
139+
140+ expect ( letters [ 0 ] . letterId ) . toBe ( "first-letter" ) ;
141+ expect ( letters [ 1 ] . letterId ) . toBe ( "second-letter" ) ;
142+ expect ( letters [ 2 ] . letterId ) . toBe ( "third-letter" ) ;
143+ expect ( letters [ 3 ] . letterId ) . toBe ( "fourth-letter" ) ;
144+ expect ( letters [ 4 ] . letterId ) . toBe ( "fifth-letter" ) ;
145+ } ) ;
146+
147+ it ( "limits results to the supplied number" , async ( ) => {
148+ await letterQueueRepository . putLetter ( createLetter ( "first-letter" ) ) ;
149+ await letterQueueRepository . putLetter ( createLetter ( "second-letter" ) ) ;
150+ await letterQueueRepository . putLetter ( createLetter ( "third-letter" ) ) ;
151+ await letterQueueRepository . putLetter ( createLetter ( "fourth-letter" ) ) ;
152+
153+ const letters = await letterQueueRepository . getLetters ( "supplier1" , 3 ) ;
154+
155+ expect ( letters ) . toHaveLength ( 3 ) ;
156+ expect ( letters [ 2 ] . letterId ) . toBe ( "third-letter" ) ;
157+ } ) ;
158+ } ) ;
159+
160+ describe ( "updateLetterTimestamp" , ( ) => {
161+ it ( "updates the queueTimestamp on an existing letter" , async ( ) => {
162+ jest . useFakeTimers ( ) . setSystemTime ( new Date ( "2026-03-04T13:15:45.000Z" ) ) ;
163+ await letterQueueRepository . putLetter ( createLetter ( ) ) ;
164+
165+ await letterQueueRepository . updateLetterTimestamp (
166+ "supplier1" ,
167+ "letter1" ,
168+ 600 ,
169+ ) ;
170+
171+ const letter = await getLetter ( db , "supplier1" , "letter1" ) ;
172+ expect ( letter ?. queueTimestamp ) . toBe ( "2026-03-04T13:25:45.000Z" ) ;
173+ } ) ;
174+
175+ it ( "throws LetterDoesNotExistError when the letter does not exist" , async ( ) => {
176+ await expect (
177+ letterQueueRepository . updateLetterTimestamp ( "supplier1" , "letter1" , 60 ) ,
178+ ) . rejects . toThrow ( LetterDoesNotExistError ) ;
179+ } ) ;
180+
181+ it ( "does nothing when the letter is deleted before it can be updated" , async ( ) => {
182+ jest . spyOn ( db . docClient , "send" ) . mockImplementationOnce ( ( _ ) => ( {
183+ // Fake the existence of the letter for the GetCommand
184+ Item : {
185+ ...createLetter ( ) ,
186+ queueTimestamp : "2026-03-04T13:15:45.000Z" ,
187+ } ,
188+ } ) ) ;
189+
190+ await letterQueueRepository . updateLetterTimestamp (
191+ "supplier1" ,
192+ "letter1" ,
193+ 60 ,
194+ ) ;
195+
196+ expect ( await letterExists ( db , "supplier1" , "letter1" ) ) . toBe ( false ) ;
197+ } ) ;
198+
199+ it ( "rethrows errors from DynamoDB when getting the letter" , async ( ) => {
200+ const misconfiguredRepository = new LetterQueueRepository (
201+ db . docClient ,
202+ logger ,
203+ {
204+ ...db . config ,
205+ letterQueueTableName : "nonexistent-table" ,
206+ } ,
207+ ) ;
208+ await expect (
209+ misconfiguredRepository . updateLetterTimestamp (
210+ "supplier1" ,
211+ "letter1" ,
212+ 60 ,
213+ ) ,
214+ ) . rejects . toThrow ( "Cannot do operations on a non-existent table" ) ;
215+ } ) ;
216+
217+ it ( "rethrows errors from DynamoDB when updating the error" , async ( ) => {
218+ await letterQueueRepository . putLetter ( createLetter ( ) ) ;
219+ const originalSend = db . docClient . send . bind ( db . docClient ) ;
220+ jest
221+ . spyOn ( db . docClient , "send" )
222+ . mockImplementationOnce ( originalSend )
223+ . mockImplementationOnce ( ( _ ) => {
224+ throw new Error ( "error" ) ;
225+ } ) ;
226+
227+ await expect (
228+ letterQueueRepository . updateLetterTimestamp ( "supplier1" , "letter1" , 60 ) ,
229+ ) . rejects . toThrow ( "error" ) ;
230+ } ) ;
231+ } ) ;
137232} ) ;
138233
139- async function letterExists (
140- db : DBContext ,
141- supplierId : string ,
142- letterId : string ,
143- ) : Promise < boolean > {
234+ async function getLetter ( db : DBContext , supplierId : string , letterId : string ) {
144235 const result = await db . docClient . send (
145236 new GetCommand ( {
146237 TableName : db . config . letterQueueTableName ,
147238 Key : { supplierId, letterId } ,
148239 } ) ,
149240 ) ;
150- return result . Item !== undefined ;
241+ return result . Item ;
242+ }
243+
244+ async function letterExists (
245+ db : DBContext ,
246+ supplierId : string ,
247+ letterId : string ,
248+ ) : Promise < boolean > {
249+ const letter = await getLetter ( db , supplierId , letterId ) ;
250+ return letter !== undefined ;
151251}
0 commit comments