Testing & Debugging Guide
๐งช Test-Umgebungโ
Test-Systemโ
PunchFlow bietet Test-Mรถglichkeiten mit Test-API-Keys:
API: https://api.punchflow.de/api/v1
Features:
- Identische Funktionalitรคt wie Produktion
- Test-API-Keys fรผr sichere Tests
- Keine echten Transaktionen mit Test-Keys
- Test-Daten und Mock-Responses
Test-Credentialsโ
// Test API-Key (von Ihrem Dashboard)
const API_KEY = 'test_pk_YOUR_TEST_KEY';
// Test-Shop Zugangsdaten (Ihre eigenen Test-Shops)
const TEST_SHOPS = {
shopware: {
url: 'https://ihr-test-shop.de',
client_id: 'your_test_client_id',
client_secret: 'your_test_secret'
},
woocommerce: {
url: 'https://ihr-test-shop.de',
key: 'ck_your_test_key',
secret: 'cs_your_test_secret'
}
};
// Test ERP-System (Ihr Test-System)
const TEST_ERP = {
return_url: 'https://ihr-erp.de/return',
buyer: 'test.buyer@example.com',
cookie: 'TEST_COOKIE_123'
};
๐ฌ Test-Szenarienโ
1. Basis-Verbindungstestโ
#!/bin/bash
# test-connection.sh
echo "Testing PunchFlow Connection..."
# Health Check
curl -X GET https://api.punchflow.de/api/v1/health
# API Key Validation
curl -X GET https://api.punchflow.de/api/v1/validate \
-H "Authorization: Bearer $API_KEY"
# Shop Connection Test
curl -X POST https://api.punchflow.de/api/v1/shops/test \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"shop_type": "shopware6",
"config": {
"url": "https://demo.shopware.com",
"client_id": "test_id",
"client_secret": "test_secret"
}
}'
2. cXML PunchOut Testโ
Setup Request Testโ
<!-- test-setup-request.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE cXML SYSTEM "http://xml.cxml.org/schemas/cXML/1.2.014/cXML.dtd">
<cXML payloadID="test_${timestamp}" timestamp="${timestamp}">
<Header>
<From>
<Credential domain="NetworkId">
<Identity>test.buyer@example.com</Identity>
</Credential>
</From>
<To>
<Credential domain="DUNS">
<Identity>123456789</Identity>
</Credential>
</To>
<Sender>
<Credential domain="NetworkId">
<Identity>test.buyer@example.com</Identity>
<SharedSecret>TestSecret123</SharedSecret>
</Credential>
</Sender>
</Header>
<Request>
<PunchOutSetupRequest operation="create">
<BuyerCookie>TEST_COOKIE_${random}</BuyerCookie>
<Extrinsic name="UserEmail">john.doe@testcompany.com</Extrinsic>
<Extrinsic name="UniqueName">John Doe</Extrinsic>
<Extrinsic name="CompanyCode">TEST001</Extrinsic>
<BrowserFormPost>
<URL>https://ihr-erp.de/cxml/return</URL>
</BrowserFormPost>
</PunchOutSetupRequest>
</Request>
</cXML>
Test-Scriptโ
# test_cxml_punchout.py
import requests
import xml.etree.ElementTree as ET
from datetime import datetime
import uuid
def test_cxml_setup():
# Timestamp und PayloadID generieren
timestamp = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S+00:00')
payload_id = str(uuid.uuid4())
# XML erstellen
xml_template = open('test-setup-request.xml').read()
xml_data = xml_template.replace('${timestamp}', timestamp)
xml_data = xml_data.replace('${random}', str(uuid.uuid4())[:8])
# Request senden
response = requests.post(
'https://api.punchflow.de/api/v1/punchout/setup',
headers={
'Content-Type': 'application/xml',
'Authorization': f'Bearer {API_KEY}'
},
data=xml_data
)
# Response validieren
assert response.status_code == 200
# XML parsen
root = ET.fromstring(response.text)
status = root.find('.//Status')
assert status.get('code') == '200'
# Session URL extrahieren
url = root.find('.//StartPage/URL')
print(f"Session URL: {url.text}")
return url.text
if __name__ == '__main__':
session_url = test_cxml_setup()
print(f"โ
Test erfolgreich! Session: {session_url}")
3. OCI PunchOut Testโ
# test_oci_punchout.py
import requests
from urllib.parse import urlencode
def test_oci_setup():
params = {
'HOOK_URL': 'https://ihr-erp.de/oci/return',
'USERNAME': 'test.user',
'PASSWORD': 'test.pass',
'FUNCTION': 'CREATE_SESSION',
'LANGUAGE': 'de',
'CURRENCY': 'EUR'
}
response = requests.post(
'https://api.punchflow.de/api/v1/punchout/oci/setup',
headers={
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': f'Bearer {API_KEY}'
},
data=urlencode(params)
)
assert response.status_code == 200
assert 'location' in response.headers
session_url = response.headers['location']
print(f"โ
OCI Session created: {session_url}")
return session_url
def test_oci_transfer():
items = {
'NEW_ITEM-DESCRIPTION[1]': 'Test Product 1',
'NEW_ITEM-QUANTITY[1]': '2',
'NEW_ITEM-UNIT[1]': 'PCE',
'NEW_ITEM-PRICE[1]': '99.99',
'NEW_ITEM-CURRENCY[1]': 'EUR',
'NEW_ITEM-VENDORMAT[1]': 'SKU-001',
'NEW_ITEM-DESCRIPTION[2]': 'Test Product 2',
'NEW_ITEM-QUANTITY[2]': '1',
'NEW_ITEM-UNIT[2]': 'PCE',
'NEW_ITEM-PRICE[2]': '149.99',
'NEW_ITEM-CURRENCY[2]': 'EUR',
'NEW_ITEM-VENDORMAT[2]': 'SKU-002'
}
response = requests.post(
'https://ihr-erp.de/oci/return',
data=urlencode(items)
)
assert response.status_code == 200
print(f"โ
OCI Transfer successful")
if __name__ == '__main__':
session_url = test_oci_setup()
test_oci_transfer()
4. End-to-End Testโ
// e2e-test.js
const puppeteer = require('puppeteer');
const assert = require('assert');
async function testPunchOutFlow() {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
try {
// 1. Setup Request simulieren
console.log('๐ Sending Setup Request...');
const setupResponse = await fetch('https://api.punchflow.de/api/v1/punchout/setup', {
method: 'POST',
headers: {
'Content-Type': 'application/xml',
'Authorization': `Bearer ${API_KEY}`
},
body: getSetupRequestXML()
});
const responseXML = await setupResponse.text();
const sessionUrl = extractSessionUrl(responseXML);
console.log(`โ
Session created: ${sessionUrl}`);
// 2. Shop รถffnen
console.log('๐ Opening shop...');
await page.goto(sessionUrl);
await page.waitForSelector('.product-grid');
// 3. Produkte zum Warenkorb hinzufรผgen
console.log('โ Adding products to cart...');
await page.click('.product:first-child .add-to-cart');
await page.waitForTimeout(1000);
await page.click('.product:nth-child(2) .add-to-cart');
await page.waitForTimeout(1000);
// 4. Warenkorb prรผfen
console.log('๐ Checking cart...');
await page.click('.cart-icon');
await page.waitForSelector('.cart-items');
const itemCount = await page.$$eval('.cart-item', items => items.length);
assert(itemCount === 2, 'Cart should contain 2 items');
// 5. Transfer initiieren
console.log('๐ค Transferring cart...');
await page.click('#punchout-transfer');
await page.waitForNavigation();
// 6. Verify return to ERP
const finalUrl = page.url();
assert(finalUrl.includes('ihr-erp.de'), 'Should return to ERP');
console.log('โ
E2E Test completed successfully!');
} catch (error) {
console.error('โ Test failed:', error);
await page.screenshot({ path: 'error-screenshot.png' });
throw error;
} finally {
await browser.close();
}
}
testPunchOutFlow();
๐ Debuggingโ
Debug-Modus aktivierenโ
// Debug-Logging in Ihrer Anwendung
const DEBUG = true;
// Request-Logging
async function apiRequest(endpoint, options = {}) {
const url = `https://api.punchflow.de/api/v1${endpoint}`;
if (DEBUG) {
console.log('[REQUEST]', {
url,
method: options.method || 'GET',
headers: options.headers
});
}
const response = await fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
...options.headers
}
});
const data = await response.json();
if (DEBUG) {
console.log('[RESPONSE]', {
status: response.status,
data
});
}
return data;
}
Request/Response Loggingโ
# debug_logger.py
import logging
from datetime import datetime
import json
class PunchFlowDebugger:
def __init__(self, log_file='punchout_debug.log'):
logging.basicConfig(
filename=log_file,
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)
self.logger = logging.getLogger(__name__)
def log_request(self, method, url, headers, body):
self.logger.debug(f"""
========== REQUEST ==========
Method: {method}
URL: {url}
Headers: {json.dumps(headers, indent=2)}
Body: {body[:1000] if body else 'None'}
Timestamp: {datetime.now().isoformat()}
============================
""")
def log_response(self, status_code, headers, body):
self.logger.debug(f"""
========== RESPONSE ==========
Status: {status_code}
Headers: {json.dumps(dict(headers), indent=2)}
Body: {body[:1000] if body else 'None'}
Timestamp: {datetime.now().isoformat()}
==============================
""")
def log_error(self, error):
self.logger.error(f"""
========== ERROR ==========
Error: {str(error)}
Type: {type(error).__name__}
Timestamp: {datetime.now().isoformat()}
==========================
""")
Browser DevTools Integrationโ
// browser-debug.js
class PunchOutDebugger {
constructor() {
this.logs = [];
this.setupInterceptors();
}
setupInterceptors() {
// XMLHttpRequest interceptor
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url) {
this._punchflow = { method, url, startTime: Date.now() };
originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(body) {
if (this._punchflow) {
this._punchflow.body = body;
this.addEventListener('load', () => {
const duration = Date.now() - this._punchflow.startTime;
window.punchflowDebugger.log({
type: 'xhr',
method: this._punchflow.method,
url: this._punchflow.url,
status: this.status,
duration,
requestBody: this._punchflow.body,
responseBody: this.responseText
});
});
}
originalSend.apply(this, arguments);
};
// Fetch API interceptor
const originalFetch = window.fetch;
window.fetch = async function(...args) {
const startTime = Date.now();
const [url, options = {}] = args;
try {
const response = await originalFetch.apply(this, args);
const duration = Date.now() - startTime;
window.punchflowDebugger.log({
type: 'fetch',
url,
method: options.method || 'GET',
status: response.status,
duration
});
return response;
} catch (error) {
window.punchflowDebugger.log({
type: 'fetch-error',
url,
error: error.message
});
throw error;
}
};
}
log(entry) {
entry.timestamp = new Date().toISOString();
this.logs.push(entry);
// Console output
console.group(`๐ PunchFlow ${entry.type}`);
console.log(entry);
console.groupEnd();
// Send to debug panel
this.updateDebugPanel(entry);
}
updateDebugPanel(entry) {
// DevTools panel update
if (window.__PUNCHFLOW_DEVTOOLS__) {
window.__PUNCHFLOW_DEVTOOLS__.addEntry(entry);
}
}
exportLogs() {
const blob = new Blob([JSON.stringify(this.logs, null, 2)],
{ type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `punchflow-debug-${Date.now()}.json`;
a.click();
}
}
// Initialize debugger
window.punchflowDebugger = new PunchOutDebugger();
๐ Validierungโ
XML-Validierungโ
# validate_xml.py
import xmlschema
from lxml import etree
class XMLValidator:
def __init__(self):
self.cxml_schema = xmlschema.XMLSchema('cxml-1.2.014.xsd')
def validate_cxml(self, xml_string):
"""Validate cXML document against schema"""
try:
# Parse XML
doc = etree.fromstring(xml_string)
# Schema validation
self.cxml_schema.validate(doc)
# Business rules validation
errors = self.validate_business_rules(doc)
if errors:
return False, errors
return True, "Valid cXML document"
except xmlschema.XMLSchemaException as e:
return False, f"Schema validation failed: {e}"
except Exception as e:
return False, f"Validation error: {e}"
def validate_business_rules(self, doc):
errors = []
# Check required fields
buyer_cookie = doc.find('.//BuyerCookie')
if buyer_cookie is None or not buyer_cookie.text:
errors.append("BuyerCookie is required")
# Check return URL
return_url = doc.find('.//BrowserFormPost/URL')
if return_url is None or not return_url.text:
errors.append("Return URL is required")
# Check credentials
identity = doc.find('.//Sender/Credential/Identity')
if identity is None or not identity.text:
errors.append("Sender identity is required")
return errors
OCI Parameter Validierungโ
// validate-oci.js
function validateOCIParameters(params) {
const errors = [];
// Required parameters
const required = ['HOOK_URL', 'USERNAME', 'PASSWORD'];
for (const field of required) {
if (!params[field]) {
errors.push(`Missing required parameter: ${field}`);
}
}
// Validate URL format
if (params.HOOK_URL && !isValidUrl(params.HOOK_URL)) {
errors.push(`Invalid HOOK_URL format: ${params.HOOK_URL}`);
}
// Validate items
const itemPattern = /^NEW_ITEM-(\w+)\[(\d+)\]$/;
const items = {};
for (const [key, value] of Object.entries(params)) {
const match = key.match(itemPattern);
if (match) {
const [, field, index] = match;
if (!items[index]) items[index] = {};
items[index][field] = value;
}
}
// Check item completeness
for (const [index, item] of Object.entries(items)) {
if (!item.DESCRIPTION) {
errors.push(`Item ${index}: Missing DESCRIPTION`);
}
if (!item.QUANTITY || isNaN(item.QUANTITY)) {
errors.push(`Item ${index}: Invalid QUANTITY`);
}
if (!item.PRICE || isNaN(item.PRICE)) {
errors.push(`Item ${index}: Invalid PRICE`);
}
}
return {
valid: errors.length === 0,
errors,
itemCount: Object.keys(items).length
};
}
๐ Performance Testingโ
Load Testing mit k6โ
// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';
const errorRate = new Rate('errors');
export let options = {
stages: [
{ duration: '2m', target: 10 }, // Ramp up
{ duration: '5m', target: 50 }, // Stay at 50
{ duration: '2m', target: 100 }, // Spike
{ duration: '5m', target: 50 }, // Back to normal
{ duration: '2m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<3000'], // 95% unter 3s
errors: ['rate<0.1'], // Fehlerrate unter 10%
},
};
export default function() {
// Setup Request
const setupPayload = getSetupRequestXML();
const setupResponse = http.post(
'https://api.punchflow.de/api/v1/punchout/setup',
setupPayload,
{
headers: {
'Content-Type': 'application/xml',
'Authorization': `Bearer ${API_KEY}`,
},
}
);
const setupSuccess = check(setupResponse, {
'Setup status is 200': (r) => r.status === 200,
'Setup returns session URL': (r) => r.body.includes('<URL>'),
});
errorRate.add(!setupSuccess);
sleep(1);
// Simulate shopping
if (setupSuccess) {
const sessionUrl = extractSessionUrl(setupResponse.body);
// Browse products
const browseResponse = http.get(sessionUrl);
check(browseResponse, {
'Browse status is 200': (r) => r.status === 200,
});
sleep(2);
// Transfer cart
const transferResponse = http.post(
'https://api.punchflow.de/api/v1/punchout/transfer',
getTransferPayload(),
{
headers: {
'Content-Type': 'application/xml',
'Authorization': `Bearer ${API_KEY}`,
},
}
);
check(transferResponse, {
'Transfer status is 200': (r) => r.status === 200,
});
}
sleep(1);
}
๐ ๏ธ Troubleshooting Toolsโ
API Testing mit cURLโ
# Verbindung testen
curl -X GET https://api.punchflow.de/api/v1/health \
-H "Authorization: Bearer $API_KEY"
# Shop verbinden
curl -X POST https://api.punchflow.de/api/v1/shops \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"shop_type": "shopware6",
"config": {
"url": "https://ihr-shop.de",
"client_id": "xxx",
"client_secret": "yyy"
}
}'
# Session erstellen
curl -X POST https://api.punchflow.de/api/v1/punchout/setup \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/xml" \
--data-binary @request.xml
# Monitoring-Daten abrufen
curl -X GET https://api.punchflow.de/api/v1/monitoring/metrics \
-H "Authorization: Bearer $API_KEY"
Debug Dashboardโ
<!-- debug-dashboard.html -->
<!DOCTYPE html>
<html>
<head>
<title>PunchFlow Debug Dashboard</title>
<style>
.dashboard {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
padding: 20px;
}
.panel {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
}
.log-entry {
margin: 10px 0;
padding: 10px;
background: #f5f5f5;
border-radius: 4px;
}
.success { border-left: 4px solid #4caf50; }
.error { border-left: 4px solid #f44336; }
.warning { border-left: 4px solid #ff9800; }
</style>
</head>
<body>
<div class="dashboard">
<div class="panel">
<h2>Session Monitor</h2>
<div id="sessions"></div>
</div>
<div class="panel">
<h2>Request Log</h2>
<div id="requests"></div>
</div>
<div class="panel">
<h2>Performance Metrics</h2>
<canvas id="perfChart"></canvas>
</div>
<div class="panel">
<h2>Error Console</h2>
<div id="errors"></div>
</div>
</div>
<script>
// Polling for updates (WebSocket Alternative)
setInterval(async () => {
const response = await fetch('/api/v1/monitoring/metrics', {
headers: {
'Authorization': `Bearer ${API_KEY}`
}
});
const data = await response.json();
updateDashboard(data);
}, 5000); // Poll every 5 seconds
function updateDashboard(data) {
// Update appropriate panel based on data type
switch(data.type) {
case 'session':
updateSessions(data);
break;
case 'request':
updateRequests(data);
break;
case 'performance':
updatePerformance(data);
break;
case 'error':
updateErrors(data);
break;
}
}
</script>
</body>
</html>
๐ Monitoringโ
Prometheus Metricsโ
# prometheus-config.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'punchflow'
static_configs:
- targets: ['api.punchflow.de:9090']
metrics_path: '/metrics'
Grafana Dashboardโ
{
"dashboard": {
"title": "PunchFlow Testing Dashboard",
"panels": [
{
"title": "Request Rate",
"targets": [
{
"expr": "rate(punchflow_requests_total[5m])"
}
]
},
{
"title": "Error Rate",
"targets": [
{
"expr": "rate(punchflow_errors_total[5m])"
}
]
},
{
"title": "Response Time",
"targets": [
{
"expr": "histogram_quantile(0.95, punchflow_response_time_seconds)"
}
]
}
]
}
}
โ Test-Checklisteโ
Vor Go-Liveโ
- Verbindungstest erfolgreich
- cXML Setup/Response funktioniert
- OCI Setup/Transfer funktioniert
- Produktsynchronisation lรคuft
- Preise werden korrekt รผbertragen
- Kundenspezifische Preise aktiv
- Session-Timeout konfiguriert
- Error-Handling implementiert
- Logging aktiviert
- Performance akzeptabel
- Security-Headers gesetzt
- SSL-Zertifikat gรผltig
- Backup-Strategie definiert
- Monitoring eingerichtet
- Support-Kontakt hinterlegt
๐ Hilfeโ
Bei Problemen:
- Debug-Logs prรผfen
- Validation durchfรผhren
- Support kontaktieren: support@punchflow.de