refactor: пульт ДДС владеет живыми карточками, сессия не копирует их поля
Связка 112→ДДС больше не подменяет таймеры оператора таймерами первой карточки ДДС, поэтому время заполнения карточки 112 снова попадает в оценку.
This commit is contained in:
parent
c0fa0a50a1
commit
5a3761a830
24 changed files with 2093 additions and 762 deletions
127
backend/tests/test_dds_desk.py
Normal file
127
backend/tests/test_dds_desk.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
"""Пульт ДДС без сокетов и хаба: очередь, переключение, завершение, темп."""
|
||||
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
from app.domain.statuses import ServiceStatus
|
||||
from app.domain.timers import TimerCode
|
||||
from app.scenarios.loader import load_file
|
||||
from app.session.dds import DdsCardRecord, DdsDesk
|
||||
from app.session.timers import SessionTimers, now_utc
|
||||
|
||||
LIBRARY = Path(__file__).resolve().parents[2] / "scenarios"
|
||||
NAMES = ("fire-apartment-l2.yaml", "tickets/t20-2-stroke.yaml",
|
||||
"tickets/t01-1-fire-container.yaml")
|
||||
|
||||
|
||||
def scenarios(count: int = 3):
|
||||
return [load_file(LIBRARY / name, LIBRARY) for name in NAMES[:count]]
|
||||
|
||||
|
||||
def record(card) -> DdsCardRecord:
|
||||
return DdsCardRecord(card_id=card.card_id, scenario_id=card.scenario.id, reply_text="",
|
||||
metrics=[], findings=[], actions=[], duration_ms=0)
|
||||
|
||||
|
||||
def started(interval: int = 0, max_waiting: int = 3, now=None) -> DdsDesk:
|
||||
desk = DdsDesk()
|
||||
desk.start(scenarios(), limits=SessionTimers().limits,
|
||||
arrival_interval_seconds=interval, max_waiting=max_waiting, now=now or now_utc())
|
||||
return desk
|
||||
|
||||
|
||||
def test_first_card_becomes_active_and_rest_wait_when_no_pace():
|
||||
desk = started()
|
||||
|
||||
assert [card.scenario.id for card in desk.ordered()] == [
|
||||
"fire-apartment-l2", "t20-2-stroke", "t01-1-fire-container"]
|
||||
assert desk.active is desk.ordered()[0]
|
||||
assert desk.next_arrival_at is None
|
||||
|
||||
|
||||
def test_paced_queue_delivers_next_card_only_after_interval():
|
||||
start = now_utc()
|
||||
desk = started(interval=60, now=start)
|
||||
first = desk.active
|
||||
|
||||
assert desk.deliver_due(start + timedelta(seconds=59)) == 0
|
||||
assert desk.deliver_due(start + timedelta(seconds=61)) == 1
|
||||
|
||||
assert len(desk.cards) == 2
|
||||
assert desk.active is first
|
||||
|
||||
|
||||
def test_waiting_cap_holds_back_delivery():
|
||||
desk = started(max_waiting=1)
|
||||
|
||||
assert len(desk.cards) == 2
|
||||
assert desk.next_index == 2
|
||||
|
||||
|
||||
def test_open_switches_by_reference_without_copying_fields():
|
||||
desk = started()
|
||||
first, second = desk.ordered()[:2]
|
||||
first.status_log.append(object())
|
||||
|
||||
assert desk.open(second.card_id)
|
||||
second.reply_text = "ответ по второй"
|
||||
assert desk.open(first.card_id)
|
||||
|
||||
assert desk.active is first
|
||||
assert len(first.status_log) == 1 and second.status_log == []
|
||||
assert second.reply_text == "ответ по второй" and first.reply_text == ""
|
||||
assert desk.card_index == 0
|
||||
|
||||
|
||||
def test_open_unknown_card_keeps_active():
|
||||
desk = started()
|
||||
active = desk.active
|
||||
|
||||
assert not desk.open(uuid4())
|
||||
assert desk.active is active
|
||||
|
||||
|
||||
def test_complete_moves_to_following_card_and_keeps_record():
|
||||
desk = started()
|
||||
first, second, _third = desk.ordered()
|
||||
|
||||
following = desk.complete(first.card_id, record(first))
|
||||
|
||||
assert following is second and desk.active is second
|
||||
assert first.card_id not in desk.cards
|
||||
assert desk.is_completed(first.card_id)
|
||||
|
||||
|
||||
def test_completing_last_card_leaves_desk_between_cards_with_last_index():
|
||||
desk = started(interval=60, max_waiting=1)
|
||||
only = desk.active
|
||||
|
||||
assert desk.complete(only.card_id, record(only)) is None
|
||||
|
||||
assert desk.active is None and desk.active_id is None
|
||||
assert desk.card_index == 0
|
||||
assert desk.deliver_due(now_utc() + timedelta(seconds=61)) == 1
|
||||
assert desk.active.scenario.id == "t20-2-stroke"
|
||||
|
||||
|
||||
def test_each_card_runs_own_decision_timer_with_configured_limit():
|
||||
desk = DdsDesk()
|
||||
limits = {**SessionTimers().limits, TimerCode.DDS_ACK: 45_000}
|
||||
desk.start(scenarios(2), limits=limits, now=now_utc())
|
||||
first, second = desk.ordered()
|
||||
|
||||
assert first.timers is not second.timers
|
||||
assert first.timers.limits[TimerCode.DDS_ACK] == 45_000
|
||||
assert TimerCode.DDS_ACK in second.timers.timers
|
||||
|
||||
|
||||
def test_card_status_mark_lands_in_that_card_only():
|
||||
desk = started()
|
||||
first, second = desk.ordered()[:2]
|
||||
service = first.dispatched_card.notify[0]
|
||||
|
||||
first.set_service_status(service, ServiceStatus.ACCEPTED, "Основание: доклад")
|
||||
|
||||
assert [mark.status for mark in first.status_log] == [ServiceStatus.ACCEPTED]
|
||||
assert second.status_log == []
|
||||
Loading…
Reference in a new issue