/** * Unit tests for tmp-cleanup.js -- orphaned temp files + stale Firefox profiles. */ import fs from 'fs'; import path from 'path'; import os from 'os'; import { cleanupOrphanedTempFiles, cleanupStaleFirefoxProfiles } from '../../lib/tmp-cleanup.js'; function makeTmpDir() { return fs.mkdtempSync(path.join(os.tmpdir(), 'camofox-test-')); } describe('cleanupOrphanedTempFiles', () => { it('removes old orphaned .so files', () => { const dir = makeTmpDir(); const name = '.fea5abcdef1234.so'; const full = path.join(dir, name); fs.writeFileSync(full, 'data'); // Set mtime to 10 minutes ago const oldTime = new Date(Date.now() - 10 * 60 * 1000); fs.utimesSync(full, oldTime, oldTime); const result = cleanupOrphanedTempFiles({ tmpDir: dir, minAgeMs: 5 * 60 * 1000 }); expect(result.removed).toBe(1); expect(fs.existsSync(full)).toBe(false); fs.rmSync(dir, { recursive: true, force: true }); }); it('skips recent orphaned files', () => { const dir = makeTmpDir(); const name = '.fea5abcdef1234.so'; const full = path.join(dir, name); fs.writeFileSync(full, 'data'); // mtime is now (recent) const result = cleanupOrphanedTempFiles({ tmpDir: dir, minAgeMs: 5 * 60 * 1000 }); expect(result.removed).toBe(0); expect(result.skipped).toBe(1); expect(fs.existsSync(full)).toBe(true); fs.rmSync(dir, { recursive: true, force: true }); }); it('ignores non-matching files', () => { const dir = makeTmpDir(); fs.writeFileSync(path.join(dir, 'normal-file.txt'), 'data'); fs.writeFileSync(path.join(dir, '.fea5.so'), 'data'); // too short hex fs.writeFileSync(path.join(dir, '.fea5abc.txt'), 'data'); // wrong extension const result = cleanupOrphanedTempFiles({ tmpDir: dir }); expect(result.scanned).toBe(0); expect(result.removed).toBe(0); fs.rmSync(dir, { recursive: true, force: true }); }); it('returns zeros for nonexistent tmpDir', () => { const result = cleanupOrphanedTempFiles({ tmpDir: '/tmp/camofox-nonexistent-xyz' }); expect(result).toEqual({ scanned: 0, removed: 0, bytes: 0, skipped: 0 }); }); it('supports injected now for deterministic age comparison', () => { const dir = makeTmpDir(); const full = path.join(dir, '.fea5abc123.so'); fs.writeFileSync(full, 'x'.repeat(10)); const mtimeMs = fs.statSync(full).mtimeMs; // 1 min after mtime: still fresh const fresh = cleanupOrphanedTempFiles({ tmpDir: dir, minAgeMs: 5 * 60 * 1000, now: mtimeMs + 60 * 1000 }); expect(fresh.removed).toBe(0); expect(fresh.skipped).toBe(1); // 10 min after mtime: stale const stale = cleanupOrphanedTempFiles({ tmpDir: dir, minAgeMs: 5 * 60 * 1000, now: mtimeMs + 10 * 60 * 1000 }); expect(stale.removed).toBe(1); fs.rmSync(dir, { recursive: true, force: true }); }); }); describe('cleanupStaleFirefoxProfiles', () => { it('removes old playwright_firefoxdev_profile- directories', () => { const dir = makeTmpDir(); const profileDir = path.join(dir, 'playwright_firefoxdev_profile-abc123'); fs.mkdirSync(profileDir); fs.writeFileSync(path.join(profileDir, 'cache.db'), 'x'.repeat(1000)); fs.writeFileSync(path.join(profileDir, 'prefs.js'), 'data'); // Set mtime to 5 minutes ago const oldTime = new Date(Date.now() - 5 * 60 * 1000); fs.utimesSync(profileDir, oldTime, oldTime); const result = cleanupStaleFirefoxProfiles({ tmpDir: dir, minAgeMs: 2 * 60 * 1000 }); expect(result.removed).toBe(1); expect(result.bytes).toBeGreaterThan(0); expect(fs.existsSync(profileDir)).toBe(false); fs.rmSync(dir, { recursive: true, force: true }); }); it('skips recent profile directories', () => { const dir = makeTmpDir(); const profileDir = path.join(dir, 'playwright_firefoxdev_profile-recent'); fs.mkdirSync(profileDir); fs.writeFileSync(path.join(profileDir, 'data.db'), 'data'); // mtime is now (recent) const result = cleanupStaleFirefoxProfiles({ tmpDir: dir, minAgeMs: 2 * 60 * 1000 }); expect(result.removed).toBe(0); expect(result.skipped).toBe(1); expect(fs.existsSync(profileDir)).toBe(true); fs.rmSync(dir, { recursive: true, force: true }); }); it('removes camoufox- prefixed temp dirs', () => { const dir = makeTmpDir(); const camoDir = path.join(dir, 'camoufox-session-xyz'); fs.mkdirSync(camoDir); fs.writeFileSync(path.join(camoDir, 'state'), 'data'); const oldTime = new Date(Date.now() - 5 * 60 * 1000); fs.utimesSync(camoDir, oldTime, oldTime); const result = cleanupStaleFirefoxProfiles({ tmpDir: dir, minAgeMs: 2 * 60 * 1000 }); expect(result.removed).toBe(1); expect(fs.existsSync(camoDir)).toBe(false); fs.rmSync(dir, { recursive: true, force: true }); }); it('calculates byte count of removed directories', () => { const dir = makeTmpDir(); const profileDir = path.join(dir, 'playwright_firefoxdev_profile-sizeme'); fs.mkdirSync(profileDir); const subDir = path.join(profileDir, 'cache2'); fs.mkdirSync(subDir); fs.writeFileSync(path.join(subDir, 'big.dat'), 'x'.repeat(5000)); fs.writeFileSync(path.join(profileDir, 'small.dat'), 'x'.repeat(100)); const oldTime = new Date(Date.now() - 5 * 60 * 1000); fs.utimesSync(profileDir, oldTime, oldTime); const result = cleanupStaleFirefoxProfiles({ tmpDir: dir, minAgeMs: 2 * 60 * 1000 }); expect(result.removed).toBe(1); expect(result.bytes).toBe(5100); fs.rmSync(dir, { recursive: true, force: true }); }); it('ignores non-matching directories', () => { const dir = makeTmpDir(); fs.mkdirSync(path.join(dir, 'some-other-dir')); const result = cleanupStaleFirefoxProfiles({ tmpDir: dir }); expect(result.scanned).toBe(0); fs.rmSync(dir, { recursive: true, force: true }); }); it('handles empty tmpDir', () => { const dir = makeTmpDir(); const result = cleanupStaleFirefoxProfiles({ tmpDir: dir }); expect(result.scanned).toBe(0); expect(result.removed).toBe(0); fs.rmSync(dir, { recursive: true, force: true }); }); it('handles nonexistent tmpDir gracefully', () => { const result = cleanupStaleFirefoxProfiles({ tmpDir: '/tmp/camofox-nonexistent-dir-xyz' }); expect(result.scanned).toBe(0); }); });