import { Decimal } from 'decimal.js'; import { describe, expect, it } from 'vitest'; import { Personne } from '../../src/domain/personne.js'; import { Tache } from '../../src/domain/tache.js'; import type { Role } from '../../src/domain/types.js'; const developpeur = (overrides: Partial[0]> = {}) => new Personne({ id: 1, nom: 'Dev', prenom: 'Jane', email: 'jane@acadenice.fr', capaciteAnnuelle: new Decimal(1000), splitFormationPct: new Decimal(20), splitAgencePct: new Decimal(80), roles: new Set(['developpeur']), statut: 'actif', ...overrides, }); describe('Tache — constructeur', () => { it('cree une tache valide', () => { const t = new Tache({ id: 1, projetId: 5, titre: 'T1', chargeHeures: new Decimal(8) }); expect(t.statut).toBe('todo'); }); it('throw si charge < 0', () => { expect( () => new Tache({ id: 1, projetId: 5, titre: 'T1', chargeHeures: new Decimal(-1) }), ).toThrow(); }); }); describe('Tache — creerIntervention', () => { it('happy path', () => { const t = new Tache({ id: 1, projetId: 5, titre: 'T1', chargeHeures: new Decimal(8) }); const p = developpeur(); const i = t.creerIntervention(p, new Decimal(3), new Date('2026-05-01'), 100); expect(i.heures.toNumber()).toBe(3); expect(t.heuresRealisees().toNumber()).toBe(3); expect(p.heuresAttribueesAgence.toNumber()).toBe(3); }); it('throw si role != developpeur', () => { const t = new Tache({ id: 1, projetId: 5, titre: 'T1', chargeHeures: new Decimal(8) }); const p = developpeur({ roles: new Set(['formateur']) }); expect(() => t.creerIntervention(p, new Decimal(3), new Date(), 1)).toThrow(/developpeur/); }); it('throw si heures <= 0', () => { const t = new Tache({ id: 1, projetId: 5, titre: 'T1', chargeHeures: new Decimal(8) }); const p = developpeur(); expect(() => t.creerIntervention(p, new Decimal(0), new Date(), 1)).toThrow(); expect(() => t.creerIntervention(p, new Decimal(-1), new Date(), 1)).toThrow(); }); it('throw si personne inactive', () => { const t = new Tache({ id: 1, projetId: 5, titre: 'T1', chargeHeures: new Decimal(8) }); const p = developpeur({ statut: 'inactif' }); expect(() => t.creerIntervention(p, new Decimal(2), new Date(), 1)).toThrow(/inactiv/); }); it('annulation exclue du rollup heuresRealisees', () => { const t = new Tache({ id: 1, projetId: 5, titre: 'T1', chargeHeures: new Decimal(8) }); const p = developpeur(); const i1 = t.creerIntervention(p, new Decimal(3), new Date(), 1); t.creerIntervention(p, new Decimal(2), new Date(), 2); i1.annuler('test'); expect(t.heuresRealisees().toNumber()).toBe(2); }); }); describe('Tache — transitions de statut', () => { it('marquerInProgress', () => { const t = new Tache({ id: 1, projetId: 5, titre: 'T1', chargeHeures: new Decimal(8) }); t.marquerInProgress(); expect(t.statut).toBe('in_progress'); }); it('marquerReview depuis in_progress', () => { const t = new Tache({ id: 1, projetId: 5, titre: 'T1', chargeHeures: new Decimal(8) }); t.marquerInProgress(); t.marquerReview(); expect(t.statut).toBe('review'); }); it('marquerReview invalide depuis todo', () => { const t = new Tache({ id: 1, projetId: 5, titre: 'T1', chargeHeures: new Decimal(8) }); expect(() => t.marquerReview()).toThrow(); }); it('marquerDone depuis review', () => { const t = new Tache({ id: 1, projetId: 5, titre: 'T1', chargeHeures: new Decimal(8) }); t.marquerInProgress(); t.marquerReview(); t.marquerDone(); expect(t.statut).toBe('done'); }); it('marquerDone bloque depuis done', () => { const t = new Tache({ id: 1, projetId: 5, titre: 'T1', chargeHeures: new Decimal(8), statut: 'done', }); expect(() => t.marquerDone()).toThrow(); }); it('marquerInProgress bloque depuis abandoned', () => { const t = new Tache({ id: 1, projetId: 5, titre: 'T1', chargeHeures: new Decimal(8), statut: 'abandoned', }); expect(() => t.marquerInProgress()).toThrow(); }); });