refactor: made pydantic check the input.
no longer by the code itself. ref: N25B-198
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request
|
||||
from pydantic import ValidationError
|
||||
from fastapi import APIRouter, Request
|
||||
|
||||
from control_backend.schemas.message import Message
|
||||
from control_backend.schemas.program import Program
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -11,20 +9,12 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/program", status_code=202)
|
||||
async def receive_message(program: Message, request: Request):
|
||||
async def receive_message(program: Program, request: Request):
|
||||
"""
|
||||
Receives a BehaviorProgram as a stringified JSON list inside `message`.
|
||||
Receives a BehaviorProgram, pydantic checks it.
|
||||
Converts it into real Phase objects.
|
||||
"""
|
||||
logger.debug("Received raw program: %s", program)
|
||||
raw_str = program.message # This is the JSON string
|
||||
|
||||
# Validate program
|
||||
try:
|
||||
program = Program.model_validate_json(raw_str)
|
||||
except ValidationError as e:
|
||||
logger.error("Failed to validate program JSON: %s", e)
|
||||
raise HTTPException(status_code=400, detail="Not a valid program") from None
|
||||
|
||||
# send away
|
||||
topic = b"program"
|
||||
|
||||
@@ -6,7 +6,6 @@ from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from control_backend.api.v1.endpoints import program
|
||||
from control_backend.schemas.message import Message
|
||||
from control_backend.schemas.program import Program
|
||||
|
||||
|
||||
@@ -57,51 +56,48 @@ def test_receive_program_success(client):
|
||||
client.app.state.endpoints_pub_socket = mock_pub_socket
|
||||
|
||||
program_dict = make_valid_program_dict()
|
||||
message_body = json.dumps(program_dict)
|
||||
msg = Message(message=message_body)
|
||||
|
||||
# Act
|
||||
response = client.post("/program", json=msg.model_dump())
|
||||
response = client.post("/program", json=program_dict)
|
||||
|
||||
# Assert
|
||||
assert response.status_code == 202
|
||||
assert response.json() == {"status": "Program parsed"}
|
||||
|
||||
# Verify socket call (don't compare raw JSON string)
|
||||
# Verify socket call
|
||||
mock_pub_socket.send_multipart.assert_awaited_once()
|
||||
args, kwargs = mock_pub_socket.send_multipart.await_args
|
||||
|
||||
assert args[0][0] == b"program"
|
||||
|
||||
sent_bytes = args[0][1]
|
||||
|
||||
# Decode sent bytes and compare actual structures
|
||||
sent_obj = json.loads(sent_bytes.decode())
|
||||
expected_obj = Program.model_validate_json(message_body).model_dump()
|
||||
|
||||
expected_obj = Program.model_validate(program_dict).model_dump()
|
||||
assert sent_obj == expected_obj
|
||||
|
||||
|
||||
def test_receive_program_invalid_json(client):
|
||||
"""Invalid JSON string (not parseable) should trigger HTTP 400."""
|
||||
"""
|
||||
Invalid JSON (malformed) -> FastAPI never calls endpoint.
|
||||
It returns a 422 Unprocessable Entity.
|
||||
"""
|
||||
mock_pub_socket = AsyncMock()
|
||||
client.app.state.endpoints_pub_socket = mock_pub_socket
|
||||
|
||||
bad_json_str = "{invalid json}"
|
||||
msg = Message(message=bad_json_str)
|
||||
# FastAPI only accepts valid JSON bodies, so send raw string
|
||||
response = client.post("/program", content="{invalid json}")
|
||||
|
||||
response = client.post("/program", json=msg.model_dump())
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json()["detail"] == "Not a valid program"
|
||||
assert response.status_code == 422
|
||||
mock_pub_socket.send_multipart.assert_not_called()
|
||||
|
||||
|
||||
def test_receive_program_invalid_deep_structure(client):
|
||||
"""Valid JSON shape but invalid deep nested data should still raise 400."""
|
||||
"""
|
||||
Valid JSON but schema invalid -> Pydantic throws validation error -> 422.
|
||||
"""
|
||||
mock_pub_socket = AsyncMock()
|
||||
client.app.state.endpoints_pub_socket = mock_pub_socket
|
||||
|
||||
# Structurally correct Program, but with missing elements
|
||||
# Missing "value" in norms element
|
||||
bad_program = {
|
||||
"phases": [
|
||||
{
|
||||
@@ -110,7 +106,7 @@ def test_receive_program_invalid_deep_structure(client):
|
||||
"nextPhaseId": "phase2",
|
||||
"phaseData": {
|
||||
"norms": [
|
||||
{"id": "n1", "name": "norm"} # Missing "value"
|
||||
{"id": "n1", "name": "norm"} # INVALID: missing "value"
|
||||
],
|
||||
"goals": [
|
||||
{"id": "g1", "name": "goal", "description": "desc", "achieved": False}
|
||||
@@ -123,9 +119,7 @@ def test_receive_program_invalid_deep_structure(client):
|
||||
]
|
||||
}
|
||||
|
||||
msg = Message(message=json.dumps(bad_program))
|
||||
response = client.post("/program", json=msg.model_dump())
|
||||
response = client.post("/program", json=bad_program)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert response.json()["detail"] == "Not a valid program"
|
||||
assert response.status_code == 422
|
||||
mock_pub_socket.send_multipart.assert_not_called()
|
||||
|
||||
Reference in New Issue
Block a user