diff --git a/phpunit.xml b/phpunit.xml index c09b5bc..61c031c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -22,7 +22,8 @@ - + + diff --git a/routes/web.php b/routes/web.php index bb65cc6..f6a3e9a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -8,5 +8,13 @@ Route::get('/author/{author}', [PostController::class, 'authorPosts'])->name('author.posts'); Route::get('/category/{category}', [PostController::class, 'categoryPosts'])->name('category.posts'); Route::get('/tag/{slug}', [PostController::class, 'tagPosts'])->name('tag.posts'); -Route::get('/{year}/{month}', [PostController::class, 'yearMonthPosts'])->name('year-month.posts'); -Route::get('/{year}/{month}/{slug}', [PostController::class, 'show'])->name('post'); +// Mirrors the WordPress permalink structure (%year%/%monthnum%/%postname%). +// Without the constraints these two act as catch-alls for any 2- and +// 3-segment path, answering 200 with an empty archive instead of 404. +Route::get('/{year}/{month}', [PostController::class, 'yearMonthPosts']) + ->where(['year' => '[0-9]{4}', 'month' => '0[1-9]|1[0-2]']) + ->name('year-month.posts'); + +Route::get('/{year}/{month}/{slug}', [PostController::class, 'show']) + ->where(['year' => '[0-9]{4}', 'month' => '0[1-9]|1[0-2]']) + ->name('post'); diff --git a/tests/Feature/ArchiveRouteConstraintsTest.php b/tests/Feature/ArchiveRouteConstraintsTest.php new file mode 100644 index 0000000..d83450a --- /dev/null +++ b/tests/Feature/ArchiveRouteConstraintsTest.php @@ -0,0 +1,55 @@ +get($path)->assertNotFound(); +})->with([ + '/foo/bar', + '/abcd/ef', + '/wp-admin/setup-config.php', + '/assets/app', +]); + +it('rejects out-of-range months', function (string $path) { + $this->get($path)->assertNotFound(); +})->with([ + '/2026/00', + '/2026/13', + '/2026/99', + '/2026/7', // month must be zero padded, as WordPress emits it + '/2026/007', +]); + +it('rejects malformed years', function (string $path) { + $this->get($path)->assertNotFound(); +})->with([ + '/26/07', + '/20260/07', + '/2026a/07', +]); + +it('rejects the same malformed paths on the single post route', function (string $path) { + $this->get($path)->assertNotFound(); +})->with([ + '/foo/bar/some-slug', + '/2026/13/some-slug', + '/2026/7/some-slug', +]); + +it('still matches well formed archive paths', function () { + // Reaching the controller means the route matched. The controller then hits + // wp_posts, which does not exist here, so anything other than 404 proves + // the constraint let the request through. + $this->get('/2026/07')->assertStatus(500); +}); + +it('does not shadow the named static routes', function () { + expect(route('year-month.posts', ['year' => '2026', 'month' => '07'])) + ->toEndWith('/2026/07'); + + expect(route('post', ['year' => '2026', 'month' => '07', 'slug' => 'meu-post'])) + ->toEndWith('/2026/07/meu-post'); +});