feat: seeder
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
@@ -17,11 +16,8 @@ class DatabaseSeeder extends Seeder
|
||||
{
|
||||
$this->call(LegalDocumentSeeder::class);
|
||||
|
||||
// User::factory(10)->create();
|
||||
|
||||
User::factory()->create([
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
]);
|
||||
if (app()->environment(['local', 'testing'])) {
|
||||
$this->call(DemoContentSeeder::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,507 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Enums\DietType;
|
||||
use App\Enums\FollowStatus;
|
||||
use App\Enums\IngredientUnit;
|
||||
use App\Enums\MealPostType;
|
||||
use App\Enums\MealPostVisibility;
|
||||
use App\Enums\PacePreference;
|
||||
use App\Enums\PhysicalActivityLevel;
|
||||
use App\Enums\UserSex;
|
||||
use App\Enums\WeightGoal;
|
||||
use App\Enums\WorkoutSource;
|
||||
use App\Models\User;
|
||||
use App\Models\WeightEntry;
|
||||
use App\Models\WorkoutSessions;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use LogicException;
|
||||
|
||||
class DemoContentSeeder extends Seeder
|
||||
{
|
||||
use WithoutModelEvents;
|
||||
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
DB::transaction(function (): void {
|
||||
$users = $this->createUsers();
|
||||
|
||||
$this->createMeals($users);
|
||||
$this->createSocialGraph($users);
|
||||
$this->createProgressHistory($this->demoUser($users));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, User>
|
||||
*/
|
||||
private function createUsers(): Collection
|
||||
{
|
||||
$users = collect([
|
||||
[
|
||||
'name' => 'Alex Martin',
|
||||
'email' => 'test@example.com',
|
||||
'locale' => 'fr',
|
||||
'bio' => 'Cuisine simple, course à pied et progression régulière.',
|
||||
'account_verified_at' => now(),
|
||||
'height' => 178,
|
||||
'target_weight' => 68,
|
||||
'weight_goal' => WeightGoal::LOSE_WEIGHT,
|
||||
'physical_activity_level' => PhysicalActivityLevel::MODERATELY_ACTIVE,
|
||||
'pace_preference' => PacePreference::NORMAL,
|
||||
'sex' => UserSex::MAN,
|
||||
'date_of_birth' => '1994-04-16',
|
||||
'terms_accepted_at' => now(),
|
||||
],
|
||||
[
|
||||
'name' => 'Camille Green',
|
||||
'email' => 'camille@bowli.test',
|
||||
'locale' => 'fr',
|
||||
'bio' => 'Recettes végétales colorées et faciles à préparer.',
|
||||
'account_verified_at' => now(),
|
||||
],
|
||||
[
|
||||
'name' => 'Noah Kitchen',
|
||||
'email' => 'noah@bowli.test',
|
||||
'locale' => 'en',
|
||||
'bio' => 'High-protein meals without complicated preparation.',
|
||||
],
|
||||
[
|
||||
'name' => 'Lina Fraîcheur',
|
||||
'email' => 'lina@bowli.test',
|
||||
'locale' => 'fr',
|
||||
'bio' => 'Bols frais, produits de saison et cuisine méditerranéenne.',
|
||||
'account_verified_at' => now(),
|
||||
],
|
||||
[
|
||||
'name' => 'Sam Fit Food',
|
||||
'email' => 'sam@bowli.test',
|
||||
'locale' => 'en',
|
||||
'bio' => 'Balanced meals for active weeks.',
|
||||
],
|
||||
[
|
||||
'name' => 'Inès Brunch',
|
||||
'email' => 'ines@bowli.test',
|
||||
'locale' => 'fr',
|
||||
'bio' => 'Petits-déjeuners gourmands et brunchs du dimanche.',
|
||||
],
|
||||
[
|
||||
'name' => 'Hugo Maison',
|
||||
'email' => 'hugo@bowli.test',
|
||||
'locale' => 'fr',
|
||||
'bio' => 'Cuisine maison généreuse avec des ingrédients simples.',
|
||||
],
|
||||
[
|
||||
'name' => 'Maya Plant',
|
||||
'email' => 'maya@bowli.test',
|
||||
'locale' => 'en',
|
||||
'bio' => 'Plant-based bowls, soups and snacks.',
|
||||
'account_verified_at' => now(),
|
||||
],
|
||||
[
|
||||
'name' => 'Louis Endurance',
|
||||
'email' => 'louis@bowli.test',
|
||||
'locale' => 'fr',
|
||||
'bio' => 'Des repas adaptés aux longues sorties et à la récupération.',
|
||||
],
|
||||
])->map(
|
||||
fn (array $profile): User => User::factory()->create($profile)
|
||||
);
|
||||
|
||||
User::factory()->withoutNutritionOnboarding()->create([
|
||||
'name' => 'Nouveau profil',
|
||||
'email' => 'onboarding@example.com',
|
||||
'locale' => 'fr',
|
||||
'terms_accepted_at' => now(),
|
||||
]);
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, User> $users
|
||||
*/
|
||||
private function createMeals(Collection $users): void
|
||||
{
|
||||
$templates = collect($this->mealTemplates());
|
||||
$reviewComments = collect([
|
||||
'Très bonne idée, je vais essayer cette semaine.',
|
||||
'Simple, équilibré et vraiment appétissant.',
|
||||
'Les proportions sont parfaites pour mon déjeuner.',
|
||||
'Testé hier soir, toute la famille a aimé.',
|
||||
'Belle assiette et excellente source d’inspiration.',
|
||||
'Parfait après une séance de sport.',
|
||||
]);
|
||||
|
||||
foreach (range(0, 47) as $index) {
|
||||
$owner = $users->get($index % $users->count());
|
||||
$template = $templates->get($index % $templates->count());
|
||||
$eatenAt = now()->subHours(($index * 4) + 1);
|
||||
|
||||
if (! $owner instanceof User || ! is_array($template)) {
|
||||
throw new LogicException('Unable to build the demo meal collection.');
|
||||
}
|
||||
|
||||
$meal = $owner->mealPosts()->create([
|
||||
'image_url' => $template['imageUrl'],
|
||||
'caption' => $template['caption'],
|
||||
'calories' => $template['calories'],
|
||||
'proteins' => $template['proteins'],
|
||||
'carbs' => $template['carbs'],
|
||||
'fats' => $template['fats'],
|
||||
'title' => $template['title'],
|
||||
'eaten_at' => $eatenAt,
|
||||
'visibility' => MealPostVisibility::Public,
|
||||
'type' => $template['type'],
|
||||
'ai_generated' => $index % 9 === 0,
|
||||
'diet_type' => $template['dietType'],
|
||||
]);
|
||||
|
||||
$meal->ingredients()->createMany(
|
||||
collect($template['ingredients'])
|
||||
->values()
|
||||
->map(fn (array $ingredient, int $position): array => [
|
||||
...$ingredient,
|
||||
'position' => $position + 1,
|
||||
])
|
||||
->all()
|
||||
);
|
||||
|
||||
$eligibleUsers = $users
|
||||
->reject(fn (User $user): bool => $user->is($owner))
|
||||
->values();
|
||||
$engagedUsers = $eligibleUsers
|
||||
->slice($index % $eligibleUsers->count())
|
||||
->concat($eligibleUsers->take($index % $eligibleUsers->count()))
|
||||
->values();
|
||||
|
||||
$meal->likedByUsers()->attach(
|
||||
$engagedUsers->take(2 + ($index % 5))->pluck('id')->all(),
|
||||
[
|
||||
'created_at' => $eatenAt->addMinutes(30),
|
||||
'updated_at' => $eatenAt->addMinutes(30),
|
||||
]
|
||||
);
|
||||
|
||||
$engagedUsers->take(2 + ($index % 2))->each(
|
||||
function (User $reviewer, int $reviewerIndex) use ($index, $meal, $reviewComments): void {
|
||||
$meal->reviews()->create([
|
||||
'rating' => 5 - (($index + $reviewerIndex) % 2),
|
||||
'comment' => $reviewComments->get(
|
||||
($index + $reviewerIndex) % $reviewComments->count()
|
||||
),
|
||||
'user_id' => $reviewer->getKey(),
|
||||
]);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, User> $users
|
||||
*/
|
||||
private function createSocialGraph(Collection $users): void
|
||||
{
|
||||
$demoUser = $this->demoUser($users);
|
||||
$creators = $users
|
||||
->reject(fn (User $user): bool => $user->is($demoUser))
|
||||
->values();
|
||||
$pivot = [
|
||||
'status' => FollowStatus::Accepted->value,
|
||||
'created_at' => now()->subDays(10),
|
||||
'updated_at' => now()->subDays(10),
|
||||
];
|
||||
|
||||
$demoUser->following()->attach($creators->pluck('id')->all(), $pivot);
|
||||
|
||||
$creators->take(5)->each(
|
||||
fn (User $creator) => $creator->following()->attach($demoUser->getKey(), $pivot)
|
||||
);
|
||||
|
||||
$creators->each(function (User $creator, int $index) use ($creators, $pivot): void {
|
||||
$nextCreator = $creators->get(($index + 1) % $creators->count());
|
||||
|
||||
if ($nextCreator instanceof User) {
|
||||
$creator->following()->attach($nextCreator->getKey(), $pivot);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function createProgressHistory(User $demoUser): void
|
||||
{
|
||||
$demoUser->weightEntries()->delete();
|
||||
|
||||
foreach (range(11, 0) as $weeksAgo) {
|
||||
WeightEntry::factory()->for($demoUser)->create([
|
||||
'weight_kg' => round(74.8 - ((11 - $weeksAgo) * 0.28), 2),
|
||||
'measured_at' => now()->startOfDay()->subWeeks($weeksAgo),
|
||||
]);
|
||||
}
|
||||
|
||||
$demoUser->nutritionPlans()
|
||||
->where('is_active', true)
|
||||
->update([
|
||||
'calories' => 2050,
|
||||
'proteins' => 130,
|
||||
'carbs' => 235,
|
||||
'fats' => 65,
|
||||
'maintenance_calories' => 2350,
|
||||
'calorie_adjustment_percent' => -0.1277,
|
||||
'calculation_details' => [
|
||||
'activityFactor' => 1.55,
|
||||
'inputWeightKg' => 71.72,
|
||||
],
|
||||
]);
|
||||
|
||||
$workouts = collect([
|
||||
['title' => 'Course facile', 'type' => 'running', 'duration' => 2700, 'calories' => 390, 'distance' => 7200],
|
||||
['title' => 'Renforcement complet', 'type' => 'strength', 'duration' => 3300, 'calories' => 310, 'distance' => 0],
|
||||
['title' => 'Sortie vélo', 'type' => 'cycling', 'duration' => 5400, 'calories' => 620, 'distance' => 32000],
|
||||
['title' => 'Marche active', 'type' => 'walking', 'duration' => 3600, 'calories' => 260, 'distance' => 6100],
|
||||
]);
|
||||
|
||||
foreach (range(0, 11) as $index) {
|
||||
$workout = $workouts->get($index % $workouts->count());
|
||||
$source = $index % 4 === 0 ? WorkoutSource::STRAVA : WorkoutSource::MANUAL;
|
||||
|
||||
if (! is_array($workout)) {
|
||||
throw new LogicException('Unable to build the demo workout collection.');
|
||||
}
|
||||
|
||||
WorkoutSessions::factory()->for($demoUser)->create([
|
||||
'external_id' => $source === WorkoutSource::STRAVA ? "demo-strava-{$index}" : null,
|
||||
'source' => $source,
|
||||
'type' => $workout['type'],
|
||||
'title' => $workout['title'],
|
||||
'duration_seconds' => $workout['duration'],
|
||||
'calories_burned' => $workout['calories'],
|
||||
'distance_meters' => $workout['distance'],
|
||||
'started_at' => now()->subDays($index)->setTime(18, 30),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection<int, User> $users
|
||||
*/
|
||||
private function demoUser(Collection $users): User
|
||||
{
|
||||
$demoUser = $users->first(
|
||||
fn (User $user): bool => $user->email === 'test@example.com'
|
||||
);
|
||||
|
||||
if (! $demoUser instanceof User) {
|
||||
throw new LogicException('The demo user could not be created.');
|
||||
}
|
||||
|
||||
return $demoUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
private function mealTemplates(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'title' => 'Bowl saumon et avocat',
|
||||
'caption' => 'Un déjeuner complet avec du riz, des légumes croquants et une sauce citronnée.',
|
||||
'imageUrl' => 'https://images.unsplash.com/photo-1546069901-ba9599a7e63c?auto=format&fit=crop&w=1200&q=85',
|
||||
'calories' => 620,
|
||||
'proteins' => 38,
|
||||
'carbs' => 58,
|
||||
'fats' => 24,
|
||||
'type' => MealPostType::LUNCH,
|
||||
'dietType' => DietType::PESCATARIAN,
|
||||
'ingredients' => [
|
||||
['ingredient' => 'Saumon', 'quantity' => 140, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Riz complet', 'quantity' => 160, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Avocat', 'quantity' => 1, 'unit' => IngredientUnit::PIECE],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Pancakes aux fruits rouges',
|
||||
'caption' => 'Des pancakes moelleux accompagnés de fruits rouges et de yaourt.',
|
||||
'imageUrl' => 'https://images.unsplash.com/photo-1528207776546-365bb710ee93?auto=format&fit=crop&w=1200&q=85',
|
||||
'calories' => 480,
|
||||
'proteins' => 24,
|
||||
'carbs' => 64,
|
||||
'fats' => 14,
|
||||
'type' => MealPostType::BREAKFAST,
|
||||
'dietType' => DietType::VEGETARIAN,
|
||||
'ingredients' => [
|
||||
['ingredient' => 'Flocons d’avoine', 'quantity' => 80, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Œufs', 'quantity' => 2, 'unit' => IngredientUnit::PIECE],
|
||||
['ingredient' => 'Fruits rouges', 'quantity' => 120, 'unit' => IngredientUnit::GRAM],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Poulet rôti et légumes',
|
||||
'caption' => 'Une assiette familiale, riche en protéines et pleine de couleurs.',
|
||||
'imageUrl' => 'https://images.unsplash.com/photo-1532550907401-a500c9a57435?auto=format&fit=crop&w=1200&q=85',
|
||||
'calories' => 590,
|
||||
'proteins' => 52,
|
||||
'carbs' => 46,
|
||||
'fats' => 19,
|
||||
'type' => MealPostType::DINNER,
|
||||
'dietType' => DietType::OMNIVORE,
|
||||
'ingredients' => [
|
||||
['ingredient' => 'Blanc de poulet', 'quantity' => 180, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Pommes de terre', 'quantity' => 220, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Légumes rôtis', 'quantity' => 180, 'unit' => IngredientUnit::GRAM],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Curry de pois chiches',
|
||||
'caption' => 'Un curry végétal doux, crémeux et parfait pour le batch cooking.',
|
||||
'imageUrl' => 'https://images.unsplash.com/photo-1603894584373-5ac82b2ae398?auto=format&fit=crop&w=1200&q=85',
|
||||
'calories' => 540,
|
||||
'proteins' => 21,
|
||||
'carbs' => 72,
|
||||
'fats' => 18,
|
||||
'type' => MealPostType::DINNER,
|
||||
'dietType' => DietType::VEGAN,
|
||||
'ingredients' => [
|
||||
['ingredient' => 'Pois chiches', 'quantity' => 180, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Lait de coco', 'quantity' => 120, 'unit' => IngredientUnit::MILLILITER],
|
||||
['ingredient' => 'Épinards', 'quantity' => 100, 'unit' => IngredientUnit::GRAM],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Pâtes au pesto maison',
|
||||
'caption' => 'Un classique rapide avec tomates cerises, roquette et parmesan.',
|
||||
'imageUrl' => 'https://images.unsplash.com/photo-1473093295043-cdd812d0e601?auto=format&fit=crop&w=1200&q=85',
|
||||
'calories' => 670,
|
||||
'proteins' => 25,
|
||||
'carbs' => 88,
|
||||
'fats' => 24,
|
||||
'type' => MealPostType::LUNCH,
|
||||
'dietType' => DietType::VEGETARIAN,
|
||||
'ingredients' => [
|
||||
['ingredient' => 'Pâtes complètes', 'quantity' => 190, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Pesto', 'quantity' => 2, 'unit' => IngredientUnit::TABLESPOON],
|
||||
['ingredient' => 'Tomates cerises', 'quantity' => 120, 'unit' => IngredientUnit::GRAM],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Toast avocat et œufs',
|
||||
'caption' => 'Le brunch simple qui tient toute la matinée.',
|
||||
'imageUrl' => 'https://images.unsplash.com/photo-1482049016688-2d3e1b311543?auto=format&fit=crop&w=1200&q=85',
|
||||
'calories' => 510,
|
||||
'proteins' => 26,
|
||||
'carbs' => 42,
|
||||
'fats' => 27,
|
||||
'type' => MealPostType::BRUNCH,
|
||||
'dietType' => DietType::VEGETARIAN,
|
||||
'ingredients' => [
|
||||
['ingredient' => 'Pain complet', 'quantity' => 2, 'unit' => IngredientUnit::PIECE],
|
||||
['ingredient' => 'Avocat', 'quantity' => 1, 'unit' => IngredientUnit::PIECE],
|
||||
['ingredient' => 'Œufs', 'quantity' => 2, 'unit' => IngredientUnit::PIECE],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Tacos de poisson',
|
||||
'caption' => 'Poisson grillé, chou croquant et sauce au yaourt citronnée.',
|
||||
'imageUrl' => 'https://images.unsplash.com/photo-1551504734-5ee1c4a1479b?auto=format&fit=crop&w=1200&q=85',
|
||||
'calories' => 560,
|
||||
'proteins' => 36,
|
||||
'carbs' => 61,
|
||||
'fats' => 20,
|
||||
'type' => MealPostType::DINNER,
|
||||
'dietType' => DietType::PESCATARIAN,
|
||||
'ingredients' => [
|
||||
['ingredient' => 'Poisson blanc', 'quantity' => 160, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Tortillas', 'quantity' => 3, 'unit' => IngredientUnit::PIECE],
|
||||
['ingredient' => 'Chou rouge', 'quantity' => 100, 'unit' => IngredientUnit::GRAM],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Buddha bowl au tofu',
|
||||
'caption' => 'Tofu croustillant, quinoa, légumes et sauce sésame.',
|
||||
'imageUrl' => 'https://images.unsplash.com/photo-1512621776951-a57141f2eefd?auto=format&fit=crop&w=1200&q=85',
|
||||
'calories' => 575,
|
||||
'proteins' => 29,
|
||||
'carbs' => 69,
|
||||
'fats' => 22,
|
||||
'type' => MealPostType::LUNCH,
|
||||
'dietType' => DietType::VEGAN,
|
||||
'ingredients' => [
|
||||
['ingredient' => 'Tofu', 'quantity' => 160, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Quinoa', 'quantity' => 150, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Brocoli', 'quantity' => 140, 'unit' => IngredientUnit::GRAM],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Steak et patate douce',
|
||||
'caption' => 'Une assiette protéinée avec patate douce rôtie et haricots verts.',
|
||||
'imageUrl' => 'https://images.unsplash.com/photo-1600891964092-4316c288032e?auto=format&fit=crop&w=1200&q=85',
|
||||
'calories' => 710,
|
||||
'proteins' => 49,
|
||||
'carbs' => 65,
|
||||
'fats' => 28,
|
||||
'type' => MealPostType::DINNER,
|
||||
'dietType' => DietType::OMNIVORE,
|
||||
'ingredients' => [
|
||||
['ingredient' => 'Steak de bœuf', 'quantity' => 180, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Patate douce', 'quantity' => 240, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Haricots verts', 'quantity' => 150, 'unit' => IngredientUnit::GRAM],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Overnight oats banane',
|
||||
'caption' => 'Préparé la veille avec banane, graines de chia et beurre de cacahuète.',
|
||||
'imageUrl' => 'https://images.unsplash.com/photo-1517673400267-0251440c45dc?auto=format&fit=crop&w=1200&q=85',
|
||||
'calories' => 445,
|
||||
'proteins' => 18,
|
||||
'carbs' => 62,
|
||||
'fats' => 15,
|
||||
'type' => MealPostType::BREAKFAST,
|
||||
'dietType' => DietType::VEGETARIAN,
|
||||
'ingredients' => [
|
||||
['ingredient' => 'Flocons d’avoine', 'quantity' => 70, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Banane', 'quantity' => 1, 'unit' => IngredientUnit::PIECE],
|
||||
['ingredient' => 'Graines de chia', 'quantity' => 2, 'unit' => IngredientUnit::TEASPOON],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Salade grecque complète',
|
||||
'caption' => 'Tomates, concombre, feta, olives et pain pita.',
|
||||
'imageUrl' => 'https://images.unsplash.com/photo-1540420773420-3366772f4999?auto=format&fit=crop&w=1200&q=85',
|
||||
'calories' => 495,
|
||||
'proteins' => 19,
|
||||
'carbs' => 47,
|
||||
'fats' => 26,
|
||||
'type' => MealPostType::LUNCH,
|
||||
'dietType' => DietType::VEGETARIAN,
|
||||
'ingredients' => [
|
||||
['ingredient' => 'Tomates', 'quantity' => 180, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Feta', 'quantity' => 80, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Pain pita', 'quantity' => 1, 'unit' => IngredientUnit::PIECE],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Ramen au poulet',
|
||||
'caption' => 'Un bouillon chaud avec nouilles, poulet, œuf et légumes.',
|
||||
'imageUrl' => 'https://images.unsplash.com/photo-1569718212165-3a8278d5f624?auto=format&fit=crop&w=1200&q=85',
|
||||
'calories' => 640,
|
||||
'proteins' => 42,
|
||||
'carbs' => 78,
|
||||
'fats' => 18,
|
||||
'type' => MealPostType::DINNER,
|
||||
'dietType' => DietType::OMNIVORE,
|
||||
'ingredients' => [
|
||||
['ingredient' => 'Nouilles', 'quantity' => 180, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Poulet', 'quantity' => 140, 'unit' => IngredientUnit::GRAM],
|
||||
['ingredient' => 'Œuf', 'quantity' => 1, 'unit' => IngredientUnit::PIECE],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\MealPostVisibility;
|
||||
use App\Models\MealPostIngredients;
|
||||
use App\Models\MealPosts;
|
||||
use App\Models\PostReviews;
|
||||
use App\Models\User;
|
||||
use Database\Seeders\DemoContentSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('creates a complete local demonstration dataset', function () {
|
||||
$this->seed();
|
||||
|
||||
$demoUser = User::query()->where('email', 'test@example.com')->firstOrFail();
|
||||
$onboardingUser = User::query()->where('email', 'onboarding@example.com')->firstOrFail();
|
||||
|
||||
expect(User::query()->count())->toBe(10)
|
||||
->and($demoUser->hasCompletedNutritionOnboarding())->toBeTrue()
|
||||
->and($onboardingUser->hasCompletedNutritionOnboarding())->toBeFalse()
|
||||
->and($demoUser->weightEntries()->count())->toBe(12)
|
||||
->and($demoUser->workouts()->count())->toBe(12)
|
||||
->and($demoUser->following()->count())->toBe(8)
|
||||
->and(MealPosts::query()->where('visibility', MealPostVisibility::Public)->count())->toBe(48)
|
||||
->and(MealPostIngredients::query()->count())->toBe(144)
|
||||
->and(PostReviews::query()->count())->toBe(120)
|
||||
->and(
|
||||
MealPosts::query()
|
||||
->withCount('likedByUsers')
|
||||
->get()
|
||||
->sum('liked_by_users_count')
|
||||
)->toBeGreaterThan(100);
|
||||
});
|
||||
|
||||
it('fills more than one explorer page with usable meal images', function () {
|
||||
$this->seed(DemoContentSeeder::class);
|
||||
|
||||
$demoUser = User::query()->where('email', 'test@example.com')->firstOrFail();
|
||||
|
||||
Sanctum::actingAs($demoUser);
|
||||
|
||||
$this->getJson('/api/meal-posts?per_page=30&page=1')
|
||||
->assertOk()
|
||||
->assertJsonCount(30, 'data')
|
||||
->assertJsonPath('meta.current_page', 1)
|
||||
->assertJsonPath('meta.last_page', 2)
|
||||
->assertJsonPath('meta.total', 48)
|
||||
->assertJsonPath('data.0.imageUrl', fn (string $url): bool => str_starts_with($url, 'https://images.unsplash.com/'));
|
||||
|
||||
$this->getJson('/api/meal-posts?per_page=30&page=2')
|
||||
->assertOk()
|
||||
->assertJsonCount(18, 'data')
|
||||
->assertJsonPath('meta.current_page', 2);
|
||||
});
|
||||
Reference in New Issue
Block a user