test: cover attribution and timeout safety

This commit is contained in:
mARTin-B78 2026-09-04 09:34:46 +02:00
parent 0fd39d4acb
commit 2608c953ee
4 changed files with 27 additions and 3 deletions

View File

@ -48,6 +48,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
workflow submission, prompt injection, polling, seed derivation, and image retrieval. workflow submission, prompt injection, polling, seed derivation, and image retrieval.
- **Casting Constraint Coverage.** Added deterministic regression coverage proving casting filters invented voice IDs - **Casting Constraint Coverage.** Added deterministic regression coverage proving casting filters invented voice IDs
and avoids unnecessary duplicate assignments when distinct catalogue voices are available. and avoids unnecessary duplicate assignments when distinct catalogue voices are available.
- **Attribution and Timeout Safety Coverage.** Added regressions preventing phantom narrator speakers, preserving source
prose after malformed LLM JSON, clamping caller timeouts, and bounding upstream error text.
- **4 GiB Container Validation.** Verified the current image serves the application under a hard 4 GiB cgroup limit - **4 GiB Container Validation.** Verified the current image serves the application under a hard 4 GiB cgroup limit
with no OOM kill; the probe recorded approximately 50.6 MiB idle memory and 0.09% CPU. with no OOM kill; the probe recorded approximately 50.6 MiB idle memory and 0.09% CPU.
- **Profile Completeness Coverage.** Added browser regression coverage for empty, partial, and fully populated - **Profile Completeness Coverage.** Added browser regression coverage for empty, partial, and fully populated

View File

@ -165,13 +165,13 @@ the first block before investing in architecture or polish.
### Automated tests ### Automated tests
- [ ] Character parsing and phantom-speaker prevention. - [x] Character parsing and phantom-speaker prevention, including malformed-response prose preservation.
- [x] Voice-matching constraints reject invented voice IDs and avoid unnecessary duplicate assignments. - [x] Voice-matching constraints reject invented voice IDs and avoid unnecessary duplicate assignments.
- [x] Profile-completeness counting for empty, partial, and fully populated sheets. - [x] Profile-completeness counting for empty, partial, and fully populated sheets.
- [x] Rating persistence. - [x] Rating persistence.
- [x] STT endpoint normalization and backend-response shape handling. - [x] STT endpoint normalization and backend-response shape handling.
- [x] ComfyUI workflow submission, prompt injection, polling, and image retrieval. - [x] ComfyUI workflow submission, prompt injection, polling, and image retrieval.
- [ ] API failure and timeout behavior. - [x] API timeout clamping and bounded upstream error text; live-provider failure drills remain environment-specific.
### Playwright smoke suite ### Playwright smoke suite

View File

@ -1 +1 @@
1.23.5 1.23.6

View File

@ -21,6 +21,8 @@ from routes.conversation import (
_comfyui_run, _comfyui_run,
_CastJobRequest, _CastJobRequest,
match_characters_voices, match_characters_voices,
_request_timeout_seconds,
_response_error_text,
) )
@ -43,6 +45,26 @@ class AttributionParserTests(unittest.TestCase):
self.assertEqual([segment["type"] for segment in result["segments"]], ["narration", "dialogue", "narration"]) self.assertEqual([segment["type"] for segment in result["segments"]], ["narration", "dialogue", "narration"])
self.assertIn("Ein langer Satz endet hier.", result["segments"][1]["text"]) self.assertIn("Ein langer Satz endet hier.", result["segments"][1]["text"])
def test_attribution_normalization_prevents_phantom_narrator_and_preserves_text_on_bad_json(self):
raw = json.dumps({"segments": [
{"speaker": "Invented Narrator", "type": "narration", "text": "A road disappeared.", "emotion": "dramatic"},
{"speaker": "Mira", "type": "dialogue", "text": "We keep walking.", "emotion": ""},
]})
result = _attribution_parse(raw, "unused")
self.assertEqual(result["segments"][0]["speaker"], "Narrator")
self.assertEqual(result["characters"], ["Mira"])
fallback = _attribution_parse("not valid JSON", "Original prose must survive")
self.assertEqual(fallback["segments"], [{"speaker": "Narrator", "type": "narration", "text": "Original prose must survive", "emotion": ""}])
def test_api_timeout_and_error_text_are_bounded(self):
self.assertEqual(_request_timeout_seconds(-10), 5.0)
self.assertEqual(_request_timeout_seconds(99999), 600.0)
self.assertEqual(_request_timeout_seconds("not-a-number", default=12), 12.0)
class ResponseStub:
text = "x" * 5000
reason = "Bad Gateway"
self.assertEqual(len(_response_error_text(ResponseStub())), 2000)
def test_repairs_epigraph_split_before_mixed_guillemet_close(self): def test_repairs_epigraph_split_before_mixed_guillemet_close(self):
raw = '{"segments":[' \ 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":"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"},' \