89 lines
4.0 KiB
Python
89 lines
4.0 KiB
Python
"""Focused attribution parser regressions."""
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import json
|
||
import tempfile
|
||
import unittest
|
||
|
||
os.environ.setdefault("CONFIG_DIR", tempfile.mkdtemp(prefix="ttvc-conversation-test-config-"))
|
||
|
||
from routes.conversation import (
|
||
_attribution_parse,
|
||
_character_form_sheet,
|
||
_character_identity_fingerprint,
|
||
_cij_check_cancelled,
|
||
_cij_new,
|
||
_consistent_image_cancel_events,
|
||
_consistent_image_jobs,
|
||
_fallback_attribute_response,
|
||
)
|
||
|
||
|
||
class AttributionParserTests(unittest.TestCase):
|
||
def test_active_character_form_overlays_visual_identity_without_changing_record(self):
|
||
sheet = {
|
||
"physical": "base form",
|
||
"image_prompt": "base prompt",
|
||
"forms": [{"id": "wolf", "name": "Wolf form", "physical": "black wolf", "image_prompt": "black wolf portrait"}],
|
||
"active_form_id": "wolf",
|
||
}
|
||
resolved = _character_form_sheet(sheet)
|
||
self.assertEqual(resolved["physical"], "black wolf")
|
||
self.assertEqual(resolved["image_prompt"], "black wolf portrait")
|
||
self.assertEqual(sheet["active_form_id"], "wolf")
|
||
self.assertNotEqual(_character_identity_fingerprint(sheet), _character_identity_fingerprint({**sheet, "active_form_id": ""}))
|
||
|
||
def test_fallback_accepts_mixed_guillemets(self):
|
||
result = _fallback_attribute_response("Intro. »Ein langer Satz endet hier.‹ Danach.")
|
||
self.assertEqual([segment["type"] for segment in result["segments"]], ["narration", "dialogue", "narration"])
|
||
self.assertIn("Ein langer Satz endet hier.", result["segments"][1]["text"])
|
||
|
||
def test_repairs_epigraph_split_before_mixed_guillemet_close(self):
|
||
raw = '{"segments":[' \
|
||
'{"speaker":"Schwester Larissa","type":"dialogue","text":"»Von dem Windhager Felsgesteyn bis zur Kueste von Mendena ziehet es sich hin, von Ifirns Eisgebirg bis zu dem dampfenden Waldt der Mohas, und"},' \
|
||
'{"speaker":"Narrator","type":"narration","text":"fürwahr, es ist ein seltsam Land. Lieblich bisweilen, mild und voll der guten Menschen. ‹ Zitiert aus dem Buch."}' \
|
||
']}'
|
||
result = _attribution_parse(raw, "unused")
|
||
self.assertEqual(len(result["segments"]), 2)
|
||
dialogue, narration = result["segments"]
|
||
self.assertEqual(dialogue["speaker"], "Schwester Larissa")
|
||
self.assertEqual(dialogue["type"], "dialogue")
|
||
self.assertTrue(dialogue["text"].endswith("Land. Lieblich bisweilen, mild und voll der guten Menschen. ‹"))
|
||
self.assertEqual(narration["text"], "Zitiert aus dem Buch.")
|
||
|
||
def test_book2_neutral_fixture_preserves_multiple_speakers(self):
|
||
fixture_path = os.path.join(os.path.dirname(__file__), "fixtures", "book2_attribution.json")
|
||
with open(fixture_path, encoding="utf-8") as handle:
|
||
fixture = json.load(handle)
|
||
result = _attribution_parse(fixture["raw"], " ".join(fixture["known_characters"]))
|
||
self.assertEqual(
|
||
[segment["speaker"] for segment in result["segments"]],
|
||
["Narrator", "Captain Vale", "The Ferryman", "Mira"],
|
||
)
|
||
self.assertTrue(all(segment["type"] == "dialogue" for segment in result["segments"][1:]))
|
||
|
||
def test_consistent_image_job_can_be_cancelled(self):
|
||
from fastapi.testclient import TestClient
|
||
from server import app
|
||
|
||
job_id = "cancel-test"
|
||
_cij_new(job_id, "Test character")
|
||
try:
|
||
with TestClient(app) as client:
|
||
response = client.post(
|
||
f"/api/character-generate-consistent-images/cancel/{job_id}",
|
||
headers={"origin": "http://testserver"},
|
||
)
|
||
self.assertEqual(response.status_code, 200)
|
||
self.assertTrue(response.json()["cancelled"])
|
||
with self.assertRaises(Exception):
|
||
_cij_check_cancelled(job_id)
|
||
finally:
|
||
_consistent_image_jobs.pop(job_id, None)
|
||
_consistent_image_cancel_events.pop(job_id, None)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|