cf-voice/tests/test_prefs.py
pyr0ball 24f04b67db feat: full voice pipeline — AST acoustic, accent, privacy, prosody, dimensional, trajectory, telephony, FastAPI app
New modules shipped (from Linnet integration):
- acoustic.py: AST (MIT/ast-finetuned-audioset-10-10-0.4593) replaces YAMNet stub;
  527 AudioSet classes mapped to queue/speaker/environ/scene labels; _LABEL_MAP
  includes hold_music, ringback, DTMF, background_shift, AMD signal chain
- accent.py: facebook/mms-lid-126 language ID → regional accent labels
  (en_gb, en_us, en_au, fr, es, de, zh, …); lazy-loaded, gated by CF_VOICE_ACCENT
- privacy.py: compound privacy risk scorer — public_env, background_voices,
  nature scene, accent signals; returns 0–3 score without storing any audio
- prosody.py: openSMILE-backed prosody extractor (sarcasm_risk, flat_f0_score,
  speech_rate, pitch_range); mock mode returns neutral values
- dimensional.py: audeering/wav2vec2-large-robust-12-ft-emotion-msp-dim
  valence/arousal/dominance scorer; gated by CF_VOICE_DIMENSIONAL
- trajectory.py: rolling buffer for arousal/valence deltas, trend detection
  (escalating/suppressed/stable), coherence scoring, suppression/reframe flags
- telephony.py: TelephonyBackend Protocol + MockTelephonyBackend + SignalWireBackend
  + FreeSWITCHBackend; CallSession dataclass; make_telephony() factory
- app.py: FastAPI service (port 8007) — /health + /classify; accepts base64 PCM
  chunks, returns full AudioEventOut including dimensional/prosody/accent fields
- prefs.py: voice preference helpers (elcor_mode, confidence_threshold,
  whisper_model, elcor_prior_frames); cf-core and env-var fallback

Tests: fix stale tests (YAMNetAcousticBackend → ASTAcousticBackend, scene field
added to AcousticResult, speaker_at gap now resolves dominant speaker not UNKNOWN,
make_io real path returns MicVoiceIO when sounddevice installed). 78 tests passing.

Closes #2, #3.
2026-04-18 22:36:58 -07:00

109 lines
4.1 KiB
Python

import os
import pytest
from cf_voice.prefs import (
PREF_CONFIDENCE_THRESHOLD,
PREF_ELCOR_MODE,
PREF_ELCOR_PRIOR_FRAMES,
PREF_WHISPER_MODEL,
get_confidence_threshold,
get_elcor_prior_frames,
get_voice_pref,
get_whisper_model,
is_elcor_enabled,
set_voice_pref,
)
class _DictStore:
"""In-memory preference store for testing."""
def __init__(self, data: dict | None = None) -> None:
self._data: dict = data or {}
def get(self, user_id, path, default=None):
return self._data.get(path, default)
def set(self, user_id, path, value):
self._data[path] = value
class TestGetVoicePref:
def test_returns_default_when_nothing_set(self, monkeypatch):
monkeypatch.delenv("CF_VOICE_ELCOR", raising=False)
val = get_voice_pref(PREF_ELCOR_MODE, store=_DictStore())
assert val is False
def test_explicit_store_takes_priority(self):
store = _DictStore({PREF_ELCOR_MODE: True})
assert get_voice_pref(PREF_ELCOR_MODE, store=store) is True
def test_env_fallback_bool(self, monkeypatch):
monkeypatch.setenv("CF_VOICE_ELCOR", "1")
assert get_voice_pref(PREF_ELCOR_MODE, store=_DictStore()) is True
def test_env_fallback_false(self, monkeypatch):
monkeypatch.setenv("CF_VOICE_ELCOR", "0")
assert get_voice_pref(PREF_ELCOR_MODE, store=_DictStore()) is False
def test_env_fallback_float(self, monkeypatch):
monkeypatch.setenv("CF_VOICE_CONFIDENCE_THRESHOLD", "0.7")
val = get_voice_pref(PREF_CONFIDENCE_THRESHOLD, store=_DictStore())
assert abs(val - 0.7) < 1e-9
def test_env_fallback_int(self, monkeypatch):
monkeypatch.setenv("CF_VOICE_ELCOR_PRIOR_FRAMES", "6")
val = get_voice_pref(PREF_ELCOR_PRIOR_FRAMES, store=_DictStore())
assert val == 6
def test_env_fallback_str(self, monkeypatch):
monkeypatch.setenv("CF_VOICE_WHISPER_MODEL", "medium")
val = get_voice_pref(PREF_WHISPER_MODEL, store=_DictStore())
assert val == "medium"
def test_store_beats_env(self, monkeypatch):
monkeypatch.setenv("CF_VOICE_ELCOR", "1")
store = _DictStore({PREF_ELCOR_MODE: False})
# store has explicit False — but store.get returns None for falsy values
# only if the key is absent; here key IS set so store wins
store._data[PREF_ELCOR_MODE] = True
assert get_voice_pref(PREF_ELCOR_MODE, store=store) is True
def test_unknown_key_returns_none(self):
val = get_voice_pref("voice.nonexistent", store=_DictStore())
assert val is None
class TestSetVoicePref:
def test_sets_in_store(self):
store = _DictStore()
set_voice_pref(PREF_ELCOR_MODE, True, store=store)
assert store._data[PREF_ELCOR_MODE] is True
def test_no_store_raises(self, monkeypatch):
# Patch _cf_core_store to return None (simulates no cf-core installed)
import cf_voice.prefs as prefs_mod
monkeypatch.setattr(prefs_mod, "_cf_core_store", lambda: None)
with pytest.raises(RuntimeError, match="No writable preference store"):
set_voice_pref(PREF_ELCOR_MODE, True)
class TestConvenienceHelpers:
def test_is_elcor_enabled_false_default(self, monkeypatch):
monkeypatch.delenv("CF_VOICE_ELCOR", raising=False)
assert is_elcor_enabled(store=_DictStore()) is False
def test_is_elcor_enabled_true_from_store(self):
store = _DictStore({PREF_ELCOR_MODE: True})
assert is_elcor_enabled(store=store) is True
def test_get_confidence_threshold_default(self, monkeypatch):
monkeypatch.delenv("CF_VOICE_CONFIDENCE_THRESHOLD", raising=False)
assert get_confidence_threshold(store=_DictStore()) == pytest.approx(0.55)
def test_get_whisper_model_default(self, monkeypatch):
monkeypatch.delenv("CF_VOICE_WHISPER_MODEL", raising=False)
assert get_whisper_model(store=_DictStore()) == "small"
def test_get_elcor_prior_frames_default(self, monkeypatch):
monkeypatch.delenv("CF_VOICE_ELCOR_PRIOR_FRAMES", raising=False)
assert get_elcor_prior_frames(store=_DictStore()) == 4