Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
/**
* Integration suite for `HybridDiscoveryManager`.
*
* Per the spec (Phase 6 of the proposal): exercises the orchestrator
* against real `DiscoveryStrategy` implementations (the in-package
* `TestStrategy` real fixture under `tests/setup/test-strategy.ts`),
* NOT mocks. NO `vi.mock` / `jest.mock` / `__mocks__` anywhere in this
* suite. The five shipped Wave-2 sibling discovery packages are
* dependency-graph imported via `dep-graph-siblings.test.ts` (separate
* file) to prove they are reachable as optional peer-deps — but the
* orchestrator-level behaviour assertions in THIS file run against the
* faster, deterministic `TestStrategy` so the suite is hermetic.
*
* Scenarios covered:
* 1. Register all five Wave-2 sibling IDs + verify peer dedup with
* `PeerSource` correctness.
* 2. Degrade one strategy and verify fallback to next-healthiest.
* 3. Lifecycle (start all → stop → restart) idempotency.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import { HybridDiscoveryManager } from "../src/index.js";
import { makePeer, TestStrategy } from "./setup/test-strategy.js";
describe("HybridDiscoveryManager — integration against real strategies", () => {
it("registers all five Wave-2 sibling ids and dedupes with PeerSource provenance", {
timeout: 10_000,
}, async () => {
// Shared peer "alice" is observed by 3 strategies; "bob" by 2;
// "charlie" by 1; each strategy also contributes a unique peer.
const sharedAlice = makePeer("alice", "shared");
const sharedBob = makePeer("bob", "shared");
const peerJs = new TestStrategy({
name: "peerjs",
peers: [sharedAlice, makePeer("uniq-peerjs", "peerjs")],
});
const trystero = new TestStrategy({
name: "trystero",
peers: [
sharedAlice,
sharedBob,
makePeer("uniq-trystero", "trystero"),
],
});
const crdt = new TestStrategy({
name: "crdt",
peers: [sharedAlice, makePeer("uniq-crdt", "crdt")],
});
const webtorrent = new TestStrategy({
name: "webtorrent",
peers: [sharedBob, makePeer("uniq-webtorrent", "webtorrent")],
});
const gundb = new TestStrategy({
name: "gundb",
peers: [
makePeer("charlie", "gundb"),
makePeer("uniq-gundb", "gundb"),
],
});
const m = new HybridDiscoveryManager({
strategy: "hybrid",
strategies: [
{ id: "peerjs", priority: "primary", strategy: peerJs },
{ id: "trystero", priority: "primary", strategy: trystero },
{ id: "crdt", priority: "fallback", strategy: crdt },
{
id: "webtorrent",
priority: "fallback",
strategy: webtorrent,
},
{ id: "gundb", priority: "backup", strategy: gundb },
],
});
await m.start();
const peers = await m.discover(2000);
// Unique peer ids: alice, bob, charlie, uniq-peerjs, uniq-trystero,
// uniq-crdt, uniq-webtorrent, uniq-gundb = 8.
expect(peers).toHaveLength(8);
const status = m.getStatus();
// PeerSource provenance: alice → 3 strategies; bob → 2; charlie → 1
// Cross-check via the per-strategy peer counts.
expect(status.peersByStrategy.peerjs).toBe(2);
expect(status.peersByStrategy.trystero).toBe(3);
expect(status.peersByStrategy.crdt).toBe(2);
expect(status.peersByStrategy.webtorrent).toBe(2);
expect(status.peersByStrategy.gundb).toBe(2);
expect(status.totalPeers).toBe(8);
expect(status.activeStrategies).toEqual([
"peerjs",
"trystero",
"crdt",
"webtorrent",
"gundb",
]);
await m.stop();
});
it("degrades one strategy and falls back to the next healthiest", {
timeout: 10_000,
}, async () => {
const primary = new TestStrategy({
name: "peerjs",
peers: [makePeer("a", "peerjs")],
});
// Initially-failing factory that the fallback path will retry.
let factoryAttempts = 0;
const m = new HybridDiscoveryManager({
strategy: "hybrid",
autoFallback: true,
strategies: [
{ id: "peerjs", priority: "primary", strategy: primary },
{
id: "trystero",
priority: "fallback",
factory: async () => {
factoryAttempts += 1;
if (factoryAttempts === 1) {
// Simulate "signaling broken" on first attempt;
// the orchestrator records UNHEALTHY and treats
// it as dormant. NO mocks.
throw new Error("signaling unreachable");
}
return new TestStrategy({
name: "trystero",
peers: [makePeer("b", "trystero")],
});
},
},
],
});
await m.start();
// Engineered degradation: cut the primary's signal. NO mocks.
primary.setAvailable(false);
// Drive the health-check directly so the suite stays under 10 s.
await (
m as unknown as {
strategyManager: { runHealthCheck(): Promise<void> };
}
).strategyManager.runHealthCheck();
const status = m.getStatus();
const trysteroHealth = status.strategies.find(
(s) => s.id === "trystero"
);
expect(trysteroHealth?.active).toBe(true);
expect(factoryAttempts).toBe(2);
await m.stop();
});
it("lifecycle: start → stop → restart is idempotent + safe", {
timeout: 10_000,
}, async () => {
const s1 = new TestStrategy({
name: "peerjs",
peers: [makePeer("a", "peerjs")],
});
const s2 = new TestStrategy({
name: "trystero",
peers: [makePeer("b", "trystero")],
});
const m = new HybridDiscoveryManager({
strategy: "hybrid",
strategies: [
{ id: "peerjs", priority: "primary", strategy: s1 },
{ id: "trystero", priority: "fallback", strategy: s2 },
],
});
await m.start();
let peers = await m.discover(1000);
expect(peers).toHaveLength(2);
await m.stop();
expect(await m.isAvailable()).toBe(false);
// Restart — verify everything is functional again.
await m.start();
peers = await m.discover(1000);
expect(peers).toHaveLength(2);
expect(s1.startedCount).toBe(2);
expect(s2.startedCount).toBe(2);
await m.stop();
});
it("shipped Wave-2 sibling packages are importable as optional peer-deps", {
timeout: 10_000,
}, async () => {
// Pure dep-graph reachability check — verifies that the five
// sibling packages are present in the workspace and that the
// hybrid orchestrator can dynamically load them via the factory
// path. We import the package's class symbols and assert they
// exist; we do NOT instantiate (each has transport-specific
// construction requirements that are exercised in the sibling's
// own test suite).
const peerjs = await import("@suaveplan/network-discovery-peerjs");
const crdt = await import("@suaveplan/network-discovery-crdt");
const trystero = await import("@suaveplan/network-discovery-trystero");
const webtorrent = await import(
"@suaveplan/network-discovery-webtorrent"
);
const gundb = await import("@suaveplan/network-discovery-gundb");
expect(peerjs.PeerJSDiscovery).toBeDefined();
expect(crdt.CRDTDiscovery).toBeDefined();
expect(trystero.TrysteroDiscovery).toBeDefined();
expect(webtorrent.WebTorrentDiscovery).toBeDefined();
expect(gundb.GunDBDiscovery).toBeDefined();
});
});