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
Modele OO complet (cf docs/12-uml-class-diagram.md) en TypeScript strict :
- Personne (pivot multi-roles, splits formation/agence, heuresRestantes*)
- Formation -> Bloc -> Module composition + heuresRestantes rollup
- Module.creerAttribution avec validation RG-01 (capacite) + role formateur
- Attribution lifecycle : demarrer/saisirHeuresRealisees/cloturer/annuler
- Client -> Projet -> Tache composition + lierFormationPedagogique
- Tache.creerIntervention avec validation role developpeur + heures > 0 + actif
- Schemas zod pour runtime validation (z.infer types exposes)
- Decimal.js partout pour les heures (zero erreur flottante)
Patterns appliques :
- Statuts comme discriminated unions ('actif' | 'inactif' | ...)
- Statuts annules exclus des rollups (annulation libere capacite)
- _appliquerHeures* en pseudo-private (convention underscore, pas de friend en TS)
- Warn surcharge Personne non bloquant (overbooking volontaire possible) — RG-01 Module reste bloquante
Tests : 111 pass / 0 fail. Coverage domain : 97.86% lines, 98.57% funcs.
tsc strict EXIT 0, biome ci EXIT 0.
Hors scope (a venir) :
- Repository pattern (Bloc 3 avec routes Hono)
- rapportPDF (Phase 2.4)
- Tests adapters Bloc 1 (Bloc 6 via bridge-tester)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
125 lines
3 KiB
TypeScript
125 lines
3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
AttributionSchema,
|
|
BlocSchema,
|
|
ClientSchema,
|
|
FormationSchema,
|
|
InterventionSchema,
|
|
ModuleSchema,
|
|
PersonneSchema,
|
|
ProjetSchema,
|
|
TacheSchema,
|
|
} from '../../src/domain/schemas.js';
|
|
|
|
describe('schemas zod', () => {
|
|
it('PersonneSchema valide', () => {
|
|
const r = PersonneSchema.parse({
|
|
id: 1,
|
|
nom: 'Doe',
|
|
prenom: 'John',
|
|
email: 'john@a.fr',
|
|
capaciteAnnuelle: '1000',
|
|
splitFormationPct: 50,
|
|
splitAgencePct: 50,
|
|
roles: ['formateur'],
|
|
});
|
|
expect(r.statut).toBe('actif');
|
|
expect(r.capaciteAnnuelle).toBe(1000);
|
|
});
|
|
|
|
it('PersonneSchema rejette splits qui ne somment pas a 100', () => {
|
|
expect(() =>
|
|
PersonneSchema.parse({
|
|
id: 1,
|
|
nom: 'X',
|
|
prenom: 'Y',
|
|
email: 'x@y.fr',
|
|
capaciteAnnuelle: 1000,
|
|
splitFormationPct: 50,
|
|
splitAgencePct: 40,
|
|
roles: ['formateur'],
|
|
}),
|
|
).toThrow();
|
|
});
|
|
|
|
it('PersonneSchema rejette email invalide', () => {
|
|
expect(() =>
|
|
PersonneSchema.parse({
|
|
id: 1,
|
|
nom: 'X',
|
|
prenom: 'Y',
|
|
email: 'not-an-email',
|
|
capaciteAnnuelle: 1000,
|
|
splitFormationPct: 50,
|
|
splitAgencePct: 50,
|
|
roles: ['formateur'],
|
|
}),
|
|
).toThrow();
|
|
});
|
|
|
|
it('FormationSchema valide avec defaults', () => {
|
|
const r = FormationSchema.parse({ id: 1, nom: 'BTS', heuresTotales: 500 });
|
|
expect(r.statut).toBe('draft');
|
|
});
|
|
|
|
it('BlocSchema rejette heuresPrevues negative', () => {
|
|
expect(() =>
|
|
BlocSchema.parse({ id: 1, formationId: 1, nom: 'B', heuresPrevues: -5 }),
|
|
).toThrow();
|
|
});
|
|
|
|
it('ModuleSchema valide', () => {
|
|
const r = ModuleSchema.parse({ id: 1, blocId: 1, nom: 'M', heuresPrevues: 20 });
|
|
expect(r.statut).toBe('a_attribuer');
|
|
});
|
|
|
|
it('AttributionSchema rejette heuresAttribuees = 0', () => {
|
|
expect(() =>
|
|
AttributionSchema.parse({ id: 1, moduleId: 1, personneId: 1, heuresAttribuees: 0 }),
|
|
).toThrow();
|
|
});
|
|
|
|
it('ClientSchema valide minimal', () => {
|
|
const r = ClientSchema.parse({ id: 1, nom: 'Acme' });
|
|
expect(r.statut).toBe('prospect');
|
|
});
|
|
|
|
it('ProjetSchema valide avec formationId', () => {
|
|
const r = ProjetSchema.parse({
|
|
id: 1,
|
|
clientId: 1,
|
|
nom: 'P',
|
|
chargeHeures: 100,
|
|
formationId: 5,
|
|
});
|
|
expect(r.formationId).toBe(5);
|
|
});
|
|
|
|
it('TacheSchema valide minimal', () => {
|
|
const r = TacheSchema.parse({ id: 1, projetId: 1, titre: 'T', chargeHeures: 4 });
|
|
expect(r.statut).toBe('todo');
|
|
});
|
|
|
|
it('InterventionSchema parse date string', () => {
|
|
const r = InterventionSchema.parse({
|
|
id: 1,
|
|
tacheId: 1,
|
|
personneId: 1,
|
|
heures: 3,
|
|
date: '2026-05-01',
|
|
});
|
|
expect(r.date instanceof Date).toBe(true);
|
|
});
|
|
|
|
it('InterventionSchema rejette heures = 0', () => {
|
|
expect(() =>
|
|
InterventionSchema.parse({
|
|
id: 1,
|
|
tacheId: 1,
|
|
personneId: 1,
|
|
heures: 0,
|
|
date: '2026-05-01',
|
|
}),
|
|
).toThrow();
|
|
});
|
|
});
|