refactor: apply repository naming contract
Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
This commit is contained in:
82
packages/lsp/lsp-stdio/tests/framing.spec.ts
Normal file
82
packages/lsp/lsp-stdio/tests/framing.spec.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { encodeMessage, MessageDecoder } from '@deepseek-ai/dsh-lsp-stdio'
|
||||
|
||||
/** 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/)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user