diff --git a/src/stellar/__tests__/stellar.test.ts b/src/stellar/__tests__/stellar.test.ts index b7466b0..eb86f55 100644 --- a/src/stellar/__tests__/stellar.test.ts +++ b/src/stellar/__tests__/stellar.test.ts @@ -155,4 +155,101 @@ describe('Stellar Integration - Unit Tests', () => { expect(mockEvent.type).toBe('rebalance'); }); }); + + describe('Dead-Letter Queue - Persistent Storage', () => { + it('should create a DLQ entry with required fields', () => { + const event = { + type: 'deposit', + ledger: 12345, + txHash: 'tx-fail-001', + contractId: 'CVAULT', + }; + const errorMsg = 'User not found'; + + const entry = { + id: 'dlq-id-1', + contractId: event.contractId, + txHash: event.txHash, + eventType: event.type, + ledger: event.ledger, + error: errorMsg, + payload: event, + status: 'PENDING' as const, + retryCount: 0, + createdAt: new Date(), + updatedAt: new Date(), + }; + + expect(entry.txHash).toBe('tx-fail-001'); + expect(entry.status).toBe('PENDING'); + expect(entry.retryCount).toBe(0); + expect(entry.contractId).toBe('CVAULT'); + expect(entry.error).toBe(errorMsg); + }); + + it('should mark entry as RESOLVED after successful retry', () => { + let status: 'PENDING' | 'RETRIED' | 'RESOLVED' = 'PENDING'; + let retryCount = 0; + + // Simulate successful retry + retryCount++; + status = 'RESOLVED'; + + expect(status).toBe('RESOLVED'); + expect(retryCount).toBe(1); + }); + + it('should mark entry as RETRIED after failed retry', () => { + let status: 'PENDING' | 'RETRIED' | 'RESOLVED' = 'PENDING'; + let retryCount = 0; + + // Simulate failed retry + retryCount++; + status = 'RETRIED'; + + expect(status).toBe('RETRIED'); + expect(retryCount).toBe(1); + }); + + it('should only retry PENDING and RETRIED entries', () => { + const entries = [ + { id: '1', status: 'PENDING' as const }, + { id: '2', status: 'RETRIED' as const }, + { id: '3', status: 'RESOLVED' as const }, + ]; + + const retryable = entries.filter( + e => e.status === 'PENDING' || e.status === 'RETRIED' + ); + + expect(retryable.length).toBe(2); + expect(retryable.map(e => e.id)).toEqual(['1', '2']); + }); + + it('should trigger alert when queue size reaches threshold', () => { + const threshold = 50; + const sizes = [49, 50, 51]; + const alerts = sizes.map(size => size >= threshold); + + expect(alerts[0]).toBe(false); // 49 — no alert + expect(alerts[1]).toBe(true); // 50 — alert + expect(alerts[2]).toBe(true); // 51 — alert + }); + + it('should persist retry metadata across restarts', () => { + // Database-backed DLQ: all state lives in DB rows + const dbRecord = { + id: 'dlq-1', + retryCount: 3, + status: 'RETRIED' as const, + updatedAt: new Date(), + }; + + // After a restart, the same record is loaded from DB unchanged + const reloaded = { ...dbRecord }; + + expect(reloaded.retryCount).toBe(3); + expect(reloaded.status).toBe('RETRIED'); + }); + }); });