mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-24 13:32:22 +02:00
[eric] tests: the suite can never resume real chats with live credentials (ENG-388)
This commit is contained in:
@@ -4,6 +4,7 @@ on boot). Split from SessionLifecycle (which handles ONE session at a time) so e
|
||||
one concern. self.sessions resolves across the MRO as before."""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from typeguard import typechecked
|
||||
|
||||
@@ -18,6 +19,13 @@ from backend.apps.agents.manager.session.apply_context_window import apply_conte
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def running_under_test() -> bool:
|
||||
"""Auto-resume dispatches REAL turns: live credentials, live Bash, in whatever tree the process
|
||||
was started from. A test that boots the app lifespan must never do that to the developer's own
|
||||
chats, so this is the one gate that keeps a suite run from becoming an agent run."""
|
||||
return "pytest" in sys.modules
|
||||
|
||||
|
||||
from backend.apps.agents.manager.AgentManagerProtocol import AgentManagerProtocol
|
||||
|
||||
|
||||
@@ -80,6 +88,10 @@ class SessionPersistence(AgentManagerProtocol):
|
||||
"""Fire one hidden continuation into each crash-interrupted session (called after
|
||||
restore, off the boot critical path). Failure is per-session and non-fatal: a session
|
||||
that cannot resume just keeps its amber chip."""
|
||||
if running_under_test():
|
||||
logger.info("crash-resume: skipped, running under test")
|
||||
self.crash_resume_queue = []
|
||||
return
|
||||
for sid in list(getattr(self, "crash_resume_queue", []) or []):
|
||||
try:
|
||||
# send_message lives on the Messaging mixin; AgentManager composes both.
|
||||
|
||||
@@ -58,6 +58,13 @@ def test_second_consecutive_crash_trips_the_breaker(manager, tmp_path, monkeypat
|
||||
assert json.load(f)["crash_interrupt_count"] == 2
|
||||
|
||||
|
||||
def p_act_like_a_real_app(monkeypatch) -> None:
|
||||
"""Auto-resume refuses to dispatch under pytest, because it sends REAL turns and the suite shares
|
||||
the developer's data root (ENG-388). These tests are about the production path, so they opt out."""
|
||||
import backend.apps.agents.manager.session.SessionPersistence as p_sp
|
||||
monkeypatch.setattr(p_sp, "running_under_test", lambda: False)
|
||||
|
||||
|
||||
def test_waiting_approval_never_auto_resumes(manager, tmp_path, monkeypatch):
|
||||
p_write_session(tmp_path, monkeypatch, "s-appr", "waiting_approval", [p_msg("user"), p_msg("tool_call")])
|
||||
asyncio.run(manager.reconcile_on_startup())
|
||||
@@ -65,6 +72,7 @@ def test_waiting_approval_never_auto_resumes(manager, tmp_path, monkeypatch):
|
||||
|
||||
|
||||
def test_auto_resume_sends_one_hidden_continuation(manager, tmp_path, monkeypatch):
|
||||
p_act_like_a_real_app(monkeypatch)
|
||||
p_write_session(tmp_path, monkeypatch, "s-cut", "running", [p_msg("user"), p_msg("tool_call")])
|
||||
asyncio.run(manager.reconcile_on_startup())
|
||||
sent = []
|
||||
@@ -81,6 +89,7 @@ def test_auto_resume_sends_one_hidden_continuation(manager, tmp_path, monkeypatc
|
||||
|
||||
|
||||
def test_resume_failure_is_per_session_and_non_fatal(manager, tmp_path, monkeypatch):
|
||||
p_act_like_a_real_app(monkeypatch)
|
||||
p_write_session(tmp_path, monkeypatch, "s-a", "running", [p_msg("user"), p_msg("tool_call")])
|
||||
p_write_session(tmp_path, monkeypatch, "s-b", "running", [p_msg("user"), p_msg("tool_call")])
|
||||
asyncio.run(manager.reconcile_on_startup())
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
"""A test run must never become an agent run.
|
||||
|
||||
The bug class (caught live 2026-08-21): `agents_lifespan` fires `auto_resume_crashed_turns()`, which
|
||||
sends a real hidden continuation into every crash-interrupted session. The suite does not isolate the
|
||||
data root, so any test that boots the app lifespan resumed the DEVELOPER'S OWN chats with live
|
||||
credentials and live Bash, inside whatever tree the suite was running from. Observed: a full
|
||||
`pytest backend/tests` run spawned two real CLI processes which each started their own
|
||||
`pytest backend/tests/` run, because that was the task those chats had been interrupted mid-way
|
||||
through. It spends real money, rewrites real session files, and executes commands from old
|
||||
conversations against the working tree.
|
||||
|
||||
The seal: auto-resume refuses to dispatch when pytest is loaded.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
import backend.apps.agents.manager.session.SessionPersistence as p_sp
|
||||
from backend.apps.agents.agent_manager import agent_manager
|
||||
|
||||
|
||||
def p_capture_sends(monkeypatch) -> list:
|
||||
sent: list = []
|
||||
|
||||
async def fake_send(session_id, prompt, hidden=False, **kwargs):
|
||||
sent.append(session_id)
|
||||
|
||||
monkeypatch.setattr(agent_manager, "send_message", fake_send)
|
||||
agent_manager.crash_resume_queue = ["sid-a", "sid-b"]
|
||||
return sent
|
||||
|
||||
|
||||
def test_auto_resume_never_dispatches_under_test(monkeypatch):
|
||||
sent = p_capture_sends(monkeypatch)
|
||||
asyncio.run(agent_manager.auto_resume_crashed_turns())
|
||||
assert sent == [], "a suite run must not spend credentials resuming real chats"
|
||||
assert agent_manager.crash_resume_queue == [], "the queue is still cleared, so nothing resumes later either"
|
||||
|
||||
|
||||
def test_the_gate_is_what_stops_it(monkeypatch):
|
||||
"""Control: with the gate reporting a normal app process, the very same call does dispatch.
|
||||
Without this arm the test above passes even if auto-resume quietly stopped working."""
|
||||
sent = p_capture_sends(monkeypatch)
|
||||
monkeypatch.setattr(p_sp, "running_under_test", lambda: False)
|
||||
asyncio.run(agent_manager.auto_resume_crashed_turns())
|
||||
assert sent == ["sid-a", "sid-b"]
|
||||
|
||||
|
||||
def test_the_gate_sees_pytest():
|
||||
assert p_sp.running_under_test() is True
|
||||
Reference in New Issue
Block a user