feat: CI/CD pipeline setup complete - Ansible playbooks updated, secrets configured, workflow ready

This commit is contained in:
2025-10-31 01:39:24 +01:00
parent 55c04e4fd0
commit e26eb2aa12
601 changed files with 44184 additions and 32477 deletions

View 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);
});
});