Files
coolify/tests/Unit/DatabaseImport/CleanupDatabaseImportTest.php
T
Andras Bacsai d4a655c390 fix(database): keep S3 import credentials out of activity commands
Pass S3 endpoint and keys as helper container env vars and run mc alias
via those variables so the stored import command no longer contains
access keys or secrets. Start the helper in a separate remote process,
use the storage filesystem adapter for object checks, and remove a
credential temp file during import cleanup when the path is safe.
2026-09-09 14:27:31 +02:00

104 lines
4.1 KiB
PHP

<?php
use App\Events\DatabaseImportFinished;
use App\Listeners\CleanupDatabaseImport;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
function importCleanupPayload(array $overrides = []): array
{
return array_merge([
'container' => 'postgres-abc',
'containerTmpPath' => '/tmp/restore_op',
'scriptPath' => '/tmp/restore_op.sh',
'serverId' => 1,
], $overrides);
}
test('the finished event keeps the payload and does not import Server', function () {
$data = importCleanupPayload(['serverTmpPath' => '/tmp/database-import-op']);
$event = new DatabaseImportFinished($data);
expect($event->data)->toBe($data)
->and(file_get_contents(app_path('Events/DatabaseImportFinished.php')))
->not->toContain('instant_remote_process')
->not->toContain('use App\Models\Server');
});
test('the cleanup listener is queued and discovered', function () {
expect(class_implements(CleanupDatabaseImport::class))->toContain(ShouldQueue::class);
$listener = new CleanupDatabaseImport;
expect($listener->tries)->toBe(3)
->and($listener->backoff)->toBe([5, 15, 30]);
Event::fake();
Event::assertListening(DatabaseImportFinished::class, CleanupDatabaseImport::class);
});
test('builds S3, upload, and server-path cleanup commands', function () {
$listener = new CleanupDatabaseImport;
expect($listener->commands(importCleanupPayload([
'containerName' => 's3-restore-op',
'serverTmpPath' => '/tmp/s3-restore-op',
'credentialTmpPath' => '/tmp/s3-restore-op.env',
])))->toBe([
'docker rm -f '.escapeshellarg('s3-restore-op').' 2>/dev/null || true',
'rm -f '.escapeshellarg('/tmp/s3-restore-op').' 2>/dev/null || true',
'rm -f '.escapeshellarg('/tmp/s3-restore-op.env').' 2>/dev/null || true',
'docker exec '.escapeshellarg('postgres-abc').' rm -f '.escapeshellarg('/tmp/restore_op').' 2>/dev/null || true',
'docker exec '.escapeshellarg('postgres-abc').' rm -f '.escapeshellarg('/tmp/restore_op.sh').' 2>/dev/null || true',
]);
expect($listener->commands(importCleanupPayload([
'serverTmpPath' => '/tmp/database-import-op',
])))->toBe([
'rm -f '.escapeshellarg('/tmp/database-import-op').' 2>/dev/null || true',
'docker exec '.escapeshellarg('postgres-abc').' rm -f '.escapeshellarg('/tmp/restore_op').' 2>/dev/null || true',
'docker exec '.escapeshellarg('postgres-abc').' rm -f '.escapeshellarg('/tmp/restore_op.sh').' 2>/dev/null || true',
]);
expect($listener->commands(importCleanupPayload()))->toBe([
'docker exec '.escapeshellarg('postgres-abc').' rm -f '.escapeshellarg('/tmp/restore_op').' 2>/dev/null || true',
'docker exec '.escapeshellarg('postgres-abc').' rm -f '.escapeshellarg('/tmp/restore_op.sh').' 2>/dev/null || true',
]);
});
test('omits unsafe paths, missing container execs, and empty payloads', function () {
$listener = new CleanupDatabaseImport;
expect($listener->commands(importCleanupPayload([
'containerName' => 's3-restore-op',
'serverTmpPath' => '/tmp/../etc/passwd',
'credentialTmpPath' => '/tmp/../etc/shadow',
'containerTmpPath' => '/etc/shadow',
'scriptPath' => '/tmp/../../etc/shadow',
])))->toBe([
'docker rm -f '.escapeshellarg('s3-restore-op').' 2>/dev/null || true',
]);
expect($listener->commands([
'serverTmpPath' => '/tmp/database-import-op',
'containerTmpPath' => '/tmp/restore_op',
'scriptPath' => '/tmp/restore_op.sh',
]))->toBe([
'rm -f '.escapeshellarg('/tmp/database-import-op').' 2>/dev/null || true',
]);
expect($listener->commands([]))->toBe([]);
});
test('handle skips remote process when the server is missing', function () {
$event = new DatabaseImportFinished(importCleanupPayload([
'serverId' => 999999,
'containerName' => 's3-restore-op',
]));
expect(fn () => (new CleanupDatabaseImport)->handle($event))->not->toThrow(Throwable::class);
});