runAll()->firstWhere('key', $key) ?? throw new \RuntimeException("No finding for {$key}"); } #[Test] public function it_finds_a_redirect_that_chains_into_another(): void { Redirect::factory()->create(['from' => '/a', 'to' => '/b', 'locale' => 'es']); Redirect::factory()->create(['from' => '/b', 'to' => '/c', 'locale' => 'es']); $finding = $this->finding('broken-redirects'); // The visitor is sent somewhere that sends them somewhere else: a // round trip per hop, and eventually a loop. $this->assertSame(Severity::Error, $finding->severity); $this->assertSame(1, $finding->count); } #[Test] public function it_finds_a_redirect_that_lands_on_nothing(): void { Redirect::factory()->create(['from' => '/viejo', 'to' => '/proyectos/no-existe']); // Worse than the 404 it was meant to prevent: the visitor is bounced // and lands on the same nothing. $this->assertSame(1, $this->finding('redirects-to-nowhere')->count); } #[Test] public function a_redirect_to_a_real_page_is_fine(): void { Project::factory()->published()->create([ 'title' => ['es' => 'Vivo'], 'slug' => ['es' => 'vivo'], ]); Redirect::factory()->create(['from' => '/viejo', 'to' => '/proyectos/vivo']); $this->assertSame(Severity::Ok, $this->finding('redirects-to-nowhere')->severity); } #[Test] public function a_redirect_off_this_site_is_not_ours_to_verify(): void { Redirect::factory()->create(['from' => '/viejo', 'to' => 'https://otra-web.example/algo']); $this->assertSame(Severity::Ok, $this->finding('redirects-to-nowhere')->severity); } #[Test] public function pruning_keeps_the_first_and_last_of_a_history(): void { $page = Page::factory()->create(); foreach (range(1, 4) as $n) { $page->update(['title' => ['es' => "Versión {$n}"]]); } Revision::query()->update(['created_at' => now()->subYears(2)]); $this->artisan('content:prune')->assertSuccessful(); // Two facts survive whatever happens: when it appeared, and who // touched it last. $this->assertSame(2, $page->revisions()->count()); // `reorder`, because the relation sorts newest-first by default and // an added orderBy would only be a tie-breaker behind it. $this->assertSame('created', $page->revisions()->reorder('id')->first()->event); } #[Test] public function recent_history_is_left_alone(): void { $page = Page::factory()->create(); $page->update(['title' => ['es' => 'Reciente']]); $this->artisan('content:prune')->assertSuccessful(); $this->assertSame(2, $page->revisions()->count()); } #[Test] public function it_erases_what_has_been_in_the_bin_long_enough(): void { $old = Page::factory()->create(); $old->delete(); $old->forceFill(['deleted_at' => now()->subYear()])->save(); $recent = Page::factory()->create(); $recent->delete(); $this->artisan('content:prune')->assertSuccessful(); $this->assertNull(Page::withTrashed()->find($old->id)); $this->assertNotNull(Page::withTrashed()->find($recent->id)); } #[Test] public function a_dry_run_changes_nothing(): void { $page = Page::factory()->create(); $page->delete(); $page->forceFill(['deleted_at' => now()->subYear()])->save(); $this->artisan('content:prune', ['--dry-run' => true])->assertSuccessful(); $this->assertNotNull(Page::withTrashed()->find($page->id)); } #[Test] public function it_deletes_image_files_nothing_points_at(): void { $disk = Storage::disk(config('media.disk')); $disk->put('projects/en-uso.jpg', 'x'); $disk->put('projects/huerfana.jpg', 'x'); Project::factory()->create(['cover_image' => 'projects/en-uso.jpg']); Media::factory()->create(['path' => 'projects/huerfana.jpg']); $this->artisan('content:prune', ['--media' => true])->assertSuccessful(); $this->assertTrue($disk->exists('projects/en-uso.jpg')); $this->assertFalse($disk->exists('projects/huerfana.jpg')); } #[Test] public function an_image_belonging_to_something_in_the_bin_is_kept(): void { $disk = Storage::disk(config('media.disk')); $disk->put('projects/borrado.jpg', 'x'); $project = Project::factory()->create(['cover_image' => 'projects/borrado.jpg']); $project->delete(); $this->artisan('content:prune', ['--media' => true])->assertSuccessful(); // It can still be restored, and restoring it to a missing image would // be worse than keeping a file. $this->assertTrue($disk->exists('projects/borrado.jpg')); } #[Test] public function files_are_left_alone_unless_asked_for(): void { $disk = Storage::disk(config('media.disk')); $disk->put('projects/huerfana.jpg', 'x'); Media::factory()->create(['path' => 'projects/huerfana.jpg']); $this->artisan('content:prune')->assertSuccessful(); // Deleting a file is the one thing here that restoring a row cannot // undo, so it never happens by routine. $this->assertTrue($disk->exists('projects/huerfana.jpg')); } }