actingAsPanelAdmin(); } /** @param array $overrides */ protected function backupFile(array $overrides = []): UploadedFile { $payload = array_replace( app(ConfigBackupService::class)->export(), $overrides, ); return UploadedFile::fake()->createWithContent( 'adconsole-config.json', json_encode($payload, JSON_UNESCAPED_UNICODE), ); } public function test_export_downloads_a_json_backup_with_the_expected_shape(): void { $res = $this->get('/settings/export')->assertOk(); $this->assertStringContainsString('attachment', $res->headers->get('content-disposition')); $this->assertStringContainsString('adconsole-config-', $res->headers->get('content-disposition')); $res->assertJsonPath('format', 'adconsole-config') ->assertJsonPath('version', 1) ->assertJsonStructure(['format', 'version', 'exported_at', 'settings', 'attributes']); $data = $res->json(); $this->assertSame(42, $data['settings']['password_policy']['max_age_days']); // Pełna lista domyślna: 24 atrybuty AD + 2 kolumny wyliczane. $this->assertCount(26, $data['attributes']); } public function test_export_can_leave_out_the_encryption_key(): void { $this->put('/settings', [ 'reset_password' => ['must_change_password' => '1', 'unlock_account' => '1'], 'password_policy' => ['max_age_days' => '42'], 'encryption' => ['key' => 'TopSecret!'], ])->assertRedirect(); $this->get('/settings/export') ->assertJsonPath('settings.encryption.key', 'TopSecret!') ->assertJsonPath('encryption_key_included', true); $this->get('/settings/export?exclude_key=1') ->assertJsonPath('settings.encryption.key', '') ->assertJsonPath('encryption_key_included', false); } public function test_import_restores_general_settings_and_attributes(): void { // Zmień konfigurację, zrób kopię, przywróć domyślne, wczytaj kopię. $this->put('/settings', [ 'reset_password' => ['must_change_password' => '0', 'unlock_account' => '0'], 'password_policy' => ['max_age_days' => '17'], 'audit' => ['require_reason' => '1'], ])->assertRedirect(); $this->put('/settings/attributes', [ 'attributes' => [ ['key' => 'title', 'label' => 'Stanowisko', 'ldap' => 'title', 'category' => 'org', 'type' => 'text', 'column' => '1'], ['key' => 'badgeId', 'label' => 'Identyfikator', 'ldap' => 'extensionAttribute9', 'category' => 'hr', 'type' => 'text', 'column' => '1'], ], ])->assertRedirect(); $file = $this->backupFile(); // Wróć do domyślnych. app(PanelSettingsService::class)->save(app(PanelSettingsService::class)->defaults()); app(PanelSettingsService::class)->saveAttributes([]); $this->assertSame(42, app(PanelSettingsService::class)->passwordMaxAgeDays()); $this->post('/settings/import', ['config_file' => $file]) ->assertRedirect() ->assertSessionHas('status') ->assertSessionHasNoErrors(); $fresh = app(PanelSettingsService::class); $this->assertSame(17, $fresh->passwordMaxAgeDays()); $this->assertTrue($fresh->requireReason()); $byKey = collect($fresh->attributes())->keyBy('key'); $this->assertTrue($byKey->has('badgeId')); $this->assertSame('extensionAttribute9', $byKey['badgeId']['ldap']); $this->assertTrue($byKey['title']['column']); // Pozycje systemowe / wyliczane zostają odtworzone przez saveAttributes(). $this->assertTrue($byKey->has('login')); $this->assertTrue($byKey->has('status')); } public function test_import_writes_an_audit_entry(): void { $this->post('/settings/import', ['config_file' => $this->backupFile()]) ->assertRedirect(); $this->assertDatabaseHas('panel_audit', ['action' => 'config.import']); } public function test_import_rejects_a_file_that_is_not_an_adconsole_backup(): void { $file = UploadedFile::fake()->createWithContent('nope.json', json_encode(['hello' => 'world'])); $this->post('/settings/import', ['config_file' => $file]) ->assertRedirect() ->assertSessionHasErrors('config_file'); $this->assertDatabaseMissing('panel_audit', ['action' => 'config.import']); } public function test_import_rejects_a_file_that_is_not_valid_json(): void { $file = UploadedFile::fake()->createWithContent('broken.json', '{ not json '); $this->post('/settings/import', ['config_file' => $file]) ->assertRedirect() ->assertSessionHasErrors('config_file'); } public function test_import_rejects_a_backup_from_a_newer_schema_version(): void { $file = $this->backupFile(['version' => 999]); $this->post('/settings/import', ['config_file' => $file]) ->assertRedirect() ->assertSessionHasErrors('config_file'); } public function test_import_sanitises_out_of_range_values_from_a_hand_edited_file(): void { $file = $this->backupFile([ 'settings' => [ 'password_policy' => ['max_age_days' => 999999, 'generator_length' => 2], 'audit' => ['require_reason' => 'yes'], ], ]); $this->post('/settings/import', ['config_file' => $file])->assertRedirect()->assertSessionHasNoErrors(); $fresh = app(PanelSettingsService::class); $this->assertSame(3650, $fresh->passwordMaxAgeDays()); $this->assertSame(8, $fresh->passwordGenerator()['length']); $this->assertTrue($fresh->requireReason()); } public function test_settings_page_shows_the_backup_card(): void { $this->get('/settings') ->assertOk() ->assertSee(__('config.backup.heading')) ->assertSee(route('config.export'), escape: false); } }