"""WAV-запись вызова: формат, микширование и защищённая выдача.""" import os import wave from datetime import UTC, datetime from types import SimpleNamespace from uuid import uuid4 import numpy as np from fastapi.testclient import TestClient from app.api.auth import Principal from app.api.http import sessions from app.domain.roles import Role from app.main import app from app.voice.recording import CallRecorder def test_recorder_mixes_16_and_24_khz_into_atomic_wav(tmp_path): ticks = iter((10.0, 10.0, 10.02)) path = tmp_path / "call.wav" recorder = CallRecorder(path, clock=lambda: next(ticks)) recorder.add_pcm(np.full(320, 1000, dtype="= 640 samples = np.frombuffer(source.readframes(source.getnframes()), dtype="= 2000 def test_recorder_recovers_audio_journal_after_process_restart(tmp_path): path = tmp_path / "interrupted.wav" clock_value = [10.0] clock = lambda: clock_value[0] first_process = CallRecorder(path, clock=clock) first_process.add_pcm((1000).to_bytes(2, "little", signed=True) * 320, sample_rate=16_000) journal = path.with_suffix(".wav.journal") # Simulate power loss halfway through a journal record. The next process # must keep all complete audio and discard only the torn tail. first_process._journal.close() with journal.open("ab") as partial: partial.write(b"\x40\x01\x00\x00\x00\x00\x00\x00\x40\x01\x00\x00\x02\x00") clock_value[0] = 50.0 # monotonic origin changed across host restart recovered = CallRecorder(path, clock=clock) recovered.add_pcm((2000).to_bytes(2, "little", signed=True) * 320, sample_rate=16_000) recovered.finalize() with wave.open(str(path), "rb") as source: samples = np.frombuffer(source.readframes(source.getnframes()), dtype="