stream.spec.ts 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. /** Output followers drain on process exit and discard queued data on detach. */
  2. import { describe, expect, it } from 'vitest'
  3. import { TerminalFollower } from '../src/stream.ts'
  4. import type { TerminalFrame } from '../src/types.ts'
  5. const frame: TerminalFrame = { type: 'output', sequence: 1, data: '终端' }
  6. const signal = (): AbortSignal => new AbortController().signal
  7. describe('TerminalFollower', () => {
  8. it('accepts the exact byte limit and restores capacity after each frame is consumed', async () => {
  9. const follower = new TerminalFollower(Buffer.byteLength(JSON.stringify(frame), 'utf8'))
  10. const stream = follower.read(signal())[Symbol.asyncIterator]()
  11. try {
  12. follower.push(frame)
  13. expect(await stream.next()).toEqual({ done: false, value: frame })
  14. follower.push(frame)
  15. follower.finish()
  16. follower.push({ ...frame, sequence: 2 })
  17. expect(await stream.next()).toEqual({ done: false, value: frame })
  18. expect(await stream.next()).toEqual({ done: true, value: undefined })
  19. } finally { follower.close(); await stream.return?.() }
  20. })
  21. it('fails accumulated overflow using encoded byte size', async () => {
  22. const follower = new TerminalFollower(Buffer.byteLength(JSON.stringify(frame), 'utf8'))
  23. follower.push(frame)
  24. follower.push(frame)
  25. await expect(follower.read(signal())[Symbol.asyncIterator]().next()).rejects.toThrow('reconnect to recover')
  26. })
  27. it('discards queued data when detached and ignores later output', async () => {
  28. const follower = new TerminalFollower(1000)
  29. follower.push(frame)
  30. follower.close()
  31. follower.push(frame)
  32. expect(await follower.read(signal())[Symbol.asyncIterator]().next()).toEqual({ done: true, value: undefined })
  33. })
  34. it('does not deliver buffered output to an already-aborted request', async () => {
  35. const follower = new TerminalFollower(1000)
  36. follower.push(frame)
  37. const abort = new AbortController()
  38. abort.abort()
  39. expect(await follower.read(abort.signal)[Symbol.asyncIterator]().next()).toEqual({ done: true, value: undefined })
  40. })
  41. it('wakes a waiting reader for output and then for graceful completion', async () => {
  42. const follower = new TerminalFollower(1000)
  43. const stream = follower.read(signal())[Symbol.asyncIterator]()
  44. try {
  45. const reading = stream.next()
  46. follower.push(frame)
  47. expect(await reading).toEqual({ done: false, value: frame })
  48. const finishing = stream.next()
  49. follower.finish()
  50. expect(await finishing).toEqual({ done: true, value: undefined })
  51. } finally { follower.close(); await stream.return?.() }
  52. })
  53. it('wakes a waiting reader on cancellation', async () => {
  54. const follower = new TerminalFollower(1000)
  55. const abort = new AbortController()
  56. const stream = follower.read(abort.signal)[Symbol.asyncIterator]()
  57. try {
  58. const reading = stream.next()
  59. abort.abort()
  60. expect(await reading).toEqual({ done: true, value: undefined })
  61. } finally { follower.close(); await stream.return?.() }
  62. })
  63. it('closes the follower when its consumer returns before EOF', async () => {
  64. const follower = new TerminalFollower(1000)
  65. const stream = follower.read(signal())[Symbol.asyncIterator]()
  66. follower.push(frame)
  67. await stream.next()
  68. await stream.return?.()
  69. follower.push(frame)
  70. expect(await follower.read(signal())[Symbol.asyncIterator]().next()).toEqual({ done: true, value: undefined })
  71. })
  72. })