186 lines
6.1 KiB
PHP
186 lines
6.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Config\Environment;
|
|
|
|
describe('Environment - Docker Secrets Support', function () {
|
|
beforeEach(function () {
|
|
// Setup test directory for secret files
|
|
$this->secretsDir = sys_get_temp_dir() . '/test-secrets-' . uniqid();
|
|
mkdir($this->secretsDir, 0700, true);
|
|
});
|
|
|
|
afterEach(function () {
|
|
// Cleanup test directory
|
|
if (is_dir($this->secretsDir)) {
|
|
array_map('unlink', glob($this->secretsDir . '/*'));
|
|
rmdir($this->secretsDir);
|
|
}
|
|
});
|
|
|
|
it('returns direct environment variable when available', function () {
|
|
$env = new Environment(['DB_PASSWORD' => 'direct_password']);
|
|
|
|
$result = $env->get('DB_PASSWORD');
|
|
|
|
expect($result)->toBe('direct_password');
|
|
});
|
|
|
|
it('reads from *_FILE when direct variable not available', function () {
|
|
// Create secret file
|
|
$secretFile = $this->secretsDir . '/db_password';
|
|
file_put_contents($secretFile, 'file_password');
|
|
|
|
$env = new Environment(['DB_PASSWORD_FILE' => $secretFile]);
|
|
|
|
$result = $env->get('DB_PASSWORD');
|
|
|
|
expect($result)->toBe('file_password');
|
|
});
|
|
|
|
it('trims whitespace from file contents', function () {
|
|
// Docker secrets often have trailing newlines
|
|
$secretFile = $this->secretsDir . '/db_password';
|
|
file_put_contents($secretFile, "file_password\n");
|
|
|
|
$env = new Environment(['DB_PASSWORD_FILE' => $secretFile]);
|
|
|
|
$result = $env->get('DB_PASSWORD');
|
|
|
|
expect($result)->toBe('file_password');
|
|
});
|
|
|
|
it('prioritizes direct variable over *_FILE', function () {
|
|
$secretFile = $this->secretsDir . '/db_password';
|
|
file_put_contents($secretFile, 'file_password');
|
|
|
|
$env = new Environment([
|
|
'DB_PASSWORD' => 'direct_password',
|
|
'DB_PASSWORD_FILE' => $secretFile
|
|
]);
|
|
|
|
$result = $env->get('DB_PASSWORD');
|
|
|
|
expect($result)->toBe('direct_password')
|
|
->and($result)->not->toBe('file_password');
|
|
});
|
|
|
|
it('returns default when neither direct nor *_FILE available', function () {
|
|
$env = new Environment([]);
|
|
|
|
$result = $env->get('DB_PASSWORD', 'default_value');
|
|
|
|
expect($result)->toBe('default_value');
|
|
});
|
|
|
|
it('returns null when file path does not exist', function () {
|
|
$env = new Environment(['DB_PASSWORD_FILE' => '/nonexistent/path']);
|
|
|
|
$result = $env->get('DB_PASSWORD');
|
|
|
|
expect($result)->toBeNull();
|
|
});
|
|
|
|
it('returns null when file path is not readable', function () {
|
|
$secretFile = $this->secretsDir . '/unreadable';
|
|
file_put_contents($secretFile, 'password');
|
|
chmod($secretFile, 0000); // Make unreadable
|
|
|
|
$env = new Environment(['DB_PASSWORD_FILE' => $secretFile]);
|
|
|
|
$result = $env->get('DB_PASSWORD');
|
|
|
|
expect($result)->toBeNull();
|
|
|
|
chmod($secretFile, 0600); // Restore for cleanup
|
|
});
|
|
|
|
it('handles multiple secrets from files', function () {
|
|
// Create multiple secret files
|
|
$dbPasswordFile = $this->secretsDir . '/db_password';
|
|
$apiKeyFile = $this->secretsDir . '/api_key';
|
|
file_put_contents($dbPasswordFile, 'db_secret');
|
|
file_put_contents($apiKeyFile, 'api_secret');
|
|
|
|
$env = new Environment([
|
|
'DB_PASSWORD_FILE' => $dbPasswordFile,
|
|
'API_KEY_FILE' => $apiKeyFile
|
|
]);
|
|
|
|
expect($env->get('DB_PASSWORD'))->toBe('db_secret')
|
|
->and($env->get('API_KEY'))->toBe('api_secret');
|
|
});
|
|
|
|
it('works with EnvKey enum', function () {
|
|
$secretFile = $this->secretsDir . '/db_password';
|
|
file_put_contents($secretFile, 'enum_password');
|
|
|
|
$env = new Environment(['DB_PASSWORD_FILE' => $secretFile]);
|
|
|
|
// Assuming DB_PASSWORD exists in EnvKey enum
|
|
$result = $env->get('DB_PASSWORD');
|
|
|
|
expect($result)->toBe('enum_password');
|
|
});
|
|
|
|
it('handles empty secret files gracefully', function () {
|
|
$secretFile = $this->secretsDir . '/empty';
|
|
file_put_contents($secretFile, '');
|
|
|
|
$env = new Environment(['SECRET_FILE' => $secretFile]);
|
|
|
|
$result = $env->get('SECRET');
|
|
|
|
expect($result)->toBe(''); // Empty string, not null
|
|
});
|
|
|
|
it('ignores non-string *_FILE values', function () {
|
|
$env = new Environment(['DB_PASSWORD_FILE' => 12345]); // Invalid type
|
|
|
|
$result = $env->get('DB_PASSWORD');
|
|
|
|
expect($result)->toBeNull();
|
|
});
|
|
|
|
it('real-world Docker Swarm secrets scenario', function () {
|
|
// Simulate Docker Swarm secrets mount
|
|
$dbPasswordFile = $this->secretsDir . '/db_password';
|
|
$appKeyFile = $this->secretsDir . '/app_key';
|
|
|
|
file_put_contents($dbPasswordFile, "MyS3cur3P@ssw0rd\n"); // With newline
|
|
file_put_contents($appKeyFile, "base64:abcdef123456");
|
|
|
|
$env = new Environment([
|
|
'DB_HOST' => 'localhost', // Direct env var
|
|
'DB_PORT' => '3306', // Direct env var
|
|
'DB_PASSWORD_FILE' => $dbPasswordFile, // From Docker secret
|
|
'APP_KEY_FILE' => $appKeyFile, // From Docker secret
|
|
]);
|
|
|
|
// Verify all values work correctly
|
|
expect($env->get('DB_HOST'))->toBe('localhost')
|
|
->and($env->get('DB_PORT'))->toBe('3306')
|
|
->and($env->get('DB_PASSWORD'))->toBe('MyS3cur3P@ssw0rd')
|
|
->and($env->get('APP_KEY'))->toBe('base64:abcdef123456');
|
|
});
|
|
|
|
it('works with getRequired() for secrets from files', function () {
|
|
$secretFile = $this->secretsDir . '/required_secret';
|
|
file_put_contents($secretFile, 'required_value');
|
|
|
|
$env = new Environment(['REQUIRED_SECRET_FILE' => $secretFile]);
|
|
|
|
$result = $env->getRequired('REQUIRED_SECRET');
|
|
|
|
expect($result)->toBe('required_value');
|
|
});
|
|
|
|
it('throws exception with getRequired() when secret file not found', function () {
|
|
$env = new Environment(['REQUIRED_SECRET_FILE' => '/nonexistent/path']);
|
|
|
|
expect(fn() => $env->getRequired('REQUIRED_SECRET'))
|
|
->toThrow(App\Framework\Config\Exceptions\RequiredEnvironmentVariableException::class);
|
|
});
|
|
});
|