161 lines
6.0 KiB
Python
161 lines
6.0 KiB
Python
"""埋点健康度聚合:纯差分函数 + admin 端点。"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
from app.admin.repositories.analytics_health import _cn_day, _rates, diff_snapshots
|
|
|
|
|
|
def _row(device, epoch, event, ts, **cum) -> dict:
|
|
base = {"attempted": 0, "drop_capture": 0, "delivered": 0, "drop_undelivered": 0}
|
|
base.update(cum)
|
|
return {
|
|
"device_id": device, "epoch_id": epoch, "event": event,
|
|
"created_at": datetime(2026, 7, 1, ts, 0, tzinfo=timezone.utc),
|
|
"app_ver": "0.2.12(62)", "oem": "ColorOS", "os": "Android 14",
|
|
**base,
|
|
}
|
|
|
|
|
|
def test_diff_first_row_is_full_cumulative() -> None:
|
|
rows = [_row("d", "e", "video_play", 1, attempted=100, delivered=90)]
|
|
out = diff_snapshots(rows)
|
|
assert len(out) == 1
|
|
assert out[0]["d_attempted"] == 100
|
|
assert out[0]["d_delivered"] == 90
|
|
|
|
|
|
def test_diff_consecutive_delta() -> None:
|
|
rows = [
|
|
_row("d", "e", "video_play", 1, attempted=100, delivered=90),
|
|
_row("d", "e", "video_play", 2, attempted=150, delivered=140),
|
|
]
|
|
out = sorted(diff_snapshots(rows), key=lambda r: r["created_at"])
|
|
assert out[1]["d_attempted"] == 50
|
|
assert out[1]["d_delivered"] == 50
|
|
|
|
|
|
def test_diff_epoch_reset_new_partition() -> None:
|
|
rows = [
|
|
_row("d", "e1", "video_play", 1, attempted=100),
|
|
_row("d", "e2", "video_play", 2, attempted=5),
|
|
]
|
|
out = {(r["epoch_id"]): r["d_attempted"] for r in diff_snapshots(rows)}
|
|
assert out["e1"] == 100
|
|
assert out["e2"] == 5
|
|
|
|
|
|
def test_diff_clamps_negative_on_reorder() -> None:
|
|
rows = [
|
|
_row("d", "e", "video_play", 1, attempted=100),
|
|
_row("d", "e", "video_play", 2, attempted=80),
|
|
]
|
|
out = sorted(diff_snapshots(rows), key=lambda r: r["created_at"])
|
|
assert out[1]["d_attempted"] == 0
|
|
|
|
|
|
def test_rates_computes_both_formulas() -> None:
|
|
out = _rates({"attempted": 150, "drop_capture": 0, "delivered": 140, "drop_undelivered": 10})
|
|
assert out["track_success_rate"] == 1.0
|
|
assert out["report_success_rate"] == 140 / 150
|
|
|
|
|
|
def test_rates_zero_denominator_yields_none() -> None:
|
|
out = _rates({"attempted": 0, "drop_capture": 0, "delivered": 0, "drop_undelivered": 0})
|
|
assert out["track_success_rate"] is None
|
|
assert out["report_success_rate"] is None
|
|
|
|
|
|
def test_cn_day_beijing_boundary() -> None:
|
|
# UTC 15:59 → 北京 23:59 同日;UTC 16:00 → 北京 次日 00:00
|
|
assert _cn_day(datetime(2026, 7, 1, 15, 59, tzinfo=timezone.utc)) == "2026-07-01"
|
|
assert _cn_day(datetime(2026, 7, 1, 16, 0, tzinfo=timezone.utc)) == "2026-07-02"
|
|
|
|
|
|
def test_diff_same_timestamp_ordered_by_id() -> None:
|
|
ts = datetime(2026, 7, 1, 1, 0, tzinfo=timezone.utc)
|
|
# 相同 created_at,乱序传入(id=2 在前);应按 id 排序 → id=1(cum100) 在前
|
|
rows = [
|
|
{"id": 2, "device_id": "d", "epoch_id": "e", "event": "vp", "created_at": ts,
|
|
"app_ver": "v", "oem": "o", "os": "s",
|
|
"attempted": 150, "drop_capture": 0, "delivered": 0, "drop_undelivered": 0},
|
|
{"id": 1, "device_id": "d", "epoch_id": "e", "event": "vp", "created_at": ts,
|
|
"app_ver": "v", "oem": "o", "os": "s",
|
|
"attempted": 100, "drop_capture": 0, "delivered": 0, "drop_undelivered": 0},
|
|
]
|
|
out = diff_snapshots(rows)
|
|
assert out[0]["d_attempted"] == 100 # id=1 sorts first → its cumulative
|
|
assert out[1]["d_attempted"] == 50 # id=2 second → 150-100
|
|
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.admin.main import admin_app
|
|
from app.admin.repositories import admin_user as admin_repo
|
|
from app.db.session import SessionLocal
|
|
|
|
|
|
@pytest.fixture()
|
|
def admin_client() -> TestClient:
|
|
return TestClient(admin_app)
|
|
|
|
|
|
@pytest.fixture()
|
|
def admin_token() -> str:
|
|
db = SessionLocal()
|
|
try:
|
|
if admin_repo.get_by_username(db, "health_admin") is None:
|
|
admin_repo.create_admin(db, username="health_admin", password="pw", role="super_admin")
|
|
finally:
|
|
db.close()
|
|
c = TestClient(admin_app)
|
|
r = c.post("/admin/api/auth/login", json={"username": "health_admin", "password": "pw"})
|
|
return r.json()["access_token"]
|
|
|
|
|
|
def _auth(t: str) -> dict:
|
|
return {"Authorization": f"Bearer {t}"}
|
|
|
|
|
|
def _seed_two_snapshots(client: TestClient) -> None:
|
|
for attempted, delivered in ((100, 90), (150, 140)):
|
|
client.post("/api/v1/analytics/selfstat", json={
|
|
"device_id": "d-health", "epoch_id": "e-health",
|
|
"app_ver": "0.2.12(62)", "oem": "ColorOS", "os": "Android 14",
|
|
"events": [{"event": "video_play", "attempted": attempted,
|
|
"drop_capture": 0, "delivered": delivered, "drop_undelivered": 0}],
|
|
})
|
|
|
|
|
|
def test_health_overview_requires_auth(admin_client: TestClient) -> None:
|
|
r = admin_client.get("/admin/api/analytics-health/overview",
|
|
params={"date_from": "2026-07-01T00:00:00Z", "date_to": "2030-01-01T00:00:00Z"})
|
|
assert r.status_code == 401
|
|
|
|
|
|
def test_health_overview(client: TestClient, admin_client: TestClient, admin_token: str) -> None:
|
|
_seed_two_snapshots(client) # 播种走公开 app(ingest 端点在 app,不在 admin_app)
|
|
r = admin_client.get(
|
|
"/admin/api/analytics-health/overview",
|
|
params={"date_from": "2026-07-01T00:00:00Z", "date_to": "2030-01-01T00:00:00Z"},
|
|
headers=_auth(admin_token),
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
data = r.json()
|
|
# 两快照累计 100→150,差分后 attempted 总 150(首快照 100 + 增量 50)
|
|
assert data["attempted"] == 150
|
|
assert data["track_success_rate"] == 1.0
|
|
|
|
|
|
def test_health_breakdown(client: TestClient, admin_client: TestClient, admin_token: str) -> None:
|
|
_seed_two_snapshots(client) # 播种走公开 app
|
|
r = admin_client.get(
|
|
"/admin/api/analytics-health/breakdown",
|
|
params={"date_from": "2026-07-01T00:00:00Z", "date_to": "2030-01-01T00:00:00Z", "dim": "event"},
|
|
headers=_auth(admin_token),
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
rows = r.json()
|
|
assert any(row["key"] == "video_play" for row in rows)
|