Files
auto-login/tests/utils.test.js
T

248 lines
8.9 KiB
JavaScript
Raw Normal View History

2026-09-14 13:45:17 +08:00
// tests/utils.test.js
// 测试 utils.js 中的纯函数:isDomainMatch(URL 路径匹配)和 escapeHtml(HTML 转义)
// 运行:node --test tests/utils.test.js
const { describe, it } = require("node:test");
const assert = require("node:assert/strict");
const { isDomainMatch, escapeHtml, toMatchPatterns, pickFrameWithForm } = require("../dist/utils.js");
2026-09-14 13:45:17 +08:00
describe("isDomainMatch - 纯 hostname 匹配", () => {
it("精确域名匹配", () => {
assert.equal(isDomainMatch("https://example.com/login", "example.com"), true);
assert.equal(isDomainMatch("https://example.com/", "example.com"), true);
assert.equal(isDomainMatch("https://other.com/", "example.com"), false);
});
it("子域名通配符 *.example.com", () => {
assert.equal(isDomainMatch("https://oa.example.com/", "*.example.com"), true);
assert.equal(isDomainMatch("https://a.b.example.com/", "*.example.com"), true);
assert.equal(isDomainMatch("https://example.com/", "*.example.com"), true); // 根域也匹配
assert.equal(isDomainMatch("https://notexample.com/", "*.example.com"), false);
});
it("任意位置通配符", () => {
assert.equal(isDomainMatch("https://api.dev.example.com/", "api.*.example.com"), true);
assert.equal(isDomainMatch("https://api.prod.example.com/", "api.*.example.com"), true);
assert.equal(isDomainMatch("https://web.dev.example.com/", "api.*.example.com"), false);
});
it("端口和协议不影响匹配", () => {
assert.equal(isDomainMatch("http://example.com:8080/path", "example.com"), true);
assert.equal(isDomainMatch("https://example.com:443/", "example.com"), true);
});
it("非法 URL 返回 false", () => {
assert.equal(isDomainMatch("not-a-url", "example.com"), false);
assert.equal(isDomainMatch("", "example.com"), false);
assert.equal(isDomainMatch("https://example.com/", ""), false);
});
});
describe("isDomainMatch - hostname + path 匹配", () => {
it("精确路径匹配", () => {
assert.equal(isDomainMatch("https://oa.com/login", "oa.com/login"), true);
assert.equal(isDomainMatch("https://oa.com/admin/login", "oa.com/login"), false);
assert.equal(isDomainMatch("https://oa.com/", "oa.com/login"), false);
});
it("路径前缀通配 login/*", () => {
assert.equal(isDomainMatch("https://oa.com/login", "oa.com/login/*"), true);
assert.equal(isDomainMatch("https://oa.com/login/step2", "oa.com/login/*"), true);
assert.equal(isDomainMatch("https://oa.com/login/sub/deep", "oa.com/login/*"), true);
assert.equal(isDomainMatch("https://oa.com/logins", "oa.com/login/*"), false);
assert.equal(isDomainMatch("https://oa.com/admin", "oa.com/login/*"), false);
});
it("路径全通配 *", () => {
assert.equal(isDomainMatch("https://oa.com/anything", "oa.com/*"), true);
assert.equal(isDomainMatch("https://oa.com/", "oa.com/*"), true);
});
it("仅 hostname + 空路径匹配该域名所有页面", () => {
assert.equal(isDomainMatch("https://oa.com/any/path", "oa.com/"), true);
assert.equal(isDomainMatch("https://oa.com/", "oa.com/"), true);
});
it("路径内任意位置通配", () => {
assert.equal(isDomainMatch("https://site.com/a/123/b", "site.com/a/*/b"), true);
assert.equal(isDomainMatch("https://site.com/a/xyz/b", "site.com/a/*/b"), true);
assert.equal(isDomainMatch("https://site.com/a/123/c", "site.com/a/*/b"), false);
});
it("path 匹配与子域名通配组合", () => {
assert.equal(isDomainMatch("https://api.dev.com/login", "*.dev.com/login"), true);
assert.equal(isDomainMatch("https://web.dev.com/admin", "*.dev.com/login"), false);
assert.equal(isDomainMatch("https://api.dev.com/login/step2", "*.dev.com/login/*"), true);
});
it("带 query 和 hash 的 URL 不影响 path 匹配", () => {
assert.equal(isDomainMatch("https://oa.com/login?next=/home", "oa.com/login"), true);
assert.equal(isDomainMatch("https://oa.com/login#section", "oa.com/login"), true);
});
});
2026-09-17 23:00:47 +08:00
describe("toMatchPatterns - 配置域名转 match pattern", () => {
it("纯域名同时生成 http / https 两条", () => {
assert.deepEqual(toMatchPatterns("example.com"), ["http://example.com/*", "https://example.com/*"]);
2026-09-17 23:00:47 +08:00
});
it("带协议前缀的配置会被剥离", () => {
assert.deepEqual(toMatchPatterns("https://example.com"), [
"http://example.com/*",
"https://example.com/*",
]);
});
it("子域名通配符 *.example.com 保持原样", () => {
assert.deepEqual(toMatchPatterns("*.example.com"), ["http://*.example.com/*", "https://*.example.com/*"]);
2026-09-17 23:00:47 +08:00
});
it("路径规则按前缀截断到第一个 *", () => {
assert.deepEqual(toMatchPatterns("oa.com/login/*"), ["http://oa.com/login/*", "https://oa.com/login/*"]);
2026-09-17 23:00:47 +08:00
// 中间通配无法表达,截断成前缀(放宽注入范围,精度仍由 isDomainMatch 把关)
assert.deepEqual(toMatchPatterns("site.com/a/*/b"), ["http://site.com/a/*", "https://site.com/a/*"]);
2026-09-17 23:00:47 +08:00
});
it("无通配的路径补上结尾 *", () => {
assert.deepEqual(toMatchPatterns("oa.com/admin"), ["http://oa.com/admin*", "https://oa.com/admin*"]);
2026-09-17 23:00:47 +08:00
});
it("去掉端口(match pattern 不支持端口)", () => {
assert.deepEqual(toMatchPatterns("example.com:8080/app"), [
"http://example.com/app*",
"https://example.com/app*",
]);
});
it("中文域名转成 punycode", () => {
assert.deepEqual(toMatchPatterns("中文域名.中国"), [
"http://xn--fiq06l2rdsvs.xn--fiqs8s/*",
"https://xn--fiq06l2rdsvs.xn--fiqs8s/*",
]);
});
it("query / hash 会被忽略", () => {
assert.deepEqual(toMatchPatterns("oa.com/login?next=/home#x"), [
"http://oa.com/login*",
"https://oa.com/login*",
]);
});
it("无法表达的规则返回空数组", () => {
assert.deepEqual(toMatchPatterns(""), []);
assert.deepEqual(toMatchPatterns(null), []);
assert.deepEqual(toMatchPatterns("*"), []); // 裸通配
assert.deepEqual(toMatchPatterns("a.*.b.com"), []); // 中间通配的 host
});
});
describe("pickFrameWithForm - 从多帧结果里挑出有表单的帧", () => {
it("只有一个帧有表单时返回该帧", () => {
assert.equal(
pickFrameWithForm([
{ frameId: 0, result: 0 },
{ frameId: 5, result: 1 },
]),
5,
);
});
it("顶层帧有表单时优先顶层帧", () => {
assert.equal(
pickFrameWithForm([
{ frameId: 0, result: 1 },
{ frameId: 3, result: 2 },
]),
0,
);
});
it("多个子帧都有表单时取 frameId 最小的", () => {
assert.equal(
pickFrameWithForm([
{ frameId: 7, result: 1 },
{ frameId: 3, result: 1 },
]),
3,
);
});
it("都没有表单时返回 null", () => {
assert.equal(
pickFrameWithForm([
{ frameId: 0, result: 0 },
{ frameId: 2, result: 0 },
]),
null,
);
assert.equal(pickFrameWithForm([]), null);
assert.equal(pickFrameWithForm(undefined), null);
});
it("缺少 frameId 时按顶层帧处理", () => {
assert.equal(pickFrameWithForm([{ result: 1 }]), 0);
});
});
describe("debugLog - 调试日志开关", () => {
it("默认静默,打开开关后才输出", async () => {
const logs = [];
const originalLog = console.log;
console.log = (...args) => logs.push(args.join(" "));
const loadUtils = () => {
delete require.cache[require.resolve("../dist/utils.js")];
return require("../dist/utils.js");
};
try {
delete global.chrome;
let U = loadUtils();
assert.equal(await U.initDebugLog(), false);
U.debugLog("默认不该出现");
assert.equal(logs.filter((l) => l.includes("默认不该出现")).length, 0);
global.chrome = { storage: { local: { get: async () => ({ debugLog: true }) } } };
U = loadUtils();
assert.equal(await U.initDebugLog(), true);
U.debugLog("开启后应出现");
assert.equal(logs.filter((l) => l.includes("开启后应出现")).length, 1);
} finally {
console.log = originalLog;
delete global.chrome;
loadUtils();
}
2026-09-17 23:00:47 +08:00
});
});
2026-09-14 13:45:17 +08:00
describe("escapeHtml", () => {
it("转义 5 个特殊字符", () => {
assert.equal(escapeHtml("<"), "&lt;");
assert.equal(escapeHtml(">"), "&gt;");
assert.equal(escapeHtml('"'), "&quot;");
assert.equal(escapeHtml("'"), "&#39;");
assert.equal(escapeHtml("&"), "&amp;");
});
it("组合转义", () => {
assert.equal(
escapeHtml('<a href="x" onclick="alert(\'hi\')">text & more</a>'),
"&lt;a href=&quot;x&quot; onclick=&quot;alert(&#39;hi&#39;)&quot;&gt;text &amp; more&lt;/a&gt;",
2026-09-14 13:45:17 +08:00
);
});
it("null 和 undefined 返回空串", () => {
assert.equal(escapeHtml(null), "");
assert.equal(escapeHtml(undefined), "");
});
it("数字会被转成字符串", () => {
assert.equal(escapeHtml(123), "123");
});
it("普通文本不变", () => {
assert.equal(escapeHtml("hello world"), "hello world");
assert.equal(escapeHtml("中文测试123"), "中文测试123");
});
});