from velvet_rope.parser import parse_model_turn from velvet_rope.state import Mood def test_parse_valid_model_json(): raw = """ { "reply": "You noticed the line. Alarming competence.", "mood": "respected", "score_delta": { "rapport": 9, "suspicion": -4, "patience": -2, "softspot_progress": 1 }, "rationale": "Player noticed line logistics.", "tactic": "line_logistics" } """ turn = parse_model_turn(raw) assert turn.reply == "You noticed the line. Alarming competence." assert turn.mood is Mood.RESPECTED assert turn.score_delta.rapport == 9 assert turn.score_delta.suspicion == -4 assert turn.score_delta.patience == -2 assert turn.score_delta.softspot_progress == 1 assert turn.rationale == "Player noticed line logistics." assert turn.tactic == "line_logistics" def test_parse_malformed_json_falls_back_to_reply_text(): turn = parse_model_turn("Marlowe checks the clipboard and sighs.") assert turn.reply == "Marlowe checks the clipboard and sighs." assert turn.mood is Mood.UNIMPRESSED assert turn.score_delta.rapport == 0 assert turn.score_delta.suspicion == 0 assert turn.score_delta.patience == -1 assert turn.score_delta.softspot_progress == 0 assert turn.rationale == "Model output was not structured JSON." assert turn.tactic == "unstructured" def test_parse_truncated_json_does_not_expose_raw_json_to_player(): raw = """ { "reply": "Aurelia begins to answer...", "mood": "respected", "score_delta": { "rapport": 5, "suspicion": -2 """ turn = parse_model_turn(raw) assert "{" not in turn.reply assert '"reply"' not in turn.reply assert "Aurelia begins" not in turn.reply assert turn.reply == "The reply arrives garbled. Try that read again." assert turn.mood is Mood.UNIMPRESSED assert turn.rationale == "Model output was malformed JSON." assert turn.tactic == "unstructured" def test_parse_unknown_mood_falls_back_to_unimpressed(): raw = '{"reply": "No.", "mood": "sparkly", "score_delta": {}, "rationale": "", "tactic": ""}' turn = parse_model_turn(raw) assert turn.mood is Mood.UNIMPRESSED def test_parse_json_string_output_falls_back_to_string_reply(): turn = parse_model_turn('"Marlowe sighs."') assert turn.reply == "Marlowe sighs." assert turn.mood is Mood.UNIMPRESSED assert turn.score_delta.rapport == 0 assert turn.score_delta.suspicion == 0 assert turn.score_delta.patience == -1 assert turn.score_delta.softspot_progress == 0 assert turn.rationale == "Model output was not a JSON object." assert turn.tactic == "unstructured" def test_parse_json_list_output_falls_back_safely(): turn = parse_model_turn("[]") assert turn.reply == "Marlowe checks the clipboard and says nothing." assert turn.mood is Mood.UNIMPRESSED assert turn.score_delta.rapport == 0 assert turn.score_delta.suspicion == 0 assert turn.score_delta.patience == -1 assert turn.score_delta.softspot_progress == 0 assert turn.rationale == "Model output was not a JSON object." assert turn.tactic == "unstructured" def test_parse_non_dict_score_delta_uses_default_score_delta(): turn = parse_model_turn('{"reply": "No.", "score_delta": "oops"}') assert turn.reply == "No." assert turn.score_delta.rapport == 0 assert turn.score_delta.suspicion == 0 assert turn.score_delta.patience == -1 assert turn.score_delta.softspot_progress == 0 def test_parse_non_int_score_delta_value_uses_field_default(): turn = parse_model_turn('{"score_delta": {"rapport": "many", "suspicion": 3}}') assert turn.score_delta.rapport == 0 assert turn.score_delta.suspicion == 3 assert turn.score_delta.patience == -1 assert turn.score_delta.softspot_progress == 0