chore: complete update

This commit is contained in:
2025-07-17 16:24:20 +02:00
parent 899227b0a4
commit 64a7051137
1300 changed files with 85570 additions and 2756 deletions

View File

@@ -0,0 +1,54 @@
<?php
namespace Tests\Framework\DateTime;
use App\Framework\DateTime\Clock;
use App\Framework\DateTime\ClockInitializer;
use App\Framework\DateTime\FrozenClock;
use App\Framework\DateTime\SystemClock;
class ClockInitializerTest extends \PHPUnit\Framework\TestCase
{
public function testDefaultInitialization(): void
{
$initializer = new ClockInitializer();
$clock = $initializer();
$this->assertInstanceOf(SystemClock::class, $clock);
$this->assertEquals('UTC', $clock->now()->getTimezone()->getName());
}
public function testCustomTimezone(): void
{
$initializer = new ClockInitializer('Europe/Berlin');
$clock = $initializer();
$this->assertInstanceOf(SystemClock::class, $clock);
$this->assertEquals('Europe/Berlin', $clock->now()->getTimezone()->getName());
}
public function testFrozenClockInitialization(): void
{
$initializer = new ClockInitializer(
useFrozenClock: true,
frozenTime: '2021-01-01 12:00:00'
);
$clock = $initializer();
$this->assertInstanceOf(FrozenClock::class, $clock);
$this->assertEquals('2021-01-01 12:00:00', $clock->now()->format('Y-m-d H:i:s'));
}
public function testFrozenClockWithCustomTimezone(): void
{
$initializer = new ClockInitializer(
timezone: 'Europe/Berlin',
useFrozenClock: true,
frozenTime: '2021-01-01 12:00:00'
);
$clock = $initializer();
$this->assertInstanceOf(FrozenClock::class, $clock);
$this->assertEquals('Europe/Berlin', $clock->now()->getTimezone()->getName());
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Tests\Framework\DateTime;
use App\Framework\DateTime\DateRange;
class DateRangeTest extends \PHPUnit\Framework\TestCase
{
public function testConstructorWithValidDates(): void
{
$start = new \DateTimeImmutable('2021-01-01');
$end = new \DateTimeImmutable('2021-01-31');
$range = new DateRange($start, $end);
$this->assertSame($start, $range->getStart());
$this->assertSame($end, $range->getEnd());
}
public function testConstructorWithInvalidDatesThrowsException(): void
{
$start = new \DateTimeImmutable('2021-01-31');
$end = new \DateTimeImmutable('2021-01-01');
$this->expectException(\InvalidArgumentException::class);
new DateRange($start, $end);
}
public function testFromStrings(): void
{
$range = DateRange::fromStrings('2021-01-01', '2021-01-31');
$this->assertEquals('2021-01-01', $range->getStart()->format('Y-m-d'));
$this->assertEquals('2021-01-31', $range->getEnd()->format('Y-m-d'));
}
public function testContains(): void
{
$range = DateRange::fromStrings('2021-01-01', '2021-01-31');
$inside = new \DateTimeImmutable('2021-01-15');
$onStart = new \DateTimeImmutable('2021-01-01');
$onEnd = new \DateTimeImmutable('2021-01-31');
$before = new \DateTimeImmutable('2020-12-31');
$after = new \DateTimeImmutable('2021-02-01');
$this->assertTrue($range->contains($inside));
$this->assertTrue($range->contains($onStart));
$this->assertTrue($range->contains($onEnd));
$this->assertFalse($range->contains($before));
$this->assertFalse($range->contains($after));
}
public function testContainsRange(): void
{
$mainRange = DateRange::fromStrings('2021-01-01', '2021-01-31');
$inside = DateRange::fromStrings('2021-01-10', '2021-01-20');
$sameRange = DateRange::fromStrings('2021-01-01', '2021-01-31');
$overlapping = DateRange::fromStrings('2021-01-15', '2021-02-15');
$outside = DateRange::fromStrings('2021-02-01', '2021-02-28');
$this->assertTrue($mainRange->containsRange($inside));
$this->assertTrue($mainRange->containsRange($sameRange));
$this->assertFalse($mainRange->containsRange($overlapping));
$this->assertFalse($mainRange->containsRange($outside));
}
public function testOverlaps(): void
{
$mainRange = DateRange::fromStrings('2021-01-01', '2021-01-31');
$inside = DateRange::fromStrings('2021-01-10', '2021-01-20');
$overlappingStart = DateRange::fromStrings('2020-12-15', '2021-01-15');
$overlappingEnd = DateRange::fromStrings('2021-01-15', '2021-02-15');
$outside = DateRange::fromStrings('2021-02-01', '2021-02-28');
$this->assertTrue($mainRange->overlaps($inside));
$this->assertTrue($mainRange->overlaps($overlappingStart));
$this->assertTrue($mainRange->overlaps($overlappingEnd));
$this->assertFalse($mainRange->overlaps($outside));
}
public function testGetDuration(): void
{
$range = DateRange::fromStrings('2021-01-01', '2021-01-31');
$duration = $range->getDuration();
$this->assertInstanceOf(\DateInterval::class, $duration);
$this->assertEquals(30, $duration->days);
}
public function testGetDurationInSeconds(): void
{
$range = DateRange::fromStrings('2021-01-01', '2021-01-02');
$seconds = $range->getDurationInSeconds();
// Ein Tag hat 86400 Sekunden
$this->assertEquals(86400, $seconds);
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Tests\Framework\DateTime;
use App\Framework\DateTime\DateTimeFormatter;
class DateTimeFormatterTest extends \PHPUnit\Framework\TestCase
{
private \DateTimeImmutable $sampleDate;
private DateTimeFormatter $formatter;
protected function setUp(): void
{
$this->sampleDate = new \DateTimeImmutable('2021-01-01 12:34:56', new \DateTimeZone('UTC'));
$this->formatter = new DateTimeFormatter();
}
public function testFormatIso8601(): void
{
$formatted = $this->formatter->formatIso8601($this->sampleDate);
$this->assertEquals('2021-01-01T12:34:56+00:00', $formatted);
}
public function testFormatSql(): void
{
$formatted = $this->formatter->formatSql($this->sampleDate);
$this->assertEquals('2021-01-01 12:34:56', $formatted);
}
public function testFormatDate(): void
{
$formatted = $this->formatter->formatDate($this->sampleDate);
$this->assertEquals('2021-01-01', $formatted);
}
public function testFormatTime(): void
{
$formatted = $this->formatter->formatTime($this->sampleDate);
$this->assertEquals('12:34:56', $formatted);
}
public function testCustomFormat(): void
{
$formatted = $this->formatter->format($this->sampleDate, 'd.m.Y H:i');
$this->assertEquals('01.01.2021 12:34', $formatted);
}
public function testWithCustomTimezone(): void
{
$formatter = new DateTimeFormatter('Europe/Berlin');
$formatted = $formatter->format($this->sampleDate, 'Y-m-d H:i:s T');
// UTC 12:34:56 sollte in Berlin 13:34:56 sein (während Standardzeit/Winterzeit)
$this->assertEquals('2021-01-01 13:34:56 CET', $formatted);
}
public function testWithDateTimeObject(): void
{
$dateTime = new \DateTime('2021-01-01 12:34:56', new \DateTimeZone('UTC'));
$formatted = $this->formatter->formatIso8601($dateTime);
$this->assertEquals('2021-01-01T12:34:56+00:00', $formatted);
}
}

View File

@@ -0,0 +1,138 @@
<?php
declare(strict_types=1);
namespace Tests\Framework\DateTime;
use App\Framework\DateTime\DateTime;
use App\Framework\DateTime\Exceptions\InvalidDateTimeException;
use App\Framework\DateTime\Exceptions\InvalidTimezoneException;
use PHPUnit\Framework\TestCase;
class DateTimeTest extends TestCase
{
protected function setUp(): void
{
// Sicherheitsvorkehrung: Default-Timezone vor jedem Test zurücksetzen
if (method_exists(DateTime::class, 'setDefaultTimezone')) {
DateTime::setDefaultTimezone('UTC');
}
}
public function testNowReturnsCurrentDateTime(): void
{
$now = DateTime::now();
$this->assertInstanceOf(\DateTimeImmutable::class, $now);
$this->assertEquals('UTC', $now->getTimezone()->getName());
$this->assertLessThanOrEqual(2, abs(time() - $now->getTimestamp()));
}
public function testFromTimestamp(): void
{
$timestamp = 1609459200; // 2021-01-01 00:00:00 UTC
$date = DateTime::fromTimestamp($timestamp);
$this->assertEquals($timestamp, $date->getTimestamp());
$this->assertEquals('UTC', $date->getTimezone()->getName());
}
public function testFromString(): void
{
$date = DateTime::fromString('2021-01-01 12:00:00');
$this->assertEquals('2021-01-01 12:00:00', $date->format('Y-m-d H:i:s'));
$this->assertEquals('UTC', $date->getTimezone()->getName());
}
public function testFromFormat(): void
{
$date = DateTime::fromFormat('01/01/2021', 'd/m/Y');
$this->assertEquals('2021-01-01', $date->format('Y-m-d'));
}
public function testFromDateTime(): void
{
$originalDate = new \DateTime('2021-01-01 12:00:00');
$immutableDate = DateTime::fromDateTime($originalDate);
$this->assertInstanceOf(\DateTimeImmutable::class, $immutableDate);
$this->assertEquals($originalDate->format('Y-m-d H:i:s'), $immutableDate->format('Y-m-d H:i:s'));
}
public function testCreateInterval(): void
{
$interval = DateTime::createInterval('P1D');
$this->assertInstanceOf(\DateInterval::class, $interval);
$this->assertEquals(1, $interval->d);
}
public function testCreateTimezone(): void
{
$timezone = DateTime::createTimezone('Europe/Berlin');
$this->assertInstanceOf(\DateTimeZone::class, $timezone);
$this->assertEquals('Europe/Berlin', $timezone->getName());
}
public function testInvalidTimezoneThrowsException(): void
{
$this->expectException(InvalidTimezoneException::class);
DateTime::createTimezone('Invalid/Timezone');
}
public function testInvalidDateTimeThrowsException(): void
{
$this->expectException(InvalidDateTimeException::class);
DateTime::fromString('not a date');
}
public function testInvalidFormatThrowsException(): void
{
$this->expectException(InvalidDateTimeException::class);
DateTime::fromFormat('2021-01-01', 'invalid format');
}
public function testInvalidIntervalThrowsException(): void
{
$this->expectException(InvalidDateTimeException::class);
DateTime::createInterval('invalid interval');
}
public function testSetDefaultTimezone(): void
{
DateTime::setDefaultTimezone('Europe/Berlin');
$now = DateTime::now();
$this->assertEquals('Europe/Berlin', $now->getTimezone()->getName());
}
public function testToday(): void
{
$today = DateTime::today();
$expected = (new \DateTimeImmutable('today'))->format('Y-m-d');
$this->assertEquals($expected, $today->format('Y-m-d'));
$this->assertEquals('00:00:00', $today->format('H:i:s'));
}
public function testTomorrow(): void
{
$tomorrow = DateTime::tomorrow();
$expected = (new \DateTimeImmutable('tomorrow'))->format('Y-m-d');
$this->assertEquals($expected, $tomorrow->format('Y-m-d'));
$this->assertEquals('00:00:00', $tomorrow->format('H:i:s'));
}
public function testYesterday(): void
{
$yesterday = DateTime::yesterday();
$expected = (new \DateTimeImmutable('yesterday'))->format('Y-m-d');
$this->assertEquals($expected, $yesterday->format('Y-m-d'));
$this->assertEquals('00:00:00', $yesterday->format('H:i:s'));
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace Tests\Framework\DateTime;
use App\Framework\DateTime\FrozenClock;
class FrozenClockTest extends \PHPUnit\Framework\TestCase
{
public function testNowReturnsFrozenTime(): void
{
$frozenTime = new \DateTimeImmutable('2021-01-01 00:00:00');
$clock = new FrozenClock($frozenTime);
$this->assertEquals($frozenTime, $clock->now());
// Überprüfen, dass die Zeit eingefroren ist
sleep(1);
$this->assertEquals($frozenTime, $clock->now());
}
public function testFromTimestampReturnsCorrectDateTime(): void
{
$clock = new FrozenClock('2021-01-01 00:00:00');
$timestamp = 1609459200; // 2021-01-01 00:00:00 UTC
$date = $clock->fromTimestamp($timestamp);
$this->assertInstanceOf(\DateTimeImmutable::class, $date);
$this->assertEquals($timestamp, $date->getTimestamp());
}
public function testFromStringReturnsCorrectDateTime(): void
{
$clock = new FrozenClock('2021-01-01 00:00:00');
$date = $clock->fromString('2021-02-01 00:00:00');
$this->assertInstanceOf(\DateTimeImmutable::class, $date);
$this->assertEquals('2021-02-01', $date->format('Y-m-d'));
$this->assertEquals('00:00:00', $date->format('H:i:s'));
}
public function testSetToChangesTime(): void
{
$clock = new FrozenClock('2021-01-01 00:00:00');
$newTime = new \DateTimeImmutable('2021-02-01 00:00:00');
$clock->setTo($newTime);
$this->assertEquals($newTime, $clock->now());
$clock->setTo('2021-03-01 00:00:00');
$this->assertEquals('2021-03-01', $clock->now()->format('Y-m-d'));
}
public function testMoveForwardAdvancesTime(): void
{
$clock = new FrozenClock('2021-01-01 00:00:00');
$clock->moveForward('P1D'); // 1 Tag vorwärts
$this->assertEquals('2021-01-02', $clock->now()->format('Y-m-d'));
$clock->moveForward('PT1H'); // 1 Stunde vorwärts
$this->assertEquals('2021-01-02 01:00:00', $clock->now()->format('Y-m-d H:i:s'));
}
public function testMoveBackwardReversesTime(): void
{
$clock = new FrozenClock('2021-01-10 10:00:00');
$clock->moveBackward('P5D'); // 5 Tage zurück
$this->assertEquals('2021-01-05', $clock->now()->format('Y-m-d'));
$clock->moveBackward('PT5H'); // 5 Stunden zurück
$this->assertEquals('2021-01-05 05:00:00', $clock->now()->format('Y-m-d H:i:s'));
}
public function testCustomTimezone(): void
{
$clock = new FrozenClock('2021-01-01 00:00:00', 'Europe/Berlin');
$now = $clock->now();
$this->assertEquals('Europe/Berlin', $now->getTimezone()->getName());
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Tests\Framework\DateTime;
use App\Framework\DateTime\SystemClock;
class SystemClockTest extends \PHPUnit\Framework\TestCase
{
public function testNowReturnsCurrentDateTime(): void
{
$clock = new SystemClock();
$now = $clock->now();
$this->assertInstanceOf(\DateTimeImmutable::class, $now);
$this->assertEquals('UTC', $now->getTimezone()->getName());
// Überprüfen, dass das Datum innerhalb von 2 Sekunden liegt (für Testlaufzeit)
$this->assertLessThanOrEqual(2, abs(time() - $now->getTimestamp()));
}
public function testFromTimestampReturnsCorrectDateTime(): void
{
$clock = new SystemClock();
$timestamp = 1609459200; // 2021-01-01 00:00:00 UTC
$date = $clock->fromTimestamp($timestamp);
$this->assertInstanceOf(\DateTimeImmutable::class, $date);
$this->assertEquals($timestamp, $date->getTimestamp());
$this->assertEquals('UTC', $date->getTimezone()->getName());
}
public function testFromStringReturnsCorrectDateTime(): void
{
$clock = new SystemClock();
$date = $clock->fromString('2021-01-01 00:00:00');
$this->assertInstanceOf(\DateTimeImmutable::class, $date);
$this->assertEquals('2021-01-01', $date->format('Y-m-d'));
$this->assertEquals('00:00:00', $date->format('H:i:s'));
}
public function testFromStringWithFormatReturnsCorrectDateTime(): void
{
$clock = new SystemClock();
$date = $clock->fromString('01/01/2021', 'd/m/Y');
$this->assertInstanceOf(\DateTimeImmutable::class, $date);
$this->assertEquals('2021-01-01', $date->format('Y-m-d'));
}
public function testFromStringWithInvalidFormatThrowsException(): void
{
$clock = new SystemClock();
$this->expectException(\Exception::class);
$clock->fromString('not a date', 'Y-m-d');
}
public function testClockWithCustomTimezone(): void
{
$clock = new SystemClock('Europe/Berlin');
$now = $clock->now();
$this->assertEquals('Europe/Berlin', $now->getTimezone()->getName());
}
}

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace Tests\Framework\Filesystem;
use App\Framework\Filesystem\Exceptions\FileNotFoundException;
use App\Framework\Filesystem\FileStorage;
use App\Framework\Filesystem\InMemoryStorage;
test('speichert und lädt Dateien', function () {
$tempFile = tempnam(sys_get_temp_dir(), 'test_');
$content = 'Testinhalt ' . uniqid();
$storage = new FileStorage();
$storage->put($tempFile, $content);
expect($storage->exists($tempFile))->toBeTrue();
expect($storage->get($tempFile))->toBe($content);
expect($storage->size($tempFile))->toBe(strlen($content));
// Aufräumen
$storage->delete($tempFile);
expect($storage->exists($tempFile))->toBeFalse();
});
test('wirft Exception bei nicht existierender Datei', function () {
$storage = new FileStorage();
$nonExistingFile = '/tmp/doesnt_exist_' . uniqid();
expect(fn() => $storage->get($nonExistingFile))
->toThrow(FileNotFoundException::class);
});
test('kopiert Dateien', function () {
$tempFile = tempnam(sys_get_temp_dir(), 'src_');
$destFile = tempnam(sys_get_temp_dir(), 'dest_');
unlink($destFile); // Löschen, damit copy funktioniert
$content = 'Kopierinhalt ' . uniqid();
$storage = new FileStorage();
$storage->put($tempFile, $content);
$storage->copy($tempFile, $destFile);
expect($storage->exists($destFile))->toBeTrue();
expect($storage->get($destFile))->toBe($content);
// Aufräumen
$storage->delete($tempFile);
$storage->delete($destFile);
});
test('InMemoryStorage funktioniert wie FileStorage', function () {
$storage = new InMemoryStorage();
$path = '/virtual/test.txt';
$content = 'Virtueller Inhalt';
$storage->put($path, $content);
expect($storage->exists($path))->toBeTrue();
expect($storage->get($path))->toBe($content);
expect($storage->size($path))->toBe(strlen($content));
$copyPath = '/virtual/copy.txt';
$storage->copy($path, $copyPath);
expect($storage->get($copyPath))->toBe($content);
$storage->delete($path);
expect($storage->exists($path))->toBeFalse();
});

View File

@@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace Tests\Framework\Filesystem;
use App\Framework\Filesystem\File;
use App\Framework\Filesystem\Directory;
use App\Framework\Filesystem\FilesystemFactory;
use App\Framework\Filesystem\InMemoryStorage;
use App\Framework\Filesystem\FileStorage;
it('lädt File-Properties erst bei Bedarf', function() {
// Test-Storage mit Instrumentierung
$storage = new class extends InMemoryStorage {
public array $accessed = [];
public function get(string $path): string {
$this->accessed[] = "get:{$path}";
return parent::get($path);
}
public function size(string $path): int {
$this->accessed[] = "size:{$path}";
return parent::size($path);
}
};
// Testdatei hinzufügen
$storage->addFile('/test.txt', 'Testinhalt');
// Lazy File erstellen
$file = $storage->file('/test.txt');
// Sollte noch keine Storage-Methoden aufgerufen haben
expect($storage->accessed)->toBeEmpty();
// Zugriff auf path sollte kein Laden auslösen
$path = $file->path;
expect($storage->accessed)->toBeEmpty();
// Zugriff auf contents sollte get() auslösen
$contents = $file->contents;
expect($storage->accessed)->toContain('get:/test.txt');
expect($contents)->toBe('Testinhalt');
// Zugriff auf size sollte size() auslösen
$size = $file->size;
expect($storage->accessed)->toContain('size:/test.txt');
expect($size)->toBe(10); // Länge von 'Testinhalt'
});
it('lädt Directory-Properties erst bei Bedarf', function() {
// Test-Storage mit Instrumentierung
$storage = new class extends InMemoryStorage {
public array $accessed = [];
public function listDirectory(string $directory): array {
$this->accessed[] = "list:{$directory}";
return parent::listDirectory($directory);
}
};
// Testverzeichnis erstellen
$storage->createDirectory('/test-dir');
$storage->addFile('/test-dir/file1.txt', 'Datei 1');
$storage->addFile('/test-dir/file2.txt', 'Datei 2');
// Lazy Directory erstellen
$dir = $storage->directory('/test-dir');
// Sollte noch keine Storage-Methoden aufgerufen haben
expect($storage->accessed)->toBeEmpty();
// Zugriff auf path sollte kein Laden auslösen
$path = $dir->path;
expect($storage->accessed)->toBeEmpty();
// Zugriff auf contents sollte listDirectory() auslösen
$contents = $dir->contents;
expect($storage->accessed)->toContain('list:/test-dir');
expect($contents)->toHaveCount(2);
});
it('kann mit echtem FileStorage arbeiten', function() {
// Dieser Test kann übersprungen werden, wenn keine Schreibrechte im Temp-Verzeichnis vorhanden sind
$tempDir = sys_get_temp_dir() . '/php-lazy-test-' . uniqid();
@mkdir($tempDir, 0777, true);
if (!is_dir($tempDir) || !is_writable($tempDir)) {
$this->markTestSkipped('Kein Schreibzugriff im Temp-Verzeichnis');
}
try {
// Echten FileStorage verwenden
$storage = new FileStorage();
// Testdatei erstellen
$testFile = $tempDir . '/test.txt';
file_put_contents($testFile, 'Lazy Loading Test');
// Lazy File erstellen
$file = $storage->file($testFile);
// Properties testen
expect($file->path)->toBe($testFile);
expect($file->contents)->toBe('Lazy Loading Test');
expect($file->size)->toBe(17); // Länge von 'Lazy Loading Test'
expect($file->lastModified)->toBeGreaterThan(time() - 10);
// Directory testen
$dir = $storage->directory($tempDir);
expect($dir->exists())->toBeTrue();
$files = $dir->getFiles();
expect($files)->toHaveCount(1);
expect($files[0]->path)->toEndWith('/test.txt');
} finally {
// Aufräumen
@unlink($testFile);
@rmdir($tempDir);
}
});

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Tests\Framework\StaticSite;
use App\Framework\Router\HttpRouter;
use App\Framework\Router\Route;
use App\Framework\Router\RouteCollection;
use App\Framework\StaticSite\RouteCollector;
use PHPUnit\Framework\TestCase;
class RouteCollectorTest extends TestCase
{
public function testCollectRoutes(): void
{
// Testdaten vorbereiten
$routes = [
new Route(path: '/', controller: 'TestController', action: 'index', methods: ['GET']),
new Route(path: '/about', controller: 'TestController', action: 'about', methods: ['GET']),
new Route(path: '/api/data', controller: 'ApiController', action: 'getData', methods: ['GET']),
new Route(path: '/contact', controller: 'TestController', action: 'contact', methods: ['POST']),
new Route(path: '/users/{id}', controller: 'UserController', action: 'show', methods: ['GET']),
];
$routeCollection = $this->createMock(RouteCollection::class);
$routeCollection->method('getAll')->willReturn($routes);
$router = $this->createMock(HttpRouter::class);
$router->method('getRoutes')->willReturn($routeCollection);
// RouteCollector initialisieren
$collector = new RouteCollector($router);
// Routen sammeln
$collectedRoutes = $collector->collectRoutes();
// Prüfen, ob die richtigen Routen gesammelt wurden (nur GET-Methoden)
$this->assertContains('/', $collectedRoutes);
$this->assertContains('/about', $collectedRoutes);
$this->assertContains('/api/data', $collectedRoutes);
$this->assertContains('/users/{id}', $collectedRoutes);
$this->assertNotContains('/contact', $collectedRoutes); // POST-Route sollte nicht enthalten sein
}
public function testFilterRoutes(): void
{
// Testdaten vorbereiten
$routes = [
'/',
'/about',
'/api/data',
'/api/users',
'/admin/dashboard',
'/users/{id}',
'/products/{slug}',
];
$excludePatterns = [
'/\/api\/.*/', // API-Routen ausschließen
'/\/admin\/.*/', // Admin-Routen ausschließen
];
$router = $this->createMock(HttpRouter::class);
$collector = new RouteCollector($router);
// Routen filtern
$filteredRoutes = $collector->filterRoutes($routes, $excludePatterns);
// Prüfen, ob die richtigen Routen gefiltert wurden
$this->assertContains('/', $filteredRoutes);
$this->assertContains('/about', $filteredRoutes);
$this->assertNotContains('/api/data', $filteredRoutes); // Sollte durch Pattern ausgeschlossen sein
$this->assertNotContains('/api/users', $filteredRoutes); // Sollte durch Pattern ausgeschlossen sein
$this->assertNotContains('/admin/dashboard', $filteredRoutes); // Sollte durch Pattern ausgeschlossen sein
$this->assertNotContains('/users/{id}', $filteredRoutes); // Sollte durch Platzhalter ausgeschlossen sein
$this->assertNotContains('/products/{slug}', $filteredRoutes); // Sollte durch Platzhalter ausgeschlossen sein
}
}

View File

@@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace Tests\Framework\StaticSite;
use App\Framework\Attributes\StaticPage;
use App\Framework\Router\HttpRouter;
use App\Framework\Router\Route;
use App\Framework\Router\RouteCollection;
use App\Framework\StaticSite\StaticPageCollector;
use PHPUnit\Framework\TestCase;
class StaticPageCollectorTest extends TestCase
{
public function testCollectStaticPages(): void
{
// Controller-Klasse mit StaticPage-Attribut simulieren
$controllerClass = 'TestStaticPageController';
$this->createTestControllerClass($controllerClass);
// Testdaten vorbereiten
$routes = [
new Route(path: '/', controller: $controllerClass, action: 'indexAction', methods: ['GET']),
new Route(path: '/about', controller: $controllerClass, action: 'aboutAction', methods: ['GET']),
new Route(path: '/contact', controller: $controllerClass, action: 'contactAction', methods: ['GET']),
new Route(path: '/api/data', controller: 'ApiController', action: 'getData', methods: ['GET']),
];
$routeCollection = $this->createMock(RouteCollection::class);
$routeCollection->method('getAll')->willReturn($routes);
$router = $this->createMock(HttpRouter::class);
$router->method('getRoutes')->willReturn($routeCollection);
// StaticPageCollector initialisieren
$collector = new StaticPageCollector($router);
// Statische Seiten sammeln
$staticPages = $collector->collectStaticPages();
// Prüfen, ob die richtigen Seiten gesammelt wurden
$this->assertContains('/', $staticPages);
$this->assertContains('/about', $staticPages);
$this->assertNotContains('/contact', $staticPages); // Kein StaticPage-Attribut
$this->assertNotContains('/api/data', $staticPages); // Anderer Controller ohne StaticPage-Attribut
}
public function testCollectAllGetRoutes(): void
{
// Testdaten vorbereiten
$routes = [
new Route(path: '/', controller: 'TestController', action: 'index', methods: ['GET']),
new Route(path: '/about', controller: 'TestController', action: 'about', methods: ['GET']),
new Route(path: '/contact', controller: 'TestController', action: 'contact', methods: ['POST']),
new Route(path: '/api/data', controller: 'ApiController', action: 'getData', methods: ['GET']),
];
$routeCollection = $this->createMock(RouteCollection::class);
$routeCollection->method('getAll')->willReturn($routes);
$router = $this->createMock(HttpRouter::class);
$router->method('getRoutes')->willReturn($routeCollection);
// StaticPageCollector initialisieren
$collector = new StaticPageCollector($router);
// Alle GET-Routen sammeln
$allGetRoutes = $collector->collectAllGetRoutes();
// Prüfen, ob die richtigen Seiten gesammelt wurden
$this->assertContains('/', $allGetRoutes);
$this->assertContains('/about', $allGetRoutes);
$this->assertContains('/api/data', $allGetRoutes);
$this->assertNotContains('/contact', $allGetRoutes); // POST-Route sollte nicht enthalten sein
}
/**
* Erstellt eine temporäre Controller-Klasse mit StaticPage-Attributen für Tests
*/
private function createTestControllerClass(string $className): void
{
if (class_exists($className)) {
return;
}
$code = <<<EOT
class {$className} {
#[\App\Framework\Attributes\StaticPage]
public function indexAction() {}
#[\App\Framework\Attributes\StaticPage(outputPath: 'custom-about')]
public function aboutAction() {}
// Keine StaticPage-Annotation
public function contactAction() {}
}
EOT;
eval($code);
}
}

View File

@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
namespace Tests\Framework\StaticSite;
use App\Framework\Core\Application;
use App\Framework\Http\Method;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\Request;
use App\Framework\Http\Response;
use App\Framework\StaticSite\StaticSiteGenerator;
use PHPUnit\Framework\TestCase;
class StaticSiteGeneratorTest extends TestCase
{
private $outputDir;
private $app;
protected function setUp(): void
{
parent::setUp();
// Temporäres Verzeichnis für Tests erstellen
$this->outputDir = sys_get_temp_dir() . '/static-site-test-' . uniqid();
mkdir($this->outputDir, 0755, true);
// Mock für Application erstellen
$this->app = $this->createMock(Application::class);
}
protected function tearDown(): void
{
parent::tearDown();
// Temporäres Verzeichnis nach dem Test löschen
$this->removeDirectory($this->outputDir);
}
/**
* Hilfsfunktion zum rekursiven Löschen eines Verzeichnisses
*/
private function removeDirectory(string $dir): void
{
if (!is_dir($dir)) {
return;
}
$files = array_diff(scandir($dir), ['.', '..']);
foreach ($files as $file) {
$path = $dir . '/' . $file;
is_dir($path) ? $this->removeDirectory($path) : unlink($path);
}
rmdir($dir);
}
public function testGenerateCreatesStaticFiles(): void
{
// Testdaten vorbereiten
$routes = ['/', '/about', '/blog/post-1'];
$responseBody = '<html><body>Test Content</body></html>';
// Mock für Response konfigurieren
$response = $this->createMock(Response::class);
$response->method('getBody')->willReturn($responseBody);
// App-Mock konfigurieren, um Response zurückzugeben
$this->app->method('handleRequest')->willReturn($response);
// StaticSiteGenerator initialisieren
$generator = new StaticSiteGenerator($this->app, $routes, $this->outputDir);
// Statische Seiten generieren
$generator->generate();
// Prüfen, ob die erwarteten Dateien erstellt wurden
$this->assertFileExists($this->outputDir . '/index.html');
$this->assertFileExists($this->outputDir . '/about/index.html');
$this->assertFileExists($this->outputDir . '/blog/post-1/index.html');
// Prüfen, ob der Inhalt korrekt ist
$this->assertEquals($responseBody, file_get_contents($this->outputDir . '/index.html'));
}
public function testGenerateHandlesExceptions(): void
{
// Testdaten vorbereiten
$routes = ['/error-page'];
// App-Mock konfigurieren, um eine Exception zu werfen
$this->app->method('handleRequest')->willThrowException(new \Exception('Test Exception'));
// StaticSiteGenerator initialisieren
$generator = new StaticSiteGenerator($this->app, $routes, $this->outputDir);
// Output-Buffer verwenden, um die Echo-Ausgaben zu erfassen
ob_start();
$generator->generate();
$output = ob_get_clean();
// Prüfen, ob die Fehlermeldung ausgegeben wurde
$this->assertStringContainsString('Fehler beim Generieren von /error-page', $output);
// Prüfen, ob keine Datei erstellt wurde
$this->assertFileDoesNotExist($this->outputDir . '/error-page/index.html');
}
public function testGetFilePathForRoute(): void
{
// StaticSiteGenerator mit Reflection für private Methoden testen
$generator = new StaticSiteGenerator($this->app, [], $this->outputDir);
$reflection = new \ReflectionClass($generator);
$method = $reflection->getMethod('getFilePathForRoute');
$method->setAccessible(true);
// Verschiedene Routentypen testen
$this->assertEquals(
$this->outputDir . '/index.html',
$method->invoke($generator, '/')
);
$this->assertEquals(
$this->outputDir . '/about/index.html',
$method->invoke($generator, '/about')
);
$this->assertEquals(
$this->outputDir . '/api/data.json',
$method->invoke($generator, '/api/data.json')
);
}
}