""" This program has been developed by students from the bachelor Computer Science at Utrecht University within the Software Project course. © Copyright Utrecht University (Department of Information and Computing Sciences) """ import json import uuid 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 BasicNorm, Goal, Phase, Plan, 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.""" # Converting to JSON using Pydantic because it knows how to convert a UUID object program_json_str = Program( phases=[ Phase( id=uuid.uuid4(), name="Basic Phase", norms=[ BasicNorm( id=uuid.uuid4(), name="Some norm", norm="Do normal.", ), ], goals=[ Goal( id=uuid.uuid4(), name="Some goal", description="This description can be used to determine whether the goal " "has been achieved.", plan=Plan( id=uuid.uuid4(), name="Goal Plan", steps=[], ), can_fail=False, ), ], triggers=[], ), ], ).model_dump_json() # Converting back to a dict because that's what's expected return json.loads(program_json_str) 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()) # Converting to JSON using Pydantic because it knows how to handle UUIDs expected_obj = json.loads(Program.model_validate(program_dict).model_dump_json()) 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()