Files
harheimertc/tests/data-file-rotation.spec.ts
Torsten Schulz (local) b4e1c50ea3
Some checks failed
Code Analysis and Production Deploy / analyze (push) Failing after 5m57s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Has been skipped
semgrep problems fix
2026-06-10 13:55:50 +02:00

134 lines
4.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import os from 'os'
import path from 'path'
import { promises as fs } from 'fs'
import {
getBackupDirectoryForDataFile,
resolveDataFileBackupPath,
listDataFileBackups,
restoreDataFileBackup,
writeDataFileWithRotation
} from '../server/utils/data-file-rotation.js'
describe('Data file rotation utility', () => {
let tempRoot = ''
let backupRoot = ''
let previousBackupDir = ''
let dataFile = ''
beforeEach(async () => {
tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'harheimertc-data-rotation-'))
backupRoot = path.join(tempRoot, 'backup-store')
dataFile = path.join(tempRoot, 'server', 'data', 'users.json')
previousBackupDir = process.env.DATA_FILE_BACKUP_DIR || ''
process.env.DATA_FILE_BACKUP_DIR = backupRoot
})
afterEach(async () => {
if (previousBackupDir) {
process.env.DATA_FILE_BACKUP_DIR = previousBackupDir
} else {
delete process.env.DATA_FILE_BACKUP_DIR
}
if (tempRoot) {
await fs.rm(tempRoot, { recursive: true, force: true })
}
})
it('writes initial file without creating backup', async () => {
const result = await writeDataFileWithRotation(dataFile, 'v1', { maxBackups: 5 })
expect(result.changed).toBe(true)
expect(result.backupPath).toBe(null)
const content = await fs.readFile(dataFile, 'utf-8')
expect(content).toBe('v1')
const backups = await listDataFileBackups(dataFile)
expect(backups).toEqual([])
})
it('creates backup from previous content on change', async () => {
await writeDataFileWithRotation(dataFile, 'v1', { maxBackups: 5 })
const result = await writeDataFileWithRotation(dataFile, 'v2', { maxBackups: 5 })
expect(result.changed).toBe(true)
expect(result.backupPath).toBeTruthy()
const backups = await listDataFileBackups(dataFile)
expect(backups.length).toBe(1)
const backupDir = getBackupDirectoryForDataFile(dataFile)
const backupContent = await fs.readFile(resolveDataFileBackupPath(backupDir, backups[0]), 'utf-8')
expect(backupContent).toBe('v1')
const currentContent = await fs.readFile(dataFile, 'utf-8')
expect(currentContent).toBe('v2')
})
it('does not create backup when content is unchanged', async () => {
await writeDataFileWithRotation(dataFile, 'stable', { maxBackups: 5 })
await writeDataFileWithRotation(dataFile, 'next', { maxBackups: 5 })
const before = await listDataFileBackups(dataFile)
const result = await writeDataFileWithRotation(dataFile, 'next', { maxBackups: 5 })
const after = await listDataFileBackups(dataFile)
expect(result.changed).toBe(false)
expect(result.backupPath).toBe(null)
expect(after).toEqual(before)
})
it('rotates old backups and keeps configured maximum', async () => {
await writeDataFileWithRotation(dataFile, 'v1', { maxBackups: 2 })
await writeDataFileWithRotation(dataFile, 'v2', { maxBackups: 2 })
await writeDataFileWithRotation(dataFile, 'v3', { maxBackups: 2 })
await writeDataFileWithRotation(dataFile, 'v4', { maxBackups: 2 })
const backups = await listDataFileBackups(dataFile)
expect(backups.length).toBe(2)
const backupDir = getBackupDirectoryForDataFile(dataFile)
const backupContents = await Promise.all(
backups.map((name) => fs.readFile(resolveDataFileBackupPath(backupDir, name), 'utf-8'))
)
expect(backupContents).toEqual(expect.arrayContaining(['v2', 'v3']))
expect(backupContents).not.toContain('v1')
})
it('restores selected backup and keeps pre-restore state as backup', async () => {
await writeDataFileWithRotation(dataFile, 'v1', { maxBackups: 10 })
await writeDataFileWithRotation(dataFile, 'v2', { maxBackups: 10 })
await writeDataFileWithRotation(dataFile, 'v3', { maxBackups: 10 })
const beforeRestoreBackups = await listDataFileBackups(dataFile)
const backupDir = getBackupDirectoryForDataFile(dataFile)
const resolved = await Promise.all(
beforeRestoreBackups.map(async (name) => ({
name,
content: await fs.readFile(resolveDataFileBackupPath(backupDir, name), 'utf-8')
}))
)
const v1Entry = resolved.find((entry) => entry.content === 'v1')
expect(v1Entry).toBeTruthy()
const restoreResult = await restoreDataFileBackup(dataFile, v1Entry!.name, { maxBackups: 10 })
expect(restoreResult.changed).toBe(true)
expect(restoreResult.backupPath).toBeTruthy()
const restoredContent = await fs.readFile(dataFile, 'utf-8')
expect(restoredContent).toBe('v1')
const afterRestoreBackups = await listDataFileBackups(dataFile)
const afterContents = await Promise.all(
afterRestoreBackups.map((name) => fs.readFile(resolveDataFileBackupPath(backupDir, name), 'utf-8'))
)
expect(afterContents).toContain('v3')
})
})