feat: Permettre aux enseignants de dupliquer un devoir vers plusieurs classes
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

Un enseignant qui donne le même travail à plusieurs classes devait
jusqu'ici recréer manuellement chaque devoir. La duplication permet
de sélectionner les classes cibles, d'ajuster les dates d'échéance
par classe, et de créer tous les devoirs en une seule opération
atomique (transaction).

La validation s'effectue par classe (affectation enseignant, date
d'échéance) avec un rapport d'erreurs détaillé. L'infrastructure
de warnings est prête pour les règles de timing de la Story 5.3.
Le filtrage par classe dans la liste des devoirs passe côté serveur
pour rester compatible avec la pagination.
This commit is contained in:
2026-03-15 14:20:48 +01:00
parent e9efb90f59
commit 68179a929f
18 changed files with 1831 additions and 2 deletions

View File

@@ -39,6 +39,7 @@ final class HomeworkEndpointsTest extends ApiTestCase
private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-886655440001';
private const string HOMEWORK_ID = '550e8400-e29b-41d4-a716-996655440001';
private const string DELETED_HOMEWORK_ID = '550e8400-e29b-41d4-a716-aa6655440001';
private const string TARGET_CLASS_ID = '550e8400-e29b-41d4-a716-776655440002';
protected function setUp(): void
{
@@ -52,7 +53,7 @@ final class HomeworkEndpointsTest extends ApiTestCase
/** @var Connection $connection */
$connection = $container->get(Connection::class);
$connection->executeStatement('DELETE FROM homework WHERE tenant_id = :tid', ['tid' => self::TENANT_ID]);
$connection->executeStatement('DELETE FROM school_classes WHERE id = :id', ['id' => self::CLASS_ID]);
$connection->executeStatement('DELETE FROM school_classes WHERE id IN (:id1, :id2)', ['id1' => self::CLASS_ID, 'id2' => self::TARGET_CLASS_ID]);
$connection->executeStatement('DELETE FROM subjects WHERE id = :id', ['id' => self::SUBJECT_ID]);
$connection->executeStatement('DELETE FROM users WHERE id IN (:o, :t)', ['o' => self::OWNER_TEACHER_ID, 't' => self::OTHER_TEACHER_ID]);
@@ -92,6 +93,12 @@ final class HomeworkEndpointsTest extends ApiTestCase
ON CONFLICT (id) DO NOTHING",
['id' => self::SUBJECT_ID, 'tid' => self::TENANT_ID, 'sid' => $schoolId],
);
$connection->executeStatement(
"INSERT INTO school_classes (id, tenant_id, school_id, academic_year_id, name, status, created_at, updated_at)
VALUES (:id, :tid, :sid, :ayid, 'Test-HW-Target-Class', 'active', NOW(), NOW())
ON CONFLICT (id) DO NOTHING",
['id' => self::TARGET_CLASS_ID, 'tid' => self::TENANT_ID, 'sid' => $schoolId, 'ayid' => $academicYearId],
);
}
// =========================================================================
@@ -382,6 +389,123 @@ final class HomeworkEndpointsTest extends ApiTestCase
self::assertResponseStatusCodeSame(400);
}
// =========================================================================
// 5.2-FUNC-001 (P1) - POST /homework/{id}/duplicate without tenant -> 404
// =========================================================================
#[Test]
public function duplicateHomeworkReturns404WithoutTenant(): void
{
$client = static::createClient();
$client->request('POST', '/api/homework/' . self::HOMEWORK_ID . '/duplicate', [
'headers' => [
'Host' => 'localhost',
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
'json' => [
'targetClassIds' => [self::TARGET_CLASS_ID],
],
]);
self::assertResponseStatusCodeSame(404);
}
// =========================================================================
// 5.2-FUNC-002 (P1) - POST /homework/{id}/duplicate without auth -> 401
// =========================================================================
#[Test]
public function duplicateHomeworkReturns401WithoutAuthentication(): void
{
$client = static::createClient();
$client->request('POST', 'http://ecole-alpha.classeo.local/api/homework/' . self::HOMEWORK_ID . '/duplicate', [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
'json' => [
'targetClassIds' => [self::TARGET_CLASS_ID],
],
]);
self::assertResponseStatusCodeSame(401);
}
// =========================================================================
// 5.2-FUNC-003 (P1) - POST /homework/{id}/duplicate empty classes -> 400
// =========================================================================
#[Test]
public function duplicateHomeworkReturns400WithEmptyTargetClasses(): void
{
$this->persistHomework(self::HOMEWORK_ID, HomeworkStatus::PUBLISHED);
$client = $this->createAuthenticatedClient(self::OWNER_TEACHER_ID, ['ROLE_PROF']);
$client->request('POST', 'http://ecole-alpha.classeo.local/api/homework/' . self::HOMEWORK_ID . '/duplicate', [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
'json' => [
'targetClassIds' => [],
],
]);
self::assertResponseStatusCodeSame(400);
}
// =========================================================================
// 5.2-FUNC-004 (P1) - POST /homework/{id}/duplicate not found -> 404
// =========================================================================
#[Test]
public function duplicateHomeworkReturns404WhenHomeworkNotFound(): void
{
$nonExistentId = '00000000-0000-0000-0000-000000000099';
$client = $this->createAuthenticatedClient(self::OWNER_TEACHER_ID, ['ROLE_PROF']);
$client->request('POST', 'http://ecole-alpha.classeo.local/api/homework/' . $nonExistentId . '/duplicate', [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
'json' => [
'targetClassIds' => [self::TARGET_CLASS_ID],
],
]);
self::assertResponseStatusCodeSame(404);
}
// =========================================================================
// 5.2-FUNC-005 (P1) - POST /homework/{id}/duplicate non-owner -> 404
// =========================================================================
#[Test]
public function duplicateHomeworkReturns404WhenTeacherNotOwner(): void
{
$this->persistHomework(self::HOMEWORK_ID, HomeworkStatus::PUBLISHED);
$client = $this->createAuthenticatedClient(self::OTHER_TEACHER_ID, ['ROLE_PROF']);
$client->request('POST', 'http://ecole-alpha.classeo.local/api/homework/' . self::HOMEWORK_ID . '/duplicate', [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
'json' => [
'targetClassIds' => [self::TARGET_CLASS_ID],
],
]);
self::assertResponseStatusCodeSame(404);
}
// =========================================================================
// Helpers
// =========================================================================