diff --git a/stellar-payment-platform/server.js b/stellar-payment-platform/server.js index f7ffcdb..ff2572d 100644 --- a/stellar-payment-platform/server.js +++ b/stellar-payment-platform/server.js @@ -110,6 +110,10 @@ const USER_DATABASE = { const DEFAULT_FEDERATION_DOMAIN = 'localhost'; +let cachedCount = 0; +let lastFetched = 0; +let statsFetchPromise = null; + const normalizeNameTag = (value) => { const trimmed = typeof value === 'string' ? value.trim() : ''; if (!trimmed) { @@ -521,6 +525,7 @@ app.use((err, _req, _res, next) => { // Global error handling middleware // eslint-disable-next-line no-unused-vars app.use((err, _req, res, _next) => { + void _next; const statusCode = err.statusCode || 500; const errorMessage = err.message || 'Internal server error'; diff --git a/stellar-payment-platform/server.test.js b/stellar-payment-platform/server.test.js index db57a0f..fe93b4f 100644 --- a/stellar-payment-platform/server.test.js +++ b/stellar-payment-platform/server.test.js @@ -467,6 +467,66 @@ describe('GET /users — pagination and search', () => { }); }); +describe('GET /stats — cached total user count', () => { + let request; + let app; + let prisma; + let dateNowSpy; + + beforeEach(() => { + jest.resetModules(); + ({ app } = require('./server')); + ({ prisma } = require('./prismaClient')); + request = require('supertest'); + + prisma.user.count.mockReset(); + dateNowSpy = jest.spyOn(Date, 'now').mockReturnValue(1_000_000); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('returns totalUsers and queries the database once on first request', async () => { + prisma.user.count.mockResolvedValue(4); + + const res = await request(app).get('/stats'); + + expect(res.status).toBe(200); + expect(res.body).toEqual({ totalUsers: 4 }); + expect(prisma.user.count).toHaveBeenCalledTimes(1); + }); + + test('reuses the cached count for requests within 60 seconds', async () => { + prisma.user.count.mockResolvedValue(4); + + await request(app).get('/stats'); + expect(prisma.user.count).toHaveBeenCalledTimes(1); + + prisma.user.count.mockResolvedValue(10); + dateNowSpy.mockReturnValue(1_030_000); + + const res = await request(app).get('/stats'); + expect(res.status).toBe(200); + expect(res.body).toEqual({ totalUsers: 4 }); + expect(prisma.user.count).toHaveBeenCalledTimes(1); + }); + + test('refreshes the cached count after 60 seconds', async () => { + prisma.user.count.mockResolvedValueOnce(4).mockResolvedValueOnce(10); + + await request(app).get('/stats'); + expect(prisma.user.count).toHaveBeenCalledTimes(1); + + dateNowSpy.mockReturnValue(1_061_000); + const res = await request(app).get('/stats'); + + expect(res.status).toBe(200); + expect(res.body).toEqual({ totalUsers: 10 }); + expect(prisma.user.count).toHaveBeenCalledTimes(2); + }); +}); + describe('POST /register — block secret keys', () => { let request; let app;