c58e49bf5b
- identity.ts: 访客/会话 ID(30min 空闲轮换),localStorage 异常统一内存降级不冒泡(spec §4.5 红线) - context.ts: buildEnvContext/parseUtm/sanitizeUrl/pageContext;参数黑名单与 server URL_PARAM_DROP_PATTERNS 同源(整键精确匹配、大小写不敏感、保留空值) - 测试 21 过(含隐私模式降级、Edg 先于 Chrome、微信 UA 判定、iPad-Mobile 规格顺序锁定) Co-Authored-By: Claude Code <noreply@anthropic.com>
50 lines
1.8 KiB
TypeScript
50 lines
1.8 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
import { getVisitorId, getSessionId, touchActivity } from "../src/identity";
|
|
|
|
describe("identity", () => {
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
vi.useFakeTimers();
|
|
});
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("visitor_id 持久化且格式正确", () => {
|
|
const v = getVisitorId();
|
|
expect(v).toBe(getVisitorId());
|
|
expect(v).toMatch(/^[0-9a-f-]{36}$/);
|
|
});
|
|
|
|
it("30 分钟无活动切新会话;窗口内 touch 续期", () => {
|
|
const s1 = getSessionId();
|
|
vi.advanceTimersByTime(10 * 60_000);
|
|
touchActivity();
|
|
vi.advanceTimersByTime(10 * 60_000);
|
|
expect(getSessionId()).toBe(s1); // 20min 内有活动 → 同会话
|
|
vi.advanceTimersByTime(31 * 60_000); // 再过 31min 无活动
|
|
expect(getSessionId()).not.toBe(s1);
|
|
});
|
|
|
|
it("localStorage 抛异常时内存降级不冒泡且功能可用", () => {
|
|
// 探测确认:vitest happy-dom 环境下 window === globalThis,且 localStorage
|
|
// 是 window 上的自有可配置 accessor——spy 其 getter 即可让模块内裸
|
|
// localStorage 引用在访问时抛异常(等效隐私模式/禁存储)。
|
|
const spy = vi.spyOn(window, "localStorage", "get").mockImplementation(() => {
|
|
throw new DOMException("denied");
|
|
});
|
|
|
|
const v = getVisitorId();
|
|
expect(v).toMatch(/^[0-9a-f-]{36}$/);
|
|
const s = getSessionId();
|
|
expect(s).toMatch(/^[0-9a-f-]{36}$/);
|
|
expect(getSessionId()).toBe(s); // 纯内存降级下会话仍连续
|
|
expect(() => touchActivity()).not.toThrow();
|
|
|
|
// 还原后(内存态无法迁移到 localStorage 是预期)混用两套存储不抛
|
|
spy.mockRestore();
|
|
expect(() => getSessionId()).not.toThrow();
|
|
});
|
|
});
|