import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { JSDOM } from "jsdom"; import { createWorker } from "../../extension/worker/worker"; import { MemoryArchiveStore } from "../../extension/worker/archiveStore"; import { handlePanelRequest } from "../../extension/worker/panelApi"; import { mountPopup } from "../../extension/worker/popup"; import { generateSigningKey } from "../../tools/bundleSign"; import { startFakeCentral } from "../helpers/fakeCentral"; /** * #62: the popup and its message check on top of the real worker, against a central service that answers over HTTP (a fake one here; the real one is in the integration * tier). The worker is already enrolled by hand, as a test device is. Every control sends one request, and what the worker holds afterwards is what the popup shows. */ const ORIGIN = "http://127.0.0.1:4780"; let key: Awaited>; let central: Awaited>; beforeEach(async () => { key = await generateSigningKey(); central = await startFakeCentral({ privateKey: key.privateKey }); }); afterEach(() => central.close()); async function enrolledPerson(open = false) { const stored = new Map(); const worker = createWorker({ fetch: globalThis.fetch, storage: { get: async (k) => stored.get(k), set: async (k, v) => void stored.set(k, v) }, idb: undefined as never, store: new MemoryArchiveStore(), runJob: async () => { throw new Error("no job runs here"); }, deviceOrigins: open ? ["*"] : [ORIGIN], chrome: "155.0.0.0", pinnedKey: key.publicKey, pinnedDispatchKey: key.publicKey, now: () => Date.now(), setTimer: (fn, ms) => setTimeout(fn, ms), clearTimer: (h) => clearTimeout(h as number), requestTimeoutMs: 5000, }); await worker.configure({ central: central.url, token: central.token, deviceId: central.deviceId, accounts: { "test-qian": "qian.test" } }); const dom = new JSDOM('
'); const doc = dom.window.document; const $ = (sel: string) => doc.querySelector(sel) as T; const settle = async () => { for (let i = 0; i < 12; i++) await new Promise((r) => setTimeout(r, 15)); }; const fire = (sel: string, type: string) => $(sel).dispatchEvent(new dom.window.Event(type, { bubbles: true })); await mountPopup(doc, (method, ...args) => handlePanelRequest(worker, central.url, { method, args })); return { worker, doc, $, settle, fire, text: () => doc.body.textContent ?? "" }; } describe("the popup over the real worker", () => { it("shows the device and its profile, and its controls start off", async () => { const p = await enrolledPerson(); expect(p.text()).toContain(central.deviceId); expect(p.text()).toContain("test-qian: qian.test"); for (const id of ["#paused", "#posting", "#spending"]) expect((p.$(id) as HTMLInputElement).checked).toBe(false); expect(p.$("#origins").textContent).toContain(ORIGIN); }); it("posting and spending are separate, and each stays as the person left it", async () => { const p = await enrolledPerson(); (p.$("#spending") as HTMLInputElement).checked = true; p.fire("#spending", "change"); await p.settle(); expect(await p.worker.status()).toMatchObject({ allowSpending: true, allowPosting: false }); expect((p.$("#spending") as HTMLInputElement).checked).toBe(true); expect((p.$("#posting") as HTMLInputElement).checked).toBe(false); }); it("approves a site only when the person ticks it and saves, and takes it back the same way", async () => { const p = await enrolledPerson(); expect(await p.worker.origins()).toEqual({ asked: [ORIGIN], approved: [] }); (p.$("#origins input[type=checkbox]") as HTMLInputElement).checked = true; p.fire("#saveOrigins", "click"); await p.settle(); expect(await p.worker.origins()).toEqual({ asked: [ORIGIN], approved: [ORIGIN] }); (p.$("#origins input[type=checkbox]") as HTMLInputElement).checked = false; p.fire("#saveOrigins", "click"); await p.settle(); expect(await p.worker.origins()).toEqual({ asked: [ORIGIN], approved: [] }); }); it("shows the worker's reason when a limit is refused", async () => { const p = await enrolledPerson(); (p.$("#postsPerDay") as HTMLInputElement).value = "0"; p.fire("#savePosts", "click"); await p.settle(); expect(p.$("#msg").textContent).toContain("whole number"); expect(await p.worker.status()).toMatchObject({ postsPerDay: 10 }); }); it("checks for work now and reports that there was none", async () => { const p = await enrolledPerson(); p.fire("#tick", "click"); await p.settle(); expect(p.$("#msg").textContent).toMatch(/idle|offline|error|refused|done/); }); }); describe("the popup of an open device over the real worker", () => { it("has a box for the sites it will not test and none for approving sites", async () => { const p = await enrolledPerson(true); expect(p.$("#denied")).toBeTruthy(); expect(p.$("#origins")).toBeNull(); expect(await p.worker.status()).toMatchObject({ openMode: true, originsAsked: ["*"], deniedSites: [] }); }); it("keeps the sites the person writes, as hosts, and shows them back", async () => { const p = await enrolledPerson(true); (p.$("#denied") as HTMLTextAreaElement).value = "Bank.example\nhttps://mail.test:8443/inbox\n"; p.fire("#saveDenied", "click"); await p.settle(); expect(await p.worker.status()).toMatchObject({ deniedSites: ["bank.example", "mail.test"] }); expect((p.$("#denied") as HTMLTextAreaElement).value).toBe("bank.example\nmail.test"); expect(p.$("#msg").textContent).toBe("Saved."); }); it("shows why a line is not a site, and keeps the list it had", async () => { const p = await enrolledPerson(true); (p.$("#denied") as HTMLTextAreaElement).value = "bank.example"; p.fire("#saveDenied", "click"); await p.settle(); (p.$("#denied") as HTMLTextAreaElement).value = "bank.example\nnot a host!"; p.fire("#saveDenied", "click"); await p.settle(); expect(p.$("#msg").textContent).toContain("not a site"); expect(await p.worker.status()).toMatchObject({ deniedSites: ["bank.example"] }); }); it("cannot approve sites: there is nothing to approve", async () => { const p = await enrolledPerson(true); const r = await handlePanelRequest(p.worker, central.url, { method: "setApprovedOrigins", args: [[ORIGIN]] }); expect(r).toMatchObject({ ok: false, message: expect.stringContaining("nothing to approve") }); }); });