import pytest from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from app.db.base import Base from app.db.models import BodyMetric, FitnessProfile, User from app.fitness.body_composition import ( compute_body_composition, ffmi, lean_body_mass, navy_body_fat_pct, whr, ) def test_navy_male_reasonable_range(): bf = navy_body_fat_pct( sex="male", height_cm=180, neck_cm=38, waist_cm=84, ) assert bf == pytest.approx(15.3) def test_navy_female_requires_hip(): assert navy_body_fat_pct( sex="female", height_cm=165, neck_cm=34, waist_cm=72, hip_cm=None, ) is None bf = navy_body_fat_pct( sex="female", height_cm=165, neck_cm=34, waist_cm=72, hip_cm=98, ) assert bf == pytest.approx(26.4) def test_navy_invalid_waist_neck(): assert navy_body_fat_pct( sex="male", height_cm=180, neck_cm=40, waist_cm=39, ) is None def test_whr(): assert whr(80, 100) == 0.8 def test_lean_body_mass_and_ffmi(): lbm = lean_body_mass(80, 20) assert lbm == 64.0 score = ffmi(80, 180, 20) assert score is not None assert 15 <= score <= 25 def test_compute_manual_body_fat(): result = compute_body_composition( sex="male", height_cm=180, weight_kg=80, body_fat_pct=18, waist_cm=84, hip_cm=100, ) assert result["body_fat_pct"] == 18.0 assert result["body_fat_method"] == "manual" assert result["whr"] == 0.84 assert result["lbm_kg"] == 65.6 assert result["ffmi"] is not None def test_compute_navy_auto(): result = compute_body_composition( sex="male", height_cm=180, weight_kg=82, neck_cm=38, waist_cm=84, ) assert result["body_fat_pct"] is not None assert result["body_fat_method"] == "navy" assert result["lbm_kg"] is not None def test_compute_female_warning_without_hip(): result = compute_body_composition( sex="female", height_cm=165, weight_kg=60, neck_cm=34, waist_cm=72, ) assert result["body_fat_pct"] is None assert any("бёдер" in w for w in result["warnings"]) def test_update_body_metric_recomputes_composition_and_latest_profile_weight(): from app.fitness.service import FitnessService engine = create_engine("sqlite:///:memory:") Base.metadata.create_all(engine) Session = sessionmaker(bind=engine) with Session() as db: user = User(username="tester", api_token_hash="x") db.add(user) db.flush() db.add( FitnessProfile( user_id=user.id, sex="male", age=30, height_cm=180, weight_kg=82, goal="maintain", ) ) metric = BodyMetric( user_id=user.id, weight_kg=82, body_fat_pct=9.0, body_fat_method="navy", waist_cm=84, neck_cm=38, ) db.add(metric) db.commit() metric_id = metric.id user_id = user.id result = FitnessService(db, user_id).update_body_metric( metric_id, {"weight_kg": 83, "waist_cm": 86, "neck_cm": 38}, ) assert result is not None expected = compute_body_composition( sex="male", height_cm=180, weight_kg=83, waist_cm=86, neck_cm=38, ) assert result["metric"]["weight_kg"] == 83 assert result["metric"]["body_fat_method"] == "navy" assert result["metric"]["body_fat_pct"] == expected["body_fat_pct"] assert result["metric"]["lbm_kg"] == expected["lbm_kg"] assert result["metric"]["ffmi"] == expected["ffmi"] assert db.query(FitnessProfile).one().weight_kg == 83