From 7755d97dbafa2d62d892ea8815eae19a049a9b26 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Thu, 24 Sep 2026 13:38:08 +0800 Subject: [PATCH] test(llm): cover the account listing and gate through the Loader composition --- .../llm/llm-deepseek/tests/adapter.spec.ts | 21 +++++++++++++++++++ packages/llm/llm-deepseek/tests/helpers.ts | 16 +++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/llm/llm-deepseek/tests/adapter.spec.ts b/packages/llm/llm-deepseek/tests/adapter.spec.ts index 205869802c..78d2dc12a5 100644 --- a/packages/llm/llm-deepseek/tests/adapter.spec.ts +++ b/packages/llm/llm-deepseek/tests/adapter.spec.ts @@ -392,6 +392,27 @@ describe('Cordis provider composition', () => { }) }) + it('serves the account listing through the Loader composition and refuses models this build cannot serve', async () => { + const { ctx, http } = await boot(undefined, { data: [ + { + id: 'deepseek-flash', name: 'DeepSeek-V4.1-Flash', context_window: 1_048_576, max_output_tokens: 100_000, + input_modalities: ['text', 'image'], output_modalities: ['text'], + api_capabilities: { anthropic_messages: { system_prompt_update: 'in-history' } }, + }, + { + id: 'deepseek-v5', name: 'DeepSeek-V5', api_capabilities: { anthropic_messages: {} }, + client_requirements: { 'deepseek-harness': { min_version: '99.0.0' } }, + }, + ] }) + ctx.provide('deepseekAccount', { resolveToken: async (_url: string): Promise => 'fixture-token', + rejectToken: async (_token: string): Promise => {} } as DeepSeekAccount) + expect((await ctx.llm.listModels('deepseek-account')).map(model => [model.id, model.name])).toEqual([['deepseek-flash', 'DeepSeek-V4.1-Flash']]) + await expect(ctx.llm.prepareCall({ provider: 'deepseek-account', model: 'deepseek-v5' })) + .rejects.toMatchObject({ code: 'CLIENT_UPGRADE_REQUIRED' }) + await assemble(ctx.llm.stream(options({ provider: 'deepseek-account', model: 'deepseek-flash' })), 'deepseek-flash') + expect(http.requests.map(request => [request.path, request.body.max_tokens])).toEqual([['/anthropic/v1/messages', 100_000]]) + }) + it('does not remove account credentials for HTTP 403', async () => { const { ctx } = await boot((response) => { response.writeHead(403) diff --git a/packages/llm/llm-deepseek/tests/helpers.ts b/packages/llm/llm-deepseek/tests/helpers.ts index 606dfcfb13..539c31ba26 100644 --- a/packages/llm/llm-deepseek/tests/helpers.ts +++ b/packages/llm/llm-deepseek/tests/helpers.ts @@ -46,12 +46,26 @@ export function adapter(config: Config = {}) { const files = new DeepSeekFileStore() return new DeepSeekAdapter({ options: () => resolveAdapterOptions(config), resolveAuth: () => Promise.resolve({ headers: { 'x-api-key': 'test-key' } }), resolveUserId: () => 'test-user' as AnonymousUserId, resolveAttachments: () => undefined, resolveImageAccess: () => undefined, resolveFiles: () => files, prepareExtensions }) } -export async function server(reply: (response: ServerResponse, count: number) => void = response => response.end(sse(textEvents))) { +/** + * Loopback Messages endpoint recording each model request. + * @param reply - answers each Messages request. + * @param listing - JSON body served for `GET /models`; omission leaves that request unanswered like any other. + * @returns the endpoint base URL, recorded requests, and teardown. + */ +export async function server( + reply: (response: ServerResponse, count: number) => void = response => response.end(sse(textEvents)), + listing?: unknown, +) { const requests: { path: string; headers: IncomingHttpHeaders; body: Record }[] = [] const http = createServer((request, response) => { void handle(request, response).catch((error: unknown) => response.destroy(error as Error)) }) async function handle(request: import('node:http').IncomingMessage, response: ServerResponse) { + if (listing !== undefined && request.method === 'GET' && request.url === '/models') { + response.setHeader('content-type', 'application/json') + response.end(JSON.stringify(listing)) + return + } const parts: Buffer[] = [] for await (const part of request as AsyncIterable) parts.push(part) requests.push({ path: request.url!, headers: request.headers, body: object(JSON.parse(Buffer.concat(parts).toString())) })