354 lines
13 KiB
PHP
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]);
|
|
}
|
|
}
|