mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
83 lines
3.4 KiB
TypeScript
83 lines
3.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { encodeMessage, MessageDecoder } from '@deepseek-ai/dsh-lsp-local'
|
|
|
|
/** Frame a message the way a server would, for decoder round-trips. */
|
|
function frame(body: string): Buffer {
|
|
return Buffer.concat([Buffer.from(`Content-Length: ${Buffer.byteLength(body)}\r\n\r\n`, 'ascii'), Buffer.from(body, 'utf8')])
|
|
}
|
|
|
|
describe('encodeMessage', () => {
|
|
it('prefixes a Content-Length header with the utf-8 byte length', () => {
|
|
const buffer = encodeMessage({ jsonrpc: '2.0', method: 'x', params: { s: 'é' } })
|
|
const text = buffer.toString('utf8')
|
|
const body = '{"jsonrpc":"2.0","method":"x","params":{"s":"é"}}'
|
|
expect(text).toBe(`Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`)
|
|
})
|
|
})
|
|
|
|
describe('MessageDecoder', () => {
|
|
it('decodes a single framed message', () => {
|
|
const decoder = new MessageDecoder(1_000)
|
|
expect(decoder.push(frame('{"id":1,"result":42}'))).toEqual([{ id: 1, result: 42 }])
|
|
})
|
|
|
|
it('decodes multiple messages arriving in one chunk', () => {
|
|
const decoder = new MessageDecoder(1_000)
|
|
const chunk = Buffer.concat([frame('{"a":1}'), frame('{"b":2}')])
|
|
expect(decoder.push(chunk)).toEqual([{ a: 1 }, { b: 2 }])
|
|
})
|
|
|
|
it('reassembles a message split across chunks', () => {
|
|
const decoder = new MessageDecoder(1_000)
|
|
const full = frame('{"hello":"world"}')
|
|
expect(decoder.push(full.subarray(0, 10))).toEqual([])
|
|
expect(decoder.push(full.subarray(10))).toEqual([{ hello: 'world' }])
|
|
})
|
|
|
|
it('handles a header split from its body', () => {
|
|
const decoder = new MessageDecoder(1_000)
|
|
const body = '{"x":1}'
|
|
expect(decoder.push(Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, 'ascii'))).toEqual([])
|
|
expect(decoder.push(Buffer.from(body, 'utf8'))).toEqual([{ x: 1 }])
|
|
})
|
|
|
|
it('reads a case-insensitive header and ignores other headers', () => {
|
|
const decoder = new MessageDecoder(1_000)
|
|
const body = '{"ok":true}'
|
|
const chunk = Buffer.from(`content-length: ${body.length}\r\nContent-Type: x\r\n\r\n${body}`, 'utf8')
|
|
expect(decoder.push(chunk)).toEqual([{ ok: true }])
|
|
})
|
|
|
|
it('rejects a body over the size limit', () => {
|
|
const decoder = new MessageDecoder(4)
|
|
expect(() => decoder.push(frame('{"big":true}'))).toThrow(/exceeds the 4-byte limit/)
|
|
})
|
|
|
|
it('rejects a missing Content-Length header', () => {
|
|
const decoder = new MessageDecoder(1_000)
|
|
expect(() => decoder.push(Buffer.from('X: 1\r\n\r\n{}', 'utf8'))).toThrow(/missing Content-Length/)
|
|
})
|
|
|
|
it('rejects a non-numeric Content-Length', () => {
|
|
const decoder = new MessageDecoder(1_000)
|
|
expect(() => decoder.push(Buffer.from('Content-Length: abc\r\n\r\n{}', 'utf8'))).toThrow(/invalid Content-Length/)
|
|
})
|
|
|
|
it('rejects a header block that never terminates', () => {
|
|
const decoder = new MessageDecoder(1_000)
|
|
const huge = Buffer.alloc((1 << 16) + 1, 0x41)
|
|
expect(() => decoder.push(huge)).toThrow(/exceeded .* bytes without a terminator/)
|
|
})
|
|
|
|
it('rejects an oversized header block that includes its terminator', () => {
|
|
const decoder = new MessageDecoder(1_000)
|
|
const huge = Buffer.from(`Content-Length: 2\r\nX-Fill: ${'a'.repeat(70_000)}\r\n\r\n{}`, 'ascii')
|
|
expect(() => decoder.push(huge)).toThrow(/header exceeded .* bytes/)
|
|
})
|
|
|
|
it('rejects a non-JSON body', () => {
|
|
const decoder = new MessageDecoder(1_000)
|
|
expect(() => decoder.push(frame('not json'))).toThrow(/not valid JSON/)
|
|
})
|
|
})
|