Files
duty-teller/tests/test_import_duty_schedule_integration.py
Nikolay Tatarinov 5331fac334 Add configuration rules, refactor settings management, and enhance import functionality
- Introduced a new configuration file `.cursorrules` to define coding standards, error handling, testing requirements, and project-specific guidelines.
- Refactored `config.py` to implement a `Settings` dataclass for better management of environment variables, improving testability and maintainability.
- Updated the import duty schedule handler to utilize session management with `session_scope`, ensuring proper database session handling.
- Enhanced the import service to streamline the duty schedule import process, improving code organization and readability.
- Added new service layer functions to encapsulate business logic related to group duty pinning and duty schedule imports.
- Updated README documentation to reflect the new configuration structure and improved import functionality.
2026-02-18 12:35:11 +03:00

225 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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, session_scope
from importers.duty_schedule import (
DutyScheduleEntry,
DutyScheduleResult,
parse_duty_schedule,
)
from services.import_service 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=[],
),
],
)
with session_scope(db_url) as session:
num_users, num_duty, num_unav, num_vac = run_import(session, 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=[],
)
],
)
with session_scope(db_url) as session:
run_import(session, 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=[],
)
],
)
with session_scope(db_url) as session:
run_import(session, 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)
with session_scope(db_url) as session:
num_users, num_duty, num_unav, num_vac = run_import(session, 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 duty, unavailable (full day), vacation (periods). Unavailable: same-day 00:0023:59. Three consecutive vacation days → one record."""
result = DutyScheduleResult(
start_date=date(2026, 2, 16),
end_date=date(2026, 2, 20),
entries=[
DutyScheduleEntry(
full_name="Mixed User",
duty_dates=[date(2026, 2, 16)],
unavailable_dates=[date(2026, 2, 17)],
vacation_dates=[
date(2026, 2, 18),
date(2026, 2, 19),
date(2026, 2, 20),
],
),
],
)
with session_scope(db_url) as session:
num_users, num_duty, num_unav, num_vac = run_import(session, 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-21")
finally:
session.close()
assert len(duties) == 3
types = {d[0].event_type for d in duties}
assert types == {"duty", "unavailable", "vacation"}
by_type = {d[0].event_type: d[0] for d in duties}
unav = by_type["unavailable"]
assert unav.start_at == "2026-02-17T00:00:00Z"
assert unav.end_at == "2026-02-17T23:59:59Z"
vac = by_type["vacation"]
assert vac.start_at == "2026-02-18T00:00:00Z"
assert vac.end_at == "2026-02-20T23:59:59Z"
def test_import_vacation_with_gap_two_periods(db_url):
"""Vacation dates with a gap (17, 18, 20 Feb) → two records: 1718 and 20."""
result = DutyScheduleResult(
start_date=date(2026, 2, 16),
end_date=date(2026, 2, 21),
entries=[
DutyScheduleEntry(
full_name="Vacation User",
duty_dates=[],
unavailable_dates=[],
vacation_dates=[
date(2026, 2, 17),
date(2026, 2, 18),
date(2026, 2, 20),
],
),
],
)
with session_scope(db_url) as session:
num_users, num_duty, num_unav, num_vac = run_import(session, result, 6, 0)
assert num_users == 1
assert num_duty == 0 and num_unav == 0 and num_vac == 2
session = get_session(db_url)
try:
duties = get_duties(session, "2026-02-16", "2026-02-21")
finally:
session.close()
vacation_records = [d[0] for d in duties if d[0].event_type == "vacation"]
assert len(vacation_records) == 2
starts = sorted(r.start_at for r in vacation_records)
ends = sorted(r.end_at for r in vacation_records)
assert starts == ["2026-02-17T00:00:00Z", "2026-02-20T00:00:00Z"]
assert ends == ["2026-02-18T23:59:59Z", "2026-02-20T23:59:59Z"]