"""Tests for the per-engine output normalizers. No GPU, no network. Run: python -m pytest tests/ -q """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from ttw.actions import _extract_json from ttw.normalize import NORMALIZERS, harmony_final, identity def test_harmony_extracts_final_channel_and_json_parses(): """A harmony wrapper is stripped to the final channel, and the bare result feeds _extract_json to the intended action dict.""" text = ( "analysis The owl is cautious, sell honey while it is high." 'assistantfinal{"thought":"sell honey","offers":' '[{"side":"sell","good":"honey","price":9,"qty":3}],"gossip":""}' ) final = harmony_final(text) assert "analysis" not in final parsed = _extract_json(final) assert parsed is not None assert parsed["thought"] == "sell honey" assert parsed["offers"][0]["good"] == "honey" def test_harmony_without_marker_is_unchanged(): text = '{"thought":"hold","offers":[],"gossip":""}' assert harmony_final(text) == text def test_harmony_truncated_analysis_is_dropped(): """When the budget runs out before `assistantfinal`, the model is still in its analysis channel; raw chain-of-thought must never leak downstream.""" text = "analysisWe need to produce a punchy headline, optionally a short qu" assert harmony_final(text) == "" def test_harmony_empty_string_is_empty(): assert harmony_final("") == "" def test_harmony_final_channel_is_stripped(): """Leading/trailing whitespace around the final-channel answer is removed.""" assert harmony_final('analysis ...assistantfinal {"x":1} ') == '{"x":1}' def test_identity_returns_text_unchanged(): text = "anything at all, even {broken json" assert identity(text) == text def test_normalizers_registry_maps_keys(): assert NORMALIZERS["harmony"] is harmony_final assert NORMALIZERS["identity"] is identity