Add unit tests for data file rotation utility functions
- Implement tests for writing data files with rotation, ensuring backups are created only on changes. - Verify that old backups are rotated correctly and the maximum number of backups is maintained. - Test restoration of backups while preserving the current state as a backup. - Utilize Vitest for testing framework and manage temporary file storage during tests.
This commit is contained in:
132
tests/data-file-rotation.spec.ts
Normal file
132
tests/data-file-rotation.spec.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { promises as fs } from 'fs'
|
||||
import {
|
||||
getBackupDirectoryForDataFile,
|
||||
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(path.join(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(path.join(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(path.join(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(path.join(backupDir, name), 'utf-8'))
|
||||
)
|
||||
|
||||
expect(afterContents).toContain('v3')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user