mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-05-01 04:50:04 +00:00
Phase 7: Directory restructuring (messaging/ and tests/)
- Create messaging/platforms/ (base, discord, telegram, factory) - Create messaging/rendering/ (discord_markdown, telegram_markdown) - Create messaging/trees/ (data, repository, processor, queue_manager) - Organize tests/ into api/, providers/, messaging/, cli/, config/ - Add backward-compatible re-exports at old locations - Update handler.py and test_messaging_factory.py imports - Fix Telegram type hints for TELEGRAM_AVAILABLE=False case - Fix Python 3 except syntax in discord_markdown Co-authored-by: Ali Khokhar <alishahryar2@gmail.com>
This commit is contained in:
parent
38a7980546
commit
4b4f87515d
76 changed files with 3294 additions and 3124 deletions
157
tests/messaging/test_handler_context_isolation.py
Normal file
157
tests/messaging/test_handler_context_isolation.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from messaging.handler import ClaudeMessageHandler
|
||||
from messaging.tree_data import MessageState
|
||||
|
||||
|
||||
async def _gen_session(events):
|
||||
for e in events:
|
||||
yield e
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def handler(mock_platform, mock_cli_manager, mock_session_store):
|
||||
return ClaudeMessageHandler(mock_platform, mock_cli_manager, mock_session_store)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sibling_replies_fork_from_parent_session_id(
|
||||
handler, mock_cli_manager, incoming_message_factory
|
||||
):
|
||||
# Root node A with a known session_id.
|
||||
root_incoming = incoming_message_factory(text="A", message_id="A")
|
||||
tree = await handler.tree_queue.create_tree(
|
||||
node_id="A", incoming=root_incoming, status_message_id="status_A"
|
||||
)
|
||||
await tree.update_state("A", MessageState.COMPLETED, session_id="sess_A")
|
||||
|
||||
# Add two sibling replies R1 and R2 under A.
|
||||
r1_incoming = incoming_message_factory(
|
||||
text="R1", message_id="R1", reply_to_message_id="A"
|
||||
)
|
||||
r2_incoming = incoming_message_factory(
|
||||
text="R2", message_id="R2", reply_to_message_id="A"
|
||||
)
|
||||
_, r1_node = await handler.tree_queue.add_to_tree(
|
||||
"A", "R1", r1_incoming, "status_R1"
|
||||
)
|
||||
_, r2_node = await handler.tree_queue.add_to_tree(
|
||||
"A", "R2", r2_incoming, "status_R2"
|
||||
)
|
||||
|
||||
# Mock a fresh cli_session per node.
|
||||
calls = []
|
||||
|
||||
async def _get_or_create_session(session_id=None):
|
||||
cli_session = MagicMock()
|
||||
|
||||
async def _start_task(prompt, session_id=None, fork_session=False):
|
||||
calls.append((prompt, session_id, fork_session))
|
||||
child_sid = f"sess_{prompt}"
|
||||
async for ev in _gen_session(
|
||||
[
|
||||
{"type": "session_info", "session_id": child_sid},
|
||||
{"type": "exit", "code": 0, "stderr": None},
|
||||
]
|
||||
):
|
||||
yield ev
|
||||
|
||||
cli_session.start_task = _start_task
|
||||
return cli_session, f"pending_{len(calls) + 1}", True
|
||||
|
||||
mock_cli_manager.get_or_create_session = AsyncMock(
|
||||
side_effect=_get_or_create_session
|
||||
)
|
||||
|
||||
await handler._process_node("R1", r1_node)
|
||||
await handler._process_node("R2", r2_node)
|
||||
|
||||
# Both siblings must resume from the same parent session and fork.
|
||||
assert calls[0][0] == "R1"
|
||||
assert calls[0][1] == "sess_A"
|
||||
assert calls[0][2] is True
|
||||
|
||||
assert calls[1][0] == "R2"
|
||||
assert calls[1][1] == "sess_A"
|
||||
assert calls[1][2] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_grandchild_reply_forks_from_branch_session(
|
||||
handler, mock_cli_manager, incoming_message_factory
|
||||
):
|
||||
root_incoming = incoming_message_factory(text="A", message_id="A")
|
||||
tree = await handler.tree_queue.create_tree(
|
||||
node_id="A", incoming=root_incoming, status_message_id="status_A"
|
||||
)
|
||||
await tree.update_state("A", MessageState.COMPLETED, session_id="sess_A")
|
||||
|
||||
r1_incoming = incoming_message_factory(
|
||||
text="R1", message_id="R1", reply_to_message_id="A"
|
||||
)
|
||||
_, r1_node = await handler.tree_queue.add_to_tree(
|
||||
"A", "R1", r1_incoming, "status_R1"
|
||||
)
|
||||
|
||||
calls = []
|
||||
|
||||
async def _get_or_create_session(session_id=None):
|
||||
cli_session = MagicMock()
|
||||
|
||||
async def _start_task(prompt, session_id=None, fork_session=False):
|
||||
calls.append((prompt, session_id, fork_session))
|
||||
# R1 gets its own forked session id.
|
||||
child_sid = "sess_R1"
|
||||
async for ev in _gen_session(
|
||||
[
|
||||
{"type": "session_info", "session_id": child_sid},
|
||||
{"type": "exit", "code": 0, "stderr": None},
|
||||
]
|
||||
):
|
||||
yield ev
|
||||
|
||||
cli_session.start_task = _start_task
|
||||
return cli_session, "pending_R1", True
|
||||
|
||||
mock_cli_manager.get_or_create_session = AsyncMock(
|
||||
side_effect=_get_or_create_session
|
||||
)
|
||||
|
||||
await handler._process_node("R1", r1_node)
|
||||
assert r1_node.session_id == "sess_R1"
|
||||
|
||||
# Grandchild C1 replies to R1 and must fork from sess_R1, not sess_A.
|
||||
c1_incoming = incoming_message_factory(
|
||||
text="C1", message_id="C1", reply_to_message_id="R1"
|
||||
)
|
||||
_, c1_node = await handler.tree_queue.add_to_tree(
|
||||
"R1", "C1", c1_incoming, "status_C1"
|
||||
)
|
||||
|
||||
async def _get_or_create_session_c1(session_id=None):
|
||||
cli_session = MagicMock()
|
||||
|
||||
async def _start_task(prompt, session_id=None, fork_session=False):
|
||||
calls.append((prompt, session_id, fork_session))
|
||||
async for ev in _gen_session(
|
||||
[
|
||||
{"type": "session_info", "session_id": "sess_C1"},
|
||||
{"type": "exit", "code": 0, "stderr": None},
|
||||
]
|
||||
):
|
||||
yield ev
|
||||
|
||||
cli_session.start_task = _start_task
|
||||
return cli_session, "pending_C1", True
|
||||
|
||||
mock_cli_manager.get_or_create_session = AsyncMock(
|
||||
side_effect=_get_or_create_session_c1
|
||||
)
|
||||
|
||||
await handler._process_node("C1", c1_node)
|
||||
|
||||
# The last call should be for C1 and must resume from sess_R1.
|
||||
assert calls[-1][0] == "C1"
|
||||
assert calls[-1][1] == "sess_R1"
|
||||
assert calls[-1][2] is True
|
||||
Loading…
Add table
Add a link
Reference in a new issue