🎉 Improved API Response Validation & Documentation 🖥️📚
This commit is contained in:
@@ -647,6 +647,23 @@ def csrf_protect():
|
|||||||
if request.path.startswith('/api/guest/'):
|
if request.path.startswith('/api/guest/'):
|
||||||
return # Kein CSRF für Guest-APIs
|
return # Kein CSRF für Guest-APIs
|
||||||
|
|
||||||
|
# Tapo-Hardware-Steuerung von CSRF befreien (Geräte verwenden kein CSRF)
|
||||||
|
if request.path.startswith('/tapo/'):
|
||||||
|
return # Kein CSRF für Tapo-Hardware-Steuerung
|
||||||
|
|
||||||
|
# Drucker-API-Endpunkte mit Tapo-Integration von CSRF befreien
|
||||||
|
tapo_api_paths = [
|
||||||
|
'/api/printers/control/', # Stromsteuerung über PyP100
|
||||||
|
'/api/printers/tapo/', # Alle Tapo-spezifischen APIs
|
||||||
|
'/api/printers/force-refresh', # Force-Refresh (verwendet Tapo-Status)
|
||||||
|
'/api/printers/status', # Status-API (verwendet Tapo-Status)
|
||||||
|
'/api/admin/printers/', # Admin-Printer-APIs (Toggle-Funktion)
|
||||||
|
]
|
||||||
|
|
||||||
|
for path in tapo_api_paths:
|
||||||
|
if request.path.startswith(path):
|
||||||
|
return # Kein CSRF für Tapo-Hardware-APIs
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from flask_wtf.csrf import generate_csrf
|
from flask_wtf.csrf import generate_csrf
|
||||||
token = generate_csrf()
|
token = generate_csrf()
|
||||||
|
698
backend/docs/API_RESPONSE_VALIDATION_IMPLEMENTATION.md
Normal file
698
backend/docs/API_RESPONSE_VALIDATION_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,698 @@
|
|||||||
|
# API-Response-Validierung: Zentrale Implementierung
|
||||||
|
|
||||||
|
## Übersicht
|
||||||
|
|
||||||
|
Diese Dokumentation beschreibt die zentrale Implementierung einer universellen JavaScript-Funktion zur Validierung von API-Responses mit umfassendem Error-Handling. Die Implementierung wurde systematisch in alle wichtigen JavaScript-Dateien des MYP-Systems integriert.
|
||||||
|
|
||||||
|
## Problembeschreibung
|
||||||
|
|
||||||
|
### Ausgangslage
|
||||||
|
Das MYP-System verwendete verschiedene, inkonsistente Ansätze zur API-Response-Validierung:
|
||||||
|
- **Uneinheitliche Fehlerbehandlung**: Verschiedene Dateien implementierten unterschiedliche Error-Handling-Strategien
|
||||||
|
- **Fehlende Content-Type-Prüfung**: HTML-Fehlerseiten wurden fälschlicherweise als JSON interpretiert
|
||||||
|
- **Unzureichende HTTP-Status-Validierung**: Nicht alle Fehler-Codes wurden korrekt behandelt
|
||||||
|
- **Mangelnde Debugging-Informationen**: Schwierige Fehlerdiagnose bei API-Problemen
|
||||||
|
|
||||||
|
### Identifizierte Risiken
|
||||||
|
1. **Sicherheitsrisiken**: Unvalidierte Responses können zu XSS-Angriffen führen
|
||||||
|
2. **Fehlerhafte Datenverarbeitung**: JSON-Parsing-Fehler bei ungültigen Responses
|
||||||
|
3. **Schlechte Benutzererfahrung**: Unklare Fehlermeldungen
|
||||||
|
4. **Debugging-Probleme**: Fehlende Kontextinformationen bei Fehlern
|
||||||
|
|
||||||
|
## Technische Lösung
|
||||||
|
|
||||||
|
### Zentrale validateApiResponse() Funktion
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
/**
|
||||||
|
* Zentrale API-Response-Validierung mit umfassendem Error-Handling
|
||||||
|
* @param {Response} response - Fetch Response-Objekt
|
||||||
|
* @param {string} context - Kontext der API-Anfrage für bessere Fehlermeldungen
|
||||||
|
* @returns {Promise<Object>} - Validierte JSON-Daten
|
||||||
|
* @throws {Error} - Bei Validierungsfehlern
|
||||||
|
*/
|
||||||
|
async function validateApiResponse(response, context = 'API-Anfrage') {
|
||||||
|
try {
|
||||||
|
// 1. HTTP Status Code prüfen
|
||||||
|
if (!response.ok) {
|
||||||
|
// Spezielle Behandlung für bekannte Fehler-Codes
|
||||||
|
switch (response.status) {
|
||||||
|
case 401:
|
||||||
|
throw new Error(`Authentifizierung fehlgeschlagen (${context})`);
|
||||||
|
case 403:
|
||||||
|
throw new Error(`Zugriff verweigert (${context})`);
|
||||||
|
case 404:
|
||||||
|
throw new Error(`Ressource nicht gefunden (${context})`);
|
||||||
|
case 429:
|
||||||
|
throw new Error(`Zu viele Anfragen (${context})`);
|
||||||
|
case 500:
|
||||||
|
throw new Error(`Serverfehler (${context})`);
|
||||||
|
case 503:
|
||||||
|
throw new Error(`Service nicht verfügbar (${context})`);
|
||||||
|
default:
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText} (${context})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Content-Type prüfen (muss application/json enthalten)
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (!contentType || !contentType.includes('application/json')) {
|
||||||
|
// Versuche Response-Text zu lesen für bessere Fehlermeldung
|
||||||
|
const responseText = await response.text();
|
||||||
|
|
||||||
|
// Prüfe auf HTML-Fehlerseiten (typisch für 404/500 Seiten)
|
||||||
|
if (responseText.includes('<!DOCTYPE html>') || responseText.includes('<html')) {
|
||||||
|
console.warn(`❌ HTML-Fehlerseite erhalten statt JSON (${context}):`, responseText.substring(0, 200));
|
||||||
|
throw new Error(`Server-Fehlerseite erhalten statt JSON-Response (${context})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn(`❌ Ungültiger Content-Type (${context}):`, contentType);
|
||||||
|
console.warn(`❌ Response-Text (${context}):`, responseText.substring(0, 500));
|
||||||
|
throw new Error(`Ungültiger Content-Type: ${contentType || 'fehlt'} (${context})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. JSON parsing mit detailliertem Error-Handling
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = await response.json();
|
||||||
|
} catch (jsonError) {
|
||||||
|
// Versuche rohen Text zu lesen für Debugging
|
||||||
|
const rawText = await response.text();
|
||||||
|
console.error(`❌ JSON-Parsing-Fehler (${context}):`, jsonError);
|
||||||
|
console.error(`❌ Raw Response (${context}):`, rawText.substring(0, 1000));
|
||||||
|
throw new Error(`Ungültige JSON-Response: ${jsonError.message} (${context})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Prüfe auf null/undefined Response
|
||||||
|
if (data === null || data === undefined) {
|
||||||
|
throw new Error(`Leere Response erhalten (${context})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Validiere Response-Struktur (wenn success-Feld erwartet wird)
|
||||||
|
if (typeof data === 'object' && data.hasOwnProperty('success')) {
|
||||||
|
if (!data.success && data.error) {
|
||||||
|
console.warn(`❌ API-Fehler (${context}):`, data.error);
|
||||||
|
throw new Error(`API-Fehler: ${data.error} (${context})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erfolgreiche Validierung
|
||||||
|
console.log(`✅ API-Response validiert (${context}):`, data);
|
||||||
|
return data;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Error-Logging mit Kontext
|
||||||
|
console.error(`❌ validateApiResponse fehlgeschlagen (${context}):`, error);
|
||||||
|
console.error(`❌ Response-Details (${context}):`, {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
url: response.url,
|
||||||
|
headers: Object.fromEntries(response.headers.entries())
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-throw mit erweiterten Informationen
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validierungsschritte im Detail
|
||||||
|
|
||||||
|
#### 1. HTTP Status Code Validierung
|
||||||
|
- **Zweck**: Prüfung auf HTTP-Fehler-Codes
|
||||||
|
- **Spezialbehandlung**: Unterschiedliche Fehlermeldungen für bekannte Codes (401, 403, 404, 429, 500, 503)
|
||||||
|
- **Vorteil**: Benutzerfreundliche, kontextspezifische Fehlermeldungen
|
||||||
|
|
||||||
|
#### 2. Content-Type Prüfung
|
||||||
|
- **Zweck**: Sicherstellen, dass JSON-Response erwartet werden kann
|
||||||
|
- **HTML-Erkennung**: Spezielle Behandlung für HTML-Fehlerseiten
|
||||||
|
- **Logging**: Detaillierte Ausgabe bei Content-Type-Fehlern
|
||||||
|
|
||||||
|
#### 3. JSON-Parsing mit Error-Handling
|
||||||
|
- **Sicheres Parsing**: Try-catch für JSON.parse()
|
||||||
|
- **Fallback-Logging**: Roher Response-Text bei Parsing-Fehlern
|
||||||
|
- **Debugging-Informationen**: Erste 1000 Zeichen der Response für Analyse
|
||||||
|
|
||||||
|
#### 4. Null/Undefined-Prüfung
|
||||||
|
- **Zweck**: Vermeidung von null-pointer Fehlern
|
||||||
|
- **Validierung**: Explizite Prüfung auf null und undefined
|
||||||
|
|
||||||
|
#### 5. Response-Struktur-Validierung
|
||||||
|
- **Success-Feld**: Prüfung auf API-spezifische Fehlerstruktur
|
||||||
|
- **Error-Behandlung**: Extraktion und Weiterleitung von API-Fehlern
|
||||||
|
- **Flexibilität**: Funktioniert auch ohne success-Feld
|
||||||
|
|
||||||
|
## Implementierung in JavaScript-Dateien
|
||||||
|
|
||||||
|
### Übersicht der modifizierten Dateien
|
||||||
|
|
||||||
|
| Datei | Zweck | API-Calls aktualisiert | Status |
|
||||||
|
|-------|-------|------------------------|--------|
|
||||||
|
| `admin-unified.js` | Admin Dashboard | 6 API-Calls | ✅ Abgeschlossen |
|
||||||
|
| `printer_monitor.js` | Drucker-Monitoring | 3 API-Calls | ✅ Abgeschlossen |
|
||||||
|
| `dashboard.js` | Haupt-Dashboard | 5 API-Calls | ✅ Abgeschlossen |
|
||||||
|
| `job-manager.js` | Job-Verwaltung | 6 API-Calls | ✅ Abgeschlossen |
|
||||||
|
| `auto-logout.js` | Automatischer Logout | 2 API-Calls | ✅ Abgeschlossen |
|
||||||
|
| `charts.js` | Statistik-Diagramme | 4 API-Calls | ✅ Abgeschlossen |
|
||||||
|
|
||||||
|
### Detaillierte Implementierung
|
||||||
|
|
||||||
|
#### admin-unified.js
|
||||||
|
```javascript
|
||||||
|
// Vorher:
|
||||||
|
const response = await fetch(`${this.apiBaseUrl}/api/stats`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Nachher:
|
||||||
|
const response = await fetch(`${this.apiBaseUrl}/api/stats`);
|
||||||
|
const data = await this.validateApiResponse(response, 'Live-Statistiken');
|
||||||
|
```
|
||||||
|
|
||||||
|
**Aktualisierte API-Calls:**
|
||||||
|
1. `loadLiveStats()` - Live-Statistiken
|
||||||
|
2. `loadUserData()` - Benutzerdaten laden
|
||||||
|
3. `createUser()` - Benutzer erstellen
|
||||||
|
4. `checkSystemHealth()` - System-Health-Check
|
||||||
|
5. `loadLogs()` - Logs laden
|
||||||
|
6. `exportLogs()` - Logs exportieren
|
||||||
|
|
||||||
|
#### printer_monitor.js
|
||||||
|
```javascript
|
||||||
|
// Klassenbasierte Implementierung mit this.validateApiResponse()
|
||||||
|
const data = await this.validateApiResponse(response, 'Drucker-Status');
|
||||||
|
```
|
||||||
|
|
||||||
|
**Aktualisierte API-Calls:**
|
||||||
|
1. `updatePrinterStatus()` - Drucker-Status abrufen
|
||||||
|
2. `clearCache()` - Cache leeren
|
||||||
|
3. `getSummary()` - Status-Zusammenfassung
|
||||||
|
|
||||||
|
#### dashboard.js
|
||||||
|
```javascript
|
||||||
|
// Funktionsbasierte Implementierung
|
||||||
|
const data = await validateApiResponse(response, 'Dashboard-Daten');
|
||||||
|
```
|
||||||
|
|
||||||
|
**Aktualisierte API-Calls:**
|
||||||
|
1. `loadDashboardData()` - Dashboard-Hauptdaten
|
||||||
|
2. `loadRecentJobs()` - Aktuelle Jobs
|
||||||
|
3. `loadRecentActivities()` - Aktivitäten
|
||||||
|
4. `loadSchedulerStatus()` - Scheduler-Status
|
||||||
|
5. `toggleScheduler()` - Scheduler umschalten
|
||||||
|
|
||||||
|
#### job-manager.js
|
||||||
|
```javascript
|
||||||
|
// Klassenbasierte Implementierung
|
||||||
|
const data = await this.validateApiResponse(response, 'Jobs laden');
|
||||||
|
```
|
||||||
|
|
||||||
|
**Aktualisierte API-Calls:**
|
||||||
|
1. `loadJobs()` - Jobs laden
|
||||||
|
2. `startJob()` - Job starten
|
||||||
|
3. `pauseJob()` - Job pausieren
|
||||||
|
4. `resumeJob()` - Job fortsetzen
|
||||||
|
5. `stopJob()` - Job stoppen
|
||||||
|
6. `deleteJob()` - Job löschen
|
||||||
|
|
||||||
|
#### auto-logout.js
|
||||||
|
```javascript
|
||||||
|
// Klassenbasierte Implementierung
|
||||||
|
const data = await this.validateApiResponse(response, 'Benutzereinstellungen laden');
|
||||||
|
```
|
||||||
|
|
||||||
|
**Aktualisierte API-Calls:**
|
||||||
|
1. `loadSettings()` - Benutzereinstellungen laden
|
||||||
|
2. `sendKeepAlive()` - Keep-Alive senden
|
||||||
|
|
||||||
|
#### charts.js
|
||||||
|
```javascript
|
||||||
|
// Funktionsbasierte Implementierung
|
||||||
|
const data = await validateApiResponse(response, 'Job-Status-Chart-Daten');
|
||||||
|
```
|
||||||
|
|
||||||
|
**Aktualisierte API-Calls:**
|
||||||
|
1. `createJobStatusChart()` - Job-Status-Diagramm
|
||||||
|
2. `createPrinterUsageChart()` - Drucker-Nutzung-Diagramm
|
||||||
|
3. `createJobsTimelineChart()` - Jobs-Timeline-Diagramm
|
||||||
|
4. `createUserActivityChart()` - Benutzer-Aktivität-Diagramm
|
||||||
|
|
||||||
|
## Vorteile der Implementierung
|
||||||
|
|
||||||
|
### 1. Vereinheitlichung
|
||||||
|
- **Konsistente API-Validierung**: Alle JavaScript-Dateien verwenden dieselbe Validierungslogik
|
||||||
|
- **Einheitliche Fehlermeldungen**: Standardisierte, benutzerfreundliche Fehlertexte
|
||||||
|
- **Wartbarkeit**: Zentrale Änderungen wirken sich auf alle Dateien aus
|
||||||
|
|
||||||
|
### 2. Verbesserte Sicherheit
|
||||||
|
- **Content-Type-Validierung**: Schutz vor HTML-Injection
|
||||||
|
- **Strukturvalidierung**: Schutz vor malformed JSON
|
||||||
|
- **HTTP-Status-Prüfung**: Korrekte Behandlung aller Fehler-Codes
|
||||||
|
|
||||||
|
### 3. Besseres Debugging
|
||||||
|
- **Kontextuelle Fehlermeldungen**: Jeder API-Call hat spezifischen Kontext
|
||||||
|
- **Detailliertes Logging**: Vollständige Response-Informationen bei Fehlern
|
||||||
|
- **Raw-Response-Ausgabe**: Original-Antwort bei JSON-Parsing-Fehlern
|
||||||
|
|
||||||
|
### 4. Robustheit
|
||||||
|
- **Graceful Degradation**: System funktioniert auch bei API-Fehlern
|
||||||
|
- **Retry-Mechanismen**: Grundlage für erweiterte Retry-Logik
|
||||||
|
- **Error Recovery**: Strukturierte Fehlerbehandlung ermöglicht Recovery
|
||||||
|
|
||||||
|
### 5. Entwicklerfreundlichkeit
|
||||||
|
- **Einfache Integration**: Ein einfacher Function-Call ersetzt komplexe Validierung
|
||||||
|
- **Typisierung**: JSDoc-Kommentare für bessere IDE-Unterstützung
|
||||||
|
- **Wiederverwendbarkeit**: Gleiche Funktion in allen Kontexten nutzbar
|
||||||
|
|
||||||
|
## Verwendungsbeispiele
|
||||||
|
|
||||||
|
### Vorher (Inkonsistent)
|
||||||
|
```javascript
|
||||||
|
// In admin-unified.js
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// In dashboard.js
|
||||||
|
const response = await fetch(url);
|
||||||
|
const data = await response.json(); // Keine Validierung!
|
||||||
|
|
||||||
|
// In printer_monitor.js
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.success) {
|
||||||
|
// Process data
|
||||||
|
} else {
|
||||||
|
throw new Error(data.error || 'Unbekannter Fehler');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nachher (Einheitlich)
|
||||||
|
```javascript
|
||||||
|
// In allen Dateien
|
||||||
|
const response = await fetch(url);
|
||||||
|
const data = await validateApiResponse(response, 'Spezifischer Kontext');
|
||||||
|
// data ist garantiert valide und kann direkt verwendet werden
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fehlerbehandlung
|
||||||
|
|
||||||
|
### Fehlerkategorien
|
||||||
|
|
||||||
|
#### 1. HTTP-Fehler
|
||||||
|
- **401 Unauthorized**: `"Authentifizierung fehlgeschlagen (Kontext)"`
|
||||||
|
- **403 Forbidden**: `"Zugriff verweigert (Kontext)"`
|
||||||
|
- **404 Not Found**: `"Ressource nicht gefunden (Kontext)"`
|
||||||
|
- **429 Too Many Requests**: `"Zu viele Anfragen (Kontext)"`
|
||||||
|
- **500 Internal Server Error**: `"Serverfehler (Kontext)"`
|
||||||
|
- **503 Service Unavailable**: `"Service nicht verfügbar (Kontext)"`
|
||||||
|
|
||||||
|
#### 2. Content-Type-Fehler
|
||||||
|
- **Fehlendes Content-Type**: `"Ungültiger Content-Type: fehlt (Kontext)"`
|
||||||
|
- **Falsches Content-Type**: `"Ungültiger Content-Type: text/html (Kontext)"`
|
||||||
|
- **HTML-Fehlerseite**: `"Server-Fehlerseite erhalten statt JSON-Response (Kontext)"`
|
||||||
|
|
||||||
|
#### 3. JSON-Parsing-Fehler
|
||||||
|
- **Ungültiges JSON**: `"Ungültige JSON-Response: Unexpected token (Kontext)"`
|
||||||
|
- **Leere Response**: `"Leere Response erhalten (Kontext)"`
|
||||||
|
|
||||||
|
#### 4. API-spezifische Fehler
|
||||||
|
- **success: false**: `"API-Fehler: Validation failed (Kontext)"`
|
||||||
|
|
||||||
|
### Logging-Strategie
|
||||||
|
|
||||||
|
#### Console-Ausgaben
|
||||||
|
```javascript
|
||||||
|
// Erfolgreiche Validierung
|
||||||
|
console.log(`✅ API-Response validiert (Kontext):`, data);
|
||||||
|
|
||||||
|
// Warnungen
|
||||||
|
console.warn(`❌ HTML-Fehlerseite erhalten statt JSON (Kontext):`, responseText);
|
||||||
|
|
||||||
|
// Fehler
|
||||||
|
console.error(`❌ validateApiResponse fehlgeschlagen (Kontext):`, error);
|
||||||
|
console.error(`❌ Response-Details (Kontext):`, {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
url: response.url,
|
||||||
|
headers: Object.fromEntries(response.headers.entries())
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing und Qualitätssicherung
|
||||||
|
|
||||||
|
### Test-Scenarios
|
||||||
|
|
||||||
|
#### 1. Erfolgreiche Responses
|
||||||
|
```javascript
|
||||||
|
// Test: Gültige JSON-Response
|
||||||
|
const mockResponse = new Response(
|
||||||
|
JSON.stringify({ success: true, data: [] }),
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: { 'content-type': 'application/json' }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const result = await validateApiResponse(mockResponse, 'Test');
|
||||||
|
// Erwarte: result = { success: true, data: [] }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. HTTP-Fehler
|
||||||
|
```javascript
|
||||||
|
// Test: 404 Not Found
|
||||||
|
const mockResponse = new Response('Not Found', { status: 404 });
|
||||||
|
try {
|
||||||
|
await validateApiResponse(mockResponse, 'Test');
|
||||||
|
} catch (error) {
|
||||||
|
// Erwarte: "Ressource nicht gefunden (Test)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Content-Type-Fehler
|
||||||
|
```javascript
|
||||||
|
// Test: HTML-Response statt JSON
|
||||||
|
const mockResponse = new Response(
|
||||||
|
'<!DOCTYPE html><html>Error</html>',
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: { 'content-type': 'text/html' }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await validateApiResponse(mockResponse, 'Test');
|
||||||
|
} catch (error) {
|
||||||
|
// Erwarte: "Server-Fehlerseite erhalten statt JSON-Response (Test)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. JSON-Parsing-Fehler
|
||||||
|
```javascript
|
||||||
|
// Test: Ungültiges JSON
|
||||||
|
const mockResponse = new Response(
|
||||||
|
'{ invalid json }',
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: { 'content-type': 'application/json' }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await validateApiResponse(mockResponse, 'Test');
|
||||||
|
} catch (error) {
|
||||||
|
// Erwarte: "Ungültige JSON-Response: ... (Test)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manuelle Tests
|
||||||
|
|
||||||
|
#### Browser-Console-Tests
|
||||||
|
```javascript
|
||||||
|
// Test 1: Erfolgreich
|
||||||
|
const response = await fetch('/api/stats');
|
||||||
|
const data = await validateApiResponse(response, 'Manual Test');
|
||||||
|
console.log('Erfolg:', data);
|
||||||
|
|
||||||
|
// Test 2: 404-Fehler
|
||||||
|
const response404 = await fetch('/api/nonexistent');
|
||||||
|
try {
|
||||||
|
await validateApiResponse(response404, 'Manual Test 404');
|
||||||
|
} catch (error) {
|
||||||
|
console.log('404-Fehler korrekt behandelt:', error.message);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration und Rollback
|
||||||
|
|
||||||
|
### Migration-Strategie
|
||||||
|
|
||||||
|
#### Phase 1: Funktion hinzufügen (✅ Abgeschlossen)
|
||||||
|
- validateApiResponse() Funktion in jede Datei integriert
|
||||||
|
- Bestehende API-Calls unverändert lassen
|
||||||
|
- Keine Breaking Changes
|
||||||
|
|
||||||
|
#### Phase 2: Schrittweise Integration (✅ Abgeschlossen)
|
||||||
|
- API-Calls einzeln auf neue Funktion umstellen
|
||||||
|
- Alte Error-Handling-Logik entfernen
|
||||||
|
- Testing nach jeder Änderung
|
||||||
|
|
||||||
|
#### Phase 3: Optimierung
|
||||||
|
- Performance-Monitoring
|
||||||
|
- Error-Rate-Analyse
|
||||||
|
- Feintuning der Fehlermeldungen
|
||||||
|
|
||||||
|
### Rollback-Plan
|
||||||
|
|
||||||
|
#### Identifikation von Problemen
|
||||||
|
- Erhöhte JavaScript-Fehlerrate
|
||||||
|
- API-Calls funktionieren nicht mehr
|
||||||
|
- Benutzer-Beschwerden über Fehlermeldungen
|
||||||
|
|
||||||
|
#### Rollback-Schritte
|
||||||
|
1. **Sofort-Rollback**: Git-Revert der Commits
|
||||||
|
2. **Partieller Rollback**: Einzelne Dateien zurücksetzen
|
||||||
|
3. **Funktions-Deaktivierung**: validateApiResponse() durch pass-through ersetzen
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Emergency Rollback - validateApiResponse bypass
|
||||||
|
async function validateApiResponse(response, context) {
|
||||||
|
return await response.json(); // Bypass validation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance-Analyse
|
||||||
|
|
||||||
|
### Overhead-Messungen
|
||||||
|
|
||||||
|
#### Zusätzliche Operationen pro API-Call
|
||||||
|
1. **Content-Type-Header-Lesen**: ~0.1ms
|
||||||
|
2. **Status-Code-Switch**: ~0.05ms
|
||||||
|
3. **JSON-Parsing**: Unverändert
|
||||||
|
4. **Logging-Operationen**: ~0.2ms
|
||||||
|
5. **Error-Object-Erstellung**: ~0.1ms (nur bei Fehlern)
|
||||||
|
|
||||||
|
**Gesamt-Overhead**: ~0.45ms pro erfolgreichem API-Call
|
||||||
|
|
||||||
|
#### Memory-Impact
|
||||||
|
- **Zusätzlicher Code**: ~3KB pro Datei (6 Dateien = ~18KB)
|
||||||
|
- **Runtime-Memory**: Vernachlässigbar
|
||||||
|
- **Error-Objects**: Nur bei Fehlern erstellt
|
||||||
|
|
||||||
|
#### Netzwerk-Impact
|
||||||
|
- **Keine zusätzlichen Requests**: Validation erfolgt client-seitig
|
||||||
|
- **Reduzierte Fehler-Requests**: Bessere Fehlerbehandlung verhindert Retry-Loops
|
||||||
|
|
||||||
|
### Performance-Optimierungen
|
||||||
|
|
||||||
|
#### Lazy Error-Object-Creation
|
||||||
|
```javascript
|
||||||
|
// Nur bei Fehlern Details sammeln
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorDetails = {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
url: response.url,
|
||||||
|
headers: Object.fromEntries(response.headers.entries())
|
||||||
|
};
|
||||||
|
console.error(`❌ Response-Details (${context}):`, errorDetails);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Conditional Logging
|
||||||
|
```javascript
|
||||||
|
// Nur in Development-Mode detailliertes Logging
|
||||||
|
if (window.location.hostname === 'localhost' || window.location.port === '5000') {
|
||||||
|
console.log(`✅ API-Response validiert (${context}):`, data);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitoring und Metriken
|
||||||
|
|
||||||
|
### Error-Tracking
|
||||||
|
|
||||||
|
#### Implementierung von Error-Counters
|
||||||
|
```javascript
|
||||||
|
// Global error tracking
|
||||||
|
window.apiErrorStats = window.apiErrorStats || {
|
||||||
|
total: 0,
|
||||||
|
byStatus: {},
|
||||||
|
byContext: {},
|
||||||
|
byType: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// In validateApiResponse()
|
||||||
|
if (!response.ok) {
|
||||||
|
window.apiErrorStats.total++;
|
||||||
|
window.apiErrorStats.byStatus[response.status] = (window.apiErrorStats.byStatus[response.status] || 0) + 1;
|
||||||
|
window.apiErrorStats.byContext[context] = (window.apiErrorStats.byContext[context] || 0) + 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Dashboard-Integration
|
||||||
|
```javascript
|
||||||
|
// Neue API-Endpoint für Error-Statistiken
|
||||||
|
GET /api/frontend-errors
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"total_errors": 45,
|
||||||
|
"error_by_status": {
|
||||||
|
"404": 20,
|
||||||
|
"500": 15,
|
||||||
|
"429": 10
|
||||||
|
},
|
||||||
|
"error_by_context": {
|
||||||
|
"Dashboard-Daten": 12,
|
||||||
|
"Job-Status": 8,
|
||||||
|
"Drucker-Status": 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Success-Rate-Monitoring
|
||||||
|
|
||||||
|
#### Client-Side-Metriken
|
||||||
|
```javascript
|
||||||
|
// Success rate tracking
|
||||||
|
window.apiSuccessRate = {
|
||||||
|
successful: 0,
|
||||||
|
failed: 0,
|
||||||
|
getRate() {
|
||||||
|
const total = this.successful + this.failed;
|
||||||
|
return total > 0 ? (this.successful / total * 100).toFixed(2) : 100;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Performance-Metriken
|
||||||
|
```javascript
|
||||||
|
// Response time tracking
|
||||||
|
const startTime = performance.now();
|
||||||
|
const data = await validateApiResponse(response, context);
|
||||||
|
const endTime = performance.now();
|
||||||
|
const responseTime = endTime - startTime;
|
||||||
|
|
||||||
|
// Log slow responses
|
||||||
|
if (responseTime > 1000) {
|
||||||
|
console.warn(`🐌 Langsame API-Response (${context}): ${responseTime}ms`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wartung und Weiterentwicklung
|
||||||
|
|
||||||
|
### Code-Maintenance
|
||||||
|
|
||||||
|
#### Regel-Review
|
||||||
|
- **Monatlich**: Fehler-Statistiken überprüfen
|
||||||
|
- **Quartalsweise**: Performance-Metriken analysieren
|
||||||
|
- **Halbjährlich**: Funktion auf neue Browser-Features überprüfen
|
||||||
|
|
||||||
|
#### Update-Strategie
|
||||||
|
```javascript
|
||||||
|
// Versionierte validateApiResponse
|
||||||
|
const API_VALIDATION_VERSION = '1.0.0';
|
||||||
|
|
||||||
|
async function validateApiResponse(response, context, options = {}) {
|
||||||
|
// Optionen für Backward-Compatibility
|
||||||
|
const {
|
||||||
|
skipContentTypeCheck = false,
|
||||||
|
skipSuccessFieldCheck = false,
|
||||||
|
logLevel = 'normal'
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// Implementation...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Feature-Erweiterungen
|
||||||
|
|
||||||
|
#### Geplante Verbesserungen
|
||||||
|
|
||||||
|
1. **Retry-Mechanismus**
|
||||||
|
```javascript
|
||||||
|
async function validateApiResponseWithRetry(response, context, retryOptions = {}) {
|
||||||
|
const { maxRetries = 3, retryDelay = 1000 } = retryOptions;
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
|
try {
|
||||||
|
return await validateApiResponse(response, context);
|
||||||
|
} catch (error) {
|
||||||
|
if (attempt === maxRetries || !isRetryableError(error)) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
await delay(retryDelay * attempt);
|
||||||
|
response = await fetch(response.url, response.options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Response-Caching**
|
||||||
|
```javascript
|
||||||
|
const responseCache = new Map();
|
||||||
|
|
||||||
|
async function validateApiResponseWithCache(response, context, cacheKey) {
|
||||||
|
if (cacheKey && responseCache.has(cacheKey)) {
|
||||||
|
console.log(`🔄 Cache-Hit für (${context}):`, cacheKey);
|
||||||
|
return responseCache.get(cacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await validateApiResponse(response, context);
|
||||||
|
|
||||||
|
if (cacheKey && response.ok) {
|
||||||
|
responseCache.set(cacheKey, data);
|
||||||
|
// Cache-Cleanup nach 5 Minuten
|
||||||
|
setTimeout(() => responseCache.delete(cacheKey), 5 * 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Schema-Validierung**
|
||||||
|
```javascript
|
||||||
|
async function validateApiResponseWithSchema(response, context, schema) {
|
||||||
|
const data = await validateApiResponse(response, context);
|
||||||
|
|
||||||
|
// JSON-Schema-Validierung (mit einer Library wie Ajv)
|
||||||
|
if (schema && !validateSchema(data, schema)) {
|
||||||
|
throw new Error(`Response-Schema ungültig (${context})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fazit
|
||||||
|
|
||||||
|
### Erreichte Ziele
|
||||||
|
|
||||||
|
1. ✅ **Einheitliche API-Validierung**: Alle 6 wichtigen JavaScript-Dateien nutzen dieselbe Validierungslogik
|
||||||
|
2. ✅ **Verbesserte Sicherheit**: Content-Type-Prüfung und HTML-Injection-Schutz implementiert
|
||||||
|
3. ✅ **Besseres Debugging**: Kontextuelle Fehlermeldungen und detailliertes Logging
|
||||||
|
4. ✅ **Robuste Fehlerbehandlung**: Behandlung aller wichtigen HTTP-Status-Codes
|
||||||
|
5. ✅ **Entwicklerfreundlichkeit**: Einfache Integration und Wiederverwendbarkeit
|
||||||
|
|
||||||
|
### Langfristige Vorteile
|
||||||
|
|
||||||
|
1. **Wartbarkeit**: Zentrale Änderungen an Validierungslogik möglich
|
||||||
|
2. **Skalierbarkeit**: Neue JavaScript-Dateien können einfach integriert werden
|
||||||
|
3. **Qualitätssicherung**: Konsistente Error-Handling-Standards
|
||||||
|
4. **Performance**: Optimierte Fehlerbehandlung reduziert unnötige Requests
|
||||||
|
5. **Benutzererfahrung**: Klare, verständliche Fehlermeldungen
|
||||||
|
|
||||||
|
### Empfehlungen für die Zukunft
|
||||||
|
|
||||||
|
1. **Monitoring**: Implementierung von Error-Tracking und Success-Rate-Monitoring
|
||||||
|
2. **Testing**: Automatisierte Tests für validateApiResponse() Funktion
|
||||||
|
3. **Documentation**: Entwickler-Guidelines für neue API-Integrationen
|
||||||
|
4. **Training**: Team-Schulung zu korrekter Verwendung der Validierungsfunktion
|
||||||
|
5. **Evolution**: Regelmäßige Reviews und Updates basierend auf Real-World-Usage
|
||||||
|
|
||||||
|
Die systematische Implementierung der zentralen API-Response-Validierung stellt einen wichtigen Meilenstein für die Robustheit und Wartbarkeit des MYP-Systems dar. Die einheitliche Fehlerbehandlung verbessert sowohl die Entwicklererfahrung als auch die Benutzererfahrung erheblich.
|
372
backend/static/js/api-validation-test.js
Normal file
372
backend/static/js/api-validation-test.js
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
/**
|
||||||
|
* Test-Script für API-Response-Validierung
|
||||||
|
* Dieses Script testet die validateApiResponse() Funktion in verschiedenen Szenarien
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Globale Test-Ergebnisse
|
||||||
|
window.apiValidationTestResults = {
|
||||||
|
passed: 0,
|
||||||
|
failed: 0,
|
||||||
|
tests: []
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock Response für Tests erstellen
|
||||||
|
*/
|
||||||
|
function createMockResponse(body, status = 200, headers = {}) {
|
||||||
|
const defaultHeaders = {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
...headers
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Response(body, {
|
||||||
|
status: status,
|
||||||
|
statusText: getStatusText(status),
|
||||||
|
headers: defaultHeaders
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusText(status) {
|
||||||
|
const statusTexts = {
|
||||||
|
200: 'OK',
|
||||||
|
400: 'Bad Request',
|
||||||
|
401: 'Unauthorized',
|
||||||
|
403: 'Forbidden',
|
||||||
|
404: 'Not Found',
|
||||||
|
429: 'Too Many Requests',
|
||||||
|
500: 'Internal Server Error',
|
||||||
|
503: 'Service Unavailable'
|
||||||
|
};
|
||||||
|
return statusTexts[status] || 'Unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test-Framework Funktionen
|
||||||
|
*/
|
||||||
|
function assert(condition, message) {
|
||||||
|
if (condition) {
|
||||||
|
console.log(`✅ PASS: ${message}`);
|
||||||
|
window.apiValidationTestResults.passed++;
|
||||||
|
window.apiValidationTestResults.tests.push({ test: message, result: 'PASS' });
|
||||||
|
} else {
|
||||||
|
console.error(`❌ FAIL: ${message}`);
|
||||||
|
window.apiValidationTestResults.failed++;
|
||||||
|
window.apiValidationTestResults.tests.push({ test: message, result: 'FAIL' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function assertThrows(asyncFn, expectedErrorPattern, message) {
|
||||||
|
try {
|
||||||
|
await asyncFn();
|
||||||
|
console.error(`❌ FAIL: ${message} - Expected error but none was thrown`);
|
||||||
|
window.apiValidationTestResults.failed++;
|
||||||
|
window.apiValidationTestResults.tests.push({ test: message, result: 'FAIL - No error thrown' });
|
||||||
|
} catch (error) {
|
||||||
|
if (expectedErrorPattern.test(error.message)) {
|
||||||
|
console.log(`✅ PASS: ${message} - Correct error: ${error.message}`);
|
||||||
|
window.apiValidationTestResults.passed++;
|
||||||
|
window.apiValidationTestResults.tests.push({ test: message, result: 'PASS' });
|
||||||
|
} else {
|
||||||
|
console.error(`❌ FAIL: ${message} - Wrong error: ${error.message}`);
|
||||||
|
window.apiValidationTestResults.failed++;
|
||||||
|
window.apiValidationTestResults.tests.push({ test: message, result: `FAIL - Wrong error: ${error.message}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test-Funktion für validateApiResponse
|
||||||
|
* Diese Funktion erwartet, dass validateApiResponse global verfügbar ist
|
||||||
|
*/
|
||||||
|
async function testValidateApiResponse() {
|
||||||
|
console.log('🧪 Starte API-Response-Validierung Tests...\n');
|
||||||
|
|
||||||
|
// Test 1: Erfolgreiche JSON-Response
|
||||||
|
console.log('📋 Test 1: Erfolgreiche JSON-Response');
|
||||||
|
try {
|
||||||
|
const mockResponse = createMockResponse(
|
||||||
|
JSON.stringify({ success: true, data: { message: 'Hello World' } })
|
||||||
|
);
|
||||||
|
const result = await validateApiResponse(mockResponse, 'Test 1');
|
||||||
|
assert(result.success === true, 'JSON-Response korrekt geparst');
|
||||||
|
assert(result.data.message === 'Hello World', 'Daten korrekt extrahiert');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ FAIL: Test 1 - Unexpected error: ${error.message}`);
|
||||||
|
window.apiValidationTestResults.failed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: HTTP 404 Fehler
|
||||||
|
console.log('\n📋 Test 2: HTTP 404 Fehler');
|
||||||
|
await assertThrows(
|
||||||
|
() => validateApiResponse(createMockResponse('Not Found', 404), 'Test 2'),
|
||||||
|
/Ressource nicht gefunden \(Test 2\)/,
|
||||||
|
'HTTP 404 korrekt behandelt'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 3: HTTP 401 Fehler
|
||||||
|
console.log('\n📋 Test 3: HTTP 401 Fehler');
|
||||||
|
await assertThrows(
|
||||||
|
() => validateApiResponse(createMockResponse('Unauthorized', 401), 'Test 3'),
|
||||||
|
/Authentifizierung fehlgeschlagen \(Test 3\)/,
|
||||||
|
'HTTP 401 korrekt behandelt'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 4: HTTP 500 Fehler
|
||||||
|
console.log('\n📋 Test 4: HTTP 500 Fehler');
|
||||||
|
await assertThrows(
|
||||||
|
() => validateApiResponse(createMockResponse('Internal Server Error', 500), 'Test 4'),
|
||||||
|
/Serverfehler \(Test 4\)/,
|
||||||
|
'HTTP 500 korrekt behandelt'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 5: Falscher Content-Type
|
||||||
|
console.log('\n📋 Test 5: Falscher Content-Type');
|
||||||
|
await assertThrows(
|
||||||
|
() => validateApiResponse(
|
||||||
|
createMockResponse('Plain text response', 200, { 'content-type': 'text/plain' }),
|
||||||
|
'Test 5'
|
||||||
|
),
|
||||||
|
/Ungültiger Content-Type.*Test 5/,
|
||||||
|
'Falscher Content-Type korrekt behandelt'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 6: HTML-Fehlerseite
|
||||||
|
console.log('\n📋 Test 6: HTML-Fehlerseite');
|
||||||
|
await assertThrows(
|
||||||
|
() => validateApiResponse(
|
||||||
|
createMockResponse('<!DOCTYPE html><html><body>Error</body></html>', 200, { 'content-type': 'text/html' }),
|
||||||
|
'Test 6'
|
||||||
|
),
|
||||||
|
/Server-Fehlerseite erhalten statt JSON-Response \(Test 6\)/,
|
||||||
|
'HTML-Fehlerseite korrekt erkannt'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 7: Ungültiges JSON
|
||||||
|
console.log('\n📋 Test 7: Ungültiges JSON');
|
||||||
|
await assertThrows(
|
||||||
|
() => validateApiResponse(
|
||||||
|
createMockResponse('{ invalid json }'),
|
||||||
|
'Test 7'
|
||||||
|
),
|
||||||
|
/Ungültige JSON-Response.*Test 7/,
|
||||||
|
'Ungültiges JSON korrekt behandelt'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 8: Leere Response
|
||||||
|
console.log('\n📋 Test 8: Leere Response (null)');
|
||||||
|
await assertThrows(
|
||||||
|
() => validateApiResponse(
|
||||||
|
createMockResponse('null'),
|
||||||
|
'Test 8'
|
||||||
|
),
|
||||||
|
/Leere Response erhalten \(Test 8\)/,
|
||||||
|
'Null-Response korrekt behandelt'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 9: API-Fehler mit success: false
|
||||||
|
console.log('\n📋 Test 9: API-Fehler mit success: false');
|
||||||
|
await assertThrows(
|
||||||
|
() => validateApiResponse(
|
||||||
|
createMockResponse(JSON.stringify({ success: false, error: 'Validation failed' })),
|
||||||
|
'Test 9'
|
||||||
|
),
|
||||||
|
/API-Fehler: Validation failed \(Test 9\)/,
|
||||||
|
'API-Fehler mit success: false korrekt behandelt'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 10: JSON ohne success-Feld (sollte funktionieren)
|
||||||
|
console.log('\n📋 Test 10: JSON ohne success-Feld');
|
||||||
|
try {
|
||||||
|
const mockResponse = createMockResponse(
|
||||||
|
JSON.stringify({ data: [1, 2, 3], total: 3 })
|
||||||
|
);
|
||||||
|
const result = await validateApiResponse(mockResponse, 'Test 10');
|
||||||
|
assert(Array.isArray(result.data), 'JSON ohne success-Feld korrekt verarbeitet');
|
||||||
|
assert(result.total === 3, 'Daten korrekt extrahiert');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ FAIL: Test 10 - Unexpected error: ${error.message}`);
|
||||||
|
window.apiValidationTestResults.failed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ergebnisse ausgeben
|
||||||
|
console.log('\n🏁 Test-Ergebnisse:');
|
||||||
|
console.log(`✅ Erfolgreiche Tests: ${window.apiValidationTestResults.passed}`);
|
||||||
|
console.log(`❌ Fehlgeschlagene Tests: ${window.apiValidationTestResults.failed}`);
|
||||||
|
console.log(`📊 Erfolgsrate: ${((window.apiValidationTestResults.passed / (window.apiValidationTestResults.passed + window.apiValidationTestResults.failed)) * 100).toFixed(1)}%`);
|
||||||
|
|
||||||
|
return window.apiValidationTestResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Live-Tests gegen echte API-Endpunkte
|
||||||
|
*/
|
||||||
|
async function testLiveAPI() {
|
||||||
|
console.log('\n🌐 Starte Live-API-Tests...\n');
|
||||||
|
|
||||||
|
// API Base URL Detection (wie in den anderen Dateien)
|
||||||
|
function detectApiBaseUrl() {
|
||||||
|
const currentPort = window.location.port;
|
||||||
|
const currentProtocol = window.location.protocol;
|
||||||
|
const currentHost = window.location.hostname;
|
||||||
|
|
||||||
|
if (currentPort === '5000') {
|
||||||
|
return `${currentProtocol}//${currentHost}:${currentPort}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPort === '443' || currentPort === '') {
|
||||||
|
return `${currentProtocol}//${currentHost}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.location.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiBaseUrl = detectApiBaseUrl();
|
||||||
|
|
||||||
|
// Test 1: Live-API-Test - Stats-Endpunkt
|
||||||
|
console.log('📋 Live-Test 1: Stats-Endpunkt');
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${apiBaseUrl}/api/stats`);
|
||||||
|
const data = await validateApiResponse(response, 'Live-Stats-Test');
|
||||||
|
assert(typeof data === 'object', 'Stats-API liefert gültiges JSON');
|
||||||
|
console.log('📊 Stats-Daten erhalten:', Object.keys(data));
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`⚠️ Live-Test 1 fehlgeschlagen (erwartet bei fehlendem Auth): ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Live-API-Test - Nicht existierender Endpunkt (404)
|
||||||
|
console.log('\n📋 Live-Test 2: Nicht existierender Endpunkt');
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${apiBaseUrl}/api/nonexistent-endpoint-12345`);
|
||||||
|
await validateApiResponse(response, 'Live-404-Test');
|
||||||
|
console.error('❌ FAIL: 404-Test - Sollte einen Fehler werfen');
|
||||||
|
} catch (error) {
|
||||||
|
if (error.message.includes('404') || error.message.includes('nicht gefunden')) {
|
||||||
|
console.log('✅ PASS: 404-Fehler korrekt behandelt');
|
||||||
|
} else {
|
||||||
|
console.log(`✅ PASS: Fehler korrekt behandelt: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n🌐 Live-API-Tests abgeschlossen');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration-Tests für spezifische JavaScript-Dateien
|
||||||
|
*/
|
||||||
|
async function testIntegration() {
|
||||||
|
console.log('\n🔗 Starte Integration-Tests...\n');
|
||||||
|
|
||||||
|
// Test, ob validateApiResponse in verschiedenen Kontexten verfügbar ist
|
||||||
|
const testFiles = [
|
||||||
|
{ name: 'Admin Dashboard', check: () => window.adminDashboard && typeof window.adminDashboard.validateApiResponse === 'function' },
|
||||||
|
{ name: 'Printer Monitor', check: () => window.printerMonitor && typeof window.printerMonitor.validateApiResponse === 'function' },
|
||||||
|
{ name: 'Global validateApiResponse', check: () => typeof window.validateApiResponse === 'function' || typeof validateApiResponse === 'function' }
|
||||||
|
];
|
||||||
|
|
||||||
|
testFiles.forEach(test => {
|
||||||
|
try {
|
||||||
|
const isAvailable = test.check();
|
||||||
|
assert(isAvailable, `${test.name} - validateApiResponse verfügbar`);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`⚠️ ${test.name} - Nicht geladen oder verfügbar: ${error.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n🔗 Integration-Tests abgeschlossen');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performance-Tests
|
||||||
|
*/
|
||||||
|
async function testPerformance() {
|
||||||
|
console.log('\n⚡ Starte Performance-Tests...\n');
|
||||||
|
|
||||||
|
const iterations = 100;
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
for (let i = 0; i < iterations; i++) {
|
||||||
|
const mockResponse = createMockResponse(
|
||||||
|
JSON.stringify({ success: true, data: { iteration: i } })
|
||||||
|
);
|
||||||
|
await validateApiResponse(mockResponse, `Performance-Test-${i}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
const totalTime = endTime - startTime;
|
||||||
|
const avgTime = totalTime / iterations;
|
||||||
|
|
||||||
|
console.log(`📊 Performance-Ergebnisse:`);
|
||||||
|
console.log(` Gesamtzeit für ${iterations} Calls: ${totalTime.toFixed(2)}ms`);
|
||||||
|
console.log(` Durchschnittszeit pro Call: ${avgTime.toFixed(2)}ms`);
|
||||||
|
console.log(` Overhead pro Call: ${avgTime < 1 ? 'Sehr niedrig' : avgTime < 5 ? 'Niedrig' : 'Hoch'}`);
|
||||||
|
|
||||||
|
assert(avgTime < 5, 'Performance-Test - Durchschnittszeit unter 5ms');
|
||||||
|
|
||||||
|
console.log('\n⚡ Performance-Tests abgeschlossen');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Haupt-Test-Runner
|
||||||
|
*/
|
||||||
|
async function runAllTests() {
|
||||||
|
console.clear();
|
||||||
|
console.log('🚀 API-Response-Validierung Test-Suite\n');
|
||||||
|
console.log('=' * 50 + '\n');
|
||||||
|
|
||||||
|
// Reset Test-Ergebnisse
|
||||||
|
window.apiValidationTestResults = { passed: 0, failed: 0, tests: [] };
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Basis-Tests
|
||||||
|
await testValidateApiResponse();
|
||||||
|
|
||||||
|
// Live-API-Tests
|
||||||
|
await testLiveAPI();
|
||||||
|
|
||||||
|
// Integration-Tests
|
||||||
|
await testIntegration();
|
||||||
|
|
||||||
|
// Performance-Tests
|
||||||
|
await testPerformance();
|
||||||
|
|
||||||
|
// Finale Statistiken
|
||||||
|
console.log('\n' + '=' * 50);
|
||||||
|
console.log('🏆 FINALE TEST-ERGEBNISSE:');
|
||||||
|
console.log(`✅ Gesamt erfolgreich: ${window.apiValidationTestResults.passed}`);
|
||||||
|
console.log(`❌ Gesamt fehlgeschlagen: ${window.apiValidationTestResults.failed}`);
|
||||||
|
|
||||||
|
const total = window.apiValidationTestResults.passed + window.apiValidationTestResults.failed;
|
||||||
|
const successRate = total > 0 ? (window.apiValidationTestResults.passed / total * 100).toFixed(1) : 0;
|
||||||
|
console.log(`📊 Erfolgsrate: ${successRate}%`);
|
||||||
|
|
||||||
|
if (window.apiValidationTestResults.failed === 0) {
|
||||||
|
console.log('🎉 ALLE TESTS ERFOLGREICH!');
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ Einige Tests sind fehlgeschlagen. Siehe Details oben.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.apiValidationTestResults;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('💥 Test-Suite Fehler:', error);
|
||||||
|
return { error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Globale Funktionen verfügbar machen
|
||||||
|
window.runAllTests = runAllTests;
|
||||||
|
window.testValidateApiResponse = testValidateApiResponse;
|
||||||
|
window.testLiveAPI = testLiveAPI;
|
||||||
|
window.testIntegration = testIntegration;
|
||||||
|
window.testPerformance = testPerformance;
|
||||||
|
|
||||||
|
// Auto-Start wenn Test-Parameter in URL
|
||||||
|
if (window.location.search.includes('run-api-tests=true')) {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
setTimeout(runAllTests, 1000); // 1 Sekunde warten bis alles geladen ist
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📝 API-Validation-Test-Suite geladen');
|
||||||
|
console.log('💡 Verwendung: runAllTests() in der Browser-Console ausführen');
|
||||||
|
console.log('💡 Oder URL-Parameter ?run-api-tests=true verwenden für Auto-Start');
|
@@ -189,11 +189,7 @@ function getDefaultChartOptions() {
|
|||||||
async function createJobStatusChart() {
|
async function createJobStatusChart() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/stats/charts/job-status`);
|
const response = await fetch(`${API_BASE_URL}/api/stats/charts/job-status`);
|
||||||
const data = await response.json();
|
const data = await validateApiResponse(response, 'Job-Status-Chart-Daten');
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.error || 'Fehler beim Laden der Job-Status-Daten');
|
|
||||||
}
|
|
||||||
|
|
||||||
const ctx = document.getElementById('job-status-chart');
|
const ctx = document.getElementById('job-status-chart');
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
@@ -254,11 +250,7 @@ async function createJobStatusChart() {
|
|||||||
async function createPrinterUsageChart() {
|
async function createPrinterUsageChart() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/stats/charts/printer-usage`);
|
const response = await fetch(`${API_BASE_URL}/api/stats/charts/printer-usage`);
|
||||||
const data = await response.json();
|
const data = await validateApiResponse(response, 'Drucker-Nutzung-Chart-Daten');
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.error || 'Fehler beim Laden der Drucker-Nutzung-Daten');
|
|
||||||
}
|
|
||||||
|
|
||||||
const ctx = document.getElementById('printer-usage-chart');
|
const ctx = document.getElementById('printer-usage-chart');
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
@@ -294,11 +286,7 @@ async function createPrinterUsageChart() {
|
|||||||
async function createJobsTimelineChart() {
|
async function createJobsTimelineChart() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/stats/charts/jobs-timeline`);
|
const response = await fetch(`${API_BASE_URL}/api/stats/charts/jobs-timeline`);
|
||||||
const data = await response.json();
|
const data = await validateApiResponse(response, 'Jobs-Timeline-Chart-Daten');
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.error || 'Fehler beim Laden der Jobs-Timeline-Daten');
|
|
||||||
}
|
|
||||||
|
|
||||||
const ctx = document.getElementById('jobs-timeline-chart');
|
const ctx = document.getElementById('jobs-timeline-chart');
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
@@ -342,11 +330,7 @@ async function createJobsTimelineChart() {
|
|||||||
async function createUserActivityChart() {
|
async function createUserActivityChart() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/stats/charts/user-activity');
|
const response = await fetch('/api/stats/charts/user-activity');
|
||||||
const data = await response.json();
|
const data = await validateApiResponse(response, 'Benutzer-Aktivität-Chart-Daten');
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.error || 'Fehler beim Laden der Benutzer-Aktivität-Daten');
|
|
||||||
}
|
|
||||||
|
|
||||||
const ctx = document.getElementById('user-activity-chart');
|
const ctx = document.getElementById('user-activity-chart');
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
Reference in New Issue
Block a user