Checkpoint: Implémentation de l'émargement formateur par QR code avec signature tactile
Modifications principales : - Ajout des champs typeUtilisateur et formateurId dans la table presences pour distinguer formateurs et apprenants - Détection automatique du rôle (formateur/apprenant) via l'email lors du scan du QR code - Interface de signature tactile réutilisée pour les formateurs - Intégration des signatures tactiles formateur dans la génération PDF de la feuille de présence - QR code dans l'espace formateur accessible pour l'émargement - Tests unitaires validés avec succès Le système permet maintenant aux formateurs d'émarger (matin et après-midi) via le même QR code que les apprenants, avec détection automatique du rôle et intégration des signatures dans le PDF.
This commit is contained in:
164
server/__tests__/emargement-formateur.test.ts
Normal file
164
server/__tests__/emargement-formateur.test.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { getDb } from '../db';
|
||||
import { validerPresenceFormateur, getPresencesFormateurBySequence } from '../presenceDb';
|
||||
import { formateurs, sequences, datesFormation, presences } from '../../drizzle/schema';
|
||||
import { eq, and, sql } from 'drizzle-orm';
|
||||
|
||||
describe('Émargement formateur par QR code', () => {
|
||||
let testFormateurId: number;
|
||||
let testSequenceId: number;
|
||||
let testDateFormationId: number;
|
||||
const timestamp = Date.now();
|
||||
|
||||
beforeAll(async () => {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error('Database not available');
|
||||
|
||||
// Créer un formateur de test
|
||||
const formateurResult = await db.insert(formateurs).values({
|
||||
nom: `Test Formateur Émargement ${timestamp}`,
|
||||
email: `test.formateur.emargement.${timestamp}@test.com`,
|
||||
});
|
||||
testFormateurId = Number(formateurResult[0].insertId);
|
||||
|
||||
// Créer une séquence de test
|
||||
const sequenceResult = await db.insert(sequences).values({
|
||||
formationId: 1,
|
||||
nom: 'Test Séquence Émargement',
|
||||
lieu: 'Test Lieu',
|
||||
capaciteMax: 10,
|
||||
statut: 'ouverte',
|
||||
publicCible: 'tous',
|
||||
formateurId: testFormateurId,
|
||||
});
|
||||
testSequenceId = Number(sequenceResult[0].insertId);
|
||||
|
||||
// Créer une date de formation de test
|
||||
const dateResult = await db.insert(datesFormation).values({
|
||||
sequenceId: testSequenceId,
|
||||
ordre: 1,
|
||||
dateDebut: new Date(),
|
||||
dateFin: new Date(Date.now() + 3600000),
|
||||
});
|
||||
testDateFormationId = Number(dateResult[0].insertId);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
const db = await getDb();
|
||||
if (!db) return;
|
||||
|
||||
// Nettoyer les données de test
|
||||
await db.delete(presences).where(eq(presences.formateurId, testFormateurId));
|
||||
await db.delete(datesFormation).where(eq(datesFormation.sequenceId, testSequenceId));
|
||||
await db.delete(sequences).where(eq(sequences.id, testSequenceId));
|
||||
await db.delete(formateurs).where(eq(formateurs.id, testFormateurId));
|
||||
});
|
||||
|
||||
it('devrait valider la présence du formateur pour le matin', async () => {
|
||||
const result = await validerPresenceFormateur({
|
||||
formateurId: testFormateurId,
|
||||
sequenceId: testSequenceId,
|
||||
dateFormationId: testDateFormationId,
|
||||
periode: 'matin',
|
||||
modeValidation: 'qrcode',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
// Vérifier que la présence a bien été créée
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error('Database not available');
|
||||
|
||||
const presenceCreated = await db
|
||||
.select()
|
||||
.from(presences)
|
||||
.where(
|
||||
and(
|
||||
eq(presences.formateurId, testFormateurId),
|
||||
eq(presences.dateFormationId, testDateFormationId),
|
||||
sql`${presences}.periode = 'matin'`,
|
||||
sql`${presences}.typeUtilisateur = 'formateur'`
|
||||
)
|
||||
);
|
||||
|
||||
expect(presenceCreated.length).toBe(1);
|
||||
expect(presenceCreated[0].typeUtilisateur).toBe('formateur');
|
||||
expect(presenceCreated[0].modeValidation).toBe('qrcode');
|
||||
});
|
||||
|
||||
it('devrait valider la présence du formateur pour l\'après-midi', async () => {
|
||||
const result = await validerPresenceFormateur({
|
||||
formateurId: testFormateurId,
|
||||
sequenceId: testSequenceId,
|
||||
dateFormationId: testDateFormationId,
|
||||
periode: 'apres_midi',
|
||||
modeValidation: 'qrcode',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('devrait empêcher la double validation pour la même période', async () => {
|
||||
await expect(
|
||||
validerPresenceFormateur({
|
||||
formateurId: testFormateurId,
|
||||
sequenceId: testSequenceId,
|
||||
dateFormationId: testDateFormationId,
|
||||
periode: 'matin',
|
||||
modeValidation: 'qrcode',
|
||||
})
|
||||
).rejects.toThrow('Présence déjà validée');
|
||||
});
|
||||
|
||||
it('devrait récupérer les présences du formateur pour une séquence', async () => {
|
||||
const presencesFormateur = await getPresencesFormateurBySequence(testSequenceId);
|
||||
|
||||
expect(presencesFormateur.length).toBeGreaterThanOrEqual(2); // Matin + Après-midi
|
||||
expect(presencesFormateur.every(p => p.formateurId === testFormateurId)).toBe(true);
|
||||
});
|
||||
|
||||
it('devrait stocker la signature tactile du formateur', async () => {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error('Database not available');
|
||||
|
||||
// Créer une nouvelle date pour ce test
|
||||
const dateResult = await db.insert(datesFormation).values({
|
||||
sequenceId: testSequenceId,
|
||||
ordre: 2,
|
||||
dateDebut: new Date(),
|
||||
dateFin: new Date(Date.now() + 3600000),
|
||||
});
|
||||
const newDateId = Number(dateResult[0].insertId);
|
||||
|
||||
const signatureUrl = '/uploads/signatures/test-formateur-signature.png';
|
||||
const result = await validerPresenceFormateur({
|
||||
formateurId: testFormateurId,
|
||||
sequenceId: testSequenceId,
|
||||
dateFormationId: newDateId,
|
||||
periode: 'matin',
|
||||
modeValidation: 'qrcode',
|
||||
signatureUrl,
|
||||
signatureS3Key: 'signatures/test-formateur-signature.png',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
|
||||
// Vérifier que la signature a été stockée
|
||||
const presenceCreated = await db
|
||||
.select()
|
||||
.from(presences)
|
||||
.where(
|
||||
and(
|
||||
eq(presences.formateurId, testFormateurId),
|
||||
eq(presences.dateFormationId, newDateId),
|
||||
sql`${presences}.periode = 'matin'`
|
||||
)
|
||||
);
|
||||
|
||||
expect(presenceCreated.length).toBe(1);
|
||||
expect(presenceCreated[0].signatureUrl).toBe(signatureUrl);
|
||||
|
||||
// Nettoyer
|
||||
await db.delete(datesFormation).where(eq(datesFormation.id, newDateId));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user