- Introduced a new `event_type` column in the `duties` table to categorize duties as 'duty', 'unavailable', or 'vacation'. - Updated the duty schedule import functionality to parse and store event types from the JSON input. - Enhanced the API response to include event types for each duty, improving the calendar display logic. - Modified the web application to visually differentiate between duty types in the calendar and duty list. - Updated tests to cover new event type functionality and ensure correct parsing and storage of duties. - Revised README documentation to reflect changes in duty event types and their representation in the system.
168 lines
5.0 KiB
Python
168 lines
5.0 KiB
Python
"""Integration tests for duty-schedule import (parser + repo, no bot)."""
|
|
|
|
from datetime import date
|
|
|
|
import pytest
|
|
|
|
from db import init_db
|
|
from db.repository import get_duties
|
|
from db.session import get_session
|
|
from importers.duty_schedule import DutyScheduleEntry, DutyScheduleResult, parse_duty_schedule
|
|
|
|
from handlers.import_duty_schedule import _run_import
|
|
|
|
|
|
@pytest.fixture
|
|
def db_url():
|
|
return "sqlite:///:memory:"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _reset_db_session(db_url):
|
|
"""Ensure each test uses a fresh engine for :memory: (clear global cache for test URL)."""
|
|
import db.session as session_module
|
|
session_module._engine = None
|
|
session_module._SessionLocal = None
|
|
init_db(db_url)
|
|
yield
|
|
session_module._engine = None
|
|
session_module._SessionLocal = None
|
|
|
|
|
|
def test_import_creates_users_and_duties(db_url):
|
|
"""Import creates users by full_name and correct duty records."""
|
|
result = DutyScheduleResult(
|
|
start_date=date(2026, 2, 16),
|
|
end_date=date(2026, 2, 18),
|
|
entries=[
|
|
DutyScheduleEntry(
|
|
full_name="Ivanov I.I.",
|
|
duty_dates=[date(2026, 2, 16), date(2026, 2, 18)],
|
|
unavailable_dates=[],
|
|
vacation_dates=[],
|
|
),
|
|
DutyScheduleEntry(
|
|
full_name="Petrov P.P.",
|
|
duty_dates=[date(2026, 2, 17)],
|
|
unavailable_dates=[],
|
|
vacation_dates=[],
|
|
),
|
|
],
|
|
)
|
|
num_users, num_duty, num_unav, num_vac = _run_import(db_url, result, 6, 0)
|
|
assert num_users == 2
|
|
assert num_duty == 3
|
|
assert num_unav == 0
|
|
assert num_vac == 0
|
|
|
|
session = get_session(db_url)
|
|
try:
|
|
duties = get_duties(session, "2026-02-16", "2026-02-19")
|
|
finally:
|
|
session.close()
|
|
|
|
assert len(duties) == 3
|
|
starts = {d[0].start_at for d in duties}
|
|
assert "2026-02-16T06:00:00Z" in starts
|
|
assert "2026-02-17T06:00:00Z" in starts
|
|
assert "2026-02-18T06:00:00Z" in starts
|
|
for d, _ in duties:
|
|
assert d.event_type == "duty"
|
|
|
|
|
|
def test_import_replaces_duties_in_range(db_url):
|
|
"""Re-importing same range replaces old duties."""
|
|
result1 = DutyScheduleResult(
|
|
start_date=date(2026, 2, 16),
|
|
end_date=date(2026, 2, 17),
|
|
entries=[
|
|
DutyScheduleEntry(
|
|
full_name="Sidorov",
|
|
duty_dates=[date(2026, 2, 16), date(2026, 2, 17)],
|
|
unavailable_dates=[],
|
|
vacation_dates=[],
|
|
)
|
|
],
|
|
)
|
|
_run_import(db_url, result1, 9, 0)
|
|
|
|
session = get_session(db_url)
|
|
try:
|
|
duties_first = get_duties(session, "2026-02-16", "2026-02-18")
|
|
finally:
|
|
session.close()
|
|
assert len(duties_first) == 2
|
|
|
|
result2 = DutyScheduleResult(
|
|
start_date=date(2026, 2, 16),
|
|
end_date=date(2026, 2, 17),
|
|
entries=[
|
|
DutyScheduleEntry(
|
|
full_name="Sidorov",
|
|
duty_dates=[date(2026, 2, 17)],
|
|
unavailable_dates=[],
|
|
vacation_dates=[],
|
|
)
|
|
],
|
|
)
|
|
_run_import(db_url, result2, 9, 0)
|
|
|
|
session = get_session(db_url)
|
|
try:
|
|
duties_second = get_duties(session, "2026-02-16", "2026-02-18")
|
|
finally:
|
|
session.close()
|
|
assert len(duties_second) == 1
|
|
assert duties_second[0][0].start_at == "2026-02-17T09:00:00Z"
|
|
|
|
|
|
def test_import_full_flow_parse_then_import(db_url):
|
|
"""Parse real-looking JSON then run import."""
|
|
raw = (
|
|
'{"meta": {"start_date": "2026-02-16"}, '
|
|
'"schedule": [{"name": "Alexey A.", "duty": "\u0431; ; \u0432"}]}'
|
|
).encode("utf-8")
|
|
parsed = parse_duty_schedule(raw)
|
|
num_users, num_duty, num_unav, num_vac = _run_import(db_url, parsed, 6, 0)
|
|
assert num_users == 1
|
|
assert num_duty == 2
|
|
assert num_unav == 0
|
|
assert num_vac == 0
|
|
|
|
session = get_session(db_url)
|
|
try:
|
|
duties = get_duties(session, "2026-02-16", "2026-02-19")
|
|
finally:
|
|
session.close()
|
|
assert len(duties) == 2
|
|
assert duties[0][1] == "Alexey A."
|
|
assert duties[0][0].event_type == "duty"
|
|
|
|
|
|
def test_import_event_types_unavailable_vacation(db_url):
|
|
"""Import creates records with event_type duty, unavailable, vacation."""
|
|
result = DutyScheduleResult(
|
|
start_date=date(2026, 2, 16),
|
|
end_date=date(2026, 2, 18),
|
|
entries=[
|
|
DutyScheduleEntry(
|
|
full_name="Mixed User",
|
|
duty_dates=[date(2026, 2, 16)],
|
|
unavailable_dates=[date(2026, 2, 17)],
|
|
vacation_dates=[date(2026, 2, 18)],
|
|
),
|
|
],
|
|
)
|
|
num_users, num_duty, num_unav, num_vac = _run_import(db_url, result, 6, 0)
|
|
assert num_users == 1
|
|
assert num_duty == 1 and num_unav == 1 and num_vac == 1
|
|
|
|
session = get_session(db_url)
|
|
try:
|
|
duties = get_duties(session, "2026-02-16", "2026-02-19")
|
|
finally:
|
|
session.close()
|
|
assert len(duties) == 3
|
|
types = {d[0].event_type for d in duties}
|
|
assert types == {"duty", "unavailable", "vacation"}
|