"""Web API 测试""" from datetime import date, timedelta import pytest from fastapi.testclient import TestClient from src.vitals.core import database as db from src.vitals.core.models import Exercise, Meal, Sleep, Weight, UserConfig @pytest.fixture def client(): """创建测试客户端""" from src.vitals.web.app import app return TestClient(app) @pytest.fixture def populated_db(): """填充测试数据""" today = date.today() # 用户配置 config = UserConfig( age=28, gender="male", height=175.0, weight=72.0, activity_level="moderate", goal="maintain", ) db.save_config(config) # 今日数据 db.add_exercise(Exercise( date=today, type="跑步", duration=30, calories=240, distance=5.0, )) db.add_meal(Meal( date=today, meal_type="午餐", description="米饭+鸡肉", calories=500, )) db.add_meal(Meal( date=today, meal_type="早餐", description="燕麦+鸡蛋", calories=350, )) db.add_sleep(Sleep( date=today, duration=7.5, quality=4, )) db.add_weight(Weight( date=today, weight_kg=72.5, body_fat_pct=18.5, )) return today class TestRootEndpoint: """根路径测试""" def test_root_returns_html(self, client): """测试返回 HTML""" response = client.get("/") assert response.status_code == 200 assert "text/html" in response.headers["content-type"] def test_root_contains_dashboard(self, client): """测试包含仪表盘内容""" response = client.get("/") assert "Vitals" in response.text assert "健康管理" in response.text class TestConfigEndpoint: """配置接口测试""" def test_get_config(self, client, populated_db): """测试获取配置""" response = client.get("/api/config") assert response.status_code == 200 data = response.json() assert data["age"] == 28 assert data["gender"] == "male" assert data["bmr"] is not None assert data["tdee"] is not None def test_get_config_empty(self, client): """测试空配置""" response = client.get("/api/config") assert response.status_code == 200 data = response.json() assert data["activity_level"] == "moderate" class TestTodayEndpoint: """今日概览接口测试""" def test_get_today(self, client, populated_db): """测试获取今日数据""" response = client.get("/api/today") assert response.status_code == 200 data = response.json() assert data["date"] == date.today().isoformat() assert data["calories_intake"] > 0 assert data["exercise_count"] == 1 def test_today_has_meals(self, client, populated_db): """测试包含饮食""" response = client.get("/api/today") data = response.json() assert len(data["meals"]) == 2 assert data["meals"][0]["meal_type"] in ["午餐", "早餐"] def test_today_has_exercises(self, client, populated_db): """测试包含运动""" response = client.get("/api/today") data = response.json() assert len(data["exercises"]) == 1 assert data["exercises"][0]["type"] == "跑步" def test_today_empty(self, client): """测试空数据""" response = client.get("/api/today") assert response.status_code == 200 data = response.json() assert data["calories_intake"] == 0 assert data["exercise_count"] == 0 class TestWeekEndpoint: """本周汇总接口测试""" def test_get_week(self, client, populated_db): """测试获取本周数据""" response = client.get("/api/week") assert response.status_code == 200 data = response.json() assert "start_date" in data assert "end_date" in data assert "daily_stats" in data def test_week_has_daily_stats(self, client, populated_db): """测试包含每日统计""" response = client.get("/api/week") data = response.json() assert len(data["daily_stats"]) == 7 for stat in data["daily_stats"]: assert "date" in stat assert "weekday" in stat def test_week_date_range(self, client, populated_db): """测试日期范围""" response = client.get("/api/week") data = response.json() start = date.fromisoformat(data["start_date"]) end = date.fromisoformat(data["end_date"]) assert (end - start).days == 6 class TestExercisesEndpoint: """运动记录接口测试""" def test_get_exercises(self, client, populated_db): """测试获取运动记录""" response = client.get("/api/exercises") assert response.status_code == 200 data = response.json() assert len(data) >= 1 def test_exercises_with_days_param(self, client, populated_db): """测试天数参数""" response = client.get("/api/exercises?days=7") assert response.status_code == 200 def test_exercises_invalid_days(self, client): """测试无效天数""" response = client.get("/api/exercises?days=0") assert response.status_code == 422 # Validation error def test_exercises_empty(self, client): """测试空数据""" response = client.get("/api/exercises") assert response.status_code == 200 assert response.json() == [] class TestMealsEndpoint: """饮食记录接口测试""" def test_get_meals(self, client, populated_db): """测试获取饮食记录""" response = client.get("/api/meals") assert response.status_code == 200 data = response.json() assert len(data) >= 2 def test_meals_structure(self, client, populated_db): """测试数据结构""" response = client.get("/api/meals") data = response.json() meal = data[0] assert "id" in meal assert "date" in meal assert "meal_type" in meal assert "description" in meal assert "calories" in meal class TestSleepEndpoint: """睡眠记录接口测试""" def test_get_sleep(self, client, populated_db): """测试获取睡眠记录""" response = client.get("/api/sleep") assert response.status_code == 200 data = response.json() assert len(data) >= 1 def test_sleep_structure(self, client, populated_db): """测试数据结构""" response = client.get("/api/sleep") data = response.json() record = data[0] assert "duration" in record assert "quality" in record class TestWeightEndpoint: """体重记录接口测试""" def test_get_weight(self, client, populated_db): """测试获取体重记录""" response = client.get("/api/weight") assert response.status_code == 200 data = response.json() assert len(data) >= 1 def test_weight_default_days(self, client, populated_db): """测试默认天数""" response = client.get("/api/weight") assert response.status_code == 200 def test_weight_structure(self, client, populated_db): """测试数据结构""" response = client.get("/api/weight") data = response.json() record = data[0] assert "weight_kg" in record assert record["weight_kg"] == 72.5 class TestCORS: """CORS 测试""" def test_cors_headers(self, client): """测试 CORS 头""" response = client.options("/api/config") # FastAPI with CORSMiddleware should handle OPTIONS assert response.status_code in [200, 405] class TestErrorHandling: """错误处理测试""" def test_not_found(self, client): """测试 404""" response = client.get("/api/nonexistent") assert response.status_code == 404 def test_invalid_query_params(self, client): """测试无效参数""" response = client.get("/api/exercises?days=abc") assert response.status_code == 422