lct-01: контракт закрыт — координаты, обязательные поля, коды ошибок
Координаты были представлены дважды: кортежем в КИО и объектом в данных
ЭРА-ГЛОНАСС. Теперь Coords {lat, lon} везде — в кортеже не видно, где
широта, и ошибка всплывает на карте у диспетчера, а не в типах.
required_fields едет в call.incoming и session.snapshot: обязательность
полей задаёт сценарий, без списка АРМ не подсветит незаполненное поле,
и курсант узнаёт о неполноте карточки только из разбора.
Канал error получил коды (ErrorKind, восемь значений) — фронт разбирает
код, а не текст сообщения. Имя не ErrorCode: оно занято таксономией E1–E6,
два одинаковых имени дали бы коллизию в generated.ts.
Добавлены поля, которые спрашивает чек-лист, а записать было некуда:
куда идёт дым, уехал ли нарушитель, код домофона.
Документы догнали код: mkh → gkh, payload transcript.append с якорем ref,
причина director у tts.cancel, attempt и stopped у таймеров.
This commit is contained in:
parent
5844b374ad
commit
59d5a6a26d
5 changed files with 106 additions and 17 deletions
|
|
@ -6,14 +6,18 @@ from uuid import uuid4
|
|||
import pytest
|
||||
from pydantic import TypeAdapter, ValidationError
|
||||
|
||||
from app.config import Settings
|
||||
from app.domain.events import (
|
||||
CallIncoming,
|
||||
ErrorKind,
|
||||
EventCatalog,
|
||||
KioPatchIn,
|
||||
ServerToTrainee,
|
||||
SessionMode,
|
||||
SessionSnapshot,
|
||||
TraineeToServer,
|
||||
)
|
||||
from app.domain.kio import KIO, apply_patch, missing_fields
|
||||
from app.domain.kio import KIO, Coords, apply_patch, missing_fields
|
||||
from app.domain.timers import NORMATIVES, TimerCode, TimerState, state_for
|
||||
from scripts.export_types import OUT, render
|
||||
|
||||
|
|
@ -83,6 +87,49 @@ def test_event_catalog_covers_every_channel():
|
|||
}
|
||||
|
||||
|
||||
def test_coords_are_named_everywhere():
|
||||
"""Одно представление координат на весь контракт: в кортеже не видно,
|
||||
где широта, и ошибка всплывает на карте, а не в типах."""
|
||||
card = apply_patch(KIO(), {"coords.lat": 55.751244, "coords.lon": 37.618423})
|
||||
assert card.coords == Coords(lat=55.751244, lon=37.618423)
|
||||
|
||||
|
||||
def test_required_fields_reach_the_screen():
|
||||
"""Без этого АРМ не подсветит незаполненное обязательное поле."""
|
||||
incoming = CallIncoming(
|
||||
scenario_id="fire-apartment-l2",
|
||||
caller_number="+7 999 000-00-00",
|
||||
level="L2",
|
||||
mode=SessionMode.TRAINING,
|
||||
required_fields=["address", "floor"],
|
||||
)
|
||||
assert incoming.required_fields == ["address", "floor"]
|
||||
# Наблюдателю они нужны так же: монитор рисует ту же карточку
|
||||
assert "required_fields" in SessionSnapshot.model_fields
|
||||
|
||||
|
||||
def test_error_channel_has_codes_not_prose():
|
||||
"""Фронт разбирает код, а не текст: текст — для человека."""
|
||||
adapter = TypeAdapter(ServerToTrainee)
|
||||
event = adapter.validate_python(
|
||||
{"type": "error", "code": "hint_denied_in_exam", "message": "В контрольном режиме подсказок нет"}
|
||||
)
|
||||
assert event.code is ErrorKind.HINT_DENIED_IN_EXAM
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
adapter.validate_python({"type": "error", "code": "что-то пошло не так", "message": ""})
|
||||
|
||||
|
||||
def test_normative_comes_from_config_not_code():
|
||||
"""Правка норматива не должна требовать правки кода."""
|
||||
default = Settings()
|
||||
assert default.limit_ms(TimerCode.INTERVIEW) == 75_000
|
||||
|
||||
overridden = Settings(timer_limits_ms={TimerCode.INTERVIEW: 90_000})
|
||||
assert overridden.limit_ms(TimerCode.INTERVIEW) == 90_000
|
||||
assert overridden.limit_ms(TimerCode.ANSWER) == 8_000
|
||||
|
||||
|
||||
def test_generated_types_match_models():
|
||||
"""Забытый `make types` ловится здесь, а не на фронте в последнюю ночь."""
|
||||
assert OUT.exists(), "нет frontend/src/shared/types/generated.ts — запусти make types"
|
||||
|
|
|
|||
Loading…
Reference in a new issue