lct-hack/backend/tests/test_session_signals.py

152 lines
5.4 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.

"""Колонка сигналов реестра: очередь, повторные отказы, курсант не на связи."""
import time
from datetime import UTC, datetime, timedelta
from uuid import uuid4
import pytest
from fastapi.testclient import TestClient
from app.api.http import sessions as sessions_http
from app.config import get_settings
from app.main import app
from app.session.hub import hub
from app.session.store import MemorySessionStore
POOL = ["fire-apartment-l2", "t01-1-fire-container"]
@pytest.fixture
def client(monkeypatch):
monkeypatch.setenv("DEV_AUTH_BYPASS", "true")
get_settings.cache_clear()
async def audit_override(*_args, **_kwargs):
return None
async def optional_session_override():
yield None
monkeypatch.setattr(sessions_http, "audit_required", audit_override)
monkeypatch.setitem(
app.dependency_overrides, sessions_http.optional_session, optional_session_override
)
try:
with TestClient(app) as test_client:
test_client.post("/api/auth/dev-token")
hub.store = MemorySessionStore()
yield test_client
finally:
get_settings.cache_clear()
def wait_for(predicate, timeout=3):
end = time.monotonic() + timeout
while time.monotonic() < end:
value = predicate()
if value:
return value
time.sleep(0.02)
raise AssertionError("состояние не обновилось")
def read_until(socket, wanted):
received = []
for _ in range(20):
event = socket.receive_json()
received.append(event["type"])
if event["type"] == wanted:
return event
raise AssertionError(f"событие {wanted} не пришло; получены: {received}")
def start_two_card_dds(client):
session_id = uuid4()
context = client.websocket_connect(f"/ws/control/{session_id}")
control = context.__enter__()
control.send_json({
"type": "scenario.start",
"scenario_id": POOL[0],
"trainee": "Иванов",
"mode": "training",
"exercise": "dds",
"random_scenario_ids": POOL,
})
wait_for(lambda: hub.get(session_id))
return session_id, control
def row_for(client, session_id):
rows = client.get("/api/sessions/active").json()
return next(item for item in rows if item["session_id"] == str(session_id))
def signal_kinds(row):
return {signal["kind"] for signal in row["signals"]}
def test_signal_backlog_when_queue_reaches_threshold(client, monkeypatch):
monkeypatch.setenv("SIGNAL_BACKLOG_THRESHOLD", "2")
get_settings.cache_clear()
session_id, control = start_two_card_dds(client)
try:
state = hub.get(session_id)
assert len(state.desk.cards) == 2, "оба билета должны прийти сразу без интервала"
row = row_for(client, session_id)
assert row["dds_open_cards"] == 2
assert "backlog" in signal_kinds(row)
finally:
hub.stop_ticker(session_id)
control.__exit__(None, None, None)
def test_signal_refusals_after_two_consecutive_declines(client):
session_id, control = start_two_card_dds(client)
try:
state = hub.get(session_id)
first_card, second_card = state.desk.ordered()
with client.websocket_connect(f"/ws/station/{session_id}?role=dds") as station:
read_until(station, "station.state")
first_service = state.card_services(first_card)[0]
station.send_json({
"type": "card.status", "service": first_service, "status": "declined",
"comment": "не наш адрес, передано в УК",
})
read_until(station, "station.state")
row = row_for(client, session_id)
assert "refusals" not in signal_kinds(row), "одного отказа недостаточно для сигнала"
station.send_json({"type": "card.open", "card_id": str(second_card.card_id)})
read_until(station, "station.state")
second_service = state.card_services(second_card)[0]
station.send_json({
"type": "card.status", "service": second_service, "status": "declined",
"comment": "не наша территория, передано в ОМВД",
})
read_until(station, "station.state")
row = row_for(client, session_id)
assert state.consecutive_refusals == 2
assert "refusals" in signal_kinds(row)
finally:
hub.stop_ticker(session_id)
control.__exit__(None, None, None)
def test_signal_offline_when_station_socket_is_closed_past_the_window(client):
session_id, control = start_two_card_dds(client)
try:
state = hub.get(session_id)
with client.websocket_connect(f"/ws/station/{session_id}?role=dds") as station:
read_until(station, "station.state")
row = row_for(client, session_id)
assert "offline" not in signal_kinds(row), "сокет подключён — сигнала быть не должно"
window = get_settings().signal_offline_window_seconds
state.socket_last_seen_at = datetime.now(UTC) - timedelta(seconds=window + 1)
row = row_for(client, session_id)
assert "offline" in signal_kinds(row)
finally:
hub.stop_ticker(session_id)
control.__exit__(None, None, None)