206 lines
6.6 KiB
Python
206 lines
6.6 KiB
Python
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
def test_mila_dashboard_endpoint(client: TestClient):
|
|
resp = client.get("/duckdb-api/v1/mila/dashboard")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "ok"
|
|
assert data["metadata"]["subject"] == "mila"
|
|
assert "live_status" in data
|
|
assert "today" in data
|
|
assert "growth" in data
|
|
|
|
|
|
def test_moritz_dashboard_endpoint(client: TestClient):
|
|
resp = client.get("/duckdb-api/v1/moritz/dashboard")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["status"] == "ok"
|
|
assert data["metadata"]["subject"] == "moritz"
|
|
assert "today" in data
|
|
assert "yesterday" in data
|
|
assert "seven_days" in data
|
|
assert "thirty_days" in data
|
|
|
|
|
|
def test_healthz_and_status_endpoints(client: TestClient):
|
|
resp_h = client.get("/healthz")
|
|
assert resp_h.status_code == 200
|
|
assert resp_h.json()["engine_ready"] is True
|
|
|
|
resp_s = client.get("/duckdb-api/v1/status")
|
|
assert resp_s.status_code == 200
|
|
data_s = resp_s.json()
|
|
assert "status" in data_s
|
|
assert "table_counts" in data_s
|
|
assert data_s["table_counts"]["sleep"] > 0
|
|
|
|
|
|
def test_mila_history_includes_diaper_only_days(client: TestClient):
|
|
# Log a diaper change for a specific day without sleep
|
|
client.post("/duckdb-api/v1/events", json={
|
|
"kind": "diaper.change",
|
|
"subject": "mila",
|
|
"start": "2026-10-12T12:00:00+02:00",
|
|
"payload": {"pee": True, "poo": True},
|
|
})
|
|
resp = client.get("/duckdb-api/v1/mila/dashboard")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
history = data["history"]
|
|
dates = [h["date"] for h in history]
|
|
assert "2026-10-12" in dates
|
|
|
|
|
|
def test_moritz_seven_days_includes_garmin_days(client: TestClient):
|
|
resp = client.get("/duckdb-api/v1/moritz/dashboard")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
days = data["seven_days"]["days"]
|
|
assert len(days) > 0
|
|
# Every returned day has a valid date and steps or expenditure
|
|
for d in days:
|
|
assert d["date"] is not None
|
|
assert "expenditure_kcal" in d
|
|
|
|
|
|
def test_mila_dashboard_parent_cockpit_and_norms(client: TestClient):
|
|
resp = client.get("/duckdb-api/v1/mila/dashboard")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
|
|
# Parent cockpit live fields
|
|
live = data["live_status"]
|
|
assert "is_sleeping" in live
|
|
assert "active_sleep_duration_str" in live
|
|
assert "wake_window_minutes" in live
|
|
assert "last_feed_since_str" in live
|
|
assert "last_diaper_since_str" in live
|
|
assert "last_stool_str" in live
|
|
|
|
# Science age norms
|
|
assert "age_norms" in data
|
|
norms = data["age_norms"]
|
|
assert norms["age_months"] >= 6.0
|
|
assert norms["wake_window_min_minutes"] == 135
|
|
assert norms["wake_window_max_minutes"] == 180
|
|
assert norms["feed_interval_min_minutes"] == 180
|
|
|
|
# Ensure IDs on items
|
|
today = data["today"]
|
|
for f in today["feedings"]:
|
|
assert "id" in f
|
|
for d in today["diapers"]:
|
|
assert "id" in d
|
|
|
|
|
|
def test_day_detail_and_summary_endpoints(client: TestClient):
|
|
# Test mila day detail
|
|
resp_mila = client.get("/duckdb-api/v1/mila/day/2026-10-12")
|
|
assert resp_mila.status_code == 200
|
|
assert resp_mila.json()["metadata"]["today_date"] == "2026-10-12"
|
|
|
|
# Test moritz day detail
|
|
resp_moritz = client.get("/duckdb-api/v1/moritz/day/2026-10-12")
|
|
assert resp_moritz.status_code == 200
|
|
assert "today" in resp_moritz.json()
|
|
|
|
# Test summary
|
|
resp_sum = client.get("/duckdb-api/v1/summary/mila?date=2026-10-12")
|
|
assert resp_sum.status_code == 200
|
|
sum_data = resp_sum.json()
|
|
assert sum_data["subject"] == "mila"
|
|
assert sum_data["date"] == "2026-10-12"
|
|
assert "nutrition" in sum_data
|
|
|
|
|
|
def test_entities_and_places_endpoints(client: TestClient):
|
|
# Put and list entity
|
|
client.post("/duckdb-api/v1/entities", json={
|
|
"collection": "places",
|
|
"record": {
|
|
"id": "test-biergarten",
|
|
"name": "Test Biergarten",
|
|
"category": "biergarten",
|
|
"lat": 49.02,
|
|
"lon": 12.09,
|
|
}
|
|
})
|
|
|
|
resp_places = client.get("/duckdb-api/v1/entities/places")
|
|
assert resp_places.status_code == 200
|
|
places = resp_places.json()
|
|
assert any(p["id"] == "test-biergarten" for p in places)
|
|
|
|
# Places near search
|
|
resp_near = client.get("/duckdb-api/v1/places/near?lat=49.02&lon=12.09&radius_m=500")
|
|
assert resp_near.status_code == 200
|
|
near = resp_near.json()
|
|
assert len(near) > 0
|
|
assert near[0]["id"] == "test-biergarten"
|
|
|
|
|
|
def test_garmin_sync_and_duplicate_guard(client: TestClient):
|
|
# Duplicate guard test
|
|
post_data = {
|
|
"kind": "feeding.bottle",
|
|
"subject": "mila",
|
|
"start": "2026-10-12T10:00:00+02:00",
|
|
"payload": {"consumed_ml": 180, "milk_type": "pre"},
|
|
}
|
|
r1 = client.post("/duckdb-api/v1/events", json=post_data)
|
|
assert r1.status_code == 200
|
|
assert r1.json()["status"] == "success"
|
|
assert "feedback" in r1.json()
|
|
|
|
# Second post within 15min should be duplicate_skipped
|
|
r2 = client.post("/duckdb-api/v1/events", json=post_data)
|
|
assert r2.status_code == 200
|
|
assert r2.json()["status"] == "duplicate_skipped"
|
|
|
|
# Garmin sync test
|
|
garmin_payload = {
|
|
"daily": [{
|
|
"calendarDate": "2026-10-15",
|
|
"totalKilocalories": 2400,
|
|
"activeKilocalories": 600,
|
|
"bmrKilocalories": 1800,
|
|
"totalSteps": 11000,
|
|
"restingHeartRate": 52,
|
|
}],
|
|
"sleep": [],
|
|
"activities": [],
|
|
}
|
|
r_sync = client.post("/duckdb-api/v1/garmin/sync", json=garmin_payload)
|
|
assert r_sync.status_code == 200
|
|
assert r_sync.json()["status"] == "success"
|
|
assert r_sync.json()["synced_daily"] == 1
|
|
|
|
# Test alias support (daily_summary and sleep_data dicts)
|
|
garmin_alias_payload = {
|
|
"date": "2026-10-16",
|
|
"daily_summary": {
|
|
"calendarDate": "2026-10-16",
|
|
"totalKilocalories": 2550,
|
|
"activeKilocalories": 650,
|
|
"bmrKilocalories": 1900,
|
|
"totalSteps": 12000,
|
|
"restingHeartRate": 50,
|
|
},
|
|
"sleep_data": {
|
|
"dailySleepDTO": {
|
|
"calendarDate": "2026-10-16",
|
|
"sleepTimeSeconds": 28800,
|
|
}
|
|
},
|
|
"activities": [],
|
|
}
|
|
r_alias = client.post("/duckdb-api/v1/garmin/sync", json=garmin_alias_payload)
|
|
assert r_alias.status_code == 200
|
|
assert r_alias.json()["status"] == "success"
|
|
assert r_alias.json()["synced_daily"] == 1
|
|
assert r_alias.json()["synced_sleep"] == 1
|
|
|