feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready
This commit is contained in:
185
tests/Unit/Framework/Config/EnvironmentDockerSecretsTest.php
Normal file
185
tests/Unit/Framework/Config/EnvironmentDockerSecretsTest.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user