"""Test capture manager — file naming, JSON sidecars, session tracking. All tests use DemoCamera for zero-hardware-dependency captures. """ import json from pathlib import Path import pytest from birdcage_tui.camera import DemoCamera from birdcage_tui.capture_manager import ( CaptureManager, CaptureMetadata, CaptureSession, _build_filename, _sanitize_name, ) @pytest.fixture def manager(tmp_path: Path) -> CaptureManager: """CaptureManager with DemoCamera in a temp directory.""" backend = DemoCamera() return CaptureManager(backend, output_dir=tmp_path) @pytest.fixture def tracking_metadata() -> CaptureMetadata: """Metadata simulating an active tracking capture.""" return CaptureMetadata( mount_az=245.30, mount_el=34.20, target_name="ISS (ZARYA)", target_type="satellite", target_id="25544", target_az=245.30, target_el=34.20, target_distance_km=420.0, target_range_rate=-2.1, tracking_mode="craft", tracking_status="TRACKING", trigger="manual", ) def test_capture_writes_jpeg(manager: CaptureManager, tracking_metadata): """Capture should produce a JPEG file.""" result = manager.capture(tracking_metadata) assert result.success assert result.path.exists() assert result.path.suffix == ".jpg" def test_capture_writes_json_sidecar(manager: CaptureManager, tracking_metadata): """JSON sidecar should always be written alongside JPEG.""" result = manager.capture(tracking_metadata) assert result.success # Find the JSON sidecar json_files = list(result.path.parent.glob("*.json")) assert len(json_files) == 1 data = json.loads(json_files[0].read_text()) assert data["mount_az"] == 245.30 assert data["mount_el"] == 34.20 assert data["target_name"] == "ISS (ZARYA)" assert data["trigger"] == "manual" def test_filename_generation(): """Filenames should follow the naming convention.""" meta = CaptureMetadata( mount_az=245.30, mount_el=34.20, target_name="ISS (ZARYA)", timestamp="2026-02-16T08:25:00Z", ) name = _build_filename(meta, "jpg") assert name.startswith("ISS-ZARYA_") assert "245.30" in name assert "34.20" in name assert name.endswith(".jpg") def test_filename_sanitization(): """Special characters should be stripped from target names.""" assert _sanitize_name("ISS (ZARYA)") == "ISS-ZARYA" assert _sanitize_name("NOAA 19") == "NOAA-19" assert _sanitize_name("") == "manual" assert _sanitize_name("a/b\\c:d") == "abcd" def test_filename_no_target(): """Without a target name, filename should use 'manual'.""" meta = CaptureMetadata( mount_az=180.0, mount_el=45.0, timestamp="2026-02-16T12:00:00Z", ) name = _build_filename(meta, "jpg") assert name.startswith("manual_") def test_session_counter(): """Session sequence should increment monotonically.""" session = CaptureSession() assert session.next_sequence() == 1 assert session.next_sequence() == 2 assert session.next_sequence() == 3 assert session.capture_count == 3 def test_session_id_unique(): """Each session should have a unique ID.""" s1 = CaptureSession() s2 = CaptureSession() assert s1.session_id != s2.session_id def test_subdirectory_creation(manager: CaptureManager, tracking_metadata): """Captures should go into date-based subdirectories.""" result = manager.capture(tracking_metadata) assert result.success # The parent should be a date directory like "2026-02-16" parent = result.path.parent.name assert len(parent) == 10 # YYYY-MM-DD assert parent.count("-") == 2 def test_multiple_captures_increment(manager: CaptureManager, tracking_metadata): """Multiple captures should increment the session counter.""" r1 = manager.capture(tracking_metadata) r2 = manager.capture(tracking_metadata) assert r1.success and r2.success assert manager.session.capture_count == 2 # Filenames should differ assert r1.path.name != r2.path.name def test_pillow_fallback(manager: CaptureManager): """Manager should report Pillow status without crashing.""" # This test just verifies the property doesn't throw _ = manager.has_pillow # True if installed, False if not def test_astropy_fallback(manager: CaptureManager): """Manager should report astropy status without crashing.""" _ = manager.has_astropy # True if installed, False if not def test_metadata_session_populated(manager: CaptureManager, tracking_metadata): """After capture, metadata should have session info populated.""" manager.capture(tracking_metadata) assert tracking_metadata.session_id == manager.session.session_id assert tracking_metadata.sequence_number == 1 def test_manual_capture_no_target(manager: CaptureManager): """Capture without tracking should work with minimal metadata.""" meta = CaptureMetadata( mount_az=180.0, mount_el=45.0, trigger="manual", ) result = manager.capture(meta) assert result.success # JSON sidecar should exist json_files = list(result.path.parent.glob("*.json")) assert len(json_files) >= 1