import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' import { listBackups } from '@authportal/db' import { loadConfig } from '../src/config.js' import { backupDirFor, runBackup, startBackupJob } from '../src/services/backup.js' import { buildApp } from '../src/app.js' import { adminToken } from './helpers.js' const dirs: string[] = [] afterEach(() => { vi.useRealTimers() for (const d of dirs.splice(0)) { try { rmSync(d, { recursive: true, force: true }) } catch { /* Windows может держать файл БД до GC — временный каталог не критичен */ } } }) function env(dir: string, extra: NodeJS.ProcessEnv = {}) { return loadConfig({ JWT_SECRET: 'test-secret-at-least-8', ADMIN_EMAIL: 'admin@test.local', ADMIN_PASSWORD: 'adminpass', DATABASE_URL: `sqlite:${join(dir, 'app.db')}`, NODE_ENV: 'test', ...extra, }) } describe('backup', () => { it('backupDirFor: по умолчанию рядом с БД, BACKUP_DIR приоритетнее', () => { const cfg = env('/data/x') expect(backupDirFor(cfg).replaceAll(String.fromCharCode(92), '/')).toMatch(/\/data\/x\/backups$/) const custom = env('/data/x', { BACKUP_DIR: '/mnt/bk' }) expect(backupDirFor(custom).replaceAll(String.fromCharCode(92), '/')).toMatch(/\/mnt\/bk$/) }) it('бэкап живого приложения восстанавливается: вход админа работает', async () => { const dir = mkdtempSync(join(tmpdir(), 'ap-api-bk-')) dirs.push(dir) const config = env(dir, { BACKUP_KEEP: '2' }) const app = await buildApp({ config }) const res = await runBackup(app.sqlite, config) expect(listBackups(backupDirFor(config))).toHaveLength(1) await app.close() const restored = await buildApp({ config: env(dir), databaseUrl: `sqlite:${res.file}`, }) const token = await adminToken(restored as never) expect(token).toBeTruthy() await restored.close() }) it('startBackupJob выключен при 0 часов и работает по таймеру', async () => { vi.useFakeTimers() const dir = mkdtempSync(join(tmpdir(), 'ap-job-bk-')) dirs.push(dir) const config = env(dir, { BACKUP_INTERVAL_HOURS: '1' }) const app = await buildApp({ config }) const base = vi.getTimerCount() const off = startBackupJob({ ...app, config: { ...config, backupIntervalHours: 0 } } as never) expect(vi.getTimerCount()).toBe(base) off() const stop = startBackupJob(app) expect(vi.getTimerCount()).toBe(base + 1) await vi.advanceTimersByTimeAsync(60 * 60 * 1000 + 10) vi.useRealTimers() await vi.waitFor(() => expect(listBackups(backupDirFor(config)).length).toBeGreaterThan(0), ) stop() await app.close() }) })