set('app.url', 'http://skinbase.test'); config()->set('enhance.disk', 'public'); config()->set('enhance.external_worker.url', 'http://127.0.0.1:8095'); config()->set('enhance.external_worker.token', 'worker-secret'); config()->set('enhance.external_worker.timeout', 15); config()->set('enhance.external_worker.max_download_mb', 2); Storage::fake('public'); }); function makeEnhanceJob(): EnhanceJob { $user = User::factory()->create(); Storage::disk('public')->put('enhance/sources/10/source.png', UploadedFile::fake()->image('source.png', 40, 40)->get()); return EnhanceJob::query()->create([ 'user_id' => $user->id, 'status' => EnhanceJob::STATUS_QUEUED, 'engine' => EnhanceJob::ENGINE_EXTERNAL_WORKER, 'mode' => 'artwork', 'scale' => 2, 'source_disk' => 'public', 'source_path' => 'enhance/sources/10/source.png', 'input_mime' => 'image/png', ]); } it('requires a configured worker url', function (): void { config()->set('enhance.external_worker.url', ''); app(ExternalWorkerEnhanceProcessor::class)->process(makeEnhanceJob()); })->throws(RuntimeException::class, 'Worker URL is missing.'); it('requires a configured worker token', function (): void { config()->set('enhance.external_worker.token', ''); app(ExternalWorkerEnhanceProcessor::class)->process(makeEnhanceJob()); })->throws(RuntimeException::class, 'Worker token is missing.'); it('sends bearer token and stores successful worker output', function (): void { $job = makeEnhanceJob(); $outputBinary = UploadedFile::fake()->image('output.png', 80, 80)->get(); Http::fake([ 'http://127.0.0.1:8095/v1/upscale' => Http::response([ 'success' => true, 'job_id' => $job->id, 'output_url' => 'http://127.0.0.1:8095/v1/results/result-output.png', 'width' => 80, 'height' => 80, 'filesize' => strlen($outputBinary), 'mime' => 'image/png', 'metadata' => ['engine' => 'pillow'], ], 200), 'http://127.0.0.1:8095/v1/results/result-output.png' => Http::response($outputBinary, 200, ['Content-Type' => 'image/png']), 'http://127.0.0.1:8095/v1/results/result-output.png*' => Http::response(['success' => true, 'deleted' => true], 200), ]); $result = app(ExternalWorkerEnhanceProcessor::class)->process($job); expect($result->width)->toBe(80); expect($result->height)->toBe(80); expect($result->mime)->toBe('image/png'); expect($result->metadata['engine'])->toBe('pillow'); Storage::disk('public')->assertExists($result->path); Http::assertSent(function (Request $request): bool { if ($request->url() !== 'http://127.0.0.1:8095/v1/upscale') { return false; } $data = $request->data(); $sourceUrl = (string) ($data['source_url'] ?? ''); return $request->hasHeader('Authorization') && ($data['mode'] ?? null) === 'artwork' && (int) ($data['scale'] ?? 0) === 2 && (int) ($data['job_id'] ?? 0) > 0 && ($sourceUrl !== '') && (str_contains($sourceUrl, '/internal/enhance/source/') || str_contains($sourceUrl, '/enhance/sources/')); }); }); it('handles a failed worker response', function (): void { $job = makeEnhanceJob(); Http::fake([ 'http://127.0.0.1:8095/v1/upscale' => Http::response(['success' => false, 'error' => 'Worker rejected the image.'], 422), ]); app(ExternalWorkerEnhanceProcessor::class)->process($job); })->throws(RuntimeException::class, 'Worker rejected the image.'); it('handles an invalid json worker response', function (): void { $job = makeEnhanceJob(); Http::fake([ 'http://127.0.0.1:8095/v1/upscale' => Http::response('not-json', 200, ['Content-Type' => 'text/plain']), ]); app(ExternalWorkerEnhanceProcessor::class)->process($job); })->throws(RuntimeException::class, 'Worker returned an invalid response.'); it('rejects oversized downloaded output', function (): void { $job = makeEnhanceJob(); config()->set('enhance.external_worker.max_download_mb', 1); Http::fake([ 'http://127.0.0.1:8095/v1/upscale' => Http::response([ 'success' => true, 'job_id' => $job->id, 'output_url' => 'http://127.0.0.1:8095/v1/results/too-large.webp', 'mime' => 'image/webp', ], 200), 'http://127.0.0.1:8095/v1/results/too-large.webp' => Http::response(str_repeat('a', (1024 * 1024) + 1), 200, ['Content-Type' => 'image/webp']), ]); app(ExternalWorkerEnhanceProcessor::class)->process($job); })->throws(RuntimeException::class, 'The upscaled output exceeded the maximum allowed size.'); it('accepts base64 worker output', function (): void { $job = makeEnhanceJob(); $outputBinary = UploadedFile::fake()->image('output.png', 90, 60)->get(); Http::fake([ 'http://127.0.0.1:8095/v1/upscale' => Http::response([ 'success' => true, 'job_id' => $job->id, 'output_base64' => base64_encode($outputBinary), 'mime' => 'image/png', 'metadata' => ['engine' => 'pillow', 'real_ai_upscale' => false], ], 200), ]); $result = app(ExternalWorkerEnhanceProcessor::class)->process($job); expect($result->width)->toBe(90); expect($result->height)->toBe(60); expect($result->metadata['real_ai_upscale'])->toBeFalse(); });