feat: establish voice assistant V1 baseline

This commit is contained in:
2026-10-09 18:31:21 +00:00
commit cebcb3fd61
57 changed files with 9501 additions and 0 deletions

288
tests/test_config.py Normal file
View File

@@ -0,0 +1,288 @@
"""
Typed configuration models for the Voice Assistant.
Purpose:
Define and validate the configuration structures used by the
application and dynamic provider loading system.
Responsibilities:
- Define the application configuration for STT, Agent, and TTS.
- Define the provider catalog structure.
- Represent provider-specific options without coupling the core
configuration model to a specific vendor or implementation.
- Describe provider capabilities for discovery and future tooling.
Non-responsibilities:
- Loading YAML files.
- Reading environment variables or secrets.
- Selecting or instantiating provider implementations.
- Executing STT, Agent, or TTS functionality.
Architecture:
YAML
|
v
Config Loader
|
v
AppConfig / ProviderCatalog
|
v
Provider Loader
|
v
STTAdapter / AgentAdapter / TTSAdapter
"""
import pytest
import yaml
from voice_assistant.config.loader import load_config
from voice_assistant.config.loader import (
load_config,
load_provider_catalog,
)
def test_load_development_config():
config = load_config()
assert config.stt.provider == "nvidia"
assert config.stt.options["server"] == "127.0.0.1:9000"
assert config.stt.options["language_code"] == "pt-BR"
assert config.stt.options["automatic_punctuation"] is True
assert config.tts.provider == "voxcpm"
assert config.tts.options["server"] == "http://127.0.0.1:8000"
assert config.tts.options["model"] == "openbmb/VoxCPM2"
assert config.tts.options["voice"] == "default"
assert config.agent.provider == "mock"
def test_load_config_missing_file(tmp_path):
with pytest.raises(
FileNotFoundError,
match="Configuration file not found",
):
load_config(
environment="development",
config_dir=tmp_path,
)
def test_load_config_empty_file(tmp_path):
config_file = tmp_path / "development.yaml"
config_file.write_text(
"",
encoding="utf-8",
)
with pytest.raises(
ValueError,
match="Configuration file is empty",
):
load_config(
environment="development",
config_dir=tmp_path,
)
def test_load_config_root_must_be_mapping(tmp_path):
config_file = tmp_path / "development.yaml"
config_file.write_text(
"- item1\n- item2\n",
encoding="utf-8",
)
with pytest.raises(
ValueError,
match="Configuration root must be a mapping",
):
load_config(
environment="development",
config_dir=tmp_path,
)
def test_load_config_validates_required_fields(tmp_path):
config_file = tmp_path / "development.yaml"
config_file.write_text(
yaml.safe_dump(
{
"stt": {
"provider": "nvidia",
"server": "127.0.0.1:9000",
"language": "pt-BR",
},
"tts": {
"provider": "voxcpm",
"server": "http://127.0.0.1:8000",
"model": "openbmb/VoxCPM2",
"voice": "default",
},
# agent intentionally missing
}
),
encoding="utf-8",
)
with pytest.raises(
Exception,
match="agent",
):
load_config(
environment="development",
config_dir=tmp_path,
)
def test_load_provider_catalog():
catalog = load_provider_catalog()
assert "nvidia" in catalog.providers["stt"]
assert "voxcpm" in catalog.providers["tts"]
assert "mock" in catalog.providers["agent"]
voxcpm = catalog.providers["tts"]["voxcpm"]
assert voxcpm.name == "VoxCPM"
assert (
voxcpm.implementation
== "voice_assistant.tts.voxcpm:VoxCPMTTSAdapter"
)
assert voxcpm.capabilities == {
"realtime": True,
"streaming": True,
}
def test_load_provider_catalog_missing_file(tmp_path):
with pytest.raises(
FileNotFoundError,
match="Provider catalog not found",
):
load_provider_catalog(
config_dir=tmp_path,
)
def test_load_config_uses_app_env(
tmp_path,
monkeypatch,
):
development = tmp_path / "development.yaml"
development.write_text(
"""
stt:
provider: nvidia
options:
server: "dev-asr"
tts:
provider: voxcpm
options:
server: "dev-tts"
model: "dev-model"
voice: "default"
agent:
provider: mock
options: {}
""",
encoding="utf-8",
)
monkeypatch.setenv(
"APP_ENV",
"development",
)
config = load_config(
config_dir=tmp_path,
)
assert config.stt.options["server"] == "dev-asr"
assert config.tts.options["server"] == "dev-tts"
assert config.tts.options["model"] == "dev-model"
def test_load_config_rejects_unknown_environment(
tmp_path,
):
with pytest.raises(
FileNotFoundError,
match="Configuration file not found",
):
load_config(
environment="does-not-exist",
config_dir=tmp_path,
)
def test_load_config_uses_app_env(
tmp_path,
monkeypatch,
):
config_file = tmp_path / "staging.yaml"
config_file.write_text(
"""
stt:
provider: nvidia
options:
server: "staging-asr:9000"
tts:
provider: voxcpm
options:
server: "http://staging-tts:8000"
model: "staging-model"
voice: "staging-voice"
agent:
provider: mock
options: {}
""",
encoding="utf-8",
)
monkeypatch.setenv(
"APP_ENV",
"staging",
)
config = load_config(
config_dir=tmp_path,
)
assert config.stt.provider == "nvidia"
assert (
config.stt.options["server"]
== "staging-asr:9000"
)
assert config.tts.provider == "voxcpm"
assert (
config.tts.options["server"]
== "http://staging-tts:8000"
)
assert config.tts.options["model"] == "staging-model"
assert config.tts.options["voice"] == "staging-voice"
assert config.agent.provider == "mock"
def test_load_config_rejects_unknown_environment(
tmp_path,
):
with pytest.raises(
FileNotFoundError,
match="Configuration file not found",
):
load_config(
environment="does-not-exist",
config_dir=tmp_path,
)