vera-bot / tests /test_endpoints.py
Dov-tek
Restructure: rule-based decision engine + template slot-filling architecture
a1757c5
Raw
History Blame Contribute Delete
5.48 kB
"""
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!")