from typing import Any from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession from src.models import Category, Difficulty, Explanation, Pattern, Question, Solution SampleData = dict[str, Any] async def create_sample_data(db_session: AsyncSession) -> SampleData: category = Category(name="Arrays", slug="arrays", description="Array problems") db_session.add(category) pattern = Pattern( name="Two Pointers", slug="two-pointers", description="Two pointer technique", ) db_session.add(pattern) await db_session.flush() question = Question( title="Two Sum", slug="two-sum", difficulty=Difficulty.EASY, description="Find two numbers that add up to target.", leetcode_id=1, leetcode_url="https://leetcode.com/problems/two-sum/", ) question.categories = [category] question.patterns = [pattern] db_session.add(question) await db_session.flush() explanation = Explanation( question_id=question.id, approach="Use a hash map.", intuition="Store seen values for O(1) lookup.", time_complexity="O(n)", space_complexity="O(n)", ) db_session.add(explanation) solution = Solution( question_id=question.id, approach_name="Hash Map", code="def two_sum(nums, target): pass", is_optimal=True, ) db_session.add(solution) await db_session.commit() return {"question": question, "category": category, "pattern": pattern} async def test_health_check(client: AsyncClient) -> None: response = await client.get("/api/health") assert response.status_code == 200 assert response.json() == {"status": "healthy"} async def test_list_questions_empty(client: AsyncClient) -> None: response = await client.get("/api/questions") assert response.status_code == 200 data = response.json() assert data["items"] == [] assert data["total"] == 0 async def test_list_questions(client: AsyncClient, db_session: AsyncSession) -> None: await create_sample_data(db_session) response = await client.get("/api/questions") assert response.status_code == 200 data = response.json() assert data["total"] == 1 assert len(data["items"]) == 1 assert data["items"][0]["title"] == "Two Sum" assert data["items"][0]["difficulty"] == "easy" async def test_list_questions_filter_difficulty( client: AsyncClient, db_session: AsyncSession ) -> None: await create_sample_data(db_session) response = await client.get("/api/questions?difficulty=easy") assert response.status_code == 200 assert response.json()["total"] == 1 response = await client.get("/api/questions?difficulty=hard") assert response.status_code == 200 assert response.json()["total"] == 0 async def test_list_questions_invalid_difficulty(client: AsyncClient) -> None: response = await client.get("/api/questions?difficulty=invalid") assert response.status_code == 400 async def test_get_question(client: AsyncClient, db_session: AsyncSession) -> None: await create_sample_data(db_session) response = await client.get("/api/questions/two-sum") assert response.status_code == 200 data = response.json() assert data["title"] == "Two Sum" assert data["explanation"]["time_complexity"] == "O(n)" assert len(data["solutions"]) == 1 async def test_get_question_not_found(client: AsyncClient) -> None: response = await client.get("/api/questions/nonexistent") assert response.status_code == 404 async def test_list_categories_empty(client: AsyncClient) -> None: response = await client.get("/api/categories") assert response.status_code == 200 assert response.json()["items"] == [] async def test_list_categories(client: AsyncClient, db_session: AsyncSession) -> None: await create_sample_data(db_session) response = await client.get("/api/categories") assert response.status_code == 200 data = response.json() assert len(data["items"]) == 1 assert data["items"][0]["name"] == "Arrays" assert data["items"][0]["question_count"] == 1 async def test_list_patterns_empty(client: AsyncClient) -> None: response = await client.get("/api/patterns") assert response.status_code == 200 assert response.json()["items"] == [] async def test_list_patterns(client: AsyncClient, db_session: AsyncSession) -> None: await create_sample_data(db_session) response = await client.get("/api/patterns") assert response.status_code == 200 data = response.json() assert len(data["items"]) == 1 assert data["items"][0]["name"] == "Two Pointers" async def test_get_pattern(client: AsyncClient, db_session: AsyncSession) -> None: await create_sample_data(db_session) response = await client.get("/api/patterns/two-pointers") assert response.status_code == 200 data = response.json() assert data["name"] == "Two Pointers" assert data["question_count"] == 1 async def test_get_pattern_not_found(client: AsyncClient) -> None: response = await client.get("/api/patterns/nonexistent") assert response.status_code == 404 async def test_get_stats_empty(client: AsyncClient) -> None: response = await client.get("/api/stats") assert response.status_code == 200 data = response.json() assert data["total_questions"] == 0 assert data["by_difficulty"]["easy"] == 0 async def test_get_stats(client: AsyncClient, db_session: AsyncSession) -> None: await create_sample_data(db_session) response = await client.get("/api/stats") assert response.status_code == 200 data = response.json() assert data["total_questions"] == 1 assert data["by_difficulty"]["easy"] == 1 assert len(data["by_category"]) == 1 assert len(data["by_pattern"]) == 1 async def test_get_pattern_tutorial(client: AsyncClient, db_session: AsyncSession) -> None: await create_sample_data(db_session) response = await client.get("/api/patterns/two-pointers/tutorial") assert response.status_code == 200 data = response.json() assert data["name"] == "Two Pointers" assert data["learning_progression"]["warmup"][0]["title"] == "Two Sum" assert len(data["learning_progression"]["core"]) == 0 async def test_get_pattern_tutorial_not_found(client: AsyncClient) -> None: response = await client.get("/api/patterns/nonexistent/tutorial") assert response.status_code == 404 async def test_submit_passing(client: AsyncClient, db_session: AsyncSession) -> None: data = await create_sample_data(db_session) q = data["question"] q.test_cases = { "visible": [{"input": {"nums": [2, 7], "target": 9}, "expected": [0, 1]}], "hidden": [{"input": {"nums": [3, 2, 4], "target": 6}, "expected": [1, 2]}], } await db_session.commit() response = await client.post( "/api/questions/two-sum/submit", json={ "code": "def solve(nums, t): pass", "hidden_outputs": [{"test_id": 1, "output": [1, 2]}], }, ) assert response.status_code == 200 result = response.json() assert result["passed"] is True assert result["total_passed"] == 1 async def test_submit_wrong_output(client: AsyncClient, db_session: AsyncSession) -> None: data = await create_sample_data(db_session) data["question"].test_cases = { "visible": [], "hidden": [{"input": {"nums": [1, 2], "target": 3}, "expected": [0, 1]}], } await db_session.commit() response = await client.post( "/api/questions/two-sum/submit", json={ "code": "def solve(nums, t): pass", "hidden_outputs": [{"test_id": 0, "output": [9, 9]}], }, ) assert response.status_code == 200 assert response.json()["passed"] is False async def test_submit_missing_output(client: AsyncClient, db_session: AsyncSession) -> None: data = await create_sample_data(db_session) data["question"].test_cases = { "visible": [], "hidden": [{"input": {"nums": [1, 2], "target": 3}, "expected": [0, 1]}], } await db_session.commit() response = await client.post( "/api/questions/two-sum/submit", json={"code": "def solve(nums, t): pass", "hidden_outputs": []}, ) assert response.status_code == 200 assert response.json()["hidden_results"][0]["error"] == "No output provided" async def test_submit_no_test_cases(client: AsyncClient, db_session: AsyncSession) -> None: await create_sample_data(db_session) response = await client.post( "/api/questions/two-sum/submit", json={"code": "pass", "hidden_outputs": []}, ) assert response.status_code == 400 async def test_submit_not_found(client: AsyncClient) -> None: response = await client.post( "/api/questions/nonexistent/submit", json={"code": "pass", "hidden_outputs": []}, ) assert response.status_code == 404