EU-Utility/plugins/integration_tests/integration_discord/plugin.py

543 lines
19 KiB
Python

"""
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