galaxis-agent/tests/test_recovery.py
머니페니 e82dfe18f9 feat: add startup recovery and periodic container cleanup
- Add reset_running_to_pending() to PersistentTaskQueue for recovery
- Implement recover_on_startup() to reset interrupted tasks and clean zombies
- Add ContainerCleaner for periodic removal of old sandbox containers
- Add 4 tests covering recovery scenarios and container cleanup logic
2026-03-20 18:41:41 +09:00

80 lines
2.5 KiB
Python

import pytest
import os
import tempfile
from unittest.mock import AsyncMock, MagicMock, patch
from agent.task_queue import PersistentTaskQueue
from agent.recovery import recover_on_startup, ContainerCleaner
@pytest.fixture
async def task_queue():
fd, db_path = tempfile.mkstemp(suffix=".db")
os.close(fd)
queue = PersistentTaskQueue(db_path=db_path)
await queue.initialize()
yield queue
await queue.close()
os.unlink(db_path)
@pytest.mark.asyncio
async def test_recover_resets_running_to_pending(task_queue):
await task_queue.enqueue("thread-1", "gitea", {"msg": "interrupted"})
await task_queue.dequeue() # → running
assert await task_queue.has_running_task("thread-1") is True
with patch("agent.recovery._cleanup_zombie_containers", new_callable=AsyncMock):
await recover_on_startup(task_queue)
assert await task_queue.has_running_task("thread-1") is False
pending = await task_queue.get_pending()
assert len(pending) == 1
@pytest.mark.asyncio
async def test_recover_no_running_tasks(task_queue):
with patch("agent.recovery._cleanup_zombie_containers", new_callable=AsyncMock):
await recover_on_startup(task_queue)
pending = await task_queue.get_pending()
assert len(pending) == 0
@pytest.mark.asyncio
async def test_container_cleaner_removes_old():
mock_container = MagicMock()
mock_container.name = "galaxis-sandbox-old"
mock_container.labels = {"galaxis-agent-sandbox": "true"}
mock_container.attrs = {"Created": "2026-03-19T00:00:00Z"}
mock_container.stop = MagicMock()
mock_container.remove = MagicMock()
mock_docker = MagicMock()
mock_docker.containers.list.return_value = [mock_container]
cleaner = ContainerCleaner(docker_client=mock_docker, max_age_seconds=600)
removed = await cleaner.cleanup_once()
assert removed == 1
mock_container.stop.assert_called_once()
mock_container.remove.assert_called_once()
@pytest.mark.asyncio
async def test_container_cleaner_keeps_recent():
from datetime import datetime, timezone
now = datetime.now(timezone.utc).isoformat()
mock_container = MagicMock()
mock_container.labels = {"galaxis-agent-sandbox": "true"}
mock_container.attrs = {"Created": now}
mock_docker = MagicMock()
mock_docker.containers.list.return_value = [mock_container]
cleaner = ContainerCleaner(docker_client=mock_docker, max_age_seconds=3600)
removed = await cleaner.cleanup_once()
assert removed == 0
mock_container.stop.assert_not_called()