#!/usr/bin/env python3 """ Test Koyeb Terminal Tool This script tests that the Koyeb terminal backend is correctly configured and can execute commands in Koyeb sandboxes. Usage: # Run with Koyeb backend TERMINAL_ENV=koyeb python tests/test_koyeb_terminal.py # Or run directly (will use whatever TERMINAL_ENV is set in .env) python tests/test_koyeb_terminal.py """ import pytest pytestmark = pytest.mark.integration import os import sys import json from pathlib import Path # Try to load .env file if python-dotenv is available try: from dotenv import load_dotenv load_dotenv() except ImportError: # Manually load .env if dotenv not available env_file = Path(__file__).parent.parent.parent / ".env" if env_file.exists(): with open(env_file) as f: for line in f: line = line.strip() if line and not line.startswith('#') and '=' in line: key, value = line.split('=', 1) # Remove quotes if present value = value.strip().strip('"').strip("'") os.environ.setdefault(key.strip(), value) # Add project root to path for imports parent_dir = Path(__file__).parent.parent.parent sys.path.insert(0, str(parent_dir)) # Import terminal_tool module directly using importlib to avoid tools/__init__.py import importlib.util terminal_tool_path = parent_dir / "tools" / "terminal_tool.py" spec = importlib.util.spec_from_file_location("terminal_tool", terminal_tool_path) terminal_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(terminal_module) terminal_tool = terminal_module.terminal_tool check_terminal_requirements = terminal_module.check_terminal_requirements _get_env_config = terminal_module._get_env_config cleanup_vm = terminal_module.cleanup_vm def test_koyeb_requirements(): """Test that Koyeb requirements are met.""" print("\n" + "=" * 60) print("TEST 1: Koyeb Requirements Check") print("=" * 60) config = _get_env_config() print(f"Current TERMINAL_ENV: {config['env_type']}") print(f"Koyeb image: {config['koyeb_image']}") # Check for Koyeb authentication koyeb_token = os.getenv("KOYEB_API_TOKEN") print(f"\nKoyeb authentication:") print(f" KOYEB_API_TOKEN env var: {'✅ Set' if koyeb_token else '❌ Not set'}") if config['env_type'] != 'koyeb': print(f"\n⚠️ TERMINAL_ENV is '{config['env_type']}', not 'koyeb'") print(" Set TERMINAL_ENV=koyeb in .env or export it to test Koyeb backend") return False requirements_met = check_terminal_requirements() print(f"\nRequirements check: {'✅ Passed' if requirements_met else '❌ Failed'}") return requirements_met def test_simple_command(): """Test executing a simple command.""" print("\n" + "=" * 60) print("TEST 2: Simple Command Execution") print("=" * 60) test_task_id = "koyeb_test_simple" print("Executing: echo 'Hello from Koyeb!'") result = terminal_tool("echo 'Hello from Koyeb!'", task_id=test_task_id) result_json = json.loads(result) print(f"\nResult:") print(f" Output: {result_json.get('output', '')[:200]}") print(f" Exit code: {result_json.get('exit_code')}") print(f" Error: {result_json.get('error')}") success = result_json.get('exit_code') == 0 and 'Hello from Koyeb!' in result_json.get('output', '') print(f"\nTest: {'✅ Passed' if success else '❌ Failed'}") # Cleanup cleanup_vm(test_task_id) return success def test_python_execution(): """Test executing Python code in Koyeb.""" print("\n" + "=" * 60) print("TEST 3: Python Execution") print("=" * 60) test_task_id = "koyeb_test_python" python_cmd = 'python3 -c "import sys; print(f\'Python {sys.version}\')"' print(f"Executing: {python_cmd}") result = terminal_tool(python_cmd, task_id=test_task_id) result_json = json.loads(result) print(f"\nResult:") print(f" Output: {result_json.get('output', '')[:200]}") print(f" Exit code: {result_json.get('exit_code')}") print(f" Error: {result_json.get('error')}") success = result_json.get('exit_code') == 0 and 'Python' in result_json.get('output', '') print(f"\nTest: {'✅ Passed' if success else '❌ Failed'}") # Cleanup cleanup_vm(test_task_id) return success def test_filesystem_operations(): """Test filesystem operations in Koyeb.""" print("\n" + "=" * 60) print("TEST 4: Filesystem Operations") print("=" * 60) test_task_id = "koyeb_test_fs" # Create a file print("Step 1: Creating test file...") result1 = terminal_tool("echo 'koyeb filesystem test' > /tmp/koyeb_test.txt", task_id=test_task_id) result1_json = json.loads(result1) print(f" Exit code: {result1_json.get('exit_code')}") # Read the file back print("Step 2: Reading test file...") result2 = terminal_tool("cat /tmp/koyeb_test.txt", task_id=test_task_id) result2_json = json.loads(result2) print(f" Output: {result2_json.get('output', '')}") print(f" Exit code: {result2_json.get('exit_code')}") success = ( result1_json.get('exit_code') == 0 and result2_json.get('exit_code') == 0 and 'koyeb filesystem test' in result2_json.get('output', '') ) print(f"\nTest: {'✅ Passed' if success else '❌ Failed'}") # Cleanup cleanup_vm(test_task_id) return success def test_environment_isolation(): """Test that different task_ids get isolated environments.""" print("\n" + "=" * 60) print("TEST 5: Environment Isolation") print("=" * 60) task1 = "koyeb_test_iso_1" task2 = "koyeb_test_iso_2" # Create file in task1 print("Step 1: Creating file in task1...") result1 = terminal_tool("echo 'task1 data' > /tmp/isolated.txt", task_id=task1) # Try to read from task2 (should not exist) print("Step 2: Trying to read file from task2 (should not exist)...") result2 = terminal_tool("cat /tmp/isolated.txt 2>&1 || echo 'FILE_NOT_FOUND'", task_id=task2) result2_json = json.loads(result2) # The file should either not exist or be empty in task2 output = result2_json.get('output', '') isolated = 'task1 data' not in output or 'FILE_NOT_FOUND' in output or 'No such file' in output print(f" Task2 output: {output[:200]}") print(f"\nTest: {'✅ Passed (environments isolated)' if isolated else '❌ Failed (environments NOT isolated)'}") # Cleanup cleanup_vm(task1) cleanup_vm(task2) return isolated def main(): """Run all Koyeb terminal tests.""" print("🧪 Koyeb Terminal Tool Test Suite") print("=" * 60) # Check current config config = _get_env_config() print(f"\nCurrent configuration:") print(f" TERMINAL_ENV: {config['env_type']}") print(f" TERMINAL_KOYEB_IMAGE: {config['koyeb_image']}") print(f" TERMINAL_TIMEOUT: {config['timeout']}s") if config['env_type'] != 'koyeb': print(f"\n⚠️ WARNING: TERMINAL_ENV is set to '{config['env_type']}', not 'koyeb'") print(" To test Koyeb specifically, set TERMINAL_ENV=koyeb") response = input("\n Continue testing with current backend? (y/n): ") if response.lower() != 'y': print("Aborting.") return results = {} # Run tests results['requirements'] = test_koyeb_requirements() if not results['requirements']: print("\n❌ Requirements not met. Cannot continue with other tests.") return results['simple_command'] = test_simple_command() results['python_execution'] = test_python_execution() results['filesystem_operations'] = test_filesystem_operations() results['environment_isolation'] = test_environment_isolation() # Summary print("\n" + "=" * 60) print("TEST SUMMARY") print("=" * 60) passed = sum(1 for v in results.values() if v) total = len(results) for test_name, passed_test in results.items(): status = "✅ PASSED" if passed_test else "❌ FAILED" print(f" {test_name}: {status}") print(f"\nTotal: {passed}/{total} tests passed") return passed == total if __name__ == "__main__": success = main() sys.exit(0 if success else 1)