Wiki/bridge/tests/helpers/test-app.ts
Corentin JOGUET 571f5c3426
Some checks are pending
CI / Lint bridge (Biome) (push) Waiting to run
CI / Type-check bridge (push) Blocked by required conditions
CI / Tests unit bridge (push) Blocked by required conditions
CI / Tests integration bridge (push) Blocked by required conditions
CI / Security scan (push) Waiting to run
CI / Docker build + healthcheck (push) Blocked by required conditions
feat(auth): Bloc 4 — middleware OIDC-ready avec dual mode service-token + Authentik JWT
- Support JWT OIDC Authentik via jose + JWKS (cache 10min)
- Lookup Personne via PersonneRepo.findByEmail + cache Redis 60s
- Mapping groups Authentik + roles formation-hub vers scopes
- Mode OIDC active uniquement si AUTHENTIK_ISSUER + JWKS_URI + AUDIENCE set
- Service tokens brg_* inchanges, restent voie principale en local
2026-05-07 21:17:56 +02:00

142 lines
4.3 KiB
TypeScript

/**
* Test helper : construit une app Hono iso-prod avec un container minimal en memoire.
* Pas de testcontainers ici — les routes utilisent les repos qu'on mock dans chaque suite.
*/
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 { Personne } from '../../src/domain/personne.js';
import type { Container } 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 type { RepoSet, TableIds } from '../../src/repos/baserow-repo.js';
import { attributionsRoutes } from '../../src/routes/attributions.js';
import { formationsRoutes } from '../../src/routes/formations.js';
import { interventionsRoutes } from '../../src/routes/interventions.js';
import { modulesRoutes } from '../../src/routes/modules.js';
import { personnesRoutes } from '../../src/routes/personnes.js';
import { projetsRoutes } from '../../src/routes/projets.js';
import { webhooksRoutes } from '../../src/routes/webhooks.js';
const FAKE_TABLE_IDS: TableIds = {
personne: 1,
formation: 2,
bloc: 3,
module: 4,
attribution: 5,
client: 6,
projet: 7,
tache: 8,
intervention: 9,
};
export const READ_ALL_TOKEN = 'brg_read_all';
export const WRITE_ALL_TOKEN = 'brg_write_all';
export const ADMIN_TOKEN = 'brg_admin';
export const TEST_TOKENS: ApiTokenRecord[] = [
{
token: READ_ALL_TOKEN,
name: 'test-read',
scopes: ['read:personnes', 'read:formations', 'read:projets'],
},
{
token: WRITE_ALL_TOKEN,
name: 'test-write',
scopes: ['write:attributions', 'write:interventions'],
},
{ token: ADMIN_TOKEN, name: 'test-admin', scopes: ['admin:*'] },
];
export interface TestContainerOverrides {
repos: RepoSet;
baserow?: BaserowClient;
redis?: RedisCache;
tokens?: ApiTokenRecord[];
}
export function installTestContainer(over: TestContainerOverrides): Container {
const tokensMap = new Map<string, ApiTokenRecord>();
for (const t of over.tokens ?? TEST_TOKENS) tokensMap.set(t.token, t);
const fakeBaserow = over.baserow ?? ({} as BaserowClient);
const fakeRedis = over.redis ?? ({} as RedisCache);
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,
authStrictMapping: true,
},
baserow: fakeBaserow,
redis: fakeRedis,
repos: over.repos,
tokens: tokensMap,
tableIds: FAKE_TABLE_IDS,
oidc: null,
groupsScopesMap: {},
logger,
};
setContainer(container);
return container;
}
const NOOP_CACHE = {
get: async <T>(_key: string): Promise<T | null> => null,
set: async <T>(_key: string, _value: T, _ttl?: number): Promise<void> => {},
};
const NOOP_FINDER = {
findByEmail: async (_email: string): Promise<Personne | null> => null,
};
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,
groupsScopesMap: container.groupsScopesMap,
strictMapping: container.config.authStrictMapping,
cache: NOOP_CACHE,
finder: NOOP_FINDER,
logger,
}),
);
v1.route('/personnes', personnesRoutes);
v1.route('/formations', formationsRoutes);
v1.route('/projets', projetsRoutes);
v1.route('/modules', modulesRoutes);
v1.route('/interventions', interventionsRoutes);
v1.route('/attributions', attributionsRoutes);
app.route('/api/v1', v1);
return app;
}