import pytest rclpy = pytest.importorskip('rclpy') def test_the_node_constructs_and_has_the_three_targets(): """Declaring a parameter twice raises at construction, and nothing else exercises the node.""" from hermes_thermal.thermal_node import ThermalNode rclpy.init() try: node = ThermalNode() assert [t.name for t in node.targets] == ['/camera', '/hermes_face', '/hermes_face_detector'] assert node.policy.level == 'ok' node.destroy_node() finally: rclpy.shutdown() def test_a_full_heat_then_cool_run_through_the_real_node_does_not_crash_and_steps_down_one_level_at_a_time(): """The bug this guards against: a logging call site whose severity changed (warn heating, info cooling) raised in the timer callback, so the node died on its first cooling step.""" from rclpy.parameter import Parameter from hermes_thermal.thermal_node import ThermalNode rclpy.init() try: node = ThermalNode() node.act = False # no other nodes here; this is about the policy path node.set_parameters([Parameter('dwell_s', value=0.0)]) levels = [] for temp in (60.0, 73.0, 78.0, 85.0, 60.0, 60.0, 60.0, 60.0): node.set_parameters([Parameter('fake_temp_c', value=temp)]) node._poll() # _poll, not _tick: let any exception surface levels.append(node.policy.level) assert levels == ['ok', 'warm', 'hot', 'critical', 'hot', 'warm', 'ok', 'ok'] node.destroy_node() finally: rclpy.shutdown() def test_the_tick_survives_an_unexpected_error(): from hermes_thermal.thermal_node import ThermalNode rclpy.init() try: node = ThermalNode() node._poll = lambda: (_ for _ in ()).throw(RuntimeError('boom')) node._tick() # must not raise node.destroy_node() finally: rclpy.shutdown()