From b5ef38f48658b933811ad9320c56312fd02263e1 Mon Sep 17 00:00:00 2001 From: Misaka_Company Date: Tue, 3 Mar 2026 13:50:36 +0800 Subject: [PATCH] feat: add backup and restore mechanism to ConfigManager --- src/main/services/config/config-manager.ts | 37 +++++++++ .../services/config/config-manager.test.ts | 80 ++++++++++++++++++- 2 files changed, 113 insertions(+), 4 deletions(-) diff --git a/src/main/services/config/config-manager.ts b/src/main/services/config/config-manager.ts index 98db245..24985c1 100644 --- a/src/main/services/config/config-manager.ts +++ b/src/main/services/config/config-manager.ts @@ -145,6 +145,7 @@ function validateEditableFields(settings: Partial): { export class ConfigManager { private static instance: ConfigManager | null = null private envPath: string + private backupPath: string private configCache: Map = new Map() private initialized: boolean = false @@ -153,6 +154,7 @@ export class ConfigManager { return } this.envPath = path.resolve(__dirname, '../../.env') + this.backupPath = path.resolve(__dirname, '../../.env.backup') this.initialized = true } @@ -599,4 +601,39 @@ export class ConfigManager { public getDefaultSettings(): SettingsData { return DEFAULT_SETTINGS } + + /** + * Backup current .env file + */ + private async backupEnvFile(): Promise { + try { + if (fs.existsSync(this.envPath)) { + fs.copyFileSync(this.envPath, this.backupPath) + console.log('[ConfigManager] Backup created', this.backupPath) + return true + } + return false + } catch (error) { + console.error('[ConfigManager] Failed to backup .env file:', error) + return false + } + } + + /** + * Restore .env file from backup + */ + private async restoreBackup(): Promise { + try { + if (fs.existsSync(this.backupPath)) { + fs.copyFileSync(this.backupPath, this.envPath) + await this.loadEnvFile() + console.log('[ConfigManager] Restored from backup') + return true + } + return false + } catch (error) { + console.error('[ConfigManager] Failed to restore backup:', error) + return false + } + } } diff --git a/tests/main/services/config/config-manager.test.ts b/tests/main/services/config/config-manager.test.ts index 35440de..8847760 100644 --- a/tests/main/services/config/config-manager.test.ts +++ b/tests/main/services/config/config-manager.test.ts @@ -9,11 +9,38 @@ describe('ConfigManager - deep merge utilities', () => { // Setup initial state const initial: SettingsData = { - erp: { url: 'http://old.com', username: 'user1', password: 'pass1', headless: true, ignoreHttpsErrors: true, autoCloseBrowser: true }, - database: { dbType: 'mysql', server: '', mysqlHost: 'localhost', mysqlPort: 3306, database: 'db', username: 'user', password: '' }, + erp: { + url: 'http://old.com', + username: 'user1', + password: 'pass1', + headless: true, + ignoreHttpsErrors: true, + autoCloseBrowser: true + }, + database: { + dbType: 'mysql', + server: '', + mysqlHost: 'localhost', + mysqlPort: 3306, + database: 'db', + username: 'user', + password: '' + }, paths: { dataDir: '/data', defaultOutput: 'out.xlsx', validationOutput: 'validation.xlsx' }, - extraction: { batchSize: 100, verbose: true, autoConvert: true, mergeBatches: true, enableDbPersistence: true }, - validation: { dataSource: 'database_full', batchSize: 2000, matchMode: 'substring', enableCrud: false, defaultManager: '' }, + extraction: { + batchSize: 100, + verbose: true, + autoConvert: true, + mergeBatches: true, + enableDbPersistence: true + }, + validation: { + dataSource: 'database_full', + batchSize: 2000, + matchMode: 'substring', + enableCrud: false, + defaultManager: '' + }, ui: { fontFamily: 'Arial', fontSize: 12, productionIdInputWidth: 20 }, execution: { dryRun: false } } @@ -41,3 +68,48 @@ describe('ConfigManager - deep merge utilities', () => { expect(current.paths.dataDir).toBe('/data') }) }) + +describe('ConfigManager - backup and restore', () => { + it('should create backup before saving', async () => { + const manager = ConfigManager.getInstance() + await manager.initialize() + + const backupSuccess = await manager['backupEnvFile']() + + expect(backupSuccess).toBe(true) + + // Check backup file exists (in same location as .env file) + const fs = await import('fs') + const path = await import('path') + const backupPath = path.resolve('src/main/.env.backup') + + expect(fs.existsSync(backupPath)).toBe(true) + }) + + it('should restore from backup when save fails', async () => { + const manager = ConfigManager.getInstance() + await manager.initialize() + + // Create initial state + const initial = manager.getAllSettings() + const originalUrl = initial.erp.url + + // Mock fs.writeFileSync to fail + const fs = await import('fs') + const writeFileSyncSpy = vi.spyOn(fs, 'writeFileSync').mockImplementation(() => { + throw new Error('Disk full') + }) + + const result = await manager.savePartialSettings({ + erp: { url: 'http://should-not-save.com' } + }) + + expect(result.success).toBe(false) + + // Restore should have happened + const current = manager.getAllSettings() + expect(current.erp.url).toBe(originalUrl) + + writeFileSyncSpy.mockRestore() + }) +})