Files
GarminHealthLab/backend/tests/conftest.py
ericwyuan 8e37e5a551 [阶段3.1] pytest 测试基建 + auth/health/analysis 单元测试 - 102 用例全绿
测试基建:
- pytest.ini / requirements-dev.txt
- tests/conftest.py: 每个用例独立 SQLite 库,提供
  db / app / client / user / auth / seed_health 六个 fixture

tests/test_auth.py (38 通过, 1 跳过):
- 密码哈希: 加盐唯一性、格式自描述、明文不入库、畸形哈希不抛异常
- 回归用例: 无 scrypt 的解释器上也能哈希(覆盖上一个 commit 的 bug)
- JWT: 过期/换密钥/篡改签名均拒绝
- 登录错误不区分"邮箱不存在"与"密码错误"(防用户枚举)
- require_auth: 缺失/畸形/过期 header 一律 401 而非 500

tests/test_health.py (30 通过):
- 日期范围上下界均为闭区间
- 数据按 user_id 隔离,查不到他人数据
- 重复 upsert 同一天不产生重复行
- 各指标端点正确剔除 NULL 行

tests/test_analysis.py (34 通过):
- 5 条建议规则的阈值边界逐条固化(8000 步 / 7 小时 / 50 压力 /
  65 静息心率 / 40 HRV)
- 均值按窗口计算而非逐日;只取最近 14 天
- 无睡眠记录的日子不被当作 0 拉低均值
- metric 名走白名单,SQL 注入串不生效
- 建议按 high/medium/low 排序

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-23 12:34:18 +08:00

99 lines
2.7 KiB
Python

"""
Shared pytest fixtures.
Environment must be configured BEFORE `config` is imported, because config.py
reads os.environ at import time. Each test then gets its own SQLite file so
tests never share state.
"""
import os
import sys
import tempfile
import pytest
_BACKEND_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, _BACKEND_DIR)
os.environ.setdefault("DB_TYPE", "sqlite")
os.environ.setdefault("JWT_SECRET", "test_secret_at_least_32_bytes_long_ok")
os.environ.setdefault("CORS_ORIGIN", "http://localhost:3000")
# Point at a throwaway path; the db_path fixture overrides it per test.
os.environ.setdefault(
"DATABASE_PATH", os.path.join(tempfile.mkdtemp(), "bootstrap.db")
)
import db as db_module # noqa: E402
from app import create_app # noqa: E402
@pytest.fixture
def db(tmp_path, monkeypatch):
"""A freshly initialized, isolated SQLite database for one test."""
path = str(tmp_path / "test.db")
monkeypatch.setattr(db_module, "SQLITE_PATH", path)
db_module.init_db()
return db_module
@pytest.fixture
def app(db):
application = create_app()
application.config.update(TESTING=True)
return application
@pytest.fixture
def client(app):
return app.test_client()
@pytest.fixture
def user(client):
"""A registered user: returns {id, email, token, password}."""
password = "secret123"
resp = client.post(
"/api/auth/register",
json={
"email": "tester@example.com",
"garminEmail": "gm@example.com",
"garminPassword": password,
},
)
assert resp.status_code == 201, resp.get_data(as_text=True)
body = resp.get_json()
return {**body, "password": password}
@pytest.fixture
def auth(user):
"""Authorization headers for the registered user."""
return {"Authorization": f"Bearer {user['token']}"}
@pytest.fixture
def seed_health(db, user):
"""Insert daily health rows. Returns the inserted records."""
def _seed(records):
for r in records:
db.execute(
"INSERT INTO health_data (id, user_id, date, steps, heart_rate, "
"heart_rate_variability, sleep_duration, sleep_quality, stress, "
"calories_burned) VALUES (?,?,?,?,?,?,?,?,?,?)",
[
f"{user['id']}-{r['date']}",
user["id"],
r["date"],
r.get("steps"),
r.get("heart_rate"),
r.get("hrv"),
r.get("sleep_duration"),
r.get("sleep_quality"),
r.get("stress"),
r.get("calories"),
],
)
return records
return _seed