""" Error Handling Test Plugin Tests error conditions and exception handling: - Invalid input validation - Service unavailable scenarios - Resource not found errors - Type errors and malformed data - Timeout handling - Edge cases and boundary conditions Verifies APIs handle errors gracefully without crashing. """ import time import sys from datetime import datetime from typing import Dict, List, Any, Tuple from dataclasses import dataclass from enum import Enum from core.base_plugin import BasePlugin from core.api.plugin_api import get_api, PluginAPIError, ServiceNotAvailableError from core.api.widget_api import get_widget_api, WidgetType from core.api.external_api import get_external_api, ExternalAPIError class ErrorType(Enum): """Types of errors tested.""" INVALID_INPUT = "invalid_input" SERVICE_UNAVAILABLE = "service_unavailable" RESOURCE_NOT_FOUND = "resource_not_found" TYPE_ERROR = "type_error" TIMEOUT = "timeout" BOUNDARY = "boundary" UNEXPECTED = "unexpected" @dataclass class ErrorTestResult: """Result of an error handling test.""" api: str test_name: str error_type: ErrorType handled_gracefully: bool correct_exception: bool error_message: str = "" details: Dict = None class ErrorHandlingTestPlugin(BasePlugin): """ Error handling test suite for EU-Utility APIs. Tests how APIs respond to invalid inputs, missing resources, and exceptional conditions. """ def __init__(self): super().__init__() self.api = None self.widget_api = None self.external_api = None self.results: List[ErrorTestResult] = [] self.widget = None def initialize(self): """Initialize and run error handling tests.""" self.api = get_api() self.widget_api = get_widget_api() self.external_api = get_external_api() self._create_results_widget() self._run_all_tests() def _create_results_widget(self): """Create widget for results display.""" self.widget = self.widget_api.create_widget( name="error_handling_test", title="🛡️ Error Handling Test", size=(900, 650), position=(250, 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, QTableWidget, QTableWidgetItem, QHeaderView, QGroupBox, QTextBrowser ) from PyQt6.QtCore import Qt from PyQt6.QtGui import QColor container = QWidget() main_layout = QVBoxLayout(container) # Header header = QLabel("🛡️ Error Handling Test Suite") header.setStyleSheet("font-size: 22px; font-weight: bold; color: #ff8c42;") main_layout.addWidget(header) # Summary stats if self.results: summary_layout = QHBoxLayout() total = len(self.results) graceful = sum(1 for r in self.results if r.handled_gracefully) correct_exc = sum(1 for r in self.results if r.correct_exception) stats = [ ("Tests", str(total)), ("Graceful", f"{graceful}/{total}"), ("Correct Exception", f"{correct_exc}/{total}") ] for title, value in stats: group = QGroupBox(title) group_layout = QVBoxLayout(group) lbl = QLabel(value) lbl.setStyleSheet("font-size: 18px; font-weight: bold;") lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) if "Graceful" in title: lbl.setStyleSheet(f"font-size: 18px; font-weight: bold; color: {'#4ecca3' if graceful == total else '#ffd93d'};") elif "Correct" in title: lbl.setStyleSheet(f"font-size: 18px; font-weight: bold; color: {'#4ecca3' if correct_exc == total else '#ffd93d'};") group_layout.addWidget(lbl) summary_layout.addWidget(group) main_layout.addLayout(summary_layout) # Results table self.results_table = QTableWidget() self.results_table.setColumnCount(6) self.results_table.setHorizontalHeaderLabels([ "API", "Test", "Error Type", "Handled", "Correct Exc", "Error Message" ]) self.results_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) self._populate_results_table() main_layout.addWidget(self.results_table) # Controls btn_layout = QHBoxLayout() btn_run = QPushButton("▶ Run All Error Tests") btn_run.clicked.connect(self._run_all_tests) btn_layout.addWidget(btn_run) btn_summary = QPushButton("📋 View Summary Report") btn_summary.clicked.connect(self._show_summary_report) btn_layout.addWidget(btn_summary) main_layout.addLayout(btn_layout) self.widget.set_content(container) except ImportError as e: print(f"Widget error: {e}") def _populate_results_table(self): """Populate results table.""" if not hasattr(self, 'results_table'): return self.results_table.setRowCount(len(self.results)) for i, r in enumerate(self.results): self.results_table.setItem(i, 0, QTableWidgetItem(r.api)) self.results_table.setItem(i, 1, QTableWidgetItem(r.test_name)) self.results_table.setItem(i, 2, QTableWidgetItem(r.error_type.value)) handled_item = QTableWidgetItem("✅" if r.handled_gracefully else "❌") handled_item.setForeground(QColor("#4ecca3" if r.handled_gracefully else "#ff6b6b")) self.results_table.setItem(i, 3, handled_item) correct_item = QTableWidgetItem("✅" if r.correct_exception else "⚠️") self.results_table.setItem(i, 4, correct_item) msg = r.error_message[:50] + "..." if len(r.error_message) > 50 else r.error_message self.results_table.setItem(i, 5, QTableWidgetItem(msg)) def _run_test(self, api: str, test_name: str, error_type: ErrorType, test_func) -> ErrorTestResult: """Run a single error handling test.""" error_occurred = False handled_gracefully = False correct_exception = False error_message = "" details = {} try: test_func() # If no error occurred, check if we expected one error_message = "No error occurred (may be expected)" handled_gracefully = True except ServiceNotAvailableError as e: error_occurred = True handled_gracefully = True correct_exception = error_type == ErrorType.SERVICE_UNAVAILABLE error_message = str(e) except PluginAPIError as e: error_occurred = True handled_gracefully = True correct_exception = True error_message = str(e) except ExternalAPIError as e: error_occurred = True handled_gracefully = True correct_exception = True error_message = str(e) except ValueError as e: error_occurred = True handled_gracefully = True correct_exception = error_type in [ErrorType.INVALID_INPUT, ErrorType.BOUNDARY] error_message = str(e) except TypeError as e: error_occurred = True handled_gracefully = True correct_exception = error_type == ErrorType.TYPE_ERROR error_message = str(e) except TimeoutError as e: error_occurred = True handled_gracefully = True correct_exception = error_type == ErrorType.TIMEOUT error_message = str(e) except KeyError as e: error_occurred = True handled_gracefully = True correct_exception = error_type == ErrorType.RESOURCE_NOT_FOUND error_message = f"KeyError: {e}" except Exception as e: error_occurred = True handled_gracefully = False # Unexpected exception type correct_exception = False error_message = f"{type(e).__name__}: {str(e)[:100]}" details["exception_type"] = type(e).__name__ result = ErrorTestResult( api=api, test_name=test_name, error_type=error_type, handled_gracefully=handled_gracefully, correct_exception=correct_exception, error_message=error_message, details=details ) self.results.append(result) return result def _run_all_tests(self): """Execute all error handling tests.""" self.results.clear() # PluginAPI Error Tests self._test_pluginapi_errors() # WidgetAPI Error Tests self._test_widgetapi_errors() # ExternalAPI Error Tests self._test_externalapi_errors() self._update_widget_display() def _test_pluginapi_errors(self): """Test PluginAPI error handling.""" # Test invalid log line count self._run_test( "PluginAPI", "Invalid log line count (negative)", ErrorType.INVALID_INPUT, lambda: self.api.read_log_lines(-1) ) # Test invalid log line count (too large) self._run_test( "PluginAPI", "Invalid log line count (excessive)", ErrorType.BOUNDARY, lambda: self.api.read_log_lines(10000000) ) # Test OCR with invalid region self._run_test( "PluginAPI", "OCR invalid region", ErrorType.INVALID_INPUT, lambda: self.api.recognize_text((-1, -1, -1, -1)) ) # Test capture with invalid region self._run_test( "PluginAPI", "Screenshot invalid region", ErrorType.INVALID_INPUT, lambda: self.api.capture_screen((-100, -100, 0, 0)) ) # Test HTTP with invalid URL self._run_test( "PluginAPI", "HTTP invalid URL", ErrorType.INVALID_INPUT, lambda: self.api.http_get("not_a_valid_url") ) # Test HTTP with malformed URL self._run_test( "PluginAPI", "HTTP malformed URL", ErrorType.INVALID_INPUT, lambda: self.api.http_get("") ) # Test play_sound with invalid path self._run_test( "PluginAPI", "Play sound invalid path", ErrorType.RESOURCE_NOT_FOUND, lambda: self.api.play_sound("/nonexistent/path/to/sound.wav") ) # Test notification with empty title self._run_test( "PluginAPI", "Notification empty title", ErrorType.BOUNDARY, lambda: self.api.show_notification("", "message") ) # Test set_data with non-serializable object self._run_test( "PluginAPI", "Set data non-serializable", ErrorType.TYPE_ERROR, lambda: self.api.set_data("test_key", lambda x: x) ) # Test subscribe with non-callable self._run_test( "PluginAPI", "Subscribe non-callable", ErrorType.TYPE_ERROR, lambda: self.api.subscribe("test", "not_a_function") ) # Test unsubscribe with invalid ID self._run_test( "PluginAPI", "Unsubscribe invalid ID", ErrorType.RESOURCE_NOT_FOUND, lambda: self.api.unsubscribe("invalid_subscription_id_12345") ) # Test cancel_task with invalid ID self._run_test( "PluginAPI", "Cancel task invalid ID", ErrorType.RESOURCE_NOT_FOUND, lambda: self.api.cancel_task("invalid_task_id_12345") ) # Test get_data with None key self._run_test( "PluginAPI", "Get data None key", ErrorType.INVALID_INPUT, lambda: self.api.get_data(None) ) # Test volume out of range self._run_test( "PluginAPI", "Play sound volume out of range", ErrorType.BOUNDARY, lambda: self.api.play_sound("test.wav", volume=5.0) ) def _test_widgetapi_errors(self): """Test WidgetAPI error handling.""" # Test duplicate widget name def create_duplicate(): w1 = self.widget_api.create_widget(name="duplicate_test", title="Test") w2 = self.widget_api.create_widget(name="duplicate_test", title="Test 2") self._run_test( "WidgetAPI", "Duplicate widget name", ErrorType.INVALID_INPUT, create_duplicate ) # Cleanup if created try: self.widget_api.close_widget("duplicate_test") except: pass # Test get non-existent widget self._run_test( "WidgetAPI", "Get non-existent widget", ErrorType.RESOURCE_NOT_FOUND, lambda: self.widget_api.get_widget("definitely_does_not_exist_12345") ) # Test operations on non-existent widget self._run_test( "WidgetAPI", "Show non-existent widget", ErrorType.RESOURCE_NOT_FOUND, lambda: self.widget_api.show_widget("definitely_does_not_exist_12345") ) # Test invalid widget size self._run_test( "WidgetAPI", "Create widget with negative size", ErrorType.INVALID_INPUT, lambda: self.widget_api.create_widget(name="bad_size", title="Bad", size=(-100, -100)) ) # Test invalid opacity def test_bad_opacity(): w = self.widget_api.create_widget(name="opacity_test", title="Test", size=(100, 100)) w.set_opacity(5.0) # Should be clamped self._run_test( "WidgetAPI", "Set opacity out of range", ErrorType.BOUNDARY, test_bad_opacity ) # Cleanup try: self.widget_api.close_widget("opacity_test") except: pass # Test load state with invalid data def load_invalid_state(): w = self.widget_api.create_widget(name="state_test", title="Test") w.load_state({"invalid": "state_data"}) self._run_test( "WidgetAPI", "Load invalid state", ErrorType.INVALID_INPUT, load_invalid_state ) # Cleanup try: self.widget_api.close_widget("state_test") except: pass # Test close already closed widget def close_closed(): w = self.widget_api.create_widget(name="close_test", title="Test") w.close() w.close() # Second close self._run_test( "WidgetAPI", "Close already closed widget", ErrorType.RESOURCE_NOT_FOUND, close_closed ) def _test_externalapi_errors(self): """Test ExternalAPI error handling.""" # Test start server on invalid port self._run_test( "ExternalAPI", "Start server invalid port", ErrorType.INVALID_INPUT, lambda: self.external_api.start_server(port=-1) ) # Test register endpoint with invalid path self._run_test( "ExternalAPI", "Register endpoint invalid path", ErrorType.INVALID_INPUT, lambda: self.external_api.register_endpoint("", lambda x: x) ) # Test register webhook with invalid name self._run_test( "ExternalAPI", "Register webhook invalid name", ErrorType.INVALID_INPUT, lambda: self.external_api.register_webhook("", lambda x: x) ) # Test unregister non-existent endpoint self._run_test( "ExternalAPI", "Unregister non-existent endpoint", ErrorType.RESOURCE_NOT_FOUND, lambda: self.external_api.unregister_endpoint("definitely_not_registered") ) # Test unregister non-existent webhook self._run_test( "ExternalAPI", "Unregister non-existent webhook", ErrorType.RESOURCE_NOT_FOUND, lambda: self.external_api.unregister_webhook("definitely_not_registered") ) # Test revoke non-existent API key self._run_test( "ExternalAPI", "Revoke non-existent API key", ErrorType.RESOURCE_NOT_FOUND, lambda: self.external_api.revoke_api_key("invalid_key_12345") ) # Test post_webhook with invalid URL self._run_test( "ExternalAPI", "Post webhook invalid URL", ErrorType.INVALID_INPUT, lambda: self.external_api.post_webhook("not_a_url", {}) ) # Test post_webhook with unreachable host self._run_test( "ExternalAPI", "Post webhook unreachable host", ErrorType.TIMEOUT, lambda: self.external_api.post_webhook( "http://192.0.2.1:9999/test", # TEST-NET-1, should be unreachable {}, timeout=1 ) ) # Test IPC send to non-existent channel self._run_test( "ExternalAPI", "IPC send non-existent channel", ErrorType.RESOURCE_NOT_FOUND, lambda: self.external_api.send_ipc("nonexistent_channel", {}) ) # Test get_url when server not running def get_url_not_running(): # Ensure server is stopped self.external_api.stop_server() url = self.external_api.get_url("test") if not url: raise ValueError("Empty URL when server not running") self._run_test( "ExternalAPI", "Get URL server not running", ErrorType.SERVICE_UNAVAILABLE, get_url_not_running ) def _show_summary_report(self): """Display summary report.""" if not self.results: self.api.show_notification("No Results", "Run tests first") return total = len(self.results) graceful = sum(1 for r in self.results if r.handled_gracefully) correct = sum(1 for r in self.results if r.correct_exception) report = f""" Error Handling Test Summary =========================== Total Tests: {total} Handled Gracefully: {graceful}/{total} ({graceful/total*100:.1f}%) Correct Exception: {correct}/{total} ({correct/total*100:.1f}%) By Error Type: """ for error_type in ErrorType: type_tests = [r for r in self.results if r.error_type == error_type] if type_tests: type_graceful = sum(1 for r in type_tests if r.handled_gracefully) report += f" {error_type.value}: {type_graceful}/{len(type_tests)} graceful\n" report += "\nBy API:\n" for api in ["PluginAPI", "WidgetAPI", "ExternalAPI"]: api_tests = [r for r in self.results if r.api == api] if api_tests: api_graceful = sum(1 for r in api_tests if r.handled_gracefully) report += f" {api}: {api_graceful}/{len(api_tests)} graceful\n" print(report) self.api.show_notification("Report Generated", "See console for full report") def shutdown(self): """Clean up resources.""" # Clean up any test widgets for name in ["duplicate_test", "opacity_test", "state_test", "close_test"]: try: self.widget_api.close_widget(name) except: pass if self.widget: self.widget.close() # Plugin entry point plugin_class = ErrorHandlingTestPlugin