Files
timebank-cc-public/tests/Feature/ConfigMergeCommandTest.php
Ronald Huynen 2547717edb Initial commit
2026-03-23 21:37:59 +01:00

354 lines
13 KiB
PHP

<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\File;
use Tests\TestCase;
class ConfigMergeCommandTest extends TestCase
{
protected $testConfigPath;
protected $testConfigExamplePath;
protected $backupDir;
protected function setUp(): void
{
parent::setUp();
$this->testConfigPath = base_path('config/test-config.php');
$this->testConfigExamplePath = base_path('config/test-config.php.example');
$this->backupDir = storage_path('config-backups');
// Clean up any existing test files
$this->cleanupTestFiles();
}
protected function tearDown(): void
{
$this->cleanupTestFiles();
parent::tearDown();
}
protected function cleanupTestFiles(): void
{
if (File::exists($this->testConfigPath)) {
File::delete($this->testConfigPath);
}
if (File::exists($this->testConfigExamplePath)) {
File::delete($this->testConfigExamplePath);
}
if (File::isDirectory($this->backupDir)) {
$backups = File::glob("{$this->backupDir}/test-config.php.backup.*");
foreach ($backups as $backup) {
File::delete($backup);
}
}
}
/** @test */
public function it_detects_new_keys_in_dry_run_mode()
{
// Create current config
$currentConfig = [
'existing_key' => 'existing_value',
'nested' => [
'old_key' => 'old_value',
],
];
File::put($this->testConfigPath, "<?php\n\nreturn " . var_export($currentConfig, true) . ";\n");
// Create example config with new keys
$exampleConfig = [
'existing_key' => 'example_value',
'new_key' => 'new_value',
'nested' => [
'old_key' => 'old_value',
'new_nested_key' => 'new_nested_value',
],
];
File::put($this->testConfigExamplePath, "<?php\n\nreturn " . var_export($exampleConfig, true) . ";\n");
// Run command with dry-run
$this->artisan('config:merge', ['file' => 'test-config', '--dry-run' => true])
->expectsOutput('Found 2 new configuration key(s):')
->assertExitCode(0);
// Verify original config unchanged
$loadedConfig = include $this->testConfigPath;
$this->assertEquals('existing_value', $loadedConfig['existing_key']);
$this->assertArrayNotHasKey('new_key', $loadedConfig);
}
/** @test */
public function it_merges_new_keys_while_preserving_existing_values()
{
// Create current config with custom values
$currentConfig = [
'existing_key' => 'CUSTOM_VALUE',
'nested' => [
'old_key' => 'CUSTOM_OLD_VALUE',
],
];
File::put($this->testConfigPath, "<?php\n\nreturn " . var_export($currentConfig, true) . ";\n");
// Create example config with new keys and different values
$exampleConfig = [
'existing_key' => 'example_value',
'new_key' => 'new_value',
'nested' => [
'old_key' => 'example_old_value',
'new_nested_key' => 'new_nested_value',
],
];
File::put($this->testConfigExamplePath, "<?php\n\nreturn " . var_export($exampleConfig, true) . ";\n");
// Run command with force flag
$this->artisan('config:merge', ['file' => 'test-config', '--force' => true])
->assertExitCode(0);
// Verify merge
$mergedConfig = include $this->testConfigPath;
// Existing values should be preserved
$this->assertEquals('CUSTOM_VALUE', $mergedConfig['existing_key']);
$this->assertEquals('CUSTOM_OLD_VALUE', $mergedConfig['nested']['old_key']);
// New keys should be added
$this->assertEquals('new_value', $mergedConfig['new_key']);
$this->assertEquals('new_nested_value', $mergedConfig['nested']['new_nested_key']);
}
/** @test */
public function it_creates_backup_before_merge()
{
// Create current config
$currentConfig = ['existing_key' => 'value'];
File::put($this->testConfigPath, "<?php\n\nreturn " . var_export($currentConfig, true) . ";\n");
// Create example config
$exampleConfig = ['existing_key' => 'value', 'new_key' => 'new_value'];
File::put($this->testConfigExamplePath, "<?php\n\nreturn " . var_export($exampleConfig, true) . ";\n");
// Run command
$this->artisan('config:merge', ['file' => 'test-config', '--force' => true])
->assertExitCode(0);
// Verify backup was created
$backups = File::glob("{$this->backupDir}/test-config.php.backup.*");
$this->assertCount(1, $backups);
// Verify backup content matches original
$backupContent = include $backups[0];
$this->assertEquals($currentConfig, $backupContent);
}
/** @test */
public function it_handles_deep_nested_arrays()
{
// Create current config with deep nesting
$currentConfig = [
'level1' => [
'level2' => [
'level3' => [
'existing' => 'value',
],
],
],
];
File::put($this->testConfigPath, "<?php\n\nreturn " . var_export($currentConfig, true) . ";\n");
// Create example config with new deep nested key
$exampleConfig = [
'level1' => [
'level2' => [
'level3' => [
'existing' => 'example_value',
'new_deep' => 'deep_value',
],
'new_level3' => 'value',
],
],
];
File::put($this->testConfigExamplePath, "<?php\n\nreturn " . var_export($exampleConfig, true) . ";\n");
// Run command
$this->artisan('config:merge', ['file' => 'test-config', '--force' => true])
->assertExitCode(0);
// Verify deep merge
$mergedConfig = include $this->testConfigPath;
$this->assertEquals('value', $mergedConfig['level1']['level2']['level3']['existing']);
$this->assertEquals('deep_value', $mergedConfig['level1']['level2']['level3']['new_deep']);
$this->assertEquals('value', $mergedConfig['level1']['level2']['new_level3']);
}
/** @test */
public function it_reports_no_changes_when_config_is_up_to_date()
{
// Create identical configs
$config = ['key' => 'value'];
File::put($this->testConfigPath, "<?php\n\nreturn " . var_export($config, true) . ";\n");
File::put($this->testConfigExamplePath, "<?php\n\nreturn " . var_export($config, true) . ";\n");
// Run command
$this->artisan('config:merge', ['file' => 'test-config', '--dry-run' => true])
->expectsOutput(' test-config: No new keys found')
->assertExitCode(0);
}
/** @test */
public function it_handles_missing_example_file()
{
// Create only current config
File::put($this->testConfigPath, "<?php\n\nreturn [];\n");
// Run command (no example file)
$this->artisan('config:merge', ['file' => 'test-config', '--dry-run' => true])
->expectsOutput('⊘ test-config: Example file not found (config/test-config.php.example)')
->assertExitCode(0);
}
/** @test */
public function it_handles_missing_current_config()
{
// Create only example file
File::put($this->testConfigExamplePath, "<?php\n\nreturn [];\n");
// Run command (no current config)
$this->artisan('config:merge', ['file' => 'test-config', '--dry-run' => true])
->expectsOutput('⊘ test-config: Active config not found (config/test-config.php) - run deployment first')
->assertExitCode(0);
}
/** @test */
public function it_validates_merged_config_can_be_loaded()
{
// This test verifies the validation step
// We can't easily test a failure case without causing actual errors
// But we can verify success case
$currentConfig = ['key' => 'value'];
$exampleConfig = ['key' => 'value', 'new_key' => 'new_value'];
File::put($this->testConfigPath, "<?php\n\nreturn " . var_export($currentConfig, true) . ";\n");
File::put($this->testConfigExamplePath, "<?php\n\nreturn " . var_export($exampleConfig, true) . ";\n");
// Run command
$this->artisan('config:merge', ['file' => 'test-config', '--force' => true])
->assertExitCode(0);
// Verify merged config can be loaded without errors
$this->assertIsArray(include $this->testConfigPath);
}
/** @test */
public function it_keeps_only_last_5_backups()
{
// Ensure backup directory exists
if (!File::isDirectory($this->backupDir)) {
File::makeDirectory($this->backupDir, 0755, true);
}
// Create 7 old backups manually
for ($i = 1; $i <= 7; $i++) {
$timestamp = date('Y-m-d_His', strtotime("-{$i} days"));
$backupPath = "{$this->backupDir}/test-config.php.backup.{$timestamp}";
File::put($backupPath, "<?php\n\nreturn ['backup' => {$i}];\n");
// Adjust file modification time to make them appear older
touch($backupPath, strtotime("-{$i} days"));
}
// Verify we have 7 backups
$this->assertCount(7, File::glob("{$this->backupDir}/test-config.php.backup.*"));
// Create configs and run merge (this will create 8th backup)
File::put($this->testConfigPath, "<?php\n\nreturn ['key' => 'value'];\n");
File::put($this->testConfigExamplePath, "<?php\n\nreturn ['key' => 'value', 'new' => 'val'];\n");
$this->artisan('config:merge', ['file' => 'test-config', '--force' => true])
->assertExitCode(0);
// Should now have only 5 backups (oldest 3 deleted)
$remainingBackups = File::glob("{$this->backupDir}/test-config.php.backup.*");
$this->assertCount(5, $remainingBackups);
}
/** @test */
public function it_handles_array_values_correctly()
{
$currentConfig = [
'array_key' => ['item1', 'item2'],
];
$exampleConfig = [
'array_key' => ['item1', 'item2'],
'new_array' => ['new1', 'new2', 'new3'],
];
File::put($this->testConfigPath, "<?php\n\nreturn " . var_export($currentConfig, true) . ";\n");
File::put($this->testConfigExamplePath, "<?php\n\nreturn " . var_export($exampleConfig, true) . ";\n");
$this->artisan('config:merge', ['file' => 'test-config', '--force' => true])
->assertExitCode(0);
$mergedConfig = include $this->testConfigPath;
$this->assertEquals(['item1', 'item2'], $mergedConfig['array_key']);
$this->assertEquals(['new1', 'new2', 'new3'], $mergedConfig['new_array']);
}
/** @test */
public function it_handles_boolean_and_null_values()
{
$currentConfig = [
'bool_true' => true,
'bool_false' => false,
'null_value' => null,
];
$exampleConfig = [
'bool_true' => false, // Different value
'bool_false' => true, // Different value
'null_value' => 'not null', // Different value
'new_bool' => true,
'new_null' => null,
];
File::put($this->testConfigPath, "<?php\n\nreturn " . var_export($currentConfig, true) . ";\n");
File::put($this->testConfigExamplePath, "<?php\n\nreturn " . var_export($exampleConfig, true) . ";\n");
$this->artisan('config:merge', ['file' => 'test-config', '--force' => true])
->assertExitCode(0);
$mergedConfig = include $this->testConfigPath;
// Existing values preserved
$this->assertTrue($mergedConfig['bool_true']);
$this->assertFalse($mergedConfig['bool_false']);
$this->assertNull($mergedConfig['null_value']);
// New values added
$this->assertTrue($mergedConfig['new_bool']);
$this->assertNull($mergedConfig['new_null']);
}
/** @test */
public function it_handles_numeric_keys()
{
$currentConfig = [
'indexed' => [0 => 'first', 1 => 'second'],
];
$exampleConfig = [
'indexed' => [0 => 'first', 1 => 'second', 2 => 'third'],
];
File::put($this->testConfigPath, "<?php\n\nreturn " . var_export($currentConfig, true) . ";\n");
File::put($this->testConfigExamplePath, "<?php\n\nreturn " . var_export($exampleConfig, true) . ";\n");
$this->artisan('config:merge', ['file' => 'test-config', '--force' => true])
->assertExitCode(0);
$mergedConfig = include $this->testConfigPath;
$this->assertArrayHasKey(2, $mergedConfig['indexed']);
$this->assertEquals('third', $mergedConfig['indexed'][2]);
}
}