Run these scenarios after the core workflow. Each scenario must verify both the HTTP response and that rejected operations did not create or activate unintended state.
GET {{baseUrl}}/api/groupsExpected: HTTP 403 with the current Spring Security configuration.
GET {{baseUrl}}/api/groups
Authorization: Bearer not-a-valid-jwtExpected: HTTP 401.
Repeat the assignment multipart request using studentToken.
Expected: HTTP 403; no assignment is created.
POST {{baseUrl}}/api/execution/check
Authorization: Bearer {{teacherToken}}{
"code": "print(1)",
"language": "PYTHON",
"assignmentId": "{{assignmentId}}",
"executionType": "DEFINITIVE"
}Expected: HTTP 403.
Replace studentId with another student's UUID:
GET {{baseUrl}}/api/assignments/{{assignmentId}}/students/<otherStudentId>/work
Authorization: Bearer {{studentToken}}Expected: HTTP 403.
GET {{baseUrl}}/api/assignments/updates/{{updateId}}
Authorization: Bearer {{studentToken}}Expected: HTTP 403.
Use a token belonging to a user who neither initiated the execution nor owns the submission's group:
GET {{baseUrl}}/api/execution/check/{{executionId}}/report
Authorization: Bearer <unrelatedToken>Expected: HTTP 403.
{
"code": "print(1)",
"assignmentId": "{{assignmentId}}",
"executionType": "PRACTICE",
"testCases": ["1 2"]
}Expected: HTTP 400, with "Language is required" in errors.
The core assignment allows only Python:
{
"code": "public class Main {}",
"language": "JAVA",
"assignmentId": "{{assignmentId}}",
"executionType": "PRACTICE",
"testCases": ["1 2"]
}Expected: HTTP 400; no execution is created.
Run this once as the student and once as the teacher:
{
"code": "print(1)",
"language": "PYTHON",
"assignmentId": "{{assignmentId}}",
"executionType": "PRACTICE"
}Expected: HTTP 400 for both actors.
{
"code": " ",
"language": "PYTHON",
"assignmentId": "{{assignmentId}}",
"executionType": "DEFINITIVE"
}Expected: HTTP 400.
Use metadata-only PATCH with dates in the wrong order:
{
"launchDate": "2026-08-03T12:00:00Z",
"dueDate": "2026-08-02T12:00:00Z",
"closeDate": "2026-08-01T12:00:00Z"
}Expected: HTTP 400. The existing dates remain unchanged.
When assignment maxPoints is 100:
PUT {{baseUrl}}/api/assignments/{{assignmentId}}/students/{{studentId}}/grade
Authorization: Bearer {{teacherToken}}{
"value": 100.01
}Expected: HTTP 400; no grade is created or modified.
{
"body": " "
}Expected: HTTP 400; no feedback is created.
Send assignment metadata as plain text/plain.
Expected: HTTP 415 or a request-conversion 400, depending on the API
tester multipart encoding. Correct the part to application/json.
Use a metadata-only update to set future dates. Generate them in a Postman pre-request script:
const now = Date.now();
pm.environment.set("futureLaunch", new Date(now + 60 * 60 * 1000).toISOString());
pm.environment.set("futureDue", new Date(now + 2 * 60 * 60 * 1000).toISOString());
pm.environment.set("futureClose", new Date(now + 3 * 60 * 60 * 1000).toISOString());Metadata:
{
"launchDate": "{{futureLaunch}}",
"dueDate": "{{futureDue}}",
"closeDate": "{{futureClose}}"
}Then request an execution as the student.
Expected: HTTP 404; the assignment is intentionally hidden before
launch. The teacher owner may still use practice mode for preview.
Restore immediate availability:
{
"clearLaunchDate": true,
"clearDueDate": true,
"clearCloseDate": true
}Set dueDate five minutes in the past and closeDate one hour in the future.
Withdraw the current submission first, then submit definitively.
Expected: HTTP 202; the created submission has deliveredLate = true.
Set closeDate to a past instant and submit definitively.
Expected: HTTP 400; no submission or execution is created.
This requires a current submitted delivery. Set closeDate in the past, then:
POST {{baseUrl}}/api/submissions/{{submissionId}}/withdraw
Authorization: Bearer {{studentToken}}Expected: HTTP 400. The submission remains current and SUBMITTED.
Restore the dates before continuing.
With a current SUBMITTED delivery, send another definitive request.
Expected: HTTP 400; submission history remains unchanged.
Use the ID of an older withdrawn/non-current submission.
Expected: HTTP 400; only the current SUBMITTED delivery is withdrawable.
Join the same group again using studentToken.
Expected: HTTP 400; the active enrollment is not duplicated.
Send the join request using teacherToken.
Expected: HTTP 403.
Remove the student as the group owner:
DELETE {{baseUrl}}/api/groups/{{groupId}}/students/{{studentId}}
Authorization: Bearer {{teacherToken}}Then list assignments or request an execution with studentToken.
Expected: HTTP 403 or resource hiding according to the requested route.
No new submission is accepted.
Rejoin with the original join code before continuing. The historical enrollment should be reactivated rather than duplicated.
POST {{baseUrl}}/api/groups/{{groupId}}/archive
Authorization: Bearer {{teacherToken}}Attempt:
- Student join
- Assignment creation/update
- Practice execution
- Definitive submission
Expected: each mutation is rejected. Existing historical data remains readable to the owner.
Restore:
POST {{baseUrl}}/api/groups/{{groupId}}/unarchive
Authorization: Bearer {{teacherToken}}Submit a reference or test-suite update, then immediately submit another staged update before the first reaches a terminal state.
Expected: the second request returns HTTP 400 with a message that another
update is still being validated.
Use:
a, b = map(int, input().split())
print(a * b)Expected: initial HTTP 202, then update status REJECTED. Active revision
IDs remain unchanged and submissions are not reevaluated.
Upload:
def broken(Expected: initial HTTP 202, then REJECTED with a failure message.
Append:
not valid integers
Expected: initial HTTP 202, then REJECTED. The proposed suite never
becomes active, grades remain intact, and no reevaluation is created.
Use practice executions to exercise verdicts without changing submission history.
a, b = map(int, input().split())
print(a - b)def broken(raise RuntimeError("manual test")while True:
passwhile True:
print("x" * 10000)For every request:
- Expect HTTP
202and initialPENDING. - Poll until terminal.
- Retrieve the report.
- Confirm the overall and per-test status match the intended verdict.
Memory-limit behavior is language/runtime-dependent; use the worker sandbox
security tests for deterministic MLE verification.
Request the report immediately after creating an execution:
GET {{baseUrl}}/api/execution/check/{{executionId}}/report
Authorization: Bearer {{studentToken}}If the execution is still pending, expect HTTP 404 with a message indicating
that the report is not available yet.
Execution artifacts are retained for 180 days. Once artifactsExpireAt is
reached, the report endpoint returns HTTP 410 Gone, even if the execution
summary remains in PostgreSQL.
This boundary normally requires a controlled database timestamp in a local test environment; do not wait for the real retention period.
For a reusable environment:
- Clear assignment dates.
- Unarchive the group if necessary.
- Keep withdrawn submissions and feedback tombstones; they are intentional history.
- Use logical assignment/group deletion only when specifically testing those lifecycle endpoints.
- For a completely clean run, recreate the development database, clear stale RabbitMQ messages, and remove old MinIO objects.