diff --git a/tests/Feature/Files/OrphanPathAliasTest.php b/tests/Feature/Files/OrphanPathAliasTest.php new file mode 100644 index 00000000..ee43d0e6 --- /dev/null +++ b/tests/Feature/Files/OrphanPathAliasTest.php @@ -0,0 +1,75 @@ +owner = User::factory()->client()->create(); + $this->tracked = File::factory()->create([ + 'uploaded_by' => $this->owner->id, + 'path' => 'audit/client-b.txt', + 'disk' => 'files', + 'mime_type' => 'text/plain', + 'size' => 11, + ]); + Storage::disk('files')->put('audit/client-b.txt', 'hello-world'); + + $this->staff = staffWithPermissions(['upload', 'import_orphans']); +}); + +dataset('aliases of a tracked file', [ + 'leading dot' => './audit/client-b.txt', + 'inner dot' => 'audit/./client-b.txt', + 'double slash' => 'audit//client-b.txt', + 'leading slash' => '/audit/client-b.txt', + 'backslash' => 'audit\\client-b.txt', + 'climb back' => 'audit/x/../client-b.txt', +]); + +test('an alias of a tracked file cannot delete its bytes', function (string $alias) { + $this->actingAs($this->staff)->postJson('/files/orphans/delete', ['items' => [['disk' => 'files', 'path' => $alias]]]); + + Storage::disk('files')->assertExists('audit/client-b.txt'); +})->with('aliases of a tracked file'); + +test('an alias of a tracked file cannot be adopted as a second file', function (string $alias) { + $this->actingAs($this->staff)->postJson('/files/orphans/import', ['items' => [['disk' => 'files', 'path' => $alias]]]); + + expect(File::query()->count())->toBe(1); +})->with('aliases of a tracked file'); + +test('an alias cannot reach into a derived-artifact folder either', function () { + Storage::disk('files')->put('thumbnails/2026/some.jpg', 'x'); + + $this->actingAs($this->staff)->postJson('/files/orphans/delete', ['items' => [['disk' => 'files', 'path' => './thumbnails/2026/some.jpg']]]); + + Storage::disk('files')->assertExists('thumbnails/2026/some.jpg'); +}); + +test('a real orphan is still deleted and imported', function () { + makeOrphanFile('audit/stray-a.txt'); + makeOrphanFile('audit/stray-b.txt'); + + $this->actingAs($this->staff)->postJson('/files/orphans/delete', ['items' => [['disk' => 'files', 'path' => 'audit/stray-a.txt']]]); + $this->actingAs($this->staff)->postJson('/files/orphans/import', ['items' => [['disk' => 'files', 'path' => 'audit/stray-b.txt']]]); + + Storage::disk('files')->assertMissing('audit/stray-a.txt'); + expect(File::query()->where('path', 'audit/stray-b.txt')->exists())->toBeTrue(); +});