"""Integration tests: real SapiMouse loading, filtering, caching, error paths. These tests assume the SapiMouse dataset is present at the standard project location (sapimouse_data/sapimouse/). They will skip otherwise — so the core unit tests still pass on a fresh clone. """ from __future__ import annotations import math from pathlib import Path import pytest import human_mouse as hm from human_mouse import _replay as replay_mod REPO_ROOT = Path(__file__).resolve().parents[1] DATA_ROOT = REPO_ROOT / "sapimouse_data" / "sapimouse" pytestmark = pytest.mark.skipif( not DATA_ROOT.exists(), reason="SapiMouse dataset not available at the default location", ) # --------------------------------------------------------- happy-path loading def test_load_all_segments_finds_segments(): segs = hm.load_all_segments(DATA_ROOT) assert len(segs) > 1000, f"expected thousands, got {len(segs)}" assert all(len(s.points) >= 50 for s in segs) def test_segment_properties_consistent(): segs = hm.load_all_segments(DATA_ROOT) s = segs[0] sx, sy = s.start ex, ey = s.end assert s.straight_distance == pytest.approx(math.hypot(ex - sx, ey - sy)) assert s.path_length >= s.straight_distance - 1e-6 assert s.path_ratio >= 1.0 - 1e-6 assert s.duration_ms >= 0 # -------------------------------------------------------------- pick_segments def test_pick_segments_basic(): segs = hm.load_all_segments(DATA_ROOT) picked = hm.pick_segments(segs, n=3, target_distance=500, seed=42) assert len(picked) == 3 assert all(0.25 * 500 < p.straight_distance < 1.75 * 500 for p in picked) def test_pick_segments_distinct_sessions(): segs = hm.load_all_segments(DATA_ROOT) picked = hm.pick_segments(segs, n=5, target_distance=800, distinct_sessions=True, seed=1) keys = {(p.user, p.session) for p in picked} assert len(keys) == 5 def test_pick_segments_seed_is_deterministic(): segs = hm.load_all_segments(DATA_ROOT) a = hm.pick_segments(segs, n=3, target_distance=800, seed=7) b = hm.pick_segments(segs, n=3, target_distance=800, seed=7) assert [(p.user, p.session) for p in a] == [(p.user, p.session) for p in b] def test_pick_segments_path_ratio_filter(): segs = hm.load_all_segments(DATA_ROOT) picked = hm.pick_segments(segs, n=10, target_distance=800, max_path_ratio=1.3, seed=1) assert all(p.path_ratio <= 1.3 for p in picked) # ------------------------------------------------------------------ errors def test_load_missing_directory_raises(): with pytest.raises(FileNotFoundError, match="SapiMouse"): hm.load_all_segments("/nonexistent/path") def test_pick_segments_no_match_raises(): segs = hm.load_all_segments(DATA_ROOT) with pytest.raises(ValueError, match="No segments matched"): hm.pick_segments(segs, n=1, target_distance=999_999, distance_tolerance=0.01) def test_pick_segments_not_enough_distinct_raises(): segs = hm.load_all_segments(DATA_ROOT) with pytest.raises(ValueError, match="distinct"): hm.pick_segments(segs, n=10_000, target_distance=800) # ------------------------------------------------------------------- cache def test_replay_cache_reuses_same_data_root(monkeypatch): replay_mod._SEGMENT_CACHE.clear() calls = {"n": 0} original = hm.load_all_segments def counting(*a, **kw): calls["n"] += 1 return original(*a, **kw) monkeypatch.setattr(replay_mod, "load_all_segments", counting) _ = replay_mod._cached_segments(DATA_ROOT) _ = replay_mod._cached_segments(DATA_ROOT) _ = replay_mod._cached_segments(DATA_ROOT) assert calls["n"] == 1 # ------------------------------------------------------ end-to-end replay_random class _FakeMouse: def __init__(self) -> None: self.moves: list[tuple[int, int]] = [] def move(self, x: int, y: int) -> None: self.moves.append((x, y)) class _FakePage: """A Page-like duck that satisfies the _PageLike Protocol.""" def __init__(self) -> None: self.mouse = _FakeMouse() def test_replay_random_drives_page_endpoints(): page = _FakePage() seg = hm.replay_random( page, start=(100, 100), end=(900, 500), data_root=DATA_ROOT, density=2, speed=10_000.0, seed=1, ) assert isinstance(seg, hm.Segment) assert len(page.mouse.moves) >= 50 assert page.mouse.moves[0] == (100, 100) assert page.mouse.moves[-1] == (900, 500) def test_replay_random_returns_filtered_segment(): page = _FakePage() seg = hm.replay_random( page, start=(0, 0), end=(800, 0), data_root=DATA_ROOT, density=1, speed=10_000.0, seed=2, max_path_ratio=1.5, ) assert seg.path_ratio <= 1.5