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
61 changes: 43 additions & 18 deletions handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from .tools.parse import ParseError
from typing import Any, Optional

from .tools.utils import DocsResponse, ErrorResponse, HandlerResponse, JsonType, Response
from .tools.utils import DocsResponse, ErrorCode, ErrorResponse, HandlerResponse, JsonType, Response
from .tools.validate import (
LegacyReqBodyValidators,
LegacyResBodyValidators,
Expand Down Expand Up @@ -103,7 +103,7 @@ def check_muEd_version(event: JsonType) -> Optional[HandlerResponse]:
f"The requested API version '{version}' is not supported. "
f"Supported versions are: {commands.SUPPORTED_MUED_VERSIONS}."
),
"code": "VERSION_NOT_SUPPORTED",
"code": ErrorCode.VERSION_NOT_SUPPORTED,
"details": {
"requestedVersion": version,
"supportedVersions": commands.SUPPORTED_MUED_VERSIONS,
Expand All @@ -122,26 +122,51 @@ def handle_muEd_command(event: JsonType, command: str) -> HandlerResponse:
Returns:
HandlerResponse: The response object returned by the handler.
"""
version_error = check_muEd_version(event)
if version_error:
return wrap_muEd_response(version_error, event, 406)
try:
version_error = check_muEd_version(event)
if version_error:
return wrap_muEd_response(version_error, event, 406)
Comment thread
m-messer marked this conversation as resolved.

if command == "eval":
body = parse.body(event)
validate.body(body, MuEdReqBodyValidators.EVALUATION)
response = commands.evaluate_muEd(body)
validate.body(response, MuEdResBodyValidators.EVALUATION)

elif command == "healthcheck":
response = commands.healthcheck_muEd()
validate.body(response, MuEdResBodyValidators.HEALTHCHECK)
status_code = 503 if response.get("status") == "UNAVAILABLE" else 200
return wrap_muEd_response(response, event, status_code)

if command == "eval":
body = parse.body(event)
validate.body(body, MuEdReqBodyValidators.EVALUATION)
response = commands.evaluate_muEd(body)
validate.body(response, MuEdResBodyValidators.EVALUATION)
else:
error = {
"title": "Not implemented",
"message": f"Unknown command '{command}'.",
"code": ErrorCode.NOT_IMPLEMENTED,
}
return wrap_muEd_response(error, event, 501)

elif command == "healthcheck":
response = commands.healthcheck_muEd()
validate.body(response, MuEdResBodyValidators.HEALTHCHECK)
return wrap_muEd_response(response, event)

else:
response = Response(
error=ErrorResponse(message=f"Unknown command '{command}'.")
)
except (ParseError, ValidationError) as e:
error = {
"title": "Bad request",
"message": e.message,
"code": ErrorCode.VALIDATION_ERROR,
"details": {"error": str(e.error_thrown)} if e.error_thrown else None,
}
return wrap_muEd_response(error, event, 400)

return wrap_muEd_response(response, event)
except EvaluationException as e:
detail = str(e) if str(e) else repr(e)
error = {"title": "Internal server error", "message": detail, "code": ErrorCode.INTERNAL_ERROR}
return wrap_muEd_response(error, event, 500)

except Exception as e:
detail = str(e) if str(e) else repr(e)
error = {"title": "Internal server error", "message": detail, "code": ErrorCode.INTERNAL_ERROR}
return wrap_muEd_response(error, event, 500)


def handler(event: JsonType, _=None) -> HandlerResponse:
Expand Down
35 changes: 20 additions & 15 deletions tests/mued_handling_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ def test_evaluate_missing_submission_returns_error(self):

response = handler(event)

self.assertIn("error", response)
self.assertIn("submission", response["error"]["detail"]) # type: ignore
self.assertEqual(response["statusCode"], 400)
body = json.loads(response["body"])
self.assertEqual(body["code"], "VALIDATION_ERROR")

def test_evaluate_invalid_submission_type_returns_error(self):
event = {
Expand All @@ -89,18 +90,19 @@ def test_evaluate_invalid_submission_type_returns_error(self):

response = handler(event)

self.assertIn("error", response)
self.assertEqual(response["statusCode"], 400)
body = json.loads(response["body"])
self.assertEqual(body["code"], "VALIDATION_ERROR")

def test_evaluate_bodyless_event_returns_error(self):
event = {"path": "/evaluate", "random": "metadata"}

response = handler(event)

self.assertIn("error", response)
self.assertEqual(
response["error"]["message"], # type: ignore
"No data supplied in request body.",
)
self.assertEqual(response["statusCode"], 400)
body = json.loads(response["body"])
self.assertEqual(body["code"], "VALIDATION_ERROR")
self.assertEqual(body["message"], "No data supplied in request body.")

def test_healthcheck(self):
event = {"path": "/evaluate/health"}
Expand Down Expand Up @@ -389,18 +391,19 @@ def test_preview_missing_submission_returns_error(self):

response = handler(event)

self.assertIn("error", response)
self.assertEqual(response["statusCode"], 400)
body = json.loads(response["body"])
self.assertEqual(body["code"], "VALIDATION_ERROR")

def test_preview_bodyless_event_returns_error(self):
event = {"path": "/evaluate", "random": "metadata"}

response = handler(event)

self.assertIn("error", response)
self.assertEqual(
response["error"]["message"], # type: ignore
"No data supplied in request body.",
)
self.assertEqual(response["statusCode"], 400)
body = json.loads(response["body"])
self.assertEqual(body["code"], "VALIDATION_ERROR")
self.assertEqual(body["message"], "No data supplied in request body.")

def test_preview_invalid_submission_type_returns_error(self):
event = {
Expand All @@ -413,7 +416,9 @@ def test_preview_invalid_submission_type_returns_error(self):

response = handler(event)

self.assertIn("error", response)
self.assertEqual(response["statusCode"], 400)
body = json.loads(response["body"])
self.assertEqual(body["code"], "VALIDATION_ERROR")

def test_presubmission_disabled_runs_normal_evaluation(self):
event = {
Expand Down
1 change: 1 addition & 0 deletions tools/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class CaseResult(NamedTuple):

is_correct: bool = False
feedback: str = ""

warning: Optional[CaseWarning] = None


Expand Down
8 changes: 8 additions & 0 deletions tools/utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
from __future__ import annotations

import enum
from typing import Any, Callable, Dict, Literal, TypedDict, Union

from typing_extensions import NotRequired

JsonType = Dict[str, Any]


class ErrorCode(str, enum.Enum):
VALIDATION_ERROR = "VALIDATION_ERROR"
VERSION_NOT_SUPPORTED = "VERSION_NOT_SUPPORTED"
NOT_IMPLEMENTED = "NOT_IMPLEMENTED"
INTERNAL_ERROR = "INTERNAL_ERROR"
SupportedCommands = Literal["eval", "grade", "preview", "healthcheck"]

EvaluationFunctionType = Callable[[Any, Any, JsonType], JsonType]
Expand Down