Files
goodbuddy/src/main/agent/deepseek-harness-utility-launcher.test.ts
T
mesalogo 7c6f15d3c0 fix: allow HTTP for configurable service endpoints
DeepSeek Harness and Weixin binding configuration rejected non-loopback HTTP endpoints even when users explicitly selected those internal services. Configurable endpoints now accept valid HTTP and HTTPS URLs without host-location restrictions, while retaining the required protocol and API-key checks.

Release note: 模型连接、DeepSeek Harness 与微信绑定现在可直接使用局域网或公网 HTTP/HTTPS 服务地址,不再强制公网 HTTPS 或本机回环地址。
2026-08-30 16:49:47 +08:00

256 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { EventEmitter } from 'node:events'
import { mkdir, mkdtemp, realpath, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { PassThrough } from 'node:stream'
import { describe, expect, it, vi } from 'vitest'
import {
DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
DEEPSEEK_HARNESS_CONTROL_VERSION,
DEEPSEEK_HARNESS_CREDENTIAL_REF,
createDeepSeekHarnessUtilityLauncher,
parseHarnessControlMessage
} from './deepseek-harness-utility-launcher'
class FakeUtility extends EventEmitter {
readonly messages: unknown[] = []
readonly stderr = new PassThrough()
readonly pid = 123
killed = false
postMessage(message: unknown): void {
this.messages.push(message)
}
kill(): boolean {
this.killed = true
return true
}
}
async function fixture() {
const root = await realpath(
await mkdtemp(join(tmpdir(), 'goodbuddy-harness-launcher-'))
)
const workspace = join(root, 'workspace')
const dshHome = join(root, 'home')
const hostPath = join(
root,
'deepseek-harness-host-bootstrap.js'
)
await Promise.all([
mkdir(workspace),
mkdir(dshHome),
writeFile(hostPath, '', 'utf8')
])
return {
root,
dshHome,
hostPath,
launchOptions: {
cwd: workspace,
signal: new AbortController().signal,
baseUrl: 'http://gateway.example/openai/v1?api-version=1',
model: 'qwen-plus',
supportsImageInput: false,
credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF],
skillPackages: [],
extensionPackages: []
}
}
}
describe('DeepSeek Harness utility launcher', () => {
it('accepts only strict control messages and secret-free config', () => {
expect(
parseHarnessControlMessage({
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'ready',
failedExtensionIds: []
})
).toMatchObject({ type: 'ready' })
expect(
parseHarnessControlMessage({
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'ready',
failedExtensionIds: [],
apiKey: 'must-not-pass'
})
).toBeUndefined()
})
it('waits for Host readiness and sends no credential value', async () => {
const { dshHome, hostPath, launchOptions } = await fixture()
const utility = new FakeUtility()
const fork = vi.fn(() => utility as never)
const launcher = createDeepSeekHarnessUtilityLauncher({
bundledHostPath: hostPath,
dshHome,
environment: { PATH: 'C:\\Tools' },
fork
})
const launching = launcher(launchOptions)
await vi.waitFor(() =>
expect(utility.messages).toHaveLength(1)
)
expect(JSON.stringify(utility.messages[0])).not.toContain(
'secret'
)
expect(utility.messages[0]).toMatchObject({
type: 'start',
config: {
baseUrl: 'http://gateway.example/openai/v1?api-version=1',
model: 'qwen-plus',
supportsImageInput: false,
credentialRefs: [DEEPSEEK_HARNESS_CREDENTIAL_REF]
}
})
utility.emit('message', {
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'ready',
failedExtensionIds: []
})
await expect(launching).resolves.toMatchObject({
stdin: expect.any(WritableStream),
stdout: expect.any(ReadableStream)
})
expect(fork).toHaveBeenCalledWith(
hostPath,
[],
expect.objectContaining({
cwd: launchOptions.cwd,
stdio: ['ignore', 'ignore', 'pipe']
})
)
})
it('persists extension startup failures before exposing the child', async () => {
const { root, dshHome, hostPath, launchOptions } =
await fixture()
const entrypoint = join(root, 'greet.mjs')
await writeFile(entrypoint, 'export function apply() {}\n', 'utf8')
const utility = new FakeUtility()
const onExtensionStartupFailures = vi.fn(async () => undefined)
const launcher = createDeepSeekHarnessUtilityLauncher({
bundledHostPath: hostPath,
dshHome,
environment: {},
fork: () => utility as never,
onExtensionStartupFailures
})
const launching = launcher({
...launchOptions,
extensionPackages: [
{
id: 'greet',
entrypoint,
configuration: {}
}
]
})
await vi.waitFor(() =>
expect(utility.messages).toHaveLength(1)
)
utility.emit('message', {
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'ready',
failedExtensionIds: ['greet']
})
await expect(launching).resolves.toBeDefined()
expect(onExtensionStartupFailures).toHaveBeenCalledWith([
'greet'
])
})
it('fails when the Host exits while startup failures are being persisted', async () => {
const { dshHome, hostPath, launchOptions } = await fixture()
const utility = new FakeUtility()
let finishPersistence!: () => void
const persistence = new Promise<void>((resolve) => {
finishPersistence = resolve
})
const onExtensionStartupFailures = vi.fn(() => persistence)
const terminateProcess = vi.fn()
const launcher = createDeepSeekHarnessUtilityLauncher({
bundledHostPath: hostPath,
dshHome,
environment: {},
fork: () => utility as never,
terminateProcess,
onExtensionStartupFailures
})
const launching = launcher(launchOptions)
await vi.waitFor(() =>
expect(utility.messages).toHaveLength(1)
)
utility.emit('message', {
protocol: DEEPSEEK_HARNESS_CONTROL_PROTOCOL,
version: DEEPSEEK_HARNESS_CONTROL_VERSION,
type: 'ready',
failedExtensionIds: ['greet']
})
await vi.waitFor(() =>
expect(onExtensionStartupFailures).toHaveBeenCalledOnce()
)
utility.emit('exit', 9)
await expect(launching).rejects.toThrow(
'Host 启动前退出(code 9)'
)
expect(terminateProcess).toHaveBeenCalledOnce()
finishPersistence()
})
it('fails closed on an invalid Host startup message', async () => {
const { dshHome, hostPath, launchOptions } = await fixture()
const utility = new FakeUtility()
const terminateProcess = vi.fn(() => {
utility.killed = true
})
const launcher = createDeepSeekHarnessUtilityLauncher({
bundledHostPath: hostPath,
dshHome,
environment: {},
fork: () => utility as never,
terminateProcess
})
const launching = launcher(launchOptions)
await vi.waitFor(() =>
expect(utility.messages).toHaveLength(1)
)
utility.emit('message', { type: 'ready' })
await expect(launching).rejects.toThrow('启动协议无效')
expect(terminateProcess).toHaveBeenCalledOnce()
})
it.each([
'file:///private/config',
'ftp://gateway.example/v1'
])('rejects unsupported endpoint %s before forking', async (baseUrl) => {
const { dshHome, hostPath, launchOptions } = await fixture()
const fork = vi.fn()
const launcher = createDeepSeekHarnessUtilityLauncher({
bundledHostPath: hostPath,
dshHome,
environment: {},
fork
})
await expect(
launcher({ ...launchOptions, baseUrl })
).rejects.toThrow('HTTP 或 HTTPS')
expect(fork).not.toHaveBeenCalled()
})
})