Files
coolify/tests/Unit/ApplicationDeploymentNixpacksNullEnvTest.php
T
Andras BacsaiandClaude Opus 5.5 a79ee909c6 chore: remove unused components and markup-only tests
- Delete Blade components, component classes, Livewire components and
  views that nothing uses after the redesign.
- Delete tests that only compare Blade/CSS source text; keep the source
  checks for authorization gates, stable wire:key values and escaping.
- Update stale tests to the current UI and behaviour.
- Update DESIGN.md to the current navigation and layout.
- Add a lesson: test behaviour, not source markup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:18:16 +02:00

346 lines
14 KiB
PHP

<?php
use App\Jobs\ApplicationDeploymentJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\EnvironmentVariable;
use App\Models\Server;
use Tests\TestCase;
uses(TestCase::class);
/**
* Build an environment variable whose resolved value is fixed, without touching the database.
*/
function nixpacksEnvVariable(string $key, ?string $resolvedValue): EnvironmentVariable
{
$env = Mockery::mock(EnvironmentVariable::class)->makePartial();
$env->key = $key;
$env->shouldReceive('getResolvedValueWithServer')->andReturn($resolvedValue);
return $env;
}
/**
* Test to verify that null and empty environment variables are filtered out
* when generating Nixpacks configuration.
*
* This test verifies the fix for the issue where null or empty environment variable
* values would be passed to Nixpacks as `--env KEY=` (with no value), causing
* JSON parsing errors: "invalid type: null, expected a string at line 12 column 27"
*
* The fix ensures that:
* 1. User-defined environment variables with null or empty values are filtered out
* 2. COOLIFY_* environment variables with null or empty values are filtered out
* 3. Only environment variables with valid non-empty values are passed to Nixpacks
*/
it('filters out null environment variables from nixpacks build command', function () {
// Mock application with nixpacks build pack
$mockApplication = Mockery::mock(Application::class);
$mockApplication->shouldReceive('getAttribute')
->with('build_pack')
->andReturn('nixpacks');
// Mock environment variables - some with null/empty values
$envVar1 = nixpacksEnvVariable('VALID_VAR', 'valid_value');
$envVar2 = nixpacksEnvVariable('NULL_VAR', null);
$envVar3 = nixpacksEnvVariable('EMPTY_VAR', '');
$envVar4 = nixpacksEnvVariable('ANOTHER_VALID_VAR', 'another_value');
$nixpacksEnvVars = collect([$envVar1, $envVar2, $envVar3, $envVar4]);
$mockApplication->shouldReceive('getAttribute')
->with('nixpacks_environment_variables')
->andReturn($nixpacksEnvVars);
// Mock application deployment queue
$mockQueue = Mockery::mock(ApplicationDeploymentQueue::class);
$mockQueue->shouldReceive('getAttribute')->with('application_id')->andReturn(1);
// Mock the job
$job = Mockery::mock(ApplicationDeploymentJob::class)->makePartial();
$job->shouldAllowMockingProtectedMethods();
$reflection = new \ReflectionClass(ApplicationDeploymentJob::class);
// Set private properties
$applicationProperty = $reflection->getProperty('application');
$applicationProperty->setAccessible(true);
$applicationProperty->setValue($job, $mockApplication);
$mainServerProperty = $reflection->getProperty('mainServer');
$mainServerProperty->setAccessible(true);
$mainServerProperty->setValue($job, new Server);
$pullRequestProperty = $reflection->getProperty('pull_request_id');
$pullRequestProperty->setAccessible(true);
$pullRequestProperty->setValue($job, 0);
// Mock generate_coolify_env_variables to return some values including null
$job->shouldReceive('generate_coolify_env_variables')
->andReturn(collect([
'COOLIFY_FQDN' => 'example.com',
'COOLIFY_URL' => null, // null value that should be filtered
'COOLIFY_BRANCH' => '', // empty value that should be filtered
'SOURCE_COMMIT' => 'abc123',
]));
// Call the private method
$method = $reflection->getMethod('generate_nixpacks_env_variables');
$method->setAccessible(true);
$method->invoke($job);
// Get the generated env_nixpacks_args
$envArgsProperty = $reflection->getProperty('env_nixpacks_args');
$envArgsProperty->setAccessible(true);
$envArgs = $envArgsProperty->getValue($job);
// Verify that only valid environment variables are included (values are now single-quote escaped)
expect($envArgs)->toContain("--env 'VALID_VAR=valid_value'");
expect($envArgs)->toContain("--env 'ANOTHER_VALID_VAR=another_value'");
expect($envArgs)->toContain("--env 'COOLIFY_FQDN=example.com'");
expect($envArgs)->toContain("--env 'SOURCE_COMMIT=abc123'");
// Verify that null and empty environment variables are filtered out
expect($envArgs)->not->toContain('NULL_VAR');
expect($envArgs)->not->toContain('EMPTY_VAR');
expect($envArgs)->not->toContain('COOLIFY_URL');
expect($envArgs)->not->toContain('COOLIFY_BRANCH');
// Verify no environment variables end with just '=' (which indicates null/empty value)
expect($envArgs)->not->toMatch('/--env [A-Z_]+=$/');
expect($envArgs)->not->toMatch("/--env '[A-Z_]+='$/");
});
it('filters out null environment variables from nixpacks preview deployments', function () {
// Mock application with nixpacks build pack
$mockApplication = Mockery::mock(Application::class);
$mockApplication->shouldReceive('getAttribute')
->with('build_pack')
->andReturn('nixpacks');
// Mock preview environment variables - some with null/empty values
$envVar1 = nixpacksEnvVariable('PREVIEW_VAR', 'preview_value');
$envVar2 = nixpacksEnvVariable('NULL_PREVIEW_VAR', null);
$previewEnvVars = collect([$envVar1, $envVar2]);
$mockApplication->shouldReceive('getAttribute')
->with('nixpacks_environment_variables_preview')
->andReturn($previewEnvVars);
// Mock application deployment queue
$mockQueue = Mockery::mock(ApplicationDeploymentQueue::class);
$mockQueue->shouldReceive('getAttribute')->with('application_id')->andReturn(1);
// Mock the job
$job = Mockery::mock(ApplicationDeploymentJob::class)->makePartial();
$job->shouldAllowMockingProtectedMethods();
$reflection = new \ReflectionClass(ApplicationDeploymentJob::class);
// Set private properties
$applicationProperty = $reflection->getProperty('application');
$applicationProperty->setAccessible(true);
$applicationProperty->setValue($job, $mockApplication);
$mainServerProperty = $reflection->getProperty('mainServer');
$mainServerProperty->setAccessible(true);
$mainServerProperty->setValue($job, new Server);
$pullRequestProperty = $reflection->getProperty('pull_request_id');
$pullRequestProperty->setAccessible(true);
$pullRequestProperty->setValue($job, 123); // Non-zero for preview deployment
// Mock generate_coolify_env_variables
$job->shouldReceive('generate_coolify_env_variables')
->andReturn(collect([
'COOLIFY_FQDN' => 'preview.example.com',
]));
// Call the private method
$method = $reflection->getMethod('generate_nixpacks_env_variables');
$method->setAccessible(true);
$method->invoke($job);
// Get the generated env_nixpacks_args
$envArgsProperty = $reflection->getProperty('env_nixpacks_args');
$envArgsProperty->setAccessible(true);
$envArgs = $envArgsProperty->getValue($job);
// Verify that only valid environment variables are included (values are now single-quote escaped)
expect($envArgs)->toContain("--env 'PREVIEW_VAR=preview_value'");
expect($envArgs)->toContain("--env 'COOLIFY_FQDN=preview.example.com'");
// Verify that null environment variables are filtered out
expect($envArgs)->not->toContain('NULL_PREVIEW_VAR');
});
it('handles all environment variables being null or empty', function () {
// Mock application with nixpacks build pack
$mockApplication = Mockery::mock(Application::class);
$mockApplication->shouldReceive('getAttribute')
->with('build_pack')
->andReturn('nixpacks');
// Mock environment variables - all null or empty
$envVar1 = nixpacksEnvVariable('NULL_VAR', null);
$envVar2 = nixpacksEnvVariable('EMPTY_VAR', '');
$nixpacksEnvVars = collect([$envVar1, $envVar2]);
$mockApplication->shouldReceive('getAttribute')
->with('nixpacks_environment_variables')
->andReturn($nixpacksEnvVars);
// Mock application deployment queue
$mockQueue = Mockery::mock(ApplicationDeploymentQueue::class);
$mockQueue->shouldReceive('getAttribute')->with('application_id')->andReturn(1);
// Mock the job
$job = Mockery::mock(ApplicationDeploymentJob::class)->makePartial();
$job->shouldAllowMockingProtectedMethods();
$reflection = new \ReflectionClass(ApplicationDeploymentJob::class);
// Set private properties
$applicationProperty = $reflection->getProperty('application');
$applicationProperty->setAccessible(true);
$applicationProperty->setValue($job, $mockApplication);
$mainServerProperty = $reflection->getProperty('mainServer');
$mainServerProperty->setAccessible(true);
$mainServerProperty->setValue($job, new Server);
$pullRequestProperty = $reflection->getProperty('pull_request_id');
$pullRequestProperty->setAccessible(true);
$pullRequestProperty->setValue($job, 0);
// Mock generate_coolify_env_variables to return all null/empty values
$job->shouldReceive('generate_coolify_env_variables')
->andReturn(collect([
'COOLIFY_URL' => null,
'COOLIFY_BRANCH' => '',
]));
// Call the private method
$method = $reflection->getMethod('generate_nixpacks_env_variables');
$method->setAccessible(true);
$method->invoke($job);
// Get the generated env_nixpacks_args
$envArgsProperty = $reflection->getProperty('env_nixpacks_args');
$envArgsProperty->setAccessible(true);
$envArgs = $envArgsProperty->getValue($job);
// Verify that the result is empty or contains no environment variables
expect($envArgs)->toBe('');
});
it('filters out null coolify env variables from env_args used in nixpacks plan JSON', function () {
// This test verifies the fix for GitHub issue #6830:
// When application->fqdn is null, COOLIFY_FQDN/COOLIFY_URL get set to null
// in generate_coolify_env_variables(). The generate_env_variables() method
// merges these into env_args which become the nixpacks plan JSON "variables".
// Nixpacks requires all variable values to be strings, so null causes:
// "Error: Failed to parse Nixpacks config file - invalid type: null, expected a string"
// Simulate the coolify env collection with null values (as produced when fqdn is null)
$coolify_envs = collect([
'COOLIFY_URL' => null,
'COOLIFY_FQDN' => null,
'COOLIFY_BRANCH' => 'main',
'COOLIFY_RESOURCE_UUID' => 'abc123',
'COOLIFY_CONTAINER_NAME' => '',
]);
// Apply the same filtering logic used in generate_env_variables()
$env_args = collect([]);
$coolify_envs->each(function ($value, $key) use ($env_args) {
if (! is_null($value) && $value !== '') {
$env_args->put($key, $value);
}
});
// Null values must NOT be present — they cause nixpacks JSON parse errors
expect($env_args->has('COOLIFY_URL'))->toBeFalse();
expect($env_args->has('COOLIFY_FQDN'))->toBeFalse();
expect($env_args->has('COOLIFY_CONTAINER_NAME'))->toBeFalse();
// Non-null values must be preserved
expect($env_args->get('COOLIFY_BRANCH'))->toBe('main');
expect($env_args->get('COOLIFY_RESOURCE_UUID'))->toBe('abc123');
// The resulting array must be safe for json_encode into nixpacks config
$json = json_encode(['variables' => $env_args->toArray()], JSON_PRETTY_PRINT);
$parsed = json_decode($json, true);
foreach ($parsed['variables'] as $value) {
expect($value)->toBeString();
}
});
it('preserves environment variables with zero values', function () {
// Mock application with nixpacks build pack
$mockApplication = Mockery::mock(Application::class);
$mockApplication->shouldReceive('getAttribute')
->with('build_pack')
->andReturn('nixpacks');
// Mock environment variables with zero values (which should NOT be filtered)
$envVar1 = nixpacksEnvVariable('ZERO_VALUE', '0');
$envVar2 = nixpacksEnvVariable('FALSE_VALUE', 'false');
$nixpacksEnvVars = collect([$envVar1, $envVar2]);
$mockApplication->shouldReceive('getAttribute')
->with('nixpacks_environment_variables')
->andReturn($nixpacksEnvVars);
// Mock application deployment queue
$mockQueue = Mockery::mock(ApplicationDeploymentQueue::class);
$mockQueue->shouldReceive('getAttribute')->with('application_id')->andReturn(1);
// Mock the job
$job = Mockery::mock(ApplicationDeploymentJob::class)->makePartial();
$job->shouldAllowMockingProtectedMethods();
$reflection = new \ReflectionClass(ApplicationDeploymentJob::class);
// Set private properties
$applicationProperty = $reflection->getProperty('application');
$applicationProperty->setAccessible(true);
$applicationProperty->setValue($job, $mockApplication);
$mainServerProperty = $reflection->getProperty('mainServer');
$mainServerProperty->setAccessible(true);
$mainServerProperty->setValue($job, new Server);
$pullRequestProperty = $reflection->getProperty('pull_request_id');
$pullRequestProperty->setAccessible(true);
$pullRequestProperty->setValue($job, 0);
// Mock generate_coolify_env_variables
$job->shouldReceive('generate_coolify_env_variables')
->andReturn(collect([]));
// Call the private method
$method = $reflection->getMethod('generate_nixpacks_env_variables');
$method->setAccessible(true);
$method->invoke($job);
// Get the generated env_nixpacks_args
$envArgsProperty = $reflection->getProperty('env_nixpacks_args');
$envArgsProperty->setAccessible(true);
$envArgs = $envArgsProperty->getValue($job);
// Verify that zero and false string values are preserved (values are now single-quote escaped)
expect($envArgs)->toContain("--env 'ZERO_VALUE=0'");
expect($envArgs)->toContain("--env 'FALSE_VALUE=false'");
});