| … | … | @@ -96,6 +96,42 @@ test('V2 multi-source: chunks fetched from MULTIPLE peers in parallel', async (t |
| 96 | 96 | 'chunks were pulled from BOTH seeders (multi-source) — peerCount=' + stats.peerCount) |
| 97 | 97 | }) |
| 98 | 98 | |
| 99 | test('V2 re-seed: a fetcher becomes a seeder (multiplication)', async (t) => { |
| 100 | const S1 = await makePeer('seed') |
| 101 | const F = await makePeer('fetcher') |
| 102 | const G = await makePeer('late') |
| 103 | t.teardown(async () => { |
| 104 | for (const ov of [S1, F, G]) { try { await ov.close() } catch {} } |
| 105 | }) |
| 106 | |
| 107 | // S1 holds the file. |
| 108 | const content = crypto.randomBytes(150 * 1024) |
| 109 | const oid = crypto.data(content).toString('hex') |
| 110 | const dir = tmpDir('ov2-seed-files') |
| 111 | const f = path.join(dir, 'x.bin') |
| 112 | fs.writeFileSync(f, content) |
| 113 | await S1.registerFile('/attachments/' + oid, f, { contentHash: oid, size: content.length }) |
| 114 | |
| 115 | // F connects ONLY to S1 and fetches → should re-seed. |
| 116 | const [fa, sa] = makeDuplex() |
| 117 | F.attachProtocol(Protomux.from(fa)); S1.attachProtocol(Protomux.from(sa)) |
| 118 | await new Promise(r => setTimeout(r, 200)) |
| 119 | const got1 = await F.fetchFile(oid) |
| 120 | t.ok(got1 && Buffer.compare(got1.data, content) === 0, 'F fetched the blob from S1') |
| 121 | |
| 122 | // After fetching, F serves it from its own local copy. |
| 123 | const localOnF = await F.fetchFile(oid) |
| 124 | t.ok(localOnF && localOnF.local, 'F re-seeded the blob (now serves it locally)') |
| 125 | |
| 126 | // G connects ONLY to F (never to S1) — it can only succeed if F is now a seeder. |
| 127 | const [gf, fg] = makeDuplex() |
| 128 | G.attachProtocol(Protomux.from(gf)); F.attachProtocol(Protomux.from(fg)) |
| 129 | await new Promise(r => setTimeout(r, 200)) |
| 130 | const got2 = await G.fetchFile(oid) |
| 131 | t.ok(got2 && !got2.local, 'G fetched from a peer (F), not local') |
| 132 | t.is(got2 && Buffer.compare(got2.data, content), 0, 'G got the blob — F successfully re-seeded it') |
| 133 | }) |
| 134 | |
| 99 | 135 | test('V2 facade: fetch by unknown hash with no peers returns null', async (t) => { |
| 100 | 136 | const solo = await makePeer('solo') |
| 101 | 137 | t.teardown(async () => { try { await solo.close() } catch {} }) |