storage = new AssetStorage; }); test('storing content writes the file and creates an asset record', function () { $project = VideoProject::factory()->create(); $asset = $this->storage->store($project, 'script', 'Once upon a time...', 'txt', mimeType: 'text/plain'); expect($asset->type)->toBe('script'); expect($asset->version)->toBe(1); expect($asset->size_bytes)->toBe(strlen('Once upon a time...')); expect($asset->mime_type)->toBe('text/plain'); expect($asset->video_project_id)->toBe($project->id); Storage::assertExists($asset->disk_path); expect(Storage::get($asset->disk_path))->toBe('Once upon a time...'); }); test('storing the same slot again increments the version instead of overwriting', function () { $project = VideoProject::factory()->create(); $v1 = $this->storage->store($project, 'script', 'draft one', 'txt'); $v2 = $this->storage->store($project, 'script', 'draft two', 'txt'); expect($v1->version)->toBe(1); expect($v2->version)->toBe(2); expect($v1->disk_path)->not->toBe($v2->disk_path); Storage::assertExists($v1->disk_path); Storage::assertExists($v2->disk_path); expect(Storage::get($v1->disk_path))->toBe('draft one'); expect(Storage::get($v2->disk_path))->toBe('draft two'); }); test('versioning is scoped per workflow step when one is given', function () { $project = VideoProject::factory()->create(); $stepA = WorkflowStep::factory()->for($project)->create(['order' => 0]); $stepB = WorkflowStep::factory()->for($project)->create(['order' => 1]); $a = $this->storage->store($project, 'script', 'from step A', 'txt', step: $stepA); $b = $this->storage->store($project, 'script', 'from step B', 'txt', step: $stepB); expect($a->version)->toBe(1); expect($b->version)->toBe(1); }); test('contents retrieves the stored file', function () { $project = VideoProject::factory()->create(); $asset = $this->storage->store($project, 'script', 'hello', 'txt'); expect($this->storage->contents($asset))->toBe('hello'); }); test('url returns null for a missing file and a path for an existing one', function () { $project = VideoProject::factory()->create(); $asset = $this->storage->store($project, 'script', 'hello', 'txt'); expect($this->storage->url($asset))->not->toBeNull(); Storage::delete($asset->disk_path); expect($this->storage->url($asset))->toBeNull(); }); test('delete removes the file and the asset record', function () { $project = VideoProject::factory()->create(); $asset = $this->storage->store($project, 'script', 'hello', 'txt'); $this->storage->delete($asset); Storage::assertMissing($asset->disk_path); expect(\App\Models\Asset::find($asset->id))->toBeNull(); }); test('pruneOldVersions keeps only the newest N versions', function () { $project = VideoProject::factory()->create(); $versions = collect(range(1, 5))->map( fn ($i) => $this->storage->store($project, 'script', "draft {$i}", 'txt') ); $pruned = $this->storage->pruneOldVersions($project, 'script', keep: 2); expect($pruned)->toBe(3); $remaining = $project->assets()->where('type', 'script')->pluck('version')->sort()->values(); expect($remaining->all())->toBe([4, 5]); Storage::assertMissing($versions[0]->disk_path); Storage::assertMissing($versions[1]->disk_path); Storage::assertMissing($versions[2]->disk_path); Storage::assertExists($versions[3]->disk_path); Storage::assertExists($versions[4]->disk_path); }); test('pruneOldVersions does nothing when there are fewer versions than the keep count', function () { $project = VideoProject::factory()->create(); $this->storage->store($project, 'script', 'only draft', 'txt'); $pruned = $this->storage->pruneOldVersions($project, 'script', keep: 3); expect($pruned)->toBe(0); expect($project->assets()->count())->toBe(1); });