coolify/tests/Unit/ApplicationPortDetectionTest.php
Andras Bacsai 99e97900a5 feat: add automated PORT environment variable detection and UI warnings
Add detection system for PORT environment variable to help users configure applications correctly:

- Add detectPortFromEnvironment() method to Application model to detect PORT env var
- Add getDetectedPortInfoProperty() computed property in General Livewire component
- Display contextual info banners in UI when PORT is detected:
  - Warning when PORT exists but ports_exposes is empty
  - Warning when PORT doesn't match ports_exposes configuration
  - Info message when PORT matches ports_exposes
- Add deployment logging to warn about PORT/ports_exposes mismatches
- Include comprehensive unit tests for port detection logic

The ports_exposes field remains authoritative for proxy configuration, while
PORT detection provides helpful suggestions to users.
2025-11-10 13:43:27 +01:00

157 lines
5.3 KiB
PHP

<?php
/**
* Unit tests for PORT environment variable detection feature.
*
* Tests verify that the Application model can correctly detect PORT environment
* variables and provide information to the UI about matches and mismatches with
* the configured ports_exposes field.
*/
use App\Models\Application;
use App\Models\EnvironmentVariable;
use Illuminate\Support\Collection;
use Mockery;
beforeEach(function () {
// Clean up Mockery after each test
Mockery::close();
});
it('detects PORT environment variable when present', function () {
// Create a mock Application instance
$application = Mockery::mock(Application::class)->makePartial();
// Mock environment variables collection with PORT set to 3000
$portEnvVar = Mockery::mock(EnvironmentVariable::class);
$portEnvVar->shouldReceive('getAttribute')->with('real_value')->andReturn('3000');
$envVars = new Collection([$portEnvVar]);
$application->shouldReceive('getAttribute')
->with('environment_variables')
->andReturn($envVars);
// Mock the firstWhere method to return our PORT env var
$envVars = Mockery::mock(Collection::class);
$envVars->shouldReceive('firstWhere')->with('key', 'PORT')->andReturn($portEnvVar);
$application->shouldReceive('getAttribute')
->with('environment_variables')
->andReturn($envVars);
// Call the method we're testing
$detectedPort = $application->detectPortFromEnvironment();
expect($detectedPort)->toBe(3000);
});
it('returns null when PORT environment variable is not set', function () {
$application = Mockery::mock(Application::class)->makePartial();
// Mock environment variables collection without PORT
$envVars = Mockery::mock(Collection::class);
$envVars->shouldReceive('firstWhere')->with('key', 'PORT')->andReturn(null);
$application->shouldReceive('getAttribute')
->with('environment_variables')
->andReturn($envVars);
$detectedPort = $application->detectPortFromEnvironment();
expect($detectedPort)->toBeNull();
});
it('returns null when PORT value is not numeric', function () {
$application = Mockery::mock(Application::class)->makePartial();
// Mock environment variables with non-numeric PORT value
$portEnvVar = Mockery::mock(EnvironmentVariable::class);
$portEnvVar->shouldReceive('getAttribute')->with('real_value')->andReturn('invalid-port');
$envVars = Mockery::mock(Collection::class);
$envVars->shouldReceive('firstWhere')->with('key', 'PORT')->andReturn($portEnvVar);
$application->shouldReceive('getAttribute')
->with('environment_variables')
->andReturn($envVars);
$detectedPort = $application->detectPortFromEnvironment();
expect($detectedPort)->toBeNull();
});
it('handles PORT value with whitespace', function () {
$application = Mockery::mock(Application::class)->makePartial();
// Mock environment variables with PORT value that has whitespace
$portEnvVar = Mockery::mock(EnvironmentVariable::class);
$portEnvVar->shouldReceive('getAttribute')->with('real_value')->andReturn(' 8080 ');
$envVars = Mockery::mock(Collection::class);
$envVars->shouldReceive('firstWhere')->with('key', 'PORT')->andReturn($portEnvVar);
$application->shouldReceive('getAttribute')
->with('environment_variables')
->andReturn($envVars);
$detectedPort = $application->detectPortFromEnvironment();
expect($detectedPort)->toBe(8080);
});
it('detects PORT from preview environment variables when isPreview is true', function () {
$application = Mockery::mock(Application::class)->makePartial();
// Mock preview environment variables with PORT
$portEnvVar = Mockery::mock(EnvironmentVariable::class);
$portEnvVar->shouldReceive('getAttribute')->with('real_value')->andReturn('4000');
$envVars = Mockery::mock(Collection::class);
$envVars->shouldReceive('firstWhere')->with('key', 'PORT')->andReturn($portEnvVar);
$application->shouldReceive('getAttribute')
->with('environment_variables_preview')
->andReturn($envVars);
$detectedPort = $application->detectPortFromEnvironment(true);
expect($detectedPort)->toBe(4000);
});
it('verifies ports_exposes array conversion logic', function () {
// Test the logic that converts comma-separated ports to array
$portsExposesString = '3000,3001,8080';
$expectedArray = [3000, 3001, 8080];
// This simulates what portsExposesArray accessor does
$result = is_null($portsExposesString)
? []
: explode(',', $portsExposesString);
// Convert to integers for comparison
$result = array_map('intval', $result);
expect($result)->toBe($expectedArray);
});
it('verifies PORT matches detection logic', function () {
$detectedPort = 3000;
$portsExposesArray = [3000, 3001];
$isMatch = in_array($detectedPort, $portsExposesArray);
expect($isMatch)->toBeTrue();
});
it('verifies PORT mismatch detection logic', function () {
$detectedPort = 8080;
$portsExposesArray = [3000, 3001];
$isMatch = in_array($detectedPort, $portsExposesArray);
expect($isMatch)->toBeFalse();
});
it('verifies empty ports_exposes detection logic', function () {
$portsExposesArray = [];
$isEmpty = empty($portsExposesArray);
expect($isEmpty)->toBeTrue();
});