Files
hermes-desktop/tests/server/safe-file-store-backup-fallback.test.ts
toor 01d9e301fc
Build / build (push) Canceled after 0s
Playwright / browser (push) Canceled after 0s
initialize Electron desktop app and remove unused files
2026-10-25 01:09:01 +08:00

71 lines
2.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, describe, expect, it, vi } from 'vitest'
import { resolve } from 'path'
const {
mockCopyFile,
mockMkdir,
mockReadFile,
mockRename,
mockRm,
mockWriteFile,
} = vi.hoisted(() => ({
mockCopyFile: vi.fn(),
mockMkdir: vi.fn(),
mockReadFile: vi.fn(),
mockRename: vi.fn(),
mockRm: vi.fn(),
mockWriteFile: vi.fn(),
}))
vi.mock('fs/promises', () => ({
copyFile: mockCopyFile,
mkdir: mockMkdir,
readFile: mockReadFile,
rename: mockRename,
rm: mockRm,
writeFile: mockWriteFile,
}))
afterEach(() => {
vi.clearAllMocks()
})
describe('SafeFileStore backup fallback', () => {
it('uses a timestamped backup when the default backup cannot be overwritten', async () => {
mockCopyFile
.mockRejectedValueOnce(Object.assign(new Error('permission denied'), { code: 'EACCES' }))
.mockResolvedValueOnce(undefined)
mockMkdir.mockResolvedValue(undefined)
mockWriteFile.mockResolvedValue(undefined)
mockRename.mockResolvedValue(undefined)
const { SafeFileStore } = await import('../../packages/server/src/services/safe-file-store')
const store = new SafeFileStore()
// SafeFileStore.normalizePath() 走 resolve(),Windows 上 resolve('/tmp/config.yaml')
// = 'D:\tmp\config.yaml'。断言必须按生产语义写死,否则只在 Windows 上红。
const target = resolve('/tmp/config.yaml')
await store.writeText('/tmp/config.yaml', 'model:\n default: new\n', { backup: true })
expect(mockCopyFile).toHaveBeenCalledTimes(2)
expect(mockCopyFile).toHaveBeenNthCalledWith(1, target, `${target}.bak`)
expect(mockCopyFile.mock.calls[1][0]).toBe(target)
expect(mockCopyFile.mock.calls[1][1].startsWith(`${target}.bak.`)).toBe(true)
expect(mockWriteFile).toHaveBeenCalledWith(expect.stringContaining(`${target}.tmp.`), 'model:\n default: new\n', 'utf-8')
expect(mockRename).toHaveBeenCalledWith(expect.stringContaining(`${target}.tmp.`), target)
})
it('preserves explicit backup path failures', async () => {
mockCopyFile.mockRejectedValueOnce(Object.assign(new Error('permission denied'), { code: 'EACCES' }))
mockMkdir.mockResolvedValue(undefined)
const { SafeFileStore } = await import('../../packages/server/src/services/safe-file-store')
const store = new SafeFileStore()
await expect(store.writeText('/tmp/config.yaml', 'new', { backup: true, backupPath: '/tmp/custom.bak' })).rejects.toThrow('permission denied')
expect(mockCopyFile).toHaveBeenCalledTimes(1)
expect(mockWriteFile).not.toHaveBeenCalled()
})
})