/** * Test helper : construit une app Hono iso-prod avec un container minimal en * memoire, puis expose les routes generiques /api/v1/tables/*. * * R1 — Pas de tableIds metier. Les repos sont injectes via overrides. */ import { Hono } from 'hono'; import { logger as honoLogger } from 'hono/logger'; import type { BaserowClient } from '../../src/adapters/baserow-client.js'; import type { RedisCache } from '../../src/adapters/redis-cache.js'; import type { Container, RepoSet } from '../../src/lib/container.js'; import { setContainer } from '../../src/lib/container.js'; import { logger } from '../../src/lib/logger.js'; import { type ApiTokenRecord, type AuthVariables, authMiddleware, } from '../../src/middleware/auth.js'; import { errorHandler } from '../../src/middleware/error-handler.js'; import { tablesRoutes } from '../../src/routes/tables.js'; import { viewsRoutes } from '../../src/routes/views.js'; import { webhooksRoutes } from '../../src/routes/webhooks.js'; export const READ_TOKEN = 'brg_read'; export const WRITE_TOKEN = 'brg_write'; export const ADMIN_TOKEN = 'brg_admin'; export const TEST_TOKENS: ApiTokenRecord[] = [ { token: READ_TOKEN, name: 'test-read', scopes: ['read:tables'] }, { token: WRITE_TOKEN, name: 'test-write', scopes: ['read:tables', 'write:tables'] }, { token: ADMIN_TOKEN, name: 'test-admin', scopes: ['admin:*'] }, ]; export interface TestContainerOverrides { repos: RepoSet; baserow?: BaserowClient; redis?: RedisCache; tokens?: ApiTokenRecord[]; } /** * Stub Redis minimal pour les tests routes : juste les methodes que les routes * appellent (invalidatePattern + checkRateLimit + getClient). No-op qui ne refuse jamais. * R3.1.b : getClient retourne un fake Redis Streams (xadd no-op). */ function buildNoopRedis(): RedisCache { return { invalidatePattern: async (_pattern: string) => 0, checkRateLimit: async (_key: string, _max: number, _win: number) => true, getClient: () => ({ xadd: async () => '0-0' }), } as unknown as RedisCache; } export function installTestContainer(over: TestContainerOverrides): Container { const tokensMap = new Map(); for (const t of over.tokens ?? TEST_TOKENS) tokensMap.set(t.token, t); const fakeBaserow = over.baserow ?? ({} as BaserowClient); const fakeRedis = over.redis ?? buildNoopRedis(); const container: Container = { config: { nodeEnv: 'test', port: 0, logLevel: 'fatal', baserowApiUrl: 'http://localhost', baserowApiToken: 'fake', redisUrl: 'redis://localhost', baserowWebhookSecret: 'fake_secret_at_least_16_chars', docmostWebhookSecret: 'fake_docmost_secret_at_least_16_chars', bridgeApiTokens: undefined, rateLimitGlobalMax: 10000, rateLimitGlobalWindow: 60, rateLimitMutationMax: 10000, rateLimitMutationWindow: 60, streamMaxLen: 10000, }, baserow: fakeBaserow, redis: fakeRedis, repos: over.repos, tokens: tokensMap, oidc: null, docmostJwt: null, groupsScopesMap: {}, logger, }; setContainer(container); return container; } export function resetTestContainer(): void { setContainer(null); } export function buildTestApp(container: Container): Hono<{ Variables: AuthVariables }> { const app = new Hono<{ Variables: AuthVariables }>(); app.use('*', honoLogger()); app.onError(errorHandler); app.get('/api/health', (c) => c.json({ status: 'ok' })); app.route('/api/webhooks', webhooksRoutes); const v1 = new Hono<{ Variables: AuthVariables }>(); v1.use( '*', authMiddleware({ tokens: container.tokens, oidc: container.oidc, docmostJwt: container.docmostJwt, groupsScopesMap: container.groupsScopesMap, logger, }), ); v1.route('/tables', tablesRoutes); v1.route('/views', viewsRoutes); app.route('/api/v1', v1); return app; }