From d8617fb229b1cb48ac979003d25c39b171080bcb Mon Sep 17 00:00:00 2001 From: Ronan Lenouvel Date: Thu, 23 Jul 2026 18:30:57 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=85=20test(FileRepository,FileSizeFor?= =?UTF-8?q?matter):=20ajouter=20les=20tests=20du=20calcul=20de=20stockage?= =?UTF-8?q?=20(#301)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Repository/FileRepositoryTest.php | 90 +++++++++++++++++++++++++ tests/Service/FileSizeFormatterTest.php | 53 +++++++++++++++ tests/Web/DashboardTest.php | 25 ++++++- 3 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 tests/Repository/FileRepositoryTest.php create mode 100644 tests/Service/FileSizeFormatterTest.php diff --git a/tests/Repository/FileRepositoryTest.php b/tests/Repository/FileRepositoryTest.php new file mode 100644 index 00000000..c9a47ed1 --- /dev/null +++ b/tests/Repository/FileRepositoryTest.php @@ -0,0 +1,90 @@ +em = static::getContainer()->get(EntityManagerInterface::class); + $this->repository = static::getContainer()->get(FileRepository::class); + + $conn = $this->em->getConnection(); + $conn->executeStatement('SET FOREIGN_KEY_CHECKS=0'); + $conn->executeStatement('DELETE FROM files'); + $conn->executeStatement('DELETE FROM folders'); + $conn->executeStatement('DELETE FROM users'); + $conn->executeStatement('SET FOREIGN_KEY_CHECKS=1'); + $this->em->clear(); + } + + private function createUser(string $email): User + { + $user = new User($email, 'Test User'); + $user->setPassword('irrelevant-hash'); + $this->em->persist($user); + $this->em->flush(); + + return $user; + } + + private function createFile(User $owner, Folder $folder, string $name, int $size): File + { + $file = new File($name, 'application/octet-stream', $size, 'irrelevant/path', $folder, $owner); + $this->em->persist($file); + $this->em->flush(); + + return $file; + } + + public function testSumSizeByOwnerReturnsZeroWhenNoFiles(): void + { + $owner = $this->createUser('owner-empty@example.com'); + + $this->assertSame(0, $this->repository->sumSizeByOwner($owner)); + } + + public function testSumSizeByOwnerSumsMultipleFilesOfDifferentSizes(): void + { + $owner = $this->createUser('owner-sum@example.com'); + $folder = new Folder('Uploads', $owner); + $this->em->persist($folder); + $this->em->flush(); + + $this->createFile($owner, $folder, 'a.txt', 100); + $this->createFile($owner, $folder, 'b.txt', 250); + $this->createFile($owner, $folder, 'c.txt', 4096); + + $this->assertSame(4446, $this->repository->sumSizeByOwner($owner)); + } + + public function testSumSizeByOwnerExcludesOtherOwnersFiles(): void + { + $owner = $this->createUser('owner-isolated@example.com'); + $other = $this->createUser('owner-other@example.com'); + + $ownerFolder = new Folder('Uploads', $owner); + $otherFolder = new Folder('Uploads', $other); + $this->em->persist($ownerFolder); + $this->em->persist($otherFolder); + $this->em->flush(); + + $this->createFile($owner, $ownerFolder, 'mine.txt', 500); + $this->createFile($other, $otherFolder, 'not-mine.txt', 999999); + + $this->assertSame(500, $this->repository->sumSizeByOwner($owner)); + } +} diff --git a/tests/Service/FileSizeFormatterTest.php b/tests/Service/FileSizeFormatterTest.php new file mode 100644 index 00000000..9a8f9042 --- /dev/null +++ b/tests/Service/FileSizeFormatterTest.php @@ -0,0 +1,53 @@ +formatter = new FileSizeFormatter(); + } + + public function testFormatZeroBytes(): void + { + $this->assertSame('0 o', $this->formatter->format(0)); + } + + public function testFormatBytesBelowOneKilobyte(): void + { + $this->assertSame('512 o', $this->formatter->format(512)); + } + + public function testFormatExactKilobyte(): void + { + $this->assertSame('1 Ko', $this->formatter->format(1024)); + } + + public function testFormatKilobytesWithDecimal(): void + { + $this->assertSame('1,5 Ko', $this->formatter->format(1536)); + } + + public function testFormatMegabytes(): void + { + $this->assertSame('2 Mo', $this->formatter->format(2 * 1024 * 1024)); + } + + public function testFormatGigabytes(): void + { + $this->assertSame('1,25 Go', $this->formatter->format((int) (1.25 * 1024 * 1024 * 1024))); + } + + public function testFormatTerabytes(): void + { + $this->assertSame('3 To', $this->formatter->format(3 * 1024 * 1024 * 1024 * 1024)); + } +} diff --git a/tests/Web/DashboardTest.php b/tests/Web/DashboardTest.php index 925f2a64..d1185daf 100644 --- a/tests/Web/DashboardTest.php +++ b/tests/Web/DashboardTest.php @@ -81,19 +81,38 @@ public function testDashboardShowsThreeStatCards(): void $this->assertStringContainsString('hc-stat-card', $this->client->getResponse()->getContent()); } - // --- Storage Card (Placeholder) --- + // --- Storage Card --- - public function testDashboardStorageCardShowsPlaceholder(): void + public function testDashboardStorageCardShowsZeroWhenNoFiles(): void { $this->createUser(); $this->login(); $this->client->request('GET', '/'); $content = $this->client->getResponse()->getContent(); - $this->assertStringContainsString('Calcul à implémenter', $content); + $this->assertStringContainsString('0 o', $content); $this->assertStringContainsString('hc-stat-value--placeholder', $content); } + public function testDashboardStorageCardShowsFormattedTotalSize(): void + { + $this->createUser(); + $this->login(); + $user = $this->em->getRepository(User::class)->findOneBy(['email' => 'dashboard@example.com']); + + $folder = new Folder('Uploads', $user); + $this->em->persist($folder); + $this->em->flush(); + + $file = new File('a.txt', 'text/plain', 1536, 'irrelevant/path', $folder, $user); + $this->em->persist($file); + $this->em->flush(); + + $this->client->request('GET', '/'); + $content = $this->client->getResponse()->getContent(); + $this->assertStringContainsString('1,5 Ko', $content); + } + // --- File & Folder Counts --- public function testDashboardShowsFileAndFolderCounts(): void From 00036cf5feee0117726ccf4d5e6706e7423c915b Mon Sep 17 00:00:00 2001 From: Ronan Lenouvel Date: Thu, 23 Jul 2026 18:31:01 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=A8=20feat(HomeController):=20calcule?= =?UTF-8?q?r=20et=20afficher=20le=20poids=20r=C3=A9el=20du=20stockage=20ut?= =?UTF-8?q?ilis=C3=A9=20(#301)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controller/Web/HomeController.php | 4 +++- src/Repository/FileRepository.php | 13 +++++++++++++ src/Service/FileSizeFormatter.php | 25 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/Service/FileSizeFormatter.php diff --git a/src/Controller/Web/HomeController.php b/src/Controller/Web/HomeController.php index 63f700e6..39ed92ba 100644 --- a/src/Controller/Web/HomeController.php +++ b/src/Controller/Web/HomeController.php @@ -8,6 +8,7 @@ use App\Repository\FileRepository; use App\Repository\FolderRepository; use App\Repository\ShareRepository; +use App\Service\FileSizeFormatter; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; @@ -24,6 +25,7 @@ public function __construct( private readonly FolderRepository $folderRepository, private readonly FileRepository $fileRepository, private readonly ShareRepository $shareRepository, + private readonly FileSizeFormatter $fileSizeFormatter, ) {} #[Route('/', name: 'app_home')] @@ -68,7 +70,7 @@ public function index(): Response 'totalCount' => $folderCount + $fileCount, 'activeSharesCount' => $activeSharesCount, 'recentItems' => $recentItems, - 'storageUsedLabel' => 'Calcul à implémenter', + 'storageUsedLabel' => $this->fileSizeFormatter->format($this->fileRepository->sumSizeByOwner($user)), ]); } } diff --git a/src/Repository/FileRepository.php b/src/Repository/FileRepository.php index e4d4f942..cdace63d 100644 --- a/src/Repository/FileRepository.php +++ b/src/Repository/FileRepository.php @@ -135,6 +135,19 @@ public function findRecentByOwner(User $owner, int $limit = 5): array ->getResult(); } + /** Somme des tailles (octets) de tous les fichiers d'un owner. */ + public function sumSizeByOwner(User $owner): int + { + $result = $this->createQueryBuilder('f') + ->select('SUM(f.size)') + ->andWhere('IDENTITY(f.owner) = :ownerId') + ->setParameter('ownerId', $owner->getId()->toBinary()) + ->getQuery() + ->getSingleScalarResult(); + + return $result !== null ? (int) $result : 0; + } + public function findWithoutMedia(): array { return $this->createQueryBuilder('f') diff --git a/src/Service/FileSizeFormatter.php b/src/Service/FileSizeFormatter.php new file mode 100644 index 00000000..566617cd --- /dev/null +++ b/src/Service/FileSizeFormatter.php @@ -0,0 +1,25 @@ + Date: Thu, 23 Jul 2026 18:31:06 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=93=8B=20chore(avancement):=20documen?= =?UTF-8?q?ter=20la=20r=C3=A9solution=20#301?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/avancement.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/avancement.md b/.github/avancement.md index e4caef09..5c69fa37 100644 --- a/.github/avancement.md +++ b/.github/avancement.md @@ -6,6 +6,14 @@ --- +## ✅ Stockage utilisé sur le dashboard (2026-07-23, #301, branche `feature/301-storage-used-dashboard`) + +- `HomeController::index()` affichait un placeholder statique (`'Calcul à implémenter'`) à la place du poids réel de stockage. +- `FileRepository::sumSizeByOwner(User $owner): int` : agrégation SQL `SUM(f.size)` filtrée par owner, retourne `0` (pas `null`) si aucun fichier — pas de scan disque, `File::$size` est déjà connu à l'upload. +- `FileSizeFormatter` (nouveau service, `src/Service/`) : formatte un nombre d'octets en Ko/Mo/Go/To lisible (virgule française, `1,5 Ko`), testé unitairement en isolation (`TestCase`, pas de kernel). +- Décision : les fichiers neutralisés (#278) comptent dans le total — ils occupent toujours l'espace disque réel, seule leur interprétation MIME est neutralisée. `Media` (vignettes/previews) n'a pas de champ `size` en base, donc hors périmètre naturellement. +- Tests : `FileRepositoryTest` (0 fichier, somme multi-fichiers, isolation entre owners), `FileSizeFormatterTest` (paliers o/Ko/Mo/Go/To), `DashboardTest` mis à jour (l'ancien test vérifiait littéralement le placeholder — remplacé par une vérification du calcul réel bout-en-bout). + ## ✅ Espace « Mes partages » pour l'invité (2026-07-23, #273, branche `feature/273-mes-partages`) - Nouvelle page `/mes-partages` listant les `Share` actifs (non révoqués, non expirés) dont l'utilisateur connecté est le `guest` — seule porte d'entrée jusqu'ici était le lien contenu dans l'email de notification. From 0f80106ead5f4521e1d78987838fd08c2fff06e9 Mon Sep 17 00:00:00 2001 From: Ronan Lenouvel Date: Thu, 23 Jul 2026 18:32:30 +0200 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=93=96=20docs(deploiement):=20documen?= =?UTF-8?q?ter=20le=20pi=C3=A8ge=20d'instabilit=C3=A9=20SSH=20transitoire?= =?UTF-8?q?=20sur=20deploy-all.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/deploiement.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.claude/deploiement.md b/.claude/deploiement.md index 329f42d1..57a03df5 100644 --- a/.claude/deploiement.md +++ b/.claude/deploiement.md @@ -377,3 +377,4 @@ seul l'email de notification manque. | SSH refusé | IP non whitelistée | cPanel → Accès SSH → Autorisation SSH | | Pas de vignette/EXIF dans la Galerie | Worker Messenger absent | Vérifier la tâche cron (voir « Worker Messenger » ci-dessus) | | Messages Messenger jamais consommés (`messenger:stats` ne baisse jamais) | `var/log/` absent sur le serveur | Le cron redirige vers `var/log/messenger.log` (`>>`) : si le dossier n'existe pas, la redirection échoue et **la commande PHP ne s'exécute jamais**, sans erreur visible. `mkdir -p var/log` (corrigé dans `bin/deploy-all.sh` depuis le 2026-07-18, mais les instances déployées avant cette date doivent l'avoir manuellement). | +| Une seule instance en `❌` sur un `deploy-all.sh`, différente à chaque run, sans message d'erreur clair | Instabilité SSH transitoire (timeout/latence ponctuelle sur le mutualisé, distincte du piège OOM/LVE déjà documenté ci-dessus) | Relancer simplement `bash bin/deploy-all.sh` une seconde fois — le script est idempotent (`git pull`/`composer install`/migrations ne font rien si déjà à jour) ; vécu le 2026-07-23 sur `yannick.lenouvel.me`, résolu au 2ᵉ run sans autre action. Si l'échec persiste sur la même instance après 2 essais, chercher la cause précise (cf. ligne OOM/LVE ci-dessus) plutôt que de continuer à relancer en boucle. |