feat: Permettre aux administrateurs de configurer les règles de devoirs
Some checks failed
CI / Backend Tests (push) Has been cancelled
CI / Frontend Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Naming Conventions (push) Has been cancelled
CI / Build Check (push) Has been cancelled

Les établissements ont besoin de protéger les élèves et familles des
devoirs de dernière minute. Cette configuration au niveau tenant permet
de définir des règles de timing (délai minimum, pas de devoir pour
lundi après une heure limite) et un mode d'application (avertissement,
blocage ou désactivé).

Le service de validation est prêt pour être branché dans le flux de
création de devoirs (Stories 5.4/5.5). L'historique des changements
assure la traçabilité des modifications de configuration.
This commit is contained in:
2026-03-17 21:27:06 +01:00
parent a708af3a8f
commit 5f3c5c2d71
39 changed files with 4007 additions and 2 deletions

View File

@@ -0,0 +1,264 @@
<?php
declare(strict_types=1);
namespace App\Tests\Unit\Administration\Application\Service;
use App\Administration\Application\Service\HomeworkRulesValidator;
use App\Administration\Domain\Model\HomeworkRules\EnforcementMode;
use App\Administration\Domain\Model\HomeworkRules\HomeworkRule;
use App\Administration\Domain\Model\HomeworkRules\HomeworkRules;
use App\Administration\Domain\Model\HomeworkRules\RuleType;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
final class HomeworkRulesValidatorTest extends TestCase
{
private HomeworkRulesValidator $validator;
protected function setUp(): void
{
$this->validator = new HomeworkRulesValidator();
}
// -- Règles désactivées --
#[Test]
public function disabledRulesAlwaysValid(): void
{
$rules = $this->creerRulesAvecDelai(3, EnforcementMode::DISABLED, false);
$result = $this->validator->valider(
$rules,
new DateTimeImmutable('2026-03-20'), // vendredi
new DateTimeImmutable('2026-03-19 15:00'), // jeudi (1 jour, < 3)
);
self::assertTrue($result->estValide());
}
// -- Délai minimum (AC3) --
#[Test]
public function delaiMinimumOkQuandRespected(): void
{
$rules = $this->creerRulesAvecDelai(3);
// Devoir vendredi 20 mars, créé mardi 17 mars = 3 jours = OK
$result = $this->validator->valider(
$rules,
new DateTimeImmutable('2026-03-20'),
new DateTimeImmutable('2026-03-17 10:00'),
);
self::assertTrue($result->estValide());
}
#[Test]
public function delaiMinimumViolationQuandTropTard(): void
{
$rules = $this->creerRulesAvecDelai(3);
// Devoir vendredi 20 mars, créé mercredi 18 mars = 2 jours < 3
$result = $this->validator->valider(
$rules,
new DateTimeImmutable('2026-03-20'),
new DateTimeImmutable('2026-03-18 10:00'),
);
self::assertFalse($result->estValide());
self::assertCount(1, $result->violations);
self::assertSame(RuleType::MINIMUM_DELAY, $result->violations[0]->ruleType);
}
#[Test]
public function delaiMinimumSoftModeRetourneAvertissement(): void
{
$rules = $this->creerRulesAvecDelai(3, EnforcementMode::SOFT);
$result = $this->validator->valider(
$rules,
new DateTimeImmutable('2026-03-20'),
new DateTimeImmutable('2026-03-18 10:00'),
);
self::assertTrue($result->estAvertissement());
self::assertFalse($result->estBloquant());
}
#[Test]
public function delaiMinimumHardModeRetourneErreur(): void
{
$rules = $this->creerRulesAvecDelai(3, EnforcementMode::HARD);
$result = $this->validator->valider(
$rules,
new DateTimeImmutable('2026-03-20'),
new DateTimeImmutable('2026-03-18 10:00'),
);
self::assertTrue($result->estBloquant());
self::assertFalse($result->estAvertissement());
}
// -- Règle pas de devoir pour lundi (AC4) --
#[Test]
public function pasLundiApresOkQuandCreationAvantCutoff(): void
{
$rules = $this->creerRulesAvecLundi();
// Devoir pour lundi 23 mars, créé vendredi 20 mars à 11h (avant 12h)
$result = $this->validator->valider(
$rules,
new DateTimeImmutable('2026-03-23'), // lundi
new DateTimeImmutable('2026-03-20 11:00'), // vendredi 11h
);
self::assertTrue($result->estValide());
}
#[Test]
public function pasLundiApresViolationQuandCreationApresCutoff(): void
{
$rules = $this->creerRulesAvecLundi();
// Devoir pour lundi 23 mars, créé vendredi 20 mars à 14h (après 12h)
$result = $this->validator->valider(
$rules,
new DateTimeImmutable('2026-03-23'), // lundi
new DateTimeImmutable('2026-03-20 14:00'), // vendredi 14h
);
self::assertFalse($result->estValide());
self::assertSame(RuleType::NO_MONDAY_AFTER, $result->violations[0]->ruleType);
}
#[Test]
public function pasLundiApresIgnorePourJoursNonLundi(): void
{
$rules = $this->creerRulesAvecLundi();
// Devoir pour mardi 24 mars, créé n'importe quand
$result = $this->validator->valider(
$rules,
new DateTimeImmutable('2026-03-24'), // mardi
new DateTimeImmutable('2026-03-23 20:00'), // lundi soir
);
self::assertTrue($result->estValide());
}
#[Test]
public function pasLundiApresViolationSamediCreation(): void
{
$rules = $this->creerRulesAvecLundi();
// Devoir pour lundi 23 mars, créé samedi 21 mars (après vendredi 12h)
$result = $this->validator->valider(
$rules,
new DateTimeImmutable('2026-03-23'), // lundi
new DateTimeImmutable('2026-03-21 10:00'), // samedi
);
self::assertFalse($result->estValide());
}
// -- Règles combinées --
#[Test]
public function multipleViolationsQuandPlusieursReglesEnfreintes(): void
{
$rules = $this->creerRulesComplet();
// Devoir pour lundi 23 mars, créé samedi 21 mars 10h
// Violation délai minimum (3 jours: 23-3 = 20, samedi 21 > 20)
// Violation lundi (après vendredi 12h)
$result = $this->validator->valider(
$rules,
new DateTimeImmutable('2026-03-23'), // lundi
new DateTimeImmutable('2026-03-21 10:00'), // samedi
);
self::assertFalse($result->estValide());
self::assertCount(2, $result->violations);
}
#[Test]
public function messagesRetourneListeMessages(): void
{
$rules = $this->creerRulesAvecDelai(3);
$result = $this->validator->valider(
$rules,
new DateTimeImmutable('2026-03-20'),
new DateTimeImmutable('2026-03-19 10:00'),
);
self::assertCount(1, $result->messages());
self::assertStringContainsString('3 jours', $result->messages()[0]);
}
// -- Helpers --
private function creerRulesAvecDelai(
int $days,
EnforcementMode $mode = EnforcementMode::SOFT,
bool $enabled = true,
): HomeworkRules {
$rules = HomeworkRules::creer(
tenantId: TenantId::fromString('550e8400-e29b-41d4-a716-446655440001'),
now: new DateTimeImmutable('2026-03-01'),
);
$rules->mettreAJour(
rules: [new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => $days])],
enforcementMode: $mode,
enabled: $enabled,
now: new DateTimeImmutable('2026-03-01'),
);
return $rules;
}
private function creerRulesAvecLundi(
EnforcementMode $mode = EnforcementMode::SOFT,
): HomeworkRules {
$rules = HomeworkRules::creer(
tenantId: TenantId::fromString('550e8400-e29b-41d4-a716-446655440001'),
now: new DateTimeImmutable('2026-03-01'),
);
$rules->mettreAJour(
rules: [new HomeworkRule(RuleType::NO_MONDAY_AFTER, ['day' => 'friday', 'time' => '12:00'])],
enforcementMode: $mode,
enabled: true,
now: new DateTimeImmutable('2026-03-01'),
);
return $rules;
}
private function creerRulesComplet(
EnforcementMode $mode = EnforcementMode::SOFT,
): HomeworkRules {
$rules = HomeworkRules::creer(
tenantId: TenantId::fromString('550e8400-e29b-41d4-a716-446655440001'),
now: new DateTimeImmutable('2026-03-01'),
);
$rules->mettreAJour(
rules: [
new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => 3]),
new HomeworkRule(RuleType::NO_MONDAY_AFTER, ['day' => 'friday', 'time' => '12:00']),
],
enforcementMode: $mode,
enabled: true,
now: new DateTimeImmutable('2026-03-01'),
);
return $rules;
}
}