test: Testsuite fuer Kernlogik, QR-Codes, Domains und Sicherheit
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
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user