"""Focused attribution parser regressions.""" from __future__ import annotations import os 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_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()