瀏覽代碼

test(workspace-context): make the merged suite's fixtures cross-platform

Path expectations derive from join() per the cross-platform fixtures
convention; the fake fs resolves against the host root so drive-letter
targets match seeded keys; the unreadable-candidate cases use the provider
throwOnRead fixture (host chmod 0 is a no-op for the owner on Windows),
keeping the read-failure branch covered there, with a narrow win32 skip only
for the host-chmod discovery case.
Turtle 2 月之前
父節點
當前提交
ab32d3ec98
共有 1 個文件被更改,包括 25 次插入 和 12 次删除
  1. 25 12
      packages/context/workspace-context/tests/workspace-context.spec.ts

+ 25 - 12
packages/context/workspace-context/tests/workspace-context.spec.ts

@@ -58,6 +58,7 @@ async function write(path: string, content: string): Promise<void> {
 class RecordingFileSystem extends FileSystem {
   entries = new Map<string, { type: FsInfo['type']; content?: string; version?: FsVersion }>()
   throwOnStat = new Set<string>()
+  throwOnRead = new Set<string>()
   omitSizes = new Set<string>()
   readTargets: string[] = []
   readTextTargets: string[] = []
@@ -66,7 +67,9 @@ class RecordingFileSystem extends FileSystem {
   override async resolve(path: string, opts?: { cwd?: string; signal?: AbortSignal }): Promise<FsTarget> {
     if (opts?.signal !== undefined) this.signals.push(opts.signal)
     opts?.signal?.throwIfAborted()
-    const absolute = join(opts?.cwd ?? '/', path)
+    // resolve(), not join(): entries are seeded with host join() keys, and on
+    // Windows a joined '/'-rooted prefix would not match a resolved drive path.
+    const absolute = resolve(opts?.cwd ?? '/', path)
     return { targetKey: FsTargetKey(absolute), displayPath: absolute }
   }
 
@@ -108,6 +111,7 @@ class RecordingFileSystem extends FileSystem {
     if (signal !== undefined) this.signals.push(signal)
     signal?.throwIfAborted()
     this.readTargets.push(target.targetKey)
+    if (this.throwOnRead.has(target.targetKey)) throw new Error(`read failed: ${target.displayPath}`)
     const content = this.entries.get(target.targetKey)?.content ?? ''
     return (async function* () {
       const midpoint = Math.ceil(content.length / 2)
@@ -383,7 +387,8 @@ describe('workspace context instruction discovery', () => {
     }
   })
 
-  it('skips a file that becomes unreadable after discovery without failing the request', async () => {
+  // POSIX-only fixture: chmod 0 cannot make a file unreadable to its owner on Windows.
+  it.skipIf(process.platform === 'win32')('skips a file that becomes unreadable after discovery without failing the request', async () => {
     const root = await tempRepo()
     const home = await tempRepo()
     try {
@@ -1818,7 +1823,7 @@ describe('dynamic nested workspace context injection', () => {
       const text = blocksText(workspaceContextOf(result)?.content)
       expect(text).toBe([
         '<system-reminder>',
-        'Additional instructions from: pkg/AGENTS.md',
+        `Additional instructions from: ${join('pkg', 'AGENTS.md')}`,
         '',
         'These instructions apply to work under `pkg`. Use them as guidance when relevant; more specific instructions take precedence. They do not override system, developer, or direct user instructions.',
         '',
@@ -2115,7 +2120,7 @@ describe('dynamic nested workspace context injection', () => {
       })
       expect(blocksText(workspaceContextOf(changed)?.content)).toBe([
         '<system-reminder>',
-        'Updated instructions from: pkg/AGENTS.md',
+        `Updated instructions from: ${join('pkg', 'AGENTS.md')}`,
         '',
         'This file changed after it was loaded. Use the following content instead of the previously loaded instructions from this file.',
         '',
@@ -2300,7 +2305,7 @@ describe('dynamic nested workspace context injection', () => {
       })
       expect(blocksText(workspaceContextOf(removed)?.content)).toBe([
         '<system-reminder>',
-        'Instructions removed: pkg/AGENTS.md',
+        `Instructions removed: ${join('pkg', 'AGENTS.md')}`,
         '',
         'The previously loaded instructions from this file no longer apply.',
         '</system-reminder>',
@@ -2755,16 +2760,23 @@ describe('dynamic nested workspace context injection', () => {
   })
 
   it('skips unreadable nested instruction files without attaching empty context', async () => {
+    // Cross-platform unreadable fixture: the provider read throws (chmod 0
+    // cannot make a file unreadable to its owner on Windows).
     const root = await tempRepo()
     const home = await tempRepo()
+    const ctx = new Context()
     try {
-      await mkdir(join(root, '.git'), { recursive: true })
       const nested = join(root, 'pkg/AGENTS.md')
-      await write(nested, 'nested package rule')
-      await write(join(root, 'pkg/deep/file.txt'), 'hello')
-      await chmod(nested, 0)
-      const ctx = new Context()
-      await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 })
+      await ctx.plugin(SystemPrompt)
+      await ctx.plugin(ToolRegistry)
+      await ctx.plugin(RecordingFileSystem)
+      const fs = ctx.fs as RecordingFileSystem
+      fs.entries.set(join(root, '.git'), { type: 'directory' })
+      fs.entries.set(nested, { type: 'file', content: 'nested package rule' })
+      fs.entries.set(join(root, 'pkg/deep/file.txt'), { type: 'file', content: 'hello' })
+      fs.throwOnRead.add(nested)
+      await ctx.plugin(ToolFs)
+      await ctx.plugin(workspaceContext, { dshHome: home, maxBytes: 65536 })
 
       const result = await ctx.tools.execute({
         signal: testToolSignal,
@@ -2776,8 +2788,9 @@ describe('dynamic nested workspace context injection', () => {
 
       expect(result.isError).toBe(false)
       expect(result.additionalContexts).toBeUndefined()
-      await chmod(nested, 0o600)
+      expect(fs.readTargets).toContain(nested)
     } finally {
+      await ctx.fiber.dispose()
       await rm(root, { recursive: true, force: true })
       await rm(home, { recursive: true, force: true })
     }