Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
"""Regression: cancellation during the final seal must not orphan the
|
||||
remote stream (PR 85796 review round 2, finding 4).
|
||||
|
||||
_seal_open_draft pops the open entry and writes the local tombstone
|
||||
BEFORE awaiting transport I/O. CancelledError bypasses the failure
|
||||
handling (it is not an Exception), so a cancel mid-seal left:
|
||||
|
||||
remote stream: OPEN (live indicator until connector eviction)
|
||||
adapter._open_draft_by_chat: {} <- abandon finds nothing
|
||||
adapter._sealed_draft_by_chat: {key: id} <- premature tombstone
|
||||
|
||||
The seal now restores the open entry and drops its premature tombstone
|
||||
on CancelledError before re-raising, so the consumer's abandon pass can
|
||||
seal the stream in place.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.gateway.relay.test_relay_live_cards import _connected_adapter
|
||||
|
||||
|
||||
class HangOnSealTransport:
|
||||
def __init__(self):
|
||||
self.ops = []
|
||||
self.hang_seal = True
|
||||
|
||||
async def send_outbound(self, payload, platform=None):
|
||||
final = bool(payload.get("final"))
|
||||
self.ops.append((payload.get("op"), final, str(payload.get("content"))[:25]))
|
||||
if payload.get("op") == "draft" and final and self.hang_seal:
|
||||
await asyncio.sleep(30)
|
||||
return {"success": True, "message_id": "ts.1"}
|
||||
|
||||
|
||||
class TestCancelDuringSeal:
|
||||
@pytest.mark.asyncio
|
||||
async def test_cancel_mid_seal_restores_open_state(self):
|
||||
adapter, _ = _connected_adapter()
|
||||
t = HangOnSealTransport()
|
||||
adapter._transport = t
|
||||
md = {"message_id": "m.1"}
|
||||
await adapter.send_draft("C1", 11, "partial", metadata=md)
|
||||
key = adapter._draft_key("C1", md)
|
||||
|
||||
task = asyncio.create_task(adapter.send("C1", "complete", metadata=dict(md)))
|
||||
await asyncio.sleep(0.05)
|
||||
task.cancel()
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await task
|
||||
|
||||
assert adapter._open_draft_by_chat.get(key) == 11, (
|
||||
"cancelled seal must restore the open entry so abandon can close it"
|
||||
)
|
||||
assert key not in adapter._sealed_draft_by_chat, (
|
||||
"premature tombstone must not survive a cancelled seal"
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_abandon_after_cancelled_seal_closes_remote_stream(self):
|
||||
"""End-to-end: cancel mid-seal, then the abandon pass (what the
|
||||
consumer's CancelledError handler runs) seals the stream."""
|
||||
adapter, _ = _connected_adapter()
|
||||
t = HangOnSealTransport()
|
||||
adapter._transport = t
|
||||
md = {"message_id": "m.2"}
|
||||
await adapter.send_draft("C1", 12, "partial on screen", metadata=md)
|
||||
|
||||
task = asyncio.create_task(adapter.send("C1", "complete", metadata=dict(md)))
|
||||
await asyncio.sleep(0.05)
|
||||
task.cancel()
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await task
|
||||
|
||||
t.hang_seal = False # transport recovers for the abandon pass
|
||||
r = await adapter.abandon_open_draft(
|
||||
"C1", "partial on screen", metadata=dict(md)
|
||||
)
|
||||
assert r.success
|
||||
seals = [o for o in t.ops if o[0] == "draft" and o[1]]
|
||||
# First seal attempt hung (cancelled); the abandon's seal landed.
|
||||
assert seals[-1][2] == "partial on screen"
|
||||
assert not adapter._open_draft_by_chat
|
||||
Reference in New Issue
Block a user