diff --git a/src/index.js b/src/index.js index d98ef6d..871f187 100644 --- a/src/index.js +++ b/src/index.js @@ -25,7 +25,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()); @@ -142,6 +146,7 @@ async function startServer() { return server; } +module.exports = { app, server }; module.exports = app; module.exports.app = app; module.exports.server = server || { diff --git a/test/airdrops.test.js b/test/airdrops.test.js index d3cc6a6..cda68c9 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 mockZSets = new Map(); const mockLists = new Map(); const mockCounters = new Map(); @@ -16,6 +17,20 @@ const mockRedis = { 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); if (!mockZSets.has(key)) mockZSets.set(key, new Map()); mockZSets.get(key).set(member, Number(score)); }), @@ -53,7 +68,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); }), incr: jest.fn(async (key) => { const count = (mockCounters.get(key) || 0) + 1; @@ -109,12 +126,14 @@ const config = require('../src/config'); let app; beforeAll(() => { - app = require('../src/index'); + const { app: importedApp } = require('../src/index'); + app = importedApp; }); beforeEach(() => { mockStore.clear(); mockSets.clear(); + mockSortedSets.clear(); mockZSets.clear(); mockLists.clear(); mockCounters.clear(); @@ -126,6 +145,8 @@ beforeEach(() => { mockRedis.srem.mockClear(); mockRedis.zadd.mockClear(); mockRedis.zrem.mockClear(); + mockRedis.zcard.mockClear(); + mockRedis.zrevrange.mockClear(); mockRedis.zrevrange.mockClear(); mockRedis.zcard.mockClear(); mockRedis.zscan.mockClear(); @@ -150,7 +171,7 @@ 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 }, @@ -204,6 +225,7 @@ describe('POST /api/v1/airdrops', () => { describe('GET /api/v1/airdrops', () => { test('lists airdrops with pagination', async () => { + const res1 = await request(app) await request(app) .post('/api/v1/airdrops') .send({ @@ -214,6 +236,7 @@ describe('GET /api/v1/airdrops', () => { expiry_ledger: 123456, }); + const res2 = await request(app) await request(app) .post('/api/v1/airdrops') .send({ diff --git a/test/alerts-routes.test.js b/test/alerts-routes.test.js index 6f8ae8b..61aa5e3 100644 --- a/test/alerts-routes.test.js +++ b/test/alerts-routes.test.js @@ -3,11 +3,28 @@ const adminApiKey = 'a'.repeat(64); process.env.ADMIN_API_KEY = adminApiKey; +const mockStore = new Map(); +const mockSortedSets = new Map(); const mockZSets = 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); + }), if (!mockZSets.has(key)) mockZSets.set(key, new Map()); mockZSets.get(key).set(member, Number(score)); }), @@ -28,9 +45,9 @@ const mockRedis = { 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), })); @@ -46,6 +63,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 1252885..d9f2424 100644 --- a/test/auth.test.js +++ b/test/auth.test.js @@ -6,6 +6,7 @@ const crypto = require('crypto'); const mockStore = new Map(); const mockSets = new Map(); +const mockSortedSets = new Map(); const mockZSets = new Map(); const mockRedis = { @@ -18,6 +19,19 @@ const mockRedis = { 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); if (!mockZSets.has(key)) mockZSets.set(key, new Map()); mockZSets.get(key).set(member, Number(score)); }), @@ -85,12 +99,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', () => {