import { describe, expect, it, vi } from "vitest"; import { collectPassiveCapabilities, createRedactedReport, exportReportCsv, exportReportJson, runProbe, type Capability, type DeviceEnvironment, type ProbeResult, } from "../../src/core/device"; function environment( navigatorOverrides: Record = {}, ): DeviceEnvironment { const fakeWindow = { innerWidth: 1234, innerHeight: 777, devicePixelRatio: 2.25, top: null, visualViewport: { width: 1200, height: 700 }, matchMedia: (query: string) => ({ matches: query.includes("fine") }), WebGLRenderingContext: function WebGLRenderingContext() {}, indexedDB: {}, caches: {}, } as unknown as Window; Object.defineProperty(fakeWindow, "top", { value: fakeWindow }); const fakeDocument = { fullscreenEnabled: true, pictureInPictureEnabled: true, createElement: (name: string) => name === "video" || name === "audio" ? { canPlayType: (mime: string) => mime.includes("opus") ? "probably" : "", } : {}, } as unknown as Document; return { window: fakeWindow, document: fakeDocument, navigator: { onLine: true, maxTouchPoints: 5, mediaDevices: { getSupportedConstraints: () => ({ width: true, echoCancellation: true, }), }, ...navigatorOverrides, } as unknown as DeviceEnvironment["navigator"], screen: { width: 2560, height: 1440, availWidth: 2500, availHeight: 1400, colorDepth: 24, orientation: { type: "landscape-primary" }, } as Screen, secureContext: true, isolated: false, }; } describe("passive device inventory", () => { it("reports display, input, constraints, codecs and API presence without probing", () => { const capabilities = collectPassiveCapabilities(environment()); expect(capabilities.find((item) => item.id === "viewport")?.value).toBe( "1234 × 777 CSS px", ); expect(capabilities.find((item) => item.id === "touch")?.state).toBe( "available", ); expect( capabilities.find((item) => item.id === "media-constraints")?.value, ).toContain("echoCancellation"); expect(capabilities.find((item) => item.id === "codec-opus")?.value).toBe( "Probably", ); expect(capabilities.find((item) => item.id === "camera-api")?.state).toBe( "unavailable", ); }); }); describe("explicit probes", () => { it("reads storage locally and preserves only buckets in a report", async () => { const result = await runProbe( "storage", environment({ storage: { estimate: async () => ({ usage: 500_000_000, quota: 2_000_000_000 }), }, }), ); expect(result.state).toBe("complete"); expect(result.details[0]?.value).toContain("MiB"); const report = createRedactedReport([], [result]); const serialized = exportReportJson(report); expect(serialized).not.toContain("476.84"); expect(serialized).toContain("25-to-49%"); }); it("stops every user-media track immediately", async () => { const stop = vi.fn(); const result = await runProbe( "camera", environment({ mediaDevices: { getUserMedia: async () => ({ getTracks: () => [{ stop }, { stop }] }), }, }), ); expect(result.state).toBe("complete"); expect(stop).toHaveBeenCalledTimes(2); }); it("queries permission state without treating unsupported names as failure", async () => { const query = vi.fn(async ({ name }: { name: string }) => { if (name === "camera") throw new TypeError("not supported"); return { state: "prompt" }; }); const result = await runProbe( "permissions", environment({ permissions: { query } }), ); expect(result.state).toBe("complete"); expect(result.details.find((item) => item.label === "camera")?.value).toBe( "unsupported", ); expect(query).toHaveBeenCalled(); }); }); describe("redacted exports", () => { it("never emits exact high-entropy capability values", () => { const capabilities: Capability[] = [ { id: "viewport", group: "Display", label: "Viewport", state: "available", value: "1234 × 777 CSS px", privacy: "high", explanation: "", }, { id: "dpr", group: "Display", label: "DPR", state: "available", value: "2.25", privacy: "high", explanation: "", }, { id: "touch", group: "Input", label: "Touch", state: "available", value: "5 maximum point(s)", privacy: "medium", explanation: "", }, ]; const probe: ProbeResult = { id: "media-devices", state: "complete", summary: "done", details: [{ label: "videoinput", value: "7", redacted: "many" }], }; const report = createRedactedReport(capabilities, [probe]); const json = exportReportJson(report); expect(json).not.toContain("1234"); expect(json).not.toContain("2.25"); expect(json).toContain("medium"); expect(json).toContain("multi-touch"); expect(exportReportCsv(report)).toContain("capability,viewport"); expect(report.policy.join(" ")).toContain("No stable identifier"); }); });