Files
SkinbaseNova/tests/Feature/RoutingUnificationTest.php
2026-03-28 19:15:39 +01:00

308 lines
11 KiB
PHP

<?php
declare(strict_types=1);
/**
* Feature tests for the new canonical route families introduced by the
* Routing Unification spec (§3.2 Explore, §4 Blog/Pages, §6.1 redirects).
*/
use App\Models\Category;
use App\Models\ContentType;
// ── /explore routes ──────────────────────────────────────────────────────────
it('GET /explore returns 200', function () {
$this->get('/explore')->assertOk();
});
it('GET /explore contains "Explore" heading', function () {
$this->get('/explore')->assertOk()->assertSee('Explore', false);
});
it('GET /explore/wallpapers redirects to the canonical /wallpapers route', function () {
$this->get('/explore/wallpapers')->assertRedirect('/wallpapers')->assertStatus(301);
});
it('GET /explore/skins redirects to the canonical /skins route', function () {
$this->get('/explore/skins')->assertRedirect('/skins')->assertStatus(301);
});
it('GET /explore/photography redirects to the canonical /photography route', function () {
$this->get('/explore/photography')->assertRedirect('/photography')->assertStatus(301);
});
it('GET /explore/artworks returns 200', function () {
$this->get('/explore/artworks')->assertOk();
});
it('GET /explore/other redirects to the canonical /other route', function () {
$this->get('/explore/other')->assertRedirect('/other')->assertStatus(301);
});
it('GET /explore/wallpapers/trending redirects to the canonical /wallpapers route', function () {
$this->get('/explore/wallpapers/trending')->assertRedirect('/wallpapers')->assertStatus(301);
});
it('GET /explore/wallpapers/latest redirects to the canonical sorted /wallpapers route', function () {
$this->get('/explore/wallpapers/latest')->assertRedirect('/wallpapers?sort=latest')->assertStatus(301);
});
it('GET /explore/wallpapers/best redirects to the canonical sorted /wallpapers route', function () {
$this->get('/explore/wallpapers/best')->assertRedirect('/wallpapers?sort=top-rated')->assertStatus(301);
});
it('GET /explore/wallpapers/new-hot redirects to the canonical sorted /wallpapers route', function () {
$this->get('/explore/wallpapers/new-hot')->assertRedirect('/wallpapers?sort=fresh')->assertStatus(301);
});
it('/explore pages include canonical link tag', function () {
$html = $this->get('/explore')->assertOk()->getContent();
expect($html)->toContain('rel="canonical"');
});
it('/explore pages set robots index,follow', function () {
$html = $this->get('/explore')->assertOk()->getContent();
expect($html)->toContain('index,follow');
});
it('/explore hub page includes breadcrumb JSON-LD', function () {
$html = $this->get('/explore')->assertOk()->getContent();
expect($html)->toContain('BreadcrumbList');
});
// ── 301 redirects from legacy routes ─────────────────────────────────────────
it('GET /browse redirects to /explore with 301', function () {
$this->get('/browse')->assertRedirect('/explore')->assertStatus(301);
});
it('GET /discover redirects to /discover/trending with 301', function () {
$this->get('/discover')->assertRedirect('/discover/trending')->assertStatus(301);
});
it('GET /sections returns the live sections page', function () {
$this->get('/sections')->assertOk()->assertSee('Browse Sections', false);
});
it('GET /browse-categories redirects to /categories with 301', function () {
$this->get('/browse-categories')->assertRedirect('/categories')->assertStatus(301);
});
it('legacy mixed-case category route redirects to the canonical category URL with 301', function () {
$contentType = ContentType::query()->create([
'name' => 'Skins',
'slug' => 'skins',
]);
$category = Category::query()->create([
'content_type_id' => $contentType->id,
'name' => 'BrowserBob',
'slug' => 'browserbob',
]);
$this->get('/Skins/BrowserBob/' . $category->id . '?ref=legacy')
->assertRedirect('/skins/browserbob?ref=legacy')
->assertStatus(301);
});
it('legacy nested category route redirects to the canonical nested category URL with 301', function () {
$contentType = ContentType::query()->create([
'name' => 'Skins',
'slug' => 'skins',
]);
$parent = Category::query()->create([
'content_type_id' => $contentType->id,
'name' => 'BrowserBob',
'slug' => 'browserbob',
]);
$child = Category::query()->create([
'content_type_id' => $contentType->id,
'parent_id' => $parent->id,
'name' => 'sdsdsdsd',
'slug' => 'sdsdsdsd',
]);
$this->get('/Skins/BrowserBob/sdsdsdsd/' . $child->id)
->assertRedirect('/skins/browserbob/sdsdsdsd')
->assertStatus(301);
});
it('legacy category route falls back to /categories and preserves query string when no category matches', function () {
$this->get('/Skins/does-not-exist/999999?source=old-site')
->assertRedirect('/categories?source=old-site')
->assertStatus(301);
});
it('legacy /category route redirects to the canonical category URL with 301', function () {
$contentType = ContentType::query()->create([
'name' => 'Skins',
'slug' => 'skins',
]);
$category = Category::query()->create([
'content_type_id' => $contentType->id,
'name' => 'BrowserBob',
'slug' => 'browserbob',
]);
$this->get('/category/skins/browserbob/' . $category->id . '?ref=legacy-category')
->assertRedirect('/skins/browserbob?ref=legacy-category')
->assertStatus(301);
});
it('legacy /category route falls back to /categories and preserves query string when no category matches', function () {
$this->get('/category/skins/does-not-exist/999999?source=legacy-category')
->assertRedirect('/categories?source=legacy-category')
->assertStatus(301);
});
it('GET /today-in-history redirects to /discover/on-this-day with 301', function () {
$this->get('/today-in-history')->assertRedirect('/discover/on-this-day')->assertStatus(301);
});
it('GET /members redirects to /creators/top with 301', function () {
$this->get('/members')->assertRedirect('/creators/top')->assertStatus(301);
});
it('GET /explore/members redirects to /creators/top with 301', function () {
$this->get('/explore/members')->assertRedirect('/creators/top')->assertStatus(301);
});
it('GET /latest-artworks redirects to /discover/fresh with 301', function () {
$this->get('/latest-artworks')->assertRedirect('/discover/fresh')->assertStatus(301);
});
it('GET /today-downloads redirects to /downloads/today with 301', function () {
$this->get('/today-downloads')->assertRedirect('/downloads/today')->assertStatus(301);
});
it('GET /monthly-commentators redirects to /comments/monthly with 301', function () {
$this->get('/monthly-commentators')->assertRedirect('/comments/monthly')->assertStatus(301);
});
it('GET /top-favourites redirects to /discover/top-rated with 301', function () {
$this->get('/top-favourites')->assertRedirect('/discover/top-rated')->assertStatus(301);
});
it('GET /downloads/today returns 200', function () {
$this->get('/downloads/today')->assertOk();
});
// ── /blog routes ─────────────────────────────────────────────────────────────
it('GET /blog returns 200', function () {
$this->get('/blog')->assertOk();
});
it('/blog page contains Blog heading', function () {
$html = $this->get('/blog')->assertOk()->getContent();
expect($html)->toContain('Blog');
});
it('/blog page includes canonical link', function () {
$html = $this->get('/blog')->assertOk()->getContent();
expect($html)->toContain('rel="canonical"');
});
it('/blog/:slug returns 404 for non-existent post', function () {
$this->get('/blog/this-post-does-not-exist-xyz')->assertNotFound();
});
it('published blog post is accessible at /blog/:slug', function () {
$post = \App\Models\BlogPost::factory()->create([
'slug' => 'hello-world',
'title' => 'Hello World Post',
'body' => '<p>Test content.</p>',
'is_published' => true,
'published_at' => now()->subDay(),
]);
$html = $this->get('/blog/hello-world')->assertOk()->getContent();
expect($html)->toContain('Hello World Post');
});
it('unpublished blog post returns 404', function () {
\App\Models\BlogPost::factory()->create([
'slug' => 'draft-post',
'title' => 'Draft Post',
'body' => '<p>Draft.</p>',
'is_published' => false,
]);
$this->get('/blog/draft-post')->assertNotFound();
});
it('/blog post includes Article JSON-LD', function () {
\App\Models\BlogPost::factory()->create([
'slug' => 'schema-post',
'title' => 'Schema Post',
'body' => '<p>Content.</p>',
'is_published' => true,
'published_at' => now()->subHour(),
]);
$html = $this->get('/blog/schema-post')->assertOk()->getContent();
expect($html)->toContain('"Article"');
});
// ── /pages routes ─────────────────────────────────────────────────────────────
it('GET /pages/:slug returns 404 for non-existent page', function () {
$this->get('/pages/does-not-exist-xyz')->assertNotFound();
});
it('published page is accessible at /pages/:slug', function () {
\App\Models\Page::factory()->create([
'slug' => 'test-page',
'title' => 'Test Page Title',
'body' => '<p>Page content.</p>',
'is_published' => true,
'published_at' => now()->subDay(),
]);
$html = $this->get('/pages/test-page')->assertOk()->getContent();
expect($html)->toContain('Test Page Title');
});
it('/about returns 200 when about page exists', function () {
\App\Models\Page::factory()->create([
'slug' => 'about',
'title' => 'About Skinbase',
'body' => '<p>About us.</p>',
'is_published' => true,
'published_at' => now()->subDay(),
]);
$this->get('/about')->assertOk();
});
it('/legal/terms returns 200 when legal-terms page exists', function () {
\App\Models\Page::factory()->create([
'slug' => 'legal-terms',
'title' => 'Terms of Service',
'body' => '<p>Terms.</p>',
'is_published' => true,
'published_at' => now()->subDay(),
]);
$this->get('/legal/terms')->assertOk();
});
// ── tags index layout ─────────────────────────────────────────────────────────
it('/tags page renders with ContentLayout breadcrumbs', function () {
$html = $this->get('/tags')->assertOk()->getContent();
// ContentLayout should inject breadcrumb nav
expect($html)->toContain('Tags');
});
it('/tags page includes canonical and robots', function () {
$html = $this->get('/tags')->assertOk()->getContent();
expect($html)
->toContain('rel="canonical"')
->toContain('index,follow');
});