import assert from "node:assert/strict"; import { test } from "node:test"; import { classifyActionRisk, classifyClickRisk, defaultExploreDecision, isLowRiskClickable } from "../src/risk.js"; import type { ActionableElement } from "../src/types.js"; function element(overrides: Partial): ActionableElement { return { elementId: "el_test", stateId: "state_test", tag: "button", role: "button", accessibleName: "Open settings", text: "Open settings", label: null, placeholder: null, attributes: {}, bbox: { x: 0, y: 0, width: 100, height: 30 }, framePath: [], shadowPath: [], interactability: { visible: true, enabled: true, editable: false, receivesEvents: true }, locators: [], context: { dialog: null, form: null, section: null, row: null, landmark: null, listitem: null, }, parameterSurface: null, evidence: [], ...overrides, }; } test("risk classifier blocks destructive and submission terms", () => { assert.equal(classifyClickRisk(element({ accessibleName: "Delete account" })), "high"); assert.equal(classifyClickRisk(element({ accessibleName: "提交订单" })), "high"); assert.equal(classifyClickRisk(element({ attributes: { type: "submit" }, text: "Submit search" })), "high"); }); test("risk classifier treats editable controls as medium", () => { assert.equal(classifyClickRisk(element({ tag: "input", role: "textbox", accessibleName: "Query" })), "medium"); assert.equal(classifyClickRisk(element({ tag: "select", role: "combobox", accessibleName: "Segment" })), "medium"); }); test("default explorer only clicks low-risk visible clickable elements", () => { assert.equal(isLowRiskClickable(element({ accessibleName: "Open settings" })), true); assert.equal(isLowRiskClickable(element({ accessibleName: "Delete account" })), false); assert.equal(isLowRiskClickable(element({ interactability: { visible: true, enabled: false, editable: false, receivesEvents: true } })), false); }); test("risk policy exposes category, level, and reasons", () => { const decision = defaultExploreDecision(element({ accessibleName: "Authorize payment" }), "click"); assert.equal(decision.allowed, false); assert.equal(decision.risk.category, "payment_sensitive"); assert.equal(decision.risk.level, "high"); assert.equal(decision.reason.includes("payment_sensitive"), true); }); test("risk policy treats UI reveal as default explorable", () => { const risk = classifyActionRisk(element({ accessibleName: "Open settings" }), "click"); assert.equal(risk.category, "ui_reveal"); assert.equal(defaultExploreDecision(element({ accessibleName: "Open settings" }), "click").allowed, true); }); test("risk policy blocks spaced auth and payment phrases", () => { const logOut = defaultExploreDecision(element({ accessibleName: "Log out" }), "click"); assert.equal(logOut.allowed, false); assert.equal(logOut.risk.category, "auth_sensitive"); assert.equal(logOut.risk.level, "high"); const checkOut = defaultExploreDecision(element({ accessibleName: "Check out" }), "click"); assert.equal(checkOut.allowed, false); assert.equal(checkOut.risk.category, "payment_sensitive"); assert.equal(checkOut.risk.level, "high"); });