fix(session-persistence): handle Windows directory fsync
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import { appendFile, mkdtemp, mkdir, rm, readFile, writeFile, readdir, stat } from 'node:fs/promises'
|
||||
import { appendFile, mkdtemp, mkdir, open, rm, readFile, writeFile, readdir, stat } from 'node:fs/promises'
|
||||
import type { FileHandle } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
@@ -40,9 +41,25 @@ async function freshRoot(): Promise<string> {
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
vi.restoreAllMocks()
|
||||
for (const d of dirs.splice(0)) await rm(d, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
async function rejectDirectorySync(code: string): Promise<void> {
|
||||
const handle = await open(root, 'r')
|
||||
const proto = Object.getPrototypeOf(handle) as { sync: () => Promise<void> }
|
||||
await handle.close()
|
||||
const realSync = proto.sync
|
||||
vi.spyOn(proto, 'sync').mockImplementation(async function (this: FileHandle) {
|
||||
if ((await this.stat()).isDirectory()) {
|
||||
const error = new Error(`simulated directory fsync ${code}`) as NodeJS.ErrnoException
|
||||
error.code = code
|
||||
throw error
|
||||
}
|
||||
return realSync.call(this)
|
||||
})
|
||||
}
|
||||
|
||||
function appendClosedTurn(session: Session): void {
|
||||
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
session.append('user/message', {
|
||||
@@ -264,6 +281,28 @@ describe('SessionPersistenceJsonl: durability and crash semantics', () => {
|
||||
expect(loaded.events.map(e => e.seq)).toEqual([0, 1, 2, 3, 4, 5, 6, 7])
|
||||
})
|
||||
|
||||
it('keeps file fsync mandatory while tolerating unsupported Windows directory fsync', async () => {
|
||||
await rejectDirectorySync('EPERM')
|
||||
const backend = ctx.sessionPersistence as SessionPersistenceJsonl
|
||||
backend.internals.platform = 'win32'
|
||||
const m = meta('windows-directory-sync')
|
||||
await ctx.sessionPersistence.create(m)
|
||||
await expect(ctx.sessionPersistence.append(m.id, oneTurnLog())).resolves.toBeUndefined()
|
||||
expect((await ctx.sessionPersistence.load(m.id)).events).toEqual(oneTurnLog())
|
||||
})
|
||||
|
||||
it.each([
|
||||
['linux', 'EPERM'],
|
||||
['win32', 'EIO'],
|
||||
] as const)('surfaces directory fsync errors on %s with %s', async (platform, code) => {
|
||||
await rejectDirectorySync(code)
|
||||
const backend = ctx.sessionPersistence as SessionPersistenceJsonl
|
||||
backend.internals.platform = platform
|
||||
const m = meta(`directory-sync-${platform}-${code}`)
|
||||
await ctx.sessionPersistence.create(m)
|
||||
await expect(ctx.sessionPersistence.append(m.id, oneTurnLog())).rejects.toMatchObject({ code })
|
||||
})
|
||||
|
||||
it('load returns a meta copy: mutating it does not corrupt backend pathing', async () => {
|
||||
const m = meta('meta-copy', '/proj')
|
||||
await ctx.sessionPersistence.create(m)
|
||||
|
||||
Reference in New Issue
Block a user