diff --git a/api/src/index.ts b/api/src/index.ts index 9f18b6d..b700db7 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 5563a52..ff2c948 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([]);