Wiki/bridge/tests/repos/baserow-repo.test.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

307 lines
8.9 KiB
TypeScript

/**
* Tests des mappers Row -> Domain. On instancie les repos avec un BaserowClient mock
* minimal qui rend juste getRow/listRows.
*/
import type { Logger } from 'pino';
import { describe, expect, it, vi } from 'vitest';
import type {
BaserowClient,
BaserowPaginatedResponse,
BaserowRow,
} from '../../src/adapters/baserow-client.js';
import { logger } from '../../src/lib/logger.js';
import {
AttributionRepo,
FormationRepo,
ModuleRepo,
PersonneRepo,
ProjetRepo,
} from '../../src/repos/baserow-repo.js';
function fakeClient(rowsByTable: Record<number, BaserowRow[]>): BaserowClient {
return {
listRows: vi.fn(
(tableId: number): Promise<BaserowPaginatedResponse> =>
Promise.resolve({
count: rowsByTable[tableId]?.length ?? 0,
next: null,
previous: null,
results: rowsByTable[tableId] ?? [],
}),
),
getRow: vi.fn((tableId: number, rowId: number): Promise<BaserowRow> => {
const row = (rowsByTable[tableId] ?? []).find((r) => r.id === rowId);
if (!row) return Promise.reject(Object.assign(new Error('not found'), { code: 'NOT_FOUND' }));
return Promise.resolve(row);
}),
createRow: vi.fn(),
updateRow: vi.fn(),
deleteRow: vi.fn(),
resolveTableIds: vi.fn(),
healthCheck: vi.fn(),
} as unknown as BaserowClient;
}
const log = logger as Logger;
describe('PersonneRepo', () => {
it('mappe une row Baserow vers Personne', async () => {
const row: BaserowRow = {
id: 42,
order: '1',
personne_nom: 'Dupont',
personne_prenom: 'Pierre',
personne_email: 'p@a.fr',
personne_capacite_annuelle: '1000',
personne_split_formation_pct: 60,
personne_split_agence_pct: 40,
personne_roles: [{ id: 1, value: 'formateur', color: 'blue' }],
personne_statut: { id: 2, value: 'actif', color: 'green' },
personne_heures_attribuees_formation: '0',
personne_heures_attribuees_agence: '0',
};
const repo = new PersonneRepo({
client: fakeClient({ 1: [row] }),
tableId: 1,
entityName: 'Personne',
logger: log,
});
const personne = await repo.get(42);
expect(personne.id).toBe(42);
expect(personne.nom).toBe('Dupont');
expect(personne.hasRole('formateur')).toBe(true);
expect(personne.statut).toBe('actif');
expect(personne.capaciteAnnuelle.toNumber()).toBe(1000);
});
it('list pagine et map', async () => {
const repo = new PersonneRepo({
client: fakeClient({
1: [
{
id: 1,
order: '1',
personne_nom: 'A',
personne_prenom: 'B',
personne_email: 'a@b.c',
personne_capacite_annuelle: '1',
personne_split_formation_pct: 50,
personne_split_agence_pct: 50,
personne_roles: [],
personne_statut: 'actif',
},
],
}),
tableId: 1,
entityName: 'Personne',
logger: log,
});
const res = await repo.list({ size: 50 });
expect(res.items).toHaveLength(1);
expect(res.meta.total).toBe(1);
});
it('throw NOT_FOUND si row inexistante', async () => {
const repo = new PersonneRepo({
client: fakeClient({ 1: [] }),
tableId: 1,
entityName: 'Personne',
logger: log,
});
await expect(repo.get(999)).rejects.toMatchObject({ code: 'NOT_FOUND' });
});
describe('findByEmail', () => {
function makeRow(email: string, id = 1): BaserowRow {
return {
id,
order: '1',
personne_nom: 'Doe',
personne_prenom: 'Jane',
personne_email: email,
personne_capacite_annuelle: '1000',
personne_split_formation_pct: 50,
personne_split_agence_pct: 50,
personne_roles: [{ id: 1, value: 'formateur', color: 'blue' }],
personne_statut: { id: 2, value: 'actif', color: 'green' },
};
}
it('retourne la Personne sur match exact', async () => {
const repo = new PersonneRepo({
client: fakeClient({ 1: [makeRow('jane@acadenice.fr', 7)] }),
tableId: 1,
entityName: 'Personne',
logger: log,
});
const found = await repo.findByEmail('jane@acadenice.fr');
expect(found?.id).toBe(7);
});
it('insensitive a la casse + trim', async () => {
const repo = new PersonneRepo({
client: fakeClient({ 1: [makeRow('jane@acadenice.fr', 7)] }),
tableId: 1,
entityName: 'Personne',
logger: log,
});
const found = await repo.findByEmail(' Jane@AcadeNice.fr ');
expect(found?.id).toBe(7);
});
it('retourne null si email vide', async () => {
const repo = new PersonneRepo({
client: fakeClient({ 1: [] }),
tableId: 1,
entityName: 'Personne',
logger: log,
});
expect(await repo.findByEmail('')).toBeNull();
expect(await repo.findByEmail(' ')).toBeNull();
});
it('retourne null si aucune row ne match exact (substring rejet)', async () => {
const repo = new PersonneRepo({
client: fakeClient({ 1: [makeRow('john.jane@acadenice.fr', 7)] }),
tableId: 1,
entityName: 'Personne',
logger: log,
});
const found = await repo.findByEmail('jane@acadenice.fr');
expect(found).toBeNull();
});
it('retourne null si row corrompue (split != 100)', async () => {
const corrupt: BaserowRow = {
id: 1,
order: '1',
personne_nom: 'X',
personne_prenom: 'Y',
personne_email: 'corrupt@test.fr',
personne_capacite_annuelle: '1000',
personne_split_formation_pct: 60,
personne_split_agence_pct: 60, // total = 120 -> domain throw
personne_roles: [],
personne_statut: 'actif',
};
const repo = new PersonneRepo({
client: fakeClient({ 1: [corrupt] }),
tableId: 1,
entityName: 'Personne',
logger: log,
});
const found = await repo.findByEmail('corrupt@test.fr');
expect(found).toBeNull();
});
});
});
describe('FormationRepo', () => {
it('mappe filiere/statut select', async () => {
const row: BaserowRow = {
id: 10,
order: '1',
formation_nom: 'Dev',
formation_filiere: { id: 1, value: 'dev', color: 'blue' },
formation_heures_totales: 500,
formation_statut: { id: 2, value: 'actif', color: 'green' },
};
const repo = new FormationRepo({
client: fakeClient({ 2: [row] }),
tableId: 2,
entityName: 'Formation',
logger: log,
});
const f = await repo.get(10);
expect(f.filiere).toBe('dev');
expect(f.statut).toBe('actif');
});
});
describe('ModuleRepo', () => {
it('mappe blocId via link field', async () => {
const row: BaserowRow = {
id: 200,
order: '1',
module_nom: 'JS',
module_heures_prevues: 30,
module_statut: 'a_attribuer',
module_bloc: [{ id: 100, value: 'Bloc JS' }],
};
const repo = new ModuleRepo({
client: fakeClient({ 4: [row] }),
tableId: 4,
entityName: 'Module',
logger: log,
});
const m = await repo.get(200);
expect(m.blocId).toBe(100);
expect(m.statut).toBe('a_attribuer');
});
});
describe('AttributionRepo', () => {
it('mappe + create persiste les bons fields', async () => {
const row: BaserowRow = {
id: 500,
order: '1',
attribution_heures_attribuees: 10,
attribution_heures_realisees: 0,
attribution_module: [{ id: 200, value: 'JS' }],
attribution_personne: [{ id: 1, value: 'Pierre' }],
attribution_statut: 'planifie',
};
const client = fakeClient({ 5: [row] });
const repo = new AttributionRepo({
client,
tableId: 5,
entityName: 'Attribution',
logger: log,
});
const a = await repo.get(500);
expect(a.moduleId).toBe(200);
expect(a.personneId).toBe(1);
await repo.create({
moduleId: 200,
personneId: 1,
heuresAttribuees: a.heuresAttribuees,
dateDebut: new Date('2026-09-01'),
dateFin: null,
statut: 'planifie',
});
expect(client.createRow).toHaveBeenCalledWith(
5,
expect.objectContaining({
attribution_module: [200],
attribution_personne: [1],
attribution_statut: 'planifie',
}),
);
});
});
describe('ProjetRepo', () => {
it('mappe statut + clientId', async () => {
const row: BaserowRow = {
id: 300,
order: '1',
projet_nom: 'Acme',
projet_charge_heures: 80,
projet_client: [{ id: 50, value: 'Acme Inc' }],
projet_statut: { id: 1, value: 'en_cours', color: 'blue' },
projet_type: { id: 2, value: 'site_web', color: 'blue' },
};
const repo = new ProjetRepo({
client: fakeClient({ 7: [row] }),
tableId: 7,
entityName: 'Projet',
logger: log,
});
const p = await repo.get(300);
expect(p.clientId).toBe(50);
expect(p.statut).toBe('en_cours');
expect(p.type).toBe('site_web');
});
});