0, 'is_api_enabled' => true]); $this->team = Team::factory()->create(); $this->user = User::factory()->create(); $this->team->members()->attach($this->user, ['role' => 'owner']); session(['currentTeam' => $this->team]); $server = Server::factory()->create(['team_id' => $this->team->id]); $destination = StandaloneDocker::firstOrCreate(['server_id' => $server->id, 'network' => 'coolify'], ['uuid' => (string) Str::uuid(), 'name' => 'docker']); $project = Project::factory()->create(['team_id' => $this->team->id]); $environment = Environment::factory()->create(['project_id' => $project->id]); $database = StandalonePostgresql::create(['uuid' => (string) Str::uuid(), 'name' => 'db', 'postgres_user' => 'postgres', 'postgres_password' => 'password', 'postgres_db' => 'db', 'image' => 'postgres:17', 'status' => 'running', 'environment_id' => $environment->id, 'destination_id' => $destination->id, 'destination_type' => $destination->getMorphClass()]); $service = Service::factory()->create(['environment_id' => $environment->id, 'server_id' => $server->id, 'destination_id' => $destination->id, 'destination_type' => $destination->getMorphClass(), 'docker_compose_raw' => "services:\n postgres:\n image: postgres:17\n"]); $serviceDatabase = ServiceDatabase::create(['uuid' => (string) Str::uuid(), 'name' => 'postgres', 'service_id' => $service->id, 'image' => 'postgres:17']); $this->importPaths = [ 'standalone' => "/api/v1/databases/{$database->uuid}/imports", 'service' => "/api/v1/services/{$service->uuid}/databases/{$serviceDatabase->uuid}/imports", ]; }); function serverSourceImportToken(User $user, Team $team, array $abilities): string { $token = $user->createToken('server-source-import', $abilities); $token->accessToken->forceFill(['team_id' => $team->id])->save(); return $token->plainTextToken; } function expectServerSourceImportStarts(): void { $activity = Activity::create(['log_name' => 'default', 'description' => 'queued', 'properties' => ['status' => 'queued']]); $action = Mockery::mock(StartDatabaseImport::class); $action->shouldReceive('handle')->once()->andReturn($activity); app()->instance(StartDatabaseImport::class, $action); } dataset('server source import variants', ['standalone', 'service']); dataset('import sources', [ 'upload' => [fn () => ['source' => 'upload', 'upload_id' => (string) Str::uuid()]], 's3' => [fn () => ['source' => 's3', 's3_storage_uuid' => (string) Str::uuid(), 'path' => '/backups/backup.sql']], 'server' => [fn () => ['source' => 'server', 'path' => '/tmp/backup.sql']], ]); test('a write token without deploy cannot start an import from any source', function (string $variant, array $payload) { $action = Mockery::mock(StartDatabaseImport::class); $action->shouldNotReceive('handle'); app()->instance(StartDatabaseImport::class, $action); $this->withToken(serverSourceImportToken($this->user, $this->team, ['read', 'write'])) ->postJson($this->importPaths[$variant], $payload) ->assertForbidden() ->assertJsonPath('message', 'Missing required permissions: deploy. An import runs the dump in the database container, so it needs a token with the deploy permission.'); expect(Activity::query()->count())->toBe(0); })->with('server source import variants')->with('import sources'); test('a write token with deploy or a root token can start an import from any source', function (string $variant, array $payload, array $abilities) { expectServerSourceImportStarts(); $this->withToken(serverSourceImportToken($this->user, $this->team, $abilities)) ->postJson($this->importPaths[$variant], $payload) ->assertAccepted(); })->with('server source import variants')->with('import sources')->with([ 'write and deploy' => [['read', 'write', 'deploy']], 'root' => [['root']], ]);