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
Wiring HTTP du bridge service. 10 endpoints livres (cf docs/19 §6.1-6.5) :
- GET /api/v1/personnes (+ /:id, + /:id/dashboard)
- GET /api/v1/formations (+ /:id avec rollups blocs/modules)
- GET /api/v1/projets (+ /:id avec rollups taches)
- POST /api/v1/modules/:id/attribuer (RG-01 -> 422, role/heures invalides -> 400)
- POST /api/v1/interventions (validation role developpeur + heures > 0)
- PATCH /api/v1/attributions/:id/heures-realisees (409 si annule/realise)
Layers ajoutees :
- src/middleware/auth.ts : Bearer brg_*, scopes JSON-encoded BRIDGE_API_TOKENS, admin:* wildcard
- src/middleware/error-handler.ts : BridgeError -> JSON shape standard
- src/lib/container.ts : DI singleton (Baserow + Redis + 9 repos), setContainer testable
- src/lib/http.ts : parseListQuery + parseBody zod helper
- src/repos/baserow-repo.ts : BaseRepo<T> abstrait + 9 sous-classes (mapping Row<->Domain)
- src/routes/{personnes,formations,projets,modules,interventions,attributions}.ts
src/index.ts reecrit : buildApp() + initContainer + auth sur /api/v1/* + ready check Baserow+Redis.
Tests : 163/163 verts (12 suites domain + 8 nouvelles : auth, repos, 6 routes).
Coverage src global : 70.77% (cible 60%). Domain 97.86%, routes 96%, middleware 86%.
Choix : BaseRepo abstrait (pas mega-generic, Ockham) ; FakeRepos in-memory pour tests routes
(pas de testcontainers ici, c'est Bloc 7) ; mapping erreurs domain -> HTTP par message texte
(fragile, sera refactor en DomainError typees au Bloc 3.2).
Hors scope (a venir) :
- Bloc 5 : rate limiting Redis
- Bloc 7 : webhook handlers Baserow + sync bidirec + cache invalidation
- Bloc 3.2 : routes /docmost/*, /sync/*, /rapports/*
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
115 lines
3.5 KiB
TypeScript
115 lines
3.5 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 { 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';
|
|
|
|
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',
|
|
bridgeApiTokens: undefined,
|
|
},
|
|
baserow: fakeBaserow,
|
|
redis: fakeRedis,
|
|
repos: over.repos,
|
|
tokens: tokensMap,
|
|
tableIds: FAKE_TABLE_IDS,
|
|
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' }));
|
|
|
|
const v1 = new Hono<{ Variables: AuthVariables }>();
|
|
v1.use('*', authMiddleware(container.tokens));
|
|
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;
|
|
}
|