set('vision.vector_gateway.enabled', true); config()->set('vision.vector_gateway.base_url', 'https://vision.klevze.net'); config()->set('vision.vector_gateway.api_key', 'test-key'); config()->set('vision.vector_gateway.search_endpoint', '/vectors/search'); config()->set('vision.vector_gateway.search_file_endpoint', '/vectors/search/file'); config()->set('cdn.files_url', 'https://files.skinbase.org'); Storage::fake('public'); Cache::flush(); }); function limitSourceArtwork(): Artwork { return Artwork::factory()->create([ 'title' => 'Limit source', 'hash' => 'aabbccddeeff', 'thumb_ext' => 'webp', 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subHour(), ]); } function fakeFileSearchOk(): void { Http::fake(function ($request) { $url = $request->url(); if (str_contains($url, '/vectors/search')) { return Http::response(['results' => []], 200); } return Http::response('image-bytes', 200, ['Content-Type' => 'image/webp']); }); } function gatewaySearchLimit($request): ?int { $url = $request->url(); if (! str_contains($url, '/vectors/search')) { return null; } $data = $request->data(); if (isset($data['limit']) && is_numeric($data['limit'])) { return (int) $data['limit']; } foreach ($data as $part) { if (is_array($part) && ($part['name'] ?? null) === 'limit' && is_numeric($part['contents'] ?? null)) { return (int) $part['contents']; } } $body = $request->body(); if (preg_match('/name="limit".*?\r\n\r\n(\d+)/s', $body, $matches) === 1) { return (int) $matches[1]; } if (preg_match('/"limit"\s*:\s*([0-9]+)/', $body, $matches) === 1) { return (int) $matches[1]; } return null; } it('sends gateway limit 100 when similar-ai is requested at 99', function (): void { $source = limitSourceArtwork(); fakeFileSearchOk(); app(AiArtworkVectorSearchService::class)->similarToArtwork($source, 99); Http::assertSent(fn ($request): bool => gatewaySearchLimit($request) === 100); }); it('clamps similar-ai requests of 100 and 120 so the gateway never sees more than 100', function (): void { $source = limitSourceArtwork(); foreach ([100, 120, 121] as $requested) { fakeFileSearchOk(); Cache::flush(); app(AiArtworkVectorSearchService::class)->similarToArtwork($source, $requested); Http::assertSent(fn ($request): bool => gatewaySearchLimit($request) === 100); } }); it('sends gateway limit 13 for the default similar-ai request of 12', function (): void { $source = limitSourceArtwork(); fakeFileSearchOk(); app(AiArtworkVectorSearchService::class)->similarToArtwork($source, 12); Http::assertSent(fn ($request): bool => gatewaySearchLimit($request) === 13); }); it('clamps uploaded-image search to 100 without adding an extra candidate', function (): void { fakeFileSearchOk(); app(AiArtworkVectorSearchService::class)->searchByUploadedImage( UploadedFile::fake()->image('query.png', 32, 32), 120, ); Http::assertSent(fn ($request): bool => gatewaySearchLimit($request) === 100); });