mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-05-03 05:40:37 +00:00
- messaging/telegram.py: Remove unused type: ignore, fix retry_after typing with isinstance(timedelta), use local app variable for None narrowing - messaging/tree_data.py: Replace _queue._queue access with drain-and-restore approach for get_queue_snapshot (avoids private API) - tests/test_api.py: Use APIError instead of RuntimeError for status_code test - tests/test_config.py: Use cast(Any, ...) for invalid validation tests - tests/test_dependencies.py: Add isinstance check for NvidiaNimProvider - tests/test_handler_markdown_and_status_edges.py: Use patch.object for tree_queue method mocks - tests/test_response_models.py: Add isinstance narrowing for content blocks, use Literal list for stop_reason parametrization - tests/test_restart_reply_restore.py: Use patch.object for enqueue mock - tests/test_server_module.py: Use patch.object for uvicorn.run and get_settings - tests/test_telegram_edge_cases.py: Use patch.object for method mocks - tests/test_tree_concurrency.py: Add None assertions for get_node/get_tree Co-authored-by: Ali Khokhar <alishahryar2@gmail.com>
109 lines
3.9 KiB
Python
109 lines
3.9 KiB
Python
import pytest
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
from messaging.handler import ClaudeMessageHandler
|
|
from messaging.session import SessionStore
|
|
from messaging.tree_queue import TreeQueueManager
|
|
from messaging.models import IncomingMessage
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_reply_to_old_status_message_after_restore_routes_to_parent(
|
|
tmp_path, mock_platform, mock_cli_manager
|
|
):
|
|
# Build a persisted tree with a root node A and a bot status message id.
|
|
store_path = tmp_path / "sessions.json"
|
|
store = SessionStore(storage_path=str(store_path))
|
|
|
|
handler1 = ClaudeMessageHandler(mock_platform, mock_cli_manager, store)
|
|
a_incoming = IncomingMessage(
|
|
text="A",
|
|
chat_id="chat_1",
|
|
user_id="user_1",
|
|
message_id="A",
|
|
platform="telegram",
|
|
)
|
|
tree = await handler1.tree_queue.create_tree(
|
|
"A", a_incoming, status_message_id="status_A"
|
|
)
|
|
handler1.tree_queue.register_node("status_A", tree.root_id)
|
|
store.register_node("status_A", tree.root_id)
|
|
store.save_tree(tree.root_id, tree.to_dict())
|
|
|
|
# "Restart": new store instance loads from disk, and we restore TreeQueueManager.
|
|
store2 = SessionStore(storage_path=str(store_path))
|
|
handler2 = ClaudeMessageHandler(mock_platform, mock_cli_manager, store2)
|
|
handler2.tree_queue = TreeQueueManager.from_dict(
|
|
{"trees": store2.get_all_trees(), "node_to_tree": store2.get_node_mapping()},
|
|
queue_update_callback=handler2._update_queue_positions,
|
|
node_started_callback=handler2._mark_node_processing,
|
|
)
|
|
|
|
# Prevent background task scheduling; we only want to validate routing/tree mutation.
|
|
mock_platform.queue_send_message = AsyncMock(return_value="status_reply")
|
|
|
|
reply = IncomingMessage(
|
|
text="R1",
|
|
chat_id="chat_1",
|
|
user_id="user_1",
|
|
message_id="R1",
|
|
platform="telegram",
|
|
reply_to_message_id="status_A",
|
|
)
|
|
|
|
with patch.object(handler2.tree_queue, "enqueue", AsyncMock(return_value=False)):
|
|
await handler2.handle_message(reply)
|
|
|
|
restored_tree = handler2.tree_queue.get_tree_for_node("A")
|
|
assert restored_tree is not None
|
|
node_r1 = restored_tree.get_node("R1")
|
|
assert node_r1 is not None
|
|
assert node_r1.parent_id == "A"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_reply_to_old_status_message_without_mapping_creates_new_conversation(
|
|
tmp_path, mock_platform, mock_cli_manager
|
|
):
|
|
store_path = tmp_path / "sessions.json"
|
|
store = SessionStore(storage_path=str(store_path))
|
|
|
|
handler1 = ClaudeMessageHandler(mock_platform, mock_cli_manager, store)
|
|
a_incoming = IncomingMessage(
|
|
text="A",
|
|
chat_id="chat_1",
|
|
user_id="user_1",
|
|
message_id="A",
|
|
platform="telegram",
|
|
)
|
|
tree = await handler1.tree_queue.create_tree(
|
|
"A", a_incoming, status_message_id="status_A"
|
|
)
|
|
# Intentionally do NOT register "status_A" mapping.
|
|
store.save_tree(tree.root_id, tree.to_dict())
|
|
|
|
store2 = SessionStore(storage_path=str(store_path))
|
|
handler2 = ClaudeMessageHandler(mock_platform, mock_cli_manager, store2)
|
|
handler2.tree_queue = TreeQueueManager.from_dict(
|
|
{"trees": store2.get_all_trees(), "node_to_tree": store2.get_node_mapping()},
|
|
queue_update_callback=handler2._update_queue_positions,
|
|
node_started_callback=handler2._mark_node_processing,
|
|
)
|
|
mock_platform.queue_send_message = AsyncMock(return_value="status_reply")
|
|
|
|
reply = IncomingMessage(
|
|
text="R1",
|
|
chat_id="chat_1",
|
|
user_id="user_1",
|
|
message_id="R1",
|
|
platform="telegram",
|
|
reply_to_message_id="status_A",
|
|
)
|
|
|
|
with patch.object(handler2.tree_queue, "enqueue", AsyncMock(return_value=False)):
|
|
await handler2.handle_message(reply)
|
|
|
|
# Since the mapping is missing, this should be treated as a new conversation.
|
|
new_tree = handler2.tree_queue.get_tree_for_node("R1")
|
|
assert new_tree is not None
|
|
assert new_tree.root_id == "R1"
|