open($path, \ZipArchive::OVERWRITE | \ZipArchive::CREATE) !== true) { throw new \RuntimeException('Unable to create temporary zip file.'); } $zip->addFromString('skins/theme/readme.txt', 'safe'); $zip->addFromString('skins/theme/colors.ini', 'accent=blue'); $zip->close(); return new UploadedFile($path, 'pack.zip', 'application/zip', null, true); } it('authenticated user can preload image', function () { Storage::fake('local'); $user = User::factory()->create(); $main = UploadedFile::fake()->image('main.jpg', 1200, 800); $response = $this ->actingAs($user) ->postJson('/api/uploads/preload', [ 'main' => $main, ]); $response->assertOk()->assertJsonStructure([ 'upload_id', 'status', 'expires_at', ]); $uploadId = $response->json('upload_id'); $this->assertDatabaseHas('uploads', [ 'id' => $uploadId, 'user_id' => $user->id, 'type' => 'image', 'status' => 'draft', ]); $this->assertDatabaseHas('upload_files', [ 'upload_id' => $uploadId, 'type' => 'main', ]); Storage::disk('local')->assertExists("tmp/drafts/{$uploadId}/meta.json"); expect(Storage::disk('local')->allFiles("tmp/drafts/{$uploadId}"))->not->toBeEmpty(); }); it('authenticated user can preload archive with screenshot', function () { Storage::fake('local'); $user = User::factory()->create(); $main = makeValidArchiveUpload(); $screenshot = UploadedFile::fake()->image('screen1.jpg', 800, 600); $response = $this ->actingAs($user) ->postJson('/api/uploads/preload', [ 'main' => $main, 'screenshots' => [$screenshot], ]); $response->assertOk()->assertJsonStructure([ 'upload_id', 'status', 'expires_at', ]); $uploadId = $response->json('upload_id'); $this->assertDatabaseHas('uploads', [ 'id' => $uploadId, 'user_id' => $user->id, 'type' => 'archive', 'status' => 'draft', ]); $this->assertDatabaseHas('upload_files', [ 'upload_id' => $uploadId, 'type' => 'main', ]); $this->assertDatabaseHas('upload_files', [ 'upload_id' => $uploadId, 'type' => 'screenshot', ]); Storage::disk('local')->assertExists("tmp/drafts/{$uploadId}/meta.json"); expect(Storage::disk('local')->allFiles("tmp/drafts/{$uploadId}"))->not->toBeEmpty(); }); it('guest is rejected', function () { Storage::fake('local'); $main = UploadedFile::fake()->image('main.jpg', 1200, 800); $response = $this->postJson('/api/uploads/preload', [ 'main' => $main, ]); expect(in_array($response->getStatusCode(), [401, 403]))->toBeTrue(); }); it('missing main file fails', function () { Storage::fake('local'); $user = User::factory()->create(); $response = $this ->actingAs($user) ->postJson('/api/uploads/preload', []); $response->assertStatus(422)->assertJsonValidationErrors(['main']); }); it('archive without screenshot fails', function () { Storage::fake('local'); $user = User::factory()->create(); $main = UploadedFile::fake()->create('pack.zip', 1024, 'application/zip'); $response = $this ->actingAs($user) ->postJson('/api/uploads/preload', [ 'main' => $main, ]); $response->assertStatus(422)->assertJsonValidationErrors(['screenshots']); }); it('invalid file type is rejected', function () { Storage::fake('local'); $user = User::factory()->create(); $main = UploadedFile::fake()->create('notes.txt', 4, 'text/plain'); $response = $this ->actingAs($user) ->postJson('/api/uploads/preload', [ 'main' => $main, ]); $response->assertStatus(422)->assertJsonValidationErrors(['main']); }); it('preload dispatches VirusScanJob', function () { Storage::fake('local'); Bus::fake(); $user = User::factory()->create(); $main = UploadedFile::fake()->image('main.jpg', 1200, 800); $response = $this ->actingAs($user) ->postJson('/api/uploads/preload', [ 'main' => $main, ]); $response->assertOk()->assertJsonStructure([ 'upload_id', 'status', 'expires_at', ]); $uploadId = $response->json('upload_id'); Bus::assertDispatched(VirusScanJob::class, function (VirusScanJob $job) use ($uploadId) { $reflect = new \ReflectionClass($job); $property = $reflect->getProperty('uploadId'); $property->setAccessible(true); return $property->getValue($job) === $uploadId; }); });