test(backend): add unit tests
This commit is contained in:
268
backend/tests/test_api.py
Normal file
268
backend/tests/test_api.py
Normal file
@@ -0,0 +1,268 @@
|
||||
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
|
||||
Reference in New Issue
Block a user