143 lines
5.3 KiB
JavaScript
143 lines
5.3 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import {
|
|
buildNetworkPortrait,
|
|
classifyCandidateAddress,
|
|
createPendingNetworkDiagnostics,
|
|
parseIceCandidateLine,
|
|
redactNetworkDiagnosticsForRecording,
|
|
summarizeIceCandidatesForRecording,
|
|
} from "../src/network.mjs";
|
|
|
|
describe("network diagnostics", () => {
|
|
it("parses and classifies common WebRTC ICE candidate address forms", () => {
|
|
const host = parseIceCandidateLine(
|
|
"candidate:842163049 1 udp 1677729535 192.168.1.5 54400 typ host generation 0 ufrag abc network-cost 999",
|
|
);
|
|
assert.equal(host.address, "192.168.1.5");
|
|
assert.equal(host.type, "host");
|
|
assert.equal(host.category, "private");
|
|
|
|
const mdns = parseIceCandidateLine("candidate:1 1 udp 2122252543 a1b2c3d4.local 5353 typ host");
|
|
assert.equal(mdns.address, "a1b2c3d4.local");
|
|
assert.equal(mdns.category, "mdns");
|
|
|
|
const reflexive = parseIceCandidateLine(
|
|
"candidate:2 1 udp 1686052607 203.0.113.10 60000 typ srflx raddr 0.0.0.0 rport 9",
|
|
);
|
|
assert.equal(reflexive.address, "203.0.113.10");
|
|
assert.equal(reflexive.type, "srflx");
|
|
assert.equal(reflexive.category, "public");
|
|
|
|
assert.equal(classifyCandidateAddress("fd00::1"), "private");
|
|
assert.equal(classifyCandidateAddress("2001:4860:4860::8888"), "public");
|
|
});
|
|
|
|
it("summarizes WebRTC data without retaining exact network addresses", () => {
|
|
const candidates = [
|
|
parseIceCandidateLine(
|
|
"candidate:842163049 1 udp 1677729535 192.168.1.5 54400 typ host generation 0",
|
|
),
|
|
parseIceCandidateLine("candidate:1 1 udp 2122252543 a1b2c3d4.local 5353 typ host"),
|
|
parseIceCandidateLine("candidate:2 1 udp 1686052607 203.0.113.10 60000 typ srflx"),
|
|
];
|
|
const summary = summarizeIceCandidatesForRecording(candidates);
|
|
|
|
assert.deepEqual(summary, {
|
|
total: 3,
|
|
public: 1,
|
|
private: 1,
|
|
mdns: 1,
|
|
masked: 0,
|
|
host: 2,
|
|
srflx: 1,
|
|
relay: 0,
|
|
prflx: 0,
|
|
});
|
|
|
|
const signals = redactNetworkDiagnosticsForRecording({
|
|
webrtc: { candidates },
|
|
server: {
|
|
observedIp: "198.51.100.24",
|
|
forwardedFor: "203.0.113.42",
|
|
cfConnectingIp: null,
|
|
realIpHeader: null,
|
|
},
|
|
connection: { effectiveType: "4g", downlink: 10, rtt: 40, saveData: false },
|
|
});
|
|
const raw = signals.map((signal) => signal.raw).join(" ");
|
|
|
|
assert.equal(signals.every((signal) => signal.raw.length <= 160), true);
|
|
assert.match(raw, /webrtc total=3 public=1 private=1 mdns=1/);
|
|
assert.match(raw, /server observed=1 forwarded=1 cf=0 real=0/);
|
|
assert.doesNotMatch(raw, /\b(?:\d{1,3}\.){3}\d{1,3}\b/);
|
|
assert.doesNotMatch(raw, /a1b2c3d4\.local/);
|
|
});
|
|
|
|
it("builds an explainable network portrait from server and WebRTC evidence", () => {
|
|
const portrait = buildNetworkPortrait(
|
|
{
|
|
status: "done",
|
|
server: {
|
|
observedIp: "198.51.100.24",
|
|
forwardedFor: "203.0.113.42",
|
|
cfConnectingIp: "198.51.100.24",
|
|
cfIpCountry: "US",
|
|
geoIp: {
|
|
city: {
|
|
available: true,
|
|
found: true,
|
|
countryCode: "US",
|
|
region: "California",
|
|
city: "Los Angeles",
|
|
latitude: 34.05,
|
|
longitude: -118.24,
|
|
timeZone: "America/Los_Angeles",
|
|
accuracyRadiusKm: 20,
|
|
},
|
|
asn: {
|
|
available: true,
|
|
found: true,
|
|
asn: 15169,
|
|
organization: "Google LLC",
|
|
isp: "Google LLC",
|
|
},
|
|
},
|
|
},
|
|
webrtc: { summary: { total: 3, public: 1, private: 1, mdns: 1, srflx: 1 } },
|
|
connection: { supported: true, effectiveType: "4g", rtt: 40, downlink: 10, saveData: false },
|
|
},
|
|
[
|
|
{ id: "timezone", label: "系统时区", value: "America/Los_Angeles" },
|
|
{ id: "language", label: "浏览器语言", value: "en-US, zh-CN" },
|
|
],
|
|
);
|
|
|
|
assert.equal(portrait.level, "high");
|
|
assert.ok(portrait.confidence >= 80);
|
|
assert.match(portrait.headline, /网络出口/);
|
|
assert.equal(portrait.rows.some((row) => row.label === "Cloudflare 国家"), true);
|
|
assert.equal(portrait.rows.some((row) => row.label === "IP 数据库位置" && row.value.includes("Los Angeles")), true);
|
|
assert.equal(portrait.rows.some((row) => row.label === "ASN / ISP" && row.value.includes("Google LLC")), true);
|
|
assert.equal(portrait.rows.some((row) => row.label === "WebRTC 暴露"), true);
|
|
assert.equal(portrait.evidence.some((item) => item.includes("America/Los_Angeles")), true);
|
|
assert.equal(portrait.limitations.some((item) => item.includes("不能等同于精确住址")), true);
|
|
});
|
|
|
|
it("can expose server diagnostics while WebRTC is still collecting", () => {
|
|
const diagnostics = createPendingNetworkDiagnostics({
|
|
observedIp: "127.0.0.1",
|
|
geoIp: {
|
|
city: { available: true, found: false },
|
|
asn: { available: true, found: false },
|
|
},
|
|
});
|
|
|
|
assert.equal(diagnostics.status, "done");
|
|
assert.equal(diagnostics.server.observedIp, "127.0.0.1");
|
|
assert.equal(diagnostics.webrtc.pending, true);
|
|
assert.equal(diagnostics.webrtc.summary.total, 0);
|
|
assert.equal(diagnostics.connection.supported, false);
|
|
});
|
|
});
|