Files
Projektarbeit-MYP/backend/simple_form_tester.py

635 lines
22 KiB
Python

#!/usr/bin/env python3
"""
Vereinfachter HTML-Formular Tester für MYP System
===============================================
Ein schlanker Formular-Tester ohne externe Dependencies,
der grundlegende Formular-Validierung mit Standard-Python-Libraries testet.
Autor: Till Tomczak
Mercedes-Benz Projektarbeit MYP
"""
import asyncio
import json
import time
import urllib.request
import urllib.parse
import urllib.error
from urllib.parse import urljoin, urlparse
from html.parser import HTMLParser
from dataclasses import dataclass
from typing import Dict, List, Optional
import sys
import re
@dataclass
class FormField:
"""Repräsentiert ein Formular-Feld"""
name: str
field_type: str
required: bool = False
pattern: str = ""
placeholder: str = ""
value: str = ""
@dataclass
class Form:
"""Repräsentiert ein HTML-Formular"""
action: str
method: str
fields: List[FormField]
name: str = ""
class FormParser(HTMLParser):
"""Parst HTML und extrahiert Formular-Informationen"""
def __init__(self):
super().__init__()
self.forms = []
self.current_form = None
self.in_form = False
def handle_starttag(self, tag, attrs):
attrs_dict = dict(attrs)
if tag == 'form':
self.in_form = True
self.current_form = Form(
action=attrs_dict.get('action', ''),
method=attrs_dict.get('method', 'GET').upper(),
fields=[],
name=attrs_dict.get('name', attrs_dict.get('id', f'form_{len(self.forms)}'))
)
elif self.in_form and tag in ['input', 'textarea', 'select']:
field = FormField(
name=attrs_dict.get('name', attrs_dict.get('id', f'field_{len(self.current_form.fields)}')),
field_type=attrs_dict.get('type', tag),
required='required' in attrs_dict,
pattern=attrs_dict.get('pattern', ''),
placeholder=attrs_dict.get('placeholder', ''),
value=attrs_dict.get('value', '')
)
self.current_form.fields.append(field)
def handle_endtag(self, tag):
if tag == 'form' and self.in_form:
self.in_form = False
if self.current_form:
self.forms.append(self.current_form)
self.current_form = None
class SimpleFormTester:
"""
Vereinfachter Formular-Tester ohne Browser-Dependencies.
Führt HTTP-basierte Tests durch.
"""
def __init__(self, base_url: str):
self.base_url = base_url.rstrip('/')
self.session_cookies = {}
def fetch_page(self, path: str) -> str:
"""Lädt eine Seite und gibt den HTML-Inhalt zurück"""
url = urljoin(self.base_url, path)
try:
request = urllib.request.Request(url)
# Cookies hinzufügen
if self.session_cookies:
cookie_header = '; '.join([f'{k}={v}' for k, v in self.session_cookies.items()])
request.add_header('Cookie', cookie_header)
with urllib.request.urlopen(request) as response:
html = response.read().decode('utf-8')
# Cookies aus Response extrahieren
cookie_header = response.getheader('Set-Cookie')
if cookie_header:
self._parse_cookies(cookie_header)
return html
except urllib.error.URLError as e:
print(f"❌ Fehler beim Laden von {url}: {e}")
return ""
def _parse_cookies(self, cookie_header: str):
"""Parst Set-Cookie Header"""
for cookie in cookie_header.split(','):
if '=' in cookie:
name, value = cookie.split('=', 1)
self.session_cookies[name.strip()] = value.split(';')[0].strip()
def find_forms(self, html: str) -> List[Form]:
"""Findet alle Formulare in HTML"""
parser = FormParser()
parser.feed(html)
return parser.forms
def generate_test_data(self, field: FormField) -> str:
"""Generiert Test-Daten für ein Feld"""
if field.field_type == 'email':
return "test@mercedes-benz.com"
elif field.field_type == 'password':
return "TestPassword123!"
elif field.field_type == 'tel':
return "+49 711 17-0"
elif field.field_type == 'url':
return "https://www.mercedes-benz.com"
elif field.field_type == 'number':
return "42"
elif field.field_type == 'date':
return "2024-06-18"
elif field.field_type == 'checkbox':
return "on"
elif field.field_type == 'radio':
return field.value or "option1"
elif 'name' in field.name.lower():
return "Test Benutzer"
elif 'username' in field.name.lower():
return "admin"
elif 'ip' in field.name.lower():
return "192.168.1.100"
elif 'port' in field.name.lower():
return "80"
else:
return f"Test_{field.name}"
def submit_form(self, form: Form, test_data: Dict[str, str], base_url: str) -> Dict:
"""Sendet Formular-Daten und gibt Response zurück"""
# Action URL bestimmen
if form.action.startswith('http'):
url = form.action
else:
url = urljoin(base_url, form.action)
# Daten vorbereiten
form_data = {}
for field in form.fields:
if field.name in test_data:
form_data[field.name] = test_data[field.name]
elif field.field_type not in ['submit', 'button']:
form_data[field.name] = self.generate_test_data(field)
try:
if form.method == 'GET':
# GET-Request mit Query-Parametern
query_string = urllib.parse.urlencode(form_data)
full_url = f"{url}?{query_string}"
request = urllib.request.Request(full_url)
else:
# POST-Request
data = urllib.parse.urlencode(form_data).encode('utf-8')
request = urllib.request.Request(url, data=data)
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
# Cookies hinzufügen
if self.session_cookies:
cookie_header = '; '.join([f'{k}={v}' for k, v in self.session_cookies.items()])
request.add_header('Cookie', cookie_header)
with urllib.request.urlopen(request) as response:
response_html = response.read().decode('utf-8')
status_code = response.getcode()
return {
'success': 200 <= status_code < 400,
'status_code': status_code,
'html': response_html,
'form_data': form_data
}
except urllib.error.HTTPError as e:
return {
'success': False,
'status_code': e.code,
'error': str(e),
'form_data': form_data
}
except Exception as e:
return {
'success': False,
'error': str(e),
'form_data': form_data
}
def validate_field(self, field: FormField, value: str) -> Dict:
"""Validiert ein Feld mit gegebenem Wert"""
errors = []
# Required-Validierung
if field.required and not value.strip():
errors.append(f"Feld '{field.name}' ist erforderlich")
# Pattern-Validierung
if field.pattern and value:
try:
if not re.match(field.pattern, value):
errors.append(f"Feld '{field.name}' entspricht nicht dem Pattern '{field.pattern}'")
except re.error:
errors.append(f"Ungültiges Pattern für Feld '{field.name}': {field.pattern}")
# Type-spezifische Validierung
if value and field.field_type == 'email':
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_pattern, value):
errors.append(f"Ungültige Email-Adresse: {value}")
elif value and field.field_type == 'url':
try:
parsed = urlparse(value)
if not parsed.scheme or not parsed.netloc:
errors.append(f"Ungültige URL: {value}")
except:
errors.append(f"Ungültige URL: {value}")
elif value and field.field_type == 'number':
try:
float(value)
except ValueError:
errors.append(f"'{value}' ist keine gültige Zahl")
return {
'valid': len(errors) == 0,
'errors': errors,
'field': field.name,
'value': value
}
def test_form_validation(self, form: Form) -> List[Dict]:
"""Testet Formular-Validierung mit verschiedenen Daten"""
validation_results = []
# 1. Test mit leeren Required-Feldern
for field in form.fields:
if field.required:
result = self.validate_field(field, "")
validation_results.append({
'test_type': 'required_field_empty',
'field': field.name,
'expected_invalid': True,
'actual_valid': result['valid'],
'passed': not result['valid'], # Sollte ungültig sein
'errors': result['errors']
})
# 2. Test mit ungültigen Email-Adressen
for field in form.fields:
if field.field_type == 'email':
invalid_emails = ['invalid-email', 'test@', '@domain.com', 'test..test@domain.com']
for invalid_email in invalid_emails:
result = self.validate_field(field, invalid_email)
validation_results.append({
'test_type': 'invalid_email',
'field': field.name,
'value': invalid_email,
'expected_invalid': True,
'actual_valid': result['valid'],
'passed': not result['valid'],
'errors': result['errors']
})
# 3. Test mit gültigen Daten
valid_test_data = {}
for field in form.fields:
if field.field_type not in ['submit', 'button']:
valid_test_data[field.name] = self.generate_test_data(field)
for field in form.fields:
if field.name in valid_test_data:
result = self.validate_field(field, valid_test_data[field.name])
validation_results.append({
'test_type': 'valid_data',
'field': field.name,
'value': valid_test_data[field.name],
'expected_invalid': False,
'actual_valid': result['valid'],
'passed': result['valid'],
'errors': result['errors']
})
return validation_results
def test_form_submission(self, form: Form, base_url: str) -> Dict:
"""Testet Formular-Submission"""
print(f"🧪 Teste Formular: {form.name} ({form.method}{form.action})")
# Test-Daten generieren
test_data = {}
for field in form.fields:
if field.field_type not in ['submit', 'button']:
test_data[field.name] = self.generate_test_data(field)
print(f" 📝 Test-Daten: {list(test_data.keys())}")
# Formular senden
start_time = time.time()
result = self.submit_form(form, test_data, base_url)
execution_time = time.time() - start_time
# Validierungs-Tests
validation_results = self.test_form_validation(form)
return {
'form_name': form.name,
'method': form.method,
'action': form.action,
'fields_count': len(form.fields),
'test_data': test_data,
'submission_result': result,
'validation_results': validation_results,
'execution_time': execution_time,
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S')
}
def test_page_forms(self, path: str) -> List[Dict]:
"""Testet alle Formulare auf einer Seite"""
print(f"🔍 Scanne Formulare auf: {self.base_url}{path}")
# Seite laden
html = self.fetch_page(path)
if not html:
return []
# Formulare finden
forms = self.find_forms(html)
print(f"{len(forms)} Formulare gefunden")
# Jedes Formular testen
results = []
for form in forms:
result = self.test_form_submission(form, self.base_url + path)
results.append(result)
return results
def generate_report(self, test_results: List[Dict], output_file: str = "simple_form_test_report.html"):
"""Generiert einen einfachen HTML-Report"""
total_forms = len(test_results)
successful_submissions = len([r for r in test_results if r['submission_result'].get('success', False)])
# Validierungs-Statistiken
total_validations = sum(len(r['validation_results']) for r in test_results)
passed_validations = sum(len([v for v in r['validation_results'] if v['passed']]) for r in test_results)
html_report = f"""<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MYP Formular Test Report</title>
<style>
body {{
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f5f5f5;
}}
.container {{
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}}
.header {{
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
text-align: center;
margin: -20px -20px 20px -20px;
border-radius: 8px 8px 0 0;
}}
.stats {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 30px;
}}
.stat-card {{
background: #f8f9fa;
padding: 15px;
border-radius: 6px;
text-align: center;
border: 1px solid #dee2e6;
}}
.stat-number {{
font-size: 2em;
font-weight: bold;
color: #333;
}}
.form-result {{
border: 1px solid #dee2e6;
border-radius: 6px;
margin-bottom: 20px;
overflow: hidden;
}}
.form-header {{
background: #e9ecef;
padding: 15px;
border-bottom: 1px solid #dee2e6;
}}
.form-content {{
padding: 15px;
}}
.success {{ color: #28a745; }}
.failure {{ color: #dc3545; }}
.warning {{ color: #ffc107; }}
.test-data {{
background: #f8f9fa;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
font-family: monospace;
font-size: 0.9em;
}}
.validation-result {{
margin: 5px 0;
padding: 5px 10px;
border-radius: 4px;
border-left: 4px solid #dee2e6;
}}
.validation-passed {{
background: #d4edda;
border-left-color: #28a745;
}}
.validation-failed {{
background: #f8d7da;
border-left-color: #dc3545;
}}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🧪 MYP Formular Test Report</h1>
<p>Generiert am {time.strftime('%d.%m.%Y um %H:%M:%S')}</p>
</div>
<div class="stats">
<div class="stat-card">
<div class="stat-number">{total_forms}</div>
<div>Formulare getestet</div>
</div>
<div class="stat-card">
<div class="stat-number {'success' if successful_submissions == total_forms else 'warning' if successful_submissions > 0 else 'failure'}">{successful_submissions}</div>
<div>Erfolgreiche Submissions</div>
</div>
<div class="stat-card">
<div class="stat-number">{total_validations}</div>
<div>Validierungs-Tests</div>
</div>
<div class="stat-card">
<div class="stat-number {'success' if passed_validations == total_validations else 'warning' if passed_validations > 0 else 'failure'}">{passed_validations}</div>
<div>Validierungen bestanden</div>
</div>
</div>
<h2>📋 Detaillierte Ergebnisse</h2>
"""
# Formular-spezifische Ergebnisse
for result in test_results:
submission = result['submission_result']
success_class = 'success' if submission.get('success', False) else 'failure'
html_report += f"""
<div class="form-result">
<div class="form-header">
<h3>📝 {result['form_name']}</h3>
<p><strong>Method:</strong> {result['method']} | <strong>Action:</strong> {result['action']} | <strong>Felder:</strong> {result['fields_count']}</p>
</div>
<div class="form-content">
<h4>Submission-Ergebnis: <span class="{success_class}">{'✅ Erfolgreich' if submission.get('success', False) else '❌ Fehlgeschlagen'}</span></h4>
{f'<p><strong>Status Code:</strong> {submission.get("status_code", "N/A")}</p>' if 'status_code' in submission else ''}
{f'<p><strong>Fehler:</strong> {submission.get("error", "N/A")}</p>' if 'error' in submission else ''}
<h4>Test-Daten:</h4>
<div class="test-data">{json.dumps(result['test_data'], indent=2, ensure_ascii=False)}</div>
<h4>Validierungs-Ergebnisse:</h4>
"""
# Validierungs-Ergebnisse
for validation in result['validation_results']:
validation_class = 'validation-passed' if validation['passed'] else 'validation-failed'
status_icon = '' if validation['passed'] else ''
html_report += f"""
<div class="validation-result {validation_class}">
<strong>{status_icon} {validation['test_type']}</strong> - Feld: {validation['field']}
{f"<br>Wert: <code>{validation.get('value', 'N/A')}</code>" if 'value' in validation else ''}
{f"<br>Fehler: {', '.join(validation['errors'])}" if validation['errors'] else ''}
</div>
"""
html_report += f"""
<p><small>⏱️ Ausführungszeit: {result['execution_time']:.2f}s</small></p>
</div>
</div>
"""
html_report += """
</div>
</body>
</html>"""
try:
with open(output_file, 'w', encoding='utf-8') as f:
f.write(html_report)
print(f"📊 Report erstellt: {output_file}")
return output_file
except Exception as e:
print(f"❌ Fehler beim Report-Erstellen: {e}")
return ""
def main():
"""Haupt-CLI-Interface"""
if len(sys.argv) < 2:
print("""
🧪 Vereinfachter MYP Formular Tester
Verwendung:
python simple_form_tester.py <base_url> [path]
Beispiele:
python simple_form_tester.py http://localhost:5000
python simple_form_tester.py http://localhost:5000 /login
python simple_form_tester.py http://localhost:5000 /admin/add_printer
Testet alle Formulare auf der angegebenen Seite und generiert einen HTML-Report.
""")
return
base_url = sys.argv[1]
path = sys.argv[2] if len(sys.argv) > 2 else '/'
print(f"""
{'='*60}
🧪 MYP FORMULAR TESTER (Vereinfacht)
{'='*60}
🎯 Basis-URL: {base_url}
📍 Pfad: {path}
🕒 Gestartet: {time.strftime('%d.%m.%Y um %H:%M:%S')}
{'='*60}
""")
# Tester initialisieren
tester = SimpleFormTester(base_url)
# Tests ausführen
try:
results = tester.test_page_forms(path)
if results:
# Report generieren
report_file = tester.generate_report(results)
# Zusammenfassung
total_forms = len(results)
successful = len([r for r in results if r['submission_result'].get('success', False)])
print(f"""
{'='*60}
🎉 TESTS ABGESCHLOSSEN
{'='*60}
📊 ZUSAMMENFASSUNG:
• Formulare getestet: {total_forms}
• Erfolgreiche Submissions: {successful}
• Erfolgsrate: {successful/total_forms*100:.1f}%
📋 Report: {report_file}
{'='*60}
""")
else:
print("⚠️ Keine Formulare gefunden oder Fehler beim Laden der Seite")
except Exception as e:
print(f"❌ Fehler beim Testen: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()