Some checks are pending
CI / test (push) Waiting to run
Consistent brace style, spacing, and method expansion throughout domain, infrastructure, and test files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
123 lines
4.1 KiB
PHP
123 lines
4.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Infrastructure\AI;
|
|
|
|
use App\Infrastructure\AI\MistralClient;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Symfony\Component\HttpClient\MockHttpClient;
|
|
use Symfony\Component\HttpClient\Response\MockResponse;
|
|
|
|
final class MistralClientTest extends TestCase
|
|
{
|
|
private function makeClient(MockResponse $response): MistralClient
|
|
{
|
|
return new MistralClient(
|
|
new MockHttpClient($response),
|
|
'test-api-key',
|
|
'https://api.mistral.ai',
|
|
);
|
|
}
|
|
|
|
public function testGenerateReturnsParsedContent(): void
|
|
{
|
|
$body = json_encode([
|
|
'choices' => [['message' => ['content' => 'Dell Latitude 5520']]],
|
|
]);
|
|
|
|
$client = $this->makeClient(new MockResponse((string) $body));
|
|
|
|
self::assertSame('Dell Latitude 5520', $client->generate('mistral-large-latest', 'What is this?'));
|
|
}
|
|
|
|
public function testGenerateSendsAuthHeader(): void
|
|
{
|
|
$body = json_encode([
|
|
'choices' => [['message' => ['content' => 'ok']]],
|
|
]);
|
|
|
|
$response = new MockResponse((string) $body);
|
|
$client = $this->makeClient($response);
|
|
$client->generate('mistral-large-latest', 'test');
|
|
|
|
self::assertStringContainsString(
|
|
'Bearer test-api-key',
|
|
implode(', ', $response->getRequestOptions()['headers'] ?? []),
|
|
);
|
|
}
|
|
|
|
public function testGenerateWithImageReturnsParsedContent(): void
|
|
{
|
|
$tmpFile = tempnam(sys_get_temp_dir(), 'test_').'.jpg';
|
|
file_put_contents($tmpFile, 'fake-image-data');
|
|
|
|
$body = json_encode([
|
|
'choices' => [['message' => ['content' => 'MODEL: ThinkPad X1']]],
|
|
]);
|
|
|
|
$client = $this->makeClient(new MockResponse((string) $body));
|
|
$result = $client->generateWithImage('pixtral-12b-2409', 'Read the nameplate', $tmpFile);
|
|
|
|
unlink($tmpFile);
|
|
|
|
self::assertSame('MODEL: ThinkPad X1', $result);
|
|
}
|
|
|
|
public function testGenerateWithImageEncodesImageAsBase64(): void
|
|
{
|
|
$tmpFile = tempnam(sys_get_temp_dir(), 'test_').'.png';
|
|
$imageContent = 'fake-png-bytes';
|
|
file_put_contents($tmpFile, $imageContent);
|
|
|
|
$body = json_encode([
|
|
'choices' => [['message' => ['content' => 'ok']]],
|
|
]);
|
|
|
|
$response = new MockResponse((string) $body);
|
|
$client = $this->makeClient($response);
|
|
$client->generateWithImage('pixtral-12b-2409', 'describe', $tmpFile);
|
|
|
|
unlink($tmpFile);
|
|
|
|
$requestBody = json_decode($response->getRequestOptions()['body'], true);
|
|
$imageUrl = $requestBody['messages'][0]['content'][1]['image_url']['url'];
|
|
|
|
self::assertStringStartsWith('data:image/png;base64,', $imageUrl);
|
|
self::assertStringContainsString(base64_encode($imageContent), $imageUrl);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider mimeTypeProvider
|
|
*/
|
|
public function testGuessMimeTypeFromExtension(string $filename, string $expectedMime): void
|
|
{
|
|
$tmpFile = tempnam(sys_get_temp_dir(), 'test_').'.'.pathinfo($filename, \PATHINFO_EXTENSION);
|
|
file_put_contents($tmpFile, 'x');
|
|
|
|
$body = json_encode(['choices' => [['message' => ['content' => 'ok']]]]);
|
|
$response = new MockResponse((string) $body);
|
|
$client = $this->makeClient($response);
|
|
$client->generateWithImage('pixtral-12b-2409', 'x', $tmpFile);
|
|
|
|
unlink($tmpFile);
|
|
|
|
$requestBody = json_decode($response->getRequestOptions()['body'], true);
|
|
$imageUrl = $requestBody['messages'][0]['content'][1]['image_url']['url'];
|
|
|
|
self::assertStringStartsWith('data:'.$expectedMime.';base64,', $imageUrl);
|
|
}
|
|
|
|
/** @return array<string, array{string, string}> */
|
|
public static function mimeTypeProvider(): array
|
|
{
|
|
return [
|
|
'jpeg' => ['photo.jpg', 'image/jpeg'],
|
|
'jpeg uppercase' => ['photo.JPG', 'image/jpeg'],
|
|
'jpeg ext' => ['photo.jpeg', 'image/jpeg'],
|
|
'png' => ['photo.png', 'image/png'],
|
|
'webp' => ['photo.webp', 'image/webp'],
|
|
'unknown falls back to jpeg' => ['photo.bmp', 'image/jpeg'],
|
|
];
|
|
}
|
|
}
|