Lorsqu'un super-admin crée un établissement via l'interface, le système doit automatiquement créer la base tenant, exécuter les migrations, créer le premier utilisateur admin et envoyer l'invitation — le tout de manière asynchrone pour ne pas bloquer la réponse HTTP. Ce mécanisme rend chaque établissement opérationnel dès sa création sans intervention manuelle sur l'infrastructure.
272 lines
10 KiB
PHP
272 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Scolarite\Infrastructure\Api\Controller;
|
|
|
|
use App\Administration\Domain\Model\SchoolClass\ClassId;
|
|
use App\Administration\Domain\Model\Subject\SubjectId;
|
|
use App\Administration\Domain\Model\User\UserId;
|
|
use App\Administration\Infrastructure\Security\SecurityUser;
|
|
use App\Scolarite\Application\Command\UploadHomeworkAttachment\UploadHomeworkAttachmentHandler;
|
|
use App\Scolarite\Application\Port\FileStorage;
|
|
use App\Scolarite\Domain\Model\Homework\Homework;
|
|
use App\Scolarite\Domain\Model\Homework\HomeworkAttachment;
|
|
use App\Scolarite\Domain\Model\Homework\HomeworkAttachmentId;
|
|
use App\Scolarite\Domain\Repository\HomeworkRepository;
|
|
use App\Scolarite\Infrastructure\Api\Controller\HomeworkAttachmentController;
|
|
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkAttachmentRepository;
|
|
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkRepository;
|
|
use App\Shared\Domain\Clock;
|
|
use App\Shared\Domain\Tenant\TenantId;
|
|
use App\Tests\Unit\Scolarite\Infrastructure\Storage\InMemoryFileStorage;
|
|
use DateTimeImmutable;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Symfony\Bundle\SecurityBundle\Security;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
|
|
final class HomeworkAttachmentControllerTest extends TestCase
|
|
{
|
|
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
|
|
private const string TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';
|
|
private const string OTHER_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440099';
|
|
|
|
private InMemoryHomeworkRepository $homeworkRepository;
|
|
private InMemoryHomeworkAttachmentRepository $attachmentRepository;
|
|
private InMemoryFileStorage $fileStorage;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->homeworkRepository = new InMemoryHomeworkRepository();
|
|
$this->attachmentRepository = new InMemoryHomeworkAttachmentRepository();
|
|
$this->fileStorage = new InMemoryFileStorage();
|
|
}
|
|
|
|
#[Test]
|
|
public function downloadReturnsStreamedResponseForExistingAttachment(): void
|
|
{
|
|
$homework = $this->createHomework();
|
|
$this->homeworkRepository->save($homework);
|
|
|
|
$attachment = $this->createAttachment('exercices.pdf', 'homework/files/exercices.pdf');
|
|
$this->attachmentRepository->save($homework->id, $attachment);
|
|
$this->fileStorage->upload('homework/files/exercices.pdf', 'PDF content here', 'application/pdf');
|
|
|
|
$controller = $this->createController(self::TEACHER_ID);
|
|
|
|
$response = $controller->download((string) $homework->id, (string) $attachment->id);
|
|
|
|
self::assertInstanceOf(StreamedResponse::class, $response);
|
|
self::assertSame(200, $response->getStatusCode());
|
|
self::assertSame('application/pdf', $response->headers->get('Content-Type'));
|
|
self::assertStringContainsString('exercices.pdf', $response->headers->get('Content-Disposition') ?? '');
|
|
}
|
|
|
|
#[Test]
|
|
public function downloadReturns404ForNonExistentAttachment(): void
|
|
{
|
|
$homework = $this->createHomework();
|
|
$this->homeworkRepository->save($homework);
|
|
|
|
$controller = $this->createController(self::TEACHER_ID);
|
|
|
|
$this->expectException(NotFoundHttpException::class);
|
|
|
|
$controller->download((string) $homework->id, 'non-existent-attachment-id');
|
|
}
|
|
|
|
#[Test]
|
|
public function downloadReturns404WhenFileNotFoundInStorage(): void
|
|
{
|
|
$homework = $this->createHomework();
|
|
$this->homeworkRepository->save($homework);
|
|
|
|
$attachment = $this->createAttachment('missing.pdf', 'homework/files/missing.pdf');
|
|
$this->attachmentRepository->save($homework->id, $attachment);
|
|
// File NOT uploaded to storage — simulates a missing blob
|
|
|
|
$controller = $this->createController(self::TEACHER_ID);
|
|
|
|
$this->expectException(NotFoundHttpException::class);
|
|
|
|
$controller->download((string) $homework->id, (string) $attachment->id);
|
|
}
|
|
|
|
#[Test]
|
|
public function downloadDeniesAccessToNonOwnerTeacher(): void
|
|
{
|
|
$homework = $this->createHomework();
|
|
$this->homeworkRepository->save($homework);
|
|
|
|
$attachment = $this->createAttachment('exercices.pdf', 'homework/files/exercices.pdf');
|
|
$this->attachmentRepository->save($homework->id, $attachment);
|
|
|
|
$controller = $this->createController(self::OTHER_TEACHER_ID);
|
|
|
|
$this->expectException(AccessDeniedHttpException::class);
|
|
|
|
$controller->download((string) $homework->id, (string) $attachment->id);
|
|
}
|
|
|
|
#[Test]
|
|
public function listDeniesAccessToNonOwnerTeacher(): void
|
|
{
|
|
$homework = $this->createHomework();
|
|
$this->homeworkRepository->save($homework);
|
|
|
|
$controller = $this->createController(self::OTHER_TEACHER_ID);
|
|
|
|
$this->expectException(AccessDeniedHttpException::class);
|
|
|
|
$controller->list((string) $homework->id);
|
|
}
|
|
|
|
#[Test]
|
|
public function deleteDeniesAccessToNonOwnerTeacher(): void
|
|
{
|
|
$homework = $this->createHomework();
|
|
$this->homeworkRepository->save($homework);
|
|
|
|
$attachment = $this->createAttachment('exercices.pdf', 'homework/files/exercices.pdf');
|
|
$this->attachmentRepository->save($homework->id, $attachment);
|
|
|
|
$controller = $this->createController(self::OTHER_TEACHER_ID);
|
|
|
|
$this->expectException(AccessDeniedHttpException::class);
|
|
|
|
$controller->delete((string) $homework->id, (string) $attachment->id);
|
|
}
|
|
|
|
#[Test]
|
|
public function downloadDeniesAccessToUnauthenticatedUser(): void
|
|
{
|
|
$homework = $this->createHomework();
|
|
$this->homeworkRepository->save($homework);
|
|
|
|
$controller = $this->createControllerWithoutUser();
|
|
|
|
$this->expectException(AccessDeniedHttpException::class);
|
|
|
|
$controller->download((string) $homework->id, 'any-attachment-id');
|
|
}
|
|
|
|
#[Test]
|
|
public function listReturnsAttachmentsForOwner(): void
|
|
{
|
|
$homework = $this->createHomework();
|
|
$this->homeworkRepository->save($homework);
|
|
|
|
$attachment = $this->createAttachment('exercices.pdf', 'homework/files/exercices.pdf');
|
|
$this->attachmentRepository->save($homework->id, $attachment);
|
|
|
|
$controller = $this->createController(self::TEACHER_ID);
|
|
|
|
$response = $controller->list((string) $homework->id);
|
|
|
|
self::assertSame(200, $response->getStatusCode());
|
|
/** @var array<array{id: string, filename: string}> $data */
|
|
$data = json_decode((string) $response->getContent(), true);
|
|
self::assertCount(1, $data);
|
|
self::assertSame('exercices.pdf', $data[0]['filename']);
|
|
}
|
|
|
|
#[Test]
|
|
public function deleteRemovesAttachmentAndFile(): void
|
|
{
|
|
$homework = $this->createHomework();
|
|
$this->homeworkRepository->save($homework);
|
|
|
|
$attachment = $this->createAttachment('exercices.pdf', 'homework/files/exercices.pdf');
|
|
$this->attachmentRepository->save($homework->id, $attachment);
|
|
$this->fileStorage->upload('homework/files/exercices.pdf', 'content', 'application/pdf');
|
|
|
|
$controller = $this->createController(self::TEACHER_ID);
|
|
$response = $controller->delete((string) $homework->id, (string) $attachment->id);
|
|
|
|
self::assertSame(204, $response->getStatusCode());
|
|
self::assertEmpty($this->attachmentRepository->findByHomeworkId($homework->id));
|
|
self::assertFalse($this->fileStorage->has('homework/files/exercices.pdf'));
|
|
}
|
|
|
|
private function createHomework(): Homework
|
|
{
|
|
return Homework::creer(
|
|
tenantId: TenantId::fromString(self::TENANT_ID),
|
|
classId: ClassId::fromString('550e8400-e29b-41d4-a716-446655440020'),
|
|
subjectId: SubjectId::fromString('550e8400-e29b-41d4-a716-446655440030'),
|
|
teacherId: UserId::fromString(self::TEACHER_ID),
|
|
title: 'Devoir test',
|
|
description: 'Description',
|
|
dueDate: new DateTimeImmutable('2026-05-01'),
|
|
now: new DateTimeImmutable('2026-04-09'),
|
|
);
|
|
}
|
|
|
|
private function createAttachment(string $filename, string $filePath): HomeworkAttachment
|
|
{
|
|
return new HomeworkAttachment(
|
|
id: HomeworkAttachmentId::generate(),
|
|
filename: $filename,
|
|
filePath: $filePath,
|
|
fileSize: 5000,
|
|
mimeType: 'application/pdf',
|
|
uploadedAt: new DateTimeImmutable('2026-04-09'),
|
|
);
|
|
}
|
|
|
|
private function createController(string $teacherId): HomeworkAttachmentController
|
|
{
|
|
$securityUser = new SecurityUser(
|
|
userId: UserId::fromString($teacherId),
|
|
email: 'teacher@example.com',
|
|
hashedPassword: 'hashed',
|
|
tenantId: TenantId::fromString(self::TENANT_ID),
|
|
roles: ['ROLE_PROF'],
|
|
);
|
|
|
|
$security = $this->createMock(Security::class);
|
|
$security->method('getUser')->willReturn($securityUser);
|
|
|
|
$uploadHandler = $this->createUploadHandler($this->homeworkRepository, $this->fileStorage);
|
|
|
|
return new HomeworkAttachmentController(
|
|
security: $security,
|
|
homeworkRepository: $this->homeworkRepository,
|
|
attachmentRepository: $this->attachmentRepository,
|
|
uploadHandler: $uploadHandler,
|
|
fileStorage: $this->fileStorage,
|
|
);
|
|
}
|
|
|
|
private function createControllerWithoutUser(): HomeworkAttachmentController
|
|
{
|
|
$security = $this->createMock(Security::class);
|
|
$security->method('getUser')->willReturn(null);
|
|
|
|
$uploadHandler = $this->createUploadHandler($this->homeworkRepository, $this->fileStorage);
|
|
|
|
return new HomeworkAttachmentController(
|
|
security: $security,
|
|
homeworkRepository: $this->homeworkRepository,
|
|
attachmentRepository: $this->attachmentRepository,
|
|
uploadHandler: $uploadHandler,
|
|
fileStorage: $this->fileStorage,
|
|
);
|
|
}
|
|
|
|
private function createUploadHandler(HomeworkRepository $homeworkRepository, FileStorage $fileStorage): UploadHomeworkAttachmentHandler
|
|
{
|
|
$clock = new class implements Clock {
|
|
public function now(): DateTimeImmutable
|
|
{
|
|
return new DateTimeImmutable('2026-04-09 10:00:00');
|
|
}
|
|
};
|
|
|
|
return new UploadHomeworkAttachmentHandler($homeworkRepository, $fileStorage, $clock);
|
|
}
|
|
}
|