'1992-04-18', 'sex' => UserSex::WOMAN->value, 'heightCm' => 168, 'weightKg' => 72.4, 'targetWeightKg' => 66, 'physicalActivityLevel' => PhysicalActivityLevel::MODERATELY_ACTIVE->value, 'weightGoal' => WeightGoal::LOSE_WEIGHT->value, 'pacePreference' => PacePreference::NORMAL->value, 'nutritionEstimateAccepted' => true, ...$overrides, ]; } it('returns an explicit onboarding state and blocks application endpoints', function () { $user = User::factory()->withoutNutritionOnboarding()->create(); Sanctum::actingAs($user); $this->getJson('/api/auth/me') ->assertOk() ->assertJsonPath('data.onboardingStatus', 'required') ->assertJsonPath('data.nutritionGoals', null) ->assertJsonPath('data.currentWeight', null); $this->getJson('/api/meal-posts/stats?period=day&date=2026-08-14') ->assertStatus(409) ->assertJsonPath('code', 'ONBOARDING_REQUIRED') ->assertJsonMissingPath('message'); }); it('completes onboarding atomically and returns the calculated plan', function () { $user = User::factory()->withoutNutritionOnboarding()->create(); Sanctum::actingAs($user); $response = $this->postJson('/api/nutrition-onboarding', onboardingPayload()); $response ->assertOk() ->assertJsonPath('data.onboardingStatus', 'completed') ->assertJsonPath('data.height', 168) ->assertJsonPath('data.currentWeight', 72.4) ->assertJsonPath('data.targetWeight', 66) ->assertJsonPath('data.nutritionPlan.formulaVersion', 'mifflin_st_jeor_v1') ->assertJsonPath('data.nutritionPlan.source', 'onboarding'); expect($response->json('data.nutritionGoals.calories'))->toBeGreaterThan(1200); $this->assertDatabaseCount('weight_entries', 1); $this->assertDatabaseCount('nutrition_plans', 1); expect($user->fresh()->onboarding_completed_at)->not->toBeNull(); }); it('is idempotent when the completion response is retried', function () { $user = User::factory()->withoutNutritionOnboarding()->create(); Sanctum::actingAs($user); $this->postJson('/api/nutrition-onboarding', onboardingPayload())->assertOk(); $this->postJson('/api/nutrition-onboarding', onboardingPayload())->assertOk(); $this->assertDatabaseCount('weight_entries', 1); $this->assertDatabaseCount('nutrition_plans', 1); }); it('validates adult age, formula reference and target direction', function () { Sanctum::actingAs(User::factory()->withoutNutritionOnboarding()->create()); $this->postJson('/api/nutrition-onboarding', onboardingPayload([ 'dateOfBirth' => now()->subYears(17)->toDateString(), 'sex' => UserSex::OTHER->value, 'targetWeightKg' => 80, ])) ->assertUnprocessable() ->assertJsonValidationErrors(['dateOfBirth', 'sex', 'targetWeightKg']) ->assertJsonPath('code', 'VALIDATION_ERROR') ->assertJsonPath('errors.dateOfBirth.0', 'NUTRITION_ADULTS_ONLY') ->assertJsonPath('errors.sex.0', 'NUTRITION_METABOLIC_SEX_REQUIRED') ->assertJsonPath('errors.targetWeightKg.0', 'NUTRITION_TARGET_WEIGHT_MUST_BE_LOWER') ->assertJsonMissingPath('message'); });