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,163 @@
<?php
declare(strict_types=1);
namespace App\Tests\Unit\Administration\Application\Command\UpdateHomeworkRules;
use App\Administration\Application\Command\UpdateHomeworkRules\UpdateHomeworkRulesCommand;
use App\Administration\Application\Command\UpdateHomeworkRules\UpdateHomeworkRulesHandler;
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\Administration\Infrastructure\Persistence\InMemory\InMemoryHomeworkRulesHistoryRepository;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryHomeworkRulesRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
final class UpdateHomeworkRulesHandlerTest extends TestCase
{
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
private const string USER_ID = '550e8400-e29b-41d4-a716-446655440099';
private InMemoryHomeworkRulesRepository $rulesRepository;
private InMemoryHomeworkRulesHistoryRepository $historyRepository;
private Clock $clock;
protected function setUp(): void
{
$this->rulesRepository = new InMemoryHomeworkRulesRepository();
$this->historyRepository = new InMemoryHomeworkRulesHistoryRepository();
$this->clock = new class implements Clock {
public function now(): DateTimeImmutable
{
return new DateTimeImmutable('2026-03-17 10:00:00');
}
};
}
#[Test]
public function itCreatesRulesWhenNoneExist(): void
{
$handler = $this->handler();
$result = $handler($this->commandAvecDelai(3));
self::assertCount(1, $result->rules);
self::assertSame(RuleType::MINIMUM_DELAY, $result->rules[0]->type);
self::assertSame(EnforcementMode::SOFT, $result->enforcementMode);
self::assertTrue($result->enabled);
}
#[Test]
public function itUpdatesExistingRules(): void
{
$this->seedRules();
$handler = $this->handler();
$result = $handler($this->commandAvecDelai(5, 'hard'));
self::assertCount(1, $result->rules);
self::assertSame(5, $result->rules[0]->params['days']);
self::assertSame(EnforcementMode::HARD, $result->enforcementMode);
}
#[Test]
public function itRecordsHistory(): void
{
$handler = $this->handler();
$handler($this->commandAvecDelai(3));
$history = $this->historyRepository->findByTenant(TenantId::fromString(self::TENANT_ID));
self::assertCount(1, $history);
self::assertSame('soft', $history[0]['enforcement_mode']);
}
#[Test]
public function itSavesToRepository(): void
{
$handler = $this->handler();
$handler($this->commandAvecDelai(3));
$saved = $this->rulesRepository->findByTenantId(TenantId::fromString(self::TENANT_ID));
self::assertNotNull($saved);
self::assertCount(1, $saved->rules);
}
#[Test]
public function itDisablesRules(): void
{
$this->seedRules();
$handler = $this->handler();
$result = $handler(new UpdateHomeworkRulesCommand(
tenantId: self::TENANT_ID,
rules: [],
enforcementMode: 'disabled',
enabled: false,
changedBy: self::USER_ID,
));
self::assertFalse($result->enabled);
self::assertSame(EnforcementMode::DISABLED, $result->enforcementMode);
}
#[Test]
public function itHandlesMultipleRules(): void
{
$handler = $this->handler();
$result = $handler(new UpdateHomeworkRulesCommand(
tenantId: self::TENANT_ID,
rules: [
['type' => 'minimum_delay', 'params' => ['days' => 3]],
['type' => 'no_monday_after', 'params' => ['day' => 'friday', 'time' => '12:00']],
],
enforcementMode: 'soft',
enabled: true,
changedBy: self::USER_ID,
));
self::assertCount(2, $result->rules);
}
private function handler(): UpdateHomeworkRulesHandler
{
return new UpdateHomeworkRulesHandler(
$this->rulesRepository,
$this->historyRepository,
$this->clock,
);
}
private function commandAvecDelai(int $days, string $mode = 'soft'): UpdateHomeworkRulesCommand
{
return new UpdateHomeworkRulesCommand(
tenantId: self::TENANT_ID,
rules: [['type' => 'minimum_delay', 'params' => ['days' => $days]]],
enforcementMode: $mode,
enabled: true,
changedBy: self::USER_ID,
);
}
private function seedRules(): void
{
$rules = HomeworkRules::creer(
tenantId: TenantId::fromString(self::TENANT_ID),
now: new DateTimeImmutable('2026-03-01'),
);
$rules->mettreAJour(
rules: [new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => 3])],
enforcementMode: EnforcementMode::SOFT,
enabled: true,
now: new DateTimeImmutable('2026-03-01'),
);
$this->rulesRepository->save($rules);
}
}