Release Device Tools 0.2.0
Verify / verify (push) Canceled after 0s

This commit is contained in:
2026-09-02 13:08:36 +02:00
parent 06f697e180
commit 0ae5c7b7af
21 changed files with 1490 additions and 50 deletions
+165
View File
@@ -2,8 +2,12 @@ import { describe, expect, it, vi } from "vitest";
import {
collectPassiveCapabilities,
createRedactedReport,
evaluateRequirementProfile,
exportMediaCapabilityResult,
exportReportCsv,
exportReportJson,
parseRequirementProfile,
probeMediaCapabilities,
runProbe,
type Capability,
type DeviceEnvironment,
@@ -21,6 +25,7 @@ function environment(
visualViewport: { width: 1200, height: 700 },
matchMedia: (query: string) => ({ matches: query.includes("fine") }),
WebGLRenderingContext: function WebGLRenderingContext() {},
Worker: function Worker() {},
indexedDB: {},
caches: {},
} as unknown as Window;
@@ -81,6 +86,61 @@ describe("passive device inventory", () => {
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" });
});
});
@@ -180,3 +240,108 @@ describe("redacted exports", () => {
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/);
});
});