feat: record risk policy decisions
This commit is contained in:
@@ -9,6 +9,7 @@ import { shortHash } from "../src/hash.js";
|
||||
|
||||
const fixturePath = path.resolve("tests/fixtures/context-risk.html");
|
||||
const fixtureFileUrl = pathToFileURL(fixturePath).href;
|
||||
const examplePath = path.resolve("examples/simple.html");
|
||||
|
||||
async function withTempOutput<T>(name: string, callback: (out: string) => Promise<T>): Promise<T> {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "action-topology-engine-test-"));
|
||||
@@ -196,3 +197,61 @@ test("explore records low-risk edges and keeps high-risk submit actions out of d
|
||||
assert.equal(hasEdgeForAccessibleName(graph, "Submit search"), false);
|
||||
assert.equal(graph.states.length >= 2, true);
|
||||
});
|
||||
|
||||
test("explore records state-local skipped high-risk actions with evidence", async () => {
|
||||
const graph = await withTempOutput("explore-skips", (out) => exploreUrl({ url: fixturePath, maxActions: 8, out }));
|
||||
assert.equal(graph.skippedActions.some((skip) => skip.reason.includes("form_submit")), true);
|
||||
|
||||
const deleteElement = graph.elements.find((element) => element.accessibleName === "Delete account");
|
||||
assert.ok(deleteElement);
|
||||
const deleteSkip = graph.skippedActions.find((skip) => skip.elementId === deleteElement.elementId);
|
||||
assert.ok(deleteSkip);
|
||||
assert.equal(deleteSkip.stateId, deleteElement.stateId);
|
||||
assert.equal(deleteSkip.riskCategory, "destructive");
|
||||
assert.equal(deleteSkip.riskLevel, "high");
|
||||
|
||||
assert.equal(graph.skippedActions.some((skip) => skip.reason === "allowed:ui_reveal"), false);
|
||||
assert.equal(graph.failedObservations.every((failure) => failure.message.length > 0), true);
|
||||
});
|
||||
|
||||
test("disabled low-risk controls are failed observations, not risk skips", async () => {
|
||||
const graph = await withTempOutput("explore-disabled", (out) => exploreUrl({ url: fixturePath, maxActions: 8, out }));
|
||||
const disabled = graph.elements.find((element) => element.accessibleName === "Open disabled panel");
|
||||
assert.ok(disabled);
|
||||
assert.equal(graph.skippedActions.some((skip) => skip.elementId === disabled.elementId), false);
|
||||
assert.equal(
|
||||
graph.failedObservations.some(
|
||||
(failure) => failure.elementId === disabled.elementId && failure.reason === "element_disabled",
|
||||
),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("explore does not record non-click elements as skipped click actions", async () => {
|
||||
const graph = await withTempOutput("explore-example-skips", (out) => exploreUrl({ url: examplePath, maxActions: 8, out }));
|
||||
|
||||
const searchInput = graph.elements.find((element) => element.accessibleName === "Search");
|
||||
assert.ok(searchInput);
|
||||
assert.equal(graph.skippedActions.some((skip) => skip.elementId === searchInput.elementId), false);
|
||||
|
||||
const settingsDialog = graph.elements.find((element) => element.accessibleName === "Settings dialog");
|
||||
assert.ok(settingsDialog);
|
||||
assert.equal(graph.skippedActions.some((skip) => skip.elementId === settingsDialog.elementId), false);
|
||||
|
||||
const deleteElement = graph.elements.find((element) => element.accessibleName === "Delete account");
|
||||
assert.ok(deleteElement);
|
||||
assert.equal(graph.skippedActions.some((skip) => skip.elementId === deleteElement.elementId && skip.riskCategory === "destructive"), true);
|
||||
});
|
||||
|
||||
test("disabled high-risk click candidates are failed observations, not risk skips", async () => {
|
||||
const graph = await withTempOutput("explore-disabled-high-risk", (out) => exploreUrl({ url: fixturePath, maxActions: 8, out }));
|
||||
const disabled = graph.elements.find((element) => element.accessibleName === "Delete disabled account");
|
||||
assert.ok(disabled);
|
||||
assert.equal(graph.skippedActions.some((skip) => skip.elementId === disabled.elementId), false);
|
||||
assert.equal(
|
||||
graph.failedObservations.some(
|
||||
(failure) => failure.elementId === disabled.elementId && failure.reason === "element_disabled",
|
||||
),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user