Files
hermes-agent/tests/integration/test_daytona_terminal.py
rovle d5efb82c7c test(daytona): add unit and integration tests for Daytona backend
Unit tests cover cwd resolution, sandbox persistence/resume, cleanup,
command execution, resource conversion, interrupt handling, retry
exhaustion, and sandbox readiness checks. Integration tests verify
basic commands, filesystem ops, session persistence, and task
isolation against a live Daytona API.

Signed-off-by: rovle <lovre.pesut@gmail.com>
2026-03-05 10:26:22 -08:00

124 lines
4.0 KiB
Python

"""Integration tests for the Daytona terminal backend.
Requires DAYTONA_API_KEY to be set. Run with:
TERMINAL_ENV=daytona pytest tests/integration/test_daytona_terminal.py -v
"""
import json
import os
import sys
from pathlib import Path
import pytest
pytestmark = pytest.mark.integration
# Skip entire module if no API key
if not os.getenv("DAYTONA_API_KEY"):
pytest.skip("DAYTONA_API_KEY not set", allow_module_level=True)
# Import terminal_tool via importlib to avoid tools/__init__.py side effects
import importlib.util
parent_dir = Path(__file__).parent.parent.parent
sys.path.insert(0, str(parent_dir))
spec = importlib.util.spec_from_file_location(
"terminal_tool", parent_dir / "tools" / "terminal_tool.py"
)
terminal_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(terminal_module)
terminal_tool = terminal_module.terminal_tool
cleanup_vm = terminal_module.cleanup_vm
@pytest.fixture(autouse=True)
def _force_daytona(monkeypatch):
monkeypatch.setenv("TERMINAL_ENV", "daytona")
monkeypatch.setenv("TERMINAL_CONTAINER_DISK", "10240")
monkeypatch.setenv("TERMINAL_CONTAINER_PERSISTENT", "false")
@pytest.fixture()
def task_id(request):
"""Provide a unique task_id and clean up the sandbox after the test."""
tid = f"daytona_test_{request.node.name}"
yield tid
cleanup_vm(tid)
def _run(command, task_id, **kwargs):
result = terminal_tool(command, task_id=task_id, **kwargs)
return json.loads(result)
class TestDaytonaBasic:
def test_echo(self, task_id):
r = _run("echo 'Hello from Daytona!'", task_id)
assert r["exit_code"] == 0
assert "Hello from Daytona!" in r["output"]
def test_python_version(self, task_id):
r = _run("python3 --version", task_id)
assert r["exit_code"] == 0
assert "Python" in r["output"]
def test_nonzero_exit(self, task_id):
r = _run("exit 42", task_id)
assert r["exit_code"] == 42
def test_os_info(self, task_id):
r = _run("uname -a", task_id)
assert r["exit_code"] == 0
assert "Linux" in r["output"]
class TestDaytonaFilesystem:
def test_write_and_read_file(self, task_id):
_run("echo 'test content' > /tmp/daytona_test.txt", task_id)
r = _run("cat /tmp/daytona_test.txt", task_id)
assert r["exit_code"] == 0
assert "test content" in r["output"]
def test_persistence_within_session(self, task_id):
_run("pip install cowsay 2>/dev/null", task_id, timeout=120)
r = _run('python3 -c "import cowsay; print(cowsay.__file__)"', task_id)
assert r["exit_code"] == 0
assert "cowsay" in r["output"]
class TestDaytonaPersistence:
def test_filesystem_survives_stop_and_resume(self):
"""Write a file, stop the sandbox, resume it, assert the file persists."""
task = "daytona_test_persist"
try:
# Enable persistence for this test
os.environ["TERMINAL_CONTAINER_PERSISTENT"] = "true"
# Write a marker file and stop the sandbox
_run("echo 'survive' > /tmp/persist_test.txt", task)
cleanup_vm(task) # stops (not deletes) because persistent=true
# Resume with the same task_id — file should still exist
r = _run("cat /tmp/persist_test.txt", task)
assert r["exit_code"] == 0
assert "survive" in r["output"]
finally:
# Force-delete so the sandbox doesn't leak
os.environ["TERMINAL_CONTAINER_PERSISTENT"] = "false"
cleanup_vm(task)
class TestDaytonaIsolation:
def test_different_tasks_isolated(self):
task_a = "daytona_test_iso_a"
task_b = "daytona_test_iso_b"
try:
_run("echo 'secret' > /tmp/isolated.txt", task_a)
r = _run("cat /tmp/isolated.txt 2>&1 || echo NOT_FOUND", task_b)
assert "secret" not in r["output"] or "NOT_FOUND" in r["output"]
finally:
cleanup_vm(task_a)
cleanup_vm(task_b)