From 7e620db8ba470cc48b44c8a1887208b03206bd3a Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:08:59 +0800 Subject: [PATCH] test(windows): use host path semantics --- .../tests/workspace-context.spec.ts | 46 +++++++++---------- .../fs/tool-fs-search/tests/tools.spec.ts | 7 +-- .../spill-local/tests/spill-local.spec.ts | 7 +-- packages/util/paths/tests/paths.spec.ts | 4 +- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/packages/context/workspace-context/tests/workspace-context.spec.ts b/packages/context/workspace-context/tests/workspace-context.spec.ts index 5f04b3a485..9de003ae8d 100644 --- a/packages/context/workspace-context/tests/workspace-context.spec.ts +++ b/packages/context/workspace-context/tests/workspace-context.spec.ts @@ -1,5 +1,5 @@ import { chmod, mkdtemp, mkdir, rm, stat, symlink, utimes, writeFile } from 'node:fs/promises' -import { dirname, join } from 'node:path' +import { dirname, join, resolve } from 'node:path' import { tmpdir } from 'node:os' import { describe, expect, it, vi } from 'vitest' import { Context } from 'cordis' @@ -61,7 +61,7 @@ class RecordingFileSystem extends FileSystem { override async resolve(path: string, opts?: { cwd?: string; signal?: AbortSignal }): Promise { if (opts?.signal !== undefined) this.signals.push(opts.signal) opts?.signal?.throwIfAborted() - const absolute = join(opts?.cwd ?? '/', path) + const absolute = resolve(opts?.cwd ?? '/', path) return { targetKey: FsTargetKey(absolute), displayPath: absolute } } @@ -277,8 +277,8 @@ describe('workspace context instruction discovery', () => { expect(files.map(file => file.displayPath)).toEqual([ '$DSH_HOME/AGENTS.md', 'AGENTS.md', - 'packages/CLAUDE.md', - 'packages/app/AGENTS.md', + join('packages', 'CLAUDE.md'), + join('packages', 'app', 'AGENTS.md'), ]) expect(files.map(file => file.absolutePath)).not.toContain(join(root, 'CLAUDE.md')) } finally { @@ -336,7 +336,7 @@ describe('workspace context instruction discovery', () => { } }) - it('skips a file that becomes unreadable after discovery without failing the request', async () => { + 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 { @@ -932,7 +932,7 @@ describe('workspace context request injection', () => { await composeBaselinePrefix(ctx, agent) expect(derivedText(agent)).toContain('omitted AGENTS.md') - expect(derivedText(agent)).toContain('Instructions from: pkg/AGENTS.md\n\npackage rule') + expect(derivedText(agent)).toContain(`Instructions from: ${join('pkg', 'AGENTS.md')}\n\npackage rule`) } finally { await rm(root, { recursive: true, force: true }) await rm(home, { recursive: true, force: true }) @@ -1398,7 +1398,7 @@ describe('workspace context request injection', () => { await composeBaselinePrefix(ctx, agent) expect(derivedText(agent)).toContain('Instructions from: AGENTS.md\n\nroot schema default rule') - expect(derivedText(agent)).toContain('Instructions from: child/AGENTS.md\n\nchild schema default rule') + expect(derivedText(agent)).toContain(`Instructions from: ${join('child', 'AGENTS.md')}\n\nchild schema default rule`) await ctx.fiber.dispose() } finally { await rm(root, { recursive: true, force: true }) @@ -1700,7 +1700,7 @@ describe('dynamic nested workspace context injection', () => { changes: [{ action: 'set', scope: 'pkg', - path: 'pkg/AGENTS.md', + path: join('pkg', 'AGENTS.md'), }], }) const meta = workspaceContextOf(result)?.meta @@ -1714,7 +1714,7 @@ describe('dynamic nested workspace context injection', () => { const text = blocksText(workspaceContextOf(result)?.content) expect(text).toBe([ '', - '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.', '', @@ -1752,7 +1752,7 @@ describe('dynamic nested workspace context injection', () => { }) const text = blocksText(workspaceContextOf(result)?.content) - expect(text).toContain('Additional instructions from: pkg/CLAUDE.local.md') + expect(text).toContain(`Additional instructions from: ${join('pkg', 'CLAUDE.local.md')}`) expect(text).toContain('local package rule') expect(text).not.toContain('native package rule') } finally { @@ -1922,11 +1922,11 @@ describe('dynamic nested workspace context injection', () => { expect(workspaceContextOf(changed)?.meta).toMatchObject({ kind: 'workspace-instructions', - changes: [{ action: 'replace', scope: 'pkg', path: 'pkg/AGENTS.md' }], + changes: [{ action: 'replace', scope: 'pkg', path: join('pkg', 'AGENTS.md') }], }) expect(blocksText(workspaceContextOf(changed)?.content)).toBe([ '', - '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.', '', @@ -1966,11 +1966,11 @@ describe('dynamic nested workspace context injection', () => { expect(workspaceContextOf(changed)?.meta).toMatchObject({ changes: [{ - action: 'replace', scope: 'pkg', path: 'pkg/CLAUDE.md', previousPath: 'pkg/AGENTS.md', + action: 'replace', scope: 'pkg', path: join('pkg', 'CLAUDE.md'), previousPath: join('pkg', 'AGENTS.md'), }], }) - expect(blocksText(workspaceContextOf(changed)?.content)).toContain('Updated instructions from: pkg/CLAUDE.md') - expect(blocksText(workspaceContextOf(changed)?.content)).toContain('The instructions previously loaded from `pkg/AGENTS.md` no longer apply. Use the following content for `pkg` instead.') + expect(blocksText(workspaceContextOf(changed)?.content)).toContain(`Updated instructions from: ${join('pkg', 'CLAUDE.md')}`) + expect(blocksText(workspaceContextOf(changed)?.content)).toContain(`The instructions previously loaded from \`${join('pkg', 'AGENTS.md')}\` no longer apply. Use the following content for \`pkg\` instead.`) expect(blocksText(workspaceContextOf(changed)?.content)).toContain('fallback package rule') expect(unchanged.additionalContexts).toBeUndefined() } finally { @@ -2002,11 +2002,11 @@ describe('dynamic nested workspace context injection', () => { expect(workspaceContextOf(removed)?.meta).toEqual({ kind: 'workspace-instructions', version: 1, - changes: [{ action: 'remove', scope: 'pkg', path: 'pkg/AGENTS.md' }], + changes: [{ action: 'remove', scope: 'pkg', path: join('pkg', 'AGENTS.md') }], }) expect(blocksText(workspaceContextOf(removed)?.content)).toBe([ '', - 'Instructions removed: pkg/AGENTS.md', + `Instructions removed: ${join('pkg', 'AGENTS.md')}`, '', 'The previously loaded instructions from this file no longer apply.', '', @@ -2044,9 +2044,9 @@ describe('dynamic nested workspace context injection', () => { }) expect(workspaceContextOf(restored)?.meta).toMatchObject({ - changes: [{ action: 'set', scope: 'pkg', path: 'pkg/AGENTS.md' }], + changes: [{ action: 'set', scope: 'pkg', path: join('pkg', 'AGENTS.md') }], }) - expect(blocksText(workspaceContextOf(restored)?.content)).toContain('Additional instructions from: pkg/AGENTS.md') + expect(blocksText(workspaceContextOf(restored)?.content)).toContain(`Additional instructions from: ${join('pkg', 'AGENTS.md')}`) expect(blocksText(workspaceContextOf(restored)?.content)).toContain('restored package rule') } finally { await rm(root, { recursive: true, force: true }) @@ -2146,7 +2146,7 @@ describe('dynamic nested workspace context injection', () => { const update = resumed.session.events.findLast(event => event.type === 'context/message') expect(update?.type === 'context/message' && update.data.meta).toMatchObject({ - changes: [{ action: 'replace', scope: 'pkg', path: 'pkg/AGENTS.md' }], + changes: [{ action: 'replace', scope: 'pkg', path: join('pkg', 'AGENTS.md') }], }) expect(update?.type === 'context/message' && blocksText(update.data.content)).toContain('new nested rule after resume') } finally { @@ -2267,8 +2267,8 @@ describe('dynamic nested workspace context injection', () => { }) const firstText = blocksText(workspaceContextOf(first)?.content) - expect(firstText).toContain('omitted pkg/AGENTS.md') - expect(firstText).not.toContain('## pkg/AGENTS.md') + expect(firstText).toContain(`omitted ${join('pkg', 'AGENTS.md')}`) + expect(firstText).not.toContain(`## ${join('pkg', 'AGENTS.md')}`) expect(firstText).toContain('subtree rule') expect(blocksText(workspaceContextOf(second)?.content)).toContain('parent rule') } finally { @@ -2462,7 +2462,7 @@ describe('dynamic nested workspace context injection', () => { expect(workspaceContextOf(result)?.envelope).toBe('raw') expect(workspaceContextOf(result)?.meta).toMatchObject({ kind: 'workspace-instructions', - changes: [{ action: 'set', scope: 'pkg', path: 'pkg/AGENTS.md' }], + changes: [{ action: 'set', scope: 'pkg', path: join('pkg', 'AGENTS.md') }], }) expect(blocksText(workspaceContextOf(result)?.content)).toContain('nested package rule') expect(blocksText(workspaceContextOf(result)?.content)).not.toContain('downstream context') diff --git a/packages/fs/tool-fs-search/tests/tools.spec.ts b/packages/fs/tool-fs-search/tests/tools.spec.ts index 5363344f07..add5b932f9 100644 --- a/packages/fs/tool-fs-search/tests/tools.spec.ts +++ b/packages/fs/tool-fs-search/tests/tools.spec.ts @@ -12,6 +12,7 @@ import { describe, expect, it } from 'vitest' import { Context } from 'cordis' +import { join } from 'node:path' import { CallId } from '@deepseek-ai/dsh-llm' import SystemPrompt, { renderPrompt } from '@deepseek-ai/dsh-system-prompt' import ToolRegistry from '@deepseek-ai/dsh-tools' @@ -416,7 +417,7 @@ describe('glob results', () => { const { ctx, bash } = await setup() bash.handler = () => runResult('/sessions/s1/src/a.ts\n/elsewhere/b.ts\nrel/c.ts\n') const result = await call(ctx, 'glob', { pattern: '*' }, { agent: agent('/sessions/s1') }) - expect(text(result)).toBe('src/a.ts\n/elsewhere/b.ts\nrel/c.ts') + expect(text(result)).toBe(`${join('src', 'a.ts')}\n/elsewhere/b.ts\nrel/c.ts`) }) it('validates arguments (blank pattern, blank path)', async () => { @@ -498,7 +499,7 @@ describe('grep results', () => { const { ctx, bash } = await setup() bash.handler = () => runResult(`${matchLine('/sessions/s1/deep/a.ts', 2, 'hit')}\n`) const result = await call(ctx, 'grep', { pattern: 'hit', path: '/sessions/s1' }, { agent: agent('/sessions/s1') }) - expect(text(result)).toContain('deep/a.ts\nLine 2: hit') + expect(text(result)).toContain(`${join('deep', 'a.ts')}\nLine 2: hit`) }) it('previews a long matched line at grepMaxLineBytes preserving UTF-8', async () => { @@ -608,7 +609,7 @@ describe('presentation', () => { describe('helpers', () => { it('toWorkdirRelative maps inside-workdir absolutes and passes everything else through', () => { - expect(toWorkdirRelative('/w/a/b.ts', '/w')).toBe('a/b.ts') + expect(toWorkdirRelative('/w/a/b.ts', '/w')).toBe(join('a', 'b.ts')) expect(toWorkdirRelative('/w', '/w')).toBe('.') expect(toWorkdirRelative('/other/b.ts', '/w')).toBe('/other/b.ts') expect(toWorkdirRelative('/w-sibling/b.ts', '/w')).toBe('/w-sibling/b.ts') diff --git a/packages/spill/spill-local/tests/spill-local.spec.ts b/packages/spill/spill-local/tests/spill-local.spec.ts index b4abc6e3d6..46fa0b66b2 100644 --- a/packages/spill/spill-local/tests/spill-local.spec.ts +++ b/packages/spill/spill-local/tests/spill-local.spec.ts @@ -10,7 +10,7 @@ import { describe, expect, it, beforeEach, afterEach } from 'vitest' import { Context } from 'cordis' import { mkdtempSync, readFileSync, rmSync, statSync } from 'node:fs' import { tmpdir } from 'node:os' -import { dirname, isAbsolute, join } from 'node:path' +import { basename, dirname, isAbsolute, join, normalize } from 'node:path' import { CallId } from '@deepseek-ai/dsh-llm' import { SessionId } from '@deepseek-ai/dsh-session' import type { SaveTextSpill } from '@deepseek-ai/dsh-spill' @@ -63,7 +63,8 @@ describe('sessionDir', () => { it('is a stable per-session hash under the root', () => { const dir = sessionDir('/spill', 'sess-1') expect(dir).toBe(sessionDir('/spill', 'sess-1')) - expect(dir).toMatch(/\/spill\/session-[0-9a-f]{12}$/) + expect(dirname(dir)).toBe(normalize('/spill')) + expect(basename(dir)).toMatch(/^session-[0-9a-f]{12}$/) expect(sessionDir('/spill', 'sess-2')).not.toBe(dir) }) }) @@ -74,7 +75,7 @@ describe('saveTextFile', () => { expect(readFileSync(saved.path, 'utf8')).toBe('héllo') expect(saved.bytes).toBe(Buffer.byteLength('héllo', 'utf8')) expect(dirname(saved.path)).toBe(sessionDir(root, 'sess-1')) - expect(saved.path).toMatch(/\/[0-9a-f]{12}-r\.txt$/) + expect(basename(saved.path)).toMatch(/^[0-9a-f]{12}-r\.txt$/) }) it('sanitizes a traversal-shaped suggested name into one segment', async () => { diff --git a/packages/util/paths/tests/paths.spec.ts b/packages/util/paths/tests/paths.spec.ts index 97e91a556e..4ba4fab155 100644 --- a/packages/util/paths/tests/paths.spec.ts +++ b/packages/util/paths/tests/paths.spec.ts @@ -1,5 +1,5 @@ import { homedir } from 'node:os' -import { join } from 'node:path' +import { join, resolve } from 'node:path' import { describe, expect, it } from 'vitest' import { DEFAULT_DSH_HOME_DISPLAY, @@ -28,7 +28,7 @@ describe('dsh path helpers', () => { const envHome = join(homedir(), 'env-dsh') expect(resolveDshHome(undefined, { DSH_HOME: '~/env-dsh' })).toBe(envHome) - expect(resolveDshHome('/tmp/explicit-dsh', { DSH_HOME: '~/env-dsh' })).toBe('/tmp/explicit-dsh') + expect(resolveDshHome('/tmp/explicit-dsh', { DSH_HOME: '~/env-dsh' })).toBe(resolve('/tmp/explicit-dsh')) expect(resolveDshHome(undefined, {})).toBe(defaultDshHome()) }) })