import numpy as np from src.detector import MotionDetector from src.models import DetectionConfig, DetectionState def detector() -> MotionDetector: return MotionDetector(DetectionConfig(movement_threshold=2.0, alert_after=3.0, capture_interval=1.0), downscale_step=1) def test_identical_images_score_near_zero() -> None: image = np.zeros((10, 10, 3), dtype=np.uint8) assert detector().calculate_motion_score(image, image) == 0.0 def test_different_images_score_is_higher() -> None: first = np.zeros((10, 10, 3), dtype=np.uint8) second = np.full((10, 10, 3), 255, dtype=np.uint8) assert detector().calculate_motion_score(first, second) > 90.0 def test_still_time_accumulates_below_threshold() -> None: det = detector() image = np.zeros((10, 10, 3), dtype=np.uint8) det.update(image, 1.0) status = det.update(image, 1.0) assert status.state == DetectionState.STILL_WAITING assert status.still_time == 1.0 status = det.update(image, 1.0) assert status.still_time == 2.0 def test_still_time_resets_when_movement_returns() -> None: det = detector() still = np.zeros((10, 10, 3), dtype=np.uint8) moved = np.full((10, 10, 3), 255, dtype=np.uint8) det.update(still, 1.0) det.update(still, 1.0) status = det.update(moved, 1.0) assert status.state == DetectionState.MOVING assert status.still_time == 0.0 def test_only_one_alert_during_one_still_period() -> None: det = detector() image = np.zeros((10, 10, 3), dtype=np.uint8) det.update(image, 1.0) assert not det.update(image, 1.0).should_alert assert not det.update(image, 1.0).should_alert assert det.update(image, 1.0).should_alert assert not det.update(image, 1.0).should_alert assert not det.update(image, 1.0).should_alert assert det.state == DetectionState.ALERTED def test_alert_rearms_after_movement_resumes() -> None: det = detector() still = np.zeros((10, 10, 3), dtype=np.uint8) moved = np.full((10, 10, 3), 255, dtype=np.uint8) det.update(still, 1.0) det.update(still, 1.0) det.update(still, 1.0) assert det.update(still, 1.0).should_alert assert det.update(moved, 1.0).state == DetectionState.MOVING det.update(moved, 1.0) det.update(moved, 1.0) assert det.update(moved, 1.0).should_alert