validator = new HomeworkRulesValidator(); $this->suggester = new ValidDueDateSuggester($this->validator); } #[Test] public function returnsOkWhenNoRulesConfigured(): void { $checker = $this->createChecker(rules: null); $tenantId = TenantId::fromString(self::TENANT_ID); $result = $checker->verifier( $tenantId, new DateTimeImmutable('2026-03-25'), new DateTimeImmutable('2026-03-18 10:00'), ); self::assertTrue($result->estValide()); self::assertFalse($result->bloquant); self::assertEmpty($result->suggestedDates); } #[Test] public function returnsOkWhenRulesRespected(): void { $rules = $this->creerRulesAvecDelai(3, EnforcementMode::HARD); $checker = $this->createChecker(rules: $rules); $tenantId = TenantId::fromString(self::TENANT_ID); $result = $checker->verifier( $tenantId, new DateTimeImmutable('2026-03-25'), // 7 days ahead, > 3 day min new DateTimeImmutable('2026-03-18 10:00'), ); self::assertTrue($result->estValide()); self::assertEmpty($result->suggestedDates); } #[Test] public function returnsBlockingResultWithSuggestedDatesInHardMode(): void { $rules = $this->creerRulesAvecDelai(7, EnforcementMode::HARD); $checker = $this->createChecker(rules: $rules); $tenantId = TenantId::fromString(self::TENANT_ID); $result = $checker->verifier( $tenantId, new DateTimeImmutable('2026-03-19'), // only 1 day ahead, < 7 day min new DateTimeImmutable('2026-03-18 10:00'), ); self::assertTrue($result->estBloquant()); self::assertTrue($result->bloquant); self::assertNotEmpty($result->suggestedDates); self::assertCount(3, $result->suggestedDates); // Suggested dates are formatted as Y-m-d strings foreach ($result->suggestedDates as $date) { self::assertMatchesRegularExpression('/^\d{4}-\d{2}-\d{2}$/', $date); } } #[Test] public function returnsWarningWithoutSuggestedDatesInSoftMode(): void { $rules = $this->creerRulesAvecDelai(7, EnforcementMode::SOFT); $checker = $this->createChecker(rules: $rules); $tenantId = TenantId::fromString(self::TENANT_ID); $result = $checker->verifier( $tenantId, new DateTimeImmutable('2026-03-19'), // only 1 day ahead, < 7 day min new DateTimeImmutable('2026-03-18 10:00'), ); self::assertTrue($result->estAvertissement()); self::assertFalse($result->bloquant); self::assertEmpty($result->suggestedDates); } #[Test] public function warningsContainRuleParams(): void { $rules = $this->creerRulesAvecDelai(5, EnforcementMode::SOFT); $checker = $this->createChecker(rules: $rules); $tenantId = TenantId::fromString(self::TENANT_ID); $result = $checker->verifier( $tenantId, new DateTimeImmutable('2026-03-19'), new DateTimeImmutable('2026-03-18 10:00'), ); self::assertCount(1, $result->warnings); self::assertSame('minimum_delay', $result->warnings[0]->ruleType); self::assertSame(['days' => 5], $result->warnings[0]->params); } private function createChecker(?HomeworkRules $rules): AdministrationHomeworkRulesChecker { $repository = new class($rules) implements HomeworkRulesRepository { public function __construct(private readonly ?HomeworkRules $rules) { } public function save(HomeworkRules $homeworkRules): void { } public function get(TenantId $tenantId): HomeworkRules { return $this->rules ?? throw new RuntimeException('Not found'); } public function findByTenantId(TenantId $tenantId): ?HomeworkRules { return $this->rules; } }; $calendarProvider = new class implements CurrentCalendarProvider { public function forCurrentYear(TenantId $tenantId): SchoolCalendar { return SchoolCalendar::reconstitute( tenantId: $tenantId, academicYearId: AcademicYearId::fromString('550e8400-e29b-41d4-a716-446655440002'), zone: null, entries: [], ); } }; return new AdministrationHomeworkRulesChecker( $repository, $this->validator, $this->suggester, $calendarProvider, ); } private function creerRulesAvecDelai(int $days, EnforcementMode $mode): HomeworkRules { $rules = HomeworkRules::creer( tenantId: TenantId::fromString(self::TENANT_ID), now: new DateTimeImmutable('2026-03-01'), ); $rules->mettreAJour( rules: [new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => $days])], enforcementMode: $mode, enabled: true, now: new DateTimeImmutable('2026-03-01'), ); return $rules; } }