Forked from drunkktoys/vibe-lm
Project Files
tests / debug-smoke.test.ts
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { writeFileSync, unlinkSync, existsSync, mkdirSync, rmSync } from "node:fs";
import { resolve } from "node:path";
import { tmpdir } from "node:os";
const TEST_DIR = resolve(tmpdir(), `vibelm-debug-smoke-${Date.now()}`);
const CONFIG_DIR = resolve(tmpdir(), `vibelm-debug-smoke-data-${Date.now()}`);
process.env.VIBE_LM_DATA_DIR = CONFIG_DIR;
import {
getDebugProvider,
resetDebugProviderForTest,
DebugProvider,
checkIsDangerousCommand,
validateInteractionSafety,
validateHotfixSafety,
} from "../src/debugProvider";
describe("DebugProvider smoke tests", () => {
it("singleton resets cleanly", () => {
resetDebugProviderForTest();
const dp1 = getDebugProvider();
assert(dp1, "getDebugProvider must return an instance");
assert(dp1 instanceof DebugProvider);
const dp2 = getDebugProvider();
assert(dp1 === dp2, "singleton must return the same instance");
});
it("safety: blocks dangerous commands", () => {
assert.equal(checkIsDangerousCommand("rm -rf /"), "Refusing: rm -rf /");
assert.equal(checkIsDangerousCommand("ls -la"), null);
assert.equal(checkIsDangerousCommand(":(){ :|:& };:"), "Refusing: fork bomb");
assert.equal(checkIsDangerousCommand("mkfs.ext4 /dev/sda"), "Refusing: filesystem format");
assert.equal(checkIsDangerousCommand("echo hello"), null);
});
it("safety: validates interaction bounds", () => {
const bounds = { x: 0, y: 0, width: 1920, height: 1080 };
assert.equal(validateInteractionSafety({ action: "click", coordinates: { x: 500, y: 500 } }, bounds), null);
assert.match(validateInteractionSafety({ action: "click", coordinates: { x: -10, y: 500 } }, bounds)!, /outside/);
assert.match(validateInteractionSafety({ action: "click", coordinates: { x: 2000, y: 500 } }, bounds)!, /outside/);
assert.match(validateInteractionSafety({ action: "click", coordinates: { x: 500, y: 1200 } }, bounds)!, /outside/);
});
it("safety: validates hotfix payloads", () => {
assert.equal(validateHotfixSafety({ patchType: "js_eval", payload: "1+1" }), null);
// dom_mutate dangerous check fires regardless of workspacePath
assert.match(validateHotfixSafety({ patchType: "dom_mutate", payload: "document.cookie" })!, /dangerous/);
assert.match(validateHotfixSafety({ patchType: "dom_mutate", payload: "fetch('https://evil.com')" })!, /dangerous/);
assert.match(validateHotfixSafety({ patchType: "dom_mutate", payload: "document.cookie" }, "/workspace")!, /dangerous/);
assert.match(validateHotfixSafety({ patchType: "env_override", payload: "API_KEY=secret" })!, /sensitive/);
assert.equal(validateHotfixSafety({ patchType: "env_override", payload: "PORT=3000" }), null);
});
it("desktop: spawn a process, capture logs, then detach", async () => {
resetDebugProviderForTest();
const dp = getDebugProvider();
const spawnResult = await dp.attachTarget({
targetType: "desktop",
identifier: "/bin/echo",
});
// /bin/echo with no args exits immediately β may report as error or ok
// The important thing is it doesn't crash
assert(typeof spawnResult.ok === "boolean", "attach must return ok boolean");
// Even if spawn failed (e.g. process already exited), isAttached should be false
const attached = dp.isAttached();
assert(typeof attached === "boolean");
if (attached) {
const state = await dp.captureState({ logTailLines: 10 });
assert(state.ok, "captureState must succeed when attached");
assert(Array.isArray(state.data!.logs), "logs must be an array");
assert(state.data!.timestamp, "must have a timestamp");
await dp.detachTarget();
assert(!dp.isAttached(), "must be detached after detachTarget");
}
});
it("desktop: attach to a running process by PID", async () => {
resetDebugProviderForTest();
const dp = getDebugProvider();
// Attach to the current node process (always running during tests)
const attachResult = await dp.attachTarget({
targetType: "desktop",
identifier: String(process.pid),
});
assert(attachResult.ok, `must attach to own PID: ${attachResult.error}`);
const info = dp.getTargetInfo();
assert(info, "must have target info after attach");
assert.equal(info!.targetType, "desktop");
assert.equal(info!.identifier, String(process.pid));
assert.equal(info!.pid, process.pid);
const state = await dp.captureState({ logTailLines: 10 });
assert(state.ok, "captureState must succeed");
assert(state.data!.logs.length >= 0, "logs array present");
await dp.detachTarget();
assert(!dp.isAttached());
});
it("desktop: attach to non-existent PID fails safely", async () => {
resetDebugProviderForTest();
const dp = getDebugProvider();
const result = await dp.attachTarget({
targetType: "desktop",
identifier: "999999999",
});
assert(!result.ok, "must fail for non-existent PID");
assert.match(result.error!, /not found/i);
assert(!dp.isAttached(), "must not be attached after failed attach");
});
it("capture/interact/hotfix return error when not attached", async () => {
resetDebugProviderForTest();
const dp = getDebugProvider();
const capture = await dp.captureState();
assert(!capture.ok);
assert.match(capture.error!, /no.*target/i);
const interact = await dp.executeInteraction({ action: "click", coordinates: { x: 0, y: 0 } });
assert(!interact.ok);
assert.match(interact.error!, /no.*target/i);
const hotfix = await dp.applyHotfix({ patchType: "js_eval", payload: "1" });
assert(!hotfix.ok);
assert.match(hotfix.error!, /no.*target/i);
});
it("web: attach fails gracefully when no CDP endpoint", async () => {
resetDebugProviderForTest();
const dp = getDebugProvider();
const result = await dp.attachTarget({
targetType: "web",
identifier: "http://localhost:99999",
});
assert(!result.ok, "must fail when no CDP endpoint is running");
assert(result.error, "must have an error message");
assert(!dp.isAttached());
});
it("web: cdpPort parameter is accepted", async () => {
resetDebugProviderForTest();
const dp = getDebugProvider();
// Custom port should be used in the error message
const result = await dp.attachTarget({
targetType: "web",
identifier: "http://localhost:99999",
cdpPort: 19222,
});
assert(!result.ok);
assert.match(result.error!, /19222/, "error message should mention the custom port");
});
it("web: attach fails gracefully for unknown target type", async () => {
resetDebugProviderForTest();
const dp = getDebugProvider();
const result = await dp.attachTarget({
targetType: "tablet" as any,
identifier: "something",
});
assert(!result.ok);
assert.match(result.error!, /unknown/i);
});
it("safety boundary restricts interactions", async () => {
resetDebugProviderForTest();
const dp = getDebugProvider();
dp.setSafetyBoundary({ windowBounds: { x: 0, y: 0, width: 100, height: 100 } });
// Attach to own PID so executeInteraction has a target
await dp.attachTarget({ targetType: "desktop", identifier: String(process.pid) });
// Interaction inside bounds β should attempt execution (may succeed or fail on macOS a11y, but not safety)
const safe = await dp.executeInteraction({ action: "click", coordinates: { x: 50, y: 50 } });
// We just verify it doesn't get blocked by safety (the actual a11y might fail on CI)
if (!safe.ok) {
assert.doesNotMatch(safe.error!, /outside.*bounds/, "inside bounds must not trigger safety block");
}
// Interaction outside bounds β must be blocked by safety
const unsafe = await dp.executeInteraction({ action: "click", coordinates: { x: 500, y: 500 } });
assert(!unsafe.ok);
assert.match(unsafe.error!, /outside.*bounds/);
await dp.detachTarget();
});
});