Files
deepseek-harness/packages/fs/fs/tests/service.spec.ts
Tianyi Cui b3f8b4c9c6 test(fs): close abort/concurrency/observed coverage gaps + with-key e2e
Behavioral gaps from the coverage audit (line coverage was already 100%; these
close BEHAVIOR gaps):

- fs-local: service-level writeText/editText pre-abort → FS_ABORTED (file
  unchanged); concurrent guarded-write race and mixed write-vs-edit race (one
  wins, one FS_STALE_VERSION, locks released); edit→edit version refresh at the
  provider; the replaceIfVersion post-write version matches a fresh stat. fsio:
  a mid-stream abort → FS_ABORTED (previously only pre-abort was covered).
- fs-policy: the agent-without-session owner rung ({agent:{}} → no owner →
  createIfAbsent / FS_NOT_OBSERVED); fs/write-intent first-wins (symmetric to
  the existing edit-intent test).
- tool-fs: abort-through-the-tool for read/write/edit (isError FS_ABORTED, file
  unchanged); a deterministic tool-tier concurrent-edit race via a shared read;
  the throwing-fs/observed contract (a throwing listener surfaces as isError but
  the mutation already hit disk); the replace_all edit message; parseReadArgs
  rejects fractional/NaN offset and zero/negative limit.
- dsh-fs: FsError chains a cause through ErrorOptions.

New with-key e2e (packages/fs/tool-fs/tests/fs-tools.e2e.ts, self-skips without
DEEPSEEK_API_KEY): a real model drives the real read/write/edit tools to create
→ read → edit a file, verified on disk; a second test proves a relative path
resolves against the per-session cwd (factory meta.cwd) not config.cwd. Booted
via a plain tests/harness.ts. Added dsh-agent-loop + dsh-llm-deepseek devDeps.
2026-07-02 20:38:06 +08:00

119 lines
4.4 KiB
TypeScript

/**
* Tests for the filesystem provider seam itself: registration, duplicate-service
* behavior, disposal, and the branded id factories. The provider primitives and
* policy live in `dsh-fs-local` and `dsh-fs-policy`; this seam owns only the
* abstract service contract, so a minimal fake backend exercises it.
*/
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import { FileSystem, FsError, FsTargetKey, FsVersion } from '@deepseek-ai/dsh-fs'
import type {
FsEditOutcome,
FsEditRequest,
FsInfo,
FsTarget,
FsWriteIntent,
FsWriteOutcome,
} from '@deepseek-ai/dsh-fs'
/** A minimal in-memory fake implementing the six provider primitives. */
class FakeFileSystem extends FileSystem {
files = new Map<string, string>()
override async resolve(path: string): Promise<FsTarget> {
return { inputPath: path, targetKey: FsTargetKey(path), displayPath: path }
}
override async stat(target: FsTarget): Promise<FsInfo | undefined> {
const content = this.files.get(target.targetKey)
if (content === undefined) return undefined
return { version: FsVersion('v1'), type: 'file', size: content.length }
}
override async readText(target: FsTarget): Promise<string> {
const content = this.files.get(target.targetKey)
if (content === undefined) throw new FsError(`not found: ${target.displayPath}`, 'FS_NOT_FOUND')
return content
}
override async streamText(target: FsTarget): Promise<AsyncIterable<string>> {
const content = await this.readText(target)
return (async function* () { yield content })()
}
override async writeText(target: FsTarget, content: string, _expected?: FsWriteIntent): Promise<FsWriteOutcome> {
const existed = this.files.has(target.targetKey)
this.files.set(target.targetKey, content)
return { operation: existed ? 'update' : 'create', version: FsVersion('v2') }
}
override async editText(target: FsTarget, edit: FsEditRequest): Promise<FsEditOutcome> {
const content = this.files.get(target.targetKey) ?? ''
this.files.set(target.targetKey, content.split(edit.oldString).join(edit.newString))
return { replacements: 1, replaceAll: edit.replaceAll, version: FsVersion('v3') }
}
}
describe('FileSystem provider seam', () => {
it('registers as ctx.fs and serves the primitives', async () => {
const ctx = new Context()
await ctx.plugin(FakeFileSystem)
const fs = ctx.fs as FakeFileSystem
fs.files.set('a.txt', 'hi')
const target = await fs.resolve('a.txt')
expect((await fs.stat(target))?.type).toBe('file')
expect(await fs.readText(target)).toBe('hi')
})
it('throws when a second implementation is loaded (duplicate service)', async () => {
const ctx = new Context()
await ctx.plugin(FakeFileSystem)
await expect(ctx.plugin(FakeFileSystem)).rejects.toThrow()
})
it('removes the service when the providing fiber is disposed', async () => {
const ctx = new Context()
const fiber = await ctx.plugin(FakeFileSystem)
expect(ctx.fs).toBeDefined()
await fiber.dispose()
expect(ctx.fs).toBeUndefined()
})
it('streamText yields the same text readText returns', async () => {
const ctx = new Context()
await ctx.plugin(FakeFileSystem)
const fs = ctx.fs as FakeFileSystem
fs.files.set('a.txt', 'one\ntwo')
const target = await fs.resolve('a.txt')
let streamed = ''
for await (const chunk of await fs.streamText(target)) streamed += chunk
expect(streamed).toBe(await fs.readText(target))
})
it('stat returns undefined for an absent target', async () => {
const ctx = new Context()
await ctx.plugin(FakeFileSystem)
const fs = ctx.fs as FakeFileSystem
expect(await fs.stat(await fs.resolve('missing.txt'))).toBeUndefined()
})
})
describe('branded id factories', () => {
it('FsTargetKey and FsVersion brand a string at compile time (identity at runtime)', () => {
expect(FsTargetKey('k')).toBe('k')
expect(FsVersion('v')).toBe('v')
})
})
describe('FsError', () => {
it('carries a stable code and HarnessError name', () => {
const error = new FsError('nope', 'FS_NOT_FOUND')
expect(error.code).toBe('FS_NOT_FOUND')
expect(error.name).toBe('FsError')
expect(error).toBeInstanceOf(Error)
})
it('chains an underlying cause through ErrorOptions', () => {
const root = new Error('EACCES')
const error = new FsError('cannot read', 'FS_ABORTED', { cause: root })
expect(error.cause).toBe(root)
expect(error.code).toBe('FS_ABORTED')
})
})