Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/avancement.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ Score global : **9/10** — 4/5 axes de remédiation implémentés.
3. **Badge de couverture d'album** — design trop brut (icône étoile sur fond noir), à retravailler visuellement
4. **Rendre les interactions asynchrones (fetch/AJAX) au lieu de POST + reload complet** — ✅ fait pour create/edit/delete de la gestion des invités (`guest_management_controller.js`) ; reste à généraliser le même pattern (détection `Accept: application/json` + fallback progressif + contrôleur Stimulus) aux autres actions POST+redirect encore existantes (albums, dossiers, fichiers, partages...)
5. ✅ **FAIT — Notification de partage asynchrone** (#270) : `ShareWebController` dispatche un `ShareNotificationMessage` (routé vers le transport `async`) au lieu d'appeler `ShareNotificationMailer::notify()` en synchrone → `/share-create` ne bloque plus sur le SMTP (× nombre d'invités). `ShareNotificationHandler` consomme et envoie. Périmètre volontaire : notification de partage seule — reset-password, invitation invité et batch restent synchrones (worker = cron 5 min, on ne retarde pas le reset-password)
6. **Nettoyer les ~168 notices PHPUnit** — `createMock()` utilisé sans `expects()` déclenche une notice PHPUnit 13 ("Consider refactoring your test code to use a test stub instead") ; concentrées dans `tests/Unit/{Entity,EventListener,Security,Service}`, notamment `ShareLinkFactoryTest.php` (~15) ; remplacer ces `createMock()` par `createStub()` là où aucune expectation n'est réellement vérifiée (le chantier worker média a déjà converti quelques cas) — suivi dans #272
6. ✅ **FAIT — Nettoyer les notices PHPUnit createMock/createStub** (#272) : 190 → 22 notices dans `tests/Unit/{Entity,EventListener,Security,Service}` (190 → 106 sur la suite complète, 924 tests GREEN). Les `createMock()` sans jamais poser d'`->expects()` remplacés par `createStub()`. Notices résiduelles : mocks partagés en `setUp()` dont au moins un test pose un `expects()` réel sur ce même mock dans un AUTRE test — PHPUnit déclenche la notice par (test, mock) dès que le mock existe sans expectation dans CE test précis, même non utilisé ; les éliminer demanderait d'abandonner le `setUp()` partagé au profit d'une instanciation par test (jugé disproportionné, non fait) — `DefaultFolderServiceTest`, `FolderServiceTest`, `FolderMoverTest`, `SharedResourceCleanerTest`
7. ✅ **FAIT — Pack photographe EXIF (RAW + JPEG)** (#268) : `ronanlenouvel/raw-preview-extractor` **1.2.0** expose `ExtractedPreview->metadata` (date, ouverture, vitesse, ISO, focale, objectif, appareil), lu depuis l'EXIF du RAW ; `ExifService` fait de même pour les JPEG. `Media` gagne 5 colonnes (aperture, shutter_speed, iso, focal_length, lens), exposées par l'API, affichées dans un panneau EXIF du lightbox galerie, avec tri « date de prise de vue ». CR3 : métadonnées `null` pour l'instant (parseur ISO-BMFF, piste d'itération)
8. **Optimiser le redimensionnement des grandes previews** — une preview RAW pleine résolution (8256×5504) prend ~2,9 s à redimensionner sous GD, contre 23 ms pour l'extraction elle-même. Acceptable car le pipeline média est asynchrone (Messenger), mais un redimensionnement en deux passes réduirait nettement le coût
9. **Chiffrement au repos** — `APP_ENCRYPTION_KEY` est déclaré dans `config/services.yaml` mais injecté nulle part : les fichiers sont stockés en clair. Prévu comme option laissée à l'utilisateur. Incohérence résiduelle : le docblock `@param` de `ThumbnailService::generate()` (ligne 43) dit encore « chiffrée sur disque » alors que le reste de la classe (ligne 24) indique correctement « stockés en clair » — à corriger
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/Entity/ShareLinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ final class ShareLinkTest extends TestCase
{
private function makeOwner(): User
{
return $this->createMock(User::class);
return $this->createStub(User::class);
}

public function testIsActiveWhenNotExpiredAndNotRevoked(): void
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/Entity/ShareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ final class ShareTest extends TestCase
{
private function makeUser(): User
{
return $this->createMock(User::class);
return $this->createStub(User::class);
}

private function makeShare(?\DateTimeImmutable $expiresAt = null): Share
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ private function buildEvent(string $email = 'test@example.com', string $userAgen
json_encode(['email' => $email, 'password' => 'wrongpassword'])
);

$authenticator = $this->createMock(AuthenticatorInterface::class);
$authenticator = $this->createStub(AuthenticatorInterface::class);
$exception = new BadCredentialsException();

return new LoginFailureEvent($exception, $authenticator, $request, null, 'login');
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/EventListener/SecurityHeadersListenerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ final class SecurityHeadersListenerTest extends TestCase
{
private function buildEvent(string $path = '/api/v1/users', ?string $route = null): ResponseEvent
{
$kernel = $this->createMock(HttpKernelInterface::class);
$kernel = $this->createStub(HttpKernelInterface::class);
$request = Request::create($path);
if ($route !== null) {
$request->attributes->set('_route', $route);
Expand Down
18 changes: 9 additions & 9 deletions tests/Unit/Security/AlbumVoterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ final class AlbumVoterTest extends TestCase
{
private function makeUser(): User
{
$user = $this->createMock(User::class);
$user = $this->createStub(User::class);
$user->method('getId')->willReturn(Uuid::v7());

return $user;
}

private function makeAlbum(User $owner): Album
{
$album = $this->createMock(Album::class);
$album = $this->createStub(Album::class);
$album->method('getId')->willReturn(Uuid::v7());
$album->method('getOwner')->willReturn($owner);

Expand All @@ -34,7 +34,7 @@ private function makeAlbum(User $owner): Album

private function tokenFor(User $user): TokenInterface
{
$token = $this->createMock(TokenInterface::class);
$token = $this->createStub(TokenInterface::class);
$token->method('getUser')->willReturn($user);

return $token;
Expand All @@ -45,7 +45,7 @@ public function testOwnerCanViewAlbum(): void
$owner = $this->makeUser();
$album = $this->makeAlbum($owner);

$resourceAccessChecker = $this->createMock(ResourceAccessCheckerInterface::class);
$resourceAccessChecker = $this->createStub(ResourceAccessCheckerInterface::class);
$resourceAccessChecker->method('canRead')->willReturn(true);

$voter = new AlbumVoter($resourceAccessChecker);
Expand All @@ -62,7 +62,7 @@ public function testGuestWithActiveShareCanViewAlbum(): void
$guest = $this->makeUser();
$album = $this->makeAlbum($owner);

$resourceAccessChecker = $this->createMock(ResourceAccessCheckerInterface::class);
$resourceAccessChecker = $this->createStub(ResourceAccessCheckerInterface::class);
$resourceAccessChecker->method('canRead')->willReturn(true);

$voter = new AlbumVoter($resourceAccessChecker);
Expand All @@ -79,7 +79,7 @@ public function testGuestWithoutShareCannotViewAlbum(): void
$guest = $this->makeUser();
$album = $this->makeAlbum($owner);

$resourceAccessChecker = $this->createMock(ResourceAccessCheckerInterface::class);
$resourceAccessChecker = $this->createStub(ResourceAccessCheckerInterface::class);
$resourceAccessChecker->method('canRead')->willReturn(false);

$voter = new AlbumVoter($resourceAccessChecker);
Expand All @@ -98,7 +98,7 @@ public function testGuestWithExpiredShareCannotViewAlbum(): void
$guest = $this->makeUser();
$album = $this->makeAlbum($owner);

$resourceAccessChecker = $this->createMock(ResourceAccessCheckerInterface::class);
$resourceAccessChecker = $this->createStub(ResourceAccessCheckerInterface::class);
$resourceAccessChecker->method('canRead')->willReturn(false);

$voter = new AlbumVoter($resourceAccessChecker);
Expand All @@ -117,7 +117,7 @@ public function testGuestWithWriteShareCannotDeleteAlbum(): void
$guest = $this->makeUser();
$album = $this->makeAlbum($owner);

$resourceAccessChecker = $this->createMock(ResourceAccessCheckerInterface::class);
$resourceAccessChecker = $this->createStub(ResourceAccessCheckerInterface::class);
$resourceAccessChecker->method('canRead')->willReturn(true);
$resourceAccessChecker->method('canWrite')->willReturn(true);

Expand All @@ -134,7 +134,7 @@ public function testOwnerCanDeleteAlbum(): void
$owner = $this->makeUser();
$album = $this->makeAlbum($owner);

$resourceAccessChecker = $this->createMock(ResourceAccessCheckerInterface::class);
$resourceAccessChecker = $this->createStub(ResourceAccessCheckerInterface::class);

$voter = new AlbumVoter($resourceAccessChecker);

Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/Security/GuestRestrictionCheckerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ final class GuestRestrictionCheckerTest extends TestCase
{
private function makeUser(bool $isGuest): User
{
$user = $this->createMock(User::class);
$user = $this->createStub(User::class);
$user->method('isGuest')->willReturn($isGuest);

return $user;
Expand Down
10 changes: 5 additions & 5 deletions tests/Unit/Security/ResourceAccessCheckerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ final class ResourceAccessCheckerTest extends TestCase
{
private function makeUser(): User
{
$user = $this->createMock(User::class);
$user = $this->createStub(User::class);
$user->method('getId')->willReturn(Uuid::v7());

return $user;
Expand All @@ -39,7 +39,7 @@ public function testGuestWithReadShareCanReadButNotWrite(): void
$guest = $this->makeUser();
$resourceId = Uuid::v7();

$shareAccessChecker = $this->createMock(ShareAccessCheckerInterface::class);
$shareAccessChecker = $this->createStub(ShareAccessCheckerInterface::class);
$shareAccessChecker->method('canAccess')
->willReturnCallback(fn (User $u, string $type, Uuid $id, string $perm) => $perm === Share::PERMISSION_READ);

Expand All @@ -55,7 +55,7 @@ public function testGuestWithWriteShareCanReadAndWrite(): void
$guest = $this->makeUser();
$resourceId = Uuid::v7();

$shareAccessChecker = $this->createMock(ShareAccessCheckerInterface::class);
$shareAccessChecker = $this->createStub(ShareAccessCheckerInterface::class);
$shareAccessChecker->method('canAccess')->willReturn(true);

$checker = new ResourceAccessChecker($shareAccessChecker);
Expand All @@ -70,7 +70,7 @@ public function testGuestWithoutShareCannotReadOrWrite(): void
$guest = $this->makeUser();
$resourceId = Uuid::v7();

$shareAccessChecker = $this->createMock(ShareAccessCheckerInterface::class);
$shareAccessChecker = $this->createStub(ShareAccessCheckerInterface::class);
$shareAccessChecker->method('canAccess')->willReturn(false);

$checker = new ResourceAccessChecker($shareAccessChecker);
Expand All @@ -87,7 +87,7 @@ public function testExpiredShareDeniesReadAndWrite(): void
$guest = $this->makeUser();
$resourceId = Uuid::v7();

$shareAccessChecker = $this->createMock(ShareAccessCheckerInterface::class);
$shareAccessChecker = $this->createStub(ShareAccessCheckerInterface::class);
$shareAccessChecker->method('canAccess')->willReturn(false);

$checker = new ResourceAccessChecker($shareAccessChecker);
Expand Down
12 changes: 6 additions & 6 deletions tests/Unit/Security/ShareLinkAccessCheckerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ final class ShareLinkAccessCheckerTest extends TestCase
{
private function makeShareLink(\DateTimeImmutable $expiresAt, string $plainToken = 'plain-token'): ShareLink
{
$owner = $this->createMock(User::class);
$owner = $this->createStub(User::class);

return new ShareLink(
$owner,
Expand All @@ -30,7 +30,7 @@ private function makeShareLink(\DateTimeImmutable $expiresAt, string $plainToken

public function testUnknownSelectorReturnsNull(): void
{
$repository = $this->createMock(ShareLinkRepositoryInterface::class);
$repository = $this->createStub(ShareLinkRepositoryInterface::class);
$repository->method('findBySelector')->willReturn(null);

$checker = new ShareLinkAccessChecker($repository);
Expand All @@ -41,7 +41,7 @@ public function testUnknownSelectorReturnsNull(): void
public function testInvalidTokenReturnsNull(): void
{
$link = $this->makeShareLink(new \DateTimeImmutable('+7 days'));
$repository = $this->createMock(ShareLinkRepositoryInterface::class);
$repository = $this->createStub(ShareLinkRepositoryInterface::class);
$repository->method('findBySelector')->willReturn($link);

$checker = new ShareLinkAccessChecker($repository);
Expand All @@ -52,7 +52,7 @@ public function testInvalidTokenReturnsNull(): void
public function testExpiredLinkReturnsNull(): void
{
$link = $this->makeShareLink(new \DateTimeImmutable('-1 second'));
$repository = $this->createMock(ShareLinkRepositoryInterface::class);
$repository = $this->createStub(ShareLinkRepositoryInterface::class);
$repository->method('findBySelector')->willReturn($link);

$checker = new ShareLinkAccessChecker($repository);
Expand All @@ -64,7 +64,7 @@ public function testRevokedLinkReturnsNull(): void
{
$link = $this->makeShareLink(new \DateTimeImmutable('+7 days'));
$link->revoke();
$repository = $this->createMock(ShareLinkRepositoryInterface::class);
$repository = $this->createStub(ShareLinkRepositoryInterface::class);
$repository->method('findBySelector')->willReturn($link);

$checker = new ShareLinkAccessChecker($repository);
Expand All @@ -75,7 +75,7 @@ public function testRevokedLinkReturnsNull(): void
public function testValidLinkReturnsTheShareLink(): void
{
$link = $this->makeShareLink(new \DateTimeImmutable('+7 days'));
$repository = $this->createMock(ShareLinkRepositoryInterface::class);
$repository = $this->createStub(ShareLinkRepositoryInterface::class);
$repository->method('findBySelector')->willReturn($link);

$checker = new ShareLinkAccessChecker($repository);
Expand Down
42 changes: 21 additions & 21 deletions tests/Unit/Security/SharedFileScopeCheckerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ final class SharedFileScopeCheckerTest extends TestCase
{
public function testFileLinkAllowsExactlyTheLinkedFile(): void
{
$file = $this->createMock(File::class);
$file = $this->createStub(File::class);
$file->method('getId')->willReturn(Uuid::v7());

$checker = new SharedFileScopeChecker();
Expand All @@ -27,7 +27,7 @@ public function testFileLinkAllowsExactlyTheLinkedFile(): void
public function testFileLinkDeniesADifferentFile(): void
{
$linkedFileId = Uuid::v7();
$otherFile = $this->createMock(File::class);
$otherFile = $this->createStub(File::class);
$otherFile->method('getId')->willReturn(Uuid::v7());

$checker = new SharedFileScopeChecker();
Expand All @@ -37,10 +37,10 @@ public function testFileLinkDeniesADifferentFile(): void

public function testFolderLinkAllowsAFileDirectlyInThatFolder(): void
{
$folder = $this->createMock(Folder::class);
$folder = $this->createStub(Folder::class);
$folder->method('getId')->willReturn(Uuid::v7());

$file = $this->createMock(File::class);
$file = $this->createStub(File::class);
$file->method('getFolder')->willReturn($folder);

$checker = new SharedFileScopeChecker();
Expand All @@ -50,13 +50,13 @@ public function testFolderLinkAllowsAFileDirectlyInThatFolder(): void

public function testFolderLinkDeniesAFileInAnotherFolder(): void
{
$linkedFolder = $this->createMock(Folder::class);
$linkedFolder = $this->createStub(Folder::class);
$linkedFolder->method('getId')->willReturn(Uuid::v7());

$otherFolder = $this->createMock(Folder::class);
$otherFolder = $this->createStub(Folder::class);
$otherFolder->method('getId')->willReturn(Uuid::v7());

$file = $this->createMock(File::class);
$file = $this->createStub(File::class);
$file->method('getFolder')->willReturn($otherFolder);

$checker = new SharedFileScopeChecker();
Expand All @@ -69,14 +69,14 @@ public function testFolderLinkAllowsAFileInASubfolder(): void
// Partager Docs/ doit couvrir Docs/Sous/fichier.txt, pas seulement
// les fichiers directement dans Docs/ — cohérent avec VisibilityChecker
// qui autorise déjà ce fichier par remontée des ancêtres.
$linkedFolder = $this->createMock(Folder::class);
$linkedFolder = $this->createStub(Folder::class);
$linkedFolder->method('getId')->willReturn(Uuid::v7());

$subfolder = $this->createMock(Folder::class);
$subfolder = $this->createStub(Folder::class);
$subfolder->method('getId')->willReturn(Uuid::v7());
$subfolder->method('getParent')->willReturn($linkedFolder);

$file = $this->createMock(File::class);
$file = $this->createStub(File::class);
$file->method('getFolder')->willReturn($subfolder);

$checker = new SharedFileScopeChecker();
Expand All @@ -86,18 +86,18 @@ public function testFolderLinkAllowsAFileInASubfolder(): void

public function testFolderLinkDeniesAFileInAnUnrelatedFolderHierarchy(): void
{
$linkedFolder = $this->createMock(Folder::class);
$linkedFolder = $this->createStub(Folder::class);
$linkedFolder->method('getId')->willReturn(Uuid::v7());

$unrelatedGrandParent = $this->createMock(Folder::class);
$unrelatedGrandParent = $this->createStub(Folder::class);
$unrelatedGrandParent->method('getId')->willReturn(Uuid::v7());
$unrelatedGrandParent->method('getParent')->willReturn(null);

$unrelatedFolder = $this->createMock(Folder::class);
$unrelatedFolder = $this->createStub(Folder::class);
$unrelatedFolder->method('getId')->willReturn(Uuid::v7());
$unrelatedFolder->method('getParent')->willReturn($unrelatedGrandParent);

$file = $this->createMock(File::class);
$file = $this->createStub(File::class);
$file->method('getFolder')->willReturn($unrelatedFolder);

$checker = new SharedFileScopeChecker();
Expand All @@ -107,13 +107,13 @@ public function testFolderLinkDeniesAFileInAnUnrelatedFolderHierarchy(): void

public function testAlbumLinkAllowsAFileThatIsAMediaOfThatAlbum(): void
{
$file = $this->createMock(File::class);
$file = $this->createStub(File::class);
$file->method('getId')->willReturn(Uuid::v7());

$media = $this->createMock(\App\Entity\Media::class);
$media = $this->createStub(\App\Entity\Media::class);
$media->method('getFile')->willReturn($file);

$album = $this->createMock(Album::class);
$album = $this->createStub(Album::class);
$album->method('getMedias')->willReturn(new \Doctrine\Common\Collections\ArrayCollection([$media]));

$checker = new SharedFileScopeChecker();
Expand All @@ -123,15 +123,15 @@ public function testAlbumLinkAllowsAFileThatIsAMediaOfThatAlbum(): void

public function testAlbumLinkDeniesAFileNotInAlbum(): void
{
$file = $this->createMock(File::class);
$file = $this->createStub(File::class);
$file->method('getId')->willReturn(Uuid::v7());

$otherFile = $this->createMock(File::class);
$otherFile = $this->createStub(File::class);
$otherFile->method('getId')->willReturn(Uuid::v7());
$media = $this->createMock(\App\Entity\Media::class);
$media = $this->createStub(\App\Entity\Media::class);
$media->method('getFile')->willReturn($otherFile);

$album = $this->createMock(Album::class);
$album = $this->createStub(Album::class);
$album->method('getMedias')->willReturn(new \Doctrine\Common\Collections\ArrayCollection([$media]));

$checker = new SharedFileScopeChecker();
Expand Down
Loading