test: added tests for programs and its scheme

ref: N25B-198
This commit is contained in:
JobvAlewijk
2025-11-12 17:36:00 +01:00
parent 43f3cba1a8
commit 79d3bfb3a6
3 changed files with 177 additions and 1 deletions

View File

@@ -0,0 +1,91 @@
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 # <-- import your router
from control_backend.schemas.message import Message
@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_phase_dict():
"""Helper to create a valid Phase JSON structure."""
return {
"id": "phase1",
"name": "basephase",
"nextPhaseId": "phase2",
"phaseData": {
"norms": [{"id": "n1", "name": "norm", "value": "be nice"}],
"goals": [{"id": "g1", "name": "goal", "description": "test goal", "achieved": False}],
"triggers": [
{"id": "t1", "label": "trigger", "type": "keyword", "value": ["stop", "exit"]}
],
},
}
def test_receive_program_success(client):
"""Valid program JSON string should parse and be sent via the socket."""
# Arrange
mock_pub_socket = AsyncMock()
client.app.state.endpoints_pub_socket = mock_pub_socket
phases_list = [make_valid_phase_dict()]
message_body = json.dumps(phases_list)
msg = Message(message=message_body)
# Act
response = client.post("/program", json=msg.model_dump())
# Assert
assert response.status_code == 202
assert response.json() == {"status": "Program parsed", "phase_count": 1}
# Check the mocked socket
expected_body = json.dumps(phases_list).encode("utf-8")
mock_pub_socket.send_multipart.assert_awaited_once_with([b"program", expected_body])
def test_receive_program_invalid_json(client):
"""Malformed JSON string should return 400 with 'Undecodeable Json string'."""
mock_pub_socket = AsyncMock()
client.app.state.endpoints_pub_socket = mock_pub_socket
# Not valid JSON
bad_message = Message(message="{not valid json}")
response = client.post("/program", json=bad_message.model_dump())
assert response.status_code == 400
assert response.json()["detail"] == "Undecodeable Json string"
mock_pub_socket.send_multipart.assert_not_called()
def test_receive_program_invalid_phase(client):
"""Decodable JSON but invalid Phase structure should return 400 with 'Non-Phase String'."""
mock_pub_socket = AsyncMock()
client.app.state.endpoints_pub_socket = mock_pub_socket
# Missing required Phase fields
invalid_phase = [{"id": "only_id"}]
bad_message = Message(message=json.dumps(invalid_phase))
response = client.post("/program", json=bad_message.model_dump())
assert response.status_code == 400
assert response.json()["detail"] == "Non-Phase String"
mock_pub_socket.send_multipart.assert_not_called()

View File

@@ -0,0 +1,85 @@
import pytest
from pydantic import ValidationError
from control_backend.schemas.program import Goal, Norm, Phase, PhaseData, Program, Trigger
def base_norm() -> Norm:
return Norm(
id="norm1",
name="testNorm",
value="you should act nice",
)
def base_goal() -> Goal:
return Goal(
id="goal1",
name="testGoal",
description="you should act nice",
achieved=False,
)
def base_trigger() -> Trigger:
return Trigger(
id="trigger1",
label="testTrigger",
type="keyword",
value=["Stop", "Exit"],
)
def base_phase_data() -> PhaseData:
return PhaseData(
norms=[base_norm()],
goals=[base_goal()],
triggers=[base_trigger()],
)
def base_phase() -> Phase:
return Phase(
id="phase1",
name="basephase",
nextPhaseId="phase2",
phaseData=base_phase_data(),
)
def base_program() -> Program:
return Program(phases=[base_phase()])
def invalid_program() -> dict:
# wrong types inside phases list (not Phase objects)
return {
"phases": [
{"id": "phase1"}, # incomplete
{"not_a_phase": True},
]
}
def test_valid_program():
program = base_program()
validated = Program.model_validate(program)
assert isinstance(validated, Program)
assert validated.phases[0].phaseData.norms[0].name == "testNorm"
def test_valid_deepprogram():
program = base_program()
validated = Program.model_validate(program)
# validate nested components directly
phase = validated.phases[0]
assert isinstance(phase.phaseData, PhaseData)
assert isinstance(phase.phaseData.goals[0], Goal)
assert isinstance(phase.phaseData.triggers[0], Trigger)
assert isinstance(phase.phaseData.norms[0], Norm)
def test_invalid_program():
bad = invalid_program()
with pytest.raises(ValidationError):
Program.model_validate(bad)