portail-associations/server/dsu.test.ts

280 lines
10 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock the database module
vi.mock('./db', () => ({
getRequestById: vi.fn(),
updateRequest: vi.fn(),
createRequestHistory: vi.fn(),
createAuditLog: vi.fn(),
}));
import * as db from './db';
describe('DSU (Direction des Services aux Usagers) - Cadre réservé', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('Merging DSU data into formData', () => {
it('should merge DSU data into existing formData for demande_salle', () => {
const existingFormData = {
nomAssociation: 'Association Test',
sallesSelectionnees: ['Salle TOUCAN'],
dateReservation: '2026-04-15',
motifReservation: 'Réunion annuelle',
formulaireType: 'reservation_salle_mjs',
};
const dsuData = {
dateReception: '2026-03-10',
avisDSU: 'favorable' as const,
conditionsParticulieres: 'Nettoyage obligatoire',
cautionRequise: true,
montantCaution: '500',
assuranceRequise: true,
horairesImposes: '8h00 - 22h00',
responsableDSU: 'Jean Dupont',
dateDecision: '2026-03-11',
observationsDSU: 'RAS',
};
// Simulate the merge logic from routers.ts
const merged = { ...existingFormData };
(merged as any).cadreDSU = {
...dsuData,
rempliPar: 1,
dateRemplissage: new Date().toISOString(),
};
const result = JSON.parse(JSON.stringify(merged));
// Verify original data is preserved
expect(result.nomAssociation).toBe('Association Test');
expect(result.sallesSelectionnees).toEqual(['Salle TOUCAN']);
expect(result.dateReservation).toBe('2026-04-15');
expect(result.motifReservation).toBe('Réunion annuelle');
// Verify DSU data is added
expect(result.cadreDSU).toBeDefined();
expect(result.cadreDSU.avisDSU).toBe('favorable');
expect(result.cadreDSU.dateReception).toBe('2026-03-10');
expect(result.cadreDSU.conditionsParticulieres).toBe('Nettoyage obligatoire');
expect(result.cadreDSU.cautionRequise).toBe(true);
expect(result.cadreDSU.montantCaution).toBe('500');
expect(result.cadreDSU.assuranceRequise).toBe(true);
expect(result.cadreDSU.horairesImposes).toBe('8h00 - 22h00');
expect(result.cadreDSU.responsableDSU).toBe('Jean Dupont');
expect(result.cadreDSU.dateDecision).toBe('2026-03-11');
expect(result.cadreDSU.observationsDSU).toBe('RAS');
expect(result.cadreDSU.rempliPar).toBe(1);
expect(result.cadreDSU.dateRemplissage).toBeDefined();
});
it('should not add DSU data for non-salle request types', () => {
const requestType = 'subvention_fonctionnement';
const dsuData = {
avisDSU: 'favorable' as const,
responsableDSU: 'Jean Dupont',
};
// Simulate the condition check from routers.ts
const shouldAddDsu = requestType === 'demande_salle';
expect(shouldAddDsu).toBe(false);
});
it('should handle empty existing formData gracefully', () => {
const existingFormData = null;
const dsuData = {
avisDSU: 'defavorable' as const,
responsableDSU: 'Marie Martin',
observationsDSU: 'Salle non disponible',
};
// Simulate the merge logic
const parsed = existingFormData ? JSON.parse(existingFormData) : {};
parsed.cadreDSU = {
...dsuData,
rempliPar: 2,
dateRemplissage: new Date().toISOString(),
};
const result = JSON.parse(JSON.stringify(parsed));
expect(result.cadreDSU).toBeDefined();
expect(result.cadreDSU.avisDSU).toBe('defavorable');
expect(result.cadreDSU.responsableDSU).toBe('Marie Martin');
});
it('should handle favorable_avec_reserves avis correctly', () => {
const existingFormData = { nomAssociation: 'Test' };
const dsuData = {
avisDSU: 'favorable_avec_reserves' as const,
conditionsParticulieres: 'Sous réserve de présentation de l\'attestation d\'assurance',
cautionRequise: true,
montantCaution: '1000',
};
const merged = { ...existingFormData, cadreDSU: { ...dsuData, rempliPar: 1, dateRemplissage: new Date().toISOString() } };
const result = JSON.parse(JSON.stringify(merged));
expect(result.cadreDSU.avisDSU).toBe('favorable_avec_reserves');
expect(result.cadreDSU.conditionsParticulieres).toContain('attestation d\'assurance');
expect(result.cadreDSU.cautionRequise).toBe(true);
expect(result.cadreDSU.montantCaution).toBe('1000');
});
it('should merge financial decision into an existing salle request dossier', () => {
const existingFormData = {
nomAssociation: 'Association Test',
sallesSelectionnees: ['Salle TOUCAN'],
dateReservation: '2026-06-20',
cadreDSU: {
responsableDSU: 'Agent DSU',
},
};
const financialDecision = {
financialMode: 'location_payante' as const,
depositRequired: true,
depositAmountCents: 45000,
rentalAmountCents: 12500,
pricingNotes: 'Facturation exceptionnelle pour occupation prolongée',
contractStatus: 'a_generer' as const,
};
const merged = structuredClone(existingFormData) as any;
merged.cadreDSU = {
...merged.cadreDSU,
cautionRequise: financialDecision.depositRequired,
montantCaution: String(financialDecision.depositAmountCents / 100),
materielEvent: {
financialDecision,
},
};
expect(merged.cadreDSU.cautionRequise).toBe(true);
expect(merged.cadreDSU.montantCaution).toBe('450');
expect(merged.cadreDSU.materielEvent.financialDecision.financialMode).toBe('location_payante');
expect(merged.cadreDSU.materielEvent.financialDecision.rentalAmountCents).toBe(12500);
});
it('should merge material financial decision into the existing material request data', () => {
const existingFormData = {
commune: 'Kourou',
service: 'Vie associative',
quantitesDemandees: { tente3x3: '2' },
cadreDSU: {
dateReception: '2026-05-10',
materielEvent: {
itemsAccordes: { tente3x3: true },
quantitesAccordees: { tente3x3: '2' },
},
},
};
const financialDecision = {
financialMode: 'location_payante' as const,
depositRequired: true,
depositAmountCents: 25000,
rentalAmountCents: 9500,
pricingNotes: 'Tarif exceptionnel pour événement intercommunal',
contractStatus: 'a_generer' as const,
};
const merged = structuredClone(existingFormData) as any;
merged.cadreDSU = {
...merged.cadreDSU,
responsableDSU: 'Admin Logistique',
materielEvent: {
...merged.cadreDSU.materielEvent,
financialDecision,
},
};
expect(merged.cadreDSU.materielEvent.itemsAccordes.tente3x3).toBe(true);
expect(merged.cadreDSU.materielEvent.quantitesAccordees.tente3x3).toBe('2');
expect(merged.cadreDSU.materielEvent.financialDecision.financialMode).toBe('location_payante');
expect(merged.cadreDSU.materielEvent.financialDecision.depositRequired).toBe(true);
expect(merged.cadreDSU.materielEvent.financialDecision.depositAmountCents).toBe(25000);
expect(merged.cadreDSU.materielEvent.financialDecision.rentalAmountCents).toBe(9500);
expect(merged.cadreDSU.materielEvent.financialDecision.pricingNotes).toContain('Tarif exceptionnel');
});
});
describe('DSU avis labels mapping', () => {
const avisDSULabels: Record<string, { label: string; className: string }> = {
favorable: { label: 'Favorable', className: 'bg-green-100 text-green-800 border-green-200' },
defavorable: { label: 'Défavorable', className: 'bg-red-100 text-red-800 border-red-200' },
favorable_avec_reserves: { label: 'Favorable avec réserves', className: 'bg-amber-100 text-amber-800 border-amber-200' },
};
it('should have correct labels for all avis types', () => {
expect(avisDSULabels.favorable.label).toBe('Favorable');
expect(avisDSULabels.defavorable.label).toBe('Défavorable');
expect(avisDSULabels.favorable_avec_reserves.label).toBe('Favorable avec réserves');
});
it('should have appropriate CSS classes for visual distinction', () => {
expect(avisDSULabels.favorable.className).toContain('green');
expect(avisDSULabels.defavorable.className).toContain('red');
expect(avisDSULabels.favorable_avec_reserves.className).toContain('amber');
});
});
describe('Process mutation with DSU data', () => {
it('should call updateRequest with merged formData when DSU data is provided', async () => {
const mockRequest = {
id: 1,
type: 'demande_salle',
status: 'soumise',
formData: JSON.stringify({
nomAssociation: 'Test Asso',
sallesSelectionnees: ['Salle TOUCAN'],
}),
};
(db.getRequestById as any).mockResolvedValue(mockRequest);
(db.updateRequest as any).mockResolvedValue(undefined);
(db.createRequestHistory as any).mockResolvedValue(undefined);
(db.createAuditLog as any).mockResolvedValue(undefined);
const dsuData = {
avisDSU: 'favorable' as const,
responsableDSU: 'Admin Test',
dateReception: '2026-03-10',
dateDecision: '2026-03-11',
};
// Simulate the process logic
const existingFormData = JSON.parse(mockRequest.formData);
existingFormData.cadreDSU = {
...dsuData,
rempliPar: 1,
dateRemplissage: new Date().toISOString(),
};
const updatedFormData = JSON.stringify(existingFormData);
await db.updateRequest(mockRequest.id, {
status: 'validee' as any,
commentaireAdmin: 'Approuvé',
traitePar: 1,
dateTraitement: new Date(),
formData: updatedFormData,
});
expect(db.updateRequest).toHaveBeenCalledWith(
1,
expect.objectContaining({
status: 'validee',
formData: expect.stringContaining('cadreDSU'),
})
);
// Verify the formData contains the DSU data
const callArgs = (db.updateRequest as any).mock.calls[0][1];
const parsedFormData = JSON.parse(callArgs.formData);
expect(parsedFormData.cadreDSU.avisDSU).toBe('favorable');
expect(parsedFormData.cadreDSU.responsableDSU).toBe('Admin Test');
expect(parsedFormData.nomAssociation).toBe('Test Asso');
});
});
});