Compare commits
107 Commits
main
..
407979f643
| Author | SHA1 | Date | |
|---|---|---|---|
|
407979f643
|
|||
|
ab2b5b065d
|
|||
|
01e971cd4e
|
|||
|
ed2991a092
|
|||
|
bb23c87f78
|
|||
|
935b26b0f7
|
|||
|
4f54ac29a3
|
|||
|
b576af260e
|
|||
|
3b4c242f8d
|
|||
|
2446d7d5e7
|
|||
|
520e0a5980
|
|||
|
47d75c32a9
|
|||
|
df31b08ee2
|
|||
|
a99ee0ea97
|
|||
|
e805ed9b05
|
|||
|
d25d71b7a6
|
|||
|
4a053e9972
|
|||
|
5573b7f176
|
|||
|
45cba588e9
|
|||
|
f986d6a2f9
|
|||
|
a4045dccc1
|
|||
|
a5d1694917
|
|||
|
d361a1b94f
|
|||
|
d480e9c356
|
|||
|
c30249de2f
|
|||
|
283d1b3848
|
|||
|
85235c7277
|
|||
|
36233e943d
|
|||
|
54c3ef373a
|
|||
|
e6ca65e039
|
|||
|
506d22a98f
|
|||
|
777da6c186
|
|||
|
241489f1bd
|
|||
|
2d581ab14f
|
|||
|
7f330c7cbb
|
|||
|
a322cb9a3b
|
|||
|
a1f301d11b
|
|||
|
a818e5bae7
|
|||
|
ed4342d003
|
|||
|
2929b8d569
|
|||
|
2f5e1ced7c
|
|||
|
2c86e66a0e
|
|||
|
71901d045a
|
|||
|
276df85bcc
|
|||
|
df611d26c3
|
|||
|
4b04248f57
|
|||
|
20da63926d
|
|||
|
323a1aa50e
|
|||
|
eb7c731af0
|
|||
|
ccc50c4c88
|
|||
|
2021de37b4
|
|||
|
192094e714
|
|||
|
d215538799
|
|||
|
f9740f3f69
|
|||
|
7ee30b7165
|
|||
|
85e5470794
|
|||
|
01bdde9b17
|
|||
|
b4acd5eae2
|
|||
|
079c776e70
|
|||
|
980b97290a
|
|||
|
e5f935909e
|
|||
|
7c7e2ec701
|
|||
|
e58f5f64ed
|
|||
|
403f3ccbea
|
|||
|
c1853224f2
|
|||
|
16026799f5
|
|||
|
fff2b6f97b
|
|||
|
45c100993a
|
|||
|
982793cb8e
|
|||
|
d0c26b204f
|
|||
|
ca585091f3
|
|||
|
d62f8799a4
|
|||
|
a83680cbc5
|
|||
|
17fa0d84ef
|
|||
|
e5b87f2332
|
|||
|
7abf819492
|
|||
|
3f86c63640
|
|||
|
280fed27d4
|
|||
|
0a27e6d7af
|
|||
|
21cc48674e
|
|||
|
9a59f6b6b6
|
|||
|
9630cb3eab
|
|||
|
907ea034a4
|
|||
|
32d539e735
|
|||
|
335db61306
|
|||
|
012ec62532
|
|||
|
c484a8d302
|
|||
|
352eba7d9d
|
|||
|
5df1abfb33
|
|||
|
9bd045acc2
|
|||
|
2262f14559
|
|||
|
71a60bc70b
|
|||
|
8caa9661bd
|
|||
|
6aed31f754
|
|||
|
cb17533d0b
|
|||
|
b45aae7b3b
|
|||
|
b8f6d0eb1c
|
|||
|
4483c60ba6
|
|||
|
43f9384721
|
|||
|
b1ba5b1346
|
|||
|
f49d096c96
|
|||
|
fb4d4078f5
|
|||
|
0033b9e5b2
|
|||
|
7ee9cbc14c
|
|||
|
cd76315bef
|
|||
|
302bf3af47
|
|||
|
41e2f7ba41
|
@@ -35,8 +35,6 @@ jobs:
|
||||
run: mypy src/
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
DATABASE_URL: "sqlite+aiosqlite:///:memory:"
|
||||
run: pytest --cov=src --cov-report=term-missing --cov-fail-under=80
|
||||
|
||||
frontend:
|
||||
@@ -63,7 +61,7 @@ jobs:
|
||||
run: npm run lint
|
||||
|
||||
- name: Type check
|
||||
run: npx tsc --noEmit
|
||||
run: tsc --noEmit
|
||||
|
||||
- name: Test
|
||||
run: npm test
|
||||
@@ -88,6 +86,8 @@ jobs:
|
||||
context: ./backend
|
||||
push: false
|
||||
tags: codetutor-backend:test
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Build frontend image
|
||||
uses: docker/build-push-action@v5
|
||||
@@ -95,3 +95,5 @@ jobs:
|
||||
context: ./frontend
|
||||
push: false
|
||||
tags: codetutor-frontend:test
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
@@ -75,3 +75,4 @@ frontend/tsconfig.tsbuildinfo
|
||||
# Local development
|
||||
notes/
|
||||
scripts/
|
||||
CLAUDE.md
|
||||
|
||||
@@ -177,92 +177,3 @@ async def test_get_stats(client: AsyncClient, db_session: AsyncSession) -> None:
|
||||
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
|
||||
|
||||
@@ -215,55 +215,6 @@ class TestPatternService:
|
||||
|
||||
assert result is None
|
||||
|
||||
async def test_get_tutorial(self, db_session: AsyncSession) -> None:
|
||||
related = Pattern(name="BFS", slug="bfs", description="Breadth-first search")
|
||||
pattern = Pattern(
|
||||
name="Two Pointers",
|
||||
slug="two-pointers",
|
||||
description="Two pointer technique",
|
||||
common_mistakes=[
|
||||
{"title": "Off by one", "description": "Wrong bounds", "fix": "Use <="}
|
||||
],
|
||||
variations=[{"name": "Fast/slow", "description": "Different speeds"}],
|
||||
related_patterns=["bfs"],
|
||||
visualization_examples=[{"id": "ex1", "title": "Basic", "code": "x=1", "steps": []}],
|
||||
)
|
||||
db_session.add_all([related, pattern])
|
||||
await db_session.flush()
|
||||
|
||||
q1 = Question(
|
||||
title="Easy Q", slug="easy-q", difficulty=Difficulty.EASY, description="Easy."
|
||||
)
|
||||
q2 = Question(title="Med Q", slug="med-q", difficulty=Difficulty.MEDIUM, description="Med.")
|
||||
q3 = Question(
|
||||
title="Hard Q", slug="hard-q", difficulty=Difficulty.HARD, description="Hard."
|
||||
)
|
||||
q1.patterns = [pattern]
|
||||
q2.patterns = [pattern]
|
||||
q3.patterns = [pattern]
|
||||
db_session.add_all([q1, q2, q3])
|
||||
await db_session.commit()
|
||||
|
||||
service = PatternService(db_session)
|
||||
result = await service.get_pattern_tutorial("two-pointers")
|
||||
|
||||
assert result is not None
|
||||
assert result.name == "Two Pointers"
|
||||
assert len(result.common_mistakes) == 1
|
||||
assert len(result.variations) == 1
|
||||
assert len(result.related_patterns) == 1
|
||||
assert result.related_patterns[0].slug == "bfs"
|
||||
assert len(result.learning_progression.warmup) == 1
|
||||
assert len(result.learning_progression.core) == 1
|
||||
assert len(result.learning_progression.challenge) == 1
|
||||
assert len(result.visualization_examples) == 1
|
||||
|
||||
async def test_get_tutorial_not_found(self, db_session: AsyncSession) -> None:
|
||||
service = PatternService(db_session)
|
||||
result = await service.get_pattern_tutorial("nonexistent")
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestStatsService:
|
||||
async def test_get_stats(self, db_session: AsyncSession) -> None:
|
||||
|
||||
Reference in New Issue
Block a user