OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

TopicLocationScopeTest.php

8 KB
<?php

declare(strict_types=1);

use OpenNIT\Locations\LocationScope;
use OpenNIT\Locations\TopicLocations;

/**
 * Deckt die Standortbindung der Themengebiete ab.
 *
 * Ein Themengebiet kann mehreren Standorten zugeordnet sein; ohne eigene
 * Zuordnung erbt es die Menge des nächsten übergeordneten, und erbt keiner eine,
 * gilt es für alle. Die senkrechte Linie durch einen Standort umfasst dessen
 * übergeordnete und untergeordnete Standorte, nicht aber Nebenstandorte.
 */
return static function (): void {

    /**
     * Datenbank-Doppel mit Standortbaum, Themengebieten und Zuordnungstabelle.
     *
     * @param array<int,?int>                          $standorte  Standort-ID => übergeordneter Standort
     * @param array<int,array{path:string,locs:int[]}> $themen     Themengebiet-ID => Pfad und eigene Standorte
     * @param bool                                     $mitTabelle false = Zuordnungstabelle fehlt noch (vor der Migration)
     */
    $db = function (array $standorte, array $themen, bool $mitTabelle = true): object {
        return new class ($standorte, $themen, $mitTabelle) extends \OpenNIT\Core\Database {
            public function __construct(
                private array $standorte,
                private array $themen,
                private bool $mitTabelle
            ) {
                // Verbindung bewusst nicht aufbauen – die Abfragen werden bedient.
            }

            public function fetchAll(string $sql, array $params = []): array
            {
                if (str_contains($sql, 'FROM ticket_locations')) {
                    $rows = [];
                    foreach ($this->standorte as $id => $parent) {
                        $rows[] = ['id' => $id, 'parent_id' => $parent];
                    }
                    return $rows;
                }
                if (str_contains($sql, 'FROM topic_locations')) {
                    if (!$this->mitTabelle) {
                        throw new \RuntimeException('Table topic_locations does not exist');
                    }
                    $rows = [];
                    foreach ($this->themen as $id => $t) {
                        foreach ($t['locs'] as $loc) {
                            $rows[] = ['topic_id' => $id, 'location_id' => $loc];
                        }
                    }
                    return $rows;
                }
                if (str_contains($sql, 'FROM topics')) {
                    $rows = [];
                    foreach ($this->themen as $id => $t) {
                        // Die frühere Einzelzuordnung: der erste Standort.
                        $rows[] = [
                            'id'          => $id,
                            'path'        => $t['path'],
                            'location_id' => $t['locs'][0] ?? null,
                        ];
                    }
                    return $rows;
                }
                return [];
            }
        };
    };

    // Kunde A (1) mit Niederlassungen Nord (2) und Süd (3); Kunde B (4) daneben.
    $standorte = [1 => null, 2 => 1, 3 => 1, 4 => null];

    // Themenbaum: Kunde A (10) mit Unterthemen, Kunde B (20), Allgemeines (30)
    // mit einer gemeinsamen Anleitung (31) für beide Kunden.
    $themen = [
        10 => ['path' => '/10/',    'locs' => [1]],
        11 => ['path' => '/10/11/', 'locs' => []],
        12 => ['path' => '/10/12/', 'locs' => [2]],
        20 => ['path' => '/20/',    'locs' => [4]],
        30 => ['path' => '/30/',    'locs' => []],
        31 => ['path' => '/30/31/', 'locs' => [1, 4]],
    ];

    test('Ein Themengebiet ohne eigene Zuordnung erbt die des übergeordneten', function () use ($db, $standorte, $themen) {
        $wirksam = (new TopicLocations($db($standorte, $themen)))->effectiveLocations();

        assertSame([1], $wirksam[11], 'Unterthema erbt den Standort von Kunde A');
        assertSame([2], $wirksam[12], 'Eigene Zuordnung hat Vorrang vor der geerbten');
        assertSame([], $wirksam[30], 'Ohne Zuordnung im ganzen Pfad bleibt es ungebunden');
        assertSame([1, 4], $wirksam[31], 'Eine gemeinsame Anleitung trägt mehrere Standorte');
    });

    test('Ohne zugeordnete Themengebiete gibt es keine Einschränkung', function () use ($db, $standorte) {
        $ohne = [10 => ['path' => '/10/', 'locs' => []], 20 => ['path' => '/20/', 'locs' => []]];
        $tl   = new TopicLocations($db($standorte, $ohne));

        assertFalse($tl->inUse());
        assertSame(null, $tl->visibleTopicIds([1]), 'null steht für „keine Einschränkung"');
    });

    test('Der Oberstandort sieht die Themengebiete seiner Unterstandorte', function () use ($db, $standorte, $themen) {
        $erlaubt  = (new LocationScope($db($standorte, $themen)))->selfAncestorsAndDescendants(1);
        $sichtbar = (new TopicLocations($db($standorte, $themen)))->visibleTopicIds($erlaubt);

        sort($sichtbar);
        assertSame([10, 11, 12, 30, 31], $sichtbar, 'Kunde A samt Unterthemen, das Ungebundene und die gemeinsame Anleitung');
    });

    test('Die gemeinsame Anleitung erscheint bei beiden Kunden', function () use ($db, $standorte, $themen) {
        $scope = new LocationScope($db($standorte, $themen));
        $tl    = new TopicLocations($db($standorte, $themen));

        $beiA = $tl->visibleTopicIds($scope->selfAncestorsAndDescendants(1));
        $beiB = (new TopicLocations($db($standorte, $themen)))
            ->visibleTopicIds($scope->selfAncestorsAndDescendants(4));

        assertTrue(in_array(31, $beiA, true), 'Kunde A sieht die gemeinsame Anleitung');
        assertTrue(in_array(31, $beiB, true), 'Kunde B ebenfalls');
        assertFalse(in_array(10, $beiB, true), 'Kunde B sieht deswegen nicht den Bereich von Kunde A');
    });

    test('Ein Unterstandort sieht das Kundenweite mit, aber nicht den Nebenstandort', function () use ($db, $standorte, $themen) {
        $erlaubt = (new LocationScope($db($standorte, $themen)))->selfAncestorsAndDescendants(2);

        sort($erlaubt);
        assertSame([1, 2], $erlaubt, 'Standort Nord und dessen Oberstandort – Süd bleibt außen vor');

        $sichtbar = (new TopicLocations($db($standorte, $themen)))->visibleTopicIds($erlaubt);
        sort($sichtbar);
        assertSame([10, 11, 12, 30, 31], $sichtbar);
    });

    test('Ein fremder Kunde sieht nur sein eigenes und das ungebundene Themengebiet', function () use ($db, $standorte, $themen) {
        $erlaubt  = (new LocationScope($db($standorte, $themen)))->selfAncestorsAndDescendants(4);
        $sichtbar = (new TopicLocations($db($standorte, $themen)))->visibleTopicIds($erlaubt);

        sort($sichtbar);
        assertSame([20, 30, 31], $sichtbar, 'Der Bereich von Kunde A bleibt verborgen');
    });

    test('Ohne Standort bleibt nur das Ungebundene sichtbar', function () use ($db, $standorte, $themen) {
        $erlaubt  = (new LocationScope($db($standorte, $themen)))->selfAncestorsAndDescendants(null);
        $sichtbar = (new TopicLocations($db($standorte, $themen)))->visibleTopicIds($erlaubt);

        assertSame([], $erlaubt);
        assertSame([30], $sichtbar);
    });

    test('Fehlt die Zuordnungstabelle, gilt die frühere Einzelzuordnung', function () use ($db, $standorte, $themen) {
        $wirksam = (new TopicLocations($db($standorte, $themen, false)))->effectiveLocations();

        assertSame([1], $wirksam[10], 'Der Wert aus der Spalte zählt');
        assertSame([1], $wirksam[31], 'Mehrfachzuordnung gibt es dort noch nicht – der erste Standort gilt');
    });

    test('Die Sichtbarkeitsbedingung schließt Inhalte ohne Standort ein', function () use ($db, $standorte, $themen) {
        [$sql, $params] = (new LocationScope($db($standorte, $themen)))->visibilityCondition(1, 'x.location_id');

        assertTrue(str_contains($sql, 'x.location_id IS NULL'), 'Inhalte ohne Standort gelten überall');
        sort($params);
        assertSame([1, 2, 3], $params, 'Oberstandort samt beider Niederlassungen');
    });

    test('Ein Zyklus im Standortbaum bricht die Auflösung nicht', function () use ($db, $themen) {
        $zyklus  = [1 => 2, 2 => 1];
        $erlaubt = (new LocationScope($db($zyklus, $themen)))->selfAncestorsAndDescendants(1);

        sort($erlaubt);
        assertSame([1, 2], $erlaubt);
    });
};