OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

ResponseStatusTest.php

3 KB
<?php

declare(strict_types=1);

use OpenNIT\Core\Response;

/**
 * Haelt fest, dass der HTTP-Status die Ausgabe ueberlebt.
 *
 * Fehler- und Ablehnungsseiten setzen ihren Code vor dem Rendern; wuerde die
 * Ausgabe ihn auf 200 zuruecksetzen, koennte ein Aufrufer eine abgelehnte oder
 * nicht gefundene Antwort nur noch am Seiteninhalt erkennen.
 */
return static function (): void {

    /** Liest den gesetzten Status aus der Response. */
    $status = static function (Response $r): int {
        $p = (new ReflectionProperty(Response::class, 'statusCode'));
        $p->setAccessible(true);
        return (int)$p->getValue($r);
    };

    /**
     * Fuehrt die Ausgabe aus, ohne Rumpf und header()-Warnungen in die
     * Testausgabe zu spuelen (in der CLI ist der Header-Versand nicht moeglich).
     */
    $stillAusgeben = static function (callable $fn): void {
        ob_start();
        try {
            @$fn();
        } finally {
            ob_end_clean();
        }
    };

    test('Eine HTML-Ausgabe behaelt den zuvor gesetzten Status', function () use ($status, $stillAusgeben) {
        $r = new Response();
        @$r->setStatusCode(404);
        $stillAusgeben(static fn () => $r->html('<p>nicht gefunden</p>'));

        assertSame(404, $status($r));
    });

    test('Eine JSON-Ausgabe behaelt den zuvor gesetzten Status', function () use ($status, $stillAusgeben) {
        // Betrifft die Abweisungen im Router: Ratenbegrenzung, fehlende
        // Anmeldung und erzwungene Zwei-Faktor-Einrichtung.
        foreach ([429, 401, 403] as $code) {
            $r = new Response();
            @$r->setStatusCode($code);
            $stillAusgeben(static fn () => $r->json(['error' => 'abgelehnt']));

            assertSame($code, $status($r));
        }
    });

    test('Ohne Angabe gilt weiterhin 200', function () use ($status, $stillAusgeben) {
        $r = new Response();
        $stillAusgeben(static fn () => $r->html('<p>ok</p>'));
        assertSame(200, $status($r));

        $r2 = new Response();
        $stillAusgeben(static fn () => $r2->json(['ok' => true]));
        assertSame(200, $status($r2));
    });

    test('Ein ausdruecklich uebergebener Status gewinnt', function () use ($status, $stillAusgeben) {
        $r = new Response();
        @$r->setStatusCode(404);
        $stillAusgeben(static fn () => $r->html('<p>doch ok</p>', 200));
        assertSame(200, $status($r));

        $r2 = new Response();
        $stillAusgeben(static fn () => $r2->json(['error' => 'weg'], 410));
        assertSame(410, $status($r2));
    });

    test('Der Rumpf bleibt unveraendert', function () {
        // Die Statusbehandlung darf an der Ausgabe selbst nichts aendern.
        $r = new Response();
        @$r->setStatusCode(403);
        ob_start();
        try {
            @$r->html('<p>verboten</p>');
            $rumpf = (string)ob_get_contents();
        } finally {
            ob_end_clean();
        }

        assertSame('<p>verboten</p>', $rumpf);
    });

    test('Fehlerseiten setzen ihren Status vor dem Rendern', function () {
        // Der Status muss gesetzt sein, bevor der Rumpf die Kopfzeilen abschickt.
        $code = (string)file_get_contents(dirname(__DIR__) . '/src/Controllers/DnsController.php');

        assertTrue(
            (bool)preg_match('/setStatusCode\(404\);\s*\n\s*\$this->render\(\$response, \'errors\/404\'/', $code),
            'Reihenfolge von Status und Rendern vertauscht'
        );
    });
};