api/tests/Feature/GenerateAiMealPostTest.php

123 lines
4.5 KiB
PHP

<?php
use App\Ai\Agents\MealMaker;
use App\Enums\IngredientUnit;
use App\Enums\MealPostType;
use App\Enums\MealPostVisibility;
use App\Models\MealPosts;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use Laravel\Ai\Image;
uses(RefreshDatabase::class);
it('generates an ai meal post with an image', function () {
Storage::fake('public');
config()->set('meal_posts.ai_generation.user_name', 'Chef IA');
config()->set('meal_posts.ai_generation.user_email', 'chef-ia@example.test');
config()->set('meal_posts.ai_generation.image_path', 'meal-posts/ai-generated');
config()->set('meal_posts.ai_generation.image_quality', 'medium');
foreach (range(1, 11) as $index) {
$mealPost = MealPosts::factory()->create([
'eaten_at' => now()->subMinutes(11 - $index),
]);
$mealPost->ingredients()->createMany([
[
'position' => 1,
'ingredient' => $index === 1 ? 'Topinambour ancien' : "Ingredient recent {$index}",
'quantity' => 120,
'unit' => IngredientUnit::GRAM->value,
],
[
'position' => 2,
'ingredient' => $index === 11 ? 'Asperges' : "Accompagnement recent {$index}",
'quantity' => 80,
'unit' => IngredientUnit::GRAM->value,
],
[
'position' => 4,
'ingredient' => "Garniture ignoree {$index}",
'quantity' => 10,
'unit' => IngredientUnit::GRAM->value,
],
]);
}
MealMaker::fake([
[
'title' => 'Bowl de saumon croustillant',
'caption' => 'Un bowl colore avec saumon, riz vinaigre et legumes croquants.',
'type' => MealPostType::LUNCH->value,
'calories' => 640,
'proteins' => 42.4,
'carbs' => 62.2,
'fats' => 24.8,
'ingredients' => [
[
'position' => 12,
'ingredient' => 'Riz vinaigre',
'quantity' => 160,
'unit' => IngredientUnit::GRAM->value,
],
[
'position' => 13,
'ingredient' => 'Saumon',
'quantity' => 130,
'unit' => IngredientUnit::GRAM->value,
],
],
'image_prompt' => 'Photorealistic crispy salmon bowl with vinegared rice and crunchy vegetables',
],
]);
Image::fake([
base64_encode('fake-image-content'),
]);
$this->artisan('meal-posts:generate-ai')
->assertExitCode(0);
$mealPost = MealPosts::query()
->where('ai_generated', true)
->with('user', 'ingredients')
->firstOrFail();
expect($mealPost->ai_generated)->toBeTrue()
->and($mealPost->title)->toBe('Bowl de saumon croustillant')
->and($mealPost->type)->toBe(MealPostType::LUNCH)
->and($mealPost->visibility)->toBe(MealPostVisibility::Public)
->and($mealPost->user->email)->toBe('chef-ia@example.test')
->and($mealPost->user->name)->toBe('Chef IA')
->and($mealPost->ingredients)->toHaveCount(2)
->and($mealPost->ingredients[0]->position)->toBe(1)
->and($mealPost->ingredients[0]->ingredient)->toBe('Riz vinaigre');
expect($mealPost->image_url)->toStartWith('meal-posts/ai-generated/');
Storage::disk('public')->assertExists($mealPost->image_url);
$this->assertDatabaseHas('meal_posts', [
'id' => $mealPost->id,
'ai_generated' => true,
'visibility' => MealPostVisibility::Public->value,
]);
MealMaker::assertPrompted(
fn ($prompt): bool => str_contains($prompt->prompt, 'Genere un plat completement aleatoire')
&& str_contains($prompt->prompt, 'sans tenir compte du mois actuel')
&& str_contains($prompt->prompt, 'Evite les ingredients principaux deja utilises dans les 10 derniers repas')
&& str_contains($prompt->prompt, 'Ingredient recent 2')
&& str_contains($prompt->prompt, 'Asperges')
&& ! str_contains($prompt->prompt, 'Topinambour ancien')
&& ! str_contains($prompt->prompt, 'Garniture ignoree 11')
);
Image::assertGenerated(
fn ($prompt): bool => $prompt->contains('crispy salmon bowl')
&& $prompt->isLandscape()
&& $prompt->quality === 'medium'
);
});