From 57b1276cb5f569dd5a6a17f9ade8f1035922ce7d Mon Sep 17 00:00:00 2001 From: Twirre Meulenbelt <43213592+TwirreM@users.noreply.github.com> Date: Mon, 29 Dec 2025 12:31:51 +0100 Subject: [PATCH] test: make tests work again after changing Program schema ref: N25B-380 --- .../agents/bdi/bdi_core_agent.py | 6 +- .../agents/bdi/test_bdi_program_manager.py | 54 ++++++++++------ test/unit/agents/bdi/test_text_extractor.py | 11 +--- test/unit/agents/llm/test_llm_agent.py | 2 +- .../api/v1/endpoints/test_program_endpoint.py | 56 ++++++++++------- test/unit/schemas/test_ui_program_message.py | 62 +++++++++++-------- 6 files changed, 110 insertions(+), 81 deletions(-) diff --git a/src/control_backend/agents/bdi/bdi_core_agent.py b/src/control_backend/agents/bdi/bdi_core_agent.py index 8ff271c..427e024 100644 --- a/src/control_backend/agents/bdi/bdi_core_agent.py +++ b/src/control_backend/agents/bdi/bdi_core_agent.py @@ -89,9 +89,9 @@ class BDICoreAgent(BaseAgent): the agent has deferred intentions (deadlines). """ while self._running: - # await ( - # self._wake_bdi_loop.wait() - # ) # gets set whenever there's an update to the belief base + await ( + self._wake_bdi_loop.wait() + ) # gets set whenever there's an update to the belief base # Agent knows when it's expected to have to do its next thing maybe_more_work = True diff --git a/test/unit/agents/bdi/test_bdi_program_manager.py b/test/unit/agents/bdi/test_bdi_program_manager.py index a54360c..d16bc43 100644 --- a/test/unit/agents/bdi/test_bdi_program_manager.py +++ b/test/unit/agents/bdi/test_bdi_program_manager.py @@ -1,6 +1,6 @@ import asyncio -import json import sys +import uuid from unittest.mock import AsyncMock import pytest @@ -8,31 +8,45 @@ import pytest from control_backend.agents.bdi.bdi_program_manager import BDIProgramManager from control_backend.core.agent_system import InternalMessage from control_backend.schemas.belief_message import BeliefMessage -from control_backend.schemas.program import Program +from control_backend.schemas.program import BasicNorm, Goal, Phase, Plan, Program # Fix Windows Proactor loop for zmq if sys.platform.startswith("win"): asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) -def make_valid_program_json(norm="N1", goal="G1"): - return json.dumps( - { - "phases": [ - { - "id": "phase1", - "label": "Phase 1", - "triggers": [], - "norms": [{"id": "n1", "label": "Norm 1", "norm": norm}], - "goals": [ - {"id": "g1", "label": "Goal 1", "description": goal, "achieved": False} - ], - } - ] - } - ) +def make_valid_program_json(norm="N1", goal="G1") -> str: + return Program( + phases=[ + Phase( + id=uuid.uuid4(), + name="Basic Phase", + norms=[ + BasicNorm( + id=uuid.uuid4(), + name=norm, + norm=norm, + ), + ], + goals=[ + Goal( + id=uuid.uuid4(), + name=goal, + plan=Plan( + id=uuid.uuid4(), + name="Goal Plan", + steps=[], + ), + can_fail=False, + ), + ], + triggers=[], + ), + ], + ).model_dump_json() +@pytest.mark.skip(reason="Functionality being rebuilt.") @pytest.mark.asyncio async def test_send_to_bdi(): manager = BDIProgramManager(name="program_manager_test") @@ -73,5 +87,5 @@ async def test_receive_programs_valid_and_invalid(): # Only valid Program should have triggered _send_to_bdi assert manager._send_to_bdi.await_count == 1 forwarded: Program = manager._send_to_bdi.await_args[0][0] - assert forwarded.phases[0].norms[0].norm == "N1" - assert forwarded.phases[0].goals[0].description == "G1" + assert forwarded.phases[0].norms[0].name == "N1" + assert forwarded.phases[0].goals[0].name == "G1" diff --git a/test/unit/agents/bdi/test_text_extractor.py b/test/unit/agents/bdi/test_text_extractor.py index 895fef0..c51571a 100644 --- a/test/unit/agents/bdi/test_text_extractor.py +++ b/test/unit/agents/bdi/test_text_extractor.py @@ -45,10 +45,10 @@ async def test_handle_message_from_transcriber(agent, mock_settings): @pytest.mark.asyncio -async def test_process_transcription_demo(agent, mock_settings): +async def test_process_user_said(agent, mock_settings): transcription = "this is a test" - await agent._process_transcription_demo(transcription) + await agent._user_said(transcription) agent.send.assert_awaited_once() # noqa # `agent.send` has no such property, but we mock it. sent: InternalMessage = agent.send.call_args.args[0] # noqa @@ -56,10 +56,3 @@ async def test_process_transcription_demo(agent, mock_settings): assert sent.thread == "beliefs" parsed = json.loads(sent.body) assert parsed["beliefs"]["user_said"] == [transcription] - - -@pytest.mark.asyncio -async def test_setup_initializes_beliefs(agent): - """Covers the setup method and ensures beliefs are initialized.""" - await agent.setup() - assert agent.beliefs == {"mood": ["X"], "car": ["Y"]} diff --git a/test/unit/agents/llm/test_llm_agent.py b/test/unit/agents/llm/test_llm_agent.py index 5e84d8d..6cda4da 100644 --- a/test/unit/agents/llm/test_llm_agent.py +++ b/test/unit/agents/llm/test_llm_agent.py @@ -66,7 +66,7 @@ async def test_llm_processing_success(mock_httpx_client, mock_settings): # "Hello world." constitutes one sentence/chunk based on punctuation split # The agent should call send once with the full sentence assert agent.send.called - args = agent.send.call_args[0][0] + args = agent.send.call_args_list[0][0][0] assert args.to == mock_settings.agent_settings.bdi_core_name assert "Hello world." in args.body diff --git a/test/unit/api/v1/endpoints/test_program_endpoint.py b/test/unit/api/v1/endpoints/test_program_endpoint.py index 178159c..379767a 100644 --- a/test/unit/api/v1/endpoints/test_program_endpoint.py +++ b/test/unit/api/v1/endpoints/test_program_endpoint.py @@ -1,4 +1,5 @@ import json +import uuid from unittest.mock import AsyncMock import pytest @@ -6,7 +7,7 @@ from fastapi import FastAPI from fastapi.testclient import TestClient from control_backend.api.v1.endpoints import program -from control_backend.schemas.program import Program +from control_backend.schemas.program import BasicNorm, Goal, Phase, Plan, Program @pytest.fixture @@ -25,29 +26,37 @@ def client(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} + # 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.", + ), ], - "triggers": [ - { - "id": "t1", - "label": "trigger", - "type": "keywords", - "keywords": [ - {"id": "kw1", "keyword": "keyword1"}, - {"id": "kw2", "keyword": "keyword2"}, - ], - }, + goals=[ + Goal( + id=uuid.uuid4(), + name="Some goal", + 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): @@ -71,7 +80,8 @@ def test_receive_program_success(client): sent_bytes = args[0][1] sent_obj = json.loads(sent_bytes.decode()) - expected_obj = Program.model_validate(program_dict).model_dump() + # 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 diff --git a/test/unit/schemas/test_ui_program_message.py b/test/unit/schemas/test_ui_program_message.py index 7ed544e..a9f96dd 100644 --- a/test/unit/schemas/test_ui_program_message.py +++ b/test/unit/schemas/test_ui_program_message.py @@ -1,49 +1,61 @@ +import uuid + import pytest from pydantic import ValidationError from control_backend.schemas.program import ( + BasicNorm, Goal, - KeywordTrigger, - Norm, + KeywordBelief, Phase, + Plan, Program, - TriggerKeyword, + Trigger, ) -def base_norm() -> Norm: - return Norm( - id="norm1", - label="testNorm", +def base_norm() -> BasicNorm: + return BasicNorm( + id=uuid.uuid4(), + name="testNormName", norm="testNormNorm", + critical=False, ) def base_goal() -> Goal: return Goal( - id="goal1", - label="testGoal", - description="testGoalDescription", - achieved=False, + id=uuid.uuid4(), + name="testGoalName", + plan=Plan( + id=uuid.uuid4(), + name="testGoalPlanName", + steps=[], + ), + can_fail=False, ) -def base_trigger() -> KeywordTrigger: - return KeywordTrigger( - id="trigger1", - label="testTrigger", - type="keywords", - keywords=[ - TriggerKeyword(id="keyword1", keyword="testKeyword1"), - TriggerKeyword(id="keyword1", keyword="testKeyword2"), - ], +def base_trigger() -> Trigger: + return Trigger( + id=uuid.uuid4(), + name="testTriggerName", + condition=KeywordBelief( + id=uuid.uuid4(), + name="testTriggerKeywordBeliefTriggerName", + keyword="Keyword", + ), + plan=Plan( + id=uuid.uuid4(), + name="testTriggerPlanName", + steps=[], + ), ) def base_phase() -> Phase: return Phase( - id="phase1", - label="basephase", + id=uuid.uuid4(), norms=[base_norm()], goals=[base_goal()], triggers=[base_trigger()], @@ -58,7 +70,7 @@ def invalid_program() -> dict: # wrong types inside phases list (not Phase objects) return { "phases": [ - {"id": "phase1"}, # incomplete + {"id": uuid.uuid4()}, # incomplete {"not_a_phase": True}, ] } @@ -77,8 +89,8 @@ def test_valid_deepprogram(): # validate nested components directly phase = validated.phases[0] assert isinstance(phase.goals[0], Goal) - assert isinstance(phase.triggers[0], KeywordTrigger) - assert isinstance(phase.norms[0], Norm) + assert isinstance(phase.triggers[0], Trigger) + assert isinstance(phase.norms[0], BasicNorm) def test_invalid_program():