feat: Permettre aux administrateurs de configurer les règles de devoirs
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:
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Functional\Administration\Api;
|
||||
|
||||
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
|
||||
/**
|
||||
* Functional tests for homework rules API endpoints.
|
||||
*
|
||||
* Validates routing, tenant isolation, authentication, and
|
||||
* basic request/response contracts without full auth flow.
|
||||
*/
|
||||
final class HomeworkRulesEndpointsTest extends ApiTestCase
|
||||
{
|
||||
protected static ?bool $alwaysBootKernel = true;
|
||||
|
||||
// =========================================================================
|
||||
// GET /settings/homework-rules — Without tenant
|
||||
// =========================================================================
|
||||
|
||||
#[Test]
|
||||
public function getHomeworkRulesReturns404WithoutTenant(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
$client->request('GET', '/api/settings/homework-rules', [
|
||||
'headers' => [
|
||||
'Host' => 'localhost',
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
]);
|
||||
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// GET /settings/homework-rules — Without authentication (with tenant)
|
||||
// =========================================================================
|
||||
|
||||
#[Test]
|
||||
public function getHomeworkRulesReturns401WithoutAuthentication(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
$client->request('GET', 'http://ecole-alpha.classeo.local/api/settings/homework-rules', [
|
||||
'headers' => [
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
]);
|
||||
|
||||
self::assertResponseStatusCodeSame(401);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// PUT /settings/homework-rules — Without tenant
|
||||
// =========================================================================
|
||||
|
||||
#[Test]
|
||||
public function updateHomeworkRulesReturns404WithoutTenant(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
$client->request('PUT', '/api/settings/homework-rules', [
|
||||
'headers' => [
|
||||
'Host' => 'localhost',
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'json' => [
|
||||
'rules' => [],
|
||||
'enforcementMode' => 'soft',
|
||||
'enabled' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function updateHomeworkRulesReturns401WithoutAuthentication(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
$client->request('PUT', 'http://ecole-alpha.classeo.local/api/settings/homework-rules', [
|
||||
'headers' => [
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'json' => [
|
||||
'rules' => [],
|
||||
'enforcementMode' => 'soft',
|
||||
'enabled' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
self::assertResponseStatusCodeSame(401);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// GET /settings/homework-rules/history — Without tenant / auth
|
||||
// =========================================================================
|
||||
|
||||
#[Test]
|
||||
public function getHistoryReturns404WithoutTenant(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
$client->request('GET', '/api/settings/homework-rules/history', [
|
||||
'headers' => [
|
||||
'Host' => 'localhost',
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
]);
|
||||
|
||||
self::assertResponseStatusCodeSame(404);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function getHistoryReturns401WithoutAuthentication(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
$client->request('GET', 'http://ecole-alpha.classeo.local/api/settings/homework-rules/history', [
|
||||
'headers' => [
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
]);
|
||||
|
||||
self::assertResponseStatusCodeSame(401);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Route existence — tenant + no auth proves route exists (401 not 404)
|
||||
// =========================================================================
|
||||
|
||||
#[Test]
|
||||
public function getHomeworkRulesRouteExistsWithTenant(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
$client->request('GET', 'http://ecole-alpha.classeo.local/api/settings/homework-rules', [
|
||||
'headers' => ['Accept' => 'application/json'],
|
||||
]);
|
||||
|
||||
// 401 (no auth) not 404 — proves route is registered
|
||||
self::assertResponseStatusCodeSame(401);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function putHomeworkRulesRouteExistsWithTenant(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
$client->request('PUT', 'http://ecole-alpha.classeo.local/api/settings/homework-rules', [
|
||||
'headers' => [
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'json' => [
|
||||
'rules' => [],
|
||||
'enforcementMode' => 'soft',
|
||||
'enabled' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
self::assertResponseStatusCodeSame(401);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function historyRouteExistsWithTenant(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
|
||||
$client->request('GET', 'http://ecole-alpha.classeo.local/api/settings/homework-rules/history', [
|
||||
'headers' => ['Accept' => 'application/json'],
|
||||
]);
|
||||
|
||||
self::assertResponseStatusCodeSame(401);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Functional\Administration\Application;
|
||||
|
||||
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\Domain\Repository\HomeworkRulesRepository;
|
||||
use App\Shared\Domain\Tenant\TenantId;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* Tests fonctionnels pour la persistence des HomeworkRules.
|
||||
*
|
||||
* Vérifie le round-trip DBAL : save → findByTenantId → hydration correcte.
|
||||
*/
|
||||
final class HomeworkRulesPersistenceFunctionalTest extends KernelTestCase
|
||||
{
|
||||
private const string TENANT_ID = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeee01';
|
||||
|
||||
private HomeworkRulesRepository $repository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->repository = static::getContainer()->get(HomeworkRulesRepository::class);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
/** @var Connection $connection */
|
||||
$connection = static::getContainer()->get(Connection::class);
|
||||
$connection->executeStatement(
|
||||
'DELETE FROM homework_rules_history WHERE tenant_id = :t',
|
||||
['t' => self::TENANT_ID],
|
||||
);
|
||||
$connection->executeStatement(
|
||||
'DELETE FROM homework_rules WHERE tenant_id = :t',
|
||||
['t' => self::TENANT_ID],
|
||||
);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function saveAndRetrieveEmptyRules(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString(self::TENANT_ID);
|
||||
$rules = HomeworkRules::creer(tenantId: $tenantId, now: new DateTimeImmutable('2026-03-17 10:00:00'));
|
||||
$this->repository->save($rules);
|
||||
|
||||
$loaded = $this->repository->findByTenantId($tenantId);
|
||||
|
||||
self::assertNotNull($loaded);
|
||||
self::assertTrue($loaded->tenantId->equals($tenantId));
|
||||
self::assertSame([], $loaded->rules);
|
||||
self::assertSame(EnforcementMode::SOFT, $loaded->enforcementMode);
|
||||
self::assertTrue($loaded->enabled);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function saveAndRetrieveWithRules(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString(self::TENANT_ID);
|
||||
$rules = HomeworkRules::creer(tenantId: $tenantId, now: new DateTimeImmutable('2026-03-17 10:00:00'));
|
||||
|
||||
$rules->mettreAJour(
|
||||
rules: [
|
||||
new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => 3]),
|
||||
new HomeworkRule(RuleType::NO_MONDAY_AFTER, ['day' => 'friday', 'time' => '12:00']),
|
||||
],
|
||||
enforcementMode: EnforcementMode::HARD,
|
||||
enabled: true,
|
||||
now: new DateTimeImmutable('2026-03-17 11:00:00'),
|
||||
);
|
||||
|
||||
$this->repository->save($rules);
|
||||
|
||||
$loaded = $this->repository->findByTenantId($tenantId);
|
||||
|
||||
self::assertNotNull($loaded);
|
||||
self::assertCount(2, $loaded->rules);
|
||||
self::assertSame(RuleType::MINIMUM_DELAY, $loaded->rules[0]->type);
|
||||
self::assertSame(3, $loaded->rules[0]->params['days']);
|
||||
self::assertSame(RuleType::NO_MONDAY_AFTER, $loaded->rules[1]->type);
|
||||
self::assertSame('friday', $loaded->rules[1]->params['day']);
|
||||
self::assertSame('12:00', $loaded->rules[1]->params['time']);
|
||||
self::assertSame(EnforcementMode::HARD, $loaded->enforcementMode);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function upsertOverwritesExistingConfig(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString(self::TENANT_ID);
|
||||
$rules = HomeworkRules::creer(tenantId: $tenantId, now: new DateTimeImmutable('2026-03-17 10:00:00'));
|
||||
$this->repository->save($rules);
|
||||
|
||||
$rules->mettreAJour(
|
||||
rules: [new HomeworkRule(RuleType::MINIMUM_DELAY, ['days' => 5])],
|
||||
enforcementMode: EnforcementMode::HARD,
|
||||
enabled: true,
|
||||
now: new DateTimeImmutable('2026-03-17 12:00:00'),
|
||||
);
|
||||
$this->repository->save($rules);
|
||||
|
||||
$loaded = $this->repository->findByTenantId($tenantId);
|
||||
|
||||
self::assertNotNull($loaded);
|
||||
self::assertCount(1, $loaded->rules);
|
||||
self::assertSame(5, $loaded->rules[0]->params['days']);
|
||||
self::assertSame(EnforcementMode::HARD, $loaded->enforcementMode);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function findByTenantIdReturnsNullWhenNotFound(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString('99999999-9999-9999-9999-999999999999');
|
||||
|
||||
self::assertNull($this->repository->findByTenantId($tenantId));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function getThrowsWhenNotFound(): void
|
||||
{
|
||||
$this->expectException(\App\Administration\Domain\Exception\HomeworkRulesNotFoundException::class);
|
||||
|
||||
$this->repository->get(TenantId::fromString('99999999-9999-9999-9999-999999999999'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function disabledStatePersistedCorrectly(): void
|
||||
{
|
||||
$tenantId = TenantId::fromString(self::TENANT_ID);
|
||||
$rules = HomeworkRules::creer(tenantId: $tenantId, now: new DateTimeImmutable('2026-03-17 10:00:00'));
|
||||
$rules->desactiver(new DateTimeImmutable('2026-03-17 11:00:00'));
|
||||
$this->repository->save($rules);
|
||||
|
||||
$loaded = $this->repository->findByTenantId($tenantId);
|
||||
|
||||
self::assertNotNull($loaded);
|
||||
self::assertFalse($loaded->enabled);
|
||||
self::assertSame(EnforcementMode::DISABLED, $loaded->enforcementMode);
|
||||
self::assertFalse($loaded->estActif());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user