78 lines
3.2 KiB
TypeScript
78 lines
3.2 KiB
TypeScript
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>): 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");
|
|
});
|