OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

AuditCallSignatureTest.php

5 KB
<?php

declare(strict_types=1);

/**
 * Prueft die Reihenfolge der Argumente an jedem Aufruf des Protokolls.
 *
 * `log()` nimmt die Einzelheiten vor dem Ergebnis und die Vorher/Nachher-Werte
 * ganz am Ende. Wer die Vorher/Nachher-Werte an die Stelle der Einzelheiten
 * setzt, schiebt ein Array in den Parameter `$result` – das ist ein Typfehler,
 * der erst beim Ausfuehren auffaellt, also erst wenn jemand die Schaltflaeche
 * drueckt. Ein solcher Aufruf legt die ganze Aktion lahm, obwohl an ihr selbst
 * nichts falsch ist.
 */
return static function (): void {

    $wurzel = dirname(__DIR__);

    /**
     * Zerlegt einen Aufruf in seine Argumente auf oberster Klammerebene.
     *
     * @param  list<array{0:int,1:string,2:int}|string> $tok Token ab der oeffnenden Klammer.
     * @param  int                                      $von Index der oeffnenden Klammer.
     * @return list<string> Argumente als Quelltext.
     */
    $argumente = static function (array $tok, int $von): array {
        $tiefe = 0; $args = []; $cur = '';
        for ($p = $von, $n = count($tok); $p < $n; $p++) {
            $t   = $tok[$p];
            $txt = is_array($t) ? $t[1] : $t;
            if ($txt === '(' || $txt === '[') {
                $tiefe++;
                if ($tiefe === 1 && $txt === '(') { continue; }
            } elseif ($txt === ')' || $txt === ']') {
                $tiefe--;
                if ($tiefe === 0) { $args[] = trim($cur); break; }
            }
            if ($tiefe === 1 && $txt === ',') { $args[] = trim($cur); $cur = ''; continue; }
            if (is_array($t) && $t[0] === T_COMMENT) { continue; }
            $cur .= $txt;
        }
        return $args;
    };

    /**
     * Sammelt alle Protokoll-Aufrufe im Quelltext.
     *
     * @return list<array{datei:string,zeile:int,name:string,args:list<string>}>
     */
    $aufrufe = static function (string $wurzel) use ($argumente): array {
        $gefunden = [];
        $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($wurzel . '/src'));
        foreach ($it as $f) {
            if (!$f->isFile() || $f->getExtension() !== 'php') { continue; }
            $src = (string)file_get_contents($f->getPathname());
            $tok = token_get_all($src);
            for ($i = 0, $n = count($tok); $i < $n; $i++) {
                if (!is_array($tok[$i]) || $tok[$i][0] !== T_STRING) { continue; }
                if (!in_array($tok[$i][1], ['log', 'logWithUser'], true)) { continue; }

                $j = $i - 1;
                while ($j >= 0 && is_array($tok[$j]) && $tok[$j][0] === T_WHITESPACE) { $j--; }
                if (!(is_array($tok[$j]) && $tok[$j][0] === T_OBJECT_OPERATOR)) { continue; }

                $k = $i + 1;
                while ($k < $n && is_array($tok[$k]) && $tok[$k][0] === T_WHITESPACE) { $k++; }
                if (($tok[$k] ?? null) !== '(') { continue; }

                $gefunden[] = [
                    'datei' => str_replace($wurzel . '/', '', $f->getPathname()),
                    'zeile' => $tok[$i][2],
                    'name'  => $tok[$i][1],
                    'args'  => $argumente($tok, $k),
                ];
            }
        }
        return $gefunden;
    };

    $alle = $aufrufe($wurzel);

    test('Es gibt ueberhaupt Protokoll-Aufrufe zu pruefen', function () use ($alle) {
        // Sonst prueft der Rest stillschweigend nichts.
        assertTrue(count($alle) > 100, 'Zu wenige Aufrufe gefunden – das Suchmuster passt nicht mehr');
    });

    test('Das Ergebnis steht als Wort da, nicht als Array', function () use ($alle) {
        foreach ($alle as $a) {
            // logWithUser fuehrt die Person vorn, alles andere rutscht um eins.
            $idx = 6 + ($a['name'] === 'logWithUser' ? 1 : 0);
            if (!isset($a['args'][$idx])) { continue; }   // kuerzerer Aufruf nutzt den Standard
            $r = $a['args'][$idx];
            assertFalse(
                (bool)preg_match('/^\[|^array\(|^null$/i', $r),
                "Argumente verrutscht in {$a['datei']}:{$a['zeile']} – \$result ist {$r}"
            );
        }
    });

    test('Die Risikostufe steht als Wort da', function () use ($alle) {
        // Rutscht $result, rutscht auch die Stufe – dieselbe Ursache, zweiter Beleg.
        $stufen = ['low', 'medium', 'high', 'critical'];
        foreach ($alle as $a) {
            $idx = 7 + ($a['name'] === 'logWithUser' ? 1 : 0);
            if (!isset($a['args'][$idx])) { continue; }
            $s = trim($a['args'][$idx], "'\"");
            // Variablen und Ausdruecke lassen sich hier nicht beurteilen.
            if (!preg_match("/^'[a-z]+'$/", $a['args'][$idx])) { continue; }
            assertTrue(
                in_array($s, $stufen, true),
                "Argumente verrutscht in {$a['datei']}:{$a['zeile']} – Risikostufe ist '{$s}'"
            );
        }
    });

    test('Das Ergebnis ist eines der bekannten Worte', function () use ($alle) {
        $ergebnisse = ['success', 'failure', 'denied', 'error', 'warning', 'partial'];
        foreach ($alle as $a) {
            $idx = 6 + ($a['name'] === 'logWithUser' ? 1 : 0);
            if (!isset($a['args'][$idx])) { continue; }
            if (!preg_match("/^'[a-z_]+'$/", $a['args'][$idx])) { continue; }
            $r = trim($a['args'][$idx], "'");
            assertTrue(
                in_array($r, $ergebnisse, true),
                "Unbekanntes Ergebnis in {$a['datei']}:{$a['zeile']} – '{$r}'"
            );
        }
    });
};