"""设备档案上报端点 /api/v1/device/report + upsert 测试。""" from __future__ import annotations from sqlalchemy import select from app.core.security import create_token from app.db.session import SessionLocal from app.models.device import Device from app.models.user import User def _payload(**over) -> dict: base = { "device_id": "dev-report-1", "platform": "android", "oem": "Xiaomi", "model": "PJF110", "os_version": "Android 14", "app_version": "0.2.12(62)", "channel": "yingyongbao", "screen": "1080x2400", "network": "wifi", "timezone": "Asia/Shanghai", "is_emulator": False, "latitude": 31.23, "longitude": 121.47, } base.update(over) return base def _get(device_id: str) -> Device | None: with SessionLocal() as db: return db.execute( select(Device).where(Device.device_id == device_id) ).scalar_one_or_none() def test_report_creates_device_as_guest(client) -> None: r = client.post("/api/v1/device/report", json=_payload(device_id="dev-guest")) assert r.status_code == 200, r.text assert r.json()["ok"] is True d = _get("dev-guest") assert d is not None assert d.user_id is None # 游客态:未绑用户 assert d.oem == "Xiaomi" assert d.is_emulator is False assert d.latitude == 31.23 assert d.last_active_at is not None def test_report_binds_user_when_authed(client) -> None: with SessionLocal() as db: u = User(phone="13800000001", username="20000000001") db.add(u) db.commit() uid = u.id token, _ = create_token(user_id=uid, token_type="access") r = client.post( "/api/v1/device/report", json=_payload(device_id="dev-user"), headers={"Authorization": f"Bearer {token}"}, ) assert r.status_code == 200, r.text assert _get("dev-user").user_id == uid def test_report_clears_location_when_absent(client) -> None: # 首次带位置 client.post( "/api/v1/device/report", json=_payload(device_id="dev-loc", latitude=10.0, longitude=20.0), ) assert _get("dev-loc").latitude == 10.0 # 再次上报拿不到位置(lat/lng=None)→ 必须清空,不能保留旧坐标 r = client.post( "/api/v1/device/report", json=_payload(device_id="dev-loc", latitude=None, longitude=None, oem="HONOR"), ) assert r.status_code == 200, r.text d = _get("dev-loc") assert d.latitude is None # 位置以实际为准 → 清空 assert d.longitude is None assert d.oem == "HONOR" # 设备信息正常更新 def test_report_sticky_fields_not_wiped_by_missing(client) -> None: # 首次全量 client.post("/api/v1/device/report", json=_payload(device_id="dev-sticky")) # 再次只带 device_id + 位置(不带 oem/model)→ 设备信息保留旧值(非空才覆盖) client.post( "/api/v1/device/report", json={"device_id": "dev-sticky", "latitude": 1.0, "longitude": 2.0}, ) d = _get("dev-sticky") assert d.oem == "Xiaomi" # 未被漏带的 None 冲掉 assert d.model == "PJF110" assert d.latitude == 1.0 # 位置按本次