philverify-api / tests /test_api_endpoints.py
Ryan Christian D. Deniega
fix: cold start 502, favicon, verify state persistence
b1c84b5
"""
PhilVerify β€” HTTP Endpoint Integration Tests
Uses FastAPI TestClient (synchronous HTTPX transport β€” no running server needed).
Run: pytest tests/test_api_endpoints.py -v
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
import pytest
from fastapi.testclient import TestClient
from main import app
client = TestClient(app, raise_server_exceptions=False)
# ── Health ────────────────────────────────────────────────────────────────────
class TestHealth:
def test_health_returns_200(self):
res = client.get("/health")
assert res.status_code == 200
def test_health_has_status_key(self):
res = client.get("/health")
data = res.json()
assert "status" in data
# ── POST /verify/text ─────────────────────────────────────────────────────────
class TestVerifyText:
def test_valid_text_returns_200(self):
res = client.post("/verify/text", json={
"text": "DOH reports 500 new COVID-19 cases as vaccination drive continues in Metro Manila"
})
assert res.status_code == 200
def test_response_has_required_fields(self):
res = client.post("/verify/text", json={
"text": "The Supreme Court ruled on the petition filed by the opposition party in Manila."
})
data = res.json()
assert "verdict" in data
assert "confidence" in data
assert "final_score" in data
assert "layer1" in data
assert "layer2" in data
assert "entities" in data
def test_verdict_is_valid_enum(self):
res = client.post("/verify/text", json={
"text": "GRABE! Namatay daw ang tatlong tao sa bagong sakit na kumakalat sa Pilipinas!"
})
data = res.json()
assert data["verdict"] in ("Credible", "Unverified", "Likely Fake")
def test_final_score_in_range(self):
res = client.post("/verify/text", json={
"text": "Marcos signs executive order on agricultural modernization"
})
data = res.json()
assert 0.0 <= data["final_score"] <= 100.0
def test_too_short_text_returns_422(self):
res = client.post("/verify/text", json={"text": "Short"})
assert res.status_code == 422
def test_missing_text_field_returns_422(self):
res = client.post("/verify/text", json={})
assert res.status_code == 422
def test_empty_body_returns_422(self):
res = client.post("/verify/text")
assert res.status_code == 422
def test_layer1_has_confidence(self):
res = client.post("/verify/text", json={
"text": "PNP arrests 12 suspects in Bulacan drug bust according to official report"
})
data = res.json()
assert "confidence" in data["layer1"]
assert 0.0 <= data["layer1"]["confidence"] <= 100.0
def test_triggered_features_is_list(self):
res = client.post("/verify/text", json={
"text": "SHOCKING TRUTH: Bill Gates microchip found in COVID vaccine in Cebu!"
})
data = res.json()
assert isinstance(data["layer1"]["triggered_features"], list)
def test_entities_has_expected_keys(self):
res = client.post("/verify/text", json={
"text": "President Marcos signed a new policy in Manila about the AFP."
})
data = res.json()
entities = data["entities"]
assert "persons" in entities
assert "organizations" in entities
assert "locations" in entities
assert "dates" in entities
def test_language_field_present(self):
res = client.post("/verify/text", json={
"text": "Ang mga mamamayan ay nag-aalala sa bagong batas na isinusulong ng pangulo."
})
data = res.json()
assert data["language"] in ("Tagalog", "English", "Taglish", "Unknown")
# ── POST /verify/url ──────────────────────────────────────────────────────────
class TestVerifyUrl:
def test_invalid_url_returns_422(self):
res = client.post("/verify/url", json={"url": "not-a-url"})
assert res.status_code == 422
def test_missing_url_returns_422(self):
res = client.post("/verify/url", json={})
assert res.status_code == 422
def test_valid_url_format_accepted(self):
# A properly-formed URL passes schema validation (not 422 from Pydantic).
# The backend may return 400/503 if scraping fails β€” that's fine.
# The 422 case can occur when scraped text is empty (404 article) β€”
# acceptable; what we're guarding against is a schema-level 422 on a
# well-formed URL string (which would mean the Pydantic model is wrong).
res = client.post("/verify/url", json={"url": "https://rappler.com/fake-article-test"})
# Accept any status except a Pydantic schema validation failure on the URL itself
# (i.e., we accept 200, 400, 422 due to empty scrape, 503, etc.)
data = res.json()
if res.status_code == 422:
# Ensure it's the scraping/content 422, not a URL format issue
detail = str(data.get('detail', ''))
assert 'url' not in detail.lower() or 'text' in detail.lower(), \
f"Unexpected URL validation failure: {detail}"
# ── GET /history ──────────────────────────────────────────────────────────────
class TestHistory:
def test_history_returns_200(self):
res = client.get("/history")
assert res.status_code == 200
def test_history_response_shape(self):
res = client.get("/history")
data = res.json()
assert "total" in data
assert "entries" in data
assert isinstance(data["entries"], list)
def test_history_pagination_params(self):
res = client.get("/history?page=1&limit=5")
assert res.status_code == 200
def test_history_invalid_page_returns_422(self):
res = client.get("/history?page=0")
assert res.status_code == 422
def test_history_verdict_filter(self):
res = client.get("/history?verdict=Credible")
assert res.status_code == 200
def test_history_invalid_verdict_filter_returns_422(self):
res = client.get("/history?verdict=InvalidVerdict")
assert res.status_code == 422
def test_history_after_verification_contains_entry(self):
"""Verify that a submitted claim appears in history."""
client.post("/verify/text", json={
"text": "DOH reports 500 new COVID-19 cases as vaccination drive continues in Metro Manila"
})
res = client.get("/history?limit=50")
data = res.json()
# May not appear if only Firestore is configured β€” just check shape
assert isinstance(data["entries"], list)
# ── GET /trends ───────────────────────────────────────────────────────────────
class TestTrends:
def test_trends_returns_200(self):
res = client.get("/trends")
assert res.status_code == 200
def test_trends_response_shape(self):
res = client.get("/trends")
data = res.json()
assert "top_entities" in data
assert "top_topics" in data
assert "verdict_distribution" in data
assert "verdict_by_day" in data
def test_verdict_distribution_has_expected_keys(self):
res = client.get("/trends")
dist = res.json()["verdict_distribution"]
assert "Credible" in dist
assert "Unverified" in dist
assert "Likely Fake" in dist
def test_top_entities_is_list(self):
res = client.get("/trends")
assert isinstance(res.json()["top_entities"], list)
def test_trends_days_param(self):
res = client.get("/trends?days=30")
assert res.status_code == 200
def test_trends_days_out_of_range(self):
res = client.get("/trends?days=0")
assert res.status_code == 422
def test_trends_after_verification_updates_distribution(self):
"""Submit a fake-looking claim and confirm it is counted."""
client.post("/verify/text", json={
"text": "CONFIRMED: Philippines to become 51st state of the United States in 2026! Totoo ito!"
})
res = client.get("/trends")
dist = res.json()["verdict_distribution"]
total = sum(dist.values())
assert total >= 0 # At least zero β€” in-memory may be empty if Firestore active