Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
"""Regression: seal-time transport failure must never lose or duplicate
|
||||
the final answer (silent-loss class — PR 85796 review finding B1/B3-adjacent).
|
||||
|
||||
Three invariants:
|
||||
|
||||
1. A transport exception during the sealing draft frame retries the SAME
|
||||
idempotent frame once (the connector's sealed-key tombstone returns the
|
||||
original stream ts for a repeated final, so a retry can never open a
|
||||
second stream or post a duplicate).
|
||||
|
||||
2. When the seal cannot be delivered at all, the adapter reports failure —
|
||||
and the consumer must NOT record final delivery. The prior behavior
|
||||
let the turn-final retry re-enter the draft-frame path, whose no-op
|
||||
dedupe compared against the last UNSEALED frame and reported success
|
||||
with zero transport calls: flags went green, the gateway suppressed its
|
||||
fallback, and the user never received the answer.
|
||||
|
||||
3. The turn-final retry path always calls with finalize=True so it can
|
||||
never be routed through the draft-frame branch at all.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
from gateway.stream_consumer import GatewayStreamConsumer, StreamConsumerConfig
|
||||
from tests.gateway.relay.test_relay_live_cards import _connected_adapter
|
||||
|
||||
|
||||
class FlakyTransport:
|
||||
"""Raises on selected ops; records everything that got through."""
|
||||
|
||||
def __init__(self, raise_on=(), raise_times=None):
|
||||
self.ops = []
|
||||
self.attempts = []
|
||||
self.raise_on = set(raise_on)
|
||||
# op -> remaining raise count (None = raise forever)
|
||||
self.raise_times = dict(raise_times or {})
|
||||
|
||||
def _should_raise(self, op, final):
|
||||
key = "seal" if (op == "draft" and final) else op
|
||||
if key not in self.raise_on:
|
||||
return False
|
||||
remaining = self.raise_times.get(key)
|
||||
if remaining is None:
|
||||
return True
|
||||
if remaining <= 0:
|
||||
return False
|
||||
self.raise_times[key] = remaining - 1
|
||||
return True
|
||||
|
||||
async def send_outbound(self, payload, platform=None):
|
||||
op = payload.get("op")
|
||||
final = bool(payload.get("final"))
|
||||
self.attempts.append((op, final))
|
||||
if self._should_raise(op, final):
|
||||
raise ConnectionError("socket dropped mid-write")
|
||||
self.ops.append(dict(payload))
|
||||
return {"success": True, "message_id": "1723600000.42"}
|
||||
|
||||
|
||||
def _armed_adapter(transport):
|
||||
adapter, _ = _connected_adapter()
|
||||
adapter._transport = transport
|
||||
return adapter
|
||||
|
||||
|
||||
class TestSealTransportError:
|
||||
@pytest.mark.asyncio
|
||||
async def test_seal_exception_retries_same_idempotent_frame(self):
|
||||
"""One transport drop during the seal → the SAME final frame is
|
||||
retried and its success (with the stream ts) is the send result."""
|
||||
t = FlakyTransport(raise_on=("seal",), raise_times={"seal": 1})
|
||||
adapter = _armed_adapter(t)
|
||||
md = {"thread_ts": "1700.1"}
|
||||
await adapter.send_draft("C1", 7, "answer", metadata=md)
|
||||
result = await adapter.send("C1", "answer complete", metadata=md)
|
||||
assert result.success
|
||||
assert result.message_id == "1723600000.42"
|
||||
seal_attempts = [a for a in t.attempts if a == ("draft", True)]
|
||||
assert len(seal_attempts) == 2, "seal must be retried exactly once"
|
||||
# Both attempts carried the identical frame (idempotent replay).
|
||||
finals = [o for o in t.ops if o.get("op") == "draft" and o.get("final")]
|
||||
assert len(finals) == 1 and finals[0]["content"] == "answer complete"
|
||||
# No plain send: a landed retry means no duplicate.
|
||||
assert not [o for o in t.ops if o.get("op") == "send"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_seal_exception_twice_reports_failure(self):
|
||||
"""Both attempts down → SendResult failure (fail-open plain send in
|
||||
send() then also fails on the dead transport; the caller sees the
|
||||
error instead of a phantom success)."""
|
||||
t = FlakyTransport(raise_on=("seal", "send"))
|
||||
adapter = _armed_adapter(t)
|
||||
md = {"thread_ts": "1700.2"}
|
||||
await adapter.send_draft("C1", 8, "answer", metadata=md)
|
||||
with pytest.raises(ConnectionError):
|
||||
# Seal fails twice -> falls through to plain send -> transport
|
||||
# still down -> the plain-send exception propagates (existing
|
||||
# plain-send contract; callers catch).
|
||||
await adapter.send("C1", "answer complete", metadata=md)
|
||||
# The seal was attempted twice, never recorded as delivered.
|
||||
assert [a for a in t.attempts if a == ("draft", True)] == [
|
||||
("draft", True),
|
||||
("draft", True),
|
||||
]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_consumer_never_records_delivery_on_dead_transport(self):
|
||||
"""THE silent-loss regression: transport dead at seal time → the
|
||||
consumer's delivery flags must stay False so the gateway's normal
|
||||
fallback send path still owns the final."""
|
||||
t = FlakyTransport(raise_on=("seal", "send"))
|
||||
adapter = _armed_adapter(t)
|
||||
cfg = StreamConsumerConfig(
|
||||
transport="auto", chat_type="dm",
|
||||
edit_interval=0.01, buffer_threshold=1, cursor="",
|
||||
)
|
||||
sc = GatewayStreamConsumer(
|
||||
adapter, "C1", cfg, metadata={"thread_ts": "1700.3"},
|
||||
)
|
||||
task = asyncio.create_task(sc.run())
|
||||
sc.on_delta("complete answer")
|
||||
await asyncio.sleep(0.08)
|
||||
sc.finish("complete answer")
|
||||
await task
|
||||
# Frames streamed, but the final never reached the wire:
|
||||
assert [o for o in t.ops if o.get("op") == "draft" and not o.get("final")]
|
||||
assert not [o for o in t.ops if o.get("op") == "draft" and o.get("final")]
|
||||
assert not [o for o in t.ops if o.get("op") == "send"]
|
||||
# The consumer must not claim delivery — this is what previously
|
||||
# suppressed the gateway fallback and silently ate the answer.
|
||||
assert sc.final_response_sent is False
|
||||
assert getattr(sc, "_final_content_delivered", False) is False
|
||||
assert sc.delivered_final_matches("complete answer") is not True
|
||||
Reference in New Issue
Block a user