Merge remote-tracking branch 'origin/dev' into feat/vad-agent
# Conflicts: # pyproject.toml # src/control_backend/main.py # uv.lock
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from unittest.mock import MagicMock, AsyncMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import zmq
|
||||
@@ -20,7 +20,8 @@ def streaming(mocker):
|
||||
@pytest.mark.asyncio
|
||||
async def test_normal_setup(streaming):
|
||||
"""
|
||||
Test that during normal setup, the VAD agent creates a Streaming behavior and creates audio sockets.
|
||||
Test that during normal setup, the VAD agent creates a Streaming behavior and creates audio
|
||||
sockets.
|
||||
"""
|
||||
vad_agent = VADAgent("tcp://localhost:12345", False)
|
||||
vad_agent.add_behaviour = MagicMock()
|
||||
@@ -36,9 +37,10 @@ async def test_normal_setup(streaming):
|
||||
@pytest.mark.parametrize("do_bind", [True, False])
|
||||
def test_in_socket_creation(zmq_context, do_bind: bool):
|
||||
"""
|
||||
Test that the VAD agent creates an audio input socket, differentiating between binding and connecting.
|
||||
Test that the VAD agent creates an audio input socket, differentiating between binding and
|
||||
connecting.
|
||||
"""
|
||||
vad_agent = VADAgent(f"tcp://{"*" if do_bind else "localhost"}:12345", do_bind)
|
||||
vad_agent = VADAgent(f"tcp://{'*' if do_bind else 'localhost'}:12345", do_bind)
|
||||
|
||||
vad_agent._connect_audio_in_socket()
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import os
|
||||
from unittest.mock import MagicMock, AsyncMock
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
import soundfile as sf
|
||||
@@ -17,7 +17,7 @@ def get_audio_chunks() -> list[bytes]:
|
||||
|
||||
chunks = []
|
||||
|
||||
with sf.SoundFile(file, 'r') as f:
|
||||
with sf.SoundFile(file, "r") as f:
|
||||
assert f.samplerate == 16000
|
||||
assert f.channels == 1
|
||||
assert f.subtype == "FLOAT"
|
||||
@@ -54,4 +54,4 @@ async def test_real_audio(mocker):
|
||||
audio_out_socket.send.assert_called()
|
||||
for args in audio_out_socket.send.call_args_list:
|
||||
assert isinstance(args[0][0], bytes)
|
||||
assert len(args[0][0]) >= 512*4*3 # Should be at least 3 chunks of audio
|
||||
assert len(args[0][0]) >= 512 * 4 * 3 # Should be at least 3 chunks of audio
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import json
|
||||
import logging
|
||||
from unittest.mock import MagicMock, AsyncMock, call
|
||||
from unittest.mock import AsyncMock, MagicMock, call
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -26,11 +26,11 @@ def belief_setter(mock_agent, mocker):
|
||||
# Patch the settings to use a predictable agent name
|
||||
mocker.patch(
|
||||
"control_backend.agents.bdi.behaviours.belief_setter.settings.agent_settings.belief_collector_agent_name",
|
||||
COLLECTOR_AGENT_NAME
|
||||
COLLECTOR_AGENT_NAME,
|
||||
)
|
||||
# Patch asyncio.sleep to prevent tests from actually waiting
|
||||
mocker.patch("asyncio.sleep", return_value=None)
|
||||
|
||||
|
||||
setter = BeliefSetter()
|
||||
setter.agent = mock_agent
|
||||
# Mock the receive method, we will control its return value in each test
|
||||
@@ -69,7 +69,7 @@ async def test_run_message_received(belief_setter, mocker):
|
||||
Test that when a message is received, _process_message is called.
|
||||
"""
|
||||
# Arrange
|
||||
msg = MagicMock();
|
||||
msg = MagicMock()
|
||||
belief_setter.receive.return_value = msg
|
||||
mocker.patch.object(belief_setter, "_process_message")
|
||||
|
||||
@@ -115,14 +115,9 @@ def test_process_belief_message_valid_json(belief_setter, mocker):
|
||||
Test processing a valid belief message with correct thread and JSON body.
|
||||
"""
|
||||
# Arrange
|
||||
beliefs_payload = {
|
||||
"is_hot": [["kitchen"]],
|
||||
"is_clean": [["kitchen"], ["bathroom"]]
|
||||
}
|
||||
beliefs_payload = {"is_hot": [["kitchen"]], "is_clean": [["kitchen"], ["bathroom"]]}
|
||||
msg = create_mock_message(
|
||||
sender_node=COLLECTOR_AGENT_JID,
|
||||
body=json.dumps(beliefs_payload),
|
||||
thread="beliefs"
|
||||
sender_node=COLLECTOR_AGENT_JID, body=json.dumps(beliefs_payload), thread="beliefs"
|
||||
)
|
||||
mock_set_beliefs = mocker.patch.object(belief_setter, "_set_beliefs")
|
||||
|
||||
@@ -139,9 +134,7 @@ def test_process_belief_message_invalid_json(belief_setter, mocker, caplog):
|
||||
"""
|
||||
# Arrange
|
||||
msg = create_mock_message(
|
||||
sender_node=COLLECTOR_AGENT_JID,
|
||||
body="this is not a json string",
|
||||
thread="beliefs"
|
||||
sender_node=COLLECTOR_AGENT_JID, body="this is not a json string", thread="beliefs"
|
||||
)
|
||||
mock_set_beliefs = mocker.patch.object(belief_setter, "_set_beliefs")
|
||||
|
||||
@@ -160,9 +153,7 @@ def test_process_belief_message_wrong_thread(belief_setter, mocker):
|
||||
"""
|
||||
# Arrange
|
||||
msg = create_mock_message(
|
||||
sender_node=COLLECTOR_AGENT_JID,
|
||||
body='{"some": "data"}',
|
||||
thread="not_beliefs"
|
||||
sender_node=COLLECTOR_AGENT_JID, body='{"some": "data"}', thread="not_beliefs"
|
||||
)
|
||||
mock_set_beliefs = mocker.patch.object(belief_setter, "_set_beliefs")
|
||||
|
||||
@@ -172,16 +163,13 @@ def test_process_belief_message_wrong_thread(belief_setter, mocker):
|
||||
# Assert
|
||||
mock_set_beliefs.assert_not_called()
|
||||
|
||||
|
||||
def test_process_belief_message_empty_body(belief_setter, mocker):
|
||||
"""
|
||||
Test that a message with an empty body is ignored.
|
||||
"""
|
||||
# Arrange
|
||||
msg = create_mock_message(
|
||||
sender_node=COLLECTOR_AGENT_JID,
|
||||
body="",
|
||||
thread="beliefs"
|
||||
)
|
||||
msg = create_mock_message(sender_node=COLLECTOR_AGENT_JID, body="", thread="beliefs")
|
||||
mock_set_beliefs = mocker.patch.object(belief_setter, "_set_beliefs")
|
||||
|
||||
# Act
|
||||
@@ -198,9 +186,9 @@ def test_set_beliefs_success(belief_setter, mock_agent, caplog):
|
||||
# Arrange
|
||||
beliefs_to_set = {
|
||||
"is_hot": [["kitchen"], ["living_room"]],
|
||||
"door_is": [["front_door", "closed"]]
|
||||
"door_is": [["front_door", "closed"]],
|
||||
}
|
||||
|
||||
|
||||
# Act
|
||||
with caplog.at_level(logging.INFO):
|
||||
belief_setter._set_beliefs(beliefs_to_set)
|
||||
@@ -209,11 +197,11 @@ def test_set_beliefs_success(belief_setter, mock_agent, caplog):
|
||||
expected_calls = [
|
||||
call("is_hot", "kitchen"),
|
||||
call("is_hot", "living_room"),
|
||||
call("door_is", "front_door", "closed")
|
||||
call("door_is", "front_door", "closed"),
|
||||
]
|
||||
mock_agent.bdi.set_belief.assert_has_calls(expected_calls, any_order=True)
|
||||
assert mock_agent.bdi.set_belief.call_count == 3
|
||||
|
||||
|
||||
# Check logs
|
||||
assert "Set belief is_hot with arguments ['kitchen']" in caplog.text
|
||||
assert "Set belief is_hot with arguments ['living_room']" in caplog.text
|
||||
|
||||
@@ -48,14 +48,14 @@ async def test_voice_activity_detected(audio_in_socket, audio_out_socket, stream
|
||||
:return:
|
||||
"""
|
||||
speech_chunk_count = 5
|
||||
probabilities = [0.0]*5 + [1.0]*speech_chunk_count + [0.0]*5
|
||||
probabilities = [0.0] * 5 + [1.0] * speech_chunk_count + [0.0] * 5
|
||||
await simulate_streaming_with_probabilities(streaming, probabilities)
|
||||
|
||||
audio_out_socket.send.assert_called_once()
|
||||
data = audio_out_socket.send.call_args[0][0]
|
||||
assert isinstance(data, bytes)
|
||||
# each sample has 512 frames of 4 bytes, expecting 7 chunks (5 with speech, 2 as padding)
|
||||
assert len(data) == 512*4*(speech_chunk_count+2)
|
||||
assert len(data) == 512 * 4 * (speech_chunk_count + 2)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -65,14 +65,16 @@ async def test_voice_activity_short_pause(audio_in_socket, audio_out_socket, str
|
||||
short pause.
|
||||
"""
|
||||
speech_chunk_count = 5
|
||||
probabilities = [0.0]*5 + [1.0]*speech_chunk_count + [0.0] + [1.0]*speech_chunk_count + [0.0]*5
|
||||
probabilities = (
|
||||
[0.0] * 5 + [1.0] * speech_chunk_count + [0.0] + [1.0] * speech_chunk_count + [0.0] * 5
|
||||
)
|
||||
await simulate_streaming_with_probabilities(streaming, probabilities)
|
||||
|
||||
audio_out_socket.send.assert_called_once()
|
||||
data = audio_out_socket.send.call_args[0][0]
|
||||
assert isinstance(data, bytes)
|
||||
# Expecting 13 chunks (2*5 with speech, 1 pause between, 2 as padding)
|
||||
assert len(data) == 512*4*(speech_chunk_count*2+1+2)
|
||||
assert len(data) == 512 * 4 * (speech_chunk_count * 2 + 1 + 2)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import sys
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import sys
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
def pytest_configure(config):
|
||||
"""
|
||||
@@ -17,21 +15,21 @@ def pytest_configure(config):
|
||||
mock_spade_bdi.bdi = MagicMock()
|
||||
|
||||
mock_spade.agent.Message = MagicMock()
|
||||
mock_spade.behaviour.CyclicBehaviour = type('CyclicBehaviour', (object,), {})
|
||||
mock_spade_bdi.bdi.BDIAgent = type('BDIAgent', (object,), {})
|
||||
mock_spade.behaviour.CyclicBehaviour = type("CyclicBehaviour", (object,), {})
|
||||
mock_spade_bdi.bdi.BDIAgent = type("BDIAgent", (object,), {})
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
sys.modules["control_backend.core.config"] = mock_config_module
|
||||
|
||||
Reference in New Issue
Block a user