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.
pixelfed/tests/Feature/Account/MigrateLocalS3MediaUrlTest.php

190 lines
6.8 KiB
PHP

<?php
use App\Models\Media;
use App\Models\Status;
use App\Models\User;
use App\Services\ConfigCacheService;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Illuminate\Support\Facades\Config;
uses(LazilyRefreshDatabase::class);
/*
|--------------------------------------------------------------------------
| admin:MigrateLocalS3MediaURL
|--------------------------------------------------------------------------
|
| Rebuilds stale local media URLs (cdn_url, thumbnail_url, optimized_url)
| from their storage paths using the configured cloud disk host.
|
*/
beforeEach(function () {
// Point the cloud disk at a deterministic host with path-style urls so
// Storage::disk('s3')->url($path) == https://cdn.test/<path>.
Config::set('filesystems.cloud', 's3');
Config::set('filesystems.disks.s3', [
'driver' => 's3',
'key' => 'test',
'secret' => 'test',
'region' => 'us-east-1',
'bucket' => 'bucket',
'url' => 'https://cdn.test',
'endpoint' => 'https://cdn.test',
'use_path_style_endpoint' => true,
'visibility' => 'public',
]);
// Enable cloud storage. config_cache() falls through to config() when
// instance.enable_cc is off (as in CI), so set both to be safe.
Config::set('pixelfed.cloud_storage', true);
ConfigCacheService::put('pixelfed.cloud_storage', true);
});
function makeStatusWithStaleMedia(string $staleHost = 'https://s3.old.example'): Media
{
$user = User::factory()->create();
$user->refresh();
$pid = $user->profile->id;
$status = Status::factory()->create(['profile_id' => $pid, 'type' => 'video']);
$path = 'public/m/_v2/'.$pid.'/aa/bb/file.mp4';
$thumbPath = 'public/m/_v2/'.$pid.'/aa/bb/file_thumb.jpeg';
return Media::create([
'status_id' => $status->id,
'profile_id' => $pid,
'user_id' => $user->id,
'media_path' => $path,
'thumbnail_path' => $thumbPath,
'cdn_url' => 'https://cdn.test/'.$path, // already correct
'thumbnail_url' => $staleHost.'/'.$thumbPath, // stale
'optimized_url' => $staleHost.'/'.$path, // stale
'mime' => 'video/mp4',
'remote_media' => false,
'order' => 0,
]);
}
it('rebuilds stale thumbnail_url and optimized_url but leaves correct cdn_url', function () {
$media = makeStatusWithStaleMedia();
$this->artisan('admin:MigrateLocalS3MediaURL', ['id' => (string) $media->status_id, '--force' => true])
->assertExitCode(0);
$media->refresh();
expect($media->cdn_url)->toBe('https://cdn.test/'.$media->media_path);
expect($media->thumbnail_url)->toBe('https://cdn.test/'.$media->thumbnail_path);
expect($media->optimized_url)->toBe('https://cdn.test/'.$media->media_path);
expect(parse_url($media->thumbnail_url, PHP_URL_HOST))->toBe('cdn.test');
expect(parse_url($media->optimized_url, PHP_URL_HOST))->toBe('cdn.test');
});
it('does not change anything in dry-run mode', function () {
$media = makeStatusWithStaleMedia();
$originalThumb = $media->thumbnail_url;
$this->artisan('admin:MigrateLocalS3MediaURL', ['id' => (string) $media->status_id, '--dry-run' => true])
->assertExitCode(0);
expect($media->fresh()->thumbnail_url)->toBe($originalThumb);
});
it('leaves already-correct media untouched', function () {
$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/ok.jpg';
$media = Media::create([
'status_id' => $status->id,
'profile_id' => $pid,
'media_path' => $path,
'cdn_url' => 'https://cdn.test/'.$path,
'thumbnail_url' => 'https://cdn.test/'.$path,
'mime' => 'image/jpeg',
'remote_media' => false,
'order' => 0,
]);
$updatedAt = $media->fresh()->updated_at;
$this->artisan('admin:MigrateLocalS3MediaURL', ['id' => (string) $status->id, '--force' => true])
->assertExitCode(0);
expect($media->fresh()->updated_at->eq($updatedAt))->toBeTrue();
});
it('never rewrites remote media', function () {
$user = User::factory()->create();
$user->refresh();
$pid = $user->profile->id;
$status = Status::factory()->create(['profile_id' => $pid, 'type' => 'photo']);
$media = Media::create([
'status_id' => $status->id,
'profile_id' => $pid,
'media_path' => 'https://remote.example/image.jpg',
'cdn_url' => 'https://s3.old.example/image.jpg',
'remote_media' => true,
'remote_url' => 'https://remote.example/image.jpg',
'mime' => 'image/jpeg',
'order' => 0,
]);
$this->artisan('admin:MigrateLocalS3MediaURL', ['id' => (string) $status->id, '--force' => true])
->assertExitCode(0);
// Unchanged: remote media is skipped.
expect($media->fresh()->cdn_url)->toBe('https://s3.old.example/image.jpg');
});
it('requires an id or --all', function () {
$this->artisan('admin:MigrateLocalS3MediaURL')
->assertExitCode(1);
});
it('refuses to run on a local-storage instance', function () {
// Simulate local storage: cloud disabled (set both, see beforeEach).
Config::set('pixelfed.cloud_storage', false);
ConfigCacheService::put('pixelfed.cloud_storage', false);
$this->artisan('admin:MigrateLocalS3MediaURL', ['--all' => true, '--force' => true])
->expectsOutputToContain('Cloud storage is not enabled')
->assertExitCode(1);
});
it('with --oldDomain only rewrites URLs on that host', function () {
// thumbnail_url on s3.old.example, optimized_url on other.example.
$media = makeStatusWithStaleMedia('https://s3.old.example');
$media->optimized_url = 'https://other.example/'.$media->media_path;
$media->save();
$this->artisan('admin:MigrateLocalS3MediaURL', [
'id' => (string) $media->status_id,
'--oldDomain' => 's3.old.example',
'--force' => true,
])->assertExitCode(0);
$media->refresh();
// Matched the filter -> rewritten.
expect(parse_url($media->thumbnail_url, PHP_URL_HOST))->toBe('cdn.test');
// Did NOT match the filter -> left as-is.
expect(parse_url($media->optimized_url, PHP_URL_HOST))->toBe('other.example');
});
it('with --newDomain override rewrites to the given host', function () {
$media = makeStatusWithStaleMedia('https://s3.old.example');
$this->artisan('admin:MigrateLocalS3MediaURL', [
'id' => (string) $media->status_id,
'--newDomain' => 'media.example',
'--force' => true,
])->assertExitCode(0);
$media->refresh();
expect(parse_url($media->thumbnail_url, PHP_URL_HOST))->toBe('media.example');
expect(parse_url($media->optimized_url, PHP_URL_HOST))->toBe('media.example');
});