"""Tests for the expression -> movement planner. The planner is pure logic (no robot SDK), so it is fully unit-testable. We inject a seeded RNG to make the "random" movement selection deterministic. """ import random import pytest from face_detection.behavior import Movement, MovementPlanner def test_known_expressions_have_movements(): planner = MovementPlanner() for expression in ("happy", "neutral"): move = planner.plan(expression) assert isinstance(move, Movement) assert move.duration > 0 def test_unknown_expression_falls_back_to_neutral(): planner = MovementPlanner(rng=random.Random(0)) fallback = planner.plan("does-not-exist") assert fallback in planner.movements_for("neutral") def test_selection_is_deterministic_with_seeded_rng(): a = MovementPlanner(rng=random.Random(42)).plan("happy") b = MovementPlanner(rng=random.Random(42)).plan("happy") assert a == b def test_selection_varies_across_seeds_for_multi_option_expression(): planner = MovementPlanner(rng=random.Random(123)) picks = {planner.plan("happy").description for _ in range(50)} # "happy" defines more than one movement, so we expect variety. assert len(picks) > 1 def test_idle_movement_available(): planner = MovementPlanner() idle = planner.idle() assert isinstance(idle, Movement) def test_movement_fields_are_within_safe_ranges(): planner = MovementPlanner() for expression in planner.expressions: for move in planner.movements_for(expression): if move.antennas is not None: left, right = move.antennas assert -1.5 <= left <= 1.5 assert -1.5 <= right <= 1.5 if move.body_yaw is not None: assert -1.0 <= move.body_yaw <= 1.0 assert 0.1 <= move.duration <= 5.0