"""Tests for the Opus ctypes wrapper.""" import math import struct from unittest.mock import MagicMock, patch import pytest from derp.opus import CHANNELS, FRAME_BYTES, FRAME_SIZE, SAMPLE_RATE # -- Helpers ----------------------------------------------------------------- def _silence() -> bytes: """Generate one frame of silence (1920 bytes of zeros).""" return b"\x00" * FRAME_BYTES def _sine_frame(freq: float = 440.0) -> bytes: """Generate one 20ms frame of a sine wave at the given frequency.""" samples = [] for i in range(FRAME_SIZE): t = i / SAMPLE_RATE val = int(16000 * math.sin(2 * math.pi * freq * t)) samples.append(struct.pack(" 0 enc.close() def test_encode_sine(self): """Encoding a sine wave produces bytes output.""" lib = _mock_lib() with patch("derp.opus._load_lib", return_value=lib): from derp.opus import OpusEncoder enc = OpusEncoder() result = enc.encode(_sine_frame()) assert isinstance(result, bytes) enc.close() def test_encode_wrong_size(self): """Passing wrong buffer size raises ValueError.""" lib = _mock_lib() with patch("derp.opus._load_lib", return_value=lib): from derp.opus import OpusEncoder enc = OpusEncoder() with pytest.raises(ValueError, match="expected 1920"): enc.encode(b"\x00" * 100) enc.close() def test_encode_multi_frame(self): """Multiple sequential encodes work.""" lib = _mock_lib() with patch("derp.opus._load_lib", return_value=lib): from derp.opus import OpusEncoder enc = OpusEncoder() for _ in range(5): result = enc.encode(_silence()) assert isinstance(result, bytes) enc.close() def test_custom_bitrate(self): """Custom bitrate is passed to opus_encoder_ctl.""" lib = _mock_lib() with patch("derp.opus._load_lib", return_value=lib): from derp.opus import OpusEncoder enc = OpusEncoder(bitrate=96000) assert lib.opus_encoder_ctl.called enc.close() def test_init_failure(self): """RuntimeError on encoder init failure.""" lib = _mock_lib() lib.opus_encoder_init.return_value = -1 with patch("derp.opus._load_lib", return_value=lib): from derp.opus import OpusEncoder with pytest.raises(RuntimeError, match="opus_encoder_init"): OpusEncoder() def test_encode_failure(self): """RuntimeError on encode failure.""" lib = _mock_lib() lib.opus_encode.return_value = -1 with patch("derp.opus._load_lib", return_value=lib): from derp.opus import OpusEncoder enc = OpusEncoder() with pytest.raises(RuntimeError, match="opus_encode"): enc.encode(_silence()) def test_close_clears_state(self): """close() sets internal state to None.""" lib = _mock_lib() with patch("derp.opus._load_lib", return_value=lib): from derp.opus import OpusEncoder enc = OpusEncoder() enc.close() assert enc._state is None