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
126 lines
4.4 KiB
PHP
126 lines
4.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests;
|
|
|
|
use App\Database;
|
|
use App\Migrator;
|
|
use App\Repository\SettingRepository;
|
|
use App\Service\DomainChecker;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Die vollstaendige Kette (Domain kommt bei dieser Installation an)
|
|
* laesst sich nur gegen einen laufenden Server pruefen - das passiert
|
|
* im End-to-End-Test. Hier geht es um die Teile, die ohne Netz
|
|
* eindeutig sind.
|
|
*/
|
|
final class DomainCheckerTest extends TestCase
|
|
{
|
|
private DomainChecker $checker;
|
|
private SettingRepository $settings;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$db = Database::sqlite(':memory:');
|
|
(new Migrator($db, dirname(__DIR__) . '/migrations'))->migrate();
|
|
|
|
$this->settings = new SettingRepository($db);
|
|
$this->checker = new DomainChecker($this->settings);
|
|
}
|
|
|
|
/**
|
|
* @param array<string,mixed> $extra
|
|
* @return array<string,mixed>
|
|
*/
|
|
private function domain(string $baseUrl, array $extra = []): array
|
|
{
|
|
return ['id' => 1, 'base_url' => $baseUrl, 'host' => '', 'is_default' => 0] + $extra;
|
|
}
|
|
|
|
public function testSecretIsStableAcrossCalls(): void
|
|
{
|
|
$first = $this->settings->secret();
|
|
|
|
self::assertSame($first, $this->settings->secret());
|
|
self::assertGreaterThanOrEqual(32, strlen($first));
|
|
}
|
|
|
|
public function testResultHasTheExpectedShape(): void
|
|
{
|
|
$result = $this->checker->check($this->domain('http://127.0.0.1:1'));
|
|
|
|
self::assertArrayHasKey('status', $result);
|
|
self::assertArrayHasKey('summary', $result);
|
|
self::assertArrayHasKey('checks', $result);
|
|
self::assertArrayHasKey('hint', $result);
|
|
self::assertContains($result['status'], [DomainChecker::OK, DomainChecker::WARNING, DomainChecker::ERROR]);
|
|
|
|
foreach ($result['checks'] as $check) {
|
|
self::assertArrayHasKey('label', $check);
|
|
self::assertArrayHasKey('status', $check);
|
|
self::assertArrayHasKey('text', $check);
|
|
self::assertNotSame('', $check['text']);
|
|
}
|
|
}
|
|
|
|
public function testIpAddressNeedsNoDnsEntry(): void
|
|
{
|
|
$result = $this->checker->check($this->domain('http://127.0.0.1:1'));
|
|
|
|
self::assertSame('DNS', $result['checks'][0]['label']);
|
|
self::assertSame(DomainChecker::OK, $result['checks'][0]['status']);
|
|
self::assertStringContainsString('kein DNS-Eintrag', $result['checks'][0]['text']);
|
|
}
|
|
|
|
public function testUnreachableDomainIsReportedAsError(): void
|
|
{
|
|
// Auf Port 1 lauscht nichts.
|
|
$result = $this->checker->check($this->domain('http://127.0.0.1:1'));
|
|
|
|
self::assertSame(DomainChecker::ERROR, $result['status']);
|
|
self::assertStringContainsString('nicht auf diese Installation', $result['summary']);
|
|
self::assertNotNull($result['hint'], 'Bei Misserfolg muss der Hairpinning-Hinweis erscheinen.');
|
|
}
|
|
|
|
public function testNonResolvableHostFails(): void
|
|
{
|
|
// ".invalid" ist laut RFC 2606 reserviert und existiert nie.
|
|
$result = $this->checker->check($this->domain('https://gibt-es-nicht.invalid'));
|
|
|
|
self::assertSame(DomainChecker::ERROR, $result['status']);
|
|
|
|
// Je nach Resolver scheitert schon DNS oder erst der Abruf -
|
|
// in beiden Faellen darf die Domain nicht als in Ordnung gelten.
|
|
$labels = array_column($result['checks'], 'label');
|
|
self::assertContains('DNS', $labels);
|
|
}
|
|
|
|
public function testPortIsStrippedBeforeDnsLookup(): void
|
|
{
|
|
// Enthielte der Hostname noch den Port, waere die Auflösung
|
|
// zwangslaeufig fehlgeschlagen.
|
|
$result = $this->checker->check($this->domain('http://localhost:1'));
|
|
|
|
self::assertSame('DNS', $result['checks'][0]['label']);
|
|
self::assertSame(DomainChecker::OK, $result['checks'][0]['status'], $result['checks'][0]['text']);
|
|
self::assertStringContainsString('localhost', $result['checks'][0]['text']);
|
|
self::assertStringNotContainsString('localhost:1', $result['checks'][0]['text']);
|
|
}
|
|
|
|
public function testCheckDoesNotThrowOnOddInput(): void
|
|
{
|
|
foreach ([
|
|
'http://127.0.0.1:1',
|
|
'https://gibt-es-nicht.invalid',
|
|
'http://localhost:1/unterverzeichnis',
|
|
] as $baseUrl) {
|
|
$result = $this->checker->check($this->domain($baseUrl));
|
|
|
|
self::assertIsArray($result, $baseUrl);
|
|
self::assertNotSame('', $result['summary'], $baseUrl);
|
|
}
|
|
}
|
|
}
|