Files
SkinbaseNova/tests/Feature/Enhance/EnhanceExternalWorkerProcessorTest.php

151 lines
5.7 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\EnhanceJob;
use App\Models\User;
use App\Services\Enhance\Processors\ExternalWorkerEnhanceProcessor;
use Illuminate\Http\Client\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
beforeEach(function (): void {
config()->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();
});