lct-hack/backend/tests/test_service_catalog.py

110 lines
4.9 KiB
Python
Raw Normal View History

"""Каталог служб из «СЛУЖБЫ 112» (lct-38).
Каталог только расширяет ручной выбор служб в карточке. Список оповещения
по-прежнему считается по классификатору, эталоны сценариев от каталога не зависят.
"""
import asyncio
import json
from pathlib import Path
from uuid import uuid4
import pytest
from fastapi.testclient import TestClient
from pydantic import ValidationError
from app.api.ws import call as call_ws
from app.domain import ekp
from app.domain.events import ErrorEvent, KioPatchIn, SessionMode
from app.domain.kio import EDITABLE_KIO_FIELDS, KIO, apply_patch
from app.main import app
from app.scenarios.loader import load_file
from app.scoring.card import evaluate_card
from app.session.hub import hub
from app.session.state import SessionState
from app.session.store import MemorySessionStore
def test_catalog_keeps_all_209_services_with_attribution_and_source():
data = ekp.catalog()
assert len(data.services) == 209
assert len({item.name for item in data.services}) == 209
assert sum(item.service_level == "ДДС" for item in data.services) == 157
assert "ded466988f505082922aacac9fff32b777d1279a" in data.transcription
assert data.license.startswith("MIT")
assert all(item.source_reference.startswith("СЛУЖБЫ 112.docx#word/media/image")
for item in data.services)
@pytest.mark.parametrize("name", [
'Служба 101 (ГУ МЧС России по г.Москве, ГКУ "Пожарно спасательный центр" ОДС)',
'Служба 104 (АО "МОСГАЗ" Диспетчерское управление)',
"ЦЭМП",
'ЦОДД (ГКУ "Центр организации дорожного движения")',
'ЭВАЖД (ГБУ "Учреждение по эксплуатации высотных административных и жилых домов")',
])
def test_rows_checked_against_our_screenshots_are_verbatim(name):
assert name in ekp.catalog_names()
def test_classifier_services_and_notify_lists_are_untouched():
"""Индексы служб классификатора — основа списков оповещения 96 сценариев."""
raw = json.loads(ekp.DATA.read_text(encoding="utf-8"))
assert [item.name for item in ekp.services()] == [item["name"] for item in raw["services"]]
assert len(ekp.services()) == 61
def test_operator_adds_catalog_services_without_changing_the_notify_list():
assert "notify_extra" in EDITABLE_KIO_FIELDS
card = KIO(notify=["Служба 101"])
updated = apply_patch(card, {"notify_extra": ["ФСБ", "ЦЭМП", "ФСБ"], "notify": []})
assert updated.notify_extra == ["ФСБ", "ЦЭМП"]
assert updated.notify == ["Служба 101"], "автоматический список оператор не правит"
with pytest.raises(ValidationError):
apply_patch(card, {"notify_extra": ["Служба, которой нет"]})
def test_added_services_do_not_change_the_score_against_the_reference():
library = Path(__file__).resolve().parents[2] / "scenarios"
scenario = load_file(library / "fire-apartment-l2.yaml", library)
card = KIO(address="улица Ленина, 14", description="горит балкон")
plain = evaluate_card(scenario, card)
extended = evaluate_card(scenario, card.model_copy(update={"notify_extra": ["ФСБ"]}))
assert [(m.key, m.passed) for m in plain.metrics] == [(m.key, m.passed) for m in extended.metrics]
assert plain.score == extended.score
def test_catalog_is_served_like_the_classifier():
with TestClient(app) as client:
response = client.get("/api/ekp/services")
assert response.status_code == 200
body = response.json()
assert len(body["services"]) == 209
assert {"id", "name", "organization", "service_level", "source_reference"} <= body["services"][0].keys()
def test_unknown_service_in_kio_patch_is_refused_without_failing_the_lesson(monkeypatch):
sent = []
monkeypatch.setattr(hub, "store", MemorySessionStore())
monkeypatch.setattr(hub, "to_trainee", lambda _session_id, event: sent.append(event))
monkeypatch.setattr(hub, "to_observers", lambda *_args: None)
state = SessionState(session_id=uuid4(), scenario_id="test", scenario_title="Тест",
level="L1", mode=SessionMode.TRAINING)
hub.register(state)
async def patch(fields):
async with hub.operation(state.session_id):
await call_ws._handle(state.session_id, state, KioPatchIn(fields=fields))
asyncio.run(patch({"notify_extra": ["Служба, которой нет"]}))
assert isinstance(sent[-1], ErrorEvent) and "не принято" in sent[-1].message
assert state.kio.notify_extra == []
asyncio.run(patch({"notify_extra": ["ФСБ"]}))
assert state.kio.notify_extra == ["ФСБ"]