Skip to content

Commit 0a87c6d

Browse files
committed
✅ 补 extensionEnv / BgExecScriptWarp / sandbox run-in 单元测试
- extension_env.test.ts:覆盖 getExtensionUserAgentData 的 navigator 缺失、platformInfo 异常、bitness 64/32 派生 - exec_warp.test.ts:覆盖 BgExecScriptWarp 把 extensionEnv 注入 GM_info 的各分支 - sandbox/runtime.test.ts:覆盖 execScript 中 run-in × inIncognitoContext 的过滤逻辑(含 extensionEnv 为 undefined 的 fail-open 行为)
1 parent edf29bb commit 0a87c6d

3 files changed

Lines changed: 310 additions & 0 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { describe, expect, it } from "vitest";
2+
import { BgExecScriptWarp } from "./exec_warp";
3+
import type { ScriptLoadInfo } from "../service_worker/types";
4+
import type { Message } from "@Packages/message/types";
5+
import type { TExtensionEnv } from "../extension/extension_env";
6+
7+
const buildScriptRes = () =>
8+
({
9+
uuid: "uuid-bg",
10+
name: "bg-script",
11+
metadata: {
12+
grant: ["none"],
13+
version: ["1.0.0"],
14+
},
15+
code: "return GM_info;",
16+
sourceCode: "return GM_info;",
17+
value: {},
18+
}) as unknown as ScriptLoadInfo;
19+
20+
const fakeMessage = undefined as unknown as Message;
21+
22+
const getGMInfo = (exec: BgExecScriptWarp) => (exec as any).named?.GM_info;
23+
24+
describe("BgExecScriptWarp envInfo 注入", () => {
25+
it("extensionEnv 为 undefined 时使用默认值", () => {
26+
const exec = new BgExecScriptWarp(buildScriptRes(), fakeMessage, undefined);
27+
const gmInfo = getGMInfo(exec);
28+
expect(gmInfo.isIncognito).toBe(false);
29+
expect(gmInfo.userAgentData).toEqual({
30+
brands: [],
31+
mobile: false,
32+
platform: "",
33+
});
34+
expect(gmInfo.sandboxMode).toBe("raw");
35+
});
36+
37+
it("inIncognitoContext=true 时覆盖 isIncognito 为 true", () => {
38+
const extensionEnv: TExtensionEnv = { inIncognitoContext: true };
39+
const exec = new BgExecScriptWarp(buildScriptRes(), fakeMessage, extensionEnv);
40+
const gmInfo = getGMInfo(exec);
41+
expect(gmInfo.isIncognito).toBe(true);
42+
// 没传 userAgentData,沿用默认空值
43+
expect(gmInfo.userAgentData).toEqual({
44+
brands: [],
45+
mobile: false,
46+
platform: "",
47+
});
48+
});
49+
50+
it("传入完整 userAgentData 时整体替换默认值", () => {
51+
const extensionEnv: TExtensionEnv = {
52+
inIncognitoContext: false,
53+
userAgentData: {
54+
brands: [{ brand: "Chromium", version: "120" }],
55+
mobile: true,
56+
platform: "Android",
57+
architecture: "arm",
58+
bitness: "64",
59+
} as any,
60+
};
61+
const exec = new BgExecScriptWarp(buildScriptRes(), fakeMessage, extensionEnv);
62+
const gmInfo = getGMInfo(exec);
63+
expect(gmInfo.isIncognito).toBe(false);
64+
expect(gmInfo.userAgentData).toEqual({
65+
brands: [{ brand: "Chromium", version: "120" }],
66+
mobile: true,
67+
platform: "Android",
68+
architecture: "arm",
69+
bitness: "64",
70+
});
71+
});
72+
73+
it("userAgentData 为 null 时保留默认值", () => {
74+
const extensionEnv: TExtensionEnv = {
75+
inIncognitoContext: true,
76+
userAgentData: null,
77+
};
78+
const exec = new BgExecScriptWarp(buildScriptRes(), fakeMessage, extensionEnv);
79+
const gmInfo = getGMInfo(exec);
80+
expect(gmInfo.isIncognito).toBe(true);
81+
expect(gmInfo.userAgentData).toEqual({
82+
brands: [],
83+
mobile: false,
84+
platform: "",
85+
});
86+
});
87+
});
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { afterEach, describe, expect, it, vi } from "vitest";
2+
import { extensionEnv, getExtensionUserAgentData } from "./extension_env";
3+
4+
describe("extensionEnv 常量", () => {
5+
it("从 chrome.extension.inIncognitoContext 读取 incognito 状态", () => {
6+
// mock 默认 inIncognitoContext = false
7+
expect(extensionEnv.inIncognitoContext).toBe(false);
8+
// userAgentData 为可选字段,常量初始化时不应填充
9+
expect(extensionEnv.userAgentData).toBeUndefined();
10+
});
11+
});
12+
13+
describe("getExtensionUserAgentData", () => {
14+
const originalUserAgentData = (navigator as any).userAgentData;
15+
const originalGetPlatformInfo = chrome.runtime.getPlatformInfo;
16+
17+
const setNavigatorUserAgentData = (value: any) => {
18+
Object.defineProperty(navigator, "userAgentData", {
19+
configurable: true,
20+
get: () => value,
21+
});
22+
};
23+
24+
afterEach(() => {
25+
setNavigatorUserAgentData(originalUserAgentData);
26+
if (originalGetPlatformInfo === undefined) {
27+
// @ts-ignore
28+
delete chrome.runtime.getPlatformInfo;
29+
} else {
30+
chrome.runtime.getPlatformInfo = originalGetPlatformInfo;
31+
}
32+
vi.restoreAllMocks();
33+
});
34+
35+
it("navigator.userAgentData 缺失时返回 null", async () => {
36+
setNavigatorUserAgentData(undefined);
37+
const result = await getExtensionUserAgentData();
38+
expect(result).toBeNull();
39+
});
40+
41+
it("没有 chrome.runtime.getPlatformInfo 时只返回基础字段", async () => {
42+
setNavigatorUserAgentData({
43+
brands: [{ brand: "Chromium", version: "120" }],
44+
mobile: false,
45+
platform: "macOS",
46+
});
47+
// @ts-ignore
48+
delete chrome.runtime.getPlatformInfo;
49+
50+
const result = await getExtensionUserAgentData();
51+
expect(result).toEqual({
52+
brands: [{ brand: "Chromium", version: "120" }],
53+
mobile: false,
54+
platform: "macOS",
55+
});
56+
expect((result as any).architecture).toBeUndefined();
57+
expect((result as any).bitness).toBeUndefined();
58+
});
59+
60+
it("getPlatformInfo 返回 x86-64 时 bitness 为 64", async () => {
61+
setNavigatorUserAgentData({
62+
brands: [],
63+
mobile: false,
64+
platform: "Linux",
65+
});
66+
chrome.runtime.getPlatformInfo = vi.fn().mockResolvedValue({
67+
os: "linux",
68+
arch: "x86-64",
69+
nacl_arch: "x86-64",
70+
}) as any;
71+
72+
const result = await getExtensionUserAgentData();
73+
expect(result?.architecture).toBe("x86-64");
74+
expect(result?.bitness).toBe("64");
75+
});
76+
77+
it("getPlatformInfo 返回 x86-32 时 bitness 为 32", async () => {
78+
setNavigatorUserAgentData({
79+
brands: [],
80+
mobile: false,
81+
platform: "Windows",
82+
});
83+
chrome.runtime.getPlatformInfo = vi.fn().mockResolvedValue({
84+
os: "win",
85+
arch: "x86-32",
86+
nacl_arch: "x86-32",
87+
}) as any;
88+
89+
const result = await getExtensionUserAgentData();
90+
expect(result?.architecture).toBe("x86-32");
91+
expect(result?.bitness).toBe("32");
92+
});
93+
94+
it("getPlatformInfo 抛异常时降级返回基础字段", async () => {
95+
setNavigatorUserAgentData({
96+
brands: [{ brand: "Chromium", version: "120" }],
97+
mobile: false,
98+
platform: "Android",
99+
});
100+
chrome.runtime.getPlatformInfo = vi.fn().mockRejectedValue(new Error("API not available")) as any;
101+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
102+
103+
const result = await getExtensionUserAgentData();
104+
expect(result).toEqual({
105+
brands: [{ brand: "Chromium", version: "120" }],
106+
mobile: false,
107+
platform: "Android",
108+
});
109+
expect((result as any).architecture).toBeUndefined();
110+
expect((result as any).bitness).toBeUndefined();
111+
expect(warnSpy).toHaveBeenCalled();
112+
});
113+
});
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2+
import { initTestEnv } from "@Tests/utils";
3+
import type { Server } from "@Packages/message/server";
4+
import type { WindowMessage } from "@Packages/message/window_message";
5+
import type { ScriptLoadInfo } from "../service_worker/types";
6+
import type { TExtensionEnv } from "../extension/extension_env";
7+
8+
// 单测重点:sandbox runtime.execScript 中新加的 run-in 过滤逻辑
9+
// 通过 mock BgExecScriptWarp 与 offscreen client,观察是否构造执行实例来判断过滤是否生效
10+
const mockExec = vi.fn().mockResolvedValue(undefined);
11+
const mockStop = vi.fn();
12+
const BgExecScriptWarpCtor = vi.fn().mockImplementation(() => ({
13+
exec: mockExec,
14+
stop: mockStop,
15+
scriptRes: { uuid: "uuid-bg" },
16+
valueUpdate: vi.fn(),
17+
emitEvent: vi.fn(),
18+
}));
19+
20+
vi.mock("../content/exec_warp", () => ({
21+
BgExecScriptWarp: function (...args: any[]) {
22+
return BgExecScriptWarpCtor(...args);
23+
},
24+
CATRetryError: class CATRetryError {},
25+
}));
26+
27+
vi.mock("../offscreen/client", () => ({
28+
proxyUpdateRunStatus: vi.fn(),
29+
}));
30+
31+
import { Runtime } from "./runtime";
32+
33+
initTestEnv();
34+
35+
const buildScript = (runIn?: string): ScriptLoadInfo =>
36+
({
37+
uuid: `uuid-${runIn ?? "none"}`,
38+
name: "bg",
39+
type: 2,
40+
code: "",
41+
sourceCode: "",
42+
metadata: runIn ? { "run-in": [runIn] } : {},
43+
metadataStr: "",
44+
userConfig: {},
45+
userConfigStr: "",
46+
value: {},
47+
resource: {},
48+
}) as unknown as ScriptLoadInfo;
49+
50+
const setup = (extensionEnv: TExtensionEnv | undefined) => {
51+
const windowMessage = {} as WindowMessage;
52+
const api = {} as Server;
53+
return new Runtime(windowMessage, api, Promise.resolve(extensionEnv));
54+
};
55+
56+
describe("Runtime.execScript run-in 过滤", () => {
57+
beforeEach(() => {
58+
BgExecScriptWarpCtor.mockClear();
59+
mockExec.mockClear();
60+
mockStop.mockClear();
61+
vi.useFakeTimers();
62+
});
63+
64+
afterEach(() => {
65+
vi.useRealTimers();
66+
});
67+
68+
it("metadata 没有 run-in 时无论 incognito 状态都执行", async () => {
69+
const runtime = setup({ inIncognitoContext: false });
70+
await runtime.execScript(buildScript());
71+
expect(BgExecScriptWarpCtor).toHaveBeenCalledTimes(1);
72+
});
73+
74+
it('run-in === "all" 时无论 incognito 状态都执行', async () => {
75+
const runtime = setup({ inIncognitoContext: true });
76+
await runtime.execScript(buildScript("all"));
77+
expect(BgExecScriptWarpCtor).toHaveBeenCalledTimes(1);
78+
});
79+
80+
it('run-in === "normal-tabs" 在普通环境中执行', async () => {
81+
const runtime = setup({ inIncognitoContext: false });
82+
await runtime.execScript(buildScript("normal-tabs"));
83+
expect(BgExecScriptWarpCtor).toHaveBeenCalledTimes(1);
84+
});
85+
86+
it('run-in === "normal-tabs" 在隐身环境中跳过执行', async () => {
87+
const runtime = setup({ inIncognitoContext: true });
88+
await runtime.execScript(buildScript("normal-tabs"));
89+
expect(BgExecScriptWarpCtor).not.toHaveBeenCalled();
90+
});
91+
92+
it('run-in === "incognito-tabs" 在普通环境中跳过执行', async () => {
93+
const runtime = setup({ inIncognitoContext: false });
94+
await runtime.execScript(buildScript("incognito-tabs"));
95+
expect(BgExecScriptWarpCtor).not.toHaveBeenCalled();
96+
});
97+
98+
it('run-in === "incognito-tabs" 在隐身环境中执行', async () => {
99+
const runtime = setup({ inIncognitoContext: true });
100+
await runtime.execScript(buildScript("incognito-tabs"));
101+
expect(BgExecScriptWarpCtor).toHaveBeenCalledTimes(1);
102+
});
103+
104+
it("extensionEnv 为 undefined 时,过滤被跳过照常执行(fail-open)", async () => {
105+
// 当前实现:拿不到 incognito 状态时不做过滤,避免静默丢失任务
106+
const runtime = setup(undefined);
107+
await runtime.execScript(buildScript("incognito-tabs"));
108+
expect(BgExecScriptWarpCtor).toHaveBeenCalledTimes(1);
109+
});
110+
});

0 commit comments

Comments
 (0)