import { describe, expect, it, vi } from "vitest"; import { collectPassiveCapabilities, createRedactedReport, evaluateRequirementProfile, exportMediaCapabilityResult, exportReportCsv, exportReportJson, parseRequirementProfile, probeMediaCapabilities, 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() {}, Worker: function Worker() {}, 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", ); expect( capabilities.find((item) => item.id === "camera-api")?.availability, ).toBe("missing-api"); }); it("distinguishes insecure context and Permissions Policy failures", async () => { const insecure = environment(); insecure.secureContext = false; expect( collectPassiveCapabilities(insecure).find( (item) => item.id === "camera-api", ), ).toMatchObject({ availability: "insecure-context" }); expect(await runProbe("camera", insecure)).toMatchObject({ state: "unsupported", availability: "insecure-context", }); const blocked = environment({ mediaDevices: { getUserMedia: vi.fn() }, }); Object.assign(blocked.document, { permissionsPolicy: { allowsFeature: (feature: string) => feature !== "camera", }, }); expect(await runProbe("camera", blocked)).toMatchObject({ state: "denied", availability: "permissions-policy", }); }); it("distinguishes missing devices from denied permission", async () => { const missing = await runProbe( "camera", environment({ mediaDevices: { getUserMedia: async () => { throw new DOMException("No camera", "NotFoundError"); }, }, }), ); expect(missing).toMatchObject({ availability: "device-or-os" }); const denied = await runProbe( "camera", environment({ mediaDevices: { getUserMedia: async () => { throw new DOMException("Denied", "NotAllowedError"); }, }, }), ); expect(denied).toMatchObject({ availability: "user-permission" }); }); }); 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"); }); }); describe("application requirement profiles", () => { it("combines legacy requirements with required and optional capabilities", () => { const profile = parseRequirementProfile({ id: "de.example.tool", name: "Example", requirements: { secureContext: true, workers: true }, capabilities: { required: ["file-system", "workers"], optional: ["webgpu-api", "workers"], }, }); expect(profile.required).toEqual([ "file-system", "secure-context", "workers", ]); expect(profile.optional).toEqual(["webgpu-api"]); const evaluation = evaluateRequirementProfile( profile, collectPassiveCapabilities(environment()), ); expect(evaluation.status).toBe("blocked"); expect( evaluation.requirements.find((item) => item.id === "workers")?.state, ).toBe("available"); expect( evaluation.requirements.find((item) => item.id === "file-system") ?.remediation, ).toContain("browser/version"); }); it("reports unknown required identifiers and rejects malformed profiles", () => { const profile = parseRequirementProfile( JSON.stringify({ capabilities: { required: ["future-api"] } }), ); const result = evaluateRequirementProfile(profile, []); expect(result.status).toBe("blocked"); expect(result.requirements[0]).toMatchObject({ state: "unknown" }); expect(() => parseRequirementProfile({ capabilities: { required: ["Bad ID"] } }), ).toThrow(/capability ID/); }); }); describe("MediaCapabilities lab", () => { it("runs only the requested configuration and exports bucketed numbers", async () => { const decodingInfo = vi.fn(async () => ({ supported: true, smooth: false, powerEfficient: true, })); const result = await probeMediaCapabilities( { kind: "video", contentType: 'video/mp4; codecs="avc1.42E01E"', width: 1920, height: 1080, bitrate: 8_765_432, framerate: 30, }, environment({ mediaCapabilities: { decodingInfo } }), ); expect(decodingInfo).toHaveBeenCalledOnce(); expect(result).toMatchObject({ state: "complete", supported: true, smooth: false, powerEfficient: true, }); const exported = exportMediaCapabilityResult(result); expect(exported).toContain("Full-HD"); expect(exported).toContain("2-to-10-Mbit/s"); expect(exported).not.toContain("8765432"); expect(exported).not.toContain("1920"); }); it("handles unsupported browsers and validates dangerous input", async () => { const unsupported = await probeMediaCapabilities( { kind: "audio", contentType: 'audio/webm; codecs="opus"', channels: "2", bitrate: 192_000, samplerate: 48_000, }, environment(), ); expect(unsupported.state).toBe("unsupported"); await expect( probeMediaCapabilities( { kind: "video", contentType: "audio/mp4\ninvalid", width: 1920, height: 1080, bitrate: 8_000_000, framerate: 30, }, environment(), ), ).rejects.toThrow(/video MIME type/); }); });