mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-15 08:17:02 +00:00
refactor: define explicit fillable attributes on all Eloquent models (#9282)
This commit is contained in:
77 files changed
+688
-168
No files matched your search
@@ -25,13 +25,13 @@ beforeEach(function () {
|
||||
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||
|
||||
StandaloneDocker::withoutEvents(function () {
|
||||
$this->destination = StandaloneDocker::firstOrCreate(
|
||||
['server_id' => $this->server->id, 'network' => 'coolify'],
|
||||
$this->destination = $this->server->standaloneDockers()->firstOrCreate(
|
||||
['network' => 'coolify'],
|
||||
['uuid' => (string) new Cuid2, 'name' => 'test-docker']
|
||||
);
|
||||
});
|
||||
|
||||
$this->project = Project::create([
|
||||
$this->project = Project::forceCreate([
|
||||
'uuid' => (string) new Cuid2,
|
||||
'name' => 'test-project',
|
||||
'team_id' => $this->team->id,
|
||||
|
||||
@@ -14,9 +14,10 @@ it('populates fqdn from docker_compose_domains after generate_preview_fqdn_compo
|
||||
]),
|
||||
]);
|
||||
|
||||
$preview = ApplicationPreview::create([
|
||||
$preview = ApplicationPreview::forceCreate([
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => 42,
|
||||
'pull_request_html_url' => 'https://github.com/example/repo/pull/42',
|
||||
'docker_compose_domains' => $application->docker_compose_domains,
|
||||
]);
|
||||
|
||||
@@ -38,9 +39,10 @@ it('populates fqdn with multiple domains from multiple services', function () {
|
||||
]),
|
||||
]);
|
||||
|
||||
$preview = ApplicationPreview::create([
|
||||
$preview = ApplicationPreview::forceCreate([
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => 7,
|
||||
'pull_request_html_url' => 'https://github.com/example/repo/pull/7',
|
||||
'docker_compose_domains' => $application->docker_compose_domains,
|
||||
]);
|
||||
|
||||
@@ -63,9 +65,10 @@ it('sets fqdn to null when no domains are configured', function () {
|
||||
]),
|
||||
]);
|
||||
|
||||
$preview = ApplicationPreview::create([
|
||||
$preview = ApplicationPreview::forceCreate([
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => 99,
|
||||
'pull_request_html_url' => 'https://github.com/example/repo/pull/99',
|
||||
'docker_compose_domains' => $application->docker_compose_domains,
|
||||
]);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ beforeEach(function () {
|
||||
|
||||
function createDatabase($context): StandalonePostgresql
|
||||
{
|
||||
return StandalonePostgresql::create([
|
||||
return StandalonePostgresql::forceCreate([
|
||||
'name' => 'test-postgres',
|
||||
'image' => 'postgres:15-alpine',
|
||||
'postgres_user' => 'postgres',
|
||||
|
||||
@@ -33,7 +33,7 @@ beforeEach(function () {
|
||||
|
||||
describe('PATCH /api/v1/databases', function () {
|
||||
test('updates public_port_timeout on a postgresql database', function () {
|
||||
$database = StandalonePostgresql::create([
|
||||
$database = StandalonePostgresql::forceCreate([
|
||||
'name' => 'test-postgres',
|
||||
'image' => 'postgres:15-alpine',
|
||||
'postgres_user' => 'postgres',
|
||||
@@ -57,7 +57,7 @@ describe('PATCH /api/v1/databases', function () {
|
||||
});
|
||||
|
||||
test('updates public_port_timeout on a redis database', function () {
|
||||
$database = StandaloneRedis::create([
|
||||
$database = StandaloneRedis::forceCreate([
|
||||
'name' => 'test-redis',
|
||||
'image' => 'redis:7',
|
||||
'redis_password' => 'password',
|
||||
@@ -79,7 +79,7 @@ describe('PATCH /api/v1/databases', function () {
|
||||
});
|
||||
|
||||
test('rejects invalid public_port_timeout value', function () {
|
||||
$database = StandalonePostgresql::create([
|
||||
$database = StandalonePostgresql::forceCreate([
|
||||
'name' => 'test-postgres',
|
||||
'image' => 'postgres:15-alpine',
|
||||
'postgres_user' => 'postgres',
|
||||
@@ -101,7 +101,7 @@ describe('PATCH /api/v1/databases', function () {
|
||||
});
|
||||
|
||||
test('accepts null public_port_timeout', function () {
|
||||
$database = StandalonePostgresql::create([
|
||||
$database = StandalonePostgresql::forceCreate([
|
||||
'name' => 'test-postgres',
|
||||
'image' => 'postgres:15-alpine',
|
||||
'postgres_user' => 'postgres',
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationSetting;
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('creates application settings for internally created applications', function () {
|
||||
$team = Team::factory()->create();
|
||||
$project = Project::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
]);
|
||||
$environment = Environment::factory()->create([
|
||||
'project_id' => $project->id,
|
||||
]);
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
]);
|
||||
$destination = $server->standaloneDockers()->firstOrFail();
|
||||
|
||||
$application = Application::forceCreate([
|
||||
'name' => 'internal-app',
|
||||
'git_repository' => 'https://github.com/coollabsio/coolify',
|
||||
'git_branch' => 'main',
|
||||
'build_pack' => 'nixpacks',
|
||||
'ports_exposes' => '3000',
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
|
||||
$setting = ApplicationSetting::query()
|
||||
->where('application_id', $application->id)
|
||||
->first();
|
||||
|
||||
expect($application->environment_id)->toBe($environment->id);
|
||||
expect($setting)->not->toBeNull();
|
||||
expect($setting?->application_id)->toBe($application->id);
|
||||
});
|
||||
|
||||
it('creates services with protected relationship ids in trusted internal paths', function () {
|
||||
$team = Team::factory()->create();
|
||||
$project = Project::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
]);
|
||||
$environment = Environment::factory()->create([
|
||||
'project_id' => $project->id,
|
||||
]);
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
]);
|
||||
$destination = $server->standaloneDockers()->firstOrFail();
|
||||
|
||||
$service = Service::forceCreate([
|
||||
'docker_compose_raw' => 'services: {}',
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => $server->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
'service_type' => 'test-service',
|
||||
]);
|
||||
|
||||
expect($service->environment_id)->toBe($environment->id);
|
||||
expect($service->server_id)->toBe($server->id);
|
||||
expect($service->destination_id)->toBe($destination->id);
|
||||
expect($service->destination_type)->toBe($destination->getMorphClass());
|
||||
});
|
||||
@@ -168,6 +168,58 @@ describe('mass assignment protection', function () {
|
||||
expect($model->isFillable('mongo_initdb_root_username'))->toBeTrue();
|
||||
});
|
||||
|
||||
test('standalone database models allow mass assignment of public_port_timeout', function () {
|
||||
$models = [
|
||||
StandalonePostgresql::class,
|
||||
StandaloneRedis::class,
|
||||
StandaloneMysql::class,
|
||||
StandaloneMariadb::class,
|
||||
StandaloneMongodb::class,
|
||||
StandaloneKeydb::class,
|
||||
StandaloneDragonfly::class,
|
||||
StandaloneClickhouse::class,
|
||||
];
|
||||
|
||||
foreach ($models as $modelClass) {
|
||||
$model = new $modelClass;
|
||||
expect($model->isFillable('public_port_timeout'))
|
||||
->toBeTrue("{$modelClass} should allow mass assignment of 'public_port_timeout'");
|
||||
}
|
||||
});
|
||||
|
||||
test('standalone database models allow mass assignment of SSL fields where applicable', function () {
|
||||
$sslModels = [
|
||||
StandalonePostgresql::class,
|
||||
StandaloneMysql::class,
|
||||
StandaloneMariadb::class,
|
||||
StandaloneMongodb::class,
|
||||
StandaloneRedis::class,
|
||||
StandaloneKeydb::class,
|
||||
StandaloneDragonfly::class,
|
||||
];
|
||||
|
||||
foreach ($sslModels as $modelClass) {
|
||||
$model = new $modelClass;
|
||||
expect($model->isFillable('enable_ssl'))
|
||||
->toBeTrue("{$modelClass} should allow mass assignment of 'enable_ssl'");
|
||||
}
|
||||
|
||||
// Clickhouse has no SSL columns
|
||||
expect((new StandaloneClickhouse)->isFillable('enable_ssl'))->toBeFalse();
|
||||
|
||||
$sslModeModels = [
|
||||
StandalonePostgresql::class,
|
||||
StandaloneMysql::class,
|
||||
StandaloneMongodb::class,
|
||||
];
|
||||
|
||||
foreach ($sslModeModels as $modelClass) {
|
||||
$model = new $modelClass;
|
||||
expect($model->isFillable('ssl_mode'))
|
||||
->toBeTrue("{$modelClass} should allow mass assignment of 'ssl_mode'");
|
||||
}
|
||||
});
|
||||
|
||||
test('Application fill ignores non-fillable fields', function () {
|
||||
$application = new Application;
|
||||
$application->fill([
|
||||
|
||||
@@ -7,25 +7,26 @@ use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('returns the correct team through the service relationship chain', function () {
|
||||
$team = Team::factory()->create();
|
||||
|
||||
$project = Project::create([
|
||||
'uuid' => (string) Illuminate\Support\Str::uuid(),
|
||||
$project = Project::forceCreate([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Project',
|
||||
'team_id' => $team->id,
|
||||
]);
|
||||
|
||||
$environment = Environment::create([
|
||||
'name' => 'test-env-'.Illuminate\Support\Str::random(8),
|
||||
$environment = Environment::forceCreate([
|
||||
'name' => 'test-env-'.Str::random(8),
|
||||
'project_id' => $project->id,
|
||||
]);
|
||||
|
||||
$service = Service::create([
|
||||
'uuid' => (string) Illuminate\Support\Str::uuid(),
|
||||
$service = Service::forceCreate([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'supabase',
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => 1,
|
||||
@@ -33,8 +34,8 @@ it('returns the correct team through the service relationship chain', function (
|
||||
'docker_compose_raw' => 'version: "3"',
|
||||
]);
|
||||
|
||||
$serviceDatabase = ServiceDatabase::create([
|
||||
'uuid' => (string) Illuminate\Support\Str::uuid(),
|
||||
$serviceDatabase = ServiceDatabase::forceCreate([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'supabase-db',
|
||||
'service_id' => $service->id,
|
||||
]);
|
||||
@@ -46,19 +47,19 @@ it('returns the correct team through the service relationship chain', function (
|
||||
it('returns the correct team for ServiceApplication through the service relationship chain', function () {
|
||||
$team = Team::factory()->create();
|
||||
|
||||
$project = Project::create([
|
||||
'uuid' => (string) Illuminate\Support\Str::uuid(),
|
||||
$project = Project::forceCreate([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'Test Project',
|
||||
'team_id' => $team->id,
|
||||
]);
|
||||
|
||||
$environment = Environment::create([
|
||||
'name' => 'test-env-'.Illuminate\Support\Str::random(8),
|
||||
$environment = Environment::forceCreate([
|
||||
'name' => 'test-env-'.Str::random(8),
|
||||
'project_id' => $project->id,
|
||||
]);
|
||||
|
||||
$service = Service::create([
|
||||
'uuid' => (string) Illuminate\Support\Str::uuid(),
|
||||
$service = Service::forceCreate([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'supabase',
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => 1,
|
||||
@@ -66,8 +67,8 @@ it('returns the correct team for ServiceApplication through the service relation
|
||||
'docker_compose_raw' => 'version: "3"',
|
||||
]);
|
||||
|
||||
$serviceApplication = ServiceApplication::create([
|
||||
'uuid' => (string) Illuminate\Support\Str::uuid(),
|
||||
$serviceApplication = ServiceApplication::forceCreate([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'name' => 'supabase-studio',
|
||||
'service_id' => $service->id,
|
||||
]);
|
||||
|
||||
@@ -49,7 +49,7 @@ function createTestApplication($context): Application
|
||||
|
||||
function createTestDatabase($context): StandalonePostgresql
|
||||
{
|
||||
return StandalonePostgresql::create([
|
||||
return StandalonePostgresql::forceCreate([
|
||||
'name' => 'test-postgres',
|
||||
'image' => 'postgres:15-alpine',
|
||||
'postgres_user' => 'postgres',
|
||||
|
||||
@@ -7,22 +7,24 @@
|
||||
* These tests verify the fix for the issue where changing an image in a
|
||||
* docker-compose file would create a new service instead of updating the existing one.
|
||||
*/
|
||||
it('ensures service parser does not include image in firstOrCreate query', function () {
|
||||
it('ensures service parser does not include image in trusted service creation query', function () {
|
||||
// Read the serviceParser function from parsers.php
|
||||
$parsersFile = file_get_contents(__DIR__.'/../../bootstrap/helpers/parsers.php');
|
||||
|
||||
// Check that firstOrCreate is called with only name and service_id
|
||||
// and NOT with image parameter in the ServiceApplication presave loop
|
||||
// Check that trusted creation only uses name and service_id
|
||||
// and does not include image in the creation payload
|
||||
expect($parsersFile)
|
||||
->toContain("firstOrCreate([\n 'name' => \$serviceName,\n 'service_id' => \$resource->id,\n ]);")
|
||||
->not->toContain("firstOrCreate([\n 'name' => \$serviceName,\n 'image' => \$image,\n 'service_id' => \$resource->id,\n ]);");
|
||||
->toContain("\$databaseFound = ServiceDatabase::where('name', \$serviceName)->where('service_id', \$resource->id)->first();")
|
||||
->toContain("\$applicationFound = ServiceApplication::where('name', \$serviceName)->where('service_id', \$resource->id)->first();")
|
||||
->toContain("forceCreate([\n 'name' => \$serviceName,\n 'service_id' => \$resource->id,\n ]);")
|
||||
->not->toContain("forceCreate([\n 'name' => \$serviceName,\n 'image' => \$image,\n 'service_id' => \$resource->id,\n ]);");
|
||||
});
|
||||
|
||||
it('ensures service parser updates image after finding or creating service', function () {
|
||||
// Read the serviceParser function from parsers.php
|
||||
$parsersFile = file_get_contents(__DIR__.'/../../bootstrap/helpers/parsers.php');
|
||||
|
||||
// Check that image update logic exists after firstOrCreate
|
||||
// Check that image update logic exists after the trusted create/find branch
|
||||
expect($parsersFile)
|
||||
->toContain('// Update image if it changed')
|
||||
->toContain('if ($savedService->image !== $image) {')
|
||||
|
||||
@@ -77,21 +77,21 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
],
|
||||
]);
|
||||
|
||||
Project::create([
|
||||
Project::forceCreate([
|
||||
'uuid' => 'project-1',
|
||||
'name' => 'My first project',
|
||||
'description' => 'This is a test project in development',
|
||||
'team_id' => 0,
|
||||
]);
|
||||
|
||||
Project::create([
|
||||
Project::forceCreate([
|
||||
'uuid' => 'project-2',
|
||||
'name' => 'Production API',
|
||||
'description' => 'Backend services for production',
|
||||
'team_id' => 0,
|
||||
]);
|
||||
|
||||
Project::create([
|
||||
Project::forceCreate([
|
||||
'uuid' => 'project-3',
|
||||
'name' => 'Staging Environment',
|
||||
'description' => 'Staging and QA testing',
|
||||
|
||||
Reference in new issue
Block a user