import asyncio import pytest from cf_voice.models import VoiceFrame from cf_voice.io import MockVoiceIO, make_io from cf_voice.context import ContextClassifier def make_frame(**kwargs) -> VoiceFrame: defaults = dict( label="Calm and focused", confidence=0.8, speaker_id="speaker_a", shift_magnitude=0.0, timestamp=1.0, ) return VoiceFrame(**{**defaults, **kwargs}) class TestVoiceFrame: def test_is_reliable_above_threshold(self): assert make_frame(confidence=0.7).is_reliable(threshold=0.6) def test_is_reliable_below_threshold(self): assert not make_frame(confidence=0.4).is_reliable(threshold=0.6) def test_is_shift_above_threshold(self): assert make_frame(shift_magnitude=0.5).is_shift(threshold=0.3) def test_is_shift_below_threshold(self): assert not make_frame(shift_magnitude=0.1).is_shift(threshold=0.3) def test_default_reliable_threshold(self): assert make_frame(confidence=0.6).is_reliable() assert not make_frame(confidence=0.59).is_reliable() class TestMockVoiceIO: @pytest.mark.asyncio async def test_emits_frames(self): io = MockVoiceIO(interval_s=0.05, seed=42) frames = [] async for frame in io.stream(): frames.append(frame) if len(frames) >= 3: await io.stop() break assert len(frames) == 3 assert all(isinstance(f, VoiceFrame) for f in frames) @pytest.mark.asyncio async def test_confidence_in_range(self): io = MockVoiceIO(interval_s=0.05, seed=1) count = 0 async for frame in io.stream(): assert 0.0 <= frame.confidence <= 1.0 assert 0.0 <= frame.shift_magnitude <= 1.0 count += 1 if count >= 5: await io.stop() break @pytest.mark.asyncio async def test_timestamps_increase(self): io = MockVoiceIO(interval_s=0.05, seed=0) timestamps = [] async for frame in io.stream(): timestamps.append(frame.timestamp) if len(timestamps) >= 3: await io.stop() break assert timestamps == sorted(timestamps) def test_make_io_mock_env(self, monkeypatch): monkeypatch.setenv("CF_VOICE_MOCK", "1") io = make_io() assert isinstance(io, MockVoiceIO) def test_make_io_real_raises(self, monkeypatch): monkeypatch.delenv("CF_VOICE_MOCK", raising=False) with pytest.raises(NotImplementedError): make_io(mock=False) class TestContextClassifier: @pytest.mark.asyncio async def test_mock_passthrough(self): classifier = ContextClassifier.mock(interval_s=0.05, seed=7) frames = [] async for frame in classifier.stream(): frames.append(frame) if len(frames) >= 3: await classifier.stop() break assert len(frames) == 3 assert all(isinstance(f, VoiceFrame) for f in frames) @pytest.mark.asyncio async def test_from_env_mock(self, monkeypatch): monkeypatch.setenv("CF_VOICE_MOCK", "1") classifier = ContextClassifier.from_env(interval_s=0.05) async for frame in classifier.stream(): assert isinstance(frame, VoiceFrame) await classifier.stop() break