Files
deepseek-harness/apps/cli/tests/profiles/web/tests/creator-plugin-manager.expected.e2e.ts
T

106 lines
5.2 KiB
TypeScript

/** Built Web profile: MCP reaches existing/new Creator sessions and survives a process restart. */
import { spawn } from 'node:child_process'
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { expect, it } from 'vitest'
import { readProfileManifest } from '@deepseek-ai/dsh-app-boot'
import { startHttpMcpFixture } from '../../../../../../packages/mcp/mcp-client/tests/http-fixture.ts'
interface Observation {
approvals: string[]
permission: string
before: string[]
denied: { isError: boolean; content: unknown }
afterDenied: string[]
bundlesAfterDenied: { name: string }[]
after: string[]
other: string[]
remaining: string[]
result: unknown
ping: unknown
removed?: unknown
}
const repo = fileURLToPath(new URL('../../../../../../', import.meta.url))
it('configures MCP on a live profile, restores it on restart, and removes its tools', async (test) => {
const root = await mkdtemp(join(tmpdir(), 'creator-manager-'))
test.onTestFinished(() => rm(root, { recursive: true, force: true }))
const mcp = await startHttpMcpFixture()
test.onTestFinished(mcp.close)
await mkdir(join(root, 'workspace'))
const bundle = join(root, 'demo-mcp')
await mkdir(bundle)
await writeFile(join(bundle, 'package.json'), JSON.stringify({ name: '@test/creator-mcp', version: '1.0.0',
dsh: { bundle: { patch: './cordis.patch.yml' } } }))
await writeFile(join(bundle, 'cordis.patch.yml'), JSON.stringify([{ insert: [{ id: 'demo',
name: '@deepseek-ai/dsh-mcp-client', config: { serverName: 'demo', transport: 'streamable-http',
url: mcp.url, failOnStartupError: true },
}] }]))
const patch = join(root, 'test.patch.yml')
await writeFile(patch, JSON.stringify([{ insert: [{ id: 'creator-manager-observer',
name: new URL('./fixtures/creator-plugin-manager.mjs', import.meta.url).href, config: { bundle },
}] }]))
const start = async () => {
const child = spawn(process.execPath, [join(repo, 'apps/cli/lib/bin.js'), '--profile', 'web', '--patch', patch,
'--port', '0', '--no-open'], { cwd: join(root, 'workspace'),
env: { ...process.env, DSH_HOME: join(root, 'home'), DSH_AGENTS_HOME: join(root, 'agents'),
DSH_TELEMETRY_DISABLED: '1', DEEPSEEK_API_KEY: 'keyless-no-model-calls' },
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
})
let output = ''
const completion = new Promise<void>((resolve, reject) => {
child.once('close', () => { resolve() })
child.once('error', reject)
})
const stop = async () => { if (child.exitCode === null) child.kill('SIGTERM'); await completion }
test.onTestFinished(stop)
for (const stream of [child.stdout, child.stderr]) stream!.on('data', (data) => { output = (output + String(data)).slice(-30_000) })
await expect.poll(() => {
if (child.exitCode !== null) throw new Error(output)
return output.includes('dsh web: http://')
}, { timeout: 60_000 }).toBe(true)
return { stop, request: (phase: string): Promise<Observation> => new Promise((resolve, reject) => {
child.once('message', (value: { result: Observation; error?: string }) => {
if (value.error !== undefined) reject(new Error(value.error))
else resolve(value.result)
})
child.send(phase)
}) }
}
const first = await start()
const initial = await first.request('initial')
expect(initial.before).toContain('plugin_manager')
for (const retired of ['cordis_define', 'cordis_run', 'cordis_stop', 'cordis_undefine', 'cordis_inspect_self']) {
expect(initial.before).not.toContain(retired)
}
expect(initial.before).not.toContain('mcp__demo__ping')
expect(initial.denied.isError).toBe(true)
expect(JSON.stringify(initial.denied.content)).toContain('the user rejected escalating')
expect(initial.afterDenied).toEqual(initial.before)
expect(initial.bundlesAfterDenied.map(bundle => bundle.name)).not.toContain('@test/creator-mcp')
expect(initial.approvals).toEqual(['rejected', 'allowed-once'])
expect(initial.permission).toBe('workspace-write')
expect(initial.result).toMatchObject({ application: 'applied', changed: true })
expect(initial.after).toContain('mcp__demo__ping')
expect(initial.other).toContain('mcp__demo__ping')
expect(JSON.stringify(initial.ping)).toContain('pong')
const saved = readProfileManifest('dsh', join(root, 'home/profiles/web'))
expect(saved.dsh?.profile?.bundles).toContain('@test/creator-mcp')
expect(saved.dependencies).toHaveProperty('@test/creator-mcp')
await first.stop()
const second = await start()
const restarted = await second.request('restart')
expect(restarted.result).toEqual(expect.arrayContaining([expect.objectContaining({ name: '@test/creator-mcp' })]))
expect(restarted.approvals).toEqual(['rejected', 'allowed-once'])
expect(restarted.permission).toBe('workspace-write')
expect(restarted.before).toContain('mcp__demo__ping')
expect(JSON.stringify(restarted.ping)).toContain('pong')
expect(restarted.removed).toMatchObject({ application: 'applied', changed: true })
expect(restarted.remaining).not.toContain('mcp__demo__ping')
expect(mcp.calls).toEqual(['ping', 'ping'])
await second.stop()
})