| … | … | @@ -19,12 +19,15 @@ import b4a from 'b4a' |
| 19 | 19 | import { OverlayProtocol } from '../lib/protocol.js' |
| 20 | 20 | |
| 21 | 21 | // Minimal drain-controllable stream stub (only what _awaitDrain uses). |
| 22 | // Minimal drain-controllable stream stub matching the engine's _waitForDrain, |
| 23 | // which registers via stream.on('drain'|'close') + removeListener (not once). |
| 22 | 24 | function makeStream () { |
| 23 | | const listeners = [] |
| 25 | const listeners = { drain: [], close: [] } |
| 24 | 26 | return { |
| 25 | | once (ev, cb) { if (ev === 'drain') listeners.push(cb) }, |
| 26 | | removeListener (ev, cb) { const i = listeners.indexOf(cb); if (i >= 0) listeners.splice(i, 1) }, |
| 27 | | emitDrain () { const fire = listeners.splice(0); for (const cb of fire) cb() } |
| 27 | on (ev, cb) { (listeners[ev] || (listeners[ev] = [])).push(cb) }, |
| 28 | removeListener (ev, cb) { const a = listeners[ev]; if (!a) return; const i = a.indexOf(cb); if (i >= 0) a.splice(i, 1) }, |
| 29 | emitDrain () { const fire = listeners.drain.splice(0); for (const cb of fire) cb() }, |
| 30 | emitClose () { const fire = listeners.close.splice(0); for (const cb of fire) cb() } |
| 28 | 31 | } |
| 29 | 32 | } |
| 30 | 33 | const tick = () => new Promise((r) => setTimeout(r, 0)) |
| … | … | @@ -32,7 +35,13 @@ const tick = () => new Promise((r) => setTimeout(r, 0)) |
| 32 | 35 | function makeProto (n) { |
| 33 | 36 | const chunkMap = Array.from({ length: n }, (_, i) => ({ hash: 'h' + i, offset: i * 10, length: 10 })) |
| 34 | 37 | const transferManager = { |
| 35 | | _fileIndex: { getChunkMap: async () => chunkMap }, |
| 38 | // The engine resolves a 'content:<hash>' path via getChunkMapByHash |
| 39 | // (chunkmap-oid: bee key), and a plain overlay path via getChunkMap. The |
| 40 | // test serves 'content:x', so the mock must expose both. |
| 41 | _fileIndex: { |
| 42 | getChunkMap: async () => chunkMap, |
| 43 | getChunkMapByHash: async () => chunkMap |
| 44 | }, |
| 36 | 45 | readChunk: (p, off, len) => b4a.alloc(len) |
| 37 | 46 | } |
| 38 | 47 | return new OverlayProtocol(null, transferManager, { filePaths: new Map([['content:x', '/fake']]) }) |
| … | … | @@ -45,8 +54,8 @@ test('backpressure: seeder paces chunkData by drain instead of flooding', async |
| 45 | 54 | const sent = [] |
| 46 | 55 | let bufferFull = false |
| 47 | 56 | const peer = { |
| 48 | | channel: { closed: false }, |
| 49 | | mux: { get drained () { return !bufferFull }, stream }, |
| 57 | channel: { closed: false, get drained () { return !bufferFull } }, |
| 58 | mux: { stream }, |
| 50 | 59 | // every send fills the buffer and reports backpressure (returns false) |
| 51 | 60 | msgs: { chunkData: { send: (m) => { sent.push(m.index); bufferFull = true; return false } } } |
| 52 | 61 | } |
| … | … | @@ -89,8 +98,8 @@ test('concurrent chunkNeed batches: one pump per peer, all delivered exactly onc |
| 89 | 98 | const sent = [] |
| 90 | 99 | let bufferFull = false |
| 91 | 100 | const peer = { |
| 92 | | channel: { closed: false }, |
| 93 | | mux: { get drained () { return !bufferFull }, stream }, |
| 101 | channel: { closed: false, get drained () { return !bufferFull } }, |
| 102 | mux: { stream }, |
| 94 | 103 | msgs: { chunkData: { send: (m) => { sent.push(m.index); bufferFull = true; return false } } } |
| 95 | 104 | } |
| 96 | 105 | |