""" EU-Utility Integration Test - Discord Webhook ============================================== Tests Discord webhook integration for: - Sending messages to Discord channels - Webhook payload validation - Error handling and retries - Rate limiting compliance - Embed formatting Author: Integration Tester Version: 1.0.0 """ import json import time from datetime import datetime from typing import Dict, Any, Optional, List from dataclasses import dataclass, asdict from plugins.base_plugin import BasePlugin from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QTextEdit, QPushButton, QComboBox, QSpinBox, QCheckBox, QGroupBox, QTabWidget, QProgressBar, QTableWidget, QTableWidgetItem, QHeaderView ) from PyQt6.QtCore import Qt, QThread, pyqtSignal @dataclass class WebhookTest: """Represents a single webhook test case.""" name: str payload: Dict[str, Any] expected_status: int description: str class DiscordWebhookTester(BasePlugin): """Plugin for testing Discord webhook integration.""" name = "Discord Webhook Tester" version = "1.0.0" author = "Integration Tester" description = "Test Discord webhook integration and payload formats" # Discord webhook URL pattern WEBHOOK_PATTERN = "https://discord.com/api/webhooks/" # Test cases TEST_CASES = [ WebhookTest( name="Simple Message", payload={"content": "Hello from EU-Utility Test!"}, expected_status=204, description="Basic text message" ), WebhookTest( name="Embed Message", payload={ "embeds": [{ "title": "Loot Drop!", "description": "You received **50 PED** worth of loot", "color": 0x00ff00, "fields": [ {"name": "Mob", "value": "Atrox", "inline": True}, {"name": "Damage", "value": "150", "inline": True}, {"name": "DPP", "value": "2.85", "inline": True} ], "timestamp": datetime.utcnow().isoformat() }] }, expected_status=204, description="Rich embed with fields" ), WebhookTest( name="Global Announcement", payload={ "content": "🎉 **GLOBAL!** 🎉", "embeds": [{ "title": "150 PED - Atrox Young", "color": 0xffd700, "fields": [ {"name": "Player", "value": "TestPlayer", "inline": True}, {"name": "Location", "value": "Calypso", "inline": True}, {"name": "Weapon", "value": "ArMatrix LP-35", "inline": True} ] }] }, expected_status=204, description="Global/HOF announcement format" ), WebhookTest( name="Skill Gain", payload={ "embeds": [{ "title": "🎯 Skill Gain", "description": "Rifle +0.25", "color": 0x3498db, "footer": {"text": "Total: 4500.75"} }] }, expected_status=204, description="Skill tracking notification" ), WebhookTest( name="Error Alert", payload={ "content": "", "embeds": [{ "title": "⚠️ Connection Error", "description": "Failed to connect to Nexus API", "color": 0xe74c3c, "timestamp": datetime.utcnow().isoformat() }] }, expected_status=204, description="Error notification format" ), WebhookTest( name="Invalid Payload", payload={"invalid_field": "test"}, expected_status=400, description="Test error handling with invalid payload" ), ] def initialize(self): """Initialize the tester.""" self.log_info("Discord Webhook Tester initialized") self._webhook_url = "" self._test_results: List[Dict] = [] def get_ui(self) -> QWidget: """Create the plugin UI.""" widget = QWidget() layout = QVBoxLayout(widget) # Title title = QLabel("Discord Webhook Integration Tester") title.setStyleSheet("font-size: 16px; font-weight: bold;") layout.addWidget(title) # Description desc = QLabel("Test Discord webhook integration for EU-Utility notifications") desc.setWordWrap(True) layout.addWidget(desc) # Tabs tabs = QTabWidget() # Webhook URL tab tabs.addTab(self._create_url_tab(), "Webhook URL") # Test Cases tab tabs.addTab(self._create_tests_tab(), "Test Cases") # Results tab tabs.addTab(self._create_results_tab(), "Results") # Payload Builder tab tabs.addTab(self._create_builder_tab(), "Payload Builder") layout.addWidget(tabs) return widget def _create_url_tab(self) -> QWidget: """Create the webhook URL configuration tab.""" widget = QWidget() layout = QVBoxLayout(widget) # URL Input url_group = QGroupBox("Webhook Configuration") url_layout = QVBoxLayout(url_group) url_row = QHBoxLayout() url_row.addWidget(QLabel("Webhook URL:")) self.url_input = QLineEdit() self.url_input.setPlaceholderText("https://discord.com/api/webhooks/...") self.url_input.textChanged.connect(self._on_url_changed) url_row.addWidget(self.url_input) url_layout.addLayout(url_row) # URL validation status self.url_status = QLabel("❌ No URL configured") self.url_status.setStyleSheet("color: red;") url_layout.addWidget(self.url_status) # Instructions instructions = QLabel( "To get a webhook URL:\n" "1. Open Discord → Server Settings → Integrations\n" "2. Click 'Webhooks' → 'New Webhook'\n" "3. Copy the webhook URL and paste above" ) instructions.setStyleSheet("color: gray; padding: 10px;") url_layout.addWidget(instructions) layout.addWidget(url_group) # Test connection test_btn = QPushButton("Test Connection") test_btn.clicked.connect(self._test_connection) layout.addWidget(test_btn) layout.addStretch() return widget def _create_tests_tab(self) -> QWidget: """Create the test cases tab.""" widget = QWidget() layout = QVBoxLayout(widget) # Test list layout.addWidget(QLabel("Available Test Cases:")) self.test_table = QTableWidget() self.test_table.setColumnCount(3) self.test_table.setHorizontalHeaderLabels(["Test Name", "Description", "Status"]) self.test_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) # Populate tests self.test_table.setRowCount(len(self.TEST_CASES)) for i, test in enumerate(self.TEST_CASES): self.test_table.setItem(i, 0, QTableWidgetItem(test.name)) self.test_table.setItem(i, 1, QTableWidgetItem(test.description)) self.test_table.setItem(i, 2, QTableWidgetItem("⏳ Pending")) layout.addWidget(self.test_table) # Action buttons btn_layout = QHBoxLayout() run_all_btn = QPushButton("Run All Tests") run_all_btn.clicked.connect(self._run_all_tests) btn_layout.addWidget(run_all_btn) run_sel_btn = QPushButton("Run Selected") run_sel_btn.clicked.connect(self._run_selected_test) btn_layout.addWidget(run_sel_btn) clear_btn = QPushButton("Clear Results") clear_btn.clicked.connect(self._clear_results) btn_layout.addWidget(clear_btn) layout.addLayout(btn_layout) # Progress bar self.progress_bar = QProgressBar() layout.addWidget(self.progress_bar) return widget def _create_results_tab(self) -> QWidget: """Create the results tab.""" widget = QWidget() layout = QVBoxLayout(widget) # Results summary self.results_summary = QLabel("No tests run yet") layout.addWidget(self.results_summary) # Results text area layout.addWidget(QLabel("Detailed Results:")) self.results_text = QTextEdit() self.results_text.setReadOnly(True) layout.addWidget(self.results_text) # Export button export_btn = QPushButton("Export Results to JSON") export_btn.clicked.connect(self._export_results) layout.addWidget(export_btn) return widget def _create_builder_tab(self) -> QWidget: """Create the payload builder tab.""" widget = QWidget() layout = QVBoxLayout(widget) layout.addWidget(QLabel("Custom Payload Builder")) # Message type type_layout = QHBoxLayout() type_layout.addWidget(QLabel("Message Type:")) self.msg_type = QComboBox() self.msg_type.addItems(["Simple Text", "Embed", "Global Announcement", "Skill Gain"]) self.msg_type.currentTextChanged.connect(self._on_builder_type_changed) type_layout.addWidget(self.msg_type) layout.addLayout(type_layout) # Content fields self.builder_content = QTextEdit() self.builder_content.setPlaceholderText("Enter message content...") layout.addWidget(self.builder_content) # Title (for embeds) self.builder_title = QLineEdit() self.builder_title.setPlaceholderText("Embed title (optional)") layout.addWidget(self.builder_title) # Color color_layout = QHBoxLayout() color_layout.addWidget(QLabel("Color:")) self.builder_color = QComboBox() self.builder_color.addItems(["Green", "Red", "Blue", "Gold", "Purple"]) color_layout.addWidget(self.builder_color) layout.addLayout(color_layout) # Preview preview_btn = QPushButton("Preview Payload") preview_btn.clicked.connect(self._preview_payload) layout.addWidget(preview_btn) # Send custom send_btn = QPushButton("Send Custom Payload") send_btn.clicked.connect(self._send_custom_payload) layout.addWidget(send_btn) # Payload preview layout.addWidget(QLabel("Generated Payload:")) self.payload_preview = QTextEdit() self.payload_preview.setReadOnly(True) self.payload_preview.setMaximumHeight(150) layout.addWidget(self.payload_preview) layout.addStretch() return widget def _on_url_changed(self, url: str): """Handle webhook URL changes.""" self._webhook_url = url.strip() if not self._webhook_url: self.url_status.setText("❌ No URL configured") self.url_status.setStyleSheet("color: red;") elif self._webhook_url.startswith(self.WEBHOOK_PATTERN): self.url_status.setText("✅ Valid Discord webhook URL") self.url_status.setStyleSheet("color: green;") else: self.url_status.setText("⚠️ URL doesn't match Discord webhook pattern") self.url_status.setStyleSheet("color: orange;") def _test_connection(self): """Test the webhook connection.""" if not self._webhook_url: self.notify_error("No Webhook URL", "Please configure a webhook URL first") return # Send test message payload = {"content": "🔌 EU-Utility Webhook Test - Connection successful!"} self._send_webhook(payload, "Connection Test") def _run_all_tests(self): """Run all test cases.""" if not self._webhook_url: self.notify_error("No Webhook URL", "Please configure a webhook URL first") return self._test_results.clear() self.progress_bar.setMaximum(len(self.TEST_CASES)) self.progress_bar.setValue(0) for i, test in enumerate(self.TEST_CASES): self._run_test(test, i) self.progress_bar.setValue(i + 1) self._update_results_display() def _run_selected_test(self): """Run the selected test case.""" if not self._webhook_url: self.notify_error("No Webhook URL", "Please configure a webhook URL first") return row = self.test_table.currentRow() if row < 0: self.notify_warning("No Test Selected", "Please select a test from the table") return self._run_test(self.TEST_CASES[row], row) self._update_results_display() def _run_test(self, test: WebhookTest, index: int): """Run a single test case.""" self.log_info(f"Running test: {test.name}") start_time = time.time() success, response = self._send_webhook_sync(test.payload) elapsed = (time.time() - start_time) * 1000 # ms result = { "test_name": test.name, "description": test.description, "expected_status": test.expected_status, "success": success, "response": response, "elapsed_ms": round(elapsed, 2), "timestamp": datetime.now().isoformat() } self._test_results.append(result) # Update table status = "✅ PASS" if success else "❌ FAIL" self.test_table.setItem(index, 2, QTableWidgetItem(status)) def _send_webhook_sync(self, payload: Dict) -> tuple[bool, Any]: """Send webhook synchronously and return result.""" try: import requests response = requests.post( self._webhook_url, json=payload, headers={"Content-Type": "application/json"}, timeout=10 ) success = 200 <= response.status_code < 300 return success, { "status_code": response.status_code, "response_text": response.text[:500] } except Exception as e: return False, {"error": str(e)} def _send_webhook(self, payload: Dict, test_name: str = "Manual"): """Send webhook and show notification.""" success, response = self._send_webhook_sync(payload) if success: self.notify_success(f"{test_name} Sent", f"Status: {response.get('status_code', 'OK')}") else: error = response.get('error', 'Unknown error') self.notify_error(f"{test_name} Failed", error) def _clear_results(self): """Clear all test results.""" self._test_results.clear() for i in range(self.test_table.rowCount()): self.test_table.setItem(i, 2, QTableWidgetItem("⏳ Pending")) self.progress_bar.setValue(0) self.results_text.clear() self.results_summary.setText("No tests run yet") def _update_results_display(self): """Update the results display.""" if not self._test_results: return passed = sum(1 for r in self._test_results if r["success"]) total = len(self._test_results) self.results_summary.setText(f"Results: {passed}/{total} tests passed") # Build detailed results text = [] for result in self._test_results: status = "✅ PASS" if result["success"] else "❌ FAIL" text.append(f"[{status}] {result['test_name']}") text.append(f" Description: {result['description']}") text.append(f" Expected: {result['expected_status']}") text.append(f" Elapsed: {result['elapsed_ms']}ms") text.append(f" Response: {result['response']}") text.append("") self.results_text.setText("\n".join(text)) def _export_results(self): """Export results to JSON.""" if not self._test_results: self.notify_warning("No Results", "No test results to export") return filename = f"discord_webhook_test_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" with open(filename, 'w') as f: json.dump({ "webhook_url": self._webhook_url[:50] + "..." if self._webhook_url else None, "timestamp": datetime.now().isoformat(), "results": self._test_results }, f, indent=2) self.notify_success("Exported", f"Results saved to {filename}") def _on_builder_type_changed(self, msg_type: str): """Update builder based on message type.""" templates = { "Simple Text": "Hello from EU-Utility!", "Embed": "This is an embed message with rich formatting", "Global Announcement": "🎉 GLOBAL! 150 PED loot drop!", "Skill Gain": "Rifle skill increased by 0.25" } self.builder_content.setText(templates.get(msg_type, "")) def _preview_payload(self): """Preview the generated payload.""" payload = self._build_custom_payload() self.payload_preview.setText(json.dumps(payload, indent=2)) def _send_custom_payload(self): """Send the custom payload.""" if not self._webhook_url: self.notify_error("No Webhook URL", "Please configure a webhook URL first") return payload = self._build_custom_payload() self._send_webhook(payload, "Custom Payload") def _build_custom_payload(self) -> Dict: """Build custom payload from builder inputs.""" msg_type = self.msg_type.currentText() content = self.builder_content.toPlainText() title = self.builder_title.text() colors = { "Green": 0x00ff00, "Red": 0xe74c3c, "Blue": 0x3498db, "Gold": 0xffd700, "Purple": 0x9b59b6 } color = colors.get(self.builder_color.currentText(), 0x00ff00) if msg_type == "Simple Text": return {"content": content} else: embed = { "title": title or msg_type, "description": content, "color": color, "timestamp": datetime.utcnow().isoformat() } return {"embeds": [embed]} # Plugin entry point plugin_class = DiscordWebhookTester