feat(family): deploy Single-Writer DuckDB backend, Mila parent cockpit & feeding analytics, and Moritz macro tracker
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
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
|
||||
Reference in New Issue
Block a user