Spaces:
Running
Running
| """ | |
| Smoke tests for the Flask app routes. | |
| We mock init_model + predict_qa so tests run in milliseconds without | |
| actually loading BERT. This verifies routing, input validation, DB | |
| persistence, and error handling. | |
| """ | |
| from unittest.mock import patch | |
| import pytest | |
| from src import config, db | |
| def client(tmp_path, monkeypatch): | |
| # Isolate DB to a temp file | |
| monkeypatch.setattr(config, "DB_PATH", str(tmp_path / "test.db")) | |
| monkeypatch.setattr(db, "_initialized", False) | |
| # Disable rate limiting for deterministic tests | |
| monkeypatch.setattr(config, "RATE_LIMIT_ENABLED", False) | |
| # Disable warmup so init_model returns immediately | |
| monkeypatch.setattr(config, "WARMUP_ON_START", False) | |
| # Stub out the model entirely — zero BERT in tests | |
| with patch("src.app.init_model") as mock_init, \ | |
| patch("src.app.predict_qa") as mock_predict: | |
| mock_init.return_value = None | |
| mock_predict.return_value = { | |
| "answer": "5000 mAh", | |
| "confidence": 0.87, | |
| "confidence_pct": "87.0%", | |
| "confidence_level": "high", | |
| "answer_start_char": 10, | |
| "answer_end_char": 18, | |
| "context_used": "Battery: 5000 mAh capacity.", | |
| "tokens": [], | |
| "num_tokens": 0, | |
| "inference_time_ms": 42, | |
| } | |
| from src.app import create_app | |
| app = create_app() | |
| app.config["TESTING"] = True | |
| with app.test_client() as c: | |
| yield c | |
| def test_healthz(client): | |
| resp = client.get("/healthz") | |
| assert resp.status_code == 200 | |
| body = resp.get_json() | |
| assert body["status"] == "ok" | |
| assert "model" in body | |
| def test_index_renders(client): | |
| resp = client.get("/") | |
| assert resp.status_code == 200 | |
| assert b"E-Commerce Product QA" in resp.data | |
| def test_predict_requires_both_fields(client): | |
| resp = client.post("/api/predict", json={"question": "What?"}) | |
| assert resp.status_code == 400 | |
| assert "required" in resp.get_json()["error"].lower() | |
| def test_predict_rejects_short_context(client): | |
| resp = client.post("/api/predict", json={ | |
| "question": "What is the battery?", | |
| "context": "too short", | |
| }) | |
| assert resp.status_code == 400 | |
| def test_predict_success_and_persists(client): | |
| resp = client.post("/api/predict", json={ | |
| "question": "What is the battery?", | |
| "context": "Battery: 5000 mAh capacity. Long enough context here to pass validation.", | |
| "source_url": "https://example.com/x", | |
| "source_type": "amazon", | |
| "product_title": "Phone", | |
| }) | |
| assert resp.status_code == 200 | |
| body = resp.get_json() | |
| assert body["answer"] == "5000 mAh" | |
| assert body["confidence_level"] == "high" | |
| assert "history_id" in body | |
| # Verify persistence | |
| hist = client.get("/api/history").get_json() | |
| assert len(hist["items"]) == 1 | |
| assert hist["items"][0]["product_title"] == "Phone" | |
| def test_scrape_requires_url(client): | |
| resp = client.post("/api/scrape", json={}) | |
| assert resp.status_code == 400 | |
| def test_history_delete_and_clear(client): | |
| # Create an entry via predict | |
| client.post("/api/predict", json={ | |
| "question": "Q?", | |
| "context": "A context that is sufficiently long to pass validation cleanly.", | |
| }) | |
| hist = client.get("/api/history").get_json() | |
| entry_id = hist["items"][0]["id"] | |
| # Delete one | |
| resp = client.delete(f"/api/history/{entry_id}") | |
| assert resp.status_code == 200 | |
| assert resp.get_json()["deleted"] is True | |
| # Delete non-existent | |
| resp = client.delete("/api/history/99999") | |
| assert resp.status_code == 404 | |
| # Clear all | |
| client.post("/api/predict", json={ | |
| "question": "Q2?", | |
| "context": "Another context long enough to satisfy the length check cleanly.", | |
| }) | |
| resp = client.delete("/api/history") | |
| assert resp.status_code == 200 | |
| assert resp.get_json()["cleared"] >= 1 | |
| if __name__ == "__main__": | |
| pytest.main([__file__, "-v"]) | |