IRIS-AI_DEMO / tests /test_api.py
Brajmovech's picture
feat: add validation gate to API endpoints with rate limiting and health check
95c7e7f
"""Integration tests for the Flask API validation endpoints.
Run with: python -m unittest tests.test_api -v
Slow tests (live yfinance) are marked; skip them with:
python -m unittest tests.test_api.TestAPIFast -v
"""
import sys
import os
import json
import unittest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
# Import the Flask app (IRIS_System init may fail in CI — that's fine)
from app import app as flask_app
class TestAPIFast(unittest.TestCase):
"""Tests that don't require live network access."""
def setUp(self):
flask_app.config["TESTING"] = True
self.client = flask_app.test_client()
def test_health_endpoint(self):
"""GET /api/health should return 200 with ticker_db_loaded=true."""
resp = self.client.get("/api/health")
self.assertEqual(resp.status_code, 200)
data = json.loads(resp.data)
self.assertIn("status", data)
self.assertEqual(data["status"], "ok")
self.assertTrue(data.get("ticker_db_loaded"), "Ticker DB should be loaded")
self.assertGreater(data.get("ticker_count", 0), 0)
def test_validate_missing_body(self):
"""POST /api/validate-ticker with no body should return valid=false."""
resp = self.client.post("/api/validate-ticker",
content_type="application/json",
data=json.dumps({}))
self.assertEqual(resp.status_code, 200)
data = json.loads(resp.data)
self.assertFalse(data.get("valid"))
def test_validate_invalid_format(self):
"""POST /api/validate-ticker with bad format should return valid=false immediately."""
resp = self.client.post("/api/validate-ticker",
content_type="application/json",
data=json.dumps({"ticker": "123ABC"}))
self.assertEqual(resp.status_code, 200)
data = json.loads(resp.data)
self.assertFalse(data.get("valid"))
self.assertIn("error", data)
def test_analyze_rejects_invalid_ticker(self):
"""GET /api/analyze with a clearly invalid ticker should return 422."""
resp = self.client.get("/api/analyze?ticker=XYZQW")
self.assertEqual(resp.status_code, 422)
data = json.loads(resp.data)
self.assertFalse(data.get("valid"))
self.assertIn("error", data)
class TestAPISlow(unittest.TestCase):
"""Tests that hit live yfinance — skip in CI with -k 'not slow'."""
def setUp(self):
flask_app.config["TESTING"] = True
self.client = flask_app.test_client()
def test_validate_valid_ticker(self):
"""POST /api/validate-ticker with AAPL should return valid=true."""
resp = self.client.post("/api/validate-ticker",
content_type="application/json",
data=json.dumps({"ticker": "AAPL"}))
self.assertEqual(resp.status_code, 200)
data = json.loads(resp.data)
self.assertTrue(data.get("valid"))
self.assertEqual(data.get("ticker"), "AAPL")
self.assertIn("company_name", data)
def test_validate_invalid_ticker(self):
"""POST /api/validate-ticker with XYZQW should return valid=false with error."""
resp = self.client.post("/api/validate-ticker",
content_type="application/json",
data=json.dumps({"ticker": "XYZQW"}))
self.assertEqual(resp.status_code, 200)
data = json.loads(resp.data)
self.assertFalse(data.get("valid"))
self.assertIn("error", data)
def test_analyze_accepts_valid_ticker(self):
"""GET /api/analyze with AAPL should NOT return 422 (validation must pass)."""
resp = self.client.get("/api/analyze?ticker=AAPL")
self.assertNotEqual(resp.status_code, 422,
"Validation gate should not reject AAPL")
if __name__ == "__main__":
unittest.main()