mirror of https://github.com/pixelfed/pixelfed
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
151 lines
5.6 KiB
PHP
151 lines
5.6 KiB
PHP
<?php
|
|
|
|
use App\Models\Media;
|
|
use App\Models\Status;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
|
use Illuminate\Support\Facades\Config;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
uses(LazilyRefreshDatabase::class);
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Media storage migration commands
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| admin:MediaMoveStorageLocalToCloud / admin:MediaMoveStorageCloudToLocal
|
|
| Move media between local and cloud disks, verify by size/sha256, GC the
|
|
| source, and manage the PF_ENABLE_CLOUD .env flag for hot migrations.
|
|
|
|
|
*/
|
|
|
|
beforeEach(function () {
|
|
Config::set('filesystems.cloud', 's3');
|
|
Storage::fake('local');
|
|
Storage::fake('s3', ['url' => 'https://cdn.test']);
|
|
|
|
// Use a throwaway env file so the command's env edits don't touch the
|
|
// real one. App::useEnvironmentPath expects a directory; the app resolves
|
|
// the environment-specific filename (e.g. .env.testing) itself.
|
|
$this->originalEnvPath = app()->environmentPath();
|
|
$dir = sys_get_temp_dir().'/pf-env-test-'.uniqid();
|
|
mkdir($dir);
|
|
app()->useEnvironmentPath($dir);
|
|
file_put_contents(app()->environmentFilePath(), "APP_KEY=base64:test\nPF_ENABLE_CLOUD=false\n");
|
|
});
|
|
|
|
afterEach(function () {
|
|
// Restore the real environment path so we don't leak into other test files.
|
|
if (isset($this->originalEnvPath)) {
|
|
app()->useEnvironmentPath($this->originalEnvPath);
|
|
}
|
|
});
|
|
|
|
function makeCloudMedia(): Media
|
|
{
|
|
$user = User::factory()->create();
|
|
$user->refresh();
|
|
$pid = $user->profile->id;
|
|
$status = Status::factory()->create(['profile_id' => $pid, 'type' => 'photo']);
|
|
|
|
$path = 'public/m/_v2/'.$pid.'/aa/bb/file.jpg';
|
|
$thumb = 'public/m/_v2/'.$pid.'/aa/bb/file_thumb.jpeg';
|
|
|
|
// Put the files on the cloud disk only.
|
|
Storage::disk('s3')->put($path, 'PRIMARY-BYTES-1234567890');
|
|
Storage::disk('s3')->put($thumb, 'THUMB-BYTES');
|
|
|
|
return Media::create([
|
|
'status_id' => $status->id,
|
|
'profile_id' => $pid,
|
|
'user_id' => $user->id,
|
|
'media_path' => $path,
|
|
'thumbnail_path' => $thumb,
|
|
'cdn_url' => Storage::disk('s3')->url($path),
|
|
'thumbnail_url' => Storage::disk('s3')->url($thumb),
|
|
'optimized_url' => Storage::disk('s3')->url($path),
|
|
'mime' => 'image/jpeg',
|
|
'size' => strlen('PRIMARY-BYTES-1234567890'),
|
|
'remote_media' => false,
|
|
'version' => 4,
|
|
'replicated_at' => now(),
|
|
'order' => 0,
|
|
]);
|
|
}
|
|
|
|
describe('admin:MediaMoveStorageCloudToLocal', function () {
|
|
it('downloads cloud media to local, clears cloud urls and deletes the cloud copy', function () {
|
|
$media = makeCloudMedia();
|
|
|
|
$this->artisan('admin:MediaMoveStorageCloudToLocal', ['--force' => true])
|
|
->assertExitCode(0);
|
|
|
|
// File is now on local disk.
|
|
expect(Storage::disk('local')->exists($media->media_path))->toBeTrue();
|
|
// Cloud copy removed (GC), thumbnail too.
|
|
expect(Storage::disk('s3')->exists($media->media_path))->toBeFalse();
|
|
|
|
$media->refresh();
|
|
expect($media->cdn_url)->toBeNull();
|
|
expect($media->optimized_url)->toBeNull();
|
|
expect($media->thumbnail_url)->toBeNull();
|
|
expect($media->replicated_at)->toBeNull();
|
|
expect((string) $media->version)->toBe('3');
|
|
});
|
|
|
|
it('keeps the cloud copy with --keep-cloud', function () {
|
|
$media = makeCloudMedia();
|
|
|
|
$this->artisan('admin:MediaMoveStorageCloudToLocal', ['--force' => true, '--keep-cloud' => true])
|
|
->assertExitCode(0);
|
|
|
|
expect(Storage::disk('local')->exists($media->media_path))->toBeTrue();
|
|
expect(Storage::disk('s3')->exists($media->media_path))->toBeTrue();
|
|
});
|
|
|
|
it('does not modify anything in dry-run', function () {
|
|
$media = makeCloudMedia();
|
|
|
|
$this->artisan('admin:MediaMoveStorageCloudToLocal', ['--force' => true, '--dry-run' => true])
|
|
->assertExitCode(0);
|
|
|
|
expect(Storage::disk('local')->exists($media->media_path))->toBeFalse();
|
|
expect($media->fresh()->cdn_url)->not->toBeNull();
|
|
});
|
|
|
|
it('sets PF_ENABLE_CLOUD=false in .env and runtime when cloud is enabled', function () {
|
|
// Start with cloud enabled.
|
|
file_put_contents(app()->environmentFilePath(), "APP_KEY=base64:test\nPF_ENABLE_CLOUD=true\n");
|
|
Config::set('pixelfed.cloud_storage', true);
|
|
makeCloudMedia();
|
|
|
|
$this->artisan('admin:MediaMoveStorageCloudToLocal', ['--force' => true])
|
|
->assertExitCode(0);
|
|
|
|
expect(file_get_contents(app()->environmentFilePath()))->toContain('PF_ENABLE_CLOUD="false"');
|
|
expect(config('pixelfed.cloud_storage'))->toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('admin:MediaMoveStorageLocalToCloud', function () {
|
|
it('requires a configured cloud disk', function () {
|
|
// Fake s3 disk has no url() host resolvable? Storage::fake provides a
|
|
// url, so instead point cloud at a disk that throws.
|
|
Config::set('filesystems.cloud', 'does-not-exist');
|
|
|
|
$this->artisan('admin:MediaMoveStorageLocalToCloud', ['--force' => true])
|
|
->assertExitCode(1);
|
|
});
|
|
|
|
it('flips PF_ENABLE_CLOUD to true before migrating (dry-run reports it)', function () {
|
|
// cloud currently false in the temp .env from beforeEach.
|
|
$this->artisan('admin:MediaMoveStorageLocalToCloud', ['--dry-run' => true])
|
|
->expectsOutputToContain('PF_ENABLE_CLOUD')
|
|
->assertExitCode(0);
|
|
|
|
// dry-run must not write the .env.
|
|
expect(file_get_contents(app()->environmentFilePath()))->toContain('PF_ENABLE_CLOUD=false');
|
|
});
|
|
});
|