restructure and test, pure python and rust transport both _work_
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
"""Tests for cht.stream.manager — StreamManager."""
|
||||
|
||||
import json
|
||||
import time
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
@@ -21,12 +20,15 @@ class TestInit:
|
||||
def test_session_id_custom(self, manager):
|
||||
assert manager.session_id == "test_session"
|
||||
|
||||
def test_recording_path(self, manager):
|
||||
def test_recording_path_delegates_to_recorder(self, manager):
|
||||
assert manager.recording_path.name == "recording_000.mp4"
|
||||
|
||||
def test_dirs_not_created_on_init(self, manager):
|
||||
assert not manager.stream_dir.exists()
|
||||
|
||||
def test_relay_url_from_recorder(self, manager):
|
||||
assert "4445" in manager.relay_url
|
||||
|
||||
|
||||
class TestSetupDirs:
|
||||
def test_creates_all_subdirs(self, manager):
|
||||
@@ -34,61 +36,74 @@ class TestSetupDirs:
|
||||
assert manager.stream_dir.is_dir()
|
||||
assert manager.frames_dir.is_dir()
|
||||
assert manager.transcript_dir.is_dir()
|
||||
assert manager.audio_dir.is_dir()
|
||||
assert manager.agent_dir.is_dir()
|
||||
|
||||
|
||||
class TestStopAll:
|
||||
@patch("cht.stream.manager.ff.stop_proc")
|
||||
def test_stops_all_procs(self, mock_stop, manager):
|
||||
proc = MagicMock()
|
||||
manager._procs = {"recorder": proc}
|
||||
def test_stop_all_calls_processor_and_recorder(self, manager):
|
||||
manager.processor.stop = MagicMock()
|
||||
manager.recorder.stop = MagicMock()
|
||||
manager.stop_all()
|
||||
mock_stop.assert_called_with(proc)
|
||||
assert len(manager._procs) == 0
|
||||
|
||||
def test_sets_stop_flag(self, manager):
|
||||
manager.stop_all()
|
||||
assert "stop" in manager._stop_flags
|
||||
|
||||
|
||||
class TestFrameIndex:
|
||||
def test_next_frame_number_empty(self, manager):
|
||||
manager.setup_dirs()
|
||||
assert manager._next_frame_number() == 1
|
||||
|
||||
def test_next_frame_number_with_existing(self, manager):
|
||||
manager.setup_dirs()
|
||||
index = [{"id": "F0001"}, {"id": "F0002"}]
|
||||
(manager.frames_dir / "index.json").write_text(json.dumps(index))
|
||||
assert manager._next_frame_number() == 3
|
||||
|
||||
def test_append_frame_index(self, manager):
|
||||
manager.setup_dirs()
|
||||
entry = {"id": "F0001", "timestamp": 5.0, "path": "/tmp/F0001.jpg", "sent_to_agent": False}
|
||||
manager._append_frame_index(entry)
|
||||
index = json.loads((manager.frames_dir / "index.json").read_text())
|
||||
assert len(index) == 1
|
||||
assert index[0]["id"] == "F0001"
|
||||
|
||||
def test_append_frame_index_accumulates(self, manager):
|
||||
manager.setup_dirs()
|
||||
for i in range(3):
|
||||
entry = {"id": f"F{i+1:04d}", "timestamp": float(i), "path": f"/tmp/F{i+1:04d}.jpg", "sent_to_agent": False}
|
||||
manager._append_frame_index(entry)
|
||||
index = json.loads((manager.frames_dir / "index.json").read_text())
|
||||
assert len(index) == 3
|
||||
manager.processor.stop.assert_called_once()
|
||||
manager.recorder.stop.assert_called_once()
|
||||
|
||||
|
||||
class TestSceneDetector:
|
||||
def test_start_scene_detector_stores_callback(self, manager):
|
||||
def test_python_path_sets_processor_callback(self, manager):
|
||||
"""Python path (recorder present): on_new_frames goes to processor, not recorder."""
|
||||
cb = MagicMock()
|
||||
manager.start_scene_detector(on_new_frames=cb)
|
||||
assert manager._on_new_frames is cb
|
||||
assert manager.processor._on_new_frames is cb
|
||||
|
||||
def test_update_scene_threshold(self, manager):
|
||||
manager.setup_dirs()
|
||||
# Mock restart_recorder to avoid launching ffmpeg
|
||||
manager.restart_recorder = MagicMock()
|
||||
def test_python_path_does_not_start_processor_scene_detector(self, manager):
|
||||
"""Python path: recorder owns scene detection — processor.start_scene_detector not called."""
|
||||
manager.processor.start_scene_detector = MagicMock()
|
||||
manager.start_scene_detector(on_new_frames=MagicMock())
|
||||
manager.processor.start_scene_detector.assert_not_called()
|
||||
|
||||
def test_rust_path_sets_callback_and_starts_detector(self, tmp_path):
|
||||
"""Rust path (no recorder): processor owns scene detection."""
|
||||
with patch("cht.stream.manager.SESSIONS_DIR", tmp_path):
|
||||
mgr = StreamManager.__new__(StreamManager)
|
||||
mgr.recorder = None
|
||||
mgr.processor = MagicMock()
|
||||
|
||||
from cht.stream.manager import SCENE_THRESHOLD
|
||||
cb = MagicMock()
|
||||
mgr.start_scene_detector(on_new_frames=cb)
|
||||
|
||||
mgr.processor.set_on_new_frames.assert_called_once_with(cb)
|
||||
mgr.processor.start_scene_detector.assert_called_once_with(threshold=SCENE_THRESHOLD)
|
||||
|
||||
def test_update_scene_threshold_restarts_recorder(self, manager):
|
||||
manager.recorder.update_scene_threshold = MagicMock()
|
||||
manager.update_scene_threshold(0.25)
|
||||
assert manager.scene_threshold == 0.25
|
||||
manager.restart_recorder.assert_called_once()
|
||||
manager.recorder.update_scene_threshold.assert_called_once_with(0.25)
|
||||
|
||||
|
||||
class TestFromRustSession:
|
||||
def test_attaches_without_recorder(self, tmp_path):
|
||||
session_dir = tmp_path / "20260410_120000"
|
||||
session_dir.mkdir()
|
||||
(session_dir / "stream").mkdir()
|
||||
|
||||
mgr = StreamManager.from_rust_session(session_dir)
|
||||
assert mgr.recorder is None
|
||||
assert mgr.session_id == "20260410_120000"
|
||||
|
||||
def test_relay_url_fallback_without_recorder(self, tmp_path):
|
||||
session_dir = tmp_path / "20260410_120000"
|
||||
session_dir.mkdir()
|
||||
(session_dir / "stream").mkdir()
|
||||
|
||||
mgr = StreamManager.from_rust_session(session_dir)
|
||||
assert "4445" in mgr.relay_url
|
||||
|
||||
def test_recorder_alive_returns_true_without_recorder(self, tmp_path):
|
||||
session_dir = tmp_path / "20260410_120000"
|
||||
session_dir.mkdir()
|
||||
(session_dir / "stream").mkdir()
|
||||
|
||||
mgr = StreamManager.from_rust_session(session_dir)
|
||||
assert mgr.recorder_alive() is True
|
||||
|
||||
Reference in New Issue
Block a user