withHeaders(['Accept-Language' => 'es']); Queue::fake(); } #[Test] public function renaming_a_slug_leaves_a_redirect_behind(): void { $project = Project::factory()->published()->create([ 'title' => ['es' => 'Mi proyecto'], 'slug' => ['es' => 'nombre-viejo'], ]); $project->update(['slug' => ['es' => 'nombre-nuevo']]); $redirect = Redirect::query()->sole(); $this->assertSame('/proyectos/nombre-viejo', $redirect->from); $this->assertSame('/proyectos/nombre-nuevo', $redirect->to); $this->assertSame(301, $redirect->status); $this->assertSame('es', $redirect->locale); $this->assertTrue($redirect->wasAutomatic()); } #[Test] public function each_language_gets_its_own_rule(): void { $project = Project::factory()->published()->create([ 'title' => ['es' => 'Uno', 'ca' => 'U'], 'slug' => ['es' => 'viejo-es', 'ca' => 'vell-ca'], ]); $project->update(['slug' => ['es' => 'nuevo-es', 'ca' => 'nou-ca']]); // The Catalan URL carries its own prefix and its own path segment, so // one rule could never cover both. $this->assertSame( ['/proyectos/viejo-es' => '/proyectos/nuevo-es', '/ca/projectes/vell-ca' => '/ca/projectes/nou-ca'], Redirect::query()->pluck('to', 'from')->all(), ); } #[Test] public function adding_a_translation_is_not_a_rename(): void { $project = Project::factory()->published()->create([ 'title' => ['es' => 'Uno'], 'slug' => ['es' => 'uno'], ]); $project->update(['slug' => ['es' => 'uno', 'ca' => 'u']]); // Nothing broke: there was no Catalan address before. $this->assertSame(0, Redirect::query()->count()); } #[Test] public function a_second_rename_does_not_leave_a_chain_behind(): void { $project = Project::factory()->published()->create([ 'title' => ['es' => 'Uno'], 'slug' => ['es' => 'v1'], ]); $project->update(['slug' => ['es' => 'v2']]); $project->update(['slug' => ['es' => 'v3']]); // Both old addresses point at the current one. A frontend following a // chain would cost an extra round trip per hop. $this->assertSame( ['/proyectos/v1' => '/proyectos/v3', '/proyectos/v2' => '/proyectos/v3'], Redirect::query()->orderBy('from')->pluck('to', 'from')->all(), ); } #[Test] public function renaming_back_does_not_create_a_loop(): void { $project = Project::factory()->published()->create([ 'title' => ['es' => 'Uno'], 'slug' => ['es' => 'original'], ]); $project->update(['slug' => ['es' => 'cambiado']]); $project->update(['slug' => ['es' => 'original']]); // A rule from a path to itself would send a browser round for ever. foreach (Redirect::query()->get() as $redirect) { $this->assertFalse($redirect->isCircular(), "{$redirect->from} → {$redirect->to}"); } } #[Test] public function it_works_for_every_kind_of_record_with_a_public_url(): void { $service = Service::factory()->create([ 'title' => ['es' => 'Servicio'], 'slug' => ['es' => 'antiguo'], ]); $service->update(['slug' => ['es' => 'moderno']]); $this->assertSame('/servicios/antiguo', Redirect::query()->sole()->from); } #[Test] public function the_frontend_gets_the_whole_list_in_one_request(): void { Redirect::factory()->create(['from' => '/vieja', 'to' => '/nueva', 'locale' => null]); Redirect::factory()->disabled()->create(['from' => '/apagada', 'to' => '/otra']); $data = $this->getJson('/api/public/v1/redirects')->assertOk()->json('data'); // One request rather than a lookup per 404: there are never many, and // an SSR frontend would otherwise ask on every miss it serves. $this->assertCount(1, $data); $this->assertSame('/vieja', $data[0]['from']); $this->assertNull($data[0]['locale']); } #[Test] public function paths_are_normalised_however_they_were_typed(): void { $this->assertSame('/proyectos/uno', Redirect::normalise('proyectos/uno')); $this->assertSame('/proyectos/uno', Redirect::normalise('/proyectos/uno/')); $this->assertSame('/proyectos/uno', Redirect::normalise('/proyectos/uno?utm=x')); // So a rule typed by hand matches one written automatically. $this->assertSame('/', Redirect::normalise('/')); } #[Test] public function following_one_can_be_counted(): void { $redirect = Redirect::factory()->create(['hits' => 0]); $redirect->markUsed(); // The number that decides whether a rule can ever be deleted. $this->assertSame(1, $redirect->fresh()->hits); $this->assertNotNull($redirect->fresh()->last_used_at); } }