90 lines
3.7 KiB
Python
90 lines
3.7 KiB
Python
|
|
"""Офлайн-звонящий по предгенерированной таблице. Сети не требует."""
|
|||
|
|
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
import pytest
|
|||
|
|
import yaml
|
|||
|
|
|
|||
|
|
from app.dialog.persona import PersonaState
|
|||
|
|
from app.dialog.slots import SlotMachine
|
|||
|
|
from app.domain.events import Mood
|
|||
|
|
from tests.test_slots import SCENARIO, StemEmbedder
|
|||
|
|
|
|||
|
|
TABLE = {
|
|||
|
|
"scenario": SCENARIO.id,
|
|||
|
|
"first_line": "Алло! Горим!",
|
|||
|
|
"reveal": {
|
|||
|
|
"f_address": {"panic": "Ленина четырнадцать, сорок седьмая квартира! Быстрее!",
|
|||
|
|
"aggressive": "ЛЕНИНА ЧЕТЫРНАДЦАТЬ! Записали?!"},
|
|||
|
|
"f_people": {"panic": "Жена с ребёнком там, не выходят!"},
|
|||
|
|
},
|
|||
|
|
"repeat": {
|
|||
|
|
"f_address": {"panic": "Я же сказал — Ленина четырнадцать!",
|
|||
|
|
"aggressive": "СКОЛЬКО МОЖНО! ЛЕНИНА ЧЕТЫРНАДЦАТЬ!"},
|
|||
|
|
},
|
|||
|
|
"fillers": {"panic": ["Что?! Не слышу!", "Алло! Вы там?!"]},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.fixture
|
|||
|
|
def caller(tmp_path, monkeypatch):
|
|||
|
|
from app.dialog import tree
|
|||
|
|
|
|||
|
|
monkeypatch.setattr(tree, "LIBRARY", tmp_path)
|
|||
|
|
(tmp_path / f"{SCENARIO.id}.yaml").write_text(
|
|||
|
|
yaml.safe_dump(TABLE, allow_unicode=True), encoding="utf-8"
|
|||
|
|
)
|
|||
|
|
return tree.TreeCaller(SCENARIO.id)
|
|||
|
|
|
|||
|
|
|
|||
|
|
@pytest.fixture
|
|||
|
|
def slots():
|
|||
|
|
return SlotMachine(SCENARIO, StemEmbedder(), floor=0.5)
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def test_reply_comes_from_the_table(caller, slots):
|
|||
|
|
persona = PersonaState(SCENARIO.persona)
|
|||
|
|
line = await caller.reply(slots.hear("Какой адрес?"), persona, slots)
|
|||
|
|
assert line.text == TABLE["reveal"]["f_address"]["panic"]
|
|||
|
|
assert caller.misses == 0
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def test_repeat_uses_the_irritated_line(caller, slots):
|
|||
|
|
persona = PersonaState(SCENARIO.persona)
|
|||
|
|
await caller.reply(slots.hear("Какой адрес?"), persona, slots)
|
|||
|
|
line = await caller.reply(slots.hear("Повторите адрес"), persona, slots)
|
|||
|
|
assert line.text == TABLE["repeat"]["f_address"]["panic"]
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def test_mood_picks_another_line(caller, slots):
|
|||
|
|
"""Дуга и директивы переключают вариант, а не текст."""
|
|||
|
|
persona = PersonaState(SCENARIO.persona)
|
|||
|
|
persona.directive = "turns_aggressive"
|
|||
|
|
line = await caller.reply(slots.hear("Какой адрес?"), persona, slots)
|
|||
|
|
assert line.mood is Mood.AGGRESSIVE
|
|||
|
|
assert line.text == TABLE["reveal"]["f_address"]["aggressive"]
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def test_unknown_question_gets_a_filler(caller, slots):
|
|||
|
|
persona = PersonaState(SCENARIO.persona)
|
|||
|
|
line = await caller.reply(slots.hear("Вы в безопасности?"), persona, slots)
|
|||
|
|
assert line.text in TABLE["fillers"]["panic"]
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def test_hole_in_the_table_falls_back_and_is_counted(caller, slots):
|
|||
|
|
"""Пропуск не оставляет звонящего без голоса, но виден по счётчику:
|
|||
|
|
предгенерацию пора повторить."""
|
|||
|
|
persona = PersonaState(SCENARIO.persona)
|
|||
|
|
line = await caller.reply(slots.hear("Что именно горит?"), persona, slots)
|
|||
|
|
assert line.text, "звонящий промолчал"
|
|||
|
|
assert caller.misses == 1
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def test_caller_never_speaks_an_unrevealed_fact(caller, slots):
|
|||
|
|
persona = PersonaState(SCENARIO.persona)
|
|||
|
|
for question in ("Служба 112, слушаю", "Успокойтесь", "Вы одна дома?"):
|
|||
|
|
text = (await caller.reply(slots.hear(question), persona, slots)).text
|
|||
|
|
revealed = {fact.id for fact in slots.revealed_facts()}
|
|||
|
|
for fact in SCENARIO.facts:
|
|||
|
|
if fact.id not in revealed:
|
|||
|
|
assert fact.value not in text, f"выдал «{fact.value}» на «{question}»"
|