test: bunch of tests
Written with AI, still need to check them ref: N25B-449
This commit is contained in:
@@ -36,7 +36,12 @@ async def test_transcription_agent_flow(mock_zmq_context):
|
||||
agent.send = AsyncMock()
|
||||
|
||||
agent._running = True
|
||||
agent.add_behavior = AsyncMock()
|
||||
|
||||
def close_coro(coro):
|
||||
coro.close()
|
||||
return MagicMock()
|
||||
|
||||
agent.add_behavior = MagicMock(side_effect=close_coro)
|
||||
|
||||
await agent.setup()
|
||||
|
||||
@@ -143,7 +148,12 @@ async def test_transcription_loop_continues_after_error(mock_zmq_context):
|
||||
agent = TranscriptionAgent("tcp://in")
|
||||
agent._running = True # ← REQUIRED to enter the loop
|
||||
agent.send = AsyncMock() # should never be called
|
||||
agent.add_behavior = AsyncMock() # match other tests
|
||||
|
||||
def close_coro(coro):
|
||||
coro.close()
|
||||
return MagicMock()
|
||||
|
||||
agent.add_behavior = MagicMock(side_effect=close_coro) # match other tests
|
||||
|
||||
await agent.setup()
|
||||
|
||||
@@ -180,7 +190,12 @@ async def test_transcription_continue_branch_when_empty(mock_zmq_context):
|
||||
# Make loop runnable
|
||||
agent._running = True
|
||||
agent.send = AsyncMock()
|
||||
agent.add_behavior = AsyncMock()
|
||||
|
||||
def close_coro(coro):
|
||||
coro.close()
|
||||
return MagicMock()
|
||||
|
||||
agent.add_behavior = MagicMock(side_effect=close_coro)
|
||||
|
||||
await agent.setup()
|
||||
|
||||
|
||||
153
test/unit/agents/perception/vad_agent/test_vad_agent.py
Normal file
153
test/unit/agents/perception/vad_agent/test_vad_agent.py
Normal file
@@ -0,0 +1,153 @@
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from control_backend.agents.perception.vad_agent import VADAgent
|
||||
from control_backend.core.agent_system import InternalMessage
|
||||
from control_backend.schemas.program_status import PROGRAM_STATUS, ProgramStatus
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_zmq():
|
||||
with patch("zmq.asyncio.Context") as mock:
|
||||
mock.instance.return_value = MagicMock()
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def agent():
|
||||
return VADAgent("tcp://localhost:5555", False)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_message_pause(agent):
|
||||
agent._paused = MagicMock()
|
||||
# It starts set (not paused)
|
||||
|
||||
msg = InternalMessage(to="vad", sender="user_interrupt_agent", body="PAUSE")
|
||||
|
||||
# We need to mock settings to match sender name
|
||||
with patch("control_backend.agents.perception.vad_agent.settings") as mock_settings:
|
||||
mock_settings.agent_settings.user_interrupt_name = "user_interrupt_agent"
|
||||
|
||||
await agent.handle_message(msg)
|
||||
|
||||
agent._paused.clear.assert_called_once()
|
||||
assert agent._reset_needed is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_message_resume(agent):
|
||||
agent._paused = MagicMock()
|
||||
msg = InternalMessage(to="vad", sender="user_interrupt_agent", body="RESUME")
|
||||
|
||||
with patch("control_backend.agents.perception.vad_agent.settings") as mock_settings:
|
||||
mock_settings.agent_settings.user_interrupt_name = "user_interrupt_agent"
|
||||
|
||||
await agent.handle_message(msg)
|
||||
|
||||
agent._paused.set.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_message_unknown_command(agent):
|
||||
agent._paused = MagicMock()
|
||||
msg = InternalMessage(to="vad", sender="user_interrupt_agent", body="UNKNOWN")
|
||||
|
||||
with patch("control_backend.agents.perception.vad_agent.settings") as mock_settings:
|
||||
mock_settings.agent_settings.user_interrupt_name = "user_interrupt_agent"
|
||||
agent.logger = MagicMock()
|
||||
|
||||
await agent.handle_message(msg)
|
||||
|
||||
agent.logger.warning.assert_called()
|
||||
agent._paused.clear.assert_not_called()
|
||||
agent._paused.set.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_message_unknown_sender(agent):
|
||||
agent._paused = MagicMock()
|
||||
msg = InternalMessage(to="vad", sender="other_agent", body="PAUSE")
|
||||
|
||||
with patch("control_backend.agents.perception.vad_agent.settings") as mock_settings:
|
||||
mock_settings.agent_settings.user_interrupt_name = "user_interrupt_agent"
|
||||
|
||||
await agent.handle_message(msg)
|
||||
|
||||
agent._paused.clear.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_status_loop_waits_for_running(agent):
|
||||
agent._running = True
|
||||
agent.program_sub_socket = AsyncMock()
|
||||
agent.program_sub_socket.close = MagicMock()
|
||||
agent._reset_stream = AsyncMock()
|
||||
|
||||
# Sequence of messages:
|
||||
# 1. Wrong topic
|
||||
# 2. Right topic, wrong status (STARTING)
|
||||
# 3. Right topic, RUNNING -> Should break loop
|
||||
|
||||
agent.program_sub_socket.recv_multipart.side_effect = [
|
||||
(b"wrong_topic", b"whatever"),
|
||||
(PROGRAM_STATUS, ProgramStatus.STARTING.value),
|
||||
(PROGRAM_STATUS, ProgramStatus.RUNNING.value),
|
||||
]
|
||||
|
||||
await agent._status_loop()
|
||||
|
||||
assert agent._reset_stream.await_count == 1
|
||||
agent.program_sub_socket.close.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_setup_success(agent, mock_zmq):
|
||||
def close_coro(coro):
|
||||
coro.close()
|
||||
return MagicMock()
|
||||
|
||||
agent.add_behavior = MagicMock(side_effect=close_coro)
|
||||
|
||||
mock_context = mock_zmq.instance.return_value
|
||||
mock_sub = MagicMock()
|
||||
mock_pub = MagicMock()
|
||||
|
||||
# We expect multiple socket calls:
|
||||
# 1. audio_in (SUB)
|
||||
# 2. audio_out (PUB)
|
||||
# 3. program_sub (SUB)
|
||||
mock_context.socket.side_effect = [mock_sub, mock_pub, mock_sub]
|
||||
|
||||
with patch("control_backend.agents.perception.vad_agent.torch.hub.load") as mock_load:
|
||||
mock_load.return_value = (MagicMock(), None)
|
||||
|
||||
with patch("control_backend.agents.perception.vad_agent.TranscriptionAgent") as MockTrans:
|
||||
mock_trans_instance = MockTrans.return_value
|
||||
mock_trans_instance.start = AsyncMock()
|
||||
|
||||
await agent.setup()
|
||||
|
||||
mock_trans_instance.start.assert_awaited_once()
|
||||
|
||||
assert agent.add_behavior.call_count == 2 # streaming_loop + status_loop
|
||||
assert agent.audio_in_socket is not None
|
||||
assert agent.audio_out_socket is not None
|
||||
assert agent.program_sub_socket is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reset_stream(agent):
|
||||
mock_poller = MagicMock()
|
||||
agent.audio_in_poller = mock_poller
|
||||
|
||||
# poll(1) returns not None twice, then None
|
||||
mock_poller.poll = AsyncMock(side_effect=[b"data", b"data", None])
|
||||
|
||||
agent._ready = MagicMock()
|
||||
|
||||
await agent._reset_stream()
|
||||
|
||||
assert mock_poller.poll.await_count == 3
|
||||
agent._ready.set.assert_called_once()
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
import zmq
|
||||
|
||||
from control_backend.agents.perception.vad_agent import VADAgent
|
||||
from control_backend.core.config import settings
|
||||
|
||||
|
||||
# We don't want to use real ZMQ in unit tests, for example because it can give errors when sockets
|
||||
@@ -135,6 +136,54 @@ async def test_no_data(audio_out_socket, vad_agent):
|
||||
assert len(vad_agent.audio_buffer) == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_streaming_loop_reset_needed(audio_out_socket, vad_agent):
|
||||
"""Test that _reset_needed branch works as expected."""
|
||||
vad_agent._reset_needed = True
|
||||
vad_agent._ready.set()
|
||||
vad_agent._paused.set()
|
||||
vad_agent._running = True
|
||||
vad_agent.audio_buffer = np.array([1.0], dtype=np.float32)
|
||||
vad_agent.i_since_speech = 0
|
||||
|
||||
# Mock _reset_stream to stop the loop by setting _running=False
|
||||
async def mock_reset():
|
||||
vad_agent._running = False
|
||||
|
||||
vad_agent._reset_stream = mock_reset
|
||||
|
||||
# Needs a poller to avoid AssertionError
|
||||
vad_agent.audio_in_poller = AsyncMock()
|
||||
vad_agent.audio_in_poller.poll.return_value = None
|
||||
|
||||
await vad_agent._streaming_loop()
|
||||
|
||||
assert vad_agent._reset_needed is False
|
||||
assert len(vad_agent.audio_buffer) == 0
|
||||
assert vad_agent.i_since_speech == settings.behaviour_settings.vad_initial_since_speech
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_streaming_loop_no_data_clears_buffer(audio_out_socket, vad_agent):
|
||||
"""Test that if poll returns None, buffer is cleared if not empty."""
|
||||
vad_agent.audio_buffer = np.array([1.0], dtype=np.float32)
|
||||
vad_agent._ready.set()
|
||||
vad_agent._paused.set()
|
||||
vad_agent._running = True
|
||||
|
||||
class MockPoller:
|
||||
async def poll(self, timeout_ms=None):
|
||||
vad_agent._running = False # stop after one poll
|
||||
return None
|
||||
|
||||
vad_agent.audio_in_poller = MockPoller()
|
||||
|
||||
await vad_agent._streaming_loop()
|
||||
|
||||
assert len(vad_agent.audio_buffer) == 0
|
||||
assert vad_agent.i_since_speech == settings.behaviour_settings.vad_initial_since_speech
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_vad_model_load_failure_stops_agent(vad_agent):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user