Files
UmbraAudio/tests/test_voice_maintenance.py
B.D. Murphy b09e324431 Multi-pass attribution + web voice-maintenance banner + qwen demo branding
Attribution (from prior session, now committed):
- llm_attributor: mechanical narration evidence, unnamed-role rules,
  wider context (12->20 lines, 8->40 utterances), uncertain flag,
  verify pass with 20-item batches and invented-name gate
- reattribute_all: --min-id/--max-id, --dry-run, --no-verify,
  --verify-rounds, qa_accepted skip guard, per-chapter elapsed times
- test_multipass_attribution: 19 tests

Web banner:
- voice_maintenance module: pid-liveness-checked marker file so a
  crashed run self-heals the banner away
- routes/base.html: amber banner on every page while the LLM
  re-attribution run has Qwen3-TTS offline
- reattribute_all: sets banner before switching models, clears in
  finally (also on failure)
- test_voice_maintenance: 8 tests

qwen3-tts submodule: UmbraAudio Voice Studio branding in web demo
(icon data-uri + favicon logo).
2026-09-22 19:12:50 -07:00

76 lines
2.2 KiB
Python

"""Tests for the web-UI voice-maintenance banner marker.
The marker is a small JSON file whose ``pid`` field gates the banner: a
run that died without clearing the file must not leave the banner up
forever, so ``active_voice_maintenance`` checks the owner is still alive.
"""
from __future__ import annotations
import json
import os
from typing import TYPE_CHECKING
import pytest
if TYPE_CHECKING:
from pathlib import Path
from umbraaudio import voice_maintenance as vm
@pytest.fixture
def marker(tmp_path, monkeypatch: pytest.MonkeyPatch) -> Path:
path = tmp_path / "voice_maintenance.json"
monkeypatch.setattr(vm, "MAINTENANCE_FILE", path)
return path
def test_set_writes_marker_with_live_pid(marker: Path) -> None:
vm.set_voice_maintenance("analysis in progress")
assert marker.exists()
data = json.loads(marker.read_text())
assert data["pid"] == os.getpid()
assert "analysis" in data["reason"]
def test_active_returns_marker_while_owner_alive(marker: Path) -> None:
vm.set_voice_maintenance("analysis in progress")
active = vm.active_voice_maintenance()
assert active is not None
assert active["pid"] == os.getpid()
def test_active_none_when_no_marker(marker: Path) -> None:
assert vm.active_voice_maintenance() is None
def test_clear_removes_marker(marker: Path) -> None:
vm.set_voice_maintenance("analysis in progress")
vm.clear_voice_maintenance()
assert not marker.exists()
assert vm.active_voice_maintenance() is None
def test_clear_missing_marker_is_idempotent(marker: Path) -> None:
vm.clear_voice_maintenance()
vm.clear_voice_maintenance()
def test_active_none_when_owner_dead(marker: Path) -> None:
marker.write_text(
json.dumps({"pid": 2**24, "started_at": 1, "reason": "dead owner"})
)
assert vm.active_voice_maintenance() is None
def test_active_none_on_corrupt_marker(marker: Path) -> None:
marker.write_text("{not json")
assert vm.active_voice_maintenance() is None
def test_active_none_on_bad_pid_type(marker: Path) -> None:
marker.write_text(
json.dumps({"pid": "not-an-int", "started_at": 1, "reason": "bad"})
)
assert vm.active_voice_maintenance() is None