"""Invariant: closing or deleting a session must strand NO per-session state.

The orchestration core keeps several maps keyed by session id (the session
record, its asyncio task, the live partial-stream mirror, and two module-level
view-builder retry/dirty structures). Removal used to pop only `sessions` +
`tasks`, leaking the rest for the life of the process, an unbounded creep over
a long-running app. `purge_session_memory` is the single chokepoint both the
close and delete paths route through; this pins the invariant that after it
runs the id is gone from EVERY structure, while a sibling session is untouched.

Run with:  backend/.venv/bin/python -m pytest backend/tests/test_session_cleanup.py
"""
from backend.apps.agents import agent_manager as am
from backend.apps.agents.manager import view_builder_state as vbs


def test_purge_session_memory_clears_every_structure():
    mgr = am.AgentManager()
    mgr.sessions = {"dead": object(), "alive": object()}
    mgr.tasks = {"dead": object()}
    mgr.live_partial = {"dead": {"text": "half a reply"}}
    mgr.pending_messages = {"dead": [object()]}
    vbs.view_builder_render_retry_counts["dead"] = 4
    vbs.view_builder_dirty_sessions.add("dead")

    mgr.purge_session_memory("dead")

    assert "dead" not in mgr.sessions
    assert "dead" not in mgr.tasks
    assert "dead" not in mgr.live_partial
    assert "dead" not in mgr.pending_messages
    assert "dead" not in vbs.view_builder_render_retry_counts
    assert "dead" not in vbs.view_builder_dirty_sessions
    # Only the target id is purged; an unrelated live session survives.
    assert "alive" in mgr.sessions


def test_purge_is_safe_on_an_untracked_id():
    # Purging an id that was never tracked must be a quiet no-op, not a KeyError, so the delete/close paths can call it unconditionally.
    mgr = am.AgentManager()
    mgr.purge_session_memory("never-existed")
    assert mgr.sessions == {}
