| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980 |
- /** Output followers drain on process exit and discard queued data on detach. */
- import { describe, expect, it } from 'vitest'
- import { TerminalFollower } from '../src/stream.ts'
- import type { TerminalFrame } from '../src/types.ts'
- const frame: TerminalFrame = { type: 'output', sequence: 1, data: '终端' }
- const signal = (): AbortSignal => new AbortController().signal
- describe('TerminalFollower', () => {
- it('accepts the exact byte limit and restores capacity after each frame is consumed', async () => {
- const follower = new TerminalFollower(Buffer.byteLength(JSON.stringify(frame), 'utf8'))
- const stream = follower.read(signal())[Symbol.asyncIterator]()
- try {
- follower.push(frame)
- expect(await stream.next()).toEqual({ done: false, value: frame })
- follower.push(frame)
- follower.finish()
- follower.push({ ...frame, sequence: 2 })
- expect(await stream.next()).toEqual({ done: false, value: frame })
- expect(await stream.next()).toEqual({ done: true, value: undefined })
- } finally { follower.close(); await stream.return?.() }
- })
- it('fails accumulated overflow using encoded byte size', async () => {
- const follower = new TerminalFollower(Buffer.byteLength(JSON.stringify(frame), 'utf8'))
- follower.push(frame)
- follower.push(frame)
- await expect(follower.read(signal())[Symbol.asyncIterator]().next()).rejects.toThrow('reconnect to recover')
- })
- it('discards queued data when detached and ignores later output', async () => {
- const follower = new TerminalFollower(1000)
- follower.push(frame)
- follower.close()
- follower.push(frame)
- expect(await follower.read(signal())[Symbol.asyncIterator]().next()).toEqual({ done: true, value: undefined })
- })
- it('does not deliver buffered output to an already-aborted request', async () => {
- const follower = new TerminalFollower(1000)
- follower.push(frame)
- const abort = new AbortController()
- abort.abort()
- expect(await follower.read(abort.signal)[Symbol.asyncIterator]().next()).toEqual({ done: true, value: undefined })
- })
- it('wakes a waiting reader for output and then for graceful completion', async () => {
- const follower = new TerminalFollower(1000)
- const stream = follower.read(signal())[Symbol.asyncIterator]()
- try {
- const reading = stream.next()
- follower.push(frame)
- expect(await reading).toEqual({ done: false, value: frame })
- const finishing = stream.next()
- follower.finish()
- expect(await finishing).toEqual({ done: true, value: undefined })
- } finally { follower.close(); await stream.return?.() }
- })
- it('wakes a waiting reader on cancellation', async () => {
- const follower = new TerminalFollower(1000)
- const abort = new AbortController()
- const stream = follower.read(abort.signal)[Symbol.asyncIterator]()
- try {
- const reading = stream.next()
- abort.abort()
- expect(await reading).toEqual({ done: true, value: undefined })
- } finally { follower.close(); await stream.return?.() }
- })
- it('closes the follower when its consumer returns before EOF', async () => {
- const follower = new TerminalFollower(1000)
- const stream = follower.read(signal())[Symbol.asyncIterator]()
- follower.push(frame)
- await stream.next()
- await stream.return?.()
- follower.push(frame)
- expect(await follower.read(signal())[Symbol.asyncIterator]().next()).toEqual({ done: true, value: undefined })
- })
- })
|