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, " 'example_value', 'new_key' => 'new_value', 'nested' => [ 'old_key' => 'old_value', 'new_nested_key' => 'new_nested_value', ], ]; File::put($this->testConfigExamplePath, "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, " 'example_value', 'new_key' => 'new_value', 'nested' => [ 'old_key' => 'example_old_value', 'new_nested_key' => 'new_nested_value', ], ]; File::put($this->testConfigExamplePath, "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, " 'value', 'new_key' => 'new_value']; File::put($this->testConfigExamplePath, "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, " [ 'level2' => [ 'level3' => [ 'existing' => 'example_value', 'new_deep' => 'deep_value', ], 'new_level3' => 'value', ], ], ]; File::put($this->testConfigExamplePath, "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, "testConfigExamplePath, "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, "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, "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, "testConfigExamplePath, "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, " {$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, " 'value'];\n"); File::put($this->testConfigExamplePath, " '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, "testConfigExamplePath, "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, "testConfigExamplePath, "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, "testConfigExamplePath, "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]); } }