fix(bridge): smoke test fixes — skip rows malformees + BASEROW_TABLE_IDS override
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

Decouverts via smoke test local contre Baserow + Docmost reels.

1. **BaseRepo.list robuste** : try/catch toDomain par row, skip + log warn
   si throw (ex Personne avec splits null != 100). Avant : 500 sur la liste
   entiere. Apres : 200 avec items valides + meta.skipped count pour visibilite.
   `get()` continue de propager (un get sur row corrompue = legitimement 500
   pour investigation manuelle).

2. **BASEROW_TABLE_IDS env override** : BaserowClient.resolveTableIds appelle
   /api/database/tables/database/:id/ qui requiert un JWT user (Baserow API
   distingue DB tokens reservees aux endpoints rows, et JWT pour les endpoints
   admin). En dev/prod simple on passe le mapping directement par env var :
   BASEROW_TABLE_IDS={"personne":609,"formation":610,...}. Le code resolveTableIds
   reste en place pour Phase 3+ (bridge avec JWT user).

Smoke test post-fix :
- GET /api/health, /api/ready : 200
- Auth : 401 absent / 401 invalide / 200 valide
- GET /personnes (rows test invalides) : 200 data:[] meta.skipped:2
- GET /formations, /projets : 200 avec rows
- GET /personnes/9999 : 404

Tests Vitest : 163/163 verts. tsc + biome ci verts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Corentin JOGUET 2026-05-07 20:09:27 +02:00
parent c8e9b4d4ea
commit 7a3fbe455d
2 changed files with 38 additions and 7 deletions

View file

@ -57,13 +57,22 @@ export async function buildApp(): Promise<Hono<{ Variables: AuthVariables }>> {
async function main() {
const config = loadConfig();
// BASEROW_DATABASE_ID requis pour resolveTableIds. Cf .env
// Soit BASEROW_TABLE_IDS={"personne":609,...} (preferred — DB tokens n'ont pas
// acces a /api/database/tables/database/:id/), soit BASEROW_DATABASE_ID + un JWT
// user (Phase 3+). Cf doc 19 §5.
const tableIdsRaw = process.env.BASEROW_TABLE_IDS;
const databaseIdRaw = process.env.BASEROW_DATABASE_ID;
let initOpts: Parameters<typeof initContainer>[0];
if (tableIdsRaw) {
initOpts = { config, tableIds: JSON.parse(tableIdsRaw) };
} else {
const databaseId = databaseIdRaw ? Number.parseInt(databaseIdRaw, 10) : undefined;
if (!databaseId || Number.isNaN(databaseId)) {
throw new Error('BASEROW_DATABASE_ID env var requis pour resolve table ids');
throw new Error('BASEROW_TABLE_IDS ou BASEROW_DATABASE_ID requis');
}
await initContainer({ config, databaseId });
initOpts = { config, databaseId };
}
await initContainer(initOpts);
const app = await buildApp();
serve({ fetch: app.fetch, port: config.port }, (info) => {

View file

@ -131,7 +131,13 @@ abstract class BaseRepo<TDomain> {
async list(opts: BaserowListOptions = {}): Promise<{
items: TDomain[];
meta: { page: number; per_page: number; total: number; total_pages: number };
meta: {
page: number;
per_page: number;
total: number;
total_pages: number;
skipped?: number;
};
}> {
const page = opts.page ?? 1;
const size = Math.min(opts.size ?? 50, 200);
@ -140,7 +146,22 @@ abstract class BaseRepo<TDomain> {
page,
size,
});
const items = res.results.map((row) => this.toDomain(row));
// Skip rows that fail domain validation (split != 100, etc.) plutot que
// de casser la liste entiere. La row corrompue est loguee pour investigation
// manuelle. cf doc 19 §10 : robustness vs visibility.
const items: TDomain[] = [];
let skipped = 0;
for (const row of res.results) {
try {
items.push(this.toDomain(row));
} catch (err) {
skipped++;
this.logger.warn(
{ rowId: row.id, err: err instanceof Error ? err.message : String(err) },
'row skipped — invalid domain mapping',
);
}
}
return {
items,
meta: {
@ -148,6 +169,7 @@ abstract class BaseRepo<TDomain> {
per_page: size,
total: res.count,
total_pages: Math.max(1, Math.ceil(res.count / size)),
...(skipped > 0 ? { skipped } : {}),
},
};
}