Enforce 100% per-file test coverage on packages/*/src

vitest coverage (v8 provider) with per-file 100% thresholds for
statements, branches, functions, and lines. Scope: our runtime source
only — types-only files, vendor/ (upstream code), and examples/
(exercised by the demo smoke test) are excluded. yarn test:coverage
runs the gate.

59 tests added to close every gap: llm generate-waterfall and adapter
disposal; assembler edge protocol (duplicate block-start, stragglers
after block-end, id fallback, usage omission, invariant violation);
the whole Inbox surface incl. the wakeup-overwrite race; LoopAgent
disposed-state throws and double-stop idempotence; config-driven agent
creation; loop backstop catches (throwing turn-start/turn-end
listeners, non-Error throws, non-JSON tool arguments); system-prompt
dynamic sections and disposer paths; tools errorMessage fallbacks and
the full schema-DSL emission matrix. Genuinely unreachable defensive
guards carry /* v8 ignore */ comments with stated reasons rather than
deletion (132 tests total).
This commit is contained in:
Tianyi Cui
2026-06-11 14:58:36 +08:00
parent cb6bee3d03
commit bfb034830f
15 changed files with 1179 additions and 4 deletions

View File

@@ -43,4 +43,111 @@ describe('BlockAssembler', () => {
expect(assembler.blocks()).toEqual([{ type: 'text', text: 'implicit' }])
expect(assembler.finish).toEqual({ kind: 'stop' })
})
it('returns undefined usage when no usage chunk was received', () => {
const assembler = new BlockAssembler()
assembler.push({ type: 'text-delta', index: 0, text: 'no usage' })
expect(assembler.usage).toBeUndefined()
})
it('reuses an existing partial when ensure() is called with a tracked index', () => {
const assembler = new BlockAssembler()
// block-start creates the partial; block-end calls ensure() on the same index
assembler.push({ type: 'block-start', index: 0, blockType: 'text' })
// push a delta first to guarantee the partial exists
assembler.push({ type: 'text-delta', index: 0, text: 'hi' })
// block-end's ensure() must find the existing partial (the second branch path)
const block = assembler.push({ type: 'block-end', index: 0, block: { type: 'text', text: 'hi' } })
expect(block).toEqual({ type: 'text', text: 'hi' })
})
it('throws from assemble() when a partial has an unhandled blockType', () => {
const assembler = new BlockAssembler()
// Directly push a block-end for an image block whose block-start never
// called ensure — but the image block-type flows through normally.
// What we really need is a partial whose blockType is not text/reasoning/tool-call.
// We can achieve this via a block-start for 'image' followed by blocks().
assembler.push({ type: 'block-start', index: 0, blockType: 'image' } as unknown as StreamChunk)
expect(() => assembler.blocks()).toThrow('cannot assemble incomplete block of type "image"')
})
it('mustGet throws when an index is missing from the partials map (invariant violation)', () => {
const assembler = new BlockAssembler()
// Force the invariant violation: manually corrupt the data structures.
/* eslint-disable */
const hack = assembler as any
hack.order.push(99)
/* eslint-enable */
expect(() => assembler.blocks()).toThrow('BlockAssembler invariant violated')
})
it('assembles open blocks at end of stream via flushRemaining', () => {
const assembler = new BlockAssembler()
assembler.push({ type: 'text-delta', index: 0, text: 'open' })
assembler.push({ type: 'reasoning-delta', index: 1, text: 'thinking' })
// flushReady returns nothing because index 0 is incomplete and blocking
const ready = assembler.flushReady()
expect(ready).toEqual([])
// flushRemaining assembles everything still open
const remaining = assembler.flushRemaining()
expect(remaining).toEqual([
{ type: 'text', text: 'open' },
{ type: 'reasoning', text: 'thinking' },
])
// blocks() now matches the flushed view
expect(assembler.blocks()).toEqual(remaining)
})
it('result() omits usage key when no usage was received', () => {
const assembler = new BlockAssembler()
assembler.push({ type: 'text-delta', index: 0, text: 'msg' })
const result = assembler.result()
expect(result.message).toBeDefined()
expect(result.finish).toEqual({ kind: 'stop' })
// usage should NOT be present on the object at all
expect('usage' in result).toBe(false)
})
it('ignores duplicate block-start for the same index', () => {
const assembler = new BlockAssembler()
assembler.push({ type: 'block-start', index: 0, blockType: 'text' })
assembler.push({ type: 'text-delta', index: 0, text: 'one' })
// duplicate block-start — should be no-op (false branch of has check)
assembler.push({ type: 'block-start', index: 0, blockType: 'text' })
assembler.push({ type: 'text-delta', index: 0, text: ' two' })
assembler.push({ type: 'block-end', index: 0, block: { type: 'text', text: 'one two' } })
expect(assembler.blocks()).toEqual([{ type: 'text', text: 'one two' }])
})
it('ignores tool-call-delta stragglers after block-end', () => {
const assembler = new BlockAssembler()
assembler.push({ type: 'block-start', index: 0, blockType: 'tool-call' })
assembler.push({ type: 'tool-call-delta', index: 0, id: 'c1', name: 'echo', argumentsDelta: '{}' })
assembler.push({ type: 'block-end', index: 0, block: { type: 'tool-call', id: 'c1', name: 'echo', arguments: '{}' } })
// straggler after block-end — partial.block is set, so early return
assembler.push({ type: 'tool-call-delta', index: 0, id: 'c1', name: 'evil', argumentsDelta: 'oops' })
expect(assembler.blocks()).toEqual([{ type: 'tool-call', id: 'c1', name: 'echo', arguments: '{}' }])
})
it('assembles tool-call with generated id fallback when no id provided', () => {
const assembler = new BlockAssembler()
assembler.push({ type: 'tool-call-delta', index: 0, argumentsDelta: '{}' } as StreamChunk)
// No id and no name provided — uses fallback id `call-{index}` and empty name
const blocks = assembler.blocks()
expect(blocks).toEqual([
{ type: 'tool-call', id: 'call-0', name: '', arguments: '{}' },
])
})
it('includes usage in result() when usage was received', () => {
const assembler = new BlockAssembler()
assembler.push({ type: 'text-delta', index: 0, text: 'msg' })
assembler.push({ type: 'usage', usage: { inputTokens: 5, outputTokens: 3 } })
const result = assembler.result()
expect(result.usage).toEqual({ inputTokens: 5, outputTokens: 3 })
expect('usage' in result).toBe(true)
})
})

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import LlmService, { GenerateOptions, LlmAdapter, StreamChunk } from '@deepseek-ai/dsh-llm'
import LlmService, { GenerateOptions, LlmAdapter, LlmError, StreamChunk } from '@deepseek-ai/dsh-llm'
class ScriptedAdapter extends LlmAdapter {
constructor(private script: StreamChunk[]) {
@@ -70,4 +70,58 @@ describe('LlmService', () => {
expect(chunks).toHaveLength(4)
expect(chunks[0]).toMatchObject({ index: 99 })
})
it('lets llm/generate waterfall listeners intercept and transform the result', async () => {
const ctx = new Context()
await ctx.plugin(LlmService)
ctx.llm.registerAdapter(['test-model'], new ScriptedAdapter(SCRIPT))
ctx.on('llm/generate', async function (_options, next) {
const result = await next()
return { ...result, finish: { kind: 'max-tokens' } as const }
})
const result = await ctx.llm.generate({ model: 'test-model', messages: [] })
expect(result.finish).toEqual({ kind: 'max-tokens' })
expect(result.message.content).toEqual([{ type: 'text', text: 'hi' }])
})
it('creates LlmError with a code for programmatic handling', () => {
const err = new LlmError('something went wrong', 'CUSTOM_CODE')
expect(err).toBeInstanceOf(Error)
expect(err.name).toBe('LlmError')
expect(err.message).toBe('something went wrong')
expect(err.code).toBe('CUSTOM_CODE')
})
it('disposes adapter registration on adapter-change event emission', async () => {
const ctx = new Context()
await ctx.plugin(LlmService)
const changes: string[][] = []
ctx.on('llm/adapter-change', () => {
changes.push([...ctx.llm.models()])
})
const dispose = ctx.llm.registerAdapter(['m1'], new ScriptedAdapter(SCRIPT))
expect(changes).toEqual([['m1']])
dispose()
expect(changes).toEqual([['m1'], []])
expect(ctx.llm.models()).toEqual([])
})
it('rejects duplicate adapter registration with DUPLICATE_ADAPTER code', async () => {
const ctx = new Context()
await ctx.plugin(LlmService)
ctx.llm.registerAdapter(['m1'], new ScriptedAdapter(SCRIPT))
try {
ctx.llm.registerAdapter(['m1'], new ScriptedAdapter(SCRIPT))
expect.fail('expected error')
} catch (error: unknown) {
expect(error).toBeInstanceOf(LlmError)
expect((error as LlmError).message).toContain('already registered')
expect((error as LlmError).code).toBe('DUPLICATE_ADAPTER')
}
})
})