| import json |
| import importlib |
| import types |
| import unittest |
| from pathlib import Path |
| from unittest.mock import patch |
| import sys |
|
|
|
|
| ROOT = Path(__file__).resolve().parents[1] |
| if str(ROOT) not in sys.path: |
| sys.path.insert(0, str(ROOT)) |
|
|
| |
| |
| dotenv_module = types.ModuleType("dotenv") |
| dotenv_module.load_dotenv = lambda *args, **kwargs: None |
| sys.modules.setdefault("dotenv", dotenv_module) |
|
|
| openai_module = types.ModuleType("openai") |
| openai_module.OpenAI = object |
| openai_module.APIConnectionError = type("APIConnectionError", (Exception,), {}) |
| openai_module.APIStatusError = type("APIStatusError", (Exception,), {}) |
| openai_module.APITimeoutError = type("APITimeoutError", (Exception,), {}) |
| sys.modules.setdefault("openai", openai_module) |
|
|
| pymongo_module = types.ModuleType("pymongo") |
| pymongo_module.MongoClient = object |
| pymongo_errors_module = types.ModuleType("pymongo.errors") |
| pymongo_collection_module = types.ModuleType("pymongo.collection") |
| pymongo_collection_module.Collection = object |
| pymongo_database_module = types.ModuleType("pymongo.database") |
| pymongo_database_module.Database = object |
| pymongo_errors_module.PyMongoError = type("PyMongoError", (Exception,), {}) |
| pymongo_errors_module.ServerSelectionTimeoutError = type( |
| "ServerSelectionTimeoutError", (Exception,), {} |
| ) |
| sys.modules.setdefault("pymongo", pymongo_module) |
| sys.modules.setdefault("pymongo.collection", pymongo_collection_module) |
| sys.modules.setdefault("pymongo.database", pymongo_database_module) |
| sys.modules.setdefault("pymongo.errors", pymongo_errors_module) |
|
|
|
|
| class FakeServer: |
| def __init__(self): |
| self.routes = [] |
| self.api_routes = {} |
|
|
| def _route(self, method, path=None, **kwargs): |
| def decorator(func): |
| self.routes.append((method, path, kwargs, func)) |
| return func |
|
|
| return decorator |
|
|
| def get(self, path, **kwargs): |
| return self._route("GET", path, **kwargs) |
|
|
| def post(self, path, **kwargs): |
| return self._route("POST", path, **kwargs) |
|
|
| def api(self, name): |
| def decorator(func): |
| self.api_routes[name] = func |
| return func |
|
|
| return decorator |
|
|
| def mount(self, *args, **kwargs): |
| return None |
|
|
|
|
| fastapi_module = types.ModuleType("fastapi") |
| fastapi_module.Body = lambda *args, **kwargs: None |
| fastapi_responses_module = types.ModuleType("fastapi.responses") |
| fastapi_responses_module.HTMLResponse = str |
| fastapi_staticfiles_module = types.ModuleType("fastapi.staticfiles") |
| fastapi_staticfiles_module.StaticFiles = lambda *args, **kwargs: object() |
| gradio_module = types.ModuleType("gradio") |
| gradio_module.Server = FakeServer |
| sys.modules.setdefault("fastapi", fastapi_module) |
| sys.modules.setdefault("fastapi.responses", fastapi_responses_module) |
| sys.modules.setdefault("fastapi.staticfiles", fastapi_staticfiles_module) |
| sys.modules.setdefault("gradio", gradio_module) |
|
|
| from core.api_handlers import handle_deck_critique |
|
|
|
|
| DECK_TEXT = """ |
| EventRadar AI |
| Problem: students miss hackathons because events are scattered across WhatsApp, LinkedIn, Luma, and club pages. |
| Solution: an AI event discovery assistant ranks events by skills, goals, location, and deadline urgency. |
| Users: college students and student founders. |
| Traction: tested with 80 students; 62 missed at least one relevant event last month. |
| Ask: mentor feedback and pilot introductions. |
| """ |
|
|
|
|
| class DeckCritiqueTests(unittest.TestCase): |
| def test_deck_critique_returns_local_fallback_instead_of_placeholder(self): |
| with patch( |
| "core.api_handlers.model_router.generate_deck_critique_response", |
| return_value={ |
| "ok": False, |
| "model_mode": "premium_nvidia", |
| "provider": "nvidia", |
| "content": "", |
| "error": "model unavailable", |
| }, |
| ): |
| result = handle_deck_critique({"deck_text": DECK_TEXT}) |
|
|
| self.assertTrue(result["ok"]) |
| self.assertNotEqual(result.get("status"), "not_implemented") |
| self.assertEqual(result["provider"], "local") |
| self.assertFalse(result["model_ok"]) |
| self.assertTrue(result["summary"]) |
| self.assertGreaterEqual(len(result["strengths"]), 1) |
| self.assertGreaterEqual(len(result["risks"]), 1) |
| self.assertGreaterEqual(len(result["recommended_fixes"]), 1) |
| self.assertTrue(result["next_pitch_question"].endswith("?")) |
|
|
| def test_deck_critique_requires_text_or_startup_context(self): |
| result = handle_deck_critique({}) |
|
|
| self.assertFalse(result["ok"]) |
| self.assertIn("deck_text", result["error"]) |
|
|
| def test_deck_critique_uses_model_json_when_available(self): |
| model_payload = { |
| "summary": "The deck has a focused student-founder wedge but needs sharper proof.", |
| "strengths": ["Clear student pain", "Specific initial traction"], |
| "risks": ["Business model is still vague"], |
| "recommended_fixes": ["Add one slide on who pays and why this is urgent"], |
| "next_pitch_question": "Who pays for this, and what evidence shows they will pay?", |
| } |
|
|
| with patch( |
| "core.api_handlers.model_router.generate_deck_critique_response", |
| return_value={ |
| "ok": True, |
| "model_mode": "premium_nvidia", |
| "provider": "nvidia", |
| "content": json.dumps(model_payload), |
| "error": None, |
| }, |
| create=True, |
| ): |
| result = handle_deck_critique( |
| {"deck_text": DECK_TEXT, "model_mode": "premium_nvidia"} |
| ) |
|
|
| self.assertTrue(result["ok"]) |
| self.assertTrue(result["model_ok"]) |
| self.assertEqual(result["provider"], "nvidia") |
| self.assertEqual(result["summary"], model_payload["summary"]) |
| self.assertEqual(result["recommended_fixes"], model_payload["recommended_fixes"]) |
|
|
| def test_deck_critique_is_registered_as_gradio_api(self): |
| pitchfight_app = importlib.import_module("app") |
|
|
| self.assertIn("deck_critique", pitchfight_app.app.api_routes) |
| with patch( |
| "app.handle_deck_critique", |
| return_value={"ok": True, "summary": "registered"}, |
| ) as handler: |
| result = pitchfight_app.app.api_routes["deck_critique"]({"deck_text": DECK_TEXT}) |
|
|
| handler.assert_called_once_with({"deck_text": DECK_TEXT}) |
| self.assertEqual(result["summary"], "registered") |
|
|
|
|
| if __name__ == "__main__": |
| unittest.main() |
|
|