test: real-Loader dynamic composition, keyless onboarding snapshot, and .env-only e2e

llm-deepseek gains a Loader+Include composition spec proving external
settings.yaml/.env edits reach the very next request, and a real-API e2e
where only a credentials-local document holds the key. The headless example
pins the first-run missing-credential UX as a keyless stream-json snapshot
(new credentials.cordis.snapshot.yml scenario); runLoaderSmoke learns
expectedExitCode so a designed failure surface can be pinned instead of
masked.
This commit is contained in:
Yichen Jiang
2026-07-29 13:44:24 +08:00
parent c0426142c5
commit d77db29f01
9 changed files with 290 additions and 7 deletions

View File

@@ -1,7 +1,11 @@
import { afterEach, describe, expect, it } from 'vitest'
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { Context } from 'cordis'
import LlmService, { createUserMessage, CallId, ReasoningEffortId , createMessage } from '@deepseek-ai/dsh-llm'
import type { Message, ToolSchema } from '@deepseek-ai/dsh-llm'
import { CredentialsLocal } from '@deepseek-ai/dsh-credentials-local'
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
import type { Config } from '@deepseek-ai/dsh-llm-deepseek'
import { assemble, type AssembledResult } from './assemble.ts'
@@ -53,6 +57,34 @@ const weatherTool: ToolSchema = {
}
describe.skipIf(!process.env.DEEPSEEK_API_KEY)('llm-deepseek e2e (real API)', () => {
it('serves a real request with the key held only by a credentials-local document', async () => {
const key = process.env.DEEPSEEK_API_KEY
if (key === undefined) throw new Error('e2e ran without DEEPSEEK_API_KEY')
const dir = await mkdtemp(join(tmpdir(), 'dsh-e2e-credentials-'))
try {
await writeFile(join(dir, '.env'), `DEEPSEEK_API_KEY=${key}\n`, { mode: 0o600 })
// Scrub the ambient variable so only the credential seam can supply the
// key: this request proves the per-request resolution path end to end.
vi.stubEnv('DEEPSEEK_API_KEY', '')
const ctx = new Context()
contexts.push(ctx)
await ctx.plugin(LlmService)
await ctx.plugin(CredentialsLocal, { path: join(dir, '.env'), watch: false })
await ctx.plugin(LlmDeepSeek, {})
const result = await assemble(ctx, {
model: FLASH,
messages: ask('Reply with exactly the word: pong'),
maxTokens: 50,
})
expect(result.finish.kind).toBe('stop')
expect(textOf(result).toLowerCase()).toContain('pong')
} finally {
vi.unstubAllEnvs()
await rm(dir, { recursive: true, force: true })
}
})
it('flash dynamically switches from off to high', async () => {
const ctx = await harness(FLASH, { reasoningEffort: 'off' })
const withoutThinking = await assemble(ctx,{

View File

@@ -0,0 +1,142 @@
/**
* Real-composition guard for the dynamic-configuration chain: LlmService,
* settings-local, credentials-local, and llm-deepseek boot from a test-only
* cordis.yml through the actual Loader + Include path, external edits of
* settings.yaml and .env hot-publish through their providers, and the very
* next request carries the fresh base URL and credential. The same adapter
* composition without settings or credentials entries keeps entry-config
* behavior — the documented optional-inject fallback.
*/
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { Context } from 'cordis'
import Loader from '@cordisjs/plugin-loader'
import Include from '@cordisjs/plugin-include'
import LlmService from '@deepseek-ai/dsh-llm'
import { credentialRef } from '@deepseek-ai/dsh-credentials'
import CredentialsLocal from '@deepseek-ai/dsh-credentials-local'
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
import SettingsLocal from '@deepseek-ai/dsh-settings-local'
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
import { assemble } from './assemble.ts'
import { closeMockServers, mockServer, textEvents } from './mock-server.ts'
const NS = settingsNamespace('llm-deepseek')
const KEY_REF = credentialRef('DEEPSEEK_API_KEY')
let root: string | undefined
let context: Context | undefined
afterEach(async () => {
await context?.fiber.dispose()
context = undefined
if (root !== undefined) await rm(root, { recursive: true, force: true })
root = undefined
await closeMockServers()
vi.unstubAllEnvs()
})
async function loadComposition(
options: { withDynamic: boolean; baseURL: string },
): Promise<{ ctx: Context; settingsPath: string; envPath: string }> {
root = await mkdtemp(join(tmpdir(), 'dsh-llm-composition-'))
const settingsPath = join(root, 'settings.yaml')
const envPath = join(root, '.env')
if (options.withDynamic) {
await writeFile(settingsPath, '# personal settings\n')
await writeFile(envPath, 'DEEPSEEK_API_KEY=boot-key\n')
}
const configPath = join(root, 'cordis.yml')
await writeFile(configPath, [
'- id: llm',
" name: 'test-llm-service'",
...options.withDynamic
? [
'- id: settings',
" name: '@deepseek-ai/dsh-settings-local'",
' config:',
` path: ${JSON.stringify(settingsPath)}`,
' debounceMs: 10',
'- id: credentials',
" name: '@deepseek-ai/dsh-credentials-local'",
' config:',
` path: ${JSON.stringify(envPath)}`,
' debounceMs: 10',
]
: [],
'- id: llm-deepseek',
" name: '@deepseek-ai/dsh-llm-deepseek'",
' config:',
` baseURL: ${JSON.stringify(options.baseURL)}`,
...options.withDynamic ? [] : [' apiKey: entry-key'],
'',
].join('\n'))
const ctx = new Context()
context = ctx
ctx.baseUrl = pathToFileURL(root).href + '/'
await ctx.plugin(Loader)
ctx.loader.builtins.include = Include
const modules = new Map<string, unknown>([
['test-llm-service', LlmService],
['@deepseek-ai/dsh-settings-local', SettingsLocal],
['@deepseek-ai/dsh-credentials-local', CredentialsLocal],
['@deepseek-ai/dsh-llm-deepseek', LlmDeepSeek],
])
ctx.loader.internal = {
version: 'v2',
async import(specifier: string) {
if (!modules.has(specifier)) throw new Error(`unexpected Loader import: ${specifier}`)
return modules.get(specifier)
},
} as unknown as NonNullable<typeof ctx.loader.internal>
await ctx.loader.create({
name: 'cordis:include',
config: { path: pathToFileURL(configPath).href },
})
await ctx.loader.await()
return { ctx, settingsPath, envPath }
}
describe('llm-deepseek real dynamic composition', () => {
it('boots from cordis.yml and routes the next request after external settings and .env edits', async () => {
vi.stubEnv('DEEPSEEK_API_KEY', '')
const serverA = await mockServer([{ kind: 'sse', events: textEvents }])
const serverB = await mockServer([{ kind: 'sse', events: textEvents }])
const { ctx, settingsPath, envPath } = await loadComposition({ withDynamic: true, baseURL: serverA.url })
expect(ctx.get('settings')!.describe().map(entry => entry.ns)).toEqual([NS])
await assemble(ctx, { model: 'deepseek-v4-flash', messages: [] })
expect(serverA.headers[0]?.authorization).toBe('Bearer boot-key')
// External edits, exactly as a user or the web UI would leave them on disk.
await writeFile(settingsPath, `llm-deepseek:\n baseURL: ${serverB.url}\n`)
await vi.waitFor(() => {
expect((ctx.get('settings')!.get(NS) as { baseURL?: string }).baseURL).toBe(serverB.url)
}, { timeout: 5000 })
await writeFile(envPath, 'DEEPSEEK_API_KEY=rotated-key\n')
await vi.waitFor(async () => {
expect(await ctx.get('credentials')!.resolve(KEY_REF)).toEqual({ value: 'rotated-key', source: 'file' })
}, { timeout: 5000 })
await assemble(ctx, { model: 'deepseek-v4-flash', messages: [] })
expect(serverA.requests).toHaveLength(1)
expect(serverB.headers[0]?.authorization).toBe('Bearer rotated-key')
})
it('boots the same adapter without settings or credentials entries on entry config alone', async () => {
vi.stubEnv('DEEPSEEK_API_KEY', '')
const server = await mockServer([{ kind: 'sse', events: textEvents }])
const { ctx } = await loadComposition({ withDynamic: false, baseURL: server.url })
expect(ctx.get('settings')).toBeUndefined()
expect(ctx.get('credentials')).toBeUndefined()
await assemble(ctx, { model: 'deepseek-v4-flash', messages: [] })
expect(server.headers[0]?.authorization).toBe('Bearer entry-key')
})
})