OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

VendorFeedbackTest.php

7 KB
<?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);
    });
};