test(cli): cover Harness-home credential loading in built entry
Source-level environment and credential tests prove the individual loaders, but they do not prove that the published launcher runs them before Loader evaluates a shipped profile. Start the built dsh binary with the shipped base bundle and a test-only LLM probe. Put the endpoint in $DSH_HOME/.env, put the bearer token only in $DSH_HOME/.credentials.yaml, remove inherited DeepSeek overrides, and assert the mock request received both without leaking the token. This covers launch order, profile composition, the adapter, and the credential seam without a real API.
This commit is contained in:
@@ -39,6 +39,7 @@
|
|||||||
"@deepseek-ai/dsh-frontend-static": "workspace:^",
|
"@deepseek-ai/dsh-frontend-static": "workspace:^",
|
||||||
"@deepseek-ai/dsh-host-apiproxy": "workspace:^",
|
"@deepseek-ai/dsh-host-apiproxy": "workspace:^",
|
||||||
"@deepseek-ai/dsh-host-webserver": "workspace:^",
|
"@deepseek-ai/dsh-host-webserver": "workspace:^",
|
||||||
|
"@deepseek-ai/dsh-llm-mock-server": "workspace:^",
|
||||||
"@deepseek-ai/dsh-loader-smoke": "workspace:^",
|
"@deepseek-ai/dsh-loader-smoke": "workspace:^",
|
||||||
"@deepseek-ai/dsh-system-prompt": "workspace:^",
|
"@deepseek-ai/dsh-system-prompt": "workspace:^",
|
||||||
"@deepseek-ai/dsh-tools": "workspace:^",
|
"@deepseek-ai/dsh-tools": "workspace:^",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync
|
|||||||
import { tmpdir } from 'node:os'
|
import { tmpdir } from 'node:os'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||||
|
import { startMockLlmServer } from '@deepseek-ai/dsh-llm-mock-server'
|
||||||
import { execa } from 'execa'
|
import { execa } from 'execa'
|
||||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
@@ -13,14 +14,21 @@ const invalidProvider = fileURLToPath(new URL('./fixtures/invalid-provider.cordi
|
|||||||
|
|
||||||
async function runBuiltBin(
|
async function runBuiltBin(
|
||||||
args: readonly string[] = [],
|
args: readonly string[] = [],
|
||||||
env: Record<string, string> = {},
|
env: Readonly<Record<string, string | undefined>> = {},
|
||||||
|
cwd?: string,
|
||||||
): Promise<{ stdout: string; code: number; stderr: string }> {
|
): Promise<{ stdout: string; code: number; stderr: string }> {
|
||||||
|
const childEnv = Object.fromEntries(
|
||||||
|
Object.entries({ ...process.env, ...env })
|
||||||
|
.filter((entry): entry is [string, string] => entry[1] !== undefined),
|
||||||
|
)
|
||||||
const result = await execa(process.execPath, [dshBin, ...args], {
|
const result = await execa(process.execPath, [dshBin, ...args], {
|
||||||
input: '',
|
input: '',
|
||||||
timeout: 25_000,
|
timeout: 25_000,
|
||||||
killSignal: 'SIGKILL',
|
killSignal: 'SIGKILL',
|
||||||
reject: false,
|
reject: false,
|
||||||
env,
|
env: childEnv,
|
||||||
|
extendEnv: false,
|
||||||
|
...cwd === undefined ? {} : { cwd },
|
||||||
})
|
})
|
||||||
if (result.timedOut) {
|
if (result.timedOut) {
|
||||||
throw new Error(`dsh built bin did not exit within 25s. stdout:\n${result.stdout}\nstderr:\n${result.stderr}`)
|
throw new Error(`dsh built bin did not exit within 25s. stdout:\n${result.stdout}\nstderr:\n${result.stderr}`)
|
||||||
@@ -127,6 +135,44 @@ function startProfileLifecycle(fixture: ProfileLifecycleFixture) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createEnvironmentProbeProfile(home: string, project: string): void {
|
||||||
|
const pluginFile = join(project, 'environment-probe.mjs')
|
||||||
|
writeFileSync(pluginFile, [
|
||||||
|
"export const name = 'environment-probe'",
|
||||||
|
"export const inject = ['llm']",
|
||||||
|
'export function apply(ctx) {',
|
||||||
|
' void ctx.loader.await().then(async () => {',
|
||||||
|
" let text = ''",
|
||||||
|
' for await (const chunk of ctx.llm.stream({',
|
||||||
|
" provider: 'deepseek-official',",
|
||||||
|
" model: 'deepseek-v4-flash',",
|
||||||
|
' messages: [],',
|
||||||
|
' maxTokens: 32,',
|
||||||
|
' })) {',
|
||||||
|
" if (chunk.type === 'text-delta') text += chunk.text",
|
||||||
|
' }',
|
||||||
|
' process.stdout.write(`${text}\\n`)',
|
||||||
|
" process.kill(process.pid, 'SIGTERM')",
|
||||||
|
' })',
|
||||||
|
'}',
|
||||||
|
'',
|
||||||
|
].join('\n'))
|
||||||
|
const profileDir = join(home, 'profiles', 'environment-probe')
|
||||||
|
mkdirSync(profileDir, { recursive: true })
|
||||||
|
writeFileSync(join(profileDir, 'package.json'), JSON.stringify({
|
||||||
|
name: 'dsh-profile-environment-probe',
|
||||||
|
private: true,
|
||||||
|
dependencies: {},
|
||||||
|
dsh: { profile: { bundles: ['@deepseek-ai/dsh-base'] } },
|
||||||
|
}, undefined, 2))
|
||||||
|
writeFileSync(join(profileDir, 'cordis.patch.yml'), [
|
||||||
|
'- insert:',
|
||||||
|
' - id: environment-probe',
|
||||||
|
` name: ${pathToFileURL(pluginFile).href}`,
|
||||||
|
'',
|
||||||
|
].join('\n'))
|
||||||
|
}
|
||||||
|
|
||||||
describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)', () => {
|
describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)', () => {
|
||||||
it('requires --profile and rejects removed commands', async () => {
|
it('requires --profile and rejects removed commands', async () => {
|
||||||
const bare = await runBuiltBin()
|
const bare = await runBuiltBin()
|
||||||
@@ -156,6 +202,47 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
|
|||||||
}
|
}
|
||||||
}, 30_000)
|
}, 30_000)
|
||||||
|
|
||||||
|
it('uses the Harness-home environment and managed credential through the published entry', async () => {
|
||||||
|
const apiKey = 'built-home-layer-key'
|
||||||
|
const server = await startMockLlmServer({
|
||||||
|
sequence: ['success'],
|
||||||
|
apiKey,
|
||||||
|
successText: 'home environment reached the mock',
|
||||||
|
})
|
||||||
|
const home = mkdtempSync(join(tmpdir(), 'dsh-home-environment-'))
|
||||||
|
const project = mkdtempSync(join(tmpdir(), 'dsh-home-project-'))
|
||||||
|
writeFileSync(join(home, '.env'), `DEEPSEEK_BASE_URL=${server.baseURL}\n`)
|
||||||
|
writeFileSync(join(home, '.credentials.yaml'), `DEEPSEEK_API_KEY: ${apiKey}\n`, { mode: 0o600 })
|
||||||
|
createEnvironmentProbeProfile(home, project)
|
||||||
|
try {
|
||||||
|
const result = await runBuiltBin(
|
||||||
|
['--profile', 'environment-probe'],
|
||||||
|
{
|
||||||
|
DSH_HOME: home,
|
||||||
|
DSH_TELEMETRY_DISABLED: '1',
|
||||||
|
DEEPSEEK_API_KEY: undefined,
|
||||||
|
DEEPSEEK_BASE_URL: undefined,
|
||||||
|
},
|
||||||
|
project,
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
result.code,
|
||||||
|
`${result.stderr}\nstdout:\n${result.stdout}\nmock requests: ${String(server.requests.length)}`,
|
||||||
|
).toBe(0)
|
||||||
|
expect(result.stdout).toBe('home environment reached the mock')
|
||||||
|
expect(result.stdout).not.toContain(apiKey)
|
||||||
|
expect(result.stderr).not.toContain(apiKey)
|
||||||
|
expect(server.requests).toHaveLength(1)
|
||||||
|
expect(server.requests[0]?.path).toBe('/chat/completions')
|
||||||
|
expect(server.requests[0]?.headers.authorization).toBe(`Bearer ${apiKey}`)
|
||||||
|
expect(JSON.stringify(server.requests[0]?.body)).not.toContain(apiKey)
|
||||||
|
} finally {
|
||||||
|
await server.close()
|
||||||
|
rmSync(home, { recursive: true, force: true })
|
||||||
|
rmSync(project, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
}, 30_000)
|
||||||
|
|
||||||
it('reports a patch-overlay boot failure without hanging', async () => {
|
it('reports a patch-overlay boot failure without hanging', async () => {
|
||||||
// The HMR main watcher's initial scan once refreshed the include
|
// The HMR main watcher's initial scan once refreshed the include
|
||||||
// mid-initial-apply, deadlocking the failing apply's rollback against the
|
// mid-initial-apply, deadlocking the failing apply's rollback against the
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -201,6 +201,9 @@ importers:
|
|||||||
'@deepseek-ai/dsh-host-webserver':
|
'@deepseek-ai/dsh-host-webserver':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../../packages/host/webserver
|
version: link:../../packages/host/webserver
|
||||||
|
'@deepseek-ai/dsh-llm-mock-server':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../../packages/support/llm-mock-server
|
||||||
'@deepseek-ai/dsh-loader-smoke':
|
'@deepseek-ai/dsh-loader-smoke':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../../packages/support/loader-smoke
|
version: link:../../packages/support/loader-smoke
|
||||||
|
|||||||
Reference in New Issue
Block a user