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,164 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests;
|
||||
|
||||
use App\Qr\LogoStore;
|
||||
use App\Qr\QrOptions;
|
||||
use App\Qr\QrRenderer;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class QrRendererTest extends TestCase
|
||||
{
|
||||
private string $uploadDir;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->uploadDir = sys_get_temp_dir() . '/urlshort-test-' . bin2hex(random_bytes(4));
|
||||
mkdir($this->uploadDir, 0775, true);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
foreach (glob($this->uploadDir . '/*') ?: [] as $file) {
|
||||
unlink($file);
|
||||
}
|
||||
if (is_dir($this->uploadDir)) {
|
||||
rmdir($this->uploadDir);
|
||||
}
|
||||
}
|
||||
|
||||
private function renderer(): QrRenderer
|
||||
{
|
||||
return new QrRenderer(new LogoStore($this->uploadDir));
|
||||
}
|
||||
|
||||
public function testPngIsGeneratedInRequestedSize(): void
|
||||
{
|
||||
$png = $this->renderer()->png('https://kurz.test/messe2026', new QrOptions(size: 512, margin: 4));
|
||||
|
||||
self::assertStringStartsWith("\x89PNG", $png);
|
||||
|
||||
$info = getimagesizefromstring($png);
|
||||
self::assertIsArray($info);
|
||||
self::assertSame(512, $info[0]);
|
||||
self::assertSame(512, $info[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider sizeProvider
|
||||
*/
|
||||
public function testAllOfferedSizesWork(int $size): void
|
||||
{
|
||||
$info = getimagesizefromstring(
|
||||
$this->renderer()->png('https://kurz.test/abc', new QrOptions(size: $size)),
|
||||
);
|
||||
|
||||
self::assertIsArray($info);
|
||||
self::assertSame($size, $info[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{0:int}>
|
||||
*/
|
||||
public static function sizeProvider(): array
|
||||
{
|
||||
return array_map(static fn (int $size): array => [$size], QrOptions::SIZES);
|
||||
}
|
||||
|
||||
public function testSvgIsVectorAndCarriesTheColors(): void
|
||||
{
|
||||
$svg = $this->renderer()->svg(
|
||||
'https://kurz.test/messe2026',
|
||||
new QrOptions(foreground: '#0b4f9e', background: '#fff7ed', size: 1024),
|
||||
);
|
||||
|
||||
self::assertStringContainsString('<svg', $svg);
|
||||
self::assertStringContainsString('viewBox', $svg);
|
||||
self::assertStringContainsString('#0b4f9e', $svg);
|
||||
self::assertStringContainsString('#fff7ed', $svg);
|
||||
// Vektor statt eingebetteter Pixelgrafik.
|
||||
self::assertStringNotContainsString('data:image/png', $svg);
|
||||
}
|
||||
|
||||
public function testMarginZeroProducesSmallerQuietZoneThanMarginTen(): void
|
||||
{
|
||||
$data = 'https://kurz.test/abc';
|
||||
|
||||
$withoutMargin = $this->renderer()->png($data, new QrOptions(size: 512, margin: 0));
|
||||
$withMargin = $this->renderer()->png($data, new QrOptions(size: 512, margin: 10));
|
||||
|
||||
// Beide behalten die Zielgroesse ...
|
||||
self::assertSame(512, (getimagesizefromstring($withoutMargin) ?: [0])[0]);
|
||||
self::assertSame(512, (getimagesizefromstring($withMargin) ?: [0])[0]);
|
||||
// ... unterscheiden sich aber im Bildinhalt.
|
||||
self::assertNotSame($withoutMargin, $withMargin);
|
||||
}
|
||||
|
||||
public function testLogoForcesErrorCorrectionLevelH(): void
|
||||
{
|
||||
$logo = $this->uploadDir . '/logo.png';
|
||||
$this->writePngLogo($logo);
|
||||
|
||||
$options = (new QrOptions(errorCorrection: 'L'))->withLogoPath('logo.png');
|
||||
|
||||
self::assertSame('H', $options->errorCorrection);
|
||||
|
||||
$png = $this->renderer()->png('https://kurz.test/mitlogo', $options);
|
||||
self::assertStringStartsWith("\x89PNG", $png);
|
||||
}
|
||||
|
||||
public function testSvgLogoIsEmbeddedInSvgOutput(): void
|
||||
{
|
||||
file_put_contents(
|
||||
$this->uploadDir . '/logo.svg',
|
||||
'<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">'
|
||||
. '<circle cx="5" cy="5" r="5" fill="#ff0000"/></svg>',
|
||||
);
|
||||
|
||||
$svg = $this->renderer()->svg(
|
||||
'https://kurz.test/mitlogo',
|
||||
(new QrOptions())->withLogoPath('logo.svg'),
|
||||
);
|
||||
|
||||
self::assertStringContainsString('<image', $svg);
|
||||
self::assertStringContainsString('data:image/svg+xml;base64,', $svg);
|
||||
}
|
||||
|
||||
public function testDifferentColorsProduceDifferentImages(): void
|
||||
{
|
||||
$data = 'https://kurz.test/farben';
|
||||
|
||||
self::assertNotSame(
|
||||
$this->renderer()->png($data, new QrOptions(foreground: '#000000')),
|
||||
$this->renderer()->png($data, new QrOptions(foreground: '#0b4f9e')),
|
||||
);
|
||||
}
|
||||
|
||||
public function testOptionsAreClampedToValidRanges(): void
|
||||
{
|
||||
$options = new QrOptions(
|
||||
foreground: 'unsinn',
|
||||
size: 999,
|
||||
margin: 99,
|
||||
errorCorrection: 'X',
|
||||
logoScale: 500,
|
||||
);
|
||||
|
||||
self::assertSame('#000000', $options->foreground);
|
||||
self::assertSame(512, $options->size);
|
||||
self::assertSame(QrOptions::MAX_MARGIN, $options->margin);
|
||||
self::assertSame('M', $options->errorCorrection);
|
||||
self::assertSame(QrOptions::MAX_LOGO_SCALE, $options->logoScale);
|
||||
}
|
||||
|
||||
private function writePngLogo(string $path): void
|
||||
{
|
||||
$image = imagecreatetruecolor(120, 120);
|
||||
self::assertNotFalse($image);
|
||||
imagefill($image, 0, 0, (int) imagecolorallocate($image, 255, 0, 0));
|
||||
imagepng($image, $path);
|
||||
imagedestroy($image);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user