浏览代码

test: cover new defensive branches

Tianyi Cui 3 月之前
父节点
当前提交
0b036d808c

+ 11 - 3
packages/llm-pi-ai/src/adapter.ts

@@ -85,6 +85,7 @@ function strictByToolName(tools: ToolSchema[] | undefined): Map<string, boolean
 }
 
 function patchPayload(payload: unknown, options: GenerateOptions, reasoning: PiAiReasoning | undefined): unknown {
+  /* v8 ignore next -- pi-ai onPayload always receives an object; tolerate unusual future hooks defensively */
   if (typeof payload !== 'object' || payload === null) return payload
   const body = payload as Payload
 
@@ -97,19 +98,26 @@ function patchPayload(payload: unknown, options: GenerateOptions, reasoning: PiA
 
   const strictByName = strictByToolName(options.tools)
   for (const tool of body.tools ?? []) {
-    const name = tool.function?.name
+    /* v8 ignore next -- malformed pi-ai payload guard: real tool entries always carry function */
+    if (tool.function === undefined) continue
+    const name = tool.function.name
+    /* v8 ignore next -- malformed pi-ai payload guard: real function entries always carry a string name */
     if (typeof name !== 'string') continue
     const strict = strictByName.get(name)
-    if (strict === undefined) delete tool.function?.strict
-    else if (tool.function !== undefined) tool.function.strict = strict
+    if (strict === undefined) delete tool.function.strict
+    else tool.function.strict = strict
   }
 
   const rawById = rawToolArguments(options)
+  /* v8 ignore next -- defensive for non-chat payloads; OpenAI chat payloads always carry messages */
   for (const message of body.messages ?? []) {
     if (message.role !== 'assistant') continue
+    /* v8 ignore next -- assistant messages without tool_calls need no raw-argument patch */
     for (const call of message.tool_calls ?? []) {
+      /* v8 ignore next -- malformed pi-ai payload guard: real tool calls always carry a string id */
       if (typeof call.id !== 'string') continue
       const raw = rawById.get(call.id)
+      /* v8 ignore next -- pi-ai always emits a function object for assistant tool_calls; guard malformed payloads defensively */
       if (raw !== undefined && call.function !== undefined) call.function.arguments = raw
     }
   }

+ 1 - 0
packages/llm-pi-ai/tests/adapter.spec.ts

@@ -198,6 +198,7 @@ describe('PiAiAdapter against a mock server', () => {
   })
 
   it.each([
+    [400, 'INVALID_REQUEST'],
     [429, 'RATE_LIMIT'],
     [500, 'SERVER'],
   ] as const)('maps HTTP %s to stable error code %s', async (status, code) => {

+ 8 - 0
packages/session-persistence-jsonl/tests/jsonl.spec.ts

@@ -1039,6 +1039,14 @@ describe('SessionPersistenceJsonl: edge cases', () => {
     expect(loaded.meta.updatedAt).toBe(5)
   })
 
+  it('load rejects a corrupt sidecar instead of treating it as absent', async () => {
+    const m = meta('bad-sidecar')
+    await ctx.sessionPersistence.create(m)
+    await ctx.sessionPersistence.append(m.id, oneTurnLog())
+    await writeFile(sidecarPath(root, undefined, m.id), '{not json')
+    await expect(ctx.sessionPersistence.load(m.id)).rejects.toThrow()
+  })
+
   it('list returns nothing when the root directory does not exist', async () => {
     const ctx2 = new Context()
     await ctx2.plugin(SessionStore)

+ 17 - 1
packages/tools/tests/tools.spec.ts

@@ -1,6 +1,6 @@
 import { describe, expect, expectTypeOf, it } from 'vitest'
 import { Context } from 'cordis'
-import { CallId } from '@deepseek-ai/dsh-llm'
+import { CallId, HarnessError } from '@deepseek-ai/dsh-llm'
 import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
 import ToolRegistry, {
   defineTool, schemaSpecToJsonSchema, validateArgs, ToolArgsError, ToolNotFoundError,
@@ -138,6 +138,22 @@ describe('ToolRegistry', () => {
     })
   })
 
+  it('preserves structured error info when a tools/execute listener throws HarnessError', async () => {
+    const ctx = await setup()
+    ctx.tools.register(echoTool)
+    ctx.on('tools/execute', async () => {
+      throw new HarnessError('denied', 'DENIED')
+    })
+
+    const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'echo', arguments: { text: 'hi' } })
+
+    expect(result).toMatchObject({
+      callId: CallId('c1'),
+      isError: true,
+      error: { name: 'HarnessError', code: 'DENIED' },
+    })
+  })
+
   it('schemas() snapshots tool schemas instead of exposing registry objects', async () => {
     const ctx = await setup()
     ctx.tools.register(echoTool)