import { describe, test, expect } from 'vitest' import { createFlowController, DEFAULT_INITIAL_WINDOW, WINDOW_REPLENISH_THRESHOLD, } from '../mux/flow-control.js' describe('credit flow control (T4, §4.1)', () => { test('canSend true until credit exhausted, then false; never negative', () => { const fc = createFlowController() fc.registerStream(1, 100) expect(fc.canSend(1, 100)).toBe(true) fc.consumeSendCredit(1, 100) expect(fc.creditFor(1)).toBe(0) expect(fc.canSend(1, 1)).toBe(false) // Over-consume never drives credit negative. fc.consumeSendCredit(1, 50) expect(fc.creditFor(1)).toBe(0) }) test('grantCredit unblocks a backpressured stream', () => { const fc = createFlowController() fc.registerStream(1, 10) fc.consumeSendCredit(1, 10) expect(fc.canSend(1, 5)).toBe(false) fc.grantCredit(1, 20) expect(fc.canSend(1, 5)).toBe(true) expect(fc.creditFor(1)).toBe(20) }) test('onDelivered returns a replenish amount only once the threshold is crossed', () => { const fc = createFlowController() fc.registerStream(1, 100) // threshold = 50 bytes expect(fc.onDelivered(1, 40)).toBe(0) const replenish = fc.onDelivered(1, 20) // cumulative 60 ≥ 50 expect(replenish).toBe(60) // Counter reset after replenish. expect(fc.onDelivered(1, 10)).toBe(0) }) test('unknown streamId is deny-by-default: canSend false, creditFor 0', () => { const fc = createFlowController() expect(fc.canSend(999, 1)).toBe(false) expect(fc.creditFor(999)).toBe(0) expect(fc.onDelivered(999, 100)).toBe(0) }) test('released streams carry no credit (send-after-close is denied)', () => { const fc = createFlowController() fc.registerStream(1, 100) fc.releaseStream(1) expect(fc.canSend(1, 1)).toBe(false) }) test('per-stream isolation: exhausting A does not affect B (no cross-stream starvation)', () => { const fc = createFlowController() fc.registerStream(1, 50) fc.registerStream(2, 50) fc.consumeSendCredit(1, 50) expect(fc.canSend(1, 1)).toBe(false) expect(fc.canSend(2, 50)).toBe(true) // B untouched }) test('exposes the documented constants', () => { expect(DEFAULT_INITIAL_WINDOW).toBe(256 * 1024) expect(WINDOW_REPLENISH_THRESHOLD).toBe(0.5) }) })