|
| 1 | +import { describe, it, expect, beforeAll, vi } from 'vitest' |
| 2 | +import express, { Express } from 'express' |
| 3 | +import request from 'supertest' |
| 4 | + |
| 5 | +// Enable debug routes for all tests in this file |
| 6 | +vi.mock('../config/featureFlags.js', () => ({ |
| 7 | + getFeatureFlags: () => ({ enableDebugRoutes: true }) |
| 8 | +})) |
| 9 | + |
| 10 | +// Mock requireAdmin to pass through |
| 11 | +vi.mock('../middleware/auth.js', () => ({ |
| 12 | + requireAdmin: (_req: any, _res: any, next: any) => next() |
| 13 | +})) |
| 14 | + |
| 15 | +// Mock adminRateLimiter to pass through |
| 16 | +vi.mock('../middleware/rateLimit.js', () => ({ |
| 17 | + adminRateLimiter: (_req: any, _res: any, next: any) => next() |
| 18 | +})) |
| 19 | + |
| 20 | +// Mock reflector service — factory must be self-contained (vi.mock is hoisted) |
| 21 | +vi.mock('../services/reflector.js', () => { |
| 22 | + const instance = { |
| 23 | + clearCache: () => undefined, |
| 24 | + getCacheStatus: () => ({ cached: false }), |
| 25 | + getCurrentPricesWithMeta: async () => ({ prices: { XLM: 0.1 }, feedMeta: {} }), |
| 26 | + testApiConnectivity: async () => ({ ok: true }), |
| 27 | + } |
| 28 | + return { ReflectorService: function () { return instance } } |
| 29 | +}) |
| 30 | + |
| 31 | +// Mock runtimeServices |
| 32 | +vi.mock('../services/runtimeServices.js', () => ({ |
| 33 | + autoRebalancer: null |
| 34 | +})) |
| 35 | + |
| 36 | +// Mock portfolioStorage |
| 37 | +vi.mock('../services/portfolioStorage.js', () => ({ |
| 38 | + portfolioStorage: { getPortfolioCount: async () => 0 } |
| 39 | +})) |
| 40 | + |
| 41 | +// Mock notificationService |
| 42 | +vi.mock('../services/notificationService.js', () => ({ |
| 43 | + notificationService: { |
| 44 | + getPreferences: () => ({ |
| 45 | + emailEnabled: true, |
| 46 | + emailAddress: 'user@example.com', |
| 47 | + webhookEnabled: true, |
| 48 | + webhookUrl: 'https://hooks.example.com/secret-token', |
| 49 | + }), |
| 50 | + notify: async () => undefined, |
| 51 | + } |
| 52 | +})) |
| 53 | + |
| 54 | +let app: Express |
| 55 | + |
| 56 | +beforeAll(async () => { |
| 57 | + app = express() |
| 58 | + app.use(express.json()) |
| 59 | + const { debugRouter } = await import('../api/debug.routes.js') |
| 60 | + app.use('/api', debugRouter) |
| 61 | +}) |
| 62 | + |
| 63 | +describe('debug routes — secret redaction', () => { |
| 64 | + it('GET /debug/coingecko-test does not expose testUrl', async () => { |
| 65 | + const res = await request(app).get('/api/debug/coingecko-test') |
| 66 | + expect(res.status).toBe(200) |
| 67 | + expect(res.body.data).not.toHaveProperty('testUrl') |
| 68 | + expect(res.body.data).toHaveProperty('apiKeySet') |
| 69 | + expect(res.body.data).toHaveProperty('responseStatus') |
| 70 | + }) |
| 71 | + |
| 72 | + it('GET /debug/reflector-test does not expose apiKeyLength', async () => { |
| 73 | + const res = await request(app).get('/api/debug/reflector-test') |
| 74 | + expect(res.status).toBe(200) |
| 75 | + expect(res.body.data.environment).not.toHaveProperty('apiKeyLength') |
| 76 | + expect(res.body.data.environment).toHaveProperty('apiKeySet') |
| 77 | + }) |
| 78 | + |
| 79 | + it('POST /debug/notifications/test redacts email and webhook in sentTo', async () => { |
| 80 | + const res = await request(app) |
| 81 | + .post('/api/debug/notifications/test') |
| 82 | + .send({ userId: 'GUSER123', eventType: 'rebalance' }) |
| 83 | + expect(res.status).toBe(200) |
| 84 | + expect(res.body.data.sentTo.email).toBe('[REDACTED]') |
| 85 | + expect(res.body.data.sentTo.webhook).toBe('[REDACTED]') |
| 86 | + }) |
| 87 | +}) |
0 commit comments