176 lines
6.3 KiB
JavaScript
176 lines
6.3 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import { normalizeAnalytics } from "../src/analytics.mjs";
|
|
import { detectUiLanguage, getMessages, translateFact } from "../src/i18n.mjs";
|
|
|
|
describe("ui language", () => {
|
|
it("uses Chinese when the browser language list contains zh", () => {
|
|
assert.equal(detectUiLanguage(["zh-CN", "en-US"]), "zh");
|
|
assert.match(getMessages("zh").requestDescription, /IP 数据库/);
|
|
});
|
|
|
|
it("falls back to English for non-Chinese browsers", () => {
|
|
assert.equal(detectUiLanguage(["fr-FR", "en-US"]), "en");
|
|
assert.match(getMessages("en").requestDescription, /IP database/);
|
|
});
|
|
|
|
it("respects browser language priority order", () => {
|
|
assert.equal(detectUiLanguage(["en-US", "zh-CN"]), "en");
|
|
});
|
|
|
|
it("includes English copy for network diagnostics and regional signals", () => {
|
|
const copy = getMessages("en");
|
|
|
|
assert.equal(copy.network.title, "Network exposure check");
|
|
assert.equal(copy.network.serverTitle, "Address seen by the server");
|
|
assert.equal(copy.network.metrics.public, "Public");
|
|
assert.equal(copy.network.candidateCategories.private, "Private/local");
|
|
assert.equal(copy.signalCatalog.timezone.name, "System timezone");
|
|
assert.match(copy.signalCatalog.fonts.detail, /Canvas/);
|
|
});
|
|
|
|
it("localizes analytics bucket labels for public dashboard keys", () => {
|
|
const zh = getMessages("zh");
|
|
|
|
assert.equal(zh.categoryDistribution, "画像线索类别");
|
|
assert.equal(zh.analyticsBucketLabels.identity, "身份与区域");
|
|
assert.equal(zh.analyticsBucketLabels.device, "设备环境");
|
|
assert.equal(zh.analyticsBucketLabels.privacy, "隐私与存储");
|
|
assert.equal(zh.analyticsBucketLabels.preferences, "显示偏好");
|
|
assert.equal(zh.analyticsBucketLabels.automation, "自动化痕迹");
|
|
assert.equal(zh.analyticsBucketLabels.graphics, "图形栈");
|
|
assert.equal(zh.analyticsBucketLabels.audio, "音频栈");
|
|
assert.equal(zh.analyticsBucketLabels["profile-local-summary"], "本地画像摘要");
|
|
assert.equal(zh.analyticsBucketLabels["network-server"], "服务端网络摘要");
|
|
});
|
|
|
|
it("translates common staged facts for the selected language", () => {
|
|
const fact = translateFact(
|
|
{
|
|
id: "timezone",
|
|
label: "系统时区",
|
|
value: "Asia/Shanghai",
|
|
inference: "可推断所在时区和常见地区。",
|
|
sensitivity: "medium",
|
|
},
|
|
getMessages("en"),
|
|
);
|
|
|
|
assert.equal(fact.label, "System timezone");
|
|
assert.equal(fact.inference, "Can reveal timezone and likely region.");
|
|
});
|
|
|
|
it("translates common fact values and units", () => {
|
|
const fact = translateFact(
|
|
{
|
|
id: "touch",
|
|
label: "触控能力",
|
|
value: "0 点触控",
|
|
inference: "可推断是否为移动或触屏设备。",
|
|
sensitivity: "low",
|
|
},
|
|
getMessages("en"),
|
|
);
|
|
|
|
assert.equal(fact.value, "0 touch points");
|
|
});
|
|
|
|
it("translates permission-state facts without Chinese fallbacks", () => {
|
|
const fact = translateFact(
|
|
{
|
|
id: "permission-state-geolocation",
|
|
label: "定位权限状态",
|
|
value: "prompt",
|
|
inference: "只查询状态,不会弹出授权请求。",
|
|
sensitivity: "low",
|
|
},
|
|
getMessages("en"),
|
|
);
|
|
|
|
assert.equal(fact.label, "Location permission state");
|
|
assert.equal(fact.value, "Ask every time");
|
|
assert.equal(fact.inference, "This only checks the passive permission state and does not open a permission prompt.");
|
|
});
|
|
|
|
it("translates font-package facts for the staged profile report", () => {
|
|
const fact = translateFact(
|
|
{
|
|
id: "font-packages",
|
|
label: "字体包画像",
|
|
value: "中文/CJK=2 开发字体=1",
|
|
inference: "常见字体包会暴露系统、语言环境和工具链线索。",
|
|
sensitivity: "high",
|
|
},
|
|
getMessages("en"),
|
|
);
|
|
|
|
assert.equal(fact.label, "Font package profile");
|
|
assert.equal(fact.inference, "Installed font families can reveal OS, language environment, and toolchain clues.");
|
|
});
|
|
});
|
|
|
|
describe("analytics normalization", () => {
|
|
it("builds dashboard buckets with percentages", () => {
|
|
const analytics = normalizeAnalytics({
|
|
totalScans: 4,
|
|
averageScore: 51.25,
|
|
bandCounts: { high: 2 },
|
|
localeCounts: [
|
|
{ label: "zh-CN", count: 3 },
|
|
{ label: "en-US", count: 1 },
|
|
],
|
|
timezoneCounts: [{ label: "Asia/Shanghai", count: 2 }],
|
|
timezoneOffsetCounts: [{ label: "UTC+08:00", count: 3 }],
|
|
userAgentCounts: [{ label: "Chrome", count: 4 }],
|
|
scoreBuckets: [
|
|
{ label: "0-20", count: 1 },
|
|
{ label: "61-80", count: 3 },
|
|
],
|
|
stageCounts: [
|
|
{ label: "local", count: 4 },
|
|
{ label: "request", count: 2 },
|
|
],
|
|
categoryCounts: [{ label: "network", count: 6 }],
|
|
sensitivityTotals: {
|
|
low: 10,
|
|
medium: 8,
|
|
high: 4,
|
|
critical: 2,
|
|
},
|
|
networkExposure: {
|
|
webrtcSamples: 4,
|
|
webrtcCandidates: 7,
|
|
webrtcPublic: 2,
|
|
webrtcPrivate: 3,
|
|
webrtcMdns: 2,
|
|
serverSamples: 4,
|
|
serverObserved: 3,
|
|
forwardedFor: 2,
|
|
cfConnectingIp: 1,
|
|
realIpHeader: 1,
|
|
connectionSamples: 3,
|
|
saveData: 1,
|
|
averageRttMs: 86.4,
|
|
},
|
|
connectionTypeCounts: [{ label: "4g", count: 2 }],
|
|
topSignals: [{ id: "timezone", hits: 3 }],
|
|
updatedAt: "2026-07-03T10:00:00Z",
|
|
});
|
|
|
|
assert.equal(analytics.total, 4);
|
|
assert.equal(analytics.highCount, 2);
|
|
assert.equal(analytics.distributions[0].buckets[0].percent, 75);
|
|
assert.equal(analytics.riskBuckets[1].percent, 75);
|
|
assert.equal(analytics.stageBuckets[0].label, "local");
|
|
assert.equal(analytics.categoryBuckets[0].count, 6);
|
|
assert.equal(analytics.sensitivityBuckets[2].label, "high");
|
|
assert.equal(analytics.sensitivityBuckets[2].percent, 17);
|
|
assert.equal(analytics.networkExposure.webrtcPublic, 2);
|
|
assert.equal(analytics.networkExposure.proxyHeaders, 4);
|
|
assert.equal(analytics.networkExposure.averageRttMs, 86.4);
|
|
assert.equal(analytics.connectionTypeBuckets[0].percent, 67);
|
|
assert.equal(analytics.topSignals[0].label, "timezone");
|
|
assert.equal(analytics.topSignals[0].percent, 75);
|
|
});
|
|
});
|