"""Страница разбора: по ней смотрят, чья ошибка - распознавания или разметки.""" import json import re from app.view import render_job def job(**over): base = { "job_id": "abc12345", "filename": "call.mp3", "duration_sec": 120.0, "turns": [ {"speaker": 0, "start": 0.0, "end": 4.0, "text": "Первая реплика.", "acoustics": {"loudness_db": -18.0}}, {"speaker": 0, "start": 30.0, "end": 34.0, "text": "Вторая <реплика>.", "acoustics": {}, "recovered": True}, ], "sentences": [ {"n": 1, "text": "Первая реплика.", "start": 0.0, "loudness_db": -18.0, "turn": 0}, {"n": 2, "text": "Вторая <реплика>.", "start": 30.0, "loudness_db": None, "turn": 1}, ], "stats": {"turns_count": 2, "speech_sec": 8.0, "diarized": False}, "timing": {"diarization_sec": 1.0, "asr_sec": 2.0}, } base.update(over) return base class TestPage: def test_is_self_contained(self): """Страница открывается локально: внешних файлов и запросов быть не должно.""" page = render_job(job()) assert "" not in page def test_marks_gap_between_turns(self): page = render_job(job()) assert "пропуск" in page and "00:04" in page def test_marks_recovered_turn(self): assert "второй проход" in render_job(job()) def test_shows_mode(self): assert "детектор речи" in render_job(job()) assert "диаризация" in render_job(job(stats={"diarized": True, "turns_count": 2})) def test_sentences_go_into_script(self): """По ним страница собирает диалог из вставленной строки ролей.""" page = render_job(job()) payload = re.search(r"const S = (\[.*?\]);", page, re.S).group(1) assert len(json.loads(payload)) == 2 def test_closing_tag_in_text_cannot_break_out(self): """Строка в расшифровке закрыла бы тег и сломала страницу.""" evil = 'а тут ' page = render_job(job( turns=[{"speaker": 0, "start": 0.0, "end": 1.0, "text": evil, "acoustics": {}}], sentences=[{"n": 1, "text": evil, "start": 0.0, "loudness_db": None, "turn": 0}])) assert "") == 1 def test_survives_empty_result(self): page = render_job({"job_id": "x", "turns": [], "sentences": [], "duration_sec": 0}) assert "реплик нет" in page def test_old_result_explains_missing_sentences(self): """Результаты до 0.14.0 не содержат предложений - пустой блок выглядел бы поломкой.""" page = render_job({"job_id": "x", "turns": [], "sentences": [], "duration_sec": 10}) assert "старше 0.14.0" in page assert ".mp3", "status": "failed", "created_at": 1755300500, "duration_sec": 0.0, "error": "нет модели"}, ] def test_done_job_is_a_link(self): from app.view import render_index page = render_index(self.jobs(), "tok") assert f'href="/v1/jobs/{"a" * 32}/view?token=tok"' in page def test_failed_job_is_not_a_link(self): """Разбирать нечего: результата нет.""" from app.view import render_index page = render_index(self.jobs(), "tok") assert f'/v1/jobs/{"b" * 32}/view' not in page def test_error_is_shown(self): from app.view import render_index assert "нет модели" in render_index(self.jobs(), "tok") def test_filename_is_escaped(self): from app.view import render_index page = render_index(self.jobs(), "tok") assert "<bad>.mp3" in page and "" not in page def test_token_is_encoded_in_links(self): from app.view import render_index page = render_index(self.jobs(), "token s probelom") assert "token%20s%20probelom" in page def test_empty_list_says_so(self): from app.view import render_index assert "задач пока нет" in render_index([], "tok")