From fd11e63b78e712eead923a59e86f61d4ff390632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Otgaar?= Date: Tue, 28 Oct 2025 14:16:39 +0100 Subject: [PATCH] Revert "fix: unit test refactoring with conftest and more mocks" This reverts commit 423309e0630ddd322f5f690ce1bac935fb236743. --- .gitlab-ci.yml | 2 +- pyproject.toml | 6 -- .../agents/ri_communication_agent.py | 1 + ...and_agent.py => test_ri_commands_agent.py} | 2 + .../agents/test_ri_communication_agent.py | 45 +++++++-------- test/integration/conftest.py | 57 ------------------- uv.lock | 56 +----------------- 7 files changed, 27 insertions(+), 142 deletions(-) rename test/integration/agents/{test_ri_command_agent.py => test_ri_commands_agent.py} (97%) delete mode 100644 test/integration/conftest.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cac27bf..95895ea 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,6 +22,6 @@ test: tags: - test script: - - uv run --only-group integration-test pytest test/integration + - uv run --only-group test pytest test/integration - uv run --only-group test pytest test/unit diff --git a/pyproject.toml b/pyproject.toml index ee88a50..faa584e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,12 +29,6 @@ dev = [ "ruff>=0.14.2", "ruff-format>=0.3.0", ] -integration-test = [ - {include-group = "test"}, - "asyncio>=4.0.0", - "soundfile>=0.13.1", - "zmq>=0.0.0", -] test = [ "pytest>=8.4.2", "pytest-asyncio>=1.2.0", diff --git a/src/control_backend/agents/ri_communication_agent.py b/src/control_backend/agents/ri_communication_agent.py index 71c2e52..504c707 100644 --- a/src/control_backend/agents/ri_communication_agent.py +++ b/src/control_backend/agents/ri_communication_agent.py @@ -7,6 +7,7 @@ import zmq from control_backend.core.config import settings from control_backend.core.zmq_context import context +from control_backend.schemas.message import Message from control_backend.agents.ri_command_agent import RICommandAgent logger = logging.getLogger(__name__) diff --git a/test/integration/agents/test_ri_command_agent.py b/test/integration/agents/test_ri_commands_agent.py similarity index 97% rename from test/integration/agents/test_ri_command_agent.py rename to test/integration/agents/test_ri_commands_agent.py index fa310a8..219d682 100644 --- a/test/integration/agents/test_ri_command_agent.py +++ b/test/integration/agents/test_ri_commands_agent.py @@ -1,8 +1,10 @@ +import asyncio import zmq import json import pytest from unittest.mock import AsyncMock, MagicMock, patch from control_backend.agents.ri_command_agent import RICommandAgent +from control_backend.schemas.ri_message import SpeechCommand @pytest.mark.asyncio diff --git a/test/integration/agents/test_ri_communication_agent.py b/test/integration/agents/test_ri_communication_agent.py index ba2cdc2..3e4a056 100644 --- a/test/integration/agents/test_ri_communication_agent.py +++ b/test/integration/agents/test_ri_communication_agent.py @@ -81,45 +81,47 @@ def fake_json_invalid_id_negototiate(): } ) -def mock_command_agent(): - """Fixture to create a mock BDIAgent.""" - agent = MagicMock() - agent.bdi = MagicMock() - agent.jid = "ri_command_agent@test" - return agent - - - @pytest.mark.asyncio async def test_setup_creates_socket_and_negotiate_1(monkeypatch): + """ + Test the setup of the communication agent + """ + # --- Arrange --- fake_socket = MagicMock() fake_socket.send_json = AsyncMock() - fake_socket.recv_json = AsyncMock(return_value={ - "endpoint": "negotiate/ports", - "data": [ - {"id": "main", "port": 5555, "bind": False}, - {"id": "actuation", "port": 5556, "bind": True}, - ], - }) + fake_socket.recv_json = fake_json_correct_negototiate_1() + # Mock context.socket to return our fake socket monkeypatch.setattr( "control_backend.agents.ri_communication_agent.context.socket", lambda _: fake_socket ) - with patch("control_backend.agents.ri_communication_agent.RICommandAgent") as MockCommandAgent: + # Mock RICommandAgent agent startup + with patch( + "control_backend.agents.ri_communication_agent.RICommandAgent", autospec=True + ) as MockCommandAgent: fake_agent_instance = MockCommandAgent.return_value fake_agent_instance.start = AsyncMock() - agent = RICommunicationAgent("test@server", "password", address="tcp://localhost:5555", bind=False) + # --- Act --- + agent = RICommunicationAgent( + "test@server", "password", address="tcp://localhost:5555", bind=False + ) await agent.setup() + # --- Assert --- fake_socket.connect.assert_any_call("tcp://localhost:5555") fake_socket.send_json.assert_any_call({"endpoint": "negotiate/ports", "data": None}) + fake_socket.recv_json.assert_awaited() fake_agent_instance.start.assert_awaited() MockCommandAgent.assert_called_once_with( - ANY, ANY, address="tcp://*:5556", bind=True + ANY, # Server Name + ANY, # Server Password + address="tcp://*:5556", # derived from the 'port' value in negotiation + bind=True, ) + # Ensure the agent attached a ListenBehaviour assert any(isinstance(b, agent.ListenBehaviour) for b in agent.behaviours) @@ -139,9 +141,6 @@ async def test_setup_creates_socket_and_negotiate_2(monkeypatch): ) # Mock RICommandAgent agent startup - - patch("control_backend.agents.ri_communication_agent.RICommandAgent", mock_command_agent) - with patch( "control_backend.agents.ri_communication_agent.RICommandAgent", autospec=True ) as MockCommandAgent: @@ -589,4 +588,4 @@ async def test_setup_unpacking_exception(monkeypatch, caplog): fake_agent_instance.start.assert_not_awaited() # Ensure no behaviour was attached - assert not any(isinstance(b, agent.ListenBehaviour) for b in agent.behaviours) \ No newline at end of file + assert not any(isinstance(b, agent.ListenBehaviour) for b in agent.behaviours) diff --git a/test/integration/conftest.py b/test/integration/conftest.py deleted file mode 100644 index 8e05e25..0000000 --- a/test/integration/conftest.py +++ /dev/null @@ -1,57 +0,0 @@ -import sys -from unittest.mock import MagicMock, AsyncMock - -class DummyCyclicBehaviour: - async def run(self): - pass - - def kill(self): - self.is_killed = True - return None - -class DummyAgent: - def __init__(self, jid=None, password=None, *_, **__): - self.jid = jid - self.password = password - self.behaviours = [] - - async def start(self): - return AsyncMock() - - def add_behaviour(self, behaviour): - behaviour.agent = self - self.behaviours.append(behaviour) - - async def stop(self): - pass - - -def pytest_configure(config): - """ - This hook runs at the start of the pytest session, before any tests are - collected. It mocks heavy or unavailable modules to prevent ImportErrors. - """ - # --- Mock spade and spade-bdi --- - mock_spade = MagicMock() - mock_spade.agent = MagicMock(Agent=DummyAgent) - mock_spade.behaviour = MagicMock(CyclicBehaviour=DummyCyclicBehaviour) - mock_spade_bdi = MagicMock() - mock_spade_bdi.bdi = MagicMock() - - mock_spade.agent.Message = MagicMock() - - sys.modules["spade"] = mock_spade - sys.modules["spade.agent"] = mock_spade.agent - sys.modules["spade.behaviour"] = mock_spade.behaviour - sys.modules["spade_bdi"] = mock_spade_bdi - sys.modules["spade_bdi.bdi"] = mock_spade_bdi.bdi - - # --- Mock the config module to prevent Pydantic ImportError --- - mock_config_module = MagicMock() - - # The code under test does `from ... import settings`, so our mock module - # must have a `settings` attribute. We'll make it a MagicMock so we can - # configure it later in our tests using mocker.patch. - mock_config_module.settings = MagicMock() - - sys.modules["control_backend.core.config"] = mock_config_module \ No newline at end of file diff --git a/uv.lock b/uv.lock index 1c53b3f..3f1a137 100644 --- a/uv.lock +++ b/uv.lock @@ -127,15 +127,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" }, ] -[[package]] -name = "asyncio" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/ea/26c489a11f7ca862d5705db67683a7361ce11c23a7b98fc6c2deaeccede2/asyncio-4.0.0.tar.gz", hash = "sha256:570cd9e50db83bc1629152d4d0b7558d6451bb1bfd5dfc2e935d96fc2f40329b", size = 5371, upload-time = "2025-08-05T02:51:46.605Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/57/64/eff2564783bd650ca25e15938d1c5b459cda997574a510f7de69688cb0b4/asyncio-4.0.0-py3-none-any.whl", hash = "sha256:c1eddb0659231837046809e68103969b2bef8b0400d59cfa6363f6b5ed8cc88b", size = 5555, upload-time = "2025-08-05T02:51:45.767Z" }, -] - [[package]] name = "attrs" version = "25.4.0" @@ -1363,15 +1354,6 @@ dev = [ { name = "ruff" }, { name = "ruff-format" }, ] -integration-test = [ - { name = "asyncio" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "pytest-cov" }, - { name = "pytest-mock" }, - { name = "soundfile" }, - { name = "zmq" }, -] test = [ { name = "pytest" }, { name = "pytest-asyncio" }, @@ -1405,15 +1387,6 @@ dev = [ { name = "ruff", specifier = ">=0.14.2" }, { name = "ruff-format", specifier = ">=0.3.0" }, ] -integration-test = [ - { name = "asyncio", specifier = ">=4.0.0" }, - { name = "pytest", specifier = ">=8.4.2" }, - { name = "pytest-asyncio", specifier = ">=1.2.0" }, - { name = "pytest-cov", specifier = ">=7.0.0" }, - { name = "pytest-mock", specifier = ">=3.15.1" }, - { name = "soundfile", specifier = ">=0.13.1" }, - { name = "zmq", specifier = ">=0.0.0" }, -] test = [ { name = "pytest", specifier = ">=8.4.2" }, { name = "pytest-asyncio", specifier = ">=1.2.0" }, @@ -1439,6 +1412,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] + [[package]] name = "pre-commit" version = "4.3.0" @@ -2243,25 +2217,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] -[[package]] -name = "soundfile" -version = "0.13.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156, upload-time = "2025-01-25T09:17:04.831Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751, upload-time = "2025-01-25T09:16:44.235Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250, upload-time = "2025-01-25T09:16:47.583Z" }, - { url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406, upload-time = "2025-01-25T09:16:49.662Z" }, - { url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729, upload-time = "2025-01-25T09:16:53.018Z" }, - { url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646, upload-time = "2025-01-25T09:16:54.872Z" }, - { url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881, upload-time = "2025-01-25T09:16:56.663Z" }, - { url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162, upload-time = "2025-01-25T09:16:59.573Z" }, -] - [[package]] name = "spade" version = "4.1.0" @@ -2789,12 +2744,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, ] - -[[package]] -name = "zmq" -version = "0.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyzmq" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6e/78/833b2808793c1619835edb1a4e17a023d5d625f4f97ff25ffff986d1f472/zmq-0.0.0.tar.gz", hash = "sha256:6b1a1de53338646e8c8405803cffb659e8eb7bb02fff4c9be62a7acfac8370c9", size = 966, upload-time = "2015-05-21T17:34:26.603Z" }