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