import assert from "node:assert/strict"; import { test } from "node:test"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { exploreUrl, scanUrl } from "../src/engine.js"; const fixturePath = path.resolve("tests/fixtures/context-risk.html"); async function withTempOutput(name: string, callback: (out: string) => Promise): Promise { const dir = await mkdtemp(path.join(tmpdir(), "action-topology-engine-test-")); try { return await callback(path.join(dir, `${name}.json`)); } finally { await rm(dir, { recursive: true, force: true }); } } function hasEdgeForAccessibleName( graph: { edges: Array<{ elementId: string }>; elements: Array<{ elementId: string; accessibleName: string | null }>; }, accessibleName: string, ): boolean { return graph.edges.some((edge) => { const element = graph.elements.find((item) => item.elementId === edge.elementId); return element?.accessibleName === accessibleName; }); } test("scan captures a state-local inventory without clicking high-risk actions", async () => { const graph = await withTempOutput("scan", (out) => scanUrl({ url: fixturePath, out })); assert.equal(graph.states.length, 1); assert.equal(graph.edges.length, 0); assert.equal(graph.elements.some((element) => element.accessibleName === "Open settings"), true); assert.equal(graph.elements.some((element) => element.accessibleName === "Delete account"), false); }); test("graph metadata records generation context and policy versions", async () => { const graph = await withTempOutput("metadata", (out) => scanUrl({ url: fixturePath, out })); assert.equal(graph.schemaVersion, "0.2"); assert.equal(graph.metadata.engineVersion, "0.2.0"); assert.equal(graph.metadata.runtimeContext.browserName, "chromium"); assert.equal(graph.metadata.runtimeContext.viewport.width, 1440); assert.equal(graph.metadata.runtimeContext.viewport.height, 1000); assert.equal(graph.metadata.riskPolicyVersion, "risk-policy-v0.2"); assert.equal(graph.metadata.locatorScoringVersion, "locator-scoring-v0.2"); assert.equal(typeof graph.metadata.crawlSessionId, "string"); assert.equal(graph.metadata.inputUrl, fixturePath); assert.equal(typeof graph.metadata.inputFingerprint, "string"); }); test("explore records low-risk edges and keeps high-risk submit actions out of default execution", async () => { const graph = await withTempOutput("explore", (out) => exploreUrl({ url: fixturePath, maxActions: 8, out })); assert.equal(graph.edges.some((edge) => edge.riskLevel === "low" && edge.toStateId), true); assert.equal(hasEdgeForAccessibleName(graph, "Delete account"), false); assert.equal(hasEdgeForAccessibleName(graph, "Submit search"), false); assert.equal(graph.states.length >= 2, true); });