From 33355253eb82da0dc23230c67d5b87fc40d3bd5d Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Thu, 5 Feb 2026 18:02:14 -0600 Subject: [PATCH] fix stats-db backup tests and harden regex escaping Test fixes: removed shadowed mockFsReaddirSync variable, moved vi.resetModules() to beforeEach, added pragma integrity_check mock, and fixed existsSync mock for daily backup creation test. Production hardening: escape baseName dots in regex patterns used by getAvailableBackups() and rotateOldBackups() to prevent unintended matches when db filename contains regex metacharacters. --- src/__tests__/main/stats/stats-db.test.ts | 29 ++++++++++------------- src/main/stats/stats-db.ts | 4 ++-- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/__tests__/main/stats/stats-db.test.ts b/src/__tests__/main/stats/stats-db.test.ts index b909983c..0f2d20b7 100644 --- a/src/__tests__/main/stats/stats-db.test.ts +++ b/src/__tests__/main/stats/stats-db.test.ts @@ -683,31 +683,21 @@ describe('Database file creation on first launch', () => { * Daily backup system tests */ describe('Daily backup system', () => { - const mockFsReaddirSync = vi.fn(); - beforeEach(() => { + vi.resetModules(); vi.clearAllMocks(); lastDbPath = null; - mockDb.pragma.mockReturnValue([{ user_version: 3 }]); + // Return integrity_check: 'ok' so initialize() doesn't trigger corruption recovery + mockDb.pragma.mockImplementation((pragmaStr: string) => { + if (pragmaStr === 'integrity_check') return [{ integrity_check: 'ok' }]; + return [{ user_version: 3 }]; + }); mockDb.prepare.mockReturnValue(mockStatement); mockStatement.run.mockReturnValue({ changes: 1 }); mockStatement.get.mockReturnValue({ value: '0' }); // Old vacuum timestamp mockStatement.all.mockReturnValue([]); mockFsExistsSync.mockReturnValue(true); mockFsReaddirSync.mockReturnValue([]); - - // Mock readdirSync in the fs mock - vi.doMock('fs', () => ({ - existsSync: (...args: unknown[]) => mockFsExistsSync(...args), - mkdirSync: (...args: unknown[]) => mockFsMkdirSync(...args), - copyFileSync: (...args: unknown[]) => mockFsCopyFileSync(...args), - unlinkSync: (...args: unknown[]) => mockFsUnlinkSync(...args), - renameSync: (...args: unknown[]) => mockFsRenameSync(...args), - statSync: (...args: unknown[]) => mockFsStatSync(...args), - readFileSync: (...args: unknown[]) => mockFsReadFileSync(...args), - writeFileSync: (...args: unknown[]) => mockFsWriteFileSync(...args), - readdirSync: (...args: unknown[]) => mockFsReaddirSync(...args), - })); }); afterEach(() => { @@ -828,6 +818,13 @@ describe('Daily backup system', () => { describe('daily backup creation on initialize', () => { it('should attempt to create daily backup on initialization', async () => { + const today = new Date().toISOString().split('T')[0]; + // existsSync returns false for today's daily backup so createDailyBackupIfNeeded proceeds + mockFsExistsSync.mockImplementation((p: unknown) => { + if (typeof p === 'string' && p.includes(`daily.${today}`)) return false; + return true; + }); + const { StatsDB } = await import('../../../main/stats'); const db = new StatsDB(); db.initialize(); diff --git a/src/main/stats/stats-db.ts b/src/main/stats/stats-db.ts index 0824d9f6..957413b5 100644 --- a/src/main/stats/stats-db.ts +++ b/src/main/stats/stats-db.ts @@ -384,7 +384,7 @@ export class StatsDB { private rotateOldBackups(keepDays: number): void { try { const dir = path.dirname(this.dbPath); - const baseName = path.basename(this.dbPath); + const baseName = path.basename(this.dbPath).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const files = fs.readdirSync(dir); const cutoffDate = new Date(); @@ -420,7 +420,7 @@ export class StatsDB { getAvailableBackups(): Array<{ path: string; date: string; size: number }> { try { const dir = path.dirname(this.dbPath); - const baseName = path.basename(this.dbPath); + const baseName = path.basename(this.dbPath).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const files = fs.readdirSync(dir); const backups: Array<{ path: string; date: string; size: number }> = [];