"""Tests for duty-schedule JSON parser.""" from datetime import date import pytest from duty_teller.importers.duty_schedule import ( DUTY_MARKERS, UNAVAILABLE_MARKER, VACATION_MARKER, DutyScheduleParseError, parse_duty_schedule, ) def test_parse_valid_schedule(): raw = ( '{"meta": {"start_date": "2026-02-16", "weeks": 2}, ' '"schedule": [' '{"name": "Ivanov I.I.", "duty": "; ; \u0431 ; \u0411 ; \u0432 ; ;"}, ' '{"name": "Petrov P.P.", "duty": " ; \u041d ; \u041e ; ; ; ;"}' "]}" ).encode("utf-8") result = parse_duty_schedule(raw) assert result.start_date == date(2026, 2, 16) # Petrov has 7 cells -> end = start + 6 assert result.end_date == date(2026, 2, 22) assert len(result.entries) == 2 by_name = {e.full_name: e for e in result.entries} assert "Ivanov I.I." in by_name assert "Petrov P.P." in by_name # Ivanov: only duty (б, Б, в) -> 2026-02-18, 19, 20 ivan = by_name["Ivanov I.I."] assert sorted(ivan.duty_dates) == [ date(2026, 2, 18), date(2026, 2, 19), date(2026, 2, 20), ] assert ivan.unavailable_dates == [] assert ivan.vacation_dates == [] # Petrov: one Н (unavailable), one О (vacation) -> 2026-02-17, 18 petr = by_name["Petrov P.P."] assert petr.duty_dates == [] assert petr.unavailable_dates == [date(2026, 2, 17)] assert petr.vacation_dates == [date(2026, 2, 18)] def test_parse_empty_duty_string(): raw = b'{"meta": {"start_date": "2026-02-01"}, "schedule": [{"name": "A", "duty": ""}]}' result = parse_duty_schedule(raw) assert result.start_date == date(2026, 2, 1) assert result.end_date == date(2026, 2, 1) assert len(result.entries) == 1 assert result.entries[0].full_name == "A" assert result.entries[0].duty_dates == [] assert result.entries[0].unavailable_dates == [] assert result.entries[0].vacation_dates == [] def test_parse_invalid_json(): with pytest.raises(DutyScheduleParseError, match="Invalid JSON"): parse_duty_schedule(b"not json") def test_parse_missing_meta(): with pytest.raises(DutyScheduleParseError, match="meta"): parse_duty_schedule(b'{"schedule": []}') def test_parse_missing_start_date(): with pytest.raises(DutyScheduleParseError, match="start_date"): parse_duty_schedule(b'{"meta": {"weeks": 1}, "schedule": []}') def test_parse_invalid_start_date(): with pytest.raises(DutyScheduleParseError, match="start_date|Invalid"): parse_duty_schedule(b'{"meta": {"start_date": "not-a-date"}, "schedule": []}') def test_parse_missing_schedule(): with pytest.raises(DutyScheduleParseError, match="schedule"): parse_duty_schedule(b'{"meta": {"start_date": "2026-02-01"}}') def test_parse_schedule_not_array(): with pytest.raises(DutyScheduleParseError, match="schedule"): parse_duty_schedule(b'{"meta": {"start_date": "2026-02-01"}, "schedule": {}}') def test_parse_schedule_item_missing_name(): with pytest.raises(DutyScheduleParseError, match="name"): parse_duty_schedule( b'{"meta": {"start_date": "2026-02-01"}, "schedule": [{"duty": ";"}]}' ) def test_parse_schedule_item_empty_name(): with pytest.raises(DutyScheduleParseError, match="empty"): parse_duty_schedule( b'{"meta": {"start_date": "2026-02-01"}, "schedule": [{"name": " ", "duty": ""}]}' ) def test_duty_markers(): assert DUTY_MARKERS == frozenset({"б", "Б", "в", "В"}) assert "Н" not in DUTY_MARKERS and "О" not in DUTY_MARKERS def test_unavailable_and_vacation_markers(): assert UNAVAILABLE_MARKER == "Н" assert VACATION_MARKER == "О" raw = ( '{"meta": {"start_date": "2026-02-01"}, "schedule": [' '{"name": "X", "duty": "\u041d; \u041e; \u0432"}]}' ).encode("utf-8") result = parse_duty_schedule(raw) entry = result.entries[0] assert entry.unavailable_dates == [date(2026, 2, 1)] assert entry.vacation_dates == [date(2026, 2, 2)] assert entry.duty_dates == [date(2026, 2, 3)]