Files
auto-login/tests/permissions.test.js
陈银军 1b0c3c012c feat: 按域名授权、iframe 多帧填充、自动提交限流修正,并补齐工程规范与文档
安全与权限:
- 站点访问权限改为 optional_host_permissions,保存配置时逐域名授权;移除 host_permissions 与 tabs,安装提示不再出现全站数据访问与浏览记录
- 主口令不再写入 storage.session,改用 IndexedDB 中不可导出的 CryptoKey 句柄恢复解锁,锁定即丢弃
- content script 注入范围只注册已授权域名,并随权限变化即时收敛

自动登录:
- 支持 iframe 内的登录表单:脚本注入所有帧,逐帧按自身地址匹配配置
- 手动填充改为逐帧探测,定向发送到真正含密码框的帧
- 限流修正:只在真正触发提交后计数;判定登录成功后立即清零;达到上限时页面给出可见提示;重置判断只看启用中的配置
- SPA 重试改为 DOM 变更门控 + 退避,并单独监听已发现的 Shadow Root

工程化与文档:
- 引入 ESLint(扁平配置)与 Prettier,CI 增加 lint 与 format:check
- 信息类日志改为 debugLog(默认静默,chrome.storage.local.debugLog 开关)
- 测试 123 → 128 用例(新增权限、iframe、限流相关用例)
- README / PRIVACY / CHANGELOG 同步
2026-09-18 00:05:20 +08:00

150 lines
5.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// tests/permissions.test.js
// 测试 permissions.js:可选主机权限的授权状态判断与申请逻辑
// 运行:node --test tests/permissions.test.js
const { describe, it, beforeEach } = require("node:test");
const assert = require("node:assert/strict");
const { toMatchPatterns } = require("../dist/utils.js");
/** 构造一个假的 chrome.permissions,granted 集合可观察 */
function createFakePermissions(granted = []) {
const state = {
granted: new Set(granted),
requestCalls: [],
containsCalls: 0,
throwOnContains: false,
};
return {
state,
api: {
contains: async ({ origins }) => {
state.containsCalls++;
if (state.throwOnContains) throw new Error("boom");
return origins.every((o) => state.granted.has(o));
},
request: async ({ origins }) => {
state.requestCalls.push(origins);
origins.forEach((o) => state.granted.add(o));
return true;
},
},
};
}
function loadPerms(permissionsApi) {
global.toMatchPatterns = toMatchPatterns; // permissions.js 会延迟读取该全局函数
global.chrome = permissionsApi === null ? {} : { permissions: permissionsApi };
delete require.cache[require.resolve("../dist/permissions.js")];
return require("../dist/permissions.js");
}
describe("permissions - patternsForDomain", () => {
beforeEach(() => {
global.toMatchPatterns = toMatchPatterns;
});
it("返回 http / https 两条模式", () => {
const P = loadPerms(createFakePermissions().api);
assert.deepEqual(P.patternsForDomain("example.com"), ["http://example.com/*", "https://example.com/*"]);
});
it("通配符域名同样可转成可申请的 origin", () => {
const P = loadPerms(createFakePermissions().api);
assert.deepEqual(P.patternsForDomain("*.example.com"), [
"http://*.example.com/*",
"https://*.example.com/*",
]);
});
it("无法表达的规则返回空数组", () => {
const P = loadPerms(createFakePermissions().api);
assert.deepEqual(P.patternsForDomain("*"), []);
assert.deepEqual(P.patternsForDomain(""), []);
});
});
describe("permissions - partitionPatterns", () => {
it("按已授权拆分成 granted / missing", async () => {
const fake = createFakePermissions(["https://a.com/*"]);
const P = loadPerms(fake.api);
const result = await P.partitionPatterns(["https://a.com/*", "http://b.com/*", "https://b.com/*"]);
assert.deepEqual(result.granted, ["https://a.com/*"]);
assert.deepEqual(result.missing, ["http://b.com/*", "https://b.com/*"]);
});
it("contains 抛错时按未授权处理", async () => {
const fake = createFakePermissions(["https://a.com/*"]);
fake.state.throwOnContains = true;
const P = loadPerms(fake.api);
const result = await P.partitionPatterns(["https://a.com/*"]);
assert.deepEqual(result.granted, []);
assert.deepEqual(result.missing, ["https://a.com/*"]);
});
});
describe("permissions - partitionDomains", () => {
it("任一 scheme 已授权即算该域名已授权", async () => {
const fake = createFakePermissions(["https://a.com/*"]);
const P = loadPerms(fake.api);
const result = await P.partitionDomains(["a.com", "b.com"]);
assert.deepEqual(result.granted, ["a.com"]);
assert.deepEqual(result.missing, ["b.com"]);
});
it("完全未授权的域名进入 missing", async () => {
const P = loadPerms(createFakePermissions().api);
const result = await P.partitionDomains(["a.com"]);
assert.deepEqual(result.missing, ["a.com"]);
});
it("无法转成模式的域名视为未授权", async () => {
const P = loadPerms(createFakePermissions().api);
const result = await P.partitionDomains(["*"]);
assert.deepEqual(result.missing, ["*"]);
});
});
describe("permissions - requestForDomain", () => {
it("已授权时直接返回 true 且不重复申请", async () => {
const fake = createFakePermissions(["http://a.com/*", "https://a.com/*"]);
const P = loadPerms(fake.api);
assert.equal(await P.requestForDomain("a.com"), true);
assert.equal(fake.state.requestCalls.length, 0, "已授权不应再次弹框");
});
it("未授权时申请全部模式", async () => {
const fake = createFakePermissions();
const P = loadPerms(fake.api);
assert.equal(await P.requestForDomain("a.com"), true);
assert.deepEqual(fake.state.requestCalls, [["http://a.com/*", "https://a.com/*"]]);
});
it("无法转换的域名不会发起申请", async () => {
const fake = createFakePermissions();
const P = loadPerms(fake.api);
assert.equal(await P.requestForDomain("*"), false);
assert.equal(fake.state.requestCalls.length, 0);
});
it("环境没有 chrome.permissions 时返回 false", async () => {
const P = loadPerms(null);
assert.equal(await P.requestForDomain("a.com"), false);
});
it("申请被拒绝时返回 false(不抛错)", async () => {
const fake = createFakePermissions();
fake.api.request = async () => false;
const P = loadPerms(fake.api);
assert.equal(await P.requestForDomain("a.com"), false);
});
});