"""
External Integration Test Plugin
Tests all ExternalAPI integration features:
- REST API server endpoints
- Incoming/outgoing webhooks
- API key authentication
- IPC communication
- Server-Sent Events (SSE)
This plugin verifies third-party integration capabilities.
"""
import time
import json
import threading
from datetime import datetime
from typing import Dict, List, Any
from dataclasses import dataclass
from core.base_plugin import BasePlugin
from core.api.external_api import get_external_api
from core.api.widget_api import get_widget_api, WidgetType
from core.api.plugin_api import get_api
@dataclass
class IntegrationTestResult:
"""Result of an integration test."""
category: str
test_name: str
passed: bool
duration_ms: float
details: Dict = None
error: str = None
class ExternalIntegrationTestPlugin(BasePlugin):
"""
Integration test suite for ExternalAPI.
Tests REST API, webhooks, authentication, IPC, and SSE functionality.
"""
def __init__(self):
super().__init__()
self.external_api = None
self.widget_api = None
self.plugin_api = None
self.results: List[IntegrationTestResult] = []
self.widget = None
self.test_port = 9999
def initialize(self):
"""Initialize and run integration tests."""
self.external_api = get_external_api()
self.widget_api = get_widget_api()
self.plugin_api = get_api()
self._create_results_widget()
self._run_all_tests()
def _create_results_widget(self):
"""Create widget to display integration test results."""
self.widget = self.widget_api.create_widget(
name="external_integration_test",
title="🌐 External Integration Test",
size=(900, 650),
position=(150, 150),
widget_type=WidgetType.CUSTOM
)
self._update_widget_display()
self.widget.show()
def _update_widget_display(self):
"""Update widget content."""
try:
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QTextBrowser, QTabWidget, QGroupBox
)
container = QWidget()
layout = QVBoxLayout(container)
# Header
header = QLabel("🌐 External Integration Test Suite")
header.setStyleSheet("font-size: 20px; font-weight: bold; color: #ff8c42;")
layout.addWidget(header)
# Summary
passed = sum(1 for r in self.results if r.passed)
total = len(self.results)
summary = QLabel(f"Results: {passed}/{total} passed")
summary.setStyleSheet(f"font-size: 14px; color: {'#4ecca3' if passed == total else '#ff6b6b'};")
layout.addWidget(summary)
# Results display
self.results_browser = QTextBrowser()
self.results_browser.setHtml(self._generate_results_html())
layout.addWidget(self.results_browser)
# Control buttons
btn_layout = QHBoxLayout()
btn_run = QPushButton("Run All Tests")
btn_run.clicked.connect(self._run_all_tests)
btn_layout.addWidget(btn_run)
btn_server = QPushButton("Test Server Start/Stop")
btn_server.clicked.connect(self._test_server_lifecycle)
btn_layout.addWidget(btn_server)
btn_webhook = QPushButton("Test Webhooks")
btn_webhook.clicked.connect(self._test_outgoing_webhook)
btn_layout.addWidget(btn_webhook)
layout.addLayout(btn_layout)
self.widget.set_content(container)
except ImportError:
pass
def _generate_results_html(self) -> str:
"""Generate results HTML."""
html = """
| Category | Test | Result | Duration | Details |
"""
for result in self.results:
status_class = "pass" if result.passed else "fail"
status_icon = "✅" if result.passed else "❌"
details = json.dumps(result.details) if result.details else ""
if len(details) > 100:
details = details[:100] + "..."
html += f"""
| {result.category} |
{result.test_name} |
{status_icon} |
{result.duration_ms:.2f}ms |
{details} |
"""
html += "
"
return html
def _record_result(self, category: str, test_name: str, passed: bool,
duration_ms: float, details: Dict = None, error: str = None):
"""Record test result."""
result = IntegrationTestResult(
category=category,
test_name=test_name,
passed=passed,
duration_ms=duration_ms,
details=details,
error=error
)
self.results.append(result)
self._update_widget_display()
# Show notification for failures
if not passed:
self.plugin_api.show_notification(
"Integration Test Failed",
f"{category}/{test_name}: {error or 'Unknown error'}",
duration=3000
)
def _run_all_tests(self):
"""Execute all integration tests."""
self.results.clear()
# REST API Server Tests
self._test_server_lifecycle()
self._test_endpoint_registration()
self._test_endpoint_decorator()
self._test_cors_configuration()
# Webhook Tests
self._test_incoming_webhook()
self._test_outgoing_webhook()
self._test_webhook_hmac()
# Authentication Tests
self._test_api_key_creation()
self._test_api_key_revocation()
# IPC Tests
self._test_ipc_handler()
self._test_ipc_send()
# Utility Tests
self._test_status_endpoint()
self._test_url_generation()
self._test_webhook_history()
# =================================================================
# REST API Server Tests
# =================================================================
def _test_server_lifecycle(self):
"""Test server start and stop."""
start = time.time()
try:
# Start server
started = self.external_api.start_server(
port=self.test_port,
host="127.0.0.1",
cors_origins=["http://localhost:3000"]
)
if started:
time.sleep(0.5) # Let server initialize
# Verify it's running
status = self.external_api.get_status()
running = status.get('server_running', False)
# Stop server
stopped = self.external_api.stop_server()
duration = (time.time() - start) * 1000
self._record_result(
"REST Server",
"Server Lifecycle",
started and running and stopped,
duration,
{"started": started, "running": running, "stopped": stopped}
)
else:
duration = (time.time() - start) * 1000
self._record_result(
"REST Server",
"Server Lifecycle",
False,
duration,
error="Server failed to start (may already be running)"
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("REST Server", "Server Lifecycle", False, duration, error=str(e))
def _test_endpoint_registration(self):
"""Test programmatic endpoint registration."""
start = time.time()
try:
def test_handler(params):
return {"test": "data", "params": params}
self.external_api.register_endpoint(
"test/registration",
test_handler,
methods=["GET", "POST"],
auth_required=False
)
endpoints = self.external_api.get_endpoints()
success = "test/registration" in endpoints
duration = (time.time() - start) * 1000
self._record_result(
"REST Server",
"Endpoint Registration",
success,
duration,
{"endpoints": endpoints}
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("REST Server", "Endpoint Registration", False, duration, error=str(e))
def _test_endpoint_decorator(self):
"""Test decorator-based endpoint registration."""
start = time.time()
try:
@self.external_api.endpoint("test/decorator", methods=["GET"])
def decorated_endpoint():
return {"decorated": True}
endpoints = self.external_api.get_endpoints()
success = "test/decorator" in endpoints
duration = (time.time() - start) * 1000
self._record_result(
"REST Server",
"Endpoint Decorator",
success,
duration
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("REST Server", "Endpoint Decorator", False, duration, error=str(e))
def _test_cors_configuration(self):
"""Test CORS configuration."""
start = time.time()
try:
# CORS is configured during server start
status = self.external_api.get_status()
success = status.get('server_running') is not None
duration = (time.time() - start) * 1000
self._record_result(
"REST Server",
"CORS Configuration",
success,
duration,
{"cors_origins": ["http://localhost:3000"]}
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("REST Server", "CORS Configuration", False, duration, error=str(e))
# =================================================================
# Webhook Tests
# =================================================================
def _test_incoming_webhook(self):
"""Test incoming webhook registration."""
start = time.time()
try:
def webhook_handler(payload):
return {"received": payload}
self.external_api.register_webhook(
"test_incoming",
webhook_handler,
methods=["POST"],
rate_limit=60
)
webhooks = self.external_api.get_webhooks()
success = "test_incoming" in webhooks
duration = (time.time() - start) * 1000
self._record_result(
"Webhooks",
"Incoming Webhook Registration",
success,
duration,
{"webhooks": webhooks}
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("Webhooks", "Incoming Webhook", False, duration, error=str(e))
def _test_outgoing_webhook(self):
"""Test outgoing webhook POST."""
start = time.time()
try:
# Post to httpbin for testing
result = self.external_api.post_webhook(
"https://httpbin.org/post",
{"test": "data", "timestamp": time.time()},
headers={"X-Test-Header": "test"},
timeout=10
)
success = result.get('success', False)
status = result.get('status', 0)
duration = (time.time() - start) * 1000
self._record_result(
"Webhooks",
"Outgoing Webhook POST",
success and 200 <= status < 300,
duration,
{"status": status}
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("Webhooks", "Outgoing Webhook", False, duration, error=str(e))
def _test_webhook_hmac(self):
"""Test webhook HMAC signature verification."""
start = time.time()
try:
import hmac
import hashlib
secret = "test_secret_key"
def secure_handler(payload):
return {"secure": True}
self.external_api.register_webhook(
"secure_webhook",
secure_handler,
secret=secret,
methods=["POST"]
)
# Generate test signature
payload = {"event": "test"}
payload_str = json.dumps(payload, sort_keys=True)
signature = hmac.new(
secret.encode(),
payload_str.encode(),
hashlib.sha256
).hexdigest()
# Verify signature logic is in place
webhooks = self.external_api.get_webhooks()
success = "secure_webhook" in webhooks
duration = (time.time() - start) * 1000
self._record_result(
"Webhooks",
"HMAC Signature",
success,
duration,
{"signature_generated": True}
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("Webhooks", "HMAC Signature", False, duration, error=str(e))
# =================================================================
# Authentication Tests
# =================================================================
def _test_api_key_creation(self):
"""Test API key creation."""
start = time.time()
try:
key = self.external_api.create_api_key(
name="test_integration_key",
permissions=["read", "write"]
)
success = len(key) > 0 and isinstance(key, str)
self._test_api_key = key # Save for revocation test
duration = (time.time() - start) * 1000
self._record_result(
"Authentication",
"API Key Creation",
success,
duration,
{"key_length": len(key)}
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("Authentication", "API Key Creation", False, duration, error=str(e))
def _test_api_key_revocation(self):
"""Test API key revocation."""
start = time.time()
try:
# Create a key to revoke
key = self.external_api.create_api_key("temp_revoke_key")
# Revoke it
revoked = self.external_api.revoke_api_key(key)
duration = (time.time() - start) * 1000
self._record_result(
"Authentication",
"API Key Revocation",
revoked,
duration
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("Authentication", "API Key Revocation", False, duration, error=str(e))
# =================================================================
# IPC Tests
# =================================================================
def _test_ipc_handler(self):
"""Test IPC handler registration."""
start = time.time()
try:
self._ipc_received = False
def ipc_handler(data):
self._ipc_received = True
self._ipc_data = data
self.external_api.register_ipc_handler("test_channel", ipc_handler)
duration = (time.time() - start) * 1000
self._record_result(
"IPC",
"Handler Registration",
True,
duration
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("IPC", "Handler Registration", False, duration, error=str(e))
def _test_ipc_send(self):
"""Test IPC message sending."""
start = time.time()
try:
sent = self.external_api.send_ipc(
"test_channel",
{"message": "test", "timestamp": time.time()}
)
duration = (time.time() - start) * 1000
self._record_result(
"IPC",
"Send Message",
sent,
duration,
{"received": getattr(self, '_ipc_received', False)}
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("IPC", "Send Message", False, duration, error=str(e))
# =================================================================
# Utility Tests
# =================================================================
def _test_status_endpoint(self):
"""Test /health endpoint availability."""
start = time.time()
try:
status = self.external_api.get_status()
success = (
'server_running' in status and
'endpoints' in status and
'webhooks' in status
)
duration = (time.time() - start) * 1000
self._record_result(
"Utilities",
"Status Endpoint",
success,
duration,
status
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("Utilities", "Status Endpoint", False, duration, error=str(e))
def _test_url_generation(self):
"""Test URL generation."""
start = time.time()
try:
url = self.external_api.get_url("api/v1/test")
success = url.startswith("http") and "api/v1/test" in url
duration = (time.time() - start) * 1000
self._record_result(
"Utilities",
"URL Generation",
success,
duration,
{"url": url}
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("Utilities", "URL Generation", False, duration, error=str(e))
def _test_webhook_history(self):
"""Test webhook history tracking."""
start = time.time()
try:
history = self.external_api.get_webhook_history(limit=10)
success = isinstance(history, list)
duration = (time.time() - start) * 1000
self._record_result(
"Utilities",
"Webhook History",
success,
duration,
{"history_entries": len(history)}
)
except Exception as e:
duration = (time.time() - start) * 1000
self._record_result("Utilities", "Webhook History", False, duration, error=str(e))
def shutdown(self):
"""Clean up resources."""
# Stop server if running
try:
self.external_api.stop_server()
except:
pass
if self.widget:
self.widget.close()
# Plugin entry point
plugin_class = ExternalIntegrationTestPlugin