get(Connection::class); $this->connection = $connection; /** @var AuditEvaluationEventsHandler $handler */ $handler = static::getContainer()->get(AuditEvaluationEventsHandler::class); $this->handler = $handler; } #[Test] public function handleEvaluationCreeeWritesAuditEntryToDatabase(): void { $evaluationId = EvaluationId::generate(); $classId = Uuid::uuid4()->toString(); $subjectId = Uuid::uuid4()->toString(); $teacherId = Uuid::uuid4()->toString(); $event = new EvaluationCreee( evaluationId: $evaluationId, classId: $classId, subjectId: $subjectId, teacherId: $teacherId, title: 'Contrôle chapitre 3', description: 'Sur les parties 1 et 2', evaluationDate: new DateTimeImmutable('2026-04-15'), gradeScale: 20, coefficient: 2.0, occurredOn: new DateTimeImmutable(), ); $this->handler->handleEvaluationCreee($event); $entry = $this->connection->fetchAssociative( 'SELECT * FROM audit_log WHERE aggregate_id = ? AND event_type = ? ORDER BY occurred_at DESC LIMIT 1', [$evaluationId->value->toString(), 'EvaluationCreee'], ); self::assertNotFalse($entry, 'Audit log entry should exist after EvaluationCreee'); self::assertSame('Evaluation', $entry['aggregate_type']); $payload = self::decodePayload($entry['payload']); self::assertSame([], $payload['old_values']); self::assertSame('Contrôle chapitre 3', $payload['new_values']['title']); self::assertSame('Sur les parties 1 et 2', $payload['new_values']['description']); self::assertSame($classId, $payload['new_values']['class_id']); self::assertSame($subjectId, $payload['new_values']['subject_id']); self::assertSame($teacherId, $payload['new_values']['teacher_id']); self::assertSame('2026-04-15', $payload['new_values']['evaluation_date']); self::assertSame(20, $payload['new_values']['grade_scale']); self::assertSame(2.0, $payload['new_values']['coefficient']); } #[Test] public function handleEvaluationModifieeWritesAuditEntryWithDiff(): void { $evaluationId = EvaluationId::generate(); $event = new EvaluationModifiee( evaluationId: $evaluationId, oldTitle: 'Ancien titre', newTitle: 'Nouveau titre', oldDescription: null, newDescription: 'Description ajoutée', oldCoefficient: 1.0, newCoefficient: 3.0, oldEvaluationDate: new DateTimeImmutable('2026-04-15'), newEvaluationDate: new DateTimeImmutable('2026-05-02'), oldGradeScale: 20, newGradeScale: 100, occurredOn: new DateTimeImmutable(), ); $this->handler->handleEvaluationModifiee($event); $entry = $this->connection->fetchAssociative( 'SELECT * FROM audit_log WHERE aggregate_id = ? AND event_type = ? ORDER BY occurred_at DESC LIMIT 1', [$evaluationId->value->toString(), 'EvaluationModifiee'], ); self::assertNotFalse($entry, 'Audit log entry should exist after EvaluationModifiee'); self::assertSame('Evaluation', $entry['aggregate_type']); $payload = self::decodePayload($entry['payload']); self::assertSame('Ancien titre', $payload['old_values']['title']); self::assertNull($payload['old_values']['description']); self::assertSame(1.0, $payload['old_values']['coefficient']); self::assertSame('2026-04-15', $payload['old_values']['evaluation_date']); self::assertSame(20, $payload['old_values']['grade_scale']); self::assertSame('Nouveau titre', $payload['new_values']['title']); self::assertSame('Description ajoutée', $payload['new_values']['description']); self::assertSame(3.0, $payload['new_values']['coefficient']); self::assertSame('2026-05-02', $payload['new_values']['evaluation_date']); self::assertSame(100, $payload['new_values']['grade_scale']); } #[Test] public function handleNotesPublieesWritesAuditEntryToDatabase(): void { $evaluationId = EvaluationId::generate(); $event = new NotesPubliees( evaluationId: $evaluationId, occurredOn: new DateTimeImmutable(), ); $this->handler->handleNotesPubliees($event); $entry = $this->connection->fetchAssociative( 'SELECT * FROM audit_log WHERE aggregate_id = ? AND event_type = ? ORDER BY occurred_at DESC LIMIT 1', [$evaluationId->value->toString(), 'NotesPubliees'], ); self::assertNotFalse($entry, 'Audit log entry should exist after NotesPubliees'); self::assertSame('Evaluation', $entry['aggregate_type']); self::assertSame($evaluationId->value->toString(), $entry['aggregate_id']); $payload = self::decodePayload($entry['payload']); self::assertSame([], $payload['old_values']); self::assertSame([], $payload['new_values']); $metadata = self::decodePayload($entry['metadata']); self::assertArrayHasKey('correlation_id', $metadata); self::assertArrayHasKey('occurred_at', $metadata); } #[Test] public function handleEvaluationSupprimeeWritesAuditEntryToDatabase(): void { $evaluationId = EvaluationId::generate(); $event = new EvaluationSupprimee( evaluationId: $evaluationId, occurredOn: new DateTimeImmutable(), ); $this->handler->handleEvaluationSupprimee($event); $entry = $this->connection->fetchAssociative( 'SELECT * FROM audit_log WHERE aggregate_id = ? AND event_type = ? ORDER BY occurred_at DESC LIMIT 1', [$evaluationId->value->toString(), 'EvaluationSupprimee'], ); self::assertNotFalse($entry, 'Audit log entry should exist after EvaluationSupprimee'); self::assertSame('Evaluation', $entry['aggregate_type']); self::assertSame($evaluationId->value->toString(), $entry['aggregate_id']); $payload = self::decodePayload($entry['payload']); self::assertSame([], $payload['old_values']); self::assertSame([], $payload['new_values']); $metadata = self::decodePayload($entry['metadata']); self::assertArrayHasKey('correlation_id', $metadata); self::assertArrayHasKey('occurred_at', $metadata); } /** * @return array */ private static function decodePayload(mixed $raw): array { self::assertIsString($raw); /** @var array $decoded */ $decoded = json_decode($raw, true, 512, JSON_THROW_ON_ERROR); return $decoded; } }