OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

TeamboardTest.php

8 KB
<?php

declare(strict_types=1);

use OpenNIT\Core\AuthorColor;

/**
 * Deckt das Notizbrett ab.
 *
 * Zwei Zusagen tragen das Modul: Jede Person erscheint überall in derselben
 * Farbe, und ein Eintrag bleibt Text – aus einer Notiz darf nichts in die
 * Seite gelangen. Dazu die Verdrahtung, ohne die ein Modul zwar existiert,
 * aber nicht erreichbar ist.
 */
return static function (): void {

    $wurzel = dirname(__DIR__);

    // ── Farbe je Person ──────────────────────────────────────────────────

    test('Die ersten zwoelf Konten bekommen verschiedene Farben', function () {
        $farben = [];
        for ($i = 1; $i <= AuthorColor::COUNT; $i++) {
            $farben[] = AuthorColor::index($i);
        }
        assertSame(AuthorColor::COUNT, count(array_unique($farben)),
            'Zwei Konten teilen sich eine Farbe: ' . implode(',', $farben));
    });

    test('Benachbarte Konten liegen sichtbar auseinander', function () {
        // Wer nacheinander angelegt wird, sitzt meist im selben Team.
        for ($i = 1; $i < AuthorColor::COUNT; $i++) {
            $abstand = abs(AuthorColor::index($i) - AuthorColor::index($i + 1));
            assertTrue($abstand > 1, 'Konten ' . $i . ' und ' . ($i + 1) . ' liegen zu nah beieinander');
        }
    });

    test('Dieselbe Person bekommt immer dieselbe Farbe', function () {
        assertSame(AuthorColor::index(42), AuthorColor::index(42));
        assertSame(AuthorColor::css(7), AuthorColor::css(7));
    });

    test('Die Farbe kommt als Token, nie als Farbwert', function () {
        for ($i = 0; $i <= 30; $i++) {
            $css = AuthorColor::css($i);
            assertTrue(str_starts_with($css, 'var(--author-'), 'Kein Token: ' . $css);
            assertFalse(str_contains($css, '#'), 'Hex-Wert statt Token: ' . $css);
        }
    });

    test('Jede Farbe ist in den Tokens hinterlegt', function () use ($wurzel) {
        $tokens = (string)file_get_contents($wurzel . '/public/assets/css/tokens.css');
        for ($i = 1; $i <= AuthorColor::COUNT; $i++) {
            assertTrue(str_contains($tokens, '--author-' . $i . ':'), 'Token --author-' . $i . ' fehlt');
            assertTrue(str_contains($tokens, '--author-' . $i . '-soft:'), 'Token --author-' . $i . '-soft fehlt');
        }
    });

    test('Ein unbekannter Verfasser bekommt die neutrale Farbe', function () {
        assertSame(AuthorColor::COUNT, AuthorColor::index(0));
        assertSame(AuthorColor::COUNT, AuthorColor::index(-5));
    });

    test('Das Namenskuerzel bleibt auch bei ungewoehnlichen Namen lesbar', function () {
        assertSame('TT', AuthorColor::initials('Theo Test'));
        assertSame('LB', AuthorColor::initials('Lena Marie Brandt'));
        assertSame('FR', AuthorColor::initials('friloo'));
        assertSame('?',  AuthorColor::initials('   '));
    });

    // ── Der Text bleibt Text ─────────────────────────────────────────────

    test('Die Notiz wird beim Ausgeben maskiert', function () use ($wurzel) {
        // Gespeichert wird Klartext; ohne Maskierung stuende er als Markup
        // in der Seite.
        $seite = (string)file_get_contents($wurzel . '/templates/teamboard/index.php');
        assertTrue(str_contains($seite, '$this->e($n[\'body\'])'), 'Der Text wird ungefiltert ausgegeben');

        $widget = (string)file_get_contents($wurzel . '/templates/dashboard/index.php');
        assertTrue(str_contains($widget, '$this->e($_n[\'body\'])'), 'Das Widget gibt den Text ungefiltert aus');
    });

    test('Der Controller nimmt keine Steuerzeichen an', function () use ($wurzel) {
        $quelle = (string)file_get_contents($wurzel . '/src/Controllers/TeamboardController.php');
        assertTrue(str_contains($quelle, 'saubererText'), 'Die Eingabe wird nicht bereinigt');
        assertTrue(str_contains($quelle, 'MAX_LENGTH'), 'Die Länge ist nicht begrenzt');
    });

    // ── Verdrahtung ──────────────────────────────────────────────────────

    test('Der Modulschluessel steht an jeder noetigen Stelle', function () use ($wurzel) {
        $stellen = [
            'public/index.php',
            'src/Controllers/Admin/SettingsController.php',
            'templates/admin/settings/index.php',
            'public/install.php',
            'src/Core/Application.php',
            'templates/layouts/main.php',
        ];
        foreach ($stellen as $datei) {
            $inhalt = (string)file_get_contents($wurzel . '/' . $datei);
            assertTrue(str_contains($inhalt, 'teamboard'), 'Modulschlüssel fehlt in ' . $datei);
        }
    });

    test('Jede Route haengt am Modulschalter und an einem Recht', function () use ($wurzel) {
        $quelle = (string)file_get_contents($wurzel . '/src/Core/Application.php');
        $von    = strpos($quelle, "\$this->config['modules']['teamboard']");
        assertTrue($von !== false, 'Der Routenblock hängt nicht am Modulschalter');

        $block = substr($quelle, $von, 1200);
        $ende  = strpos($block, "\n        }");
        $block = substr($block, 0, $ende !== false ? $ende : 1200);

        preg_match_all("/router->(?:get|post)\('(\/teamboard[^']*)'[^;]*;/", $block, $routen);
        assertTrue(count($routen[0]) >= 4, 'Es fehlen Routen: ' . implode(', ', $routen[1]));

        // Die Optionen dürfen aus einer gemeinsamen Variablen kommen –
        // entscheidend ist, dass jede Route ein Recht mitbringt.
        assertTrue(str_contains($block, "'capability' => 'view_teamboard'"),
            'Der Routenblock nennt kein Recht');
        foreach ($routen[0] as $zeile) {
            assertTrue(str_contains($zeile, '$teamboardOpts') || str_contains($zeile, "'capability'"),
                'Route ohne Recht: ' . trim($zeile));
        }

        // Die Sammelroute muss vor der {id}-Route stehen, sonst greift sie nie.
        $store = strpos($block, "post('/teamboard',");
        $id    = strpos($block, "post('/teamboard/{id}'");
        assertTrue($store !== false && $id !== false && $store < $id,
            'Die {id}-Route steht vor der Sammelroute');
    });

    test('Die Migration legt Tabelle und Rechte an', function () use ($wurzel) {
        $sql = (string)file_get_contents($wurzel . '/database/migrations/245_teamboard.sql');
        assertTrue(str_contains($sql, 'CREATE TABLE IF NOT EXISTS `teamboard_notes`'), 'Die Tabelle fehlt');
        assertTrue(str_contains($sql, 'view_teamboard'), 'Das Leserecht fehlt');
        assertTrue(str_contains($sql, 'manage_teamboard'), 'Das Verwaltungsrecht fehlt');
        // Ein zweiter Lauf darf nichts anfassen.
        assertTrue(str_contains($sql, 'INSERT IGNORE'), 'Die Rechte werden nicht ohne Duplikate eingefügt');
        assertFalse(str_contains(strtoupper($sql), 'TRUNCATE'), 'Die Migration leert eine Tabelle');
    });

    test('Fremde Eintraege verlangen das eigene Recht', function () use ($wurzel) {
        $quelle = (string)file_get_contents($wurzel . '/src/Controllers/TeamboardController.php');
        assertTrue(str_contains($quelle, 'manage_teamboard'), 'Das Recht für fremde Einträge wird nicht geprüft');
        // Ändern und Löschen gehen beide über dieselbe Prüfung.
        assertSame(2, substr_count($quelle, '$this->darfBearbeiten('),
            'Nicht beide Wege prüfen die Zuständigkeit');
    });

    test('Schreibende Wege stehen im Audit-Log', function () use ($wurzel) {
        $quelle = (string)file_get_contents($wurzel . '/src/Controllers/TeamboardController.php');
        foreach (['note_created', 'note_updated', 'note_deleted'] as $art) {
            assertTrue(str_contains($quelle, $art), 'Ohne Eintrag im Audit-Log: ' . $art);
        }
    });

    test('Das Widget laedt die Stile seines Moduls mit', function () use ($wurzel) {
        // Ohne die Modul-CSS stuende das Widget unformatiert im Dashboard.
        $dash = (string)file_get_contents($wurzel . '/templates/dashboard/index.php');
        assertTrue(str_contains($dash, '/assets/css/teamboard.css'), 'Die Modul-CSS wird nicht geladen');
        assertTrue(str_contains($dash, "data-widget=\"teamboard\""), 'Das Widget fehlt');
    });
};