import json from datetime import datetime, timezone import pytest from coordination_engine.timing import CoordinationFrame, load_frame def test_one_hour_frame_sets_two_and_three_hour_deadlines(): frame = CoordinationFrame(3600) anchor = datetime(2026, 10, 15, 12, tzinfo=timezone.utc) assert frame.deadline(anchor, 2).hour == 14 assert frame.deadline(anchor, 3).hour == 15 def test_frame_change_rescales_all_multiples(): assert CoordinationFrame(86400).as_dict() == { "frame_seconds": 86400, "2f_seconds": 172800, "3f_seconds": 259200, } assert CoordinationFrame(600).as_dict() == { "frame_seconds": 600, "2f_seconds": 1200, "3f_seconds": 1800, } @pytest.mark.parametrize("value", [0, -1, True, 1.5, "3600", None]) def test_invalid_base_frame_is_refused(value): with pytest.raises(ValueError): CoordinationFrame(value) def test_naive_deadline_anchor_is_refused(): with pytest.raises(ValueError, match="timezone"): CoordinationFrame(3600).deadline(datetime(2026, 10, 15), 2) @pytest.mark.parametrize( "raw", [ '{"schema_version":"coordination-frame/v1","frame_seconds":3600,"frame_seconds":24}', '{"schema_version":"other","frame_seconds":3600}', '{"schema_version":"coordination-frame/v1","frame_seconds":3600,"ttl":5}', "[]", "{}", ], ) def test_invalid_configuration_is_refused_without_fallback(tmp_path, raw): path = tmp_path / "frame.json" path.write_text(raw) with pytest.raises(ValueError): load_frame(path) def test_explicit_config_is_read_without_wall_clock_or_environment_defaults(tmp_path): path = tmp_path / "frame.json" path.write_text(json.dumps({"schema_version": "coordination-frame/v1", "frame_seconds": 3600})) assert load_frame(path).seconds(2) == 7200