232 Tests, unter anderem: - Slug-Erzeugung, Kollisionsbehandlung und Validierung - URL-Validierung inklusive IDN, Prozentkodierung und abgelehnter Protokolle; Punycode gegengeprueft gegen idn_to_ascii - QR-Erzeugung in allen Groessen, mit Logo und Level-H-Automatik - Weiterleitung gegen eine echte SQLite-Datenbank samt Domain- Aufloesung ueber den Host-Header - Update einer bestehenden Installation auf mehrere Domains - Angriffsseitig: gefaelschte Proxy-Header, Host-Header-Injektion, SVG mit Skripten, XXE und externen Referenzen
144 lines
4.4 KiB
PHP
144 lines
4.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests;
|
|
|
|
use App\Support\Slug;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class SlugTest extends TestCase
|
|
{
|
|
public function testRandomSlugHasRequestedLengthAndSafeAlphabet(): void
|
|
{
|
|
for ($i = 0; $i < 50; $i++) {
|
|
$slug = Slug::random(7);
|
|
|
|
self::assertSame(7, strlen($slug));
|
|
self::assertMatchesRegularExpression('/^[a-z0-9]+$/', $slug);
|
|
// 0 und 1 sind ausgeschlossen (Verwechslung mit o und l).
|
|
self::assertDoesNotMatchRegularExpression('/[01]/', $slug);
|
|
self::assertTrue(Slug::isValid($slug));
|
|
}
|
|
}
|
|
|
|
public function testRandomSlugLengthIsConfigurable(): void
|
|
{
|
|
self::assertSame(6, strlen(Slug::random(6)));
|
|
self::assertSame(8, strlen(Slug::random(8)));
|
|
}
|
|
|
|
public function testRandomSlugsAreEssentiallyUnique(): void
|
|
{
|
|
$slugs = [];
|
|
for ($i = 0; $i < 500; $i++) {
|
|
$slugs[Slug::random(7)] = true;
|
|
}
|
|
|
|
// 33^7 Kombinationen: Kollisionen sind bei 500 Ziehungen praktisch ausgeschlossen.
|
|
self::assertGreaterThanOrEqual(499, count($slugs));
|
|
}
|
|
|
|
public function testRandomUniqueSkipsTakenSlugs(): void
|
|
{
|
|
$taken = [];
|
|
$calls = 0;
|
|
|
|
$exists = static function (string $candidate) use (&$taken, &$calls): bool {
|
|
$calls++;
|
|
|
|
// Die ersten drei Vorschlaege gelten als vergeben.
|
|
if ($calls <= 3) {
|
|
$taken[] = $candidate;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
$slug = Slug::randomUnique($exists);
|
|
|
|
self::assertNotContains($slug, $taken);
|
|
self::assertTrue(Slug::isValid($slug));
|
|
self::assertSame(4, $calls);
|
|
}
|
|
|
|
public function testRandomUniqueGrowsLengthOnRepeatedCollisions(): void
|
|
{
|
|
$attempt = 0;
|
|
$slug = Slug::randomUnique(
|
|
static function () use (&$attempt): bool {
|
|
$attempt++;
|
|
|
|
return $attempt <= 4; // vier Kollisionen erzwingen
|
|
},
|
|
7,
|
|
);
|
|
|
|
self::assertSame(8, strlen($slug));
|
|
}
|
|
|
|
public function testRandomRejectsUnreasonableLengths(): void
|
|
{
|
|
$this->expectException(\InvalidArgumentException::class);
|
|
Slug::random(3);
|
|
}
|
|
|
|
/**
|
|
* @return array<string,array{0:string,1:bool}>
|
|
*/
|
|
public static function slugValidationProvider(): array
|
|
{
|
|
return [
|
|
'einfach' => ['messe2026', true],
|
|
'mit Bindestrich' => ['messe-2026-halle-3', true],
|
|
'nur Ziffern' => ['12345', true],
|
|
'einzelnes Zeichen' => ['a', true],
|
|
'leer' => ['', false],
|
|
'Grossbuchstaben' => ['Messe2026', false],
|
|
'Umlaut' => ['grüße', false],
|
|
'Leerzeichen' => ['messe 2026', false],
|
|
'Unterstrich' => ['messe_2026', false],
|
|
'Punkt' => ['messe.2026', false],
|
|
'Slash' => ['messe/2026', false],
|
|
'fuehrender Bindestrich' => ['-messe', false],
|
|
'abschliessender Bindestrich' => ['messe-', false],
|
|
'doppelter Bindestrich' => ['messe--2026', false],
|
|
'reserviert admin' => ['admin', false],
|
|
'reserviert login' => ['login', false],
|
|
'zu lang' => [str_repeat('a', 65), false],
|
|
'maximale Laenge' => [str_repeat('a', 64), true],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider slugValidationProvider
|
|
*/
|
|
public function testValidation(string $slug, bool $expected): void
|
|
{
|
|
self::assertSame($expected, Slug::isValid($slug));
|
|
self::assertSame($expected, Slug::validationError($slug) === null);
|
|
}
|
|
|
|
public function testNormalizeCreatesUsableSlugs(): void
|
|
{
|
|
self::assertSame('messe-2026', Slug::normalize('Messe 2026'));
|
|
self::assertSame('gruesse-aus-muenchen', Slug::normalize('Grüße aus München'));
|
|
self::assertSame('a-b', Slug::normalize('--a---b--'));
|
|
self::assertSame('strasse', Slug::normalize('Straße'));
|
|
self::assertSame('', Slug::normalize(' '));
|
|
}
|
|
|
|
public function testNormalizedInputIsAlwaysValidOrEmpty(): void
|
|
{
|
|
foreach (['Messe 2026', 'Grüße!', 'a/b/c', '###', 'Test_Fall'] as $input) {
|
|
$normalized = Slug::normalize($input);
|
|
|
|
if ($normalized !== '') {
|
|
self::assertTrue(Slug::isValid($normalized), $input . ' -> ' . $normalized);
|
|
}
|
|
}
|
|
}
|
|
}
|