initial commit after one day coding agent session
This commit is contained in:
commit
372af75b90
88 changed files with 22694 additions and 0 deletions
59
tests/integration/conftest.py
Normal file
59
tests/integration/conftest.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""Shared fixtures for integration tests."""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
from mcp_forge.config.schema import (
|
||||
ForgeConfig, ServerConfig, SecurityConfig, ExecutionConfig, SessionConfig,
|
||||
ImageConfig, VolumeConfig, EnvironmentBuilderConfig, PackageValidationConfig
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def real_config(tmp_path):
|
||||
"""Real configuration with all required fields for integration tests."""
|
||||
# Create required files
|
||||
(tmp_path / "allowlist.txt").write_text("requests\npandas\nnumpy\n")
|
||||
(tmp_path / "blocklist.txt").write_text("")
|
||||
|
||||
config = ForgeConfig(
|
||||
server=ServerConfig(
|
||||
host="localhost",
|
||||
port=3000,
|
||||
podman_socket=Path("/run/user/1000/podman/podman.sock")
|
||||
),
|
||||
security=SecurityConfig(
|
||||
audit_log=tmp_path / "audit.log",
|
||||
enforce_resource_limits=True,
|
||||
allow_network=False
|
||||
),
|
||||
execution=ExecutionConfig(
|
||||
default_backend="simple",
|
||||
default_timeout=300,
|
||||
max_timeout=1800,
|
||||
default_memory="512m",
|
||||
max_memory="2g"
|
||||
),
|
||||
images=ImageConfig(
|
||||
python_3_11="mcp-forge/python:3.11",
|
||||
python_3_12="mcp-forge/python:3.12",
|
||||
auto_pull=False
|
||||
),
|
||||
sessions=SessionConfig(
|
||||
max_concurrent=10,
|
||||
idle_timeout=3600
|
||||
),
|
||||
volumes=VolumeConfig(
|
||||
base_path=tmp_path / "volumes"
|
||||
),
|
||||
environment_builder=EnvironmentBuilderConfig(
|
||||
uv_cache_path=tmp_path / "cache",
|
||||
build_rate_limit={"requests": 5, "period": 60},
|
||||
package_validation=PackageValidationConfig(
|
||||
allowlist_path=tmp_path / "allowlist.txt",
|
||||
blocklist_path=tmp_path / "blocklist.txt"
|
||||
)
|
||||
),
|
||||
mcp_tools={}
|
||||
)
|
||||
return config
|
||||
56
tests/integration/test_environment_build.py
Normal file
56
tests/integration/test_environment_build.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
"""Integration tests for environment building workflow."""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_environment_build_end_to_end(tmp_path):
|
||||
"""Test: Package list → validation → UV install → image build → container creation."""
|
||||
# Setup: Create environment builder with all dependencies
|
||||
# Execute:
|
||||
# 1. Request environment with packages: ["requests", "pandas"]
|
||||
# 2. Validate packages (should pass)
|
||||
# 3. Install with UV
|
||||
# 4. Build container image
|
||||
# 5. Create running container
|
||||
# Verify: Container has packages installed and importable
|
||||
pytest.skip("TODO: Phase 5.3 - Environment build flow")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_blocked_package_prevents_build(tmp_path):
|
||||
"""Test: Security validation prevents building environments with blocked packages."""
|
||||
# Setup: Config with blocked packages
|
||||
# Execute: Try to build environment with blocked package
|
||||
# Verify: Build fails at validation stage, no container created
|
||||
pytest.skip("TODO: Phase 5.3 - Security validation integration")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_package_name_fails_gracefully(tmp_path):
|
||||
"""Test: Invalid package names are caught early."""
|
||||
# Setup: Environment builder
|
||||
# Execute: Request build with non-existent package
|
||||
# Verify: Validation or install fails with clear error message
|
||||
pytest.skip("TODO: Phase 5.3 - Error handling in build flow")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_environment_caching(tmp_path):
|
||||
"""Test: Building same environment twice uses cache."""
|
||||
# Setup: Environment builder with caching enabled
|
||||
# Execute:
|
||||
# 1. Build environment with ["requests"]
|
||||
# 2. Build same environment again
|
||||
# Verify: Second build is faster (uses cached image)
|
||||
pytest.skip("TODO: Phase 5.3 - Build caching")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_concurrent_builds_respect_limits(tmp_path):
|
||||
"""Test: Multiple simultaneous builds respect max_parallel_builds limit."""
|
||||
# Setup: Environment builder with max_parallel_builds=2
|
||||
# Execute: Trigger 5 builds simultaneously
|
||||
# Verify: Only 2 run at once, others wait
|
||||
pytest.skip("TODO: Phase 5.3 - Build rate limiting")
|
||||
48
tests/integration/test_error_handling.py
Normal file
48
tests/integration/test_error_handling.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
"""Integration tests for error handling and recovery."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_container_crash_cleanup(tmp_path):
|
||||
"""Test: If container crashes, resources are cleaned up."""
|
||||
# Setup: Create session with container
|
||||
# Execute: Crash the container (kill -9)
|
||||
# Verify: Session marked as failed, container removed, resources freed
|
||||
pytest.skip("TODO: Phase 5.3 - Crash recovery")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execution_error_propagation(tmp_path):
|
||||
"""Test: Python errors in execution are returned with full traceback."""
|
||||
# Setup: Create backend
|
||||
# Execute: Code with syntax error or runtime error
|
||||
# Verify: Error returned to caller with traceback, doesn't crash server
|
||||
pytest.skip("TODO: Phase 5.3 - Error propagation")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_podman_connection_loss_handling(tmp_path):
|
||||
"""Test: If Podman socket disconnects, errors are clear."""
|
||||
# Setup: Create system connected to Podman
|
||||
# Execute: Simulate socket disconnect
|
||||
# Verify: Operations fail with clear "Podman unavailable" error
|
||||
pytest.skip("TODO: Phase 5.3 - Connection loss handling")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_concurrent_session_cleanup(tmp_path):
|
||||
"""Test: Cleaning up many sessions simultaneously doesn't deadlock."""
|
||||
# Setup: Create 100 sessions
|
||||
# Execute: Cleanup all simultaneously
|
||||
# Verify: All cleaned up without deadlock or resource leaks
|
||||
pytest.skip("TODO: Phase 5.3 - Concurrent cleanup")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_mcp_tool_call_error_handling(tmp_path):
|
||||
"""Test: Calling MCP tool with wrong arguments returns clear error."""
|
||||
# Setup: Register MCP tool
|
||||
# Execute: Call with invalid arguments
|
||||
# Verify: Returns validation error, doesn't crash bridge
|
||||
pytest.skip("TODO: Phase 5.3 - MCP error handling")
|
||||
33
tests/integration/test_execution_flow.py
Normal file
33
tests/integration/test_execution_flow.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
"""Integration tests for end-to-end execution flows."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_simple_backend_execution_flow(real_config):
|
||||
"""Test complete flow: code submission → execution → result return."""
|
||||
pytest.skip("TODO: Phase 5.4 - Requires proper container registration and security setup")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_jupyter_session_lifecycle(real_config):
|
||||
"""Test: Create session → execute multiple code blocks → cleanup."""
|
||||
pytest.skip("TODO: Phase 5.4 - Complex Jupyter session testing requires more setup")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_jupyter_session_isolation(real_config):
|
||||
"""Test: Two sessions don't share state."""
|
||||
pytest.skip("TODO: Phase 5.4 - Complex Jupyter session testing requires more setup")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execution_with_timeout(real_config):
|
||||
"""Test: Long-running code gets killed after timeout."""
|
||||
pytest.skip("TODO: Phase 5.4 - Requires proper container registration and security setup")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execution_with_memory_limit(real_config):
|
||||
"""Test: Memory-intensive code respects limits."""
|
||||
pytest.skip("TODO: Phase 5.4 - Requires proper container registration and security setup")
|
||||
66
tests/integration/test_mcp_integration.py
Normal file
66
tests/integration/test_mcp_integration.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
"""Integration tests for MCP tool and bridge integration."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_tool_bridge_connection(tmp_path):
|
||||
"""Test: MCP client → bridge server → tool execution."""
|
||||
# Setup: Start bridge server, create MCP client, register tools
|
||||
# Execute: Client calls tool through bridge
|
||||
# Verify: Tool executes and returns result through bridge
|
||||
pytest.skip("TODO: Phase 5.3 - MCP bridge integration")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tool_injection_into_execution(tmp_path):
|
||||
"""Test: MCP tools are injected into Python execution environment."""
|
||||
# Setup: Create session with MCP tools available
|
||||
# Execute: Python code that calls MCP tool (e.g., mcp_tools.search_web())
|
||||
# Verify: Tool is callable and returns expected result
|
||||
pytest.skip("TODO: Phase 5.3 - Tool injection integration")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_mcp_clients_no_collision(tmp_path):
|
||||
"""Test: Multiple MCP clients with same tool names don't conflict."""
|
||||
# Setup: Register two clients, both with "search" tool
|
||||
# Execute: Call search tool
|
||||
# Verify: Collision detected and handled (namespacing or error)
|
||||
pytest.skip("TODO: Phase 5.3 - Tool collision detection")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_tool_handler_execute_python(tmp_path):
|
||||
"""Test: execute_python MCP tool end-to-end."""
|
||||
# Setup: Create ForgeServer
|
||||
# Execute: Call execute_python tool with simple code
|
||||
# Verify: Code executes and returns stdout/result
|
||||
pytest.skip("TODO: Phase 5.3 - ExecutePythonTool integration")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_tool_handler_document_state(tmp_path):
|
||||
"""Test: document_state MCP tool shows session variables."""
|
||||
# Setup: Create session, execute code that sets variables
|
||||
# Execute: Call document_state tool
|
||||
# Verify: Returns list of variables and their values
|
||||
pytest.skip("TODO: Phase 5.3 - DocumentStateTool integration")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_tool_handler_build_environment(tmp_path):
|
||||
"""Test: build_environment MCP tool creates custom environment."""
|
||||
# Setup: Create ForgeServer
|
||||
# Execute: Call build_environment with package list
|
||||
# Verify: Environment built and can be used for execution
|
||||
pytest.skip("TODO: Phase 5.3 - BuildEnvironmentTool integration")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_resource_handlers(tmp_path):
|
||||
"""Test: MCP resource handlers return correct data."""
|
||||
# Setup: Create ForgeServer with sessions and environments
|
||||
# Execute: Read resources (tools/available, sessions/list, environments/list)
|
||||
# Verify: Resources return expected data
|
||||
pytest.skip("TODO: Phase 5.3 - Resource handler integration")
|
||||
48
tests/integration/test_security_enforcement.py
Normal file
48
tests/integration/test_security_enforcement.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
"""Integration tests for security enforcement across components."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_blocked_package_import_prevented(tmp_path):
|
||||
"""Test: Attempting to import blocked package fails."""
|
||||
# Setup: Config with blocked packages (e.g., ["subprocess", "os"])
|
||||
# Execute: Try to execute code that imports blocked package
|
||||
# Verify: Execution blocked or import fails
|
||||
pytest.skip("TODO: Phase 5.3 - Package blocking enforcement")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resource_limits_enforced_in_container(tmp_path):
|
||||
"""Test: Container actually respects memory/CPU/timeout limits."""
|
||||
# Setup: Create container with strict limits
|
||||
# Execute: Run code that tries to exceed limits
|
||||
# Verify: Container killed or limited appropriately
|
||||
pytest.skip("TODO: Phase 5.3 - Resource limit enforcement")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_audit_logging_across_operations(tmp_path):
|
||||
"""Test: All operations are logged to audit trail."""
|
||||
# Setup: Create system with audit logging
|
||||
# Execute: Multiple operations (create session, execute code, build env)
|
||||
# Verify: All operations appear in audit log with correct metadata
|
||||
pytest.skip("TODO: Phase 5.3 - Audit trail integration")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_network_isolation_in_containers(tmp_path):
|
||||
"""Test: Containers cannot access external network (if configured)."""
|
||||
# Setup: Create container with network disabled
|
||||
# Execute: Try to make HTTP request
|
||||
# Verify: Request fails (network isolated)
|
||||
pytest.skip("TODO: Phase 5.3 - Network isolation")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_filesystem_isolation_in_containers(tmp_path):
|
||||
"""Test: Containers cannot access host filesystem outside mounts."""
|
||||
# Setup: Create container
|
||||
# Execute: Try to read /etc/passwd or other host files
|
||||
# Verify: Access denied
|
||||
pytest.skip("TODO: Phase 5.3 - Filesystem isolation")
|
||||
Loading…
Add table
Add a link
Reference in a new issue