import { test } from "node:test"; import assert from "node:assert/strict"; import { initialSeederMemory, otherSeeders, stepSeeder, type SeederMemory, type SeederPolicy, type SeederTransition, type SwarmObservation, } from "./lastResortSeeder"; // Run with: pnpm --filter yt-dlp-transcript-common exec tsx --test lib/lastResortSeeder.test.ts // // The peer-of-last-resort state machine against a fake scrape source: a // scripted swarm, polled every POLL seconds, with the seeder's own // announce state fed back in (counted in `complete` while it seeds). const POLL = 30; const POLICY: SeederPolicy = { standbyAfterSeconds: 120, resumeAfterSeconds: 300 }; type Tick = { // Other seeders and leechers the tracker knows (the seeder itself is added // by the harness while it seeds). null = the scrape failed. others: number | null; leechers?: number; connectedSeeds?: number; connectedLeechers?: number; }; // Run a script; return each poll's state and every transition with its time. function run(script: Tick[], policy = POLICY, start: SeederMemory = initialSeederMemory(0)) { let mem = start; const states: string[] = []; const transitions: (SeederTransition & { at: number })[] = []; script.forEach((t, i) => { const at = (i + 1) * POLL * 1000; const self = mem.state === "seeding"; const o: SwarmObservation = { at, scrape: t.others === null ? null : { complete: t.others + (self ? 1 : 0), incomplete: t.leechers ?? 0 }, selfCounted: self, connectedSeeds: t.connectedSeeds ?? 0, connectedLeechers: t.connectedLeechers ?? 0, }; const step = stepSeeder(mem, o, policy); if (step.transition) transitions.push({ ...step.transition, at: at / 1000 }); mem = step.memory; states.push(mem.state === "seeding" ? "S" : "-"); }); return { states: states.join(""), transitions, memory: mem }; } const rep = (n: number, t: Tick) => Array.from({ length: n }, () => t); test("alone: it seeds and stays seeding", () => { const r = run(rep(20, { others: 0, leechers: 1 })); assert.equal(r.states, "S".repeat(20)); assert.deepEqual(r.transitions, []); }); test("others present for standbyAfterSeconds → standby, not before", () => { // 120 s at a 30 s poll: the run starts at the 1st poll (t=30) and is long // enough at the 5th (t=150). const r = run(rep(6, { others: 1 })); assert.equal(r.states, "SSSS--"); assert.equal(r.transitions.length, 1); assert.equal(r.transitions[0].to, "standby"); assert.equal(r.transitions[0].at, 150); assert.match(r.transitions[0].reason, /1 other seeder\(s\) on every poll for 120s/); }); test("hysteresis on the standby edge: one poll with no other seeder restarts the clock", () => { const r = run([...rep(3, { others: 1 }), { others: 0 }, ...rep(4, { others: 1 }), ...rep(2, { others: 1 })]); // The clock restarts at poll 5 (t=150) and is long enough at poll 9 (t=270). assert.equal(r.transitions[0]?.at, 270); }); test("its own announce is not another seeder", () => { // The scrape counts it (complete = 1) while it seeds: that is no one else. const r = run(rep(10, { others: 0 })); assert.equal(r.states, "S".repeat(10)); }); test("standby → seeding at once when no other seeder remains and a leecher waits", () => { const r = run([...rep(5, { others: 1 }), { others: 1 }, { others: 0, leechers: 1 }]); assert.equal(r.transitions.map((t) => t.to).join(","), "standby,seeding"); assert.equal(r.transitions[1].at, 210); assert.match(r.transitions[1].reason, /no other seeder and 1 leecher\(s\) waiting/); }); test("a connected leecher with no other source counts as waiting", () => { const start: SeederMemory = { state: "standby", since: 0, othersSince: null, aloneSince: null, silentSince: null }; const r = run([{ others: 0, leechers: 0, connectedLeechers: 1 }], POLICY, start); assert.equal(r.transitions[0]?.to, "seeding"); }); test("hysteresis on the resume edge: alone with nobody waiting resumes after resumeAfterSeconds", () => { const start: SeederMemory = { state: "standby", since: 0, othersSince: null, aloneSince: null, silentSince: null }; // 300 s at 30 s: the run starts at t=30 and is long enough at t=330 (poll 11). const r = run(rep(12, { others: 0, leechers: 0 }), POLICY, start); assert.equal(r.transitions.length, 1); assert.equal(r.transitions[0].at, 330); assert.match(r.transitions[0].reason, /no other seeder on every poll for 300s/); // A poll that sees a seeder again restarts that clock. const r2 = run([...rep(5, { others: 0 }), { others: 1 }, ...rep(5, { others: 0 })], POLICY, start); assert.deepEqual(r2.transitions, []); }); test("a flapping swarm does not flap the seeder", () => { // Another seeder that comes and goes every other poll, with a leecher. const flap = Array.from({ length: 40 }, (_, i) => ({ others: i % 2 === 0 ? 1 : 0, leechers: 1 })); const r = run(flap); // Never two polls in a row with others → the standby clock never fills. assert.deepEqual(r.transitions, []); assert.equal(r.states, "S".repeat(40)); }); test("after a resume the standby edge needs its whole window again", () => { const r = run([...rep(5, { others: 1 }), { others: 0, leechers: 1 }, ...rep(5, { others: 1 })]); // standby at t=150, seeding at t=180, standby again only at t=180+150=330. assert.deepEqual(r.transitions.map((t) => [t.to, t.at]), [["standby", 150], ["seeding", 180], ["standby", 330]]); }); test("a failed scrape decides nothing while seeding", () => { const r = run([...rep(3, { others: 1 }), { others: null }, ...rep(5, { others: 1 })]); // The silent poll breaks the others-run: standby needs 120 s from t=150. assert.deepEqual(r.transitions.map((t) => t.at), [270]); }); test("in standby, trackers silent for resumeAfterSeconds → resume (assume no other seeder)", () => { const start: SeederMemory = { state: "standby", since: 0, othersSince: null, aloneSince: null, silentSince: null }; const r = run(rep(11, { others: null }), POLICY, start); assert.equal(r.transitions[0]?.to, "seeding"); assert.equal(r.transitions[0]?.at, 330); assert.match(r.transitions[0]?.reason ?? "", /no tracker answered/); }); test("a connected seed is another source even when the scrape misses it", () => { assert.equal( otherSeeders({ at: 0, scrape: { complete: 1, incomplete: 0 }, selfCounted: true, connectedSeeds: 2, connectedLeechers: 0 }), 2, ); assert.equal(otherSeeders({ at: 0, scrape: null, selfCounted: false, connectedSeeds: 0, connectedLeechers: 3 }), null); assert.equal(otherSeeders({ at: 0, scrape: null, selfCounted: false, connectedSeeds: 1, connectedLeechers: 0 }), 1); assert.equal(otherSeeders({ at: 0, scrape: { complete: 0, incomplete: 0 }, selfCounted: true, connectedSeeds: 0, connectedLeechers: 0 }), 0); const r = run(rep(6, { others: 0, connectedSeeds: 1 })); assert.equal(r.transitions[0]?.to, "standby"); }); test("the rollout's check: a second browser seeds → standby; it closes → resume", () => { // Step 7 of the plan's rollout, as a script: a viewer finishes and seeds, // the home seeder stands by; the viewer leaves and another viewer arrives. const r = run([ ...rep(2, { others: 0, leechers: 1, connectedLeechers: 1 }), ...rep(6, { others: 1, leechers: 0, connectedSeeds: 1 }), ...rep(2, { others: 1 }), { others: 0, leechers: 1 }, ]); assert.deepEqual(r.transitions.map((t) => t.to), ["standby", "seeding"]); assert.equal(r.memory.state, "seeding"); });