OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

LocationScopeTest.php

7 KB
<?php

declare(strict_types=1);

use OpenNIT\Locations\LocationScope;

/**
 * Deckt den Standortbezug der Portal-Inhalte ab.
 *
 * Schwerpunkte: Auflösung des Standorts über die E-Mail-Domain (exakter
 * Treffer vor Subdomain-Treffer), Vorrang des fest gesetzten Standorts einer
 * Person und die Sichtbarkeitsbedingung inklusive Vererbung an Unterstandorte.
 */
return static function (): void {

    /**
     * Datenbank-Doppel mit Domain-Zuordnungen und Standort-Hierarchie.
     *
     * @param array<string,int> $domains  Domain => Standort-ID
     * @param array<int,?int>   $parents  Standort-ID => übergeordnete ID
     */
    $db = function (array $domains, array $parents = []): object {
        return new class ($domains, $parents) extends \OpenNIT\Core\Database {
            public function __construct(private array $domains, private array $parents)
            {
                // Verbindung bewusst nicht aufbauen – Abfragen werden bedient.
            }

            public function fetchAll(string $sql, array $params = []): array
            {
                if (str_contains($sql, 'FROM location_domains')) {
                    $rows = [];
                    foreach ($params as $candidate) {
                        if (isset($this->domains[$candidate])) {
                            $rows[] = ['domain' => $candidate, 'location_id' => $this->domains[$candidate]];
                        }
                    }
                    return $rows;
                }
                if (str_contains($sql, 'FROM ticket_locations')) {
                    $rows = [];
                    foreach ($this->parents as $id => $parent) {
                        $rows[] = ['id' => $id, 'parent_id' => $parent];
                    }
                    return $rows;
                }
                return [];
            }
        };
    };

    test('Exakte Domain gewinnt vor der übergeordneten Domain', function () use ($db) {
        $scope = new LocationScope($db(['kunde.de' => 5, 'mail.kunde.de' => 9]));
        assertSame(9, $scope->forEmail('anna@mail.kunde.de'));
        assertSame(5, $scope->forEmail('bert@kunde.de'));
    });

    test('Subdomain fällt auf die hinterlegte Hauptdomain zurück', function () use ($db) {
        $scope = new LocationScope($db(['kunde.de' => 5]));
        assertSame(5, $scope->forEmail('anna@standort.kunde.de'));
    });

    test('Unbekannte Domain und ungültige Adresse liefern keinen Standort', function () use ($db) {
        $scope = new LocationScope($db(['kunde.de' => 5]));
        assertTrue($scope->forEmail('anna@fremd.de') === null);
        assertTrue($scope->forEmail('ohne-adresse') === null);
        assertTrue($scope->forEmail(null) === null);
    });

    test('Fest gesetzter Standort der Person hat Vorrang vor der Domain', function () use ($db) {
        $scope = new LocationScope($db(['kunde.de' => 5]));
        assertSame(7, $scope->forPerson(['email' => 'anna@kunde.de', 'location_id' => 7]));
        assertSame(5, $scope->forPerson(['email' => 'anna@kunde.de', 'location_id' => null]));
    });

    test('Sichtbarkeit umfasst den eigenen Standort und dessen Oberstandorte', function () use ($db) {
        // 12 → 5 → null: Unterstandort eines Kunden
        $scope = new LocationScope($db([], [5 => null, 12 => 5, 99 => null]));
        [$sql, $params] = $scope->visibilityCondition(12, 'location_id');
        assertTrue(str_contains($sql, 'IS NULL'));
        assertSame([12, 5], $params);
    });

    test('Ohne Standort sind nur globale Inhalte sichtbar', function () use ($db) {
        $scope = new LocationScope($db([], [5 => null]));
        [$sql, $params] = $scope->visibilityCondition(null, 'location_id');
        assertSame('location_id IS NULL', $sql);
        assertSame([], $params);
    });

    test('Zyklische Hierarchie bricht die Auflösung sauber ab', function () use ($db) {
        $scope = new LocationScope($db([], [1 => 2, 2 => 1]));
        $ids = $scope->selfAndAncestors(1);
        assertSame([1, 2], $ids);
    });

    test('Themenbereich erbt den Standort des übergeordneten Bereichs', function () {
        $knowledge = new \OpenNIT\Portal\PortalKnowledge(
            new class extends \OpenNIT\Core\Database {
                public function __construct() {}
                public function fetchAll(string $sql, array $params = []): array
                {
                    if (str_contains($sql, 'FROM topic_locations')) {
                        return [
                            // Oberbereich an Standort 5; der Unterbereich 2 erbt ihn.
                            ['topic_id' => 1, 'location_id' => 5],
                            // Eigene Zuordnung schlägt die Vererbung.
                            ['topic_id' => 3, 'location_id' => 9],
                        ];
                    }
                    if (str_contains($sql, 'FROM topics')) {
                        return [
                            ['id' => 1, 'path' => '/1/', 'location_id' => 5],
                            ['id' => 2, 'path' => '/1/2/', 'location_id' => null],
                            ['id' => 3, 'path' => '/1/3/', 'location_id' => 9],
                            // Komplett standortfrei → für alle sichtbar
                            ['id' => 4, 'path' => '/4/', 'location_id' => null],
                        ];
                    }
                    if (str_contains($sql, 'FROM ticket_locations')) {
                        return [['id' => 5, 'parent_id' => null], ['id' => 9, 'parent_id' => null]];
                    }
                    return [];
                }
            }
        );

        $ref = new ReflectionMethod($knowledge, 'topicCondition');
        $ref->setAccessible(true);

        // Person an Standort 5: Oberbereich, geerbter Unterbereich und globaler Bereich
        [$sql, $params] = $ref->invoke($knowledge, 5);
        assertSame([1, 2, 4], $params);
        assertTrue(str_contains($sql, 't.id IN'));

        // Person an Standort 9: nur der eigene Bereich und der globale
        [, $params9] = $ref->invoke($knowledge, 9);
        assertSame([3, 4], $params9);

        // Person ohne Standort: ausschließlich globale Bereiche
        [, $paramsNone] = $ref->invoke($knowledge, null);
        assertSame([4], $paramsNone);
    });

    test('Ohne standortgebundene Themenbereiche bleibt die Abfrage ungefiltert', function () {
        $knowledge = new \OpenNIT\Portal\PortalKnowledge(
            new class extends \OpenNIT\Core\Database {
                public function __construct() {}
                public function fetchAll(string $sql, array $params = []): array
                {
                    if (str_contains($sql, 'FROM topics')) {
                        return [
                            ['id' => 1, 'path' => '/1/', 'location_id' => null],
                            ['id' => 2, 'path' => '/2/', 'location_id' => null],
                        ];
                    }
                    return [];
                }
            }
        );
        $ref = new ReflectionMethod($knowledge, 'topicCondition');
        $ref->setAccessible(true);

        [$sql, $params] = $ref->invoke($knowledge, 5);
        assertSame('1=1', $sql);
        assertSame([], $params);
    });
};