OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

CostOverviewTest.php

9 KB
<?php

declare(strict_types=1);

use OpenNIT\Content\CostOverview;
use OpenNIT\Core\Database;

/**
 * Deckt die Zusammenfuehrung der laufenden IT-Kosten ab.
 *
 * Der heikle Punkt ist die Doppelzaehlung: Eine Lizenz, die einem Vertrag
 * zugeordnet ist, wird ueber diesen Vertrag abgerechnet. Ihr Betrag steckt also
 * bereits in den Vertragskosten und darf nicht noch einmal in die Summe - er
 * darf aber auch nicht stillschweigend verschwinden.
 */
return static function (): void {

    /**
     * Datenbank-Attrappe mit vorgegebenen Vertraegen und Lizenzen.
     *
     * @param list<array<string,mixed>> $vertraege
     * @param list<array<string,mixed>> $lizenzen
     */
    $db = static function (array $vertraege, array $lizenzen): Database {
        return new class ($vertraege, $lizenzen) extends Database {
            public function __construct(private array $vertraege, private array $lizenzen) {}
            public function fetchAll(string $sql, array $params = []): array
            {
                return str_contains($sql, 'FROM contracts') ? $this->vertraege : $this->lizenzen;
            }
        };
    };

    /** Kurzform fuer einen Vertrag. */
    $vertrag = static fn (string $titel, float $betrag, string $intervall, string $kat = 'Allgemein'): array =>
        ['id' => 1, 'title' => $titel, 'cost_amount' => $betrag, 'cost_interval' => $intervall,
         'status' => 'active', 'kategorie' => $kat];

    /** Kurzform fuer eine Lizenz; $vertraege = Zahl zugeordneter Vertraege. */
    $lizenz = static fn (string $name, float $betrag, string $intervall, int $vertraege = 0): array =>
        ['id' => 1, 'name' => $name, 'vendor' => 'Hersteller', 'cost_amount' => $betrag,
         'cost_interval' => $intervall, 'vertraege' => $vertraege];

    // ----------------------------------------------------------------
    // Umrechnung
    // ----------------------------------------------------------------

    test('Jedes Intervall wird auf den Monat gerechnet', function () {
        assertSame(100.0, CostOverview::monatsbetrag(100.0, 'monthly'));
        assertSame(100.0, CostOverview::monatsbetrag(300.0, 'quarterly'));
        assertSame(100.0, CostOverview::monatsbetrag(1200.0, 'yearly'));
    });

    test('Eine einmalige Zahlung ergibt keine laufenden Kosten', function () {
        // Sonst erschiene eine Anschaffung als monatliche Belastung.
        assertSame(0.0, CostOverview::monatsbetrag(5000.0, 'one_time'));
        assertSame(0.0, CostOverview::monatsbetrag(100.0, null));
        assertSame(0.0, CostOverview::monatsbetrag(100.0, 'gibtesnicht'));
    });

    test('Fehlende und unsinnige Betraege zaehlen nicht', function () {
        assertSame(0.0, CostOverview::monatsbetrag(null, 'monthly'));
        assertSame(0.0, CostOverview::monatsbetrag(0.0, 'monthly'));
        assertSame(0.0, CostOverview::monatsbetrag(-50.0, 'monthly'));
    });

    // ----------------------------------------------------------------
    // Doppelzaehlung
    // ----------------------------------------------------------------

    test('Eine Lizenz mit Vertrag geht nicht in die Summe', function () use ($db, $vertrag, $lizenz) {
        $u = (new CostOverview($db(
            [$vertrag('Wartung', 1200.0, 'yearly')],              // 100/Monat
            [$lizenz('Office', 50.0, 'monthly', 1)]               // ueber Vertrag
        )))->zusammenfassung(true, true);

        assertSame(100.0, $u['monatlich'], 'Die Lizenz wurde doppelt gezaehlt');
        assertSame(1200.0, $u['jaehrlich']);
        assertSame(0.0, $u['lizenzen_monatlich']);
    });

    test('Eine Lizenz ohne Vertrag zaehlt mit', function () use ($db, $vertrag, $lizenz) {
        $u = (new CostOverview($db(
            [$vertrag('Wartung', 1200.0, 'yearly')],
            [$lizenz('Office', 50.0, 'monthly', 0)]
        )))->zusammenfassung(true, true);

        assertSame(150.0, $u['monatlich']);
        assertSame(50.0, $u['lizenzen_monatlich']);
        assertSame(100.0, $u['vertraege_monatlich']);
    });

    test('Mehrere Vertraege an einer Lizenz aendern nichts', function () use ($db, $vertrag, $lizenz) {
        // Die Zuordnung ist n:m - eine einzige genuegt fuer "abgedeckt".
        $u = (new CostOverview($db(
            [$vertrag('Wartung', 100.0, 'monthly')],
            [$lizenz('Office', 50.0, 'monthly', 3)]
        )))->zusammenfassung(true, true);

        assertSame(100.0, $u['monatlich']);
        assertSame(1, $u['abgedeckt_anzahl']);
    });

    test('Die ausgelassenen Lizenzen werden beziffert', function () use ($db, $vertrag, $lizenz) {
        // Eine stillschweigend weggelassene Position faellt niemandem auf.
        $u = (new CostOverview($db(
            [$vertrag('Wartung', 100.0, 'monthly')],
            [$lizenz('A', 60.0, 'monthly', 1), $lizenz('B', 120.0, 'yearly', 1), $lizenz('C', 10.0, 'monthly', 0)]
        )))->zusammenfassung(true, true);

        assertSame(2, $u['abgedeckt_anzahl'], 'Nicht alle ausgelassenen gezaehlt');
        assertSame(70.0, $u['abgedeckt_monatlich'], 'Der ausgelassene Betrag stimmt nicht');
        assertSame(110.0, $u['monatlich'], 'Nur die freie Lizenz gehoert dazu');
    });

    // ----------------------------------------------------------------
    // Abgeschaltete Module
    // ----------------------------------------------------------------

    test('Ohne Lizenz-Modul bleiben die Vertraege unveraendert', function () use ($db, $vertrag, $lizenz) {
        $u = (new CostOverview($db(
            [$vertrag('Wartung', 100.0, 'monthly')],
            [$lizenz('Office', 999.0, 'monthly', 0)]
        )))->zusammenfassung(true, false);

        assertSame(100.0, $u['monatlich'], 'Lizenzen flossen trotz abgeschaltetem Modul ein');
        assertSame([], $u['lizenzen']);
    });

    test('Ganz ohne Daten steht ueberall null', function () use ($db) {
        $u = (new CostOverview($db([], [])))->zusammenfassung(true, true);

        assertSame(0.0, $u['monatlich']);
        assertSame(0.0, $u['jaehrlich']);
        assertSame(0.0, $u['einmalig']);
        assertSame([], $u['nach_kategorie']);
    });

    // ----------------------------------------------------------------
    // Einmalige Zahlungen und Gruppierung
    // ----------------------------------------------------------------

    test('Einmalige Zahlungen stehen gesondert', function () use ($db, $vertrag, $lizenz) {
        $u = (new CostOverview($db(
            [$vertrag('Wartung', 100.0, 'monthly'), $vertrag('Anschaffung', 5000.0, 'one_time')],
            [$lizenz('Dauerlizenz', 800.0, 'one_time', 0)]
        )))->zusammenfassung(true, true);

        assertSame(100.0, $u['monatlich'], 'Einmaliges floss in die laufenden Kosten');
        assertSame(5800.0, $u['einmalig']);
    });

    test('Die Kategorien summieren sich zur Monatssumme', function () use ($db, $vertrag, $lizenz) {
        $u = (new CostOverview($db(
            [$vertrag('A', 100.0, 'monthly', 'Software'), $vertrag('B', 60.0, 'monthly', 'Hardware'),
             $vertrag('C', 120.0, 'yearly', 'Software')],
            [$lizenz('Frei', 30.0, 'monthly', 0)]
        )))->zusammenfassung(true, true);

        assertSame(200.0, $u['monatlich']);
        assertSame(200.0, array_sum($u['nach_kategorie']), 'Die Aufteilung ergibt nicht die Summe');
        // Absteigend, damit der groesste Posten oben steht.
        assertSame(['Software', 'Hardware', 'Lizenzen (ohne Vertrag)'], array_keys($u['nach_kategorie']));
    });

    test('Abgedeckte Lizenzen tauchen nicht in den Kategorien auf', function () use ($db, $vertrag, $lizenz) {
        $u = (new CostOverview($db(
            [$vertrag('A', 100.0, 'monthly', 'Software')],
            [$lizenz('Abgedeckt', 50.0, 'monthly', 1)]
        )))->zusammenfassung(true, true);

        assertFalse(isset($u['nach_kategorie']['Lizenzen (ohne Vertrag)']), 'Abgedeckte Lizenz in der Aufteilung');
        assertSame(100.0, array_sum($u['nach_kategorie']));
    });

    // ----------------------------------------------------------------
    // Auswahl der Datensaetze
    // ----------------------------------------------------------------

    test('Nur laufende Vertraege und aktive Lizenzen', function () {
        $quelle = (string)file_get_contents(dirname(__DIR__) . '/src/Content/CostOverview.php');

        assertTrue(str_contains($quelle, "['active', 'expiring']"), 'Vertragszustaende geaendert');
        assertTrue(str_contains($quelle, "l.status = 'active'"), 'Lizenzzustand geaendert');
        // Ohne Betrag gibt es nichts zu rechnen.
        assertSame(2, substr_count($quelle, 'cost_amount > 0'), 'Betragsfilter fehlt');
    });

    test('Die Seite schreibt nichts', function () {
        $quelle = (string)file_get_contents(dirname(__DIR__) . '/src/Content/CostOverview.php');
        foreach (['INSERT', 'UPDATE ', 'DELETE', '->insert(', '->update(', '->execute('] as $wort) {
            assertFalse(str_contains($quelle, $wort), "Schreibender Zugriff: {$wort}");
        }
    });
};