test(windows): replace POSIX-only filesystem fixtures
This commit is contained in:
@@ -459,7 +459,6 @@ export async function readScopeInstruction(
|
|||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<LoadedInstructionFile | undefined> {
|
): Promise<LoadedInstructionFile | undefined> {
|
||||||
const content = await readBounded(file, maxSourceBytes, fileSystem, signal)
|
const content = await readBounded(file, maxSourceBytes, fileSystem, signal)
|
||||||
/* v8 ignore next -- Windows cannot reproduce a post-probe unreadable file with POSIX mode bits. */
|
|
||||||
if (content === undefined) return undefined
|
if (content === undefined) return undefined
|
||||||
return {
|
return {
|
||||||
absolutePath: file.absolutePath,
|
absolutePath: file.absolutePath,
|
||||||
|
|||||||
@@ -437,7 +437,6 @@ export async function reconcileInstructionContext(
|
|||||||
) continue
|
) continue
|
||||||
|
|
||||||
const file = await readScopeInstruction(probedFile, resolved.maxSourceBytes, fileSystem, options.signal)
|
const file = await readScopeInstruction(probedFile, resolved.maxSourceBytes, fileSystem, options.signal)
|
||||||
/* v8 ignore next -- Windows cannot make the probed file unreadable through POSIX mode bits. */
|
|
||||||
if (file === undefined) continue
|
if (file === undefined) continue
|
||||||
const currentDigest = instructionContentSha1(file.content)
|
const currentDigest = instructionContentSha1(file.content)
|
||||||
const nextVersion: InstructionVersionState = {
|
const nextVersion: InstructionVersionState = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { chmod, mkdtemp, mkdir, rm, stat, symlink, utimes, writeFile } from 'node:fs/promises'
|
import { mkdtemp, mkdir, rm, stat, symlink, utimes, writeFile } from 'node:fs/promises'
|
||||||
import { dirname, join, resolve } from 'node:path'
|
import { dirname, join, resolve } from 'node:path'
|
||||||
import { tmpdir } from 'node:os'
|
import { tmpdir } from 'node:os'
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
@@ -53,6 +53,7 @@ class RecordingFileSystem extends FileSystem {
|
|||||||
entries = new Map<string, { type: FsInfo['type']; content?: string; version?: FsVersion }>()
|
entries = new Map<string, { type: FsInfo['type']; content?: string; version?: FsVersion }>()
|
||||||
lstatTypes = new Map<string, FsPathInfo['type']>()
|
lstatTypes = new Map<string, FsPathInfo['type']>()
|
||||||
throwOnStat = new Set<string>()
|
throwOnStat = new Set<string>()
|
||||||
|
throwOnRead = new Set<string>()
|
||||||
omitSizes = new Set<string>()
|
omitSizes = new Set<string>()
|
||||||
readTargets: string[] = []
|
readTargets: string[] = []
|
||||||
readTextTargets: string[] = []
|
readTextTargets: string[] = []
|
||||||
@@ -105,6 +106,7 @@ class RecordingFileSystem extends FileSystem {
|
|||||||
if (signal !== undefined) this.signals.push(signal)
|
if (signal !== undefined) this.signals.push(signal)
|
||||||
signal?.throwIfAborted()
|
signal?.throwIfAborted()
|
||||||
this.readTargets.push(target.targetKey)
|
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 ?? ''
|
const content = this.entries.get(target.targetKey)?.content ?? ''
|
||||||
return (async function* () {
|
return (async function* () {
|
||||||
const midpoint = Math.ceil(content.length / 2)
|
const midpoint = Math.ceil(content.length / 2)
|
||||||
@@ -336,22 +338,25 @@ describe('workspace context instruction discovery', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skipIf(process.platform === 'win32')('skips a file that becomes unreadable after discovery without failing the request', async () => {
|
it('skips a provider file whose read fails after a successful metadata probe', async () => {
|
||||||
const root = await tempRepo()
|
const root = await tempRepo()
|
||||||
const home = await tempRepo()
|
const home = await tempRepo()
|
||||||
|
const ctx = new Context()
|
||||||
try {
|
try {
|
||||||
const cwd = join(root, 'pkg')
|
const cwd = join(root, 'pkg')
|
||||||
await mkdir(join(root, '.git'), { recursive: true })
|
|
||||||
await mkdir(cwd, { recursive: true })
|
|
||||||
const leaf = join(cwd, 'AGENTS.md')
|
const leaf = join(cwd, 'AGENTS.md')
|
||||||
await write(leaf, 'secret-ish rule')
|
await ctx.plugin(RecordingFileSystem)
|
||||||
await chmod(leaf, 0)
|
const fs = ctx.fs as RecordingFileSystem
|
||||||
|
fs.entries.set(join(root, '.git'), { type: 'directory' })
|
||||||
|
fs.entries.set(leaf, { type: 'file', content: 'secret-ish rule' })
|
||||||
|
fs.throwOnRead.add(leaf)
|
||||||
|
|
||||||
const loaded = await loadBaselineInstructions({ cwd, dshHome: home, maxBytes: 65536 })
|
const loaded = await loadBaselineInstructions({ cwd, dshHome: home, maxBytes: 65536 }, fs)
|
||||||
|
|
||||||
expect(loaded).toBeUndefined()
|
expect(loaded).toBeUndefined()
|
||||||
await chmod(leaf, 0o600)
|
expect(fs.readTargets).toEqual([leaf])
|
||||||
} finally {
|
} finally {
|
||||||
|
await ctx.fiber.dispose()
|
||||||
await rm(root, { recursive: true, force: true })
|
await rm(root, { recursive: true, force: true })
|
||||||
await rm(home, { recursive: true, force: true })
|
await rm(home, { recursive: true, force: true })
|
||||||
}
|
}
|
||||||
@@ -2403,17 +2408,22 @@ describe('dynamic nested workspace context injection', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skipIf(process.platform === 'win32')('skips unreadable nested instruction files without attaching empty context', async () => {
|
it('skips unreadable nested instruction files without attaching empty context', async () => {
|
||||||
const root = await tempRepo()
|
const root = await tempRepo()
|
||||||
const home = await tempRepo()
|
const home = await tempRepo()
|
||||||
|
const ctx = new Context()
|
||||||
try {
|
try {
|
||||||
await mkdir(join(root, '.git'), { recursive: true })
|
|
||||||
const nested = join(root, 'pkg/AGENTS.md')
|
const nested = join(root, 'pkg/AGENTS.md')
|
||||||
await write(nested, 'nested package rule')
|
await ctx.plugin(SystemPrompt)
|
||||||
await write(join(root, 'pkg/deep/file.txt'), 'hello')
|
await ctx.plugin(ToolRegistry)
|
||||||
await chmod(nested, 0)
|
await ctx.plugin(RecordingFileSystem)
|
||||||
const ctx = new Context()
|
const fs = ctx.fs as RecordingFileSystem
|
||||||
await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 })
|
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({
|
const result = await ctx.tools.execute({
|
||||||
callId: CallId('read-with-unreadable-nested-instruction'),
|
callId: CallId('read-with-unreadable-nested-instruction'),
|
||||||
@@ -2424,8 +2434,9 @@ describe('dynamic nested workspace context injection', () => {
|
|||||||
|
|
||||||
expect(result.isError).toBe(false)
|
expect(result.isError).toBe(false)
|
||||||
expect(result.additionalContexts).toBeUndefined()
|
expect(result.additionalContexts).toBeUndefined()
|
||||||
await chmod(nested, 0o600)
|
expect(fs.readTargets).toContain(nested)
|
||||||
} finally {
|
} finally {
|
||||||
|
await ctx.fiber.dispose()
|
||||||
await rm(root, { recursive: true, force: true })
|
await rm(root, { recursive: true, force: true })
|
||||||
await rm(home, { recursive: true, force: true })
|
await rm(home, { recursive: true, force: true })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,11 +85,16 @@ describe('saveTextFile', () => {
|
|||||||
expect(saved.path.includes('/..')).toBe(false)
|
expect(saved.path.includes('/..')).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skipIf(process.platform === 'win32')('creates the session dir with owner-only permissions', async () => {
|
it('creates the session directory and file with owner-only POSIX permissions', async () => {
|
||||||
const saved = await saveTextFile({ root, sessionId: 'sess-1', suggestedName: 'r.txt', content: 'x' })
|
const saved = await saveTextFile({ root, sessionId: 'sess-1', suggestedName: 'r.txt', content: 'x' })
|
||||||
// 0o700 dir, 0o600 file (masked by umask, but the owner bits must hold).
|
const directory = statSync(dirname(saved.path))
|
||||||
expect(statSync(dirname(saved.path)).mode & 0o700).toBe(0o700)
|
const file = statSync(saved.path)
|
||||||
expect(statSync(saved.path).mode & 0o600).toBe(0o600)
|
expect(directory.isDirectory()).toBe(true)
|
||||||
|
expect(file.isFile()).toBe(true)
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
expect(directory.mode & 0o777).toBe(0o700)
|
||||||
|
expect(file.mode & 0o777).toBe(0o600)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('gives distinct paths to two saves of the same name', async () => {
|
it('gives distinct paths to two saves of the same name', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user