$id, 'user_id' => $userId, 'type' => 'archive', 'status' => 'draft', 'processing_state' => 'pending_scan', 'is_scanned' => false, 'has_tags' => false, 'created_at' => now(), 'updated_at' => now(), ]; DB::table('uploads')->insert(array_merge($defaults, $overrides)); return $id; } it('moves through explicit processing state lifecycle', function () { Storage::fake('local'); Bus::fake(); $user = User::factory()->create(); $uploadId = createUploadForLifecycle($user->id); $mainPath = "tmp/drafts/{$uploadId}/main/archive.zip"; Storage::disk('local')->put($mainPath, 'archive-bytes'); DB::table('upload_files')->insert([ 'upload_id' => $uploadId, 'path' => $mainPath, 'type' => 'main', 'hash' => 'aa11bb22cc33dd44', 'size' => 100, 'mime' => 'application/zip', 'created_at' => now(), ]); (new VirusScanJob($uploadId))->handle(app(UploadScanService::class)); expect(DB::table('uploads')->where('id', $uploadId)->value('processing_state'))->toBe('generating_preview'); (new PreviewGenerationJob($uploadId))->handle(app(PreviewService::class)); expect(DB::table('uploads')->where('id', $uploadId)->value('processing_state'))->toBe('analyzing_tags'); (new TagAnalysisJob($uploadId))->handle(app(TagAnalysisService::class)); expect(DB::table('uploads')->where('id', $uploadId)->value('processing_state'))->toBe('ready'); expect((bool) DB::table('uploads')->where('id', $uploadId)->value('has_tags'))->toBeTrue(); }); it('does not regress processing state when jobs rerun', function () { Storage::fake('local'); Bus::fake(); $user = User::factory()->create(); $uploadId = (string) Str::uuid(); DB::table('uploads')->insert([ 'id' => $uploadId, 'user_id' => $user->id, 'type' => 'archive', 'status' => 'draft', 'processing_state' => 'ready', 'is_scanned' => true, 'has_tags' => true, 'preview_path' => "tmp/drafts/{$uploadId}/preview.webp", 'created_at' => now(), 'updated_at' => now(), ]); $mainPath = "tmp/drafts/{$uploadId}/main/archive.zip"; Storage::disk('local')->put($mainPath, 'archive-bytes'); DB::table('upload_files')->insert([ 'upload_id' => $uploadId, 'path' => $mainPath, 'type' => 'main', 'hash' => 'ee11bb22cc33dd44', 'size' => 100, 'mime' => 'application/zip', 'created_at' => now(), ]); (new VirusScanJob($uploadId))->handle(app(UploadScanService::class)); (new PreviewGenerationJob($uploadId))->handle(app(PreviewService::class)); (new TagAnalysisJob($uploadId))->handle(app(TagAnalysisService::class)); expect(DB::table('uploads')->where('id', $uploadId)->value('processing_state'))->toBe('ready'); });