L'admin doit pouvoir voir en un coup d'œil quelles matières sont actives (notes saisies) pour décider lesquelles peuvent être supprimées sans perte de données. Auparavant, la suppression d'une matière était silencieuse : elle cascade-deletait évaluations et notes sans avertir. La liste des matières affiche désormais les compteurs d'enseignants, classes, évaluations et notes. La suppression déclenche une confirmation explicite quand la matière contient des notes, avec récapitulatif des volumes impactés, pour rendre l'action irréversible consciente. Côté tests, un endpoint de seeding HTTP remplace les appels docker exec dans les E2E (gain ~30-60s → 5-10s par test), et un trait partagé factorise le SQL de seeding entre les deux suites fonctionnelles.
129 lines
4.5 KiB
PHP
129 lines
4.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Administration\Application\Query\GetSubjectGradeStats;
|
|
|
|
use App\Administration\Application\Port\SubjectGradeStats;
|
|
use App\Administration\Application\Port\SubjectGradeStatsReader;
|
|
use App\Administration\Application\Query\GetSubjectGradeStats\GetSubjectGradeStatsHandler;
|
|
use App\Administration\Application\Query\GetSubjectGradeStats\GetSubjectGradeStatsQuery;
|
|
use App\Administration\Domain\Model\Subject\SubjectId;
|
|
use App\Shared\Domain\Tenant\TenantId;
|
|
use Override;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class GetSubjectGradeStatsHandlerTest extends TestCase
|
|
{
|
|
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
|
|
private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440002';
|
|
|
|
#[Test]
|
|
public function itReturnsZeroStatsWhenSubjectHasNoEvaluations(): void
|
|
{
|
|
$handler = new GetSubjectGradeStatsHandler($this->createReader(evaluations: 0, grades: 0));
|
|
|
|
$stats = $handler(new GetSubjectGradeStatsQuery(
|
|
tenantId: self::TENANT_ID,
|
|
subjectId: self::SUBJECT_ID,
|
|
));
|
|
|
|
self::assertSame(0, $stats->evaluationCount);
|
|
self::assertSame(0, $stats->gradeCount);
|
|
self::assertFalse($stats->hasGrades());
|
|
}
|
|
|
|
#[Test]
|
|
public function itReturnsCountsWhenEvaluationsExist(): void
|
|
{
|
|
$handler = new GetSubjectGradeStatsHandler($this->createReader(evaluations: 3, grades: 42));
|
|
|
|
$stats = $handler(new GetSubjectGradeStatsQuery(
|
|
tenantId: self::TENANT_ID,
|
|
subjectId: self::SUBJECT_ID,
|
|
));
|
|
|
|
self::assertSame(3, $stats->evaluationCount);
|
|
self::assertSame(42, $stats->gradeCount);
|
|
self::assertTrue($stats->hasGrades());
|
|
}
|
|
|
|
#[Test]
|
|
public function itConsidersSubjectWithEvaluationsButNoGradesAsHavingImpact(): void
|
|
{
|
|
$handler = new GetSubjectGradeStatsHandler($this->createReader(evaluations: 2, grades: 0));
|
|
|
|
$stats = $handler(new GetSubjectGradeStatsQuery(
|
|
tenantId: self::TENANT_ID,
|
|
subjectId: self::SUBJECT_ID,
|
|
));
|
|
|
|
self::assertSame(2, $stats->evaluationCount);
|
|
self::assertSame(0, $stats->gradeCount);
|
|
self::assertTrue($stats->hasGrades(), 'Une évaluation sans notes reste un impact à signaler.');
|
|
}
|
|
|
|
#[Test]
|
|
public function itConsidersSubjectWithGradesButNoEvaluationsAsHavingImpact(): void
|
|
{
|
|
// Théoriquement impossible via la FK grades.evaluation_id → evaluations(id),
|
|
// mais on couvre la logique `||` du value object contre toute régression.
|
|
$handler = new GetSubjectGradeStatsHandler($this->createReader(evaluations: 0, grades: 5));
|
|
|
|
$stats = $handler(new GetSubjectGradeStatsQuery(
|
|
tenantId: self::TENANT_ID,
|
|
subjectId: self::SUBJECT_ID,
|
|
));
|
|
|
|
self::assertSame(0, $stats->evaluationCount);
|
|
self::assertSame(5, $stats->gradeCount);
|
|
self::assertTrue($stats->hasGrades());
|
|
}
|
|
|
|
#[Test]
|
|
public function itPassesQueryParamsToReader(): void
|
|
{
|
|
$reader = new class implements SubjectGradeStatsReader {
|
|
public ?string $receivedTenantId = null;
|
|
public ?string $receivedSubjectId = null;
|
|
|
|
#[Override]
|
|
public function countForSubject(TenantId $tenantId, SubjectId $subjectId): SubjectGradeStats
|
|
{
|
|
$this->receivedTenantId = (string) $tenantId;
|
|
$this->receivedSubjectId = (string) $subjectId;
|
|
|
|
return new SubjectGradeStats(0, 0);
|
|
}
|
|
};
|
|
|
|
$handler = new GetSubjectGradeStatsHandler($reader);
|
|
|
|
$handler(new GetSubjectGradeStatsQuery(
|
|
tenantId: self::TENANT_ID,
|
|
subjectId: self::SUBJECT_ID,
|
|
));
|
|
|
|
self::assertSame(self::TENANT_ID, $reader->receivedTenantId);
|
|
self::assertSame(self::SUBJECT_ID, $reader->receivedSubjectId);
|
|
}
|
|
|
|
private function createReader(int $evaluations, int $grades): SubjectGradeStatsReader
|
|
{
|
|
return new class($evaluations, $grades) implements SubjectGradeStatsReader {
|
|
public function __construct(
|
|
private int $evaluations,
|
|
private int $grades,
|
|
) {
|
|
}
|
|
|
|
#[Override]
|
|
public function countForSubject(TenantId $tenantId, SubjectId $subjectId): SubjectGradeStats
|
|
{
|
|
return new SubjectGradeStats($this->evaluations, $this->grades);
|
|
}
|
|
};
|
|
}
|
|
}
|