- Added a new command `/import_duty_schedule` for importing duty schedules via JSON, restricted to admin users. - Introduced a two-step import process: specifying handover time and uploading a JSON file. - Updated the database schema to allow `telegram_user_id` to be nullable for user creation by full name. - Implemented repository functions for user management, including `get_or_create_user_by_full_name` and `delete_duties_in_range`. - Enhanced README documentation with details on the new import command and JSON format requirements. - Added comprehensive tests for the duty schedule parser and integration tests for the import functionality.
93 lines
3.1 KiB
Python
93 lines
3.1 KiB
Python
"""Tests for duty-schedule JSON parser."""
|
||
|
||
from datetime import date
|
||
|
||
import pytest
|
||
|
||
from importers.duty_schedule import (
|
||
DUTY_MARKERS,
|
||
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
|
||
names = [e[0] for e in result.entries]
|
||
assert "Ivanov I.I." in names
|
||
assert "Petrov P.P." in names
|
||
by_name = {e[0]: e[1] for e in result.entries}
|
||
# Ivanov: indices 2, 3, 4 are duty (б, Б, в) -> 2026-02-18, 19, 20
|
||
ivan_dates = sorted(by_name["Ivanov I.I."])
|
||
assert ivan_dates == [date(2026, 2, 18), date(2026, 2, 19), date(2026, 2, 20)]
|
||
# Petrov: indices 1, 2 (Н, О) -> 2026-02-17, 18
|
||
petr_dates = sorted(by_name["Petrov P.P."])
|
||
assert petr_dates == [date(2026, 2, 17), 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 result.entries == [("A", [])]
|
||
|
||
|
||
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 "б" in DUTY_MARKERS and "Б" in DUTY_MARKERS and "в" in DUTY_MARKERS
|