import pytest import numpy as np from unittest.mock import Mock, patch from f12_route_chunk_manager import RouteChunkManager, ChunkHandle, ChunkConfig from f02_1_flight_lifecycle_manager import GPSPoint from f07_sequential_visual_odometry import RelativePose from f09_local_geospatial_anchoring import Sim3Transform @pytest.fixture def mocks(): return { "f03": Mock(), "f05": Mock(), "f08": Mock(), "f10": Mock() } @pytest.fixture def rcm(mocks): return RouteChunkManager( f03=mocks["f03"], f05=mocks["f05"], f08=mocks["f08"], f10=mocks["f10"], config=ChunkConfig(min_frames_for_matching=5, max_frames_per_chunk=20) ) @pytest.fixture def dummy_relative_pose(): return RelativePose(translation=np.array([1, 0, 0]), rotation=np.eye(3), confidence=0.9, inlier_count=50, total_matches=100, tracking_good=True) class TestRouteChunkManager: # --- 12.01 Chunk Lifecycle Management --- def test_create_chunk(self, rcm, mocks): chunk = rcm.create_chunk("flight_1", 100) assert chunk.flight_id == "flight_1" assert chunk.start_frame_id == 100 assert chunk.is_active is True assert chunk.chunk_id in rcm._chunks mocks["f10"].create_chunk_subgraph.assert_called_once_with("flight_1", chunk.chunk_id, 100) def test_add_frame_to_active_chunk(self, rcm, dummy_relative_pose, mocks): mocks["f10"].add_relative_factor_to_chunk.return_value = True chunk = rcm.create_chunk("flight_1", 100) success = rcm.add_frame_to_chunk(chunk.chunk_id, 101, dummy_relative_pose) assert success is True assert 101 in chunk.frames assert chunk.end_frame_id == 101 mocks["f10"].add_relative_factor_to_chunk.assert_called_once() def test_add_frame_to_inactive_chunk_fails(self, rcm, dummy_relative_pose): chunk = rcm.create_chunk("flight_1", 100) rcm.deactivate_chunk(chunk.chunk_id) success = rcm.add_frame_to_chunk(chunk.chunk_id, 101, dummy_relative_pose) assert success is False def test_get_active_chunk(self, rcm): chunk1 = rcm.create_chunk("flight_1", 100) rcm.create_chunk("flight_2", 200) # Different flight active = rcm.get_active_chunk("flight_1") assert active.chunk_id == chunk1.chunk_id rcm.deactivate_chunk(chunk1.chunk_id) assert rcm.get_active_chunk("flight_1") is None # --- 12.02 Chunk Data Retrieval --- def test_get_chunk_frames_and_images(self, rcm, mocks): chunk = rcm.create_chunk("flight_1", 1) chunk.frames = [1, 2, 3] assert rcm.get_chunk_frames(chunk.chunk_id) == [1, 2, 3] mock_img = Mock(image=np.zeros((10, 10))) mocks["f05"].get_image_by_sequence.return_value = mock_img images = rcm.get_chunk_images(chunk.chunk_id) assert len(images) == 3 assert mocks["f05"].get_image_by_sequence.call_count == 3 def test_get_chunk_composite_descriptor(self, rcm, mocks): chunk = rcm.create_chunk("flight_1", 1) chunk.frames = [1, 2] mocks["f05"].get_image_by_sequence.return_value = Mock(image=np.zeros((10, 10))) mocks["f08"].compute_chunk_descriptor.return_value = np.ones(4096) desc = rcm.get_chunk_composite_descriptor(chunk.chunk_id) assert desc is not None assert desc.shape == (4096,) mocks["f08"].compute_chunk_descriptor.assert_called_once() def test_get_chunk_bounds(self, rcm, mocks): chunk = rcm.create_chunk("flight_1", 1) mocks["f10"].get_chunk_trajectory.return_value = {} # Returns dict, not mock object # Unanchored bounds bounds = rcm.get_chunk_bounds(chunk.chunk_id) assert bounds.confidence < 0.5 # Anchored bounds chunk.has_anchor = True chunk.anchor_gps = GPSPoint(lat=48.0, lon=37.0) bounds_anchored = rcm.get_chunk_bounds(chunk.chunk_id) assert bounds_anchored.confidence > 0.5 assert bounds_anchored.estimated_center.lat == 48.0 # --- 12.03 Chunk Matching Coordination --- def test_is_chunk_ready_for_matching(self, rcm): chunk = rcm.create_chunk("flight_1", 1) # Too few frames (<5) chunk.frames = [1, 2, 3] assert rcm.is_chunk_ready_for_matching(chunk.chunk_id) is False # Just right (5) chunk.frames = [1, 2, 3, 4, 5] assert rcm.is_chunk_ready_for_matching(chunk.chunk_id) is True # Already matched chunk.matching_status = "anchored" assert rcm.is_chunk_ready_for_matching(chunk.chunk_id) is False def test_mark_chunk_anchored_transactional(self, rcm, mocks): chunk = rcm.create_chunk("flight_1", 1) # F10 fails mocks["f10"].add_chunk_anchor.return_value = False assert rcm.mark_chunk_anchored(chunk.chunk_id, 1, GPSPoint(lat=0, lon=0)) is False assert chunk.has_anchor is False # F10 succeeds mocks["f10"].add_chunk_anchor.return_value = True assert rcm.mark_chunk_anchored(chunk.chunk_id, 1, GPSPoint(lat=0, lon=0)) is True assert chunk.has_anchor is True assert chunk.matching_status == "anchored" def test_merge_chunks_transactional(self, rcm, mocks): main_chunk = rcm.create_chunk("flight_1", 1) new_chunk = rcm.create_chunk("flight_1", 10) new_chunk.frames = [10, 11] transform = Sim3Transform(translation=np.zeros(3), rotation=np.eye(3), scale=1.0) mocks["f10"].merge_chunk_subgraphs.return_value = True assert rcm.merge_chunks(main_chunk.chunk_id, new_chunk.chunk_id, transform) is True assert new_chunk.is_active is False assert new_chunk.matching_status == "merged" assert 10 in main_chunk.frames assert 11 in main_chunk.frames assert mocks["f03"].save_chunk_state.call_count == 2