insertGetId([ 'name' => 'Photography', 'slug' => 'photography-' . Str::lower(Str::random(6)), 'description' => null, 'created_at' => now(), 'updated_at' => now(), ]); return DB::table('categories')->insertGetId([ 'content_type_id' => $contentTypeId, 'parent_id' => null, 'name' => 'Urban', 'slug' => 'urban-' . Str::lower(Str::random(6)), 'description' => null, 'image' => null, 'is_active' => true, 'sort_order' => 0, 'created_at' => now(), 'updated_at' => now(), ]); } it('publishes upload and moves draft files', function () { Storage::fake('local'); $user = User::factory()->create(); $categoryId = createCategoryForUploadPublishFeatureTests(); $uploadId = (string) Str::uuid(); $hash = 'aabbccddeeff00112233'; DB::table('uploads')->insert([ 'id' => $uploadId, 'user_id' => $user->id, 'type' => 'image', 'status' => 'draft', 'moderation_status' => 'approved', 'title' => 'Night City', 'category_id' => $categoryId, 'is_scanned' => true, 'has_tags' => true, 'preview_path' => "tmp/drafts/{$uploadId}/preview.webp", 'created_at' => now(), 'updated_at' => now(), ]); Storage::disk('local')->put("tmp/drafts/{$uploadId}/main/night-city.jpg", 'jpg-binary'); Storage::disk('local')->put("tmp/drafts/{$uploadId}/preview.webp", 'preview'); Storage::disk('local')->put("tmp/drafts/{$uploadId}/thumb.webp", 'thumb'); DB::table('upload_files')->insert([ [ 'upload_id' => $uploadId, 'path' => "tmp/drafts/{$uploadId}/main/night-city.jpg", 'type' => 'main', 'hash' => $hash, 'size' => 10, 'mime' => 'image/jpeg', 'created_at' => now(), ], [ 'upload_id' => $uploadId, 'path' => "tmp/drafts/{$uploadId}/preview.webp", 'type' => 'preview', 'hash' => null, 'size' => 7, 'mime' => 'image/webp', 'created_at' => now(), ], ]); $published = app(PublishService::class)->publish($uploadId, $user); expect($published)->toBeInstanceOf(Upload::class); expect($published->status)->toBe('published'); expect($published->published_at)->not->toBeNull(); expect($published->final_path)->toBe('files/artworks/aa/bb/' . $hash); Storage::disk('local')->assertMissing("tmp/drafts/{$uploadId}/main/night-city.jpg"); Storage::disk('local')->assertExists('files/artworks/aa/bb/' . $hash . '/main/night-city.jpg'); $updatedMain = DB::table('upload_files') ->where('upload_id', $uploadId) ->where('type', 'main') ->value('path'); expect($updatedMain)->toBe('files/artworks/aa/bb/' . $hash . '/main/night-city.jpg'); }); it('does not delete temp files on publish failure', function () { Storage::fake('local'); $user = User::factory()->create(); $categoryId = createCategoryForUploadPublishFeatureTests(); $uploadId = (string) Str::uuid(); DB::table('uploads')->insert([ 'id' => $uploadId, 'user_id' => $user->id, 'type' => 'image', 'status' => 'draft', 'moderation_status' => 'approved', 'title' => 'Will Fail', 'category_id' => $categoryId, 'is_scanned' => true, 'has_tags' => true, 'preview_path' => "tmp/drafts/{$uploadId}/preview.webp", 'created_at' => now(), 'updated_at' => now(), ]); Storage::disk('local')->put("tmp/drafts/{$uploadId}/main/file.jpg", 'jpg-binary'); Storage::disk('local')->put("tmp/drafts/{$uploadId}/preview.webp", 'preview'); // missing hash should trigger failure and preserve temp files DB::table('upload_files')->insert([ 'upload_id' => $uploadId, 'path' => "tmp/drafts/{$uploadId}/main/file.jpg", 'type' => 'main', 'hash' => null, 'size' => 10, 'mime' => 'image/jpeg', 'created_at' => now(), ]); expect(fn () => app(PublishService::class)->publish($uploadId, $user)) ->toThrow(RuntimeException::class); Storage::disk('local')->assertExists("tmp/drafts/{$uploadId}/main/file.jpg"); $status = DB::table('uploads')->where('id', $uploadId)->value('status'); expect($status)->toBe('draft'); }); it('publish persists generated slug when missing', function () { Storage::fake('local'); $user = User::factory()->create(); $categoryId = createCategoryForUploadPublishFeatureTests(); $uploadId = (string) Str::uuid(); $hash = '0011aabbccddeeff2233'; DB::table('uploads')->insert([ 'id' => $uploadId, 'user_id' => $user->id, 'type' => 'image', 'status' => 'draft', 'moderation_status' => 'approved', 'processing_state' => 'ready', 'title' => 'My Amazing Artwork', 'slug' => null, 'category_id' => $categoryId, 'is_scanned' => true, 'has_tags' => true, 'preview_path' => "tmp/drafts/{$uploadId}/preview.webp", 'created_at' => now(), 'updated_at' => now(), ]); Storage::disk('local')->put("tmp/drafts/{$uploadId}/main/file.jpg", 'jpg-binary'); Storage::disk('local')->put("tmp/drafts/{$uploadId}/preview.webp", 'preview'); DB::table('upload_files')->insert([ 'upload_id' => $uploadId, 'path' => "tmp/drafts/{$uploadId}/main/file.jpg", 'type' => 'main', 'hash' => $hash, 'size' => 10, 'mime' => 'image/jpeg', 'created_at' => now(), ]); app(PublishService::class)->publish($uploadId, $user); expect(DB::table('uploads')->where('id', $uploadId)->value('slug'))->toBe('my-amazing-artwork'); }); it('publish slug uniqueness appends numeric suffix for published uploads', function () { Storage::fake('local'); $user = User::factory()->create(); $categoryId = createCategoryForUploadPublishFeatureTests(); $firstUploadId = (string) Str::uuid(); $secondUploadId = (string) Str::uuid(); DB::table('uploads')->insert([ [ 'id' => $firstUploadId, 'user_id' => $user->id, 'type' => 'image', 'status' => 'draft', 'moderation_status' => 'approved', 'processing_state' => 'ready', 'title' => 'Duplicate Title', 'slug' => null, 'category_id' => $categoryId, 'is_scanned' => true, 'has_tags' => true, 'preview_path' => "tmp/drafts/{$firstUploadId}/preview.webp", 'created_at' => now(), 'updated_at' => now(), ], [ 'id' => $secondUploadId, 'user_id' => $user->id, 'type' => 'image', 'status' => 'draft', 'moderation_status' => 'approved', 'processing_state' => 'ready', 'title' => 'Duplicate Title', 'slug' => null, 'category_id' => $categoryId, 'is_scanned' => true, 'has_tags' => true, 'preview_path' => "tmp/drafts/{$secondUploadId}/preview.webp", 'created_at' => now(), 'updated_at' => now(), ], ]); Storage::disk('local')->put("tmp/drafts/{$firstUploadId}/main/file.jpg", 'first'); Storage::disk('local')->put("tmp/drafts/{$firstUploadId}/preview.webp", 'preview'); Storage::disk('local')->put("tmp/drafts/{$secondUploadId}/main/file.jpg", 'second'); Storage::disk('local')->put("tmp/drafts/{$secondUploadId}/preview.webp", 'preview'); DB::table('upload_files')->insert([ [ 'upload_id' => $firstUploadId, 'path' => "tmp/drafts/{$firstUploadId}/main/file.jpg", 'type' => 'main', 'hash' => 'aa11bb22cc33dd44', 'size' => 10, 'mime' => 'image/jpeg', 'created_at' => now(), ], [ 'upload_id' => $secondUploadId, 'path' => "tmp/drafts/{$secondUploadId}/main/file.jpg", 'type' => 'main', 'hash' => 'ee11ff22cc33dd44', 'size' => 10, 'mime' => 'image/jpeg', 'created_at' => now(), ], ]); app(PublishService::class)->publish($firstUploadId, $user); app(PublishService::class)->publish($secondUploadId, $user); expect(DB::table('uploads')->where('id', $firstUploadId)->value('slug'))->toBe('duplicate-title'); expect(DB::table('uploads')->where('id', $secondUploadId)->value('slug'))->toBe('duplicate-title-2'); });