Compare commits
107 Commits
main
..
4bddd74c8e
| Author | SHA1 | Date | |
|---|---|---|---|
|
4bddd74c8e
|
|||
|
daf7218f87
|
|||
|
aee22755b7
|
|||
|
66151ad3c3
|
|||
|
a93589ce81
|
|||
|
9cdc8b86de
|
|||
|
e5a6db99b1
|
|||
|
6f0d143c6e
|
|||
|
b61c6d1613
|
|||
|
f23455c481
|
|||
|
c7233e6917
|
|||
|
f579868d44
|
|||
|
3c1535ed89
|
|||
|
0ee388c420
|
|||
|
91e48f5239
|
|||
|
291b4aabd6
|
|||
|
7bb600f898
|
|||
|
340aa6b9af
|
|||
|
8e31fc3cb5
|
|||
|
83dd6cbf19
|
|||
|
164fb212ef
|
|||
|
4d1b84661a
|
|||
|
eae444df77
|
|||
|
97f479a5cc
|
|||
|
89164a031c
|
|||
|
4311e97d24
|
|||
|
cf0c2153db
|
|||
|
ed03d3251e
|
|||
|
ffecb9e591
|
|||
|
45105e0b77
|
|||
|
e7ba79e49c
|
|||
|
f528d832c5
|
|||
|
6fc2133238
|
|||
|
f33dddfb4c
|
|||
|
5fa210cc8e
|
|||
|
7bf6d1f472
|
|||
|
f105ffa677
|
|||
|
c49042e242
|
|||
|
deb2f64ea7
|
|||
|
3fd3021d6e
|
|||
|
2727c053bb
|
|||
|
e0bf8cda01
|
|||
|
8544efb5bf
|
|||
|
1e0cca1ef3
|
|||
|
9cf69e2c7e
|
|||
|
9b66718c29
|
|||
|
72ebe1c59f
|
|||
|
0262ca8bf6
|
|||
|
d65ccf7dc2
|
|||
|
16b02451d0
|
|||
|
98ebb1600f
|
|||
|
933b4cb5a6
|
|||
|
a4189ddeb8
|
|||
|
3ad336caad
|
|||
|
3b2c882bb1
|
|||
|
a5dbb2765f
|
|||
|
d93aec1443
|
|||
|
33332439ac
|
|||
|
2cf1ac3237
|
|||
|
8ecb700316
|
|||
|
37f9102d80
|
|||
|
f7e491f1e8
|
|||
|
68699f35ec
|
|||
|
917c371529
|
|||
|
ecf95bd23d
|
|||
|
e6a22f98f8
|
|||
|
1e0aebfbfd
|
|||
|
9eaafe4649
|
|||
|
9fc5da3a54
|
|||
|
9b8f91ab19
|
|||
|
1def615b9e
|
|||
|
1078fdec8b
|
|||
|
ec5cf52ce1
|
|||
|
d62159e57c
|
|||
|
4784e55af8
|
|||
|
e53c0b854f
|
|||
|
a6313eed14
|
|||
|
05a55d2288
|
|||
|
b091512c7d
|
|||
|
2c8b217e1f
|
|||
|
e7c31c3ece
|
|||
|
4e1158f6b5
|
|||
|
874e8b8958
|
|||
|
b62cf74e2a
|
|||
|
d29b1d9772
|
|||
|
62d87efb66
|
|||
|
a2ecd6aa85
|
|||
|
877ef2fe30
|
|||
|
9e04184b40
|
|||
|
2014689c5a
|
|||
|
7282fedebe
|
|||
|
e47ceca19a
|
|||
|
f2945e12d9
|
|||
|
7552c059ef
|
|||
|
ec477a2fa7
|
|||
|
21d927f488
|
|||
|
72bea62663
|
|||
|
f7f2ab5a3e
|
|||
|
6e7131b84a
|
|||
|
1e4ba3bb93
|
|||
|
00a5b736f9
|
|||
|
75d0d096df
|
|||
|
02ee0c219f
|
|||
|
b6f1dd8942
|
|||
|
a5853d50fc
|
|||
|
5ee9ac05f0
|
|||
|
7b9cadee9d
|
@@ -88,6 +88,8 @@ jobs:
|
|||||||
context: ./backend
|
context: ./backend
|
||||||
push: false
|
push: false
|
||||||
tags: codetutor-backend:test
|
tags: codetutor-backend:test
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
- name: Build frontend image
|
- name: Build frontend image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
@@ -95,3 +97,5 @@ jobs:
|
|||||||
context: ./frontend
|
context: ./frontend
|
||||||
push: false
|
push: false
|
||||||
tags: codetutor-frontend:test
|
tags: codetutor-frontend:test
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|||||||
@@ -177,92 +177,3 @@ async def test_get_stats(client: AsyncClient, db_session: AsyncSession) -> None:
|
|||||||
assert data["by_difficulty"]["easy"] == 1
|
assert data["by_difficulty"]["easy"] == 1
|
||||||
assert len(data["by_category"]) == 1
|
assert len(data["by_category"]) == 1
|
||||||
assert len(data["by_pattern"]) == 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
|
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:
|
class TestStatsService:
|
||||||
async def test_get_stats(self, db_session: AsyncSession) -> None:
|
async def test_get_stats(self, db_session: AsyncSession) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user