Spaces:
Running
Running
| """ | |
| End-to-end API tests using FastAPI TestClient. | |
| Tests the full flow: context push → tick → reply. | |
| """ | |
| import sys | |
| import os | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from fastapi.testclient import TestClient | |
| from app.main import app | |
| client = TestClient(app) | |
| def test_healthz(): | |
| r = client.get("/v1/healthz") | |
| assert r.status_code == 200 | |
| data = r.json() | |
| assert data["status"] == "ok" | |
| assert "contexts_loaded" in data | |
| print("PASS: healthz") | |
| def test_metadata(): | |
| r = client.get("/v1/metadata") | |
| assert r.status_code == 200 | |
| data = r.json() | |
| assert "team_name" in data | |
| assert "version" in data | |
| print("PASS: metadata") | |
| def test_context_push(): | |
| r = client.post("/v1/context", json={ | |
| "scope": "category", "context_id": "dentists", "version": 1, | |
| "payload": {"slug": "dentists", "voice": {"tone": "peer_clinical", "vocab_taboo": ["cure"]}, | |
| "peer_stats": {"avg_ctr": 0.03}, "digest": [], "offer_catalog": []}, | |
| }) | |
| assert r.status_code == 200 | |
| assert r.json()["accepted"] is True | |
| print("PASS: context push (category)") | |
| def test_context_stale_version(): | |
| # Push v2, then try v1 | |
| client.post("/v1/context", json={ | |
| "scope": "merchant", "context_id": "m_test", "version": 2, | |
| "payload": {"merchant_id": "m_test", "category_slug": "dentists", | |
| "identity": {"name": "Test", "owner_first_name": "T", "languages": ["en"]}, | |
| "offers": [], "signals": [], "performance": {}, "customer_aggregate": {}}, | |
| }) | |
| r = client.post("/v1/context", json={ | |
| "scope": "merchant", "context_id": "m_test", "version": 1, | |
| "payload": {}, | |
| }) | |
| assert r.json()["accepted"] is False | |
| assert r.json()["reason"] == "stale_version" | |
| print("PASS: stale version rejected") | |
| def test_context_invalid_scope(): | |
| r = client.post("/v1/context", json={ | |
| "scope": "invalid", "context_id": "x", "version": 1, "payload": {}, | |
| }) | |
| assert r.json()["accepted"] is False | |
| assert r.json()["reason"] == "invalid_scope" | |
| print("PASS: invalid scope rejected") | |
| def test_tick_empty(): | |
| r = client.post("/v1/tick", json={"now": "2026-04-26T10:00:00Z"}) | |
| assert r.status_code == 200 | |
| assert "actions" in r.json() | |
| print("PASS: empty tick") | |
| def test_full_flow(): | |
| """Push category + merchant + trigger, then tick, verify action.""" | |
| # Teardown first | |
| client.post("/v1/teardown") | |
| # Push category | |
| client.post("/v1/context", json={ | |
| "scope": "category", "context_id": "dentists", "version": 1, | |
| "payload": {"slug": "dentists", "voice": {"tone": "peer_clinical", "vocab_taboo": []}, | |
| "peer_stats": {"avg_ctr": 0.03}, | |
| "digest": [{"id": "d1", "title": "Test research", "source": "Test J", "trial_n": 50}], | |
| "offer_catalog": []}, | |
| }) | |
| # Push merchant | |
| client.post("/v1/context", json={ | |
| "scope": "merchant", "context_id": "m_001", "version": 1, | |
| "payload": {"merchant_id": "m_001", "category_slug": "dentists", | |
| "identity": {"name": "Test Clinic", "owner_first_name": "Doc", | |
| "languages": ["en"], "locality": "Test"}, | |
| "offers": [{"title": "Cleaning @ ₹299", "status": "active"}], | |
| "signals": [], "performance": {"calls": 10, "directions": 20}, | |
| "customer_aggregate": {"total_unique_ytd": 200}}, | |
| }) | |
| # Push trigger | |
| client.post("/v1/context", json={ | |
| "scope": "trigger", "context_id": "trg_001", "version": 1, | |
| "payload": {"id": "trg_001", "scope": "merchant", "kind": "research_digest", | |
| "source": "external", "merchant_id": "m_001", | |
| "payload": {"top_item_id": "d1"}, | |
| "urgency": 2, "suppression_key": "test:flow:1", | |
| "expires_at": "2030-01-01T00:00:00Z"}, | |
| }) | |
| # Tick | |
| r = client.post("/v1/tick", json={ | |
| "now": "2026-04-26T10:00:00Z", | |
| "available_triggers": ["trg_001"], | |
| }) | |
| data = r.json() | |
| assert len(data["actions"]) >= 1, f"Expected actions, got: {data}" | |
| action = data["actions"][0] | |
| assert action["body"] | |
| assert action["merchant_id"] == "m_001" | |
| print(f"PASS: full flow — body: {action['body'][:60]}...") | |
| # Reply | |
| r = client.post("/v1/reply", json={ | |
| "conversation_id": action["conversation_id"], | |
| "merchant_id": "m_001", | |
| "from_role": "merchant", | |
| "message": "Yes, send me the details", | |
| }) | |
| reply_data = r.json() | |
| assert reply_data["action"] == "send" | |
| print(f"PASS: reply -- action={reply_data.get('action', '')}") | |
| def test_teardown(): | |
| r = client.post("/v1/teardown") | |
| assert r.status_code == 200 | |
| assert r.json()["wiped"] is True | |
| # Verify contexts cleared | |
| h = client.get("/v1/healthz").json() | |
| total = sum(h["contexts_loaded"].values()) | |
| assert total == 0 | |
| print("PASS: teardown") | |
| def test_root(): | |
| r = client.get("/") | |
| assert r.status_code == 200 | |
| assert "Vera Bot" in r.json()["service"] | |
| print("PASS: root endpoint") | |
| if __name__ == "__main__": | |
| test_root() | |
| test_healthz() | |
| test_metadata() | |
| test_context_push() | |
| test_context_stale_version() | |
| test_context_invalid_scope() | |
| test_tick_empty() | |
| test_full_flow() | |
| test_teardown() | |
| print("\nAll endpoint tests passed!") | |