Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions app/api/schema/exercise.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,20 @@ class ExerciseCreate(BaseModel):

class ExerciseRead(ExerciseCreate):
id: int


class SystemState(BaseModel):
registers: dict[str, int]
memory: dict[int, int]


class TestCaseCreate(BaseModel):
title: str
precondition: SystemState
postcondition: SystemState
user_input: list[str]
expected_output: list[str]


class TestCaseRead(TestCaseCreate):
id: int
25 changes: 23 additions & 2 deletions app/api/v1/exercise.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.schema.exercise import ExerciseRead, ExerciseCreate
from app.api.schema.exercise import ExerciseRead, ExerciseCreate, TestCaseRead, TestCaseCreate
from app.db.database import get_session
from app.db.model import Tan
from app.db.model.exercise import Exercise, ExerciseProgress, Competition
from app.db.model.exercise import Exercise, ExerciseProgress, Competition, TestCase

router = APIRouter(
prefix="/exercise",
Expand Down Expand Up @@ -121,3 +121,24 @@ async def create_exercise(new_exercise: ExerciseCreate, session: AsyncSession =
await session.refresh(exercise)

return ExerciseRead(**exercise.to_dict())


@router.post("/{exercise_id}/test-case", response_model=TestCaseRead)
async def create_test_case(exercise_id: int, new_test_case: TestCaseCreate,
session: AsyncSession = Depends(get_session)) -> TestCaseRead:
test_case = TestCase(exercise_id=exercise_id, **new_test_case.model_dump())
session.add(test_case)
await session.commit()

await session.refresh(test_case)

return TestCaseRead(**test_case.to_dict())


@router.get("/{exercise_id}/test-case", response_model=list[TestCaseRead], status_code=status.HTTP_200_OK)
async def get_test_cases(exercise_id: int, session: AsyncSession = Depends(get_session)) -> list[TestCaseRead]:
statement = select(TestCase).where(TestCase.exercise_id == exercise_id)
result = await session.execute(statement)
test_cases = result.scalars().all()

return [TestCaseRead(**case.to_dict()) for case in test_cases]
2 changes: 1 addition & 1 deletion app/db/model/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .exercise import Exercise
from .exercise import Exercise, TestCase
from .grading import GradingJob
from .logging_event import LoggingEvent
from .tan import Tan
23 changes: 23 additions & 0 deletions app/db/model/exercise.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@ def to_dict(self):
}


class TestCase(Base):
__tablename__ = "test_case"

id = sa.Column(sa.INTEGER, default=None, primary_key=True)
exercise_id = sa.Column(sa.INTEGER, sa.ForeignKey("exercise.id"), nullable=False)
title = sa.Column(sa.TEXT, nullable=False)
precondition = sa.Column(sa.JSON, nullable=False)
postcondition = sa.Column(sa.JSON, nullable=False)
user_input = sa.Column(sa.JSON, nullable=False)
expected_output = sa.Column(sa.JSON, nullable=False)

def to_dict(self):
return {
"id": self.id,
"exercise_id": self.exercise_id,
"title": self.title,
"precondition": self.precondition,
"postcondition": self.postcondition,
"user_input": self.user_input,
"expected_output": self.expected_output,
}


class ExerciseProgress(Base):
__tablename__ = "exercise_progress"
__table_args__ = (
Expand Down
72 changes: 72 additions & 0 deletions tests/test_exercise.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,75 @@ def test_get_current_exercise_with_missing_current_progress_entry_2(self):

assert response.status_code == 200
assert response.json() == EXERCISES[0]

def test_post_test_case(self):
app.dependency_overrides[get_session] = get_override_dependency(self.engine)
client = TestClient(app)

new_test_case = {
"title": "read val to r1 and r2 and store sum in r3 + output",
"precondition": {
"registers": {
"pc": 0,
},
"memory": {}
},
"postcondition": {
"registers": {
"r1": 10,
"r2": 20,
"r3": 30,
},
"memory": {}
},
"user_input": ["10", "20"],
"expected_output": ["30"]
}

response = client.post("/exercise/1/test-case", json=new_test_case)

result_test_case = response.json()

assert response.status_code == 200
assert result_test_case["title"] == new_test_case["title"]
assert result_test_case["precondition"] == new_test_case["precondition"]
assert result_test_case["postcondition"] == new_test_case["postcondition"]
assert result_test_case["user_input"] == new_test_case["user_input"]
assert result_test_case["expected_output"] == new_test_case["expected_output"]
assert "id" in result_test_case

def test_get_test_case(self):
app.dependency_overrides[get_session] = get_override_dependency(self.engine)
client = TestClient(app)

response = client.get("/exercise/2/test-case")
result_test_cases = response.json()

expected_test_case = {
"title": "read val to r1 and r2 and store sum in r3 + output",
"precondition": {
"registers": {
"pc": 0,
},
"memory": {}
},
"postcondition": {
"registers": {
"r1": 10,
"r2": 20,
"r3": 30,
},
"memory": {}
},
"user_input": ["10", "20"],
"expected_output": ["30"]
}

assert response.status_code == 200
assert len(result_test_cases) == 1
assert result_test_cases[0]["title"] == expected_test_case["title"]
assert result_test_cases[0]["precondition"] == expected_test_case["precondition"]
assert result_test_cases[0]["postcondition"] == expected_test_case["postcondition"]
assert result_test_cases[0]["user_input"] == expected_test_case["user_input"]
assert result_test_cases[0]["expected_output"] == expected_test_case["expected_output"]
assert "id" in result_test_cases[0]
14 changes: 12 additions & 2 deletions tests/util/db_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

from app.db.database import Base
from app.db.model import Exercise, Tan
from app.db.model.exercise import ExerciseProgress, Competition
from tests.util.demo_data import COMPETITIONS, TANS, EXERCISES, EXERCISE_PROGRESS_ENTRIES
from app.db.model.exercise import ExerciseProgress, Competition, TestCase
from tests.util.demo_data import COMPETITIONS, TANS, EXERCISES, EXERCISE_PROGRESS_ENTRIES, EXERCISE_TEST_CASES

DB_URI = "sqlite+aiosqlite:///:memory:"

Expand Down Expand Up @@ -35,6 +35,10 @@ async def insert_all_records(session_factory: async_sessionmaker):
for exercise_progress in EXERCISE_PROGRESS_ENTRIES:
await insert_exercise_progress(session, exercise_progress)

for exercise_id in EXERCISE_TEST_CASES:
for test_case in EXERCISE_TEST_CASES[exercise_id]:
await insert_exercise_test_case(session, exercise_id, test_case)


async def insert_exercise(session: AsyncSession, exercise: dict) -> None:
exercise = Exercise(**exercise)
Expand All @@ -58,3 +62,9 @@ async def insert_competition(session: AsyncSession, competition: dict) -> None:
competition = Competition(**competition)
session.add(competition)
await session.commit()


async def insert_exercise_test_case(session: AsyncSession, exercise_id: int, test_case: dict) -> None:
test_case = TestCase(exercise_id=exercise_id, **test_case)
session.add(test_case)
await session.commit()
24 changes: 24 additions & 0 deletions tests/util/demo_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,27 @@
"skipped": False
},
]

EXERCISE_TEST_CASES = {
2: [
{
"title": "read val to r1 and r2 and store sum in r3 + output",
"precondition": {
"registers": {
"pc": 0,
},
"memory": {}
},
"postcondition": {
"registers": {
"r1": 10,
"r2": 20,
"r3": 30,
},
"memory": {}
},
"user_input": ["10", "20"],
"expected_output": ["30"]
}
]
}