refactor: apply repository naming contract
Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
This commit is contained in:
160
packages/test-support/loader-smoke/tests/agent-turn.spec.ts
Normal file
160
packages/test-support/loader-smoke/tests/agent-turn.spec.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { runFixtureTurn } from '../src/agent-turn.ts'
|
||||
|
||||
type Listener = (session: unknown, event: SessionEvent) => void
|
||||
|
||||
const event = (value: object): SessionEvent => value as unknown as SessionEvent
|
||||
|
||||
function turnHarness(): {
|
||||
readonly ctx: Context
|
||||
readonly session: { readonly id: string }
|
||||
readonly foreignSession: object
|
||||
readonly emit: (session: unknown, value: object) => void
|
||||
readonly setFollowup: (callback: (message: { readonly id: unknown }) => void) => void
|
||||
readonly whenIdle: ReturnType<typeof vi.fn>
|
||||
readonly disposeListener: ReturnType<typeof vi.fn>
|
||||
readonly flush: ReturnType<typeof vi.fn>
|
||||
} {
|
||||
const session = { id: 'fixture-session' }
|
||||
const foreignSession = {}
|
||||
let listener: Listener | undefined
|
||||
let followup = (_message: { readonly id: unknown }): void => {}
|
||||
const whenIdle = vi.fn(async () => {})
|
||||
const disposeListener = vi.fn()
|
||||
const flush = vi.fn(async () => {})
|
||||
const agent = {
|
||||
session,
|
||||
whenIdle,
|
||||
followup: vi.fn((message: { readonly id: unknown }) => { followup(message) }),
|
||||
}
|
||||
const ctx = {
|
||||
get: (name: string) => name === 'agents' ? { roots: () => [agent] } : undefined,
|
||||
on: (_name: string, callback: Listener) => {
|
||||
listener = callback
|
||||
return disposeListener
|
||||
},
|
||||
sessions: { flush },
|
||||
} as unknown as Context
|
||||
return {
|
||||
ctx,
|
||||
session,
|
||||
foreignSession,
|
||||
emit: (target, value) => { listener?.(target, event(value)) },
|
||||
setFollowup: (callback) => { followup = callback },
|
||||
whenIdle,
|
||||
disposeListener,
|
||||
flush,
|
||||
}
|
||||
}
|
||||
|
||||
describe('runFixtureTurn', () => {
|
||||
it.each([
|
||||
['no agent registry', undefined, 0],
|
||||
['multiple roots', { roots: () => [{}, {}] }, 2],
|
||||
])('rejects %s', async (_label, registry, count) => {
|
||||
const ctx = { get: () => registry } as unknown as Context
|
||||
await expect(runFixtureTurn(ctx, { task: 'ignored' }))
|
||||
.rejects.toThrow(`fixture turn requires exactly one top-level agent, found ${count}`)
|
||||
})
|
||||
|
||||
it('observes only the owned interval and returns its final text and deduplicated usage', async () => {
|
||||
const harness = turnHarness()
|
||||
const observed: SessionEvent[] = []
|
||||
harness.setFollowup((message) => {
|
||||
harness.emit(harness.foreignSession, {
|
||||
type: 'assistant/message', seq: 0, time: 0, data: { message: { content: [] } },
|
||||
})
|
||||
harness.emit(harness.session, {
|
||||
type: 'step/start', seq: 0, time: 0, data: { turn: 1, step: 1 },
|
||||
})
|
||||
harness.emit(harness.session, {
|
||||
type: 'agent/inbox/spliced', seq: 1, time: 1, data: { inserted: [{ id: 'other' }] },
|
||||
})
|
||||
harness.emit(harness.session, {
|
||||
type: 'agent/inbox/spliced', seq: 2, time: 2, data: { inserted: [message] },
|
||||
})
|
||||
harness.emit(harness.session, {
|
||||
type: 'assistant/chunk', seq: 3, time: 3,
|
||||
data: { turn: 1, step: 1, chunk: { type: 'text-delta', text: 'partial' } },
|
||||
})
|
||||
harness.emit(harness.session, {
|
||||
type: 'assistant/chunk', seq: 4, time: 4,
|
||||
data: {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
chunk: { type: 'usage', usage: { inputTokens: 2, outputTokens: 3, reasoningTokens: 1 } },
|
||||
},
|
||||
})
|
||||
harness.emit(harness.session, {
|
||||
type: 'assistant/message', seq: 5, time: 5,
|
||||
data: {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
message: { content: [{ type: 'text', text: 'final answer' }] },
|
||||
usage: { inputTokens: 4, outputTokens: 5, cacheReadTokens: 6 },
|
||||
},
|
||||
})
|
||||
harness.emit(harness.session, {
|
||||
type: 'assistant/chunk', seq: 6, time: 6,
|
||||
data: {
|
||||
turn: 1,
|
||||
step: 2,
|
||||
chunk: { type: 'usage', usage: { inputTokens: 1, outputTokens: 2, cacheWriteTokens: 7, reasoningTokens: 2 } },
|
||||
},
|
||||
})
|
||||
harness.emit(harness.session, {
|
||||
type: 'assistant/message', seq: 7, time: 7,
|
||||
data: { turn: 1, step: 2, message: { content: [{ type: 'tool-call' }] } },
|
||||
})
|
||||
harness.emit(harness.foreignSession, {
|
||||
type: 'assistant/message', seq: 8, time: 8, data: { message: { content: [] } },
|
||||
})
|
||||
})
|
||||
|
||||
await expect(runFixtureTurn(harness.ctx, {
|
||||
task: 'prove the fixture',
|
||||
onEvent: (_sessionId, current) => { observed.push(current) },
|
||||
})).resolves.toEqual({
|
||||
type: 'result',
|
||||
sessionId: 'fixture-session',
|
||||
output: 'final answer',
|
||||
usage: {
|
||||
inputTokens: 5,
|
||||
outputTokens: 7,
|
||||
cacheReadTokens: 6,
|
||||
cacheWriteTokens: 7,
|
||||
reasoningTokens: 2,
|
||||
},
|
||||
})
|
||||
expect(observed.map(current => current.seq)).toEqual([2, 3, 4, 5, 6, 7])
|
||||
expect(harness.whenIdle).toHaveBeenCalledTimes(2)
|
||||
expect(harness.flush).toHaveBeenCalledWith(harness.session)
|
||||
expect(harness.disposeListener).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('omits usage when the interval records none', async () => {
|
||||
const harness = turnHarness()
|
||||
harness.setFollowup((message) => {
|
||||
harness.emit(harness.session, {
|
||||
type: 'agent/inbox/spliced', seq: 0, time: 0, data: { inserted: [message] },
|
||||
})
|
||||
})
|
||||
|
||||
await expect(runFixtureTurn(harness.ctx, { task: 'no model step' })).resolves.toEqual({
|
||||
type: 'result',
|
||||
sessionId: 'fixture-session',
|
||||
output: '',
|
||||
})
|
||||
})
|
||||
|
||||
it('always removes its listener when the turn fails', async () => {
|
||||
const harness = turnHarness()
|
||||
harness.whenIdle.mockResolvedValueOnce(undefined).mockRejectedValueOnce(new Error('turn failed'))
|
||||
|
||||
await expect(runFixtureTurn(harness.ctx, { task: 'fail' })).rejects.toThrow('turn failed')
|
||||
expect(harness.disposeListener).toHaveBeenCalledOnce()
|
||||
expect(harness.flush).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
105
packages/test-support/loader-smoke/tests/example-launch.spec.ts
Normal file
105
packages/test-support/loader-smoke/tests/example-launch.spec.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
EXAMPLE_MODE_ENV,
|
||||
resolveExampleLaunch,
|
||||
resolveExampleMode,
|
||||
} from '@deepseek-ai/dsh-loader-smoke'
|
||||
|
||||
const SRC_BIN = '/repo/packages/examples/acp-demo/src/bin.ts'
|
||||
const TSCONFIG = '/repo/tsconfig.json'
|
||||
|
||||
const originalMode = process.env[EXAMPLE_MODE_ENV]
|
||||
afterEach(() => {
|
||||
if (originalMode === undefined) Reflect.deleteProperty(process.env, EXAMPLE_MODE_ENV)
|
||||
else process.env[EXAMPLE_MODE_ENV] = originalMode
|
||||
})
|
||||
|
||||
describe('resolveExampleMode', () => {
|
||||
it('defaults absent/empty/src to src', () => {
|
||||
Reflect.deleteProperty(process.env, EXAMPLE_MODE_ENV)
|
||||
expect(resolveExampleMode()).toBe('src')
|
||||
expect(resolveExampleMode('')).toBe('src')
|
||||
expect(resolveExampleMode('src')).toBe('src')
|
||||
})
|
||||
|
||||
it('accepts lib', () => {
|
||||
expect(resolveExampleMode('lib')).toBe('lib')
|
||||
})
|
||||
|
||||
it('throws on any other value', () => {
|
||||
expect(() => resolveExampleMode('prod')).toThrow(/must be 'src' or 'lib'/)
|
||||
})
|
||||
|
||||
it('reads the environment when no argument is given', () => {
|
||||
process.env[EXAMPLE_MODE_ENV] = 'lib'
|
||||
expect(resolveExampleMode()).toBe('lib')
|
||||
Reflect.deleteProperty(process.env, EXAMPLE_MODE_ENV)
|
||||
expect(resolveExampleMode()).toBe('src')
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveExampleLaunch', () => {
|
||||
it('src mode: --import tsx on the source bin with the tsconfig paths env', () => {
|
||||
const { command, args, env } = resolveExampleLaunch({
|
||||
srcBin: SRC_BIN,
|
||||
configArgs: ['./cordis.yml'],
|
||||
mode: 'src',
|
||||
tsconfigPath: TSCONFIG,
|
||||
})
|
||||
expect(command).toBe(process.execPath)
|
||||
expect(args).toContain('--import')
|
||||
expect(args).toContain(SRC_BIN)
|
||||
expect(args[args.length - 1]).toBe('./cordis.yml')
|
||||
expect(env.TSX_TSCONFIG_PATH).toBe(TSCONFIG)
|
||||
})
|
||||
|
||||
it('src mode: throws without a tsconfig path', () => {
|
||||
expect(() => resolveExampleLaunch({ srcBin: SRC_BIN, mode: 'src' })).toThrow(/needs tsconfigPath/)
|
||||
})
|
||||
|
||||
it('lib mode: plain node on the derived lib bin, no tsx and no paths env', () => {
|
||||
const { args, env } = resolveExampleLaunch({
|
||||
srcBin: SRC_BIN,
|
||||
configArgs: ['--config', './cordis.yml'],
|
||||
mode: 'lib',
|
||||
env: { DSH_HOME: '/tmp/home' },
|
||||
})
|
||||
expect(args).not.toContain('--import')
|
||||
expect(args).toContain('/repo/packages/examples/acp-demo/lib/bin.js')
|
||||
expect(args.slice(-2)).toEqual(['--config', './cordis.yml'])
|
||||
expect(env.TSX_TSCONFIG_PATH).toBeUndefined()
|
||||
expect(env.DSH_HOME).toBe('/tmp/home')
|
||||
})
|
||||
|
||||
it('lib mode: uses an explicit plain-Node bin when provided', () => {
|
||||
const fixture = '/repo/fixture.ts'
|
||||
const { args } = resolveExampleLaunch({ srcBin: fixture, libBin: fixture, mode: 'lib' })
|
||||
expect(args).toContain(fixture)
|
||||
})
|
||||
|
||||
it('lib mode: rewrites only the last /src/ segment', () => {
|
||||
const { args } = resolveExampleLaunch({
|
||||
srcBin: '/repo/src/packages/examples/acp-demo/src/bin.ts',
|
||||
mode: 'lib',
|
||||
})
|
||||
expect(args).toContain('/repo/src/packages/examples/acp-demo/lib/bin.js')
|
||||
})
|
||||
|
||||
it('lib mode: derives the built bin from a Windows source path', () => {
|
||||
const { args } = resolveExampleLaunch({
|
||||
srcBin: String.raw`D:\repo\src\packages\examples\acp-demo\src\bin.ts`,
|
||||
mode: 'lib',
|
||||
})
|
||||
expect(args).toContain(String.raw`D:\repo\src\packages\examples\acp-demo\lib\bin.js`)
|
||||
})
|
||||
|
||||
it('lib mode: throws when the bin has no /src/ segment', () => {
|
||||
expect(() => resolveExampleLaunch({ srcBin: '/repo/lib/bin.js', mode: 'lib' })).toThrow(/"\/src\/" segment/)
|
||||
})
|
||||
|
||||
it('defaults the mode from the environment', () => {
|
||||
process.env[EXAMPLE_MODE_ENV] = 'lib'
|
||||
const { args } = resolveExampleLaunch({ srcBin: SRC_BIN })
|
||||
expect(args).toContain('/repo/packages/examples/acp-demo/lib/bin.js')
|
||||
})
|
||||
})
|
||||
4
packages/test-support/loader-smoke/tests/fixtures/fail.ts
vendored
Normal file
4
packages/test-support/loader-smoke/tests/fixtures/fail.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/** Non-zero subprocess fixture for the Loader-smoke harness. */
|
||||
|
||||
console.error('fixture failed')
|
||||
process.exitCode = 7
|
||||
4
packages/test-support/loader-smoke/tests/fixtures/hang.ts
vendored
Normal file
4
packages/test-support/loader-smoke/tests/fixtures/hang.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/** Deadline subprocess fixture for the Loader-smoke harness. */
|
||||
|
||||
console.log('fixture hanging')
|
||||
setInterval(() => {}, 1_000)
|
||||
17
packages/test-support/loader-smoke/tests/fixtures/success.ts
vendored
Normal file
17
packages/test-support/loader-smoke/tests/fixtures/success.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/** Successful subprocess fixture for the Loader-smoke harness. */
|
||||
|
||||
let input = ''
|
||||
process.stdin.setEncoding('utf8')
|
||||
process.stdin.on('data', (chunk: string) => { input += chunk })
|
||||
process.stdin.on('end', () => {
|
||||
console.log(JSON.stringify({
|
||||
configPath: process.argv[2],
|
||||
args: process.argv.slice(2),
|
||||
cwd: process.cwd(),
|
||||
dshHome: process.env.DSH_HOME,
|
||||
agentsHome: process.env.DSH_AGENTS_HOME,
|
||||
marker: process.env.LOADER_SMOKE_MARKER,
|
||||
input,
|
||||
}))
|
||||
console.error('fixture stderr')
|
||||
})
|
||||
117
packages/test-support/loader-smoke/tests/loader-smoke.spec.ts
Normal file
117
packages/test-support/loader-smoke/tests/loader-smoke.spec.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { existsSync } from 'node:fs'
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
|
||||
|
||||
const configPath = '/tmp/fixture.cordis.yml'
|
||||
const tsconfigPath = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url))
|
||||
const fixture = (name: string): string => fileURLToPath(new URL(`./fixtures/${name}.ts`, import.meta.url))
|
||||
// macOS realpaths temp dirs into /private; TMPDIR may live under /var or /tmp.
|
||||
const canonicalTempPath = (path: string): string => path.replace(/^\/private(?=\/(?:var|tmp)\/)/, '')
|
||||
|
||||
describe('runLoaderSmoke', () => {
|
||||
it('isolates the process, closes stdin, captures output, and removes the cwd', async () => {
|
||||
const result = await runLoaderSmoke({
|
||||
label: 'success fixture',
|
||||
tempDirPrefix: 'loader-smoke-success-',
|
||||
binScript: fixture('success'),
|
||||
configPath,
|
||||
tsconfigPath,
|
||||
mode: 'src',
|
||||
env: { LOADER_SMOKE_MARKER: 'present' },
|
||||
})
|
||||
const output = JSON.parse(result.stdout) as {
|
||||
configPath: string
|
||||
args: string[]
|
||||
cwd: string
|
||||
dshHome: string
|
||||
agentsHome: string
|
||||
marker: string
|
||||
input: string
|
||||
}
|
||||
expect(output).toMatchObject({
|
||||
configPath,
|
||||
args: [configPath],
|
||||
marker: 'present',
|
||||
input: '',
|
||||
})
|
||||
expect(canonicalTempPath(output.dshHome)).toBe(canonicalTempPath(join(output.cwd, '.dsh')))
|
||||
expect(canonicalTempPath(output.agentsHome)).toBe(canonicalTempPath(join(output.cwd, '.agents')))
|
||||
expect(result.stderr).toContain('fixture stderr')
|
||||
expect(existsSync(output.cwd)).toBe(false)
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('passes an arbitrary bin argv and inspects world state before cleanup', async () => {
|
||||
let inspected = ''
|
||||
let marker = ''
|
||||
const result = await runLoaderSmoke({
|
||||
label: 'argv fixture',
|
||||
tempDirPrefix: 'loader-smoke-argv-',
|
||||
binScript: fixture('success'),
|
||||
libBinScript: fixture('success'),
|
||||
configPath,
|
||||
binArgs: ['--config', configPath, '--output-format', 'json', 'task with spaces'],
|
||||
tsconfigPath,
|
||||
prepare: cwd => writeFile(join(cwd, 'marker.txt'), 'prepared'),
|
||||
inspect: async (cwd) => {
|
||||
inspected = cwd
|
||||
marker = await readFile(join(cwd, 'marker.txt'), 'utf8')
|
||||
},
|
||||
})
|
||||
const output = JSON.parse(result.stdout) as { args: string[]; cwd: string }
|
||||
expect(output.args).toEqual(['--config', configPath, '--output-format', 'json', 'task with spaces'])
|
||||
expect(canonicalTempPath(inspected)).toBe(canonicalTempPath(output.cwd))
|
||||
expect(marker).toBe('prepared')
|
||||
expect(existsSync(inspected)).toBe(false)
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('rejects a non-zero exit with captured diagnostics', async () => {
|
||||
await expect(runLoaderSmoke({
|
||||
label: 'failure fixture',
|
||||
tempDirPrefix: 'loader-smoke-fail-',
|
||||
binScript: fixture('fail'),
|
||||
libBinScript: fixture('fail'),
|
||||
configPath,
|
||||
tsconfigPath,
|
||||
})).rejects.toThrow('failure fixture exited 7 (expected 0). stdout:\n\nstderr:\nfixture failed')
|
||||
})
|
||||
|
||||
it('accepts a declared expected failure exit and rejects any other outcome', async () => {
|
||||
// A scenario pinning a designed failure surface declares its exit code…
|
||||
const declared = await runLoaderSmoke({
|
||||
label: 'declared failure fixture',
|
||||
tempDirPrefix: 'loader-smoke-declared-fail-',
|
||||
binScript: fixture('fail'),
|
||||
libBinScript: fixture('fail'),
|
||||
configPath,
|
||||
tsconfigPath,
|
||||
expectedExitCode: 7,
|
||||
})
|
||||
expect(declared.stderr).toBe('fixture failed\n')
|
||||
|
||||
// …and a run that succeeds instead still fails the smoke.
|
||||
await expect(runLoaderSmoke({
|
||||
label: 'unexpectedly clean fixture',
|
||||
tempDirPrefix: 'loader-smoke-clean-',
|
||||
binScript: fixture('success'),
|
||||
libBinScript: fixture('success'),
|
||||
configPath,
|
||||
tsconfigPath,
|
||||
expectedExitCode: 7,
|
||||
})).rejects.toThrow(/exited 0 \(expected 7\)/)
|
||||
})
|
||||
|
||||
it('kills a process at its deadline and reports captured output', async () => {
|
||||
await expect(runLoaderSmoke({
|
||||
label: 'hanging fixture',
|
||||
tempDirPrefix: 'loader-smoke-hang-',
|
||||
binScript: fixture('hang'),
|
||||
libBinScript: fixture('hang'),
|
||||
configPath,
|
||||
tsconfigPath,
|
||||
processTimeoutMs: 100,
|
||||
})).rejects.toThrow('hanging fixture did not exit within 0.1s.')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user