Files
Notes/tests/unit/ipc-sender.test.js
2026-10-16 16:34:15 +08:00

174 lines
6.3 KiB
JavaScript
Raw 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.
// main/ipc-sender.js —— assertMainSender / assertTopFrame / assertAiRequestId 单测
//
// 覆盖:
// - assertMainSender 四种拒绝路径:event 缺 sender / mainWindow 未创建 /
// mainWindow 已销毁 / sender 不是主窗口 webContents
// - assertMainSender 放行路径:sender 匹配 + mainWindow 存活
// - assertTopFrame:senderFrame 缺失拒绝 / parent 真值拒绝 / parent null 放行
// - assertAiRequestId:两种合法格式(ai-<ts>-<uuid> / 裸 uuid)+ 各种垃圾拒绝
// @vitest-environment node
import { describe, it, expect, vi } from 'vitest';
const {
assertMainSender,
assertTopFrame,
assertAiRequestId,
SENDER_NOT_ALLOWED,
INVALID_REQUEST_ID,
} = await import('../../main/ipc-sender.js');
// ---- helpers ----
function makeWin(webContents) {
return {
webContents,
isDestroyed: () => false,
};
}
function makeEvent(sender) {
return { sender };
}
describe('assertMainSender', () => {
const sender = { id: 42 };
it('event 缺 sender → 拒绝(SENDER_NOT_ALLOWED)', () => {
const getMainWindow = () => makeWin(sender);
const r = assertMainSender({}, getMainWindow);
expect(r).toEqual({ ok: false, error: SENDER_NOT_ALLOWED });
});
it('event 完全 undefined → 拒绝', () => {
const r = assertMainSender(undefined, () => makeWin(sender));
expect(r).toEqual({ ok: false, error: SENDER_NOT_ALLOWED });
});
it('mainWindow 未创建(getMainWindow 返回 null) → 拒绝', () => {
const r = assertMainSender(makeEvent(sender), () => null);
expect(r).toEqual({ ok: false, error: SENDER_NOT_ALLOWED });
});
it('mainWindow 已销毁 → 拒绝', () => {
const win = { isDestroyed: () => true };
const r = assertMainSender(makeEvent(sender), () => win);
expect(r).toEqual({ ok: false, error: SENDER_NOT_ALLOWED });
});
it('sender 不等于 mainWindow.webContents → 拒绝(防 webview / 子窗口)', () => {
const otherSender = { id: 99 };
const getMainWindow = () => makeWin(sender);
const r = assertMainSender(makeEvent(otherSender), getMainWindow);
expect(r).toEqual({ ok: false, error: SENDER_NOT_ALLOWED });
});
it('sender 等于 mainWindow.webContents + mainWindow 存活 → 放行', () => {
const getMainWindow = () => makeWin(sender);
const r = assertMainSender(makeEvent(sender), getMainWindow);
expect(r).toEqual({ ok: true });
});
it('getMainWindow 不是函数 → 拒绝(防御性)', () => {
const r = assertMainSender(makeEvent(sender), null);
expect(r).toEqual({ ok: false, error: SENDER_NOT_ALLOWED });
});
it('主窗口在调用过程中被销毁(getMainWindow 返回已销毁 win) → 拒绝', () => {
// 模拟:handler 入口时主窗口还活着,但 getter 被调时已销毁(destroy 事件刚好触发)
const sender = { id: 42 };
const getMainWindow = vi.fn(() => ({ isDestroyed: () => true }));
const r = assertMainSender(makeEvent(sender), getMainWindow);
expect(r).toEqual({ ok: false, error: SENDER_NOT_ALLOWED });
});
});
describe('assertTopFrame', () => {
it('event.senderFrame 缺失 → 拒绝(保守)', () => {
const r = assertTopFrame({});
expect(r).toEqual({ ok: false, error: SENDER_NOT_ALLOWED });
});
it('event 完全 undefined → 拒绝', () => {
expect(assertTopFrame(undefined)).toEqual({ ok: false, error: SENDER_NOT_ALLOWED });
});
it('senderFrame.parent 真值(iframe / webview) → 拒绝', () => {
const r = assertTopFrame({ senderFrame: { parent: { id: 99 } } });
expect(r).toEqual({ ok: false, error: SENDER_NOT_ALLOWED });
});
it('senderFrame.parent null(顶层 frame) → 放行', () => {
const r = assertTopFrame({ senderFrame: { parent: null } });
expect(r).toEqual({ ok: true });
});
});
describe('assertAiRequestId', () => {
// 标准 UUIDv4 形式
const UUID_V4 = 'f47ac10b-58cc-4372-a567-0e02b2c3d479';
// main/ai.js:968 的格式:ai-<timestamp>-<uuid>
const AI_TS_UUID = `ai-1700000000000-${UUID_V4}`;
it('renderer 侧格式:裸 UUIDv4 → 放行', () => {
const r = assertAiRequestId(UUID_V4);
expect(r).toEqual({ ok: true, requestId: UUID_V4 });
});
it('main 侧 fallback 格式:ai-<ts>-<uuid> → 放行', () => {
const r = assertAiRequestId(AI_TS_UUID);
expect(r).toEqual({ ok: true, requestId: AI_TS_UUID });
});
it('大写 UUIDv4 也放行(正则 case-insensitive)', () => {
const r = assertAiRequestId(UUID_V4.toUpperCase());
expect(r.ok).toBe(true);
});
it('非 string(number / object / null) → 拒绝', () => {
expect(assertAiRequestId(123).error).toBe(INVALID_REQUEST_ID);
expect(assertAiRequestId({}).error).toBe(INVALID_REQUEST_ID);
expect(assertAiRequestId(null).error).toBe(INVALID_REQUEST_ID);
expect(assertAiRequestId(undefined).error).toBe(INVALID_REQUEST_ID);
});
it('空串 → 拒绝', () => {
expect(assertAiRequestId('').error).toBe(INVALID_REQUEST_ID);
});
it('过长(>200 字符) → 拒绝(防内存放大)', () => {
expect(assertAiRequestId('a'.repeat(201)).error).toBe(INVALID_REQUEST_ID);
});
it('UUIDv4 但缺一位 → 拒绝(hex 字符数错)', () => {
const short = 'f47ac10b-58cc-4372-a567-0e02b2c3d47';
expect(assertAiRequestId(short).error).toBe(INVALID_REQUEST_ID);
});
it('UUIDv4 但第 3 段首位不是 4 → 拒绝', () => {
const v3 = 'f47ac10b-58cc-4372-3567-0e02b2c3d479';
expect(assertAiRequestId(v3).error).toBe(INVALID_REQUEST_ID);
});
it('UUIDv4 但第 4 段首位不是 [89ab] → 拒绝', () => {
const badVariant = 'f47ac10b-58cc-4372-4567-0e02b2c3d479';
expect(assertAiRequestId(badVariant).error).toBe(INVALID_REQUEST_ID);
});
it('ai- 前缀但 timestamp 不是纯数字 → 拒绝', () => {
const bad = `ai-notanumber-${UUID_V4}`;
expect(assertAiRequestId(bad).error).toBe(INVALID_REQUEST_ID);
});
it('ai- 前缀但 timestamp 过长(>18 位)→ 拒绝(防 DoS)', () => {
const bad = `ai-${'1'.repeat(19)}-${UUID_V4}`;
expect(assertAiRequestId(bad).error).toBe(INVALID_REQUEST_ID);
});
it('包含 SQL 注入 / shell 元字符 → 拒绝', () => {
expect(assertAiRequestId(`' OR 1=1; --`).error).toBe(INVALID_REQUEST_ID);
expect(assertAiRequestId(`$(rm -rf /)`).error).toBe(INVALID_REQUEST_ID);
});
});