Delayed chat archive finalization after stream stops.
CI / Formatting (push) Successful in 4s
CI / Linting (push) Successful in 4s
CI / Tests (Python 3.12) (push) Successful in 7s
CI / Tests (Python 3.13) (push) Successful in 7s
CI / Tests (Python 3.14) (push) Successful in 6s
CI / Type Checking (push) Successful in 7s
CI / Spelling (push) Successful in 4s
CI / Formatting (push) Successful in 4s
CI / Linting (push) Successful in 4s
CI / Tests (Python 3.12) (push) Successful in 7s
CI / Tests (Python 3.13) (push) Successful in 7s
CI / Tests (Python 3.14) (push) Successful in 6s
CI / Type Checking (push) Successful in 7s
CI / Spelling (push) Successful in 4s
This commit is contained in:
+129
-5
@@ -287,12 +287,12 @@ class TestChatArchive:
|
||||
"chat-archive-20260526T120000Z.json"
|
||||
]
|
||||
|
||||
async def test_stream_stopped_finalizes_archive_and_ignores_later_events(
|
||||
async def test_stream_stopped_keeps_archive_active_during_grace(
|
||||
self,
|
||||
archive_dir: Path,
|
||||
event_dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
"""STREAM_STOPPED finalizes the active archive and closes capture."""
|
||||
"""STREAM_STOPPED records stop metadata but keeps capturing grace chat."""
|
||||
await event_dispatcher.dispatch(
|
||||
EventType.STREAM_STARTED,
|
||||
make_stream_started_event(
|
||||
@@ -312,17 +312,59 @@ class TestChatArchive:
|
||||
|
||||
path = _archive_files(archive_dir)[0]
|
||||
data = _read_archive(path)
|
||||
assert data["archive_stop_reason"] == "stream_stopped_event"
|
||||
assert data["archive_stop_reason"] is None
|
||||
assert data["archive_stopped_at"] is None
|
||||
assert data["stream_stopped_at"] == "2026-05-26T13:30:00+00:00"
|
||||
assert data["event_count"] == 1
|
||||
|
||||
await event_dispatcher.dispatch(
|
||||
EventType.CHAT,
|
||||
make_chat_event(raw_body="grace chat", message_id="msg-grace"),
|
||||
)
|
||||
|
||||
data = _read_archive(path)
|
||||
assert data["archive_stop_reason"] is None
|
||||
assert data["event_count"] == 2
|
||||
assert _events(data)[1]["event_data"]["message_id"] == "msg-grace"
|
||||
|
||||
async def test_stream_stopped_finalizes_after_grace_and_ignores_later_events(
|
||||
self,
|
||||
archive_dir: Path,
|
||||
event_dispatcher: EventDispatcher,
|
||||
module_context: ModuleContext,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""The delayed stop task closes capture after the grace period."""
|
||||
monkeypatch.setattr(chat_archive, "_STOP_GRACE_SECONDS", 0.0)
|
||||
await event_dispatcher.dispatch(
|
||||
EventType.STREAM_STARTED,
|
||||
make_stream_started_event(
|
||||
timestamp=datetime(2026, 5, 26, 12, tzinfo=UTC),
|
||||
),
|
||||
)
|
||||
|
||||
stopped_at = datetime(2026, 5, 26, 13, 30, tzinfo=UTC)
|
||||
await event_dispatcher.dispatch(
|
||||
EventType.STREAM_STOPPED,
|
||||
make_stream_stopped_event(timestamp=stopped_at),
|
||||
)
|
||||
|
||||
state = chat_archive._get_state(module_context)
|
||||
assert state.stop_task is not None
|
||||
await asyncio.wait_for(state.stop_task, timeout=1)
|
||||
|
||||
path = _archive_files(archive_dir)[0]
|
||||
data = _read_archive(path)
|
||||
assert data["archive_stop_reason"] == "stream_stopped_event"
|
||||
assert data["stream_stopped_at"] == "2026-05-26T13:30:00+00:00"
|
||||
_assert_iso_datetime(data["archive_stopped_at"])
|
||||
|
||||
await event_dispatcher.dispatch(
|
||||
EventType.CHAT,
|
||||
make_chat_event(raw_body="after stop", message_id="msg-after"),
|
||||
make_chat_event(raw_body="after grace", message_id="msg-after-grace"),
|
||||
)
|
||||
|
||||
assert _read_archive(path)["event_count"] == 1
|
||||
assert _read_archive(path)["event_count"] == 0
|
||||
|
||||
async def test_new_stream_replaces_active_archive_and_uses_filename_suffix(
|
||||
self,
|
||||
@@ -364,6 +406,54 @@ class TestChatArchive:
|
||||
assert new_archive["event_count"] == 1
|
||||
assert _events(new_archive)[0]["event_data"]["message_id"] == "msg-new"
|
||||
|
||||
async def test_new_stream_during_grace_replaces_stopped_archive(
|
||||
self,
|
||||
archive_dir: Path,
|
||||
event_dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
"""A new stream during the grace period cancels the delayed stop."""
|
||||
first_started_at = datetime(2026, 5, 26, 12, tzinfo=UTC)
|
||||
second_started_at = datetime(2026, 5, 26, 14, tzinfo=UTC)
|
||||
await event_dispatcher.dispatch(
|
||||
EventType.STREAM_STARTED,
|
||||
make_stream_started_event(stream_title="First", timestamp=first_started_at),
|
||||
)
|
||||
await event_dispatcher.dispatch(
|
||||
EventType.STREAM_STOPPED,
|
||||
make_stream_stopped_event(
|
||||
stream_title="First",
|
||||
timestamp=datetime(2026, 5, 26, 13, 30, tzinfo=UTC),
|
||||
),
|
||||
)
|
||||
await event_dispatcher.dispatch(
|
||||
EventType.CHAT,
|
||||
make_chat_event(raw_body="grace chat", message_id="msg-grace"),
|
||||
)
|
||||
await event_dispatcher.dispatch(
|
||||
EventType.STREAM_STARTED,
|
||||
make_stream_started_event(
|
||||
stream_title="Second",
|
||||
timestamp=second_started_at,
|
||||
),
|
||||
)
|
||||
await event_dispatcher.dispatch(
|
||||
EventType.CHAT,
|
||||
make_chat_event(raw_body="new stream message", message_id="msg-new"),
|
||||
)
|
||||
|
||||
old_archive = _read_archive(archive_dir / "chat-archive-20260526T120000Z.json")
|
||||
assert old_archive["archive_stop_reason"] == "replaced_by_stream_started_event"
|
||||
assert old_archive["stream_stopped_at"] == "2026-05-26T13:30:00+00:00"
|
||||
assert old_archive["event_count"] == 1
|
||||
assert _events(old_archive)[0]["event_data"]["message_id"] == "msg-grace"
|
||||
|
||||
new_archive = _read_archive(archive_dir / "chat-archive-20260526T140000Z.json")
|
||||
assert new_archive["archive_start_reason"] == "stream_started_event"
|
||||
assert new_archive["archive_stop_reason"] is None
|
||||
assert new_archive["stream_title"] == "Second"
|
||||
assert new_archive["event_count"] == 1
|
||||
assert _events(new_archive)[0]["event_data"]["message_id"] == "msg-new"
|
||||
|
||||
|
||||
class TestSetupAndTeardown:
|
||||
"""Cases that need direct control around setup and teardown timing."""
|
||||
@@ -487,6 +577,40 @@ class TestSetupAndTeardown:
|
||||
assert data["stream_stopped_at"] is None
|
||||
_assert_iso_datetime(data["archive_stopped_at"])
|
||||
|
||||
@pytest.mark.usefixtures("registered_handlers")
|
||||
async def test_teardown_cancels_pending_stream_stop_grace(
|
||||
self,
|
||||
archive_dir: Path,
|
||||
module_context: ModuleContext,
|
||||
event_dispatcher: EventDispatcher,
|
||||
) -> None:
|
||||
"""Module teardown cancels delayed stop and finalizes immediately."""
|
||||
await chat_archive.setup(module_context)
|
||||
await event_dispatcher.dispatch(
|
||||
EventType.STREAM_STARTED,
|
||||
make_stream_started_event(
|
||||
timestamp=datetime(2026, 5, 26, 12, tzinfo=UTC),
|
||||
),
|
||||
)
|
||||
await event_dispatcher.dispatch(
|
||||
EventType.STREAM_STOPPED,
|
||||
make_stream_stopped_event(
|
||||
timestamp=datetime(2026, 5, 26, 13, 30, tzinfo=UTC),
|
||||
),
|
||||
)
|
||||
|
||||
state = chat_archive._get_state(module_context)
|
||||
assert state.stop_task is not None
|
||||
path = _archive_files(archive_dir)[0]
|
||||
|
||||
await chat_archive.teardown(module_context)
|
||||
|
||||
assert chat_archive._get_state(module_context).stop_task is None
|
||||
data = _read_archive(path)
|
||||
assert data["archive_stop_reason"] == "module_teardown"
|
||||
assert data["stream_stopped_at"] == "2026-05-26T13:30:00+00:00"
|
||||
_assert_iso_datetime(data["archive_stopped_at"])
|
||||
|
||||
|
||||
class TestConfiguredOutput:
|
||||
"""Configuration-dependent archive output behavior."""
|
||||
|
||||
Reference in New Issue
Block a user