feat: Bloquer la création de devoirs non conformes en mode hard
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 utilisant le mode "Hard" des règles de devoirs
empêchent désormais les enseignants de créer des devoirs hors règles.
Contrairement au mode "Soft" (avertissement avec possibilité de passer
outre), le mode "Hard" est un blocage strict : même acknowledgeWarning
ne permet pas de contourner.

L'API retourne 422 (au lieu de 409 pour le soft) avec des dates
conformes suggérées calculées via le calendrier scolaire (weekends,
fériés, vacances exclus). Le frontend affiche un modal de blocage
avec les raisons, des dates cliquables, et une validation client
inline qui empêche la soumission de dates non conformes.
This commit is contained in:
2026-03-19 00:35:20 +01:00
parent c46d053db7
commit 40b646a5de
15 changed files with 1496 additions and 8 deletions

View File

@@ -0,0 +1,204 @@
<?php
declare(strict_types=1);
namespace App\Tests\Unit\Administration\Application\Service;
use App\Administration\Application\Service\HomeworkRulesValidator;
use App\Administration\Application\Service\ValidDueDateSuggester;
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 function count;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
final class ValidDueDateSuggesterTest extends TestCase
{
private ValidDueDateSuggester $suggester;
protected function setUp(): void
{
$this->suggester = new ValidDueDateSuggester(new HomeworkRulesValidator());
}
#[Test]
public function suggestsNextValidDatesForMinimumDelay(): void
{
$rules = $this->creerRulesAvecDelai(3);
$creationDate = new DateTimeImmutable('2026-03-18 10:00'); // mercredi
// Requested date is too soon (jeudi 19 mars, only 1 day)
$suggestions = $this->suggester->suggerer(
new DateTimeImmutable('2026-03-19'),
$rules,
$creationDate,
);
self::assertNotEmpty($suggestions);
self::assertLessThanOrEqual(3, count($suggestions));
// Each suggestion must be valid against the rules
$validator = new HomeworkRulesValidator();
foreach ($suggestions as $date) {
self::assertInstanceOf(DateTimeImmutable::class, $date);
$result = $validator->valider($rules, $date, $creationDate);
self::assertTrue($result->estValide(), 'Suggested date ' . $date->format('Y-m-d') . ' should be valid');
}
}
#[Test]
public function suggestsUpToMaxSuggestions(): void
{
$rules = $this->creerRulesAvecDelai(3);
$creationDate = new DateTimeImmutable('2026-03-18 10:00');
$suggestions = $this->suggester->suggerer(
new DateTimeImmutable('2026-03-19'),
$rules,
$creationDate,
maxSuggestions: 5,
);
self::assertCount(5, $suggestions);
}
#[Test]
public function skipsWeekends(): void
{
$rules = $this->creerRulesAvecDelai(3);
// Jeudi 19 mars, creation date is mercredi 18 mars
$creationDate = new DateTimeImmutable('2026-03-18 10:00');
$suggestions = $this->suggester->suggerer(
new DateTimeImmutable('2026-03-19'),
$rules,
$creationDate,
);
foreach ($suggestions as $date) {
$dayOfWeek = (int) $date->format('N');
self::assertLessThanOrEqual(5, $dayOfWeek, 'Suggested date ' . $date->format('Y-m-d') . ' should not be a weekend');
}
}
#[Test]
public function suggestsDatesInChronologicalOrder(): void
{
$rules = $this->creerRulesAvecDelai(3);
$creationDate = new DateTimeImmutable('2026-03-18 10:00');
$suggestions = $this->suggester->suggerer(
new DateTimeImmutable('2026-03-19'),
$rules,
$creationDate,
);
for ($i = 1; $i < count($suggestions); ++$i) {
self::assertGreaterThan(
$suggestions[$i - 1]->format('Y-m-d'),
$suggestions[$i]->format('Y-m-d'),
'Suggestions should be in chronological order',
);
}
}
#[Test]
public function returnsEmptyWhenRulesDisabled(): void
{
$rules = $this->creerRulesAvecDelai(3, EnforcementMode::DISABLED, false);
$creationDate = new DateTimeImmutable('2026-03-18 10:00');
$suggestions = $this->suggester->suggerer(
new DateTimeImmutable('2026-03-19'),
$rules,
$creationDate,
);
self::assertEmpty($suggestions);
}
#[Test]
public function searchIsBoundedAndDoesNotRunForever(): void
{
$rules = $this->creerRulesAvecDelai(30);
$creationDate = new DateTimeImmutable('2026-03-18 10:00');
$suggestions = $this->suggester->suggerer(
new DateTimeImmutable('2026-03-19'),
$rules,
$creationDate,
maxSuggestions: 100,
);
// La recherche est bornée à 60 jours. Avec un délai de 30 jours,
// seules les dates après le 17 avril sont valides (~21 jours ouvrés).
self::assertNotEmpty($suggestions);
self::assertLessThan(100, count($suggestions));
}
#[Test]
public function suggestsValidDatesForMondayRule(): void
{
$rules = $this->creerRulesAvecLundi();
// Samedi 21 mars, trying to create homework for lundi 23 mars (after friday cutoff)
$creationDate = new DateTimeImmutable('2026-03-21 10:00');
$suggestions = $this->suggester->suggerer(
new DateTimeImmutable('2026-03-23'), // lundi
$rules,
$creationDate,
);
self::assertNotEmpty($suggestions);
$validator = new HomeworkRulesValidator();
foreach ($suggestions as $date) {
$result = $validator->valider($rules, $date, $creationDate);
self::assertTrue($result->estValide(), 'Suggested date ' . $date->format('Y-m-d') . ' should be valid');
}
}
private function creerRulesAvecDelai(
int $days,
EnforcementMode $mode = EnforcementMode::HARD,
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::HARD,
): 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;
}
}