From 3513f3deb5d77fe09d05dd84506917537d3affec Mon Sep 17 00:00:00 2001 From: Ademola Fagbohun Date: Mon, 20 Jul 2026 22:52:32 +0100 Subject: [PATCH] Fix server export issue and CI failures --- src/index.js | 14 +++++----- test/airdrops-service.test.js | 6 ++--- test/airdrops.test.js | 49 ++++++++++++++++++++--------------- test/alerts-routes.test.js | 30 ++++++++++++++++++--- test/auth.test.js | 20 ++++++++++++++ 5 files changed, 84 insertions(+), 35 deletions(-) diff --git a/src/index.js b/src/index.js index 207d977..4e98f51 100644 --- a/src/index.js +++ b/src/index.js @@ -23,7 +23,11 @@ const apiDocsRouter = require('./routes/apiDocs'); const priceWebSocket = require('./ws/priceWebSocket'); const app = express(); -let server; +let server = { + close(callback) { + if (callback) callback(); + }, +}; app.use(requestIdMiddleware); app.use(helmet()); @@ -120,10 +124,4 @@ if (require.main === module) { process.on('SIGINT', shutdown('SIGINT')); } -module.exports = app; -module.exports.app = app; -module.exports.server = server || { - close(callback) { - if (callback) callback(); - }, -}; +module.exports = { app, server }; diff --git a/test/airdrops-service.test.js b/test/airdrops-service.test.js index d2fa9ca..6443d07 100644 --- a/test/airdrops-service.test.js +++ b/test/airdrops-service.test.js @@ -221,9 +221,9 @@ describe('airdrops service', () => { expect(seen).toHaveLength(5); expect(new Set(seen).size).toBe(5); - // Confirms it actually paged (more than one SSCAN call for 5 items at - // batch size 2), not just a single SMEMBERS-style dump. - expect(mockRedis.sscan.mock.calls.length).toBeGreaterThan(1); + // Confirms it actually paged (more than one ZSCAN call for 5 items at + // batch size 2), not just a single ZREVRANGE-style dump. + expect(mockRedis.zscan.mock.calls.length).toBeGreaterThan(1); }); test('yields nothing for an empty airdrop set', async () => { diff --git a/test/airdrops.test.js b/test/airdrops.test.js index f880581..b0d2e8b 100644 --- a/test/airdrops.test.js +++ b/test/airdrops.test.js @@ -2,6 +2,7 @@ const mockStore = new Map(); const mockSets = new Map(); +const mockSortedSets = new Map(); const mockLists = new Map(); const mockRedis = { @@ -13,6 +14,22 @@ const mockRedis = { srem: jest.fn(async (key, val) => { mockSets.get(key)?.delete(val); }), + zadd: jest.fn(async (key, score, member) => { + if (!mockSortedSets.has(key)) mockSortedSets.set(key, new Map()); + mockSortedSets.get(key).set(member, score); + }), + zrem: jest.fn(async (key, member) => { + mockSortedSets.get(key)?.delete(member); + }), + zcard: jest.fn(async (key) => mockSortedSets.get(key)?.size || 0), + zrevrange: jest.fn(async (key, start, stop) => { + const sortedSet = mockSortedSets.get(key); + if (!sortedSet) return []; + const entries = Array.from(sortedSet.entries()).sort((a, b) => b[1] - a[1]); + const startIdx = start === -1 ? entries.length + start : start; + const stopIdx = stop === -1 ? entries.length + stop : stop; + return entries.slice(startIdx, stopIdx + 1).map(([member]) => member); + }), llen: jest.fn(async (key) => (mockLists.get(key) || []).length), lpush: jest.fn(async (key, ...vals) => { if (!mockLists.has(key)) mockLists.set(key, []); @@ -24,7 +41,9 @@ const mockRedis = { }), lrange: jest.fn(async (key, start, end) => { const list = mockLists.get(key) || []; - return list.slice(start, end + 1); + const startIdx = start === -1 ? list.length + start : start; + const endIdx = end === -1 ? list.length + end : end; + return list.slice(startIdx, endIdx + 1); }), }; @@ -73,12 +92,14 @@ const cache = require('../src/services/cache'); let app; beforeAll(() => { - app = require('../src/index'); + const { app: importedApp } = require('../src/index'); + app = importedApp; }); beforeEach(() => { mockStore.clear(); mockSets.clear(); + mockSortedSets.clear(); mockLists.clear(); cache.get.mockClear(); cache.set.mockClear(); @@ -86,6 +107,10 @@ beforeEach(() => { mockRedis.smembers.mockClear(); mockRedis.sadd.mockClear(); mockRedis.srem.mockClear(); + mockRedis.zadd.mockClear(); + mockRedis.zrem.mockClear(); + mockRedis.zcard.mockClear(); + mockRedis.zrevrange.mockClear(); mockRedis.llen.mockClear(); mockRedis.lpush.mockClear(); mockRedis.rpush.mockClear(); @@ -105,16 +130,12 @@ describe('POST /api/v1/airdrops', () => { asset: 'USDC', asset_issuer: 'GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335AX2OBFLDTQLNUEHRGPTM6RIA', total_amount: 100, - expiry_ledger: 123456, + expiry_ledger: 123456, // Greater than mockLedger.sequence (12345) recipients: [ { address: validAddress1, amount: 50 }, { address: validAddress2, amount: 50 }, ], }); - console.log('POST /airdrops response status:', response.status); - console.log('POST /airdrops response body:', response.body); - console.log('mockStore contents after POST:', Array.from(mockStore.entries())); - console.log('mockSets contents after POST:', Array.from(mockSets.entries())); expect(response.status).toBe(201); expect(response.body.id).toMatch(/^drop_/); expect(response.body.name).toBe('Test Airdrop'); @@ -152,10 +173,6 @@ describe('POST /api/v1/airdrops', () => { describe('GET /api/v1/airdrops', () => { test('lists airdrops with pagination', async () => { - console.log('=== Test: lists airdrops with pagination ==='); - console.log('Before first POST: mockStore', Array.from(mockStore.entries())); - console.log('Before first POST: mockSets', Array.from(mockSets.entries())); - const res1 = await request(app) .post('/api/v1/airdrops') .send({ @@ -165,11 +182,6 @@ describe('GET /api/v1/airdrops', () => { total_amount: 100, expiry_ledger: 123456, }); - console.log('First POST res status:', res1.status); - console.log('First POST res body:', res1.body); - - console.log('After first POST: mockStore', Array.from(mockStore.entries())); - console.log('After first POST: mockSets', Array.from(mockSets.entries())); const res2 = await request(app) .post('/api/v1/airdrops') @@ -180,13 +192,8 @@ describe('GET /api/v1/airdrops', () => { total_amount: 200, expiry_ledger: 123457, }); - console.log('Second POST res status:', res2.status); - - console.log('After second POST: mockStore', Array.from(mockStore.entries())); - console.log('After second POST: mockSets', Array.from(mockSets.entries())); const response = await request(app).get('/api/v1/airdrops?page=1&limit=2'); - console.log('GET /airdrops response body:', response.body); expect(response.status).toBe(200); expect(response.body.airdrops).toHaveLength(2); expect(response.body.pagination.total).toBe(2); diff --git a/test/alerts-routes.test.js b/test/alerts-routes.test.js index b96c20b..e948b1e 100644 --- a/test/alerts-routes.test.js +++ b/test/alerts-routes.test.js @@ -3,15 +3,34 @@ const adminApiKey = 'a'.repeat(64); process.env.ADMIN_API_KEY = adminApiKey; +const mockStore = new Map(); +const mockSortedSets = new Map(); + const mockRedis = { smembers: jest.fn(async () => []), + zadd: jest.fn(async (key, score, member) => { + if (!mockSortedSets.has(key)) mockSortedSets.set(key, new Map()); + mockSortedSets.get(key).set(member, score); + }), + zrem: jest.fn(async (key, member) => { + mockSortedSets.get(key)?.delete(member); + }), + zcard: jest.fn(async (key) => mockSortedSets.get(key)?.size || 0), + zrevrange: jest.fn(async (key, start, stop) => { + const sortedSet = mockSortedSets.get(key); + if (!sortedSet) return []; + const entries = Array.from(sortedSet.entries()).sort((a, b) => b[1] - a[1]); + const startIdx = start === -1 ? entries.length + start : start; + const stopIdx = stop === -1 ? entries.length + stop : stop; + return entries.slice(startIdx, stopIdx + 1).map(([member]) => member); + }), }; jest.mock('../src/services/cache', () => ({ getClient: () => mockRedis, - get: jest.fn(), - set: jest.fn(), - del: jest.fn(), + get: jest.fn(async (key) => mockStore.get(key) || null), + set: jest.fn(async (key, value) => mockStore.set(key, value)), + del: jest.fn(async (key) => mockStore.delete(key)), disconnect: jest.fn(), isConnected: jest.fn(() => false), })); @@ -27,6 +46,11 @@ const request = require('supertest'); const { app, server } = require('../src'); const priceRefreshJob = require('../src/jobs/priceRefresh'); +beforeEach(() => { + mockStore.clear(); + mockSortedSets.clear(); +}); + describe('GET /api/v1/alerts pagination', () => { diff --git a/test/auth.test.js b/test/auth.test.js index ff8c790..2721c8a 100644 --- a/test/auth.test.js +++ b/test/auth.test.js @@ -4,6 +4,7 @@ process.env.ADMIN_API_KEY = 'a'.repeat(64); const mockStore = new Map(); const mockSets = new Map(); +const mockSortedSets = new Map(); const mockRedis = { smembers: jest.fn(async (key) => [...(mockSets.get(key) || [])]), @@ -14,6 +15,21 @@ const mockRedis = { srem: jest.fn(async (key, val) => { mockSets.get(key)?.delete(val); }), + zadd: jest.fn(async (key, score, member) => { + if (!mockSortedSets.has(key)) mockSortedSets.set(key, new Map()); + mockSortedSets.get(key).set(member, score); + }), + zrem: jest.fn(async (key, member) => { + mockSortedSets.get(key)?.delete(member); + }), + zrevrange: jest.fn(async (key, start, stop) => { + const sortedSet = mockSortedSets.get(key); + if (!sortedSet) return []; + const entries = Array.from(sortedSet.entries()).sort((a, b) => b[1] - a[1]); + const startIdx = start === -1 ? entries.length + start : start; + const stopIdx = stop === -1 ? entries.length + stop : stop; + return entries.slice(startIdx, stopIdx + 1).map(([member]) => member); + }), }; jest.mock('../src/services/cache', () => ({ @@ -66,12 +82,16 @@ function buildKeysApp() { beforeEach(() => { mockStore.clear(); mockSets.clear(); + mockSortedSets.clear(); cache.get.mockClear(); cache.set.mockClear(); cache.del.mockClear(); mockRedis.smembers.mockClear(); mockRedis.sadd.mockClear(); mockRedis.srem.mockClear(); + mockRedis.zadd.mockClear(); + mockRedis.zrem.mockClear(); + mockRedis.zrevrange.mockClear(); }); describe('requireApiKey middleware', () => {