feat(platform): add new services, tools, tests and crews modules
New router intelligence modules (26 files): alert_ingest/store, audit_store, architecture_pressure, backlog_generator/store, cost_analyzer, data_governance, dependency_scanner, drift_analyzer, incident_* (5 files), llm_enrichment, platform_priority_digest, provider_budget, release_check_runner, risk_* (6 files), signature_state_store, sofiia_auto_router, tool_governance New services: - sofiia-console: Dockerfile, adapters/, monitor/nodes/ops/voice modules, launchd, react static - memory-service: integration_endpoints, integrations, voice_endpoints, static UI - aurora-service: full app suite (analysis, job_store, orchestrator, reporting, schemas, subagents) - sofiia-supervisor: new supervisor service - aistalk-bridge-lite: Telegram bridge lite - calendar-service: CalDAV calendar service with reminders - mlx-stt-service / mlx-tts-service: Apple Silicon speech services - binance-bot-monitor: market monitor service - node-worker: STT/TTS memory providers New tools (9): agent_email, browser_tool, contract_tool, observability_tool, oncall_tool, pr_reviewer_tool, repo_tool, safe_code_executor, secure_vault New crews: agromatrix_crew (10 modules: depth_classifier, doc_facts, doc_focus, farm_state, light_reply, llm_factory, memory_manager, proactivity, reflection_engine, session_context, style_adapter, telemetry) Tests: 85+ test files for all new modules Made-with: Cursor
This commit is contained in:
0
tools/oncall_tool/tests/__init__.py
Normal file
0
tools/oncall_tool/tests/__init__.py
Normal file
248
tools/oncall_tool/tests/test_oncall_tool.py
Normal file
248
tools/oncall_tool/tests/test_oncall_tool.py
Normal file
@@ -0,0 +1,248 @@
|
||||
"""
|
||||
Tests for Oncall/Runbook Tool
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import tempfile
|
||||
from unittest.mock import AsyncMock, patch, MagicMock
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from services.router.tool_manager import ToolManager, ToolResult
|
||||
|
||||
|
||||
class TestOncallToolServicesList:
|
||||
"""Test services_list action"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_services_list(self):
|
||||
"""Test getting services list from docker-compose"""
|
||||
tool_mgr = ToolManager({})
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Create a mock docker-compose file
|
||||
compose_file = os.path.join(tmpdir, "docker-compose.yml")
|
||||
with open(compose_file, 'w') as f:
|
||||
f.write("""services:
|
||||
router:
|
||||
image: router:latest
|
||||
gateway:
|
||||
image: gateway:latest
|
||||
""")
|
||||
|
||||
with patch.dict(os.environ, {"REPO_ROOT": tmpdir}):
|
||||
result = await tool_mgr._oncall_tool({
|
||||
"action": "services_list"
|
||||
}, agent_id="sofiia")
|
||||
|
||||
assert result.success is True
|
||||
assert "services" in result.result
|
||||
|
||||
|
||||
class TestOncallToolRunbook:
|
||||
"""Test runbook actions"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_runbook_search(self):
|
||||
"""Test searching runbooks"""
|
||||
tool_mgr = ToolManager({})
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Create ops directory with runbook
|
||||
ops_dir = os.path.join(tmpdir, "ops")
|
||||
os.makedirs(ops_dir)
|
||||
|
||||
runbook_file = os.path.join(ops_dir, "deploy.md")
|
||||
with open(runbook_file, 'w') as f:
|
||||
f.write("# Deployment Runbook\n\nInstructions for deploying.")
|
||||
|
||||
with patch.dict(os.environ, {"REPO_ROOT": tmpdir}):
|
||||
result = await tool_mgr._oncall_tool({
|
||||
"action": "runbook_search",
|
||||
"params": {"query": "deploy"}
|
||||
}, agent_id="sofiia")
|
||||
|
||||
assert result.success is True
|
||||
assert "results" in result.result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_runbook_read_path_traversal_blocked(self):
|
||||
"""Test that path traversal is blocked"""
|
||||
tool_mgr = ToolManager({})
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
with patch.dict(os.environ, {"REPO_ROOT": tmpdir}):
|
||||
result = await tool_mgr._oncall_tool({
|
||||
"action": "runbook_read",
|
||||
"params": {"runbook_path": "../etc/passwd"}
|
||||
}, agent_id="sofiia")
|
||||
|
||||
assert result.success is False
|
||||
assert "traversal" in result.error.lower() or "not found" in result.error.lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_runbook_read_not_in_allowed_dir(self):
|
||||
"""Test that reading outside allowed dirs is blocked"""
|
||||
tool_mgr = ToolManager({})
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Create a file outside allowed dirs
|
||||
outside_file = os.path.join(tmpdir, "secret.txt")
|
||||
with open(outside_file, 'w') as f:
|
||||
f.write("API_KEY=secret123")
|
||||
|
||||
with patch.dict(os.environ, {"REPO_ROOT": tmpdir}):
|
||||
result = await tool_mgr._oncall_tool({
|
||||
"action": "runbook_read",
|
||||
"params": {"runbook_path": "secret.txt"}
|
||||
}, agent_id="sofiia")
|
||||
|
||||
# Should fail because not in allowed dirs
|
||||
assert result.success is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_runbook_secret_masking(self):
|
||||
"""Test that secrets are masked in runbook content"""
|
||||
tool_mgr = ToolManager({})
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Create ops directory with runbook containing secrets
|
||||
ops_dir = os.path.join(tmpdir, "ops")
|
||||
os.makedirs(ops_dir)
|
||||
|
||||
runbook_file = os.path.join(ops_dir, "config.md")
|
||||
with open(runbook_file, 'w') as f:
|
||||
f.write("""# Config
|
||||
API_KEY = sk-live-secret123
|
||||
password = mypassword
|
||||
token = abc123
|
||||
""")
|
||||
|
||||
with patch.dict(os.environ, {"REPO_ROOT": tmpdir}):
|
||||
result = await tool_mgr._oncall_tool({
|
||||
"action": "runbook_read",
|
||||
"params": {"runbook_path": "ops/config.md"}
|
||||
}, agent_id="sofiia")
|
||||
|
||||
assert result.success is True
|
||||
content = result.result.get("content", "")
|
||||
# Secrets should be masked
|
||||
assert "sk-live-secret123" not in content
|
||||
assert "***" in content
|
||||
|
||||
|
||||
class TestOncallToolIncidents:
|
||||
"""Test incident log actions"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_incident_log_list(self):
|
||||
"""Test listing incidents"""
|
||||
tool_mgr = ToolManager({})
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Create ops directory with incidents
|
||||
ops_dir = os.path.join(tmpdir, "ops")
|
||||
os.makedirs(ops_dir)
|
||||
|
||||
incidents_file = os.path.join(ops_dir, "incidents.jsonl")
|
||||
with open(incidents_file, 'w') as f:
|
||||
f.write(json.dumps({"ts": "2024-01-01", "severity": "sev1", "title": "Test"}) + "\n")
|
||||
|
||||
with patch.dict(os.environ, {"REPO_ROOT": tmpdir}):
|
||||
result = await tool_mgr._oncall_tool({
|
||||
"action": "incident_log_list",
|
||||
"params": {}
|
||||
}, agent_id="sofiia")
|
||||
|
||||
assert result.success is True
|
||||
assert "incidents" in result.result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_incident_log_append_allowed_for_sofiia(self):
|
||||
"""Test that Sofiia can append incidents"""
|
||||
tool_mgr = ToolManager({})
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
ops_dir = os.path.join(tmpdir, "ops")
|
||||
|
||||
with patch.dict(os.environ, {"REPO_ROOT": tmpdir}):
|
||||
result = await tool_mgr._oncall_tool({
|
||||
"action": "incident_log_append",
|
||||
"params": {
|
||||
"incident_title": "Test incident",
|
||||
"incident_severity": "sev2",
|
||||
"incident_details": "Test details"
|
||||
}
|
||||
}, agent_id="sofiia")
|
||||
|
||||
assert result.success is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_incident_log_append_blocked_for_regular_agent(self):
|
||||
"""Test that regular agents cannot append incidents"""
|
||||
tool_mgr = ToolManager({})
|
||||
|
||||
result = await tool_mgr._oncall_tool({
|
||||
"action": "incident_log_append",
|
||||
"params": {
|
||||
"incident_title": "Test incident",
|
||||
"incident_severity": "sev2"
|
||||
}
|
||||
}, agent_id="regular_agent")
|
||||
|
||||
assert result.success is False
|
||||
assert "entitlement" in result.error.lower() or "require" in result.error.lower()
|
||||
|
||||
|
||||
class TestOncallToolDeployments:
|
||||
"""Test deployment actions"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_deployments_recent(self):
|
||||
"""Test getting recent deployments"""
|
||||
tool_mgr = ToolManager({})
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
# Create ops directory with deployments
|
||||
ops_dir = os.path.join(tmpdir, "ops")
|
||||
os.makedirs(ops_dir)
|
||||
|
||||
deploy_file = os.path.join(ops_dir, "deployments.jsonl")
|
||||
with open(deploy_file, 'w') as f:
|
||||
f.write(json.dumps({"ts": "2024-01-01", "service": "router", "version": "1.0.0"}) + "\n")
|
||||
|
||||
with patch.dict(os.environ, {"REPO_ROOT": tmpdir}):
|
||||
result = await tool_mgr._oncall_tool({
|
||||
"action": "deployments_recent",
|
||||
"params": {}
|
||||
}, agent_id="sofiia")
|
||||
|
||||
assert result.success is True
|
||||
assert "deployments" in result.result
|
||||
|
||||
|
||||
class TestOncallToolHealth:
|
||||
"""Test health check action"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_service_health_not_in_allowlist(self):
|
||||
"""Test that health check to non-allowlisted host is blocked"""
|
||||
tool_mgr = ToolManager({})
|
||||
|
||||
result = await tool_mgr._oncall_tool({
|
||||
"action": "service_health",
|
||||
"params": {
|
||||
"service_name": "evil-service",
|
||||
"health_endpoint": "http://evil.com/health"
|
||||
}
|
||||
}, agent_id="sofiia")
|
||||
|
||||
assert result.success is False
|
||||
assert "allowlist" in result.error.lower()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Reference in New Issue
Block a user