@@ -51,6 +51,7 @@ describe("LetterQueueRepository", () => {
5151 afterEach ( async ( ) => {
5252 await deleteTables ( db ) ;
5353 jest . useRealTimers ( ) ;
54+ jest . restoreAllMocks ( ) ;
5455 } ) ;
5556
5657 afterAll ( async ( ) => {
@@ -149,6 +150,196 @@ describe("LetterQueueRepository", () => {
149150 ) . rejects . toThrow ( "Cannot do operations on a non-existent table" ) ;
150151 } ) ;
151152 } ) ;
153+
154+ describe ( "getLetters" , ( ) => {
155+ it ( "filters by supplierId" , async ( ) => {
156+ await letterQueueRepository . putLetter ( createLetter ( ) ) ;
157+
158+ const letters = await letterQueueRepository . getLetters ( "supplier2" , 1 ) ;
159+
160+ expect ( letters ) . toHaveLength ( 0 ) ;
161+ } ) ;
162+
163+ it ( "filters by visibilityTimestamp" , async ( ) => {
164+ const pendingLetter = createLetter ( ) ;
165+ await letterQueueRepository . putLetter ( createLetter ( ) ) ;
166+ await letterQueueRepository . updateVisibilityTimestamp (
167+ pendingLetter ,
168+ new Date ( Date . now ( ) + 600_000 ) ,
169+ ) ;
170+
171+ const letters = await letterQueueRepository . getLetters ( "supplier1" , 1 ) ;
172+
173+ expect ( letters ) . toHaveLength ( 0 ) ;
174+ } ) ;
175+
176+ it ( "returns letters in timestamp order" , async ( ) => {
177+ jest . useFakeTimers ( ) . setSystemTime ( new Date ( ) ) ;
178+ await letterQueueRepository . putLetter (
179+ createLetter ( { letterId : "first-letter" } ) ,
180+ ) ;
181+ jest . advanceTimersByTime ( 1 ) ;
182+ await letterQueueRepository . putLetter (
183+ createLetter ( { letterId : "second-letter" } ) ,
184+ ) ;
185+ jest . advanceTimersByTime ( 1 ) ;
186+ await letterQueueRepository . putLetter (
187+ createLetter ( { letterId : "third-letter" } ) ,
188+ ) ;
189+ jest . advanceTimersByTime ( 1 ) ;
190+ await letterQueueRepository . putLetter (
191+ createLetter ( { letterId : "fourth-letter" } ) ,
192+ ) ;
193+ jest . advanceTimersByTime ( 1 ) ;
194+ await letterQueueRepository . putLetter (
195+ createLetter ( { letterId : "fifth-letter" } ) ,
196+ ) ;
197+ jest . advanceTimersByTime ( 1 ) ;
198+
199+ const letters = await letterQueueRepository . getLetters ( "supplier1" , 5 ) ;
200+
201+ expect ( letters [ 0 ] . letterId ) . toBe ( "first-letter" ) ;
202+ expect ( letters [ 1 ] . letterId ) . toBe ( "second-letter" ) ;
203+ expect ( letters [ 2 ] . letterId ) . toBe ( "third-letter" ) ;
204+ expect ( letters [ 3 ] . letterId ) . toBe ( "fourth-letter" ) ;
205+ expect ( letters [ 4 ] . letterId ) . toBe ( "fifth-letter" ) ;
206+ } ) ;
207+
208+ it ( "limits results to the supplied number" , async ( ) => {
209+ await letterQueueRepository . putLetter (
210+ createLetter ( { letterId : "first-letter" } ) ,
211+ ) ;
212+ await letterQueueRepository . putLetter (
213+ createLetter ( { letterId : "second-letter" } ) ,
214+ ) ;
215+ await letterQueueRepository . putLetter (
216+ createLetter ( { letterId : "third-letter" } ) ,
217+ ) ;
218+ await letterQueueRepository . putLetter (
219+ createLetter ( { letterId : "fourth-letter" } ) ,
220+ ) ;
221+
222+ const letters = await letterQueueRepository . getLetters ( "supplier1" , 3 ) ;
223+
224+ expect ( letters ) . toHaveLength ( 3 ) ;
225+ expect ( letters [ 2 ] . letterId ) . toBe ( "third-letter" ) ;
226+ } ) ;
227+
228+ it ( "applies the limit after filtering on supplier" , async ( ) => {
229+ await letterQueueRepository . putLetter (
230+ createLetter ( { letterId : "first-letter" } ) ,
231+ ) ;
232+ await letterQueueRepository . putLetter (
233+ createLetter ( { letterId : "second-letter" , supplierId : "supplier2" } ) ,
234+ ) ;
235+ await letterQueueRepository . putLetter (
236+ createLetter ( { letterId : "third-letter" } ) ,
237+ ) ;
238+ await letterQueueRepository . putLetter (
239+ createLetter ( { letterId : "fourth-letter" } ) ,
240+ ) ;
241+
242+ const letters = await letterQueueRepository . getLetters ( "supplier1" , 3 ) ;
243+
244+ expect ( letters ) . toHaveLength ( 3 ) ;
245+ expect ( letters [ 2 ] . letterId ) . toBe ( "fourth-letter" ) ;
246+ } ) ;
247+
248+ it ( "applies the limit after filtering on visibilityTimestamp" , async ( ) => {
249+ await letterQueueRepository . putLetter (
250+ createLetter ( { letterId : "first-letter" } ) ,
251+ ) ;
252+ await letterQueueRepository . putLetter (
253+ createLetter ( { letterId : "second-letter" } ) ,
254+ ) ;
255+ await letterQueueRepository . putLetter (
256+ createLetter ( { letterId : "third-letter" } ) ,
257+ ) ;
258+ await letterQueueRepository . putLetter (
259+ createLetter ( { letterId : "fourth-letter" } ) ,
260+ ) ;
261+ await letterQueueRepository . updateVisibilityTimestamp (
262+ createLetter ( { letterId : "second-letter" } ) ,
263+ new Date ( Date . now ( ) + 600_000 ) ,
264+ ) ;
265+
266+ const letters = await letterQueueRepository . getLetters ( "supplier1" , 3 ) ;
267+
268+ expect ( letters ) . toHaveLength ( 3 ) ;
269+ expect ( letters [ 2 ] . letterId ) . toBe ( "fourth-letter" ) ;
270+ } ) ;
271+
272+ it ( "paginates through multiple DynamoDB pages to reach the limit" , async ( ) => {
273+ await letterQueueRepository . putLetter (
274+ createLetter ( { letterId : "first-letter" } ) ,
275+ ) ;
276+ await letterQueueRepository . putLetter (
277+ createLetter ( { letterId : "second-letter" } ) ,
278+ ) ;
279+ await letterQueueRepository . putLetter (
280+ createLetter ( { letterId : "third-letter" } ) ,
281+ ) ;
282+
283+ const pagedRepository = new LetterQueueRepository ( db . docClient , logger , {
284+ ...db . config ,
285+ queryPageSize : 1 ,
286+ } ) ;
287+
288+ const letters = await pagedRepository . getLetters ( "supplier1" , 3 ) ;
289+
290+ expect ( letters ) . toHaveLength ( 3 ) ;
291+ expect ( letters [ 0 ] . letterId ) . toBe ( "first-letter" ) ;
292+ expect ( letters [ 1 ] . letterId ) . toBe ( "second-letter" ) ;
293+ expect ( letters [ 2 ] . letterId ) . toBe ( "third-letter" ) ;
294+ } ) ;
295+
296+ it ( "returns an empty array if no items found" , async ( ) => {
297+ const letters = await letterQueueRepository . getLetters ( "supplier1" , 3 ) ;
298+
299+ expect ( letters ) . toHaveLength ( 0 ) ;
300+ } ) ;
301+ } ) ;
302+
303+ describe ( "updateVisibilityTimestamp" , ( ) => {
304+ it ( "updates the visibilityTimestamp on an existing letter" , async ( ) => {
305+ const pendingLetter =
306+ await letterQueueRepository . putLetter ( createLetter ( ) ) ;
307+
308+ await letterQueueRepository . updateVisibilityTimestamp (
309+ pendingLetter ,
310+ new Date ( "2026-03-04T13:15:45.000Z" ) ,
311+ ) ;
312+
313+ const letter = await getLetter ( db , "supplier1" , "letter1" ) ;
314+ expect ( letter ?. visibilityTimestamp ) . toBe ( "2026-03-04T13:15:45.000Z" ) ;
315+ } ) ;
316+
317+ it ( "does nothing when the letter does not exist" , async ( ) => {
318+ await letterQueueRepository . updateVisibilityTimestamp (
319+ createLetter ( ) ,
320+ new Date ( ) ,
321+ ) ;
322+
323+ expect ( await letterExists ( db , "supplier1" , "letter1" ) ) . toBe ( false ) ;
324+ } ) ;
325+
326+ it ( "rethrows errors from DynamoDB when updating the letter" , async ( ) => {
327+ const misconfiguredRepository = new LetterQueueRepository (
328+ db . docClient ,
329+ logger ,
330+ {
331+ ...db . config ,
332+ letterQueueTableName : "nonexistent-table" ,
333+ } ,
334+ ) ;
335+ await expect (
336+ misconfiguredRepository . updateVisibilityTimestamp (
337+ createLetter ( ) ,
338+ new Date ( ) ,
339+ ) ,
340+ ) . rejects . toThrow ( "Cannot do operations on a non-existent table" ) ;
341+ } ) ;
342+ } ) ;
152343} ) ;
153344
154345async function getLetter ( db : DBContext , supplierId : string , letterId : string ) {
0 commit comments