285 lines
8.0 KiB
Python
285 lines
8.0 KiB
Python
|
|
"""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
|