Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
use OpenNIT\Feedback\VendorFeedbackService as VFS;
/**
* Deckt die DB-/netzfreie Logik des Hersteller-Meldewegs ab: Aufbau des
* Anfragekörpers, Interpretation der Dienst-Antworten (201/404/422/429/503)
* und die Warteschlangen-Entscheidungen (einreihen, erneut senden, aufgeben).
*/
return static function (): void {
$tech = [
'app_version' => '0.94.25',
'current_sha' => 'abc123',
'os' => 'Linux 6.1',
'metadata' => ['php_version' => '8.2.0'],
];
// ---------------- Anfragekörper ----------------
test('Payload: Pflichtfelder, technische Angaben und severity beim Fehlerbericht', function () use ($tech) {
$p = VFS::buildPayload('bugreport', [
'title' => ' Export bricht ab ',
'body' => 'Ab etwa 5000 Zeilen bricht der Export ab.',
'email' => 'anwender@example.com',
'name' => 'Max Mustermann',
'severity' => 'high',
], $tech);
assertSame('Export bricht ab', $p['title']);
assertSame('high', $p['severity']);
assertSame('0.94.25', $p['app_version']);
assertSame('abc123', $p['current_sha']);
assertSame('Linux 6.1', $p['os']);
assertSame('anwender@example.com', $p['email']);
assertSame(['php_version' => '8.2.0'], $p['metadata']);
});
test('Payload: Funktionswunsch ohne severity, leere freiwillige Felder entfallen', function () use ($tech) {
$p = VFS::buildPayload('featurerequest', [
'title' => 'Dunkler Modus',
'body' => 'Bitte einen dunklen Modus ergänzen.',
'email' => ' ',
'severity' => 'critical',
], $tech);
assertFalse(array_key_exists('severity', $p), 'severity nur bei Fehlerberichten');
assertFalse(array_key_exists('email', $p), 'leere E-Mail wird weggelassen');
assertFalse(array_key_exists('name', $p), 'fehlender Name wird weggelassen');
});
test('Payload: Titel wird auf 255 Zeichen gekürzt, metadata auf 50 Felder begrenzt', function () use ($tech) {
$tech50 = $tech;
for ($i = 0; $i < 60; $i++) {
$tech50['metadata']['feld_' . $i] = $i;
}
$p = VFS::buildPayload('bugreport', [
'title' => str_repeat('x', 300),
'body' => 'Beschreibung mit genug Zeichen.',
], $tech50);
assertSame(255, mb_strlen($p['title']));
assertTrue(count($p['metadata']) <= 50, 'höchstens 50 metadata-Felder');
});
test('Payload: ungültige severity fällt auf normal zurück', function () use ($tech) {
$p = VFS::buildPayload('bugreport', ['title' => 'T', 'body' => 'Beschreibung xx', 'severity' => 'bogus'], $tech);
assertSame('normal', $p['severity']);
});
// ---------------- Eingabeprüfung ----------------
test('Validierung: fehlender Betreff und zu kurze Beschreibung werden je Feld gemeldet', function () {
$errors = VFS::validateInput('bugreport', ['title' => '', 'body' => 'zu kurz']);
assertTrue(isset($errors['title']));
assertTrue(isset($errors['body']));
});
test('Validierung: E-Mail ist freiwillig, aber wenn gesetzt muss sie gültig sein', function () {
assertSame([], VFS::validateInput('bugreport', ['title' => 'T', 'body' => 'zehn Zeichen mindestens']));
$errors = VFS::validateInput('bugreport', ['title' => 'T', 'body' => 'zehn Zeichen mindestens', 'email' => 'kaputt@']);
assertTrue(isset($errors['email']));
});
// ---------------- Antwort-Interpretation ----------------
test('201: angenommen, Referenz wird übernommen', function () {
$r = VFS::interpretResponse(201, ['ok' => true, 'reference' => 'BUG-8F3K2Q', 'id' => 'uuid']);
assertSame('sent', $r['outcome']);
assertSame('BUG-8F3K2Q', $r['reference']);
});
test('404: Meldeweg nicht freigeschaltet → dauerhaft ausblenden', function () {
$r = VFS::interpretResponse(404, ['code' => 'endpoint_not_enabled']);
assertSame('blocked', $r['outcome']);
});
test('422: Eingabe unvollständig, Feldfehler werden übernommen', function () {
$r = VFS::interpretResponse(422, ['code' => 'validation', 'errors' => ['body' => 'mindestens 10 Zeichen']]);
assertSame('invalid', $r['outcome']);
assertSame('mindestens 10 Zeichen', $r['field_errors']['body']);
});
test('429: Rate-Limit mit Retry-After', function () {
$r = VFS::interpretResponse(429, ['code' => 'rate_limited'], 1800);
assertSame('rate_limited', $r['outcome']);
assertSame(1800, $r['retry_after']);
});
test('429 ohne Retry-After-Header: konservativer Standardwert', function () {
$r = VFS::interpretResponse(429, null);
assertSame('rate_limited', $r['outcome']);
assertTrue($r['retry_after'] >= 60);
});
test('503 (Wartung), 409 (in Verarbeitung) und Netzausfall führen zum Wiederholen', function () {
assertSame('retry', VFS::interpretResponse(503, ['code' => 'maintenance'])['outcome']);
assertSame('retry', VFS::interpretResponse(409, ['code' => 'in_progress'])['outcome']);
assertSame('retry', VFS::interpretResponse(0, null)['outcome']);
assertSame('retry', VFS::interpretResponse(500, null)['outcome']);
});
// ---------------- Warteschlange ----------------
test('Warteschlange: vorübergehender Fehler wird mit wachsendem Abstand erneut eingereiht', function () {
$t1 = VFS::queueTransition('retry', 1);
$t2 = VFS::queueTransition('retry', 2);
assertSame('requeue', $t1['action']);
assertSame('requeue', $t2['action']);
assertTrue($t2['delay'] > $t1['delay'], 'Abstand wächst mit den Versuchen');
});
test('Warteschlange: nach fünf Versuchen wird aufgegeben', function () {
assertSame(null, VFS::retryDelaySeconds(5));
assertSame('fail', VFS::queueTransition('retry', 5)['action']);
});
test('Warteschlange: Erfolg beendet, 422/404 werden nicht wiederholt', function () {
assertSame('sent', VFS::queueTransition('sent', 1)['action']);
assertSame('fail', VFS::queueTransition('invalid', 1)['action']);
assertSame('fail', VFS::queueTransition('blocked', 1)['action']);
});
test('Warteschlange: Rate-Limit wartet die genannte Zeit ab', function () {
$t = VFS::queueTransition('rate_limited', 1, 1200);
assertSame('requeue', $t['action']);
assertSame(1200, $t['delay']);
});
// ---------------- Endpunkt-Ermittlung ----------------
test('Discovery: Endpunkt-Feld je Programm wird ausgewertet', function () {
$available = VFS::parseSoftwareIndex([
'software' => [
['slug' => 'anderes-tool', 'endpoints' => ['bugreport']],
['slug' => 'opennit', 'endpoints' => ['bugreport', 'featurerequest']],
],
]);
assertSame(['bugreport', 'featurerequest'], $available);
});
test('Discovery: nicht freigeschaltete Wege fehlen in der Liste', function () {
$available = VFS::parseSoftwareIndex([
'software' => [['slug' => 'opennit', 'endpoints' => ['bugreport']]],
]);
assertSame(['bugreport'], $available);
});
};