diff --git a/app/Actions/Photo/Pipes/Shared/Save.php b/app/Actions/Photo/Pipes/Shared/Save.php index 60ad890b486..0a54d9be587 100644 --- a/app/Actions/Photo/Pipes/Shared/Save.php +++ b/app/Actions/Photo/Pipes/Shared/Save.php @@ -10,7 +10,6 @@ use App\Contracts\PhotoCreate\PhotoDTO; use App\Contracts\PhotoCreate\PhotoPipe; -use App\Events\PhotoSaved; /** * Persist current Photo object into database. @@ -22,9 +21,6 @@ public function handle(PhotoDTO $state, \Closure $next): PhotoDTO $state->getPhoto()->save(); $state->getPhoto()->tags()->sync($state->getTags()->pluck('id')->all()); - // Dispatch event for album stats recomputation - PhotoSaved::dispatch($state->getPhoto()->id); - return $next($state); } } \ No newline at end of file diff --git a/app/Actions/Photo/Pipes/Shared/SetParent.php b/app/Actions/Photo/Pipes/Shared/SetParent.php index 8517b5ed535..f13bd63859c 100644 --- a/app/Actions/Photo/Pipes/Shared/SetParent.php +++ b/app/Actions/Photo/Pipes/Shared/SetParent.php @@ -12,6 +12,7 @@ use App\Contracts\PhotoCreate\SharedPipe; use App\DTO\PhotoCreate\DuplicateDTO; use App\DTO\PhotoCreate\StandaloneDTO; +use App\Events\PhotoSaved; use App\Exceptions\Internal\LycheeLogicException; use App\Models\Album; use Illuminate\Support\Facades\DB; @@ -44,6 +45,10 @@ public function handle(DuplicateDTO|StandaloneDTO $state, \Closure $next): Dupli // Avoid unnecessary DB request, when we access the album of a // photo later (e.g. when a notification is sent). $state->photo->load('albums'); + + // Dispatch event for album stats recomputation + // This must be done after SetParent so the photo_album relationship exists + PhotoSaved::dispatch($state->photo->id); } return $next($state); diff --git a/tests/ImageProcessing/Photo/PhotoAddTest.php b/tests/ImageProcessing/Photo/PhotoAddTest.php index af24d17465f..d25b505a24e 100644 --- a/tests/ImageProcessing/Photo/PhotoAddTest.php +++ b/tests/ImageProcessing/Photo/PhotoAddTest.php @@ -18,6 +18,9 @@ namespace Tests\ImageProcessing\Photo; +use App\Jobs\RecomputeAlbumSizeJob; +use App\Jobs\RecomputeAlbumStatsJob; +use Illuminate\Support\Facades\Queue; use Tests\Constants\TestConstants; use Tests\Feature_v2\Base\BaseApiWithDataTest; @@ -159,4 +162,32 @@ public function testGoogleMotionPicture(): void $response = $this->deleteJson('Photo', ['photo_ids' => [$id], 'from_id' => 'unsorted']); $this->assertNoContent($response); } + + public function testPhotoUploadDispatchesJobs(): void + { + // Fake only the recompute jobs, not the ProcessImageJob + // This allows the photo upload to complete while capturing the recompute job dispatches + Queue::fake([ + RecomputeAlbumStatsJob::class, + RecomputeAlbumSizeJob::class, + ]); + + $this->catchFailureSilence = []; + + // Upload a photo to an album + $response = $this->actingAs($this->admin)->upload('Photo', filename: TestConstants::SAMPLE_FILE_NIGHT_IMAGE, album_id: $this->album1->id); + $this->assertCreated($response); + + // Assert that RecomputeAlbumStatsJob was dispatched for the album + Queue::assertPushed(RecomputeAlbumStatsJob::class, function (RecomputeAlbumStatsJob $job) { + return $job->album_id === $this->album1->id; + }); + + // Assert that RecomputeAlbumSizeJob was dispatched for the album + Queue::assertPushed(RecomputeAlbumSizeJob::class, function (RecomputeAlbumSizeJob $job) { + return $job->album_id === $this->album1->id; + }); + + $this->catchFailureSilence = ["App\Exceptions\MediaFileOperationException"]; + } }