mirror your GitHub repos to tangled.org automatically
1import { describe, expect, it } from 'vitest'
2import { encodePktLine, flushPkt, lineToString, PktLineReader } from '../../server/utils/git-wire/pkt-line'
3
4async function* chunks(...parts: (Buffer | string)[]): AsyncGenerator<Buffer> {
5 for (const p of parts) yield typeof p === 'string' ? Buffer.from(p) : p
6}
7
8describe('pkt-line', () => {
9 describe('encodePktLine', () => {
10 it('frames a payload with a 4-hex-digit length including the prefix', () => {
11 // "hello\n" is 6 bytes, +4 prefix = 10 = 0x000a.
12 expect(encodePktLine('hello\n').toString()).toBe('000ahello\n')
13 })
14
15 it('frames an empty payload as length 4', () => {
16 expect(encodePktLine('').toString()).toBe('0004')
17 })
18
19 it('does not append a trailing newline of its own', () => {
20 expect(encodePktLine('want abc').toString()).toBe('000cwant abc')
21 })
22
23 it('rejects payloads larger than the max', () => {
24 expect(() => encodePktLine(Buffer.alloc(65517))).toThrow(/too large/)
25 })
26
27 it('exposes the flush-pkt as 0000', () => {
28 expect(flushPkt.toString()).toBe('0000')
29 })
30 })
31
32 describe('PktLineReader', () => {
33 it('decodes consecutive lines', async () => {
34 const r = new PktLineReader(chunks('0006a\n0006b\n'))
35 expect(lineToString((await r.next() as { data: Buffer }).data)).toBe('a')
36 expect(lineToString((await r.next() as { data: Buffer }).data)).toBe('b')
37 expect(await r.next()).toBeNull()
38 })
39
40 it('returns flush-pkts as a distinct type without ending iteration', async () => {
41 const r = new PktLineReader(chunks('0006a\n00000006b\n'))
42 expect((await r.next())!.type).toBe('line')
43 expect((await r.next())!.type).toBe('flush')
44 expect((await r.next())!.type).toBe('line')
45 expect(await r.next()).toBeNull()
46 })
47
48 it('reassembles a line split across chunk boundaries', async () => {
49 const r = new PktLineReader(chunks('00', '0a', 'hel', 'lo\n'))
50 expect(lineToString((await r.next() as { data: Buffer }).data)).toBe('hello')
51 })
52
53 it('readUntilFlush collects line payloads and stops at flush', async () => {
54 const r = new PktLineReader(chunks('0006a\n0006b\n0000'))
55 const lines = await r.readUntilFlush()
56 expect(lines!.map(l => lineToString(l))).toEqual(['a', 'b'])
57 })
58
59 it('hands raw trailing bytes back via remaining(), including pre-buffered ones', async () => {
60 // One pkt-line "NAK\n" then raw pack bytes "PACK..." arriving in the
61 // same chunk: the reader must not swallow the pack head.
62 const r = new PktLineReader(chunks('0008NAK\nPACK\x00\x01\x02', 'more'))
63 const first = await r.next()
64 expect(lineToString((first as { data: Buffer }).data)).toBe('NAK')
65
66 let raw = Buffer.alloc(0)
67 for await (const c of r.remaining()) raw = Buffer.concat([raw, c])
68 expect(raw.toString('binary')).toBe('PACK\x00\x01\x02more')
69 })
70
71 it('throws on a truncated length prefix', async () => {
72 const r = new PktLineReader(chunks('00'))
73 await expect(r.next()).rejects.toThrow(/truncated pkt-line length/)
74 })
75
76 it('throws on a truncated payload', async () => {
77 const r = new PktLineReader(chunks('000ahel'))
78 await expect(r.next()).rejects.toThrow(/wanted 10 bytes/)
79 })
80
81 it('throws on a reserved length', async () => {
82 const r = new PktLineReader(chunks('0001'))
83 await expect(r.next()).rejects.toThrow(/reserved pkt-line length/)
84 })
85 })
86
87 describe('lineToString', () => {
88 it('strips a single trailing newline', () => {
89 expect(lineToString(Buffer.from('abc\n'))).toBe('abc')
90 })
91
92 it('leaves a line without a trailing newline intact', () => {
93 expect(lineToString(Buffer.from('abc'))).toBe('abc')
94 })
95 })
96})