Unifi-Voucher-Tool/tests/KioskTest.php
Friederich Loheide 4159b92268
Some checks are pending
CI / PHP Lint (push) Waiting to run
CI / PHP Lint (pull_request) Waiting to run
CI / PHP Lint-1 (pull_request) Waiting to run
CI / PHP Lint-1 (push) Waiting to run
CI / Unit Tests & Static Analysis (pull_request) Waiting to run
CI / Unit Tests & Static Analysis (push) Waiting to run
Display-Seiten individuell gestalten
Jede Display-Seite bringt jetzt ihr eigenes Erscheinungsbild mit – der
Empfang sieht anders aus als der Tagungsraum nebenan:

- eigenes Logo (leer = Logo aus den Einstellungen)
- formatfüllendes Hintergrundbild mit einstellbarer Abdunklung (0–90 %),
  damit die Karte auf hellen Fotos lesbar bleibt
- eigene Akzentfarbe für den Knopf (leer = Farbe aus dem Design-Tab)
- Karte wahlweise hell oder dunkel; auf Fotos wirkt dunkel meist ruhiger

Logo und Hintergrund lassen sich hochladen oder als URL hinterlegen; beim
Löschen einer Display-Seite verschwinden die hochgeladenen Dateien mit.

Nebenbei aufgeräumt: das Bildfeld (Vorschau + Upload + URL + Entfernen)
lag als Funktion in admin/settings.php und wird jetzt von beiden Seiten
genutzt – Ui::imageField() für die Darstellung, Upload::resolveField()
für die Auswertung.

Sicherheit: die Akzentfarbe landet in einem style-Attribut, deshalb wird
sie sowohl beim Speichern als auch beim Ausgeben auf eine echte Hex-Farbe
geprüft; ein Test hält das fest.

Migration 0006, database.sql nachgezogen, 4 neue Tests (42 gesamt),
Screenshots ergänzt, Version 2.8.0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 16:58:54 +00:00

205 lines
6.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace Tests;
use PHPUnit\Framework\TestCase;
require_once __DIR__ . '/../includes/Kiosk.php';
/**
* Datenbank-Attrappe: liefert feste Zählwerte und Einstellungen.
*/
class FakeKioskDb
{
public int $usedToday = 0;
/** @var array<string, string> */
private array $settings;
/** @param array<string, string> $settings */
public function __construct(array $settings = [])
{
$this->settings = $settings;
}
public function fetchOne($sql, $params = [])
{
return ['c' => $this->usedToday];
}
public function getSetting($key, $default = null)
{
return $this->settings[$key] ?? $default;
}
}
/**
* Die Grenzen der Kiosk-Seite sind das, was sie vor Missbrauch schützt
* der Link ist öffentlich, also muss diese Logik stimmen.
*/
class KioskTest extends TestCase
{
/** @param array<string, mixed> $overrides */
private function kiosk(array $overrides = []): array
{
return array_merge([
'id' => 1,
'daily_limit' => 100,
'cooldown_seconds' => 20,
'last_used_at' => null,
], $overrides);
}
public function testTokenHasFixedShape(): void
{
$token = \Kiosk::newToken();
$this->assertMatchesRegularExpression('/^[0-9a-f]{32}$/', $token);
$this->assertNotSame($token, \Kiosk::newToken(), 'Tokens dürfen sich nicht wiederholen');
}
public function testSanitizeTokenRejectsAnythingElse(): void
{
$valid = \Kiosk::newToken();
$this->assertSame($valid, \Kiosk::sanitizeToken($valid));
$this->assertSame($valid, \Kiosk::sanitizeToken(strtoupper($valid)));
$this->assertSame('', \Kiosk::sanitizeToken('kurz'));
$this->assertSame('', \Kiosk::sanitizeToken("' OR 1=1 --"));
$this->assertSame('', \Kiosk::sanitizeToken(null));
$this->assertSame('', \Kiosk::sanitizeToken($valid . 'ff'));
}
public function testCooldownBlocksSecondCode(): void
{
$db = new FakeKioskDb();
$kiosk = $this->kiosk(['last_used_at' => date('Y-m-d H:i:s', time() - 5)]);
$result = \Kiosk::checkLimits($db, $kiosk);
$this->assertFalse($result['allowed']);
$this->assertSame('cooldown', $result['reason']);
$this->assertGreaterThan(0, $result['wait']);
$this->assertLessThanOrEqual(20, $result['wait']);
}
public function testCooldownExpires(): void
{
$db = new FakeKioskDb();
$kiosk = $this->kiosk(['last_used_at' => date('Y-m-d H:i:s', time() - 60)]);
$this->assertTrue(\Kiosk::checkLimits($db, $kiosk)['allowed']);
}
public function testDailyLimitBlocks(): void
{
$db = new FakeKioskDb();
$db->usedToday = 100;
$result = \Kiosk::checkLimits($db, $this->kiosk());
$this->assertFalse($result['allowed']);
$this->assertSame('daily_limit', $result['reason']);
}
public function testZeroMeansUnlimited(): void
{
$db = new FakeKioskDb();
$db->usedToday = 5000;
$kiosk = $this->kiosk(['daily_limit' => 0, 'cooldown_seconds' => 0]);
$this->assertTrue(\Kiosk::checkLimits($db, $kiosk)['allowed']);
}
public function testVoucherSettingsPreferTemplate(): void
{
$db = new FakeKioskDb(['default_max_uses' => '1', 'default_expire_minutes' => '480']);
$kiosk = $this->kiosk([
'tpl_max_uses' => 5,
'tpl_expire_minutes' => 240,
'qos_rate_max_down' => 20000,
'qos_rate_max_up' => 5000,
'qos_usage_quota' => 1024,
]);
$settings = \Kiosk::voucherSettings($db, $kiosk);
$this->assertSame(5, $settings['max_uses']);
$this->assertSame(240, $settings['expire_minutes']);
$this->assertSame(20000, $settings['qos']['down']);
$this->assertSame(1024, $settings['qos']['quota_mb']);
}
public function testVoucherSettingsFallBackToDefaults(): void
{
$db = new FakeKioskDb(['default_max_uses' => '3', 'default_expire_minutes' => '120']);
$settings = \Kiosk::voucherSettings($db, $this->kiosk());
$this->assertSame(3, $settings['max_uses']);
$this->assertSame(120, $settings['expire_minutes']);
$this->assertSame(0, $settings['qos']['down']);
}
public function testAppearanceFallsBackToSystemLogo(): void
{
$db = new FakeKioskDb(['logo_url' => 'uploads/global.png']);
$look = \Kiosk::appearance($db, $this->kiosk());
$this->assertSame('uploads/global.png', $look['logo']);
$this->assertSame('', $look['background']);
$this->assertSame('', $look['accent'], 'Ohne eigene Farbe bleibt das Design-System zuständig');
$this->assertSame('light', $look['card']);
}
public function testAppearanceUsesOwnValues(): void
{
$db = new FakeKioskDb(['logo_url' => 'uploads/global.png']);
$kiosk = $this->kiosk([
'logo_url' => 'uploads/hotel.svg',
'background_url' => 'uploads/lobby.jpg',
'bg_overlay' => 60,
'accent_color' => '#0F766E',
'card_style' => 'dark',
]);
$look = \Kiosk::appearance($db, $kiosk);
$this->assertSame('uploads/hotel.svg', $look['logo']);
$this->assertSame('uploads/lobby.jpg', $look['background']);
$this->assertSame(0.6, $look['overlay']);
$this->assertSame('#0f766e', $look['accent']);
$this->assertSame('dark', $look['card']);
}
public function testAppearanceRejectsUnsafeColour(): void
{
$db = new FakeKioskDb();
// Der Wert landet in einem style-Attribut nur echte Hex-Farben durch.
$look = \Kiosk::appearance($db, $this->kiosk(['accent_color' => 'red;background:url(evil)']));
$this->assertSame('', $look['accent']);
}
public function testAppearanceClampsOverlay(): void
{
$db = new FakeKioskDb();
$this->assertSame(0.9, \Kiosk::appearance($db, $this->kiosk(['bg_overlay' => 250]))['overlay']);
$this->assertSame(0.0, \Kiosk::appearance($db, $this->kiosk(['bg_overlay' => -10]))['overlay']);
}
public function testPublicUrl(): void
{
$token = \Kiosk::newToken();
$this->assertSame(
'https://wlan.example.com/kiosk.php?k=' . $token,
\Kiosk::publicUrl($token, 'https://wlan.example.com/')
);
}
}