messages.autolock-refresh.test.ts

4.6 KB · raw

1// PB-26-107: passive page traffic must not refresh the auto-lock timer.2//3// Every inbound message used to call touchAutoLock(), which reschedules the4// autolock alarm. The content script fires AUTOFILL_QUERY and ML_PREDICT_BATCH5// on every `focusin` — an event browsers deliver for programmatic6// element.focus() too — so a page running7//   setInterval(() => passwordField.focus(), 1000)8// pinned the timer open for as long as the tab existed. Idle auto-lock is the9// control a shared-machine user relies on, and it silently never fired.10//11// Both passive types are now excluded from the refresh; genuinely user-initiated12// traffic still reschedules it.1314import { beforeEach, describe, expect, it, vi } from "vitest";1516type StorageShape = Record<string, unknown>;1718function makeChromeStorageMock() {19  const storage: StorageShape = {};20  const chrome = {21    storage: {22      local: {23        get: vi.fn(async (key?: string | string[]) => {24          if (!key) return { ...storage };25          const keys = Array.isArray(key) ? key : [key];26          return keys.reduce<StorageShape>((acc, k) => { acc[k] = storage[k]; return acc; }, {});27        }),28        set: vi.fn(async (next: StorageShape) => { Object.assign(storage, next); }),29        remove: vi.fn(async () => {}),30      },31      session: {32        get: vi.fn(async () => ({})),33        set: vi.fn(async () => {}),34        remove: vi.fn(async () => {}),35      },36    },37    alarms: { clear: vi.fn(), create: vi.fn(), onAlarm: { addListener: vi.fn() } },38    idle: { setDetectionInterval: vi.fn(), onStateChanged: { addListener: vi.fn() } },39    runtime: {40      id: "test-extension-id",41      getURL: vi.fn((path: string) => `chrome-extension://test-extension-id/${path}`),42    },43  } as unknown as typeof globalThis.chrome;44  return { chrome };45}4647const pageSender = {48  tab: { url: "https://example.com/login" },49  url: "https://example.com/login",50} as unknown as chrome.runtime.MessageSender;5152describe("PB-26-107: passive page traffic does not refresh auto-lock", () => {53  let vault: typeof import("./vault");54  let messages: typeof import("./messages");5556  beforeEach(async () => {57    vi.resetModules();58    (globalThis as Record<string, unknown>).chrome = makeChromeStorageMock().chrome;59    vi.stubGlobal("fetch", vi.fn(async () => { throw new Error("no network"); }));60    vi.stubGlobal("__PEACH_E2E_PASSKEYS__", false);6162    vault = await import("./vault");63    messages = await import("./messages");64    await vault.createVault("test-autolock-pass");65  });6667  function alarmCreations(): number {68    return (chrome.alarms.create as unknown as ReturnType<typeof vi.fn>).mock.calls.length;69  }7071  it("repeated AUTOFILL_QUERY does not reschedule the lock alarm", async () => {72    (chrome.alarms.create as unknown as ReturnType<typeof vi.fn>).mockClear();7374    for (let i = 0; i < 50; i += 1) {75      await messages.handleMessage(76        { type: "AUTOFILL_QUERY", payload: { domain: "example.com", kind: "login" } },77        pageSender,78      );79    }8081    expect(alarmCreations()).toBe(0);82  });8384  it("repeated ML_PREDICT_BATCH does not reschedule the lock alarm", async () => {85    (chrome.alarms.create as unknown as ReturnType<typeof vi.fn>).mockClear();8687    for (let i = 0; i < 50; i += 1) {88      await messages.handleMessage(89        { type: "ML_PREDICT_BATCH", payload: { fields: [] } },90        pageSender,91      );92    }9394    expect(alarmCreations()).toBe(0);95  });9697  // The control: this is not a blanket stop-refreshing. A user-initiated98  // message still pushes the deadline out.99  it("a user-initiated message still reschedules the lock alarm", async () => {100    (chrome.alarms.create as unknown as ReturnType<typeof vi.fn>).mockClear();101102    await messages.handleMessage({ type: "TOUCH_AUTO_LOCK", payload: {} }, pageSender);103104    expect(alarmCreations()).toBe(1);105  });106107  // A focus loop interleaved with genuine activity must not be able to build a108  // longer session than the activity alone would.109  it("a focus loop between real actions adds nothing to the deadline", async () => {110    (chrome.alarms.create as unknown as ReturnType<typeof vi.fn>).mockClear();111112    for (let i = 0; i < 20; i += 1) {113      await messages.handleMessage(114        { type: "AUTOFILL_QUERY", payload: { domain: "example.com", kind: "login" } },115        pageSender,116      );117    }118    await messages.handleMessage({ type: "TOUCH_AUTO_LOCK", payload: {} }, pageSender);119    for (let i = 0; i < 20; i += 1) {120      await messages.handleMessage(121        { type: "AUTOFILL_QUERY", payload: { domain: "example.com", kind: "login" } },122        pageSender,123      );124    }125126    expect(alarmCreations()).toBe(1);127  });128});