import json from unittest.mock import AsyncMock import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from control_backend.api.v1.endpoints import program from control_backend.schemas.program import Program @pytest.fixture def app(): """Create a FastAPI app with the /program route and mock socket.""" app = FastAPI() app.include_router(program.router) return app @pytest.fixture def client(app): """Create a TestClient.""" return TestClient(app) def make_valid_program_dict(): """Helper to create a valid Program JSON structure.""" return { "phases": [ { "id": "phase1", "label": "basephase", "norms": [{"id": "n1", "label": "norm", "norm": "be nice"}], "goals": [ {"id": "g1", "label": "goal", "description": "test goal", "achieved": False} ], "triggers": [ { "id": "t1", "label": "trigger", "type": "keywords", "keywords": [ {"id": "kw1", "keyword": "keyword1"}, {"id": "kw2", "keyword": "keyword2"}, ], }, ], } ] } def test_receive_program_success(client): """Valid Program JSON should be parsed and sent through the socket.""" mock_pub_socket = AsyncMock() client.app.state.endpoints_pub_socket = mock_pub_socket program_dict = make_valid_program_dict() response = client.post("/program", json=program_dict) assert response.status_code == 202 assert response.json() == {"status": "Program parsed"} # 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] sent_obj = json.loads(sent_bytes.decode()) expected_obj = Program.model_validate(program_dict).model_dump() assert sent_obj == expected_obj def test_receive_program_invalid_json(client): """ 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 # FastAPI only accepts valid JSON bodies, so send raw string response = client.post("/program", content="{invalid json}") assert response.status_code == 422 mock_pub_socket.send_multipart.assert_not_called() def test_receive_program_invalid_deep_structure(client): """ Valid JSON but schema invalid -> Pydantic throws validation error -> 422. """ mock_pub_socket = AsyncMock() client.app.state.endpoints_pub_socket = mock_pub_socket # Missing "value" in norms element bad_program = { "phases": [ { "id": "phase1", "name": "deepfail", "nextPhaseId": "phase2", "phaseData": { "norms": [ {"id": "n1", "name": "norm"} # INVALID: missing "value" ], "goals": [ {"id": "g1", "name": "goal", "description": "desc", "achieved": False} ], "triggers": [ {"id": "t1", "label": "trigger", "type": "keyword", "value": ["start"]} ], }, } ] } response = client.post("/program", json=bad_program) assert response.status_code == 422 mock_pub_socket.send_multipart.assert_not_called()