Major additions: - marketplace/: Agent template registry with FTS5 search, ratings, versioning - observability/: Prometheus metrics, distributed tracing, structured logging - ledger/migrations/: Database migration scripts for multi-tenant support - tests/governance/: 15 new test files for phases 6-12 (295 total tests) - bin/validate-phases: Full 12-phase validation script New features: - Multi-tenant support with tenant isolation and quota enforcement - Agent marketplace with semantic versioning and search - Observability with metrics, tracing, and log correlation - Tier-1 agent bootstrap scripts Updated components: - ledger/api.py: Extended API for tenants, marketplace, observability - ledger/schema.sql: Added tenant, project, marketplace tables - testing/framework.ts: Enhanced test framework - checkpoint/checkpoint.py: Improved checkpoint management Archived: - External integrations (Slack/GitHub/PagerDuty) moved to .archive/ - Old checkpoint files cleaned up Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
232 lines
6.3 KiB
Python
232 lines
6.3 KiB
Python
"""
|
|
Phase 9: External Integrations Tests
|
|
====================================
|
|
Tests for webhook delivery and notification paths.
|
|
|
|
Required tests:
|
|
- github_webhook: Verify GitHub event handling in dry-run
|
|
- slack_notification: Verify Slack notification dispatch in dry-run
|
|
- webhook_delivery: Verify IntegrationManager broadcast delivery
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Add integrations to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "integrations"))
|
|
|
|
PASSED = 0
|
|
FAILED = 0
|
|
|
|
|
|
def log(msg: str, status: str = "info"):
|
|
icons = {"pass": "\033[92m✓\033[0m", "fail": "\033[91m✗\033[0m", "info": "→"}
|
|
print(f" {icons.get(status, '•')} {msg}")
|
|
|
|
|
|
def load_module(name: str, path: Path):
|
|
import importlib.util
|
|
|
|
spec = importlib.util.spec_from_file_location(name, path)
|
|
if spec is None or spec.loader is None:
|
|
raise ImportError(f"Module spec missing for {name}")
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def test_github_webhook():
|
|
"""Test GitHub webhook handling in dry-run mode"""
|
|
global PASSED, FAILED
|
|
|
|
print("\n[TEST] github_webhook")
|
|
|
|
os.environ["INTEGRATION_DRY_RUN"] = "true"
|
|
os.environ["GITHUB_TOKEN"] = "ghp_test_token"
|
|
os.environ["GITHUB_REPO"] = "example/repo"
|
|
|
|
try:
|
|
base = load_module(
|
|
"common.base",
|
|
Path(__file__).parent.parent.parent / "integrations" / "common" / "base.py",
|
|
)
|
|
github = load_module(
|
|
"github.github",
|
|
Path(__file__).parent.parent.parent
|
|
/ "integrations"
|
|
/ "github"
|
|
/ "github.py",
|
|
)
|
|
except ImportError as e:
|
|
log(f"GitHub import failed: {e}", "fail")
|
|
FAILED += 1
|
|
return False
|
|
|
|
gh = github.GitHubIntegration()
|
|
event = base.IntegrationEvent(
|
|
event_type="plan_created",
|
|
source="agent-001",
|
|
data={"plan": {"title": "Test Plan", "objective": "Verify webhook"}},
|
|
)
|
|
|
|
if gh.send_event(event):
|
|
log("GitHub plan event handled", "pass")
|
|
PASSED += 1
|
|
else:
|
|
log("GitHub plan event failed", "fail")
|
|
FAILED += 1
|
|
|
|
audit = gh.get_audit_log()
|
|
if any(entry.get("operation") == "create_pr" for entry in audit):
|
|
log("GitHub create_pr audit recorded", "pass")
|
|
PASSED += 1
|
|
else:
|
|
log("GitHub create_pr audit missing", "fail")
|
|
FAILED += 1
|
|
|
|
return True
|
|
|
|
|
|
def test_slack_notification():
|
|
"""Test Slack notification dispatch in dry-run mode"""
|
|
global PASSED, FAILED
|
|
|
|
print("\n[TEST] slack_notification")
|
|
|
|
os.environ["INTEGRATION_DRY_RUN"] = "true"
|
|
os.environ["SLACK_BOT_TOKEN"] = "xoxb-test-token"
|
|
os.environ["SLACK_WEBHOOK_URL"] = "https://hooks.slack.com/test"
|
|
|
|
try:
|
|
base = load_module(
|
|
"common.base",
|
|
Path(__file__).parent.parent.parent / "integrations" / "common" / "base.py",
|
|
)
|
|
slack_module = load_module(
|
|
"slack.slack",
|
|
Path(__file__).parent.parent.parent / "integrations" / "slack" / "slack.py",
|
|
)
|
|
except ImportError as e:
|
|
log(f"Slack import failed: {e}", "fail")
|
|
FAILED += 1
|
|
return False
|
|
|
|
slack = slack_module.SlackIntegration()
|
|
event = base.IntegrationEvent(
|
|
event_type="violation_detected",
|
|
source="agent-002",
|
|
data={
|
|
"violation": {"type": "policy", "severity": "high", "description": "Test"}
|
|
},
|
|
priority="high",
|
|
)
|
|
|
|
if slack.send_event(event):
|
|
log("Slack violation notification dispatched", "pass")
|
|
PASSED += 1
|
|
else:
|
|
log("Slack notification failed", "fail")
|
|
FAILED += 1
|
|
|
|
audit = slack.get_audit_log()
|
|
if any(entry.get("operation") == "send_message" for entry in audit):
|
|
log("Slack send_message audit recorded", "pass")
|
|
PASSED += 1
|
|
else:
|
|
log("Slack send_message audit missing", "fail")
|
|
FAILED += 1
|
|
|
|
return True
|
|
|
|
|
|
def test_webhook_delivery():
|
|
"""Test IntegrationManager broadcast delivery"""
|
|
global PASSED, FAILED
|
|
|
|
print("\n[TEST] webhook_delivery")
|
|
|
|
os.environ["INTEGRATION_DRY_RUN"] = "true"
|
|
os.environ["SLACK_BOT_TOKEN"] = "xoxb-test-token"
|
|
os.environ["GITHUB_TOKEN"] = "ghp_test_token"
|
|
os.environ["GITHUB_REPO"] = "example/repo"
|
|
|
|
try:
|
|
base = load_module(
|
|
"common.base",
|
|
Path(__file__).parent.parent.parent / "integrations" / "common" / "base.py",
|
|
)
|
|
slack_module = load_module(
|
|
"slack.slack",
|
|
Path(__file__).parent.parent.parent / "integrations" / "slack" / "slack.py",
|
|
)
|
|
github = load_module(
|
|
"github.github",
|
|
Path(__file__).parent.parent.parent
|
|
/ "integrations"
|
|
/ "github"
|
|
/ "github.py",
|
|
)
|
|
except ImportError as e:
|
|
log(f"Integration import failed: {e}", "fail")
|
|
FAILED += 1
|
|
return False
|
|
|
|
manager = base.IntegrationManager()
|
|
manager.register(
|
|
slack_module.SlackIntegration(
|
|
webhook_url="https://hooks.slack.com/test",
|
|
bot_token="xoxb-test-token",
|
|
)
|
|
)
|
|
manager.register(
|
|
github.GitHubIntegration(
|
|
token="ghp_test_token",
|
|
repo="example/repo",
|
|
)
|
|
)
|
|
|
|
event = base.IntegrationEvent(
|
|
event_type="execution_complete",
|
|
source="agent-003",
|
|
data={"result": {"success": True, "duration": 4.2}, "commit_sha": "HEAD"},
|
|
)
|
|
|
|
results = manager.broadcast(event)
|
|
if results.get("slack") and results.get("github"):
|
|
log("Broadcast delivered to Slack and GitHub", "pass")
|
|
PASSED += 1
|
|
else:
|
|
log(f"Broadcast results missing: {results}", "fail")
|
|
FAILED += 1
|
|
|
|
return True
|
|
|
|
|
|
def main():
|
|
global PASSED, FAILED
|
|
|
|
print("\n" + "=" * 60)
|
|
print("PHASE 9: EXTERNAL INTEGRATIONS TESTS")
|
|
print("=" * 60)
|
|
|
|
try:
|
|
test_github_webhook()
|
|
test_slack_notification()
|
|
test_webhook_delivery()
|
|
except Exception as e:
|
|
print(f"\n\033[91mTest execution error: {e}\033[0m")
|
|
FAILED += 1
|
|
|
|
print("\n" + "=" * 60)
|
|
print(f"RESULTS: {PASSED} passed, {FAILED} failed")
|
|
print("=" * 60 + "\n")
|
|
|
|
return FAILED == 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
success = main()
|
|
sys.exit(0 if success else 1)
|