83 lines
2.9 KiB
TypeScript
83 lines
2.9 KiB
TypeScript
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()
|
|
})
|
|
})
|