Files
obsidian-livesync/src/apps/cli/commands/p2p.unit.spec.ts
T

138 lines
4.9 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { collectPeers, createPeerConnectionStatsPayload, parseTimeoutSeconds, syncWithPeer } from "./p2p";
function createCore() {
const settings = { P2P_Enabled: true, P2P_AppID: "app-id", P2P_IsHeadless: false };
return {
services: {
setting: { currentSettings: () => settings },
replicator: { getNewReplicator: vi.fn(() => Promise.reject(new Error("must not be called"))) },
},
} as never;
}
function createP2PService() {
const connect = vi.fn(async () => undefined);
const disconnect = vi.fn(async () => undefined);
const pullFromPeer = vi.fn(async () => ({ ok: true }));
const requestPushToPeer = vi.fn(async () => ({ ok: true }));
return {
service: {
transportLifecycle: { isConnected: false, connect, disconnect },
peerDirectory: {
getPeers: () => [{ peerId: "peer-a", name: "Peer A", platform: "test" }],
},
targetedTransfer: {
pullFromPeer,
requestPushToPeer,
synchroniseWithPeer: vi.fn(),
},
diagnostics: { requestStatus: vi.fn(), getPeerConnectionMetrics: vi.fn() },
},
connect,
disconnect,
pullFromPeer,
requestPushToPeer,
};
}
describe("p2p command helpers", () => {
it("accepts non-negative timeout", () => {
expect(parseTimeoutSeconds("0", "p2p-peers")).toBe(0);
expect(parseTimeoutSeconds("2.5", "p2p-sync")).toBe(2.5);
});
it("rejects invalid timeout values", () => {
expect(() => parseTimeoutSeconds("-1", "p2p-peers")).toThrow(
"p2p-peers requires a non-negative timeout in seconds"
);
expect(() => parseTimeoutSeconds("abc", "p2p-sync")).toThrow(
"p2p-sync requires a non-negative timeout in seconds"
);
});
it("collects peers through service views without acquiring a concrete replicator", async () => {
const { service, connect, disconnect } = createP2PService();
await expect(collectPeers(createCore(), service as never, 0)).resolves.toEqual([
{ peerId: "peer-a", name: "Peer A" },
]);
expect(connect).toHaveBeenCalledOnce();
expect(disconnect).toHaveBeenCalledOnce();
});
it("synchronises through the targeted-transfer view", async () => {
const { service, pullFromPeer, requestPushToPeer } = createP2PService();
await expect(syncWithPeer(createCore(), service as never, "peer-a", 0)).resolves.toEqual({
peerId: "peer-a",
name: "Peer A",
});
expect(pullFromPeer).toHaveBeenCalledWith("peer-a", { showNotice: false });
expect(requestPushToPeer).toHaveBeenCalledWith("peer-a");
});
it("preserves the benchmark diagnostics JSONL contract", () => {
expect(
createPeerConnectionStatsPayload(
{ peerId: "peer-a", name: "Peer A" },
{
selectedPairPresent: true,
selectedPairId: "pair-1",
state: "succeeded",
currentRoundTripTime: 0.01,
totalRoundTripTime: 0.1,
requestsSent: 3,
responsesReceived: 3,
packetsDiscardedOnSend: 0,
bytesSent: 100,
bytesReceived: 200,
localCandidate: {
id: "local-1",
candidateType: "host",
protocol: "udp",
relayProtocol: "unknown",
},
remoteCandidate: {
id: "remote-1",
candidateType: "relay",
protocol: "udp",
relayProtocol: "udp",
},
},
"2026-08-27T00:00:00.000Z"
)
).toEqual({
generatedAt: "2026-08-27T00:00:00.000Z",
command: "p2p-sync",
peerId: "peer-a",
peerName: "Peer A",
candidatePathCollected: true,
selectedPath: "host<->relay",
selectedPair: {
id: "pair-1",
state: "succeeded",
currentRoundTripTime: 0.01,
totalRoundTripTime: 0.1,
requestsSent: 3,
responsesReceived: 3,
packetsDiscardedOnSend: 0,
bytesSent: 100,
bytesReceived: 200,
},
localCandidate: {
id: "local-1",
candidateType: "host",
protocol: "udp",
relayProtocol: "unknown",
},
remoteCandidate: {
id: "remote-1",
candidateType: "relay",
protocol: "udp",
relayProtocol: "udp",
},
});
});
});