test: add first unit test for VAD agent

Mocking audio input probabilities, checking whether it publishes audio data on the output socket.

ref: N25B-213
This commit is contained in:
Twirre Meulenbelt
2025-10-23 17:40:47 +02:00
parent 6391af883a
commit ca5e59d029
4 changed files with 102 additions and 57 deletions

View File

@@ -0,0 +1,45 @@
from unittest.mock import AsyncMock, MagicMock
import numpy as np
import pytest
from control_backend.agents.vad_agent import Streaming
@pytest.fixture
def audio_in_socket():
return AsyncMock()
@pytest.fixture
def audio_out_socket():
return AsyncMock()
@pytest.fixture
def streaming(audio_in_socket, audio_out_socket):
return Streaming(audio_in_socket, audio_out_socket)
@pytest.mark.asyncio
async def test_voice_activity_detected(audio_in_socket, audio_out_socket, streaming):
# After three chunks of audio with speech probability of 1.0, then four chunks of audio with
# speech probability of 0.0, it should send a message over the audio out socket
probabilities = [1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0]
model_item = MagicMock()
model_item.item.side_effect = probabilities
streaming.model = MagicMock()
streaming.model.return_value = model_item
audio_in_poller = AsyncMock()
audio_in_poller.poll.return_value = np.empty(shape=512, dtype=np.float32)
streaming.audio_in_poller = audio_in_poller
for _ in probabilities:
await streaming.run()
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 5 chunks (3 with speech, 2 as padding)
assert len(data) == 512*4*5

37
test/unit/conftest.py Normal file
View File

@@ -0,0 +1,37 @@
import sys
from unittest.mock import MagicMock
import sys
from unittest.mock import MagicMock
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()
mock_spade.behaviour = MagicMock()
mock_spade_bdi = MagicMock()
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,), {})
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