Skip to content
Snippets Groups Projects
Verified Commit 10c41a5a authored by Sofiane Lasri's avatar Sofiane Lasri
Browse files

feat: add unit tests for AiProviderService and improve error handling

- Implemented unit tests for prompt sending methods in AiProviderService.
- Added handling for transcoding failures and API call errors in AiProviderService.
- Utilized Laravel's HTTP client and improved logging for better debugging.
parent 2f044f84
Branches
No related tags found
1 merge request!59Ajouter service ia & détection bots user agents
Pipeline #1024 failed
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
namespace App\Services; namespace App\Services;
use App\Models\UploadedPicture; use App\Models\UploadedPicture;
use GuzzleHttp\Client; use Illuminate\Http\Client\ConnectionException;
use GuzzleHttp\Exception\GuzzleException; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use RuntimeException; use RuntimeException;
...@@ -25,12 +25,21 @@ public function promptWithPictures(string $systemRole, string $prompt, UploadedP ...@@ -25,12 +25,21 @@ public function promptWithPictures(string $systemRole, string $prompt, UploadedP
$transcodedPictures = []; $transcodedPictures = [];
foreach ($pictures as $picture) { foreach ($pictures as $picture) {
$picturePath = Storage::disk('public')->get($picture->path_original); $picturePath = Storage::disk('public')->get($picture->path_original);
$transcodedPictures[] = $transcodingService->transcode($picturePath, UploadedPicture::MEDIUM_SIZE, 'jpeg'); $transcodedPicture = $transcodingService->transcode($picturePath, UploadedPicture::MEDIUM_SIZE, 'jpeg');
if (! $transcodedPicture) {
Log::error('Failed to transcode picture', [
'picture' => $picture,
]);
throw new RuntimeException('Failed to transcode picture');
}
$transcodedPictures[] = $transcodedPicture;
} }
$selectedProvider = config('ai-provider.selected-provider'); $selectedProvider = config('ai-provider.selected-provider');
$picturesArray = array_map(fn (UploadedPicture $transcodedPicture) => [ $picturesArray = array_map(fn (string $transcodedPicture) => [
'type' => 'image_url', 'type' => 'image_url',
'image_url' => [ 'image_url' => [
'url' => 'data:image/jpeg;base64,'.base64_encode($transcodedPicture), 'url' => 'data:image/jpeg;base64,'.base64_encode($transcodedPicture),
...@@ -135,17 +144,20 @@ public function prompt(string $systemRole, string $prompt): array ...@@ -135,17 +144,20 @@ public function prompt(string $systemRole, string $prompt): array
*/ */
private function callApi(string $url, array $requestBody): array private function callApi(string $url, array $requestBody): array
{ {
$client = new Client; Log::info('Calling AI provider API', [
'url' => $url,
'request_body' => $requestBody,
]);
try { try {
$response = $client->post($url, $requestBody); $response = Http::post($url, $requestBody);
} catch (GuzzleException $e) { } catch (ConnectionException $e) {
Log::error('Failed to call AI provider API', [ Log::error('Failed to call AI provider API', [
'exception' => $e, 'exception' => $e,
]); ]);
throw new RuntimeException('Failed to call AI provider API'); throw new RuntimeException('Failed to call AI provider API');
} }
$result = json_decode($response->getBody()->getContents(), true); $result = $response->json();
if (! isset($result['choices'][0]['message']['content'])) { if (! isset($result['choices'][0]['message']['content'])) {
Log::error('Failed to get response from AI provider', [ Log::error('Failed to get response from AI provider', [
......
<?php
namespace Tests\Feature\Service;
use App\Models\UploadedPicture;
use App\Services\AiProviderService;
use App\Services\ImageTranscodingService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Mockery;
use PHPUnit\Framework\Attributes\CoversClass;
use RuntimeException;
use Tests\TestCase;
#[CoversClass(AiProviderService::class)]
class AiProviderServiceTest extends TestCase
{
use RefreshDatabase;
private array $sampleResponse = [
'id' => 'chat_1',
'object' => 'chat.completion',
'created' => 1739718124,
'model' => 'gpt-4o-mini-2024-07-18',
'choices' => [
[
'index' => 0,
'message' => [
'role' => 'assistant',
'content' => '{\'message\': \'Why don\'t scientists trust atoms? Because they make up everything!\'}',
'refusal' => null,
],
'logprobs' => null,
'finish_reason' => 'stop',
],
],
'usage' => [
'prompt_tokens' => 33,
'completion_tokens' => 20,
'total_tokens' => 53,
'prompt_tokens_details' => [
'cached_tokens' => 0,
'audio_tokens' => 0,
],
'completion_tokens_details' => [
'reasoning_tokens' => 0,
'audio_tokens' => 0,
'accepted_prediction_tokens' => 0,
'rejected_prediction_tokens' => 0,
],
],
'service_tier' => 'default',
'system_fingerprint' => 'system-1',
];
public function test_prompt_with_pictures_sends_correct_request()
{
Storage::fake('public');
Http::fake([
'https://api.test-provider.com' => Http::response(json_encode($this->sampleResponse)),
]);
$mockTranscodingService = Mockery::mock(ImageTranscodingService::class);
$mockTranscodingService->shouldReceive('transcode')
->andReturn('transcoded-image-content');
App::instance(ImageTranscodingService::class, $mockTranscodingService);
Config::set('ai-provider.selected-provider', 'test-provider');
Config::set('ai-provider.providers.test-provider', [
'api-key' => 'test-api-key',
'url' => 'https://api.test-provider.com',
'model' => 'test-model',
'max-tokens' => 100,
]);
$uploadedPicture = UploadedPicture::factory()->create();
$service = new AiProviderService;
$response = $service->promptWithPictures(
'You are a helpful assistant.',
'Describe this image.',
$uploadedPicture
);
}
public function test_prompt_sends_correct_request()
{
Http::fake([
'https://api.test-provider.com' => Http::response(json_encode($this->sampleResponse)),
]);
Config::set('ai-provider.selected-provider', 'test-provider');
Config::set('ai-provider.providers.test-provider', [
'api-key' => 'test-api-key',
'url' => 'https://api.test-provider.com',
'model' => 'test-model',
'max-tokens' => 100,
]);
$service = new AiProviderService;
$response = $service->prompt(
'You are a helpful assistant.',
'Tell me a joke.'
);
$this->assertEquals(['response' => 'test-response'], $response);
}
public function test_prompt_with_pictures_handles_transcoding_failure()
{
Storage::fake('public');
Http::fake([
'https://api.test-provider.com' => Http::response(json_encode($this->sampleResponse)),
]);
$mockTranscodingService = Mockery::mock(ImageTranscodingService::class);
$mockTranscodingService->shouldReceive('transcode')
->andReturn(null);
App::instance(ImageTranscodingService::class, $mockTranscodingService);
$uploadedPicture = UploadedPicture::factory()->create();
$service = new AiProviderService;
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Failed to transcode picture');
$service->promptWithPictures(
'You are a helpful assistant.',
'Describe this image.',
$uploadedPicture
);
}
public function test_prompt_handles_api_failure()
{
Http::fake([
'https://api.test-provider.com' => Http::response('Error', 500),
]);
Config::set('ai-provider.selected-provider', 'test-provider');
Config::set('ai-provider.providers.test-provider', [
'api-key' => 'test-api-key',
'url' => 'https://api.test-provider.com',
'model' => 'test-model',
'max-tokens' => 100,
]);
$service = new AiProviderService;
$this->expectException(RuntimeException::class);
$service->prompt(
'You are a helpful assistant.',
'Tell me a joke.'
);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment