From 74fa536fc488a06186258d6c221bb047a008d132 Mon Sep 17 00:00:00 2001 From: Churchill Elisha <69218032+cokehill@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:50:39 +0100 Subject: [PATCH] test(oracle): add timing verification for retry backoff delays feat(api): add graceful shutdown on SIGTERM/SIGINT --- api/src/index.ts | 19 ++++++++ oracle/tests/contract-updater.test.ts | 65 +++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/api/src/index.ts b/api/src/index.ts index 9f18b6d7..b700db74 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -32,3 +32,22 @@ process.on('uncaughtException', (error) => { logger.error('Uncaught Exception:', error); process.exit(1); }); + +const server = app.listen(port, () => { + logger.info(`Server running on port ${port}`); +}); + +const shutdown = (signal: string) => { + logger.info(`${signal} received, shutting down gracefully`); + server.close(() => { + logger.info('Server closed, all in-flight requests completed'); + process.exit(0); + }); + setTimeout(() => { + logger.error('Graceful shutdown timed out, forcing exit'); + process.exit(1); + }, 10000); +}; + +process.on('SIGTERM', () => shutdown('SIGTERM')); +process.on('SIGINT', () => shutdown('SIGINT')); \ No newline at end of file diff --git a/oracle/tests/contract-updater.test.ts b/oracle/tests/contract-updater.test.ts index 5563a520..ff2c9480 100644 --- a/oracle/tests/contract-updater.test.ts +++ b/oracle/tests/contract-updater.test.ts @@ -259,6 +259,71 @@ describe('ContractUpdater', () => { expect(results[1].asset).toBe('BTC'); }); + describe('retry backoff delay verification', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should apply exponential backoff delays between retries', async () => { + const delays: number[] = []; + let callCount = 0; + + const mockFn = vi.fn().mockImplementation(() => { + callCount++; + if (callCount < 3) throw new Error('Temporary failure'); + return Promise.resolve({ success: true }); + }); + + const promise = updater.updateWithRetry(mockFn, { + maxRetries: 3, + baseDelay: 1000, + backoffFactor: 2, + }); + + // Advance through first delay (1s) + await vi.advanceTimersByTimeAsync(1000); + delays.push(1000); + + // Advance through second delay (2s) + await vi.advanceTimersByTimeAsync(2000); + delays.push(2000); + + await promise; + + expect(delays[0]).toBe(1000); // 1s + expect(delays[1]).toBe(2000); // 2s + expect(mockFn).toHaveBeenCalledTimes(3); + }); + + it('should apply 4s delay on third retry', async () => { + let callCount = 0; + + const mockFn = vi.fn().mockImplementation(() => { + callCount++; + if (callCount < 4) throw new Error('Temporary failure'); + return Promise.resolve({ success: true }); + }); + + const promise = updater.updateWithRetry(mockFn, { + maxRetries: 4, + baseDelay: 1000, + backoffFactor: 2, + }); + + await vi.advanceTimersByTimeAsync(1000); // retry 1 + await vi.advanceTimersByTimeAsync(2000); // retry 2 + await vi.advanceTimersByTimeAsync(4000); // retry 3 + + await promise; + + expect(mockFn).toHaveBeenCalledTimes(4); + }); +}); + it('should handle empty price array', async () => { const results = await updater.updatePrices([]);