feat: establish voice assistant V1 baseline
This commit is contained in:
170
tests/test_protocol.py
Normal file
170
tests/test_protocol.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
Tests for the Voice Assistant WebSocket protocol.
|
||||
|
||||
Purpose:
|
||||
Verify that protocol message models enforce their declared
|
||||
event types and canonical audio contract.
|
||||
|
||||
Responsibilities:
|
||||
- Validate inbound protocol event discriminators.
|
||||
- Validate SessionStart audio configuration.
|
||||
- Reject messages whose type does not match their model.
|
||||
|
||||
Non-responsibilities:
|
||||
- Testing WebSocket transport behavior.
|
||||
- Testing session or turn lifecycle.
|
||||
- Testing STT, Agent, or TTS providers.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
from voice_assistant.audio.models import CANONICAL_AUDIO_FORMAT
|
||||
from voice_assistant.audio_gateway.app.protocol import (
|
||||
AgentResponse,
|
||||
AudioChunk,
|
||||
AudioStart,
|
||||
AudioStop,
|
||||
ErrorEvent,
|
||||
Interrupt,
|
||||
Interrupted,
|
||||
SessionEnd,
|
||||
SessionEnded,
|
||||
SessionReady,
|
||||
SessionStart,
|
||||
STTFinal,
|
||||
STTPartial,
|
||||
TurnEnded,
|
||||
TurnStarted,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model", "expected_type"),
|
||||
[
|
||||
(SessionStart, "session.start"),
|
||||
(AudioStart, "audio.start"),
|
||||
(AudioChunk, "audio.chunk"),
|
||||
(AudioStop, "audio.stop"),
|
||||
(Interrupt, "interrupt"),
|
||||
(SessionEnd, "session.end"),
|
||||
],
|
||||
)
|
||||
def test_protocol_event_has_expected_type(
|
||||
model,
|
||||
expected_type,
|
||||
):
|
||||
payload = {
|
||||
"type": expected_type,
|
||||
}
|
||||
|
||||
if model in (AudioChunk, AudioStop, Interrupt):
|
||||
payload["turn_id"] = 1
|
||||
|
||||
if model is AudioChunk:
|
||||
payload["sequence_number"] = 0
|
||||
|
||||
event = model.model_validate(payload)
|
||||
|
||||
assert event.type == expected_type
|
||||
|
||||
|
||||
def test_protocol_rejects_wrong_event_type():
|
||||
with pytest.raises(ValidationError):
|
||||
AudioStart.model_validate(
|
||||
{
|
||||
"type": "audio.stop",
|
||||
}
|
||||
)
|
||||
|
||||
def test_session_ready_output():
|
||||
event = SessionReady(
|
||||
protocol_version="1.0",
|
||||
session_id="session-1",
|
||||
audio=CANONICAL_AUDIO_FORMAT,
|
||||
)
|
||||
|
||||
assert event.model_dump() == {
|
||||
"type": "session.ready",
|
||||
"protocol_version": "1.0",
|
||||
"session_id": "session-1",
|
||||
"audio": {
|
||||
"encoding": "pcm_s16le",
|
||||
"sample_rate": 16000,
|
||||
"channels": 1,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model", "event_type"),
|
||||
[
|
||||
(TurnStarted, "turn.started"),
|
||||
(STTPartial, "stt.partial"),
|
||||
(STTFinal, "stt.final"),
|
||||
(AgentResponse, "agent.response"),
|
||||
(TurnEnded, "turn.ended"),
|
||||
(Interrupted, "interrupted"),
|
||||
],
|
||||
)
|
||||
def test_turn_output_event_types(
|
||||
model,
|
||||
event_type,
|
||||
):
|
||||
data = {
|
||||
"session_id": "session-1",
|
||||
"turn_id": 1,
|
||||
}
|
||||
|
||||
if model in (STTPartial, STTFinal, AgentResponse):
|
||||
data["text"] = "test"
|
||||
|
||||
event = model(**data)
|
||||
|
||||
assert event.type == event_type
|
||||
|
||||
|
||||
def test_session_ended_output():
|
||||
event = SessionEnded(
|
||||
session_id="session-1",
|
||||
)
|
||||
|
||||
assert event.type == "session.ended"
|
||||
assert event.session_id == "session-1"
|
||||
|
||||
def test_error_event():
|
||||
event = ErrorEvent(
|
||||
session_id="session-1",
|
||||
turn_id=1,
|
||||
code="TEST_ERROR",
|
||||
message="Something went wrong.",
|
||||
retryable=True,
|
||||
details={
|
||||
"reason": "test",
|
||||
},
|
||||
)
|
||||
|
||||
assert event.model_dump() == {
|
||||
"type": "error",
|
||||
"session_id": "session-1",
|
||||
"turn_id": 1,
|
||||
"code": "TEST_ERROR",
|
||||
"message": "Something went wrong.",
|
||||
"retryable": True,
|
||||
"details": {
|
||||
"reason": "test",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_error_event_defaults():
|
||||
event = ErrorEvent(
|
||||
code="TEST_ERROR",
|
||||
message="Something went wrong.",
|
||||
)
|
||||
|
||||
assert event.type == "error"
|
||||
assert event.session_id is None
|
||||
assert event.turn_id is None
|
||||
assert event.retryable is False
|
||||
assert event.details == {}
|
||||
|
||||
Reference in New Issue
Block a user