"""Turn de-duplication: the UI hits two endpoints for one message (SSE stream + blocking fallback). Only the first should execute; the duplicate reuses its result.""" from __future__ import annotations import pytest from lyra import chat @pytest.fixture(autouse=True) def _clean_turns(): chat._turns.clear() yield chat._turns.clear() def test_claim_is_owner_once_per_key(): o1, r1 = chat._claim_turn("s1", "flopped a set") o2, r2 = chat._claim_turn("s1", "flopped a set") assert o1 is True and o2 is False assert r1 is r2 # the duplicate waits on the SAME record def test_different_messages_each_own(): o1, _ = chat._claim_turn("s1", "hand A") o2, _ = chat._claim_turn("s1", "hand B") o3, _ = chat._claim_turn("s2", "hand A") # different session assert o1 and o2 and o3 def test_await_returns_owner_reply(): _, rec = chat._claim_turn("s1", "msg") chat._finish_turn(rec, "the answer") assert chat._await_duplicate(rec) == "the answer" def test_respond_duplicate_reuses_result_without_running_turn(monkeypatch): # owner already ran and cached its reply _, rec = chat._claim_turn("s1", "same hand") chat._finish_turn(rec, "owner reply") def boom(*a, **k): raise AssertionError("duplicate must NOT execute the turn") monkeypatch.setattr(chat.mind, "assemble", boom) out = chat.respond("s1", "same hand", "cloud") assert out == "owner reply" def test_respond_stream_duplicate_yields_cached_reply(monkeypatch): _, rec = chat._claim_turn("s1", "same hand") chat._finish_turn(rec, "owner reply") def boom(*a, **k): raise AssertionError("duplicate must NOT execute the turn") monkeypatch.setattr(chat.mind, "assemble", boom) events = list(chat.respond_stream("s1", "same hand", "cloud")) assert ("delta", "owner reply") in events assert ("done", "owner reply") in events def test_fresh_message_after_window_runs_again(): # a completed turn lingers only briefly; simulate expiry and confirm re-ownership o1, rec = chat._claim_turn("s1", "later resend") chat._finish_turn(rec, "first") rec["ts"] -= chat._TURN_TTL + 1 # age it past the window o2, _ = chat._claim_turn("s1", "later resend") assert o1 and o2 # a genuine later resend runs fresh