mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-09 03:22:40 +00:00
refactor: share gateway misc test helpers
This commit is contained in:
@@ -117,10 +117,36 @@ describe("GatewayClient", () => {
|
||||
);
|
||||
}
|
||||
|
||||
test("uses a large maxPayload for node snapshots", () => {
|
||||
const client = new GatewayClient({ url: "ws://127.0.0.1:1" });
|
||||
function startGatewayClient(params: { url: string; tlsFingerprint?: string }) {
|
||||
const client = new GatewayClient(params);
|
||||
client.start();
|
||||
const last = wsMockState.last as { url: unknown; opts: unknown } | null;
|
||||
return wsMockState.last;
|
||||
}
|
||||
|
||||
function expectNoGatewayClientAgent(params: { url: string; tlsFingerprint?: string }) {
|
||||
const last = startGatewayClient(params) as { opts: { agent?: unknown } } | null;
|
||||
expect(last?.opts.agent).toBeUndefined();
|
||||
}
|
||||
|
||||
function setCorporateNoProxy() {
|
||||
process.env["NO_PROXY"] = "corp.example.com";
|
||||
process.env["no_proxy"] = "corp.example.com";
|
||||
}
|
||||
|
||||
function setGatewayOnlyProxyEnv() {
|
||||
process.env["HTTP_PROXY"] = "http://127.0.0.1:3128";
|
||||
process.env["HTTPS_PROXY"] = "http://127.0.0.1:3128";
|
||||
}
|
||||
|
||||
function registerGatewayOnlyProxy() {
|
||||
return registerActiveManagedProxyUrl(new URL("http://127.0.0.1:3128"), "gateway-only");
|
||||
}
|
||||
|
||||
test("uses a large maxPayload for node snapshots", () => {
|
||||
const last = startGatewayClient({ url: "ws://127.0.0.1:1" }) as {
|
||||
url: unknown;
|
||||
opts: unknown;
|
||||
} | null;
|
||||
const opts = last?.opts as { maxPayload?: number } | undefined;
|
||||
|
||||
expect(last?.url).toBe("ws://127.0.0.1:1");
|
||||
@@ -128,52 +154,32 @@ describe("GatewayClient", () => {
|
||||
});
|
||||
|
||||
test("does not pass an explicit direct agent for loopback control-plane WebSocket connections", () => {
|
||||
const client = new GatewayClient({ url: "ws://127.0.0.1:1" });
|
||||
client.start();
|
||||
const last = wsMockState.last as { opts: { agent?: unknown } } | null;
|
||||
|
||||
expect(last?.opts.agent).toBeUndefined();
|
||||
expectNoGatewayClientAgent({ url: "ws://127.0.0.1:1" });
|
||||
});
|
||||
|
||||
test("does not pass an explicit direct agent for IPv6 loopback control-plane WebSocket connections", () => {
|
||||
const client = new GatewayClient({ url: "ws://[::1]:1" });
|
||||
client.start();
|
||||
const last = wsMockState.last as { opts: { agent?: unknown } } | null;
|
||||
|
||||
expect(last?.opts.agent).toBeUndefined();
|
||||
expectNoGatewayClientAgent({ url: "ws://[::1]:1" });
|
||||
});
|
||||
|
||||
test("does not pass an explicit direct agent for localhost hostnames", () => {
|
||||
const client = new GatewayClient({ url: "ws://localhost:1" });
|
||||
client.start();
|
||||
const last = wsMockState.last as { opts: { agent?: unknown } } | null;
|
||||
|
||||
expect(last?.opts.agent).toBeUndefined();
|
||||
expectNoGatewayClientAgent({ url: "ws://localhost:1" });
|
||||
});
|
||||
|
||||
test("does not force a direct agent for remote Gateway WebSocket connections", () => {
|
||||
const client = new GatewayClient({
|
||||
expectNoGatewayClientAgent({
|
||||
url: "wss://gateway.example.com",
|
||||
tlsFingerprint: "SHA256:AA:BB",
|
||||
});
|
||||
client.start();
|
||||
const last = wsMockState.last as { opts: { agent?: unknown } } | null;
|
||||
|
||||
expect(last?.opts.agent).toBeUndefined();
|
||||
});
|
||||
|
||||
test("scopes Gateway loopback bypass to WebSocket connection setup without mutating NO_PROXY", () => {
|
||||
process.env["NO_PROXY"] = "corp.example.com";
|
||||
process.env["no_proxy"] = "corp.example.com";
|
||||
const registration = registerActiveManagedProxyUrl(
|
||||
new URL("http://127.0.0.1:3128"),
|
||||
"gateway-only",
|
||||
);
|
||||
setCorporateNoProxy();
|
||||
const registration = registerGatewayOnlyProxy();
|
||||
|
||||
try {
|
||||
const client = new GatewayClient({ url: "ws://127.0.0.1:18789" });
|
||||
client.start();
|
||||
const last = wsMockState.last as { noProxyDuringConstruction: unknown } | null;
|
||||
const last = startGatewayClient({ url: "ws://127.0.0.1:18789" }) as {
|
||||
noProxyDuringConstruction: unknown;
|
||||
} | null;
|
||||
|
||||
expect(last?.noProxyDuringConstruction).toBe("corp.example.com");
|
||||
expect(process.env["NO_PROXY"]).toBe("corp.example.com");
|
||||
@@ -184,19 +190,12 @@ describe("GatewayClient", () => {
|
||||
});
|
||||
|
||||
test("scopes IPv6 loopback bypass during Gateway-only proxy mode connection setup", () => {
|
||||
process.env["NO_PROXY"] = "corp.example.com";
|
||||
process.env["no_proxy"] = "corp.example.com";
|
||||
process.env["HTTP_PROXY"] = "http://127.0.0.1:3128";
|
||||
process.env["HTTPS_PROXY"] = "http://127.0.0.1:3128";
|
||||
const registration = registerActiveManagedProxyUrl(
|
||||
new URL("http://127.0.0.1:3128"),
|
||||
"gateway-only",
|
||||
);
|
||||
setCorporateNoProxy();
|
||||
setGatewayOnlyProxyEnv();
|
||||
const registration = registerGatewayOnlyProxy();
|
||||
|
||||
try {
|
||||
const client = new GatewayClient({ url: "ws://[::1]:18789" });
|
||||
client.start();
|
||||
const last = wsMockState.last as {
|
||||
const last = startGatewayClient({ url: "ws://[::1]:18789" }) as {
|
||||
noProxyDuringConstruction: unknown;
|
||||
httpProxyDuringConstruction: unknown;
|
||||
httpsProxyDuringConstruction: unknown;
|
||||
@@ -306,6 +305,24 @@ function makeOperatorWsClient(connId: string, socket: TestSocket, scopes: string
|
||||
} as GatewayWsClient["connect"]);
|
||||
}
|
||||
|
||||
function makeOperatorWsClients(
|
||||
entries: Array<{ connId: string; socket: TestSocket; scopes: string[] }>,
|
||||
) {
|
||||
return new Set<GatewayWsClient>(
|
||||
entries.map(({ connId, socket, scopes }) => makeOperatorWsClient(connId, socket, scopes)),
|
||||
);
|
||||
}
|
||||
|
||||
function makeReadPairClients(
|
||||
first: { connId: string; socket: TestSocket; scopes: string[] },
|
||||
readSocket: TestSocket,
|
||||
) {
|
||||
return makeOperatorWsClients([
|
||||
first,
|
||||
{ connId: "c-read", socket: readSocket, scopes: ["operator.read"] },
|
||||
]);
|
||||
}
|
||||
|
||||
function makeScopedBroadcastClients() {
|
||||
const pairingSocket = makeRecordingSocket();
|
||||
const nodeSocket = makeRecordingSocket();
|
||||
@@ -326,6 +343,42 @@ function makeScopedBroadcastClients() {
|
||||
return { pairingSocket, nodeSocket, readSocket, writeSocket, adminSocket, clients };
|
||||
}
|
||||
|
||||
function makeScopedBroadcastContext() {
|
||||
const scoped = makeScopedBroadcastClients();
|
||||
return {
|
||||
...scoped,
|
||||
...createGatewayBroadcaster({ clients: scoped.clients }),
|
||||
};
|
||||
}
|
||||
|
||||
function sentEvents(socket: RecordingSocket) {
|
||||
return socket.sent.map((frame) => frame.event);
|
||||
}
|
||||
|
||||
function expectSentEvents(socket: RecordingSocket, events: string[]) {
|
||||
expect(sentEvents(socket)).toEqual(events);
|
||||
}
|
||||
|
||||
function sentEventSeq(socket: RecordingSocket) {
|
||||
return socket.sent.map((frame) => [frame.event, frame.seq]);
|
||||
}
|
||||
|
||||
function chatPayload() {
|
||||
return { sessionKey: "agent:main:main", message: "secret" };
|
||||
}
|
||||
|
||||
function chatSideResultPayload() {
|
||||
return { sessionKey: "agent:main:main", text: "tool output" };
|
||||
}
|
||||
|
||||
function broadcastChatClassEvents(
|
||||
broadcast: ReturnType<typeof createGatewayBroadcaster>["broadcast"],
|
||||
) {
|
||||
broadcast("chat", chatPayload());
|
||||
broadcast("agent", { type: "status", sessionKey: "agent:main:main" });
|
||||
broadcast("chat.side_result", chatSideResultPayload());
|
||||
}
|
||||
|
||||
describe("gateway broadcaster", () => {
|
||||
it("filters approval and pairing events by scope", () => {
|
||||
const approvalsSocket: TestSocket = {
|
||||
@@ -367,42 +420,25 @@ describe("gateway broadcaster", () => {
|
||||
});
|
||||
|
||||
it("requires operator.read for chat-class broadcast events", () => {
|
||||
const { pairingSocket, nodeSocket, readSocket, writeSocket, adminSocket, clients } =
|
||||
makeScopedBroadcastClients();
|
||||
const { pairingSocket, nodeSocket, readSocket, writeSocket, adminSocket, broadcast } =
|
||||
makeScopedBroadcastContext();
|
||||
|
||||
const { broadcast } = createGatewayBroadcaster({ clients });
|
||||
|
||||
broadcast("chat", { sessionKey: "agent:main:main", message: "secret" });
|
||||
broadcast("agent", { type: "status", sessionKey: "agent:main:main" });
|
||||
broadcast("chat.side_result", { sessionKey: "agent:main:main", text: "tool output" });
|
||||
broadcastChatClassEvents(broadcast);
|
||||
|
||||
expect(pairingSocket.send).not.toHaveBeenCalled();
|
||||
expect(nodeSocket.send).not.toHaveBeenCalled();
|
||||
expect(readSocket.send).toHaveBeenCalledTimes(3);
|
||||
expect(writeSocket.send).toHaveBeenCalledTimes(3);
|
||||
expect(adminSocket.send).toHaveBeenCalledTimes(3);
|
||||
expect(readSocket.sent.map((frame) => frame.event)).toEqual([
|
||||
"chat",
|
||||
"agent",
|
||||
"chat.side_result",
|
||||
]);
|
||||
expect(writeSocket.sent.map((frame) => frame.event)).toEqual([
|
||||
"chat",
|
||||
"agent",
|
||||
"chat.side_result",
|
||||
]);
|
||||
expect(adminSocket.sent.map((frame) => frame.event)).toEqual([
|
||||
"chat",
|
||||
"agent",
|
||||
"chat.side_result",
|
||||
]);
|
||||
const expectedEvents = ["chat", "agent", "chat.side_result"];
|
||||
expectSentEvents(readSocket, expectedEvents);
|
||||
expectSentEvents(writeSocket, expectedEvents);
|
||||
expectSentEvents(adminSocket, expectedEvents);
|
||||
});
|
||||
|
||||
it("allows plugin.* broadcast events for operator.write and operator.admin", () => {
|
||||
const { pairingSocket, nodeSocket, readSocket, writeSocket, adminSocket, clients } =
|
||||
makeScopedBroadcastClients();
|
||||
|
||||
const { broadcast } = createGatewayBroadcaster({ clients });
|
||||
const { pairingSocket, nodeSocket, readSocket, writeSocket, adminSocket, broadcast } =
|
||||
makeScopedBroadcastContext();
|
||||
|
||||
broadcast("plugin.myplugin.custom", { data: "test" });
|
||||
broadcast("plugin.otherplugin.state", { state: "updated" });
|
||||
@@ -412,21 +448,14 @@ describe("gateway broadcaster", () => {
|
||||
expect(readSocket.send).not.toHaveBeenCalled();
|
||||
expect(writeSocket.send).toHaveBeenCalledTimes(2);
|
||||
expect(adminSocket.send).toHaveBeenCalledTimes(2);
|
||||
expect(writeSocket.sent.map((frame) => frame.event)).toEqual([
|
||||
"plugin.myplugin.custom",
|
||||
"plugin.otherplugin.state",
|
||||
]);
|
||||
expect(adminSocket.sent.map((frame) => frame.event)).toEqual([
|
||||
"plugin.myplugin.custom",
|
||||
"plugin.otherplugin.state",
|
||||
]);
|
||||
const expectedEvents = ["plugin.myplugin.custom", "plugin.otherplugin.state"];
|
||||
expectSentEvents(writeSocket, expectedEvents);
|
||||
expectSentEvents(adminSocket, expectedEvents);
|
||||
});
|
||||
|
||||
it("defaults unknown events to deny and classifies remaining gateway broadcast events", () => {
|
||||
const { pairingSocket, nodeSocket, readSocket, writeSocket, adminSocket, clients } =
|
||||
makeScopedBroadcastClients();
|
||||
|
||||
const { broadcast } = createGatewayBroadcaster({ clients });
|
||||
const { pairingSocket, nodeSocket, readSocket, writeSocket, adminSocket, broadcast } =
|
||||
makeScopedBroadcastContext();
|
||||
|
||||
broadcast("cron", { jobId: "job-1" });
|
||||
broadcast("talk.mode", { enabled: true });
|
||||
@@ -440,7 +469,7 @@ describe("gateway broadcaster", () => {
|
||||
broadcast("update.available", { updateAvailable: { version: "2026.4.20" } });
|
||||
broadcast("unknown.future.event", { hidden: true });
|
||||
|
||||
expect(pairingSocket.sent.map((frame) => frame.event)).toEqual([
|
||||
expectSentEvents(pairingSocket, [
|
||||
"heartbeat",
|
||||
"presence",
|
||||
"health",
|
||||
@@ -448,7 +477,7 @@ describe("gateway broadcaster", () => {
|
||||
"shutdown",
|
||||
"update.available",
|
||||
]);
|
||||
expect(nodeSocket.sent.map((frame) => frame.event)).toEqual([
|
||||
expectSentEvents(nodeSocket, [
|
||||
"voicewake.changed",
|
||||
"voicewake.routing.changed",
|
||||
"heartbeat",
|
||||
@@ -458,7 +487,7 @@ describe("gateway broadcaster", () => {
|
||||
"shutdown",
|
||||
"update.available",
|
||||
]);
|
||||
expect(readSocket.sent.map((frame) => frame.event)).toEqual([
|
||||
expectSentEvents(readSocket, [
|
||||
"cron",
|
||||
"voicewake.changed",
|
||||
"voicewake.routing.changed",
|
||||
@@ -469,7 +498,7 @@ describe("gateway broadcaster", () => {
|
||||
"shutdown",
|
||||
"update.available",
|
||||
]);
|
||||
expect(writeSocket.sent.map((frame) => frame.event)).toEqual([
|
||||
const writeVisibleEvents = [
|
||||
"cron",
|
||||
"talk.mode",
|
||||
"voicewake.changed",
|
||||
@@ -480,42 +509,32 @@ describe("gateway broadcaster", () => {
|
||||
"tick",
|
||||
"shutdown",
|
||||
"update.available",
|
||||
]);
|
||||
expect(adminSocket.sent.map((frame) => frame.event)).toEqual([
|
||||
"cron",
|
||||
"talk.mode",
|
||||
"voicewake.changed",
|
||||
"voicewake.routing.changed",
|
||||
"heartbeat",
|
||||
"presence",
|
||||
"health",
|
||||
"tick",
|
||||
"shutdown",
|
||||
"update.available",
|
||||
]);
|
||||
];
|
||||
expectSentEvents(writeSocket, writeVisibleEvents);
|
||||
expectSentEvents(adminSocket, writeVisibleEvents);
|
||||
});
|
||||
|
||||
it("keeps event seq contiguous per receiving client when scoped events are filtered", () => {
|
||||
const pairingSocket = makeRecordingSocket();
|
||||
const readSocket = makeRecordingSocket();
|
||||
|
||||
const clients = new Set<GatewayWsClient>([
|
||||
makeOperatorWsClient("c-pairing", pairingSocket, ["operator.pairing"]),
|
||||
makeOperatorWsClient("c-read", readSocket, ["operator.read"]),
|
||||
]);
|
||||
const clients = makeReadPairClients(
|
||||
{ connId: "c-pairing", socket: pairingSocket, scopes: ["operator.pairing"] },
|
||||
readSocket,
|
||||
);
|
||||
|
||||
const { broadcast } = createGatewayBroadcaster({ clients });
|
||||
|
||||
broadcast("chat", { sessionKey: "agent:main:main", message: "secret" });
|
||||
broadcast("chat", chatPayload());
|
||||
broadcast("heartbeat", { ts: 1 });
|
||||
broadcast("chat.side_result", { sessionKey: "agent:main:main", text: "tool output" });
|
||||
broadcast("chat.side_result", chatSideResultPayload());
|
||||
broadcast("tick", { ts: 2 });
|
||||
|
||||
expect(pairingSocket.sent.map((frame) => [frame.event, frame.seq])).toEqual([
|
||||
expect(sentEventSeq(pairingSocket)).toEqual([
|
||||
["heartbeat", 1],
|
||||
["tick", 2],
|
||||
]);
|
||||
expect(readSocket.sent.map((frame) => [frame.event, frame.seq])).toEqual([
|
||||
expect(sentEventSeq(readSocket)).toEqual([
|
||||
["chat", 1],
|
||||
["heartbeat", 2],
|
||||
["chat.side_result", 3],
|
||||
@@ -527,10 +546,10 @@ describe("gateway broadcaster", () => {
|
||||
const firstSocket = makeRecordingSocket();
|
||||
const secondSocket = makeRecordingSocket();
|
||||
const thirdSocket = makeRecordingSocket();
|
||||
const clients = new Set<GatewayWsClient>([
|
||||
makeOperatorWsClient("c-1", firstSocket, ["operator.read"]),
|
||||
makeOperatorWsClient("c-2", secondSocket, ["operator.write"]),
|
||||
makeOperatorWsClient("c-3", thirdSocket, ["operator.admin"]),
|
||||
const clients = makeOperatorWsClients([
|
||||
{ connId: "c-1", socket: firstSocket, scopes: ["operator.read"] },
|
||||
{ connId: "c-2", socket: secondSocket, scopes: ["operator.write"] },
|
||||
{ connId: "c-3", socket: thirdSocket, scopes: ["operator.admin"] },
|
||||
]);
|
||||
const payloadKeys: string[] = [];
|
||||
const payload = {
|
||||
@@ -560,21 +579,19 @@ describe("gateway broadcaster", () => {
|
||||
slowReadSocket.bufferedAmount = Number.MAX_SAFE_INTEGER;
|
||||
const readSocket = makeRecordingSocket();
|
||||
|
||||
const clients = new Set<GatewayWsClient>([
|
||||
makeOperatorWsClient("c-slow-read", slowReadSocket, ["operator.read"]),
|
||||
makeOperatorWsClient("c-read", readSocket, ["operator.read"]),
|
||||
]);
|
||||
const clients = makeReadPairClients(
|
||||
{ connId: "c-slow-read", socket: slowReadSocket, scopes: ["operator.read"] },
|
||||
readSocket,
|
||||
);
|
||||
|
||||
const { broadcast } = createGatewayBroadcaster({ clients });
|
||||
|
||||
broadcast("chat", { sessionKey: "agent:main:main", message: "secret" }, { dropIfSlow: true });
|
||||
broadcast("chat", chatPayload(), { dropIfSlow: true });
|
||||
slowReadSocket.bufferedAmount = 0;
|
||||
broadcast("heartbeat", { ts: 1 });
|
||||
|
||||
expect(slowReadSocket.sent.map((frame) => [frame.event, frame.seq])).toEqual([
|
||||
["heartbeat", 2],
|
||||
]);
|
||||
expect(readSocket.sent.map((frame) => [frame.event, frame.seq])).toEqual([
|
||||
expect(sentEventSeq(slowReadSocket)).toEqual([["heartbeat", 2]]);
|
||||
expect(sentEventSeq(readSocket)).toEqual([
|
||||
["chat", 1],
|
||||
["heartbeat", 2],
|
||||
]);
|
||||
@@ -593,7 +610,7 @@ describe("gateway broadcaster", () => {
|
||||
|
||||
const { broadcast } = createGatewayBroadcaster({ clients });
|
||||
|
||||
broadcast("chat", { sessionKey: "agent:main:main", message: "secret" }, { dropIfSlow: true });
|
||||
broadcast("chat", chatPayload(), { dropIfSlow: true });
|
||||
broadcast("heartbeat", { ts: 1 });
|
||||
|
||||
const payloadEvent = events.find((event) => event.type === "payload.large");
|
||||
@@ -755,6 +772,22 @@ describe("node subscription manager", () => {
|
||||
});
|
||||
|
||||
describe("resolveNodeCommandAllowlist", () => {
|
||||
function expectAllowed(allow: { has: (cmd: string) => boolean }, commands: string[]) {
|
||||
for (const cmd of commands) {
|
||||
expect(allow.has(cmd)).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
function expectDenied(allow: { has: (cmd: string) => boolean }, commands: string[]) {
|
||||
for (const cmd of commands) {
|
||||
expect(allow.has(cmd)).toBe(false);
|
||||
}
|
||||
}
|
||||
|
||||
function expectDangerousCommandsDenied(allow: { has: (cmd: string) => boolean }) {
|
||||
expectDenied(allow, DEFAULT_DANGEROUS_NODE_COMMANDS);
|
||||
}
|
||||
|
||||
it("includes iOS service commands by default", () => {
|
||||
const allow = resolveNodeCommandAllowlist(
|
||||
{},
|
||||
@@ -764,18 +797,17 @@ describe("resolveNodeCommandAllowlist", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(allow.has("device.info")).toBe(true);
|
||||
expect(allow.has("device.status")).toBe(true);
|
||||
expect(allow.has("system.notify")).toBe(true);
|
||||
expect(allow.has("contacts.search")).toBe(true);
|
||||
expect(allow.has("calendar.events")).toBe(true);
|
||||
expect(allow.has("reminders.list")).toBe(true);
|
||||
expect(allow.has("photos.latest")).toBe(true);
|
||||
expect(allow.has("motion.activity")).toBe(true);
|
||||
|
||||
for (const cmd of DEFAULT_DANGEROUS_NODE_COMMANDS) {
|
||||
expect(allow.has(cmd)).toBe(false);
|
||||
}
|
||||
expectAllowed(allow, [
|
||||
"device.info",
|
||||
"device.status",
|
||||
"system.notify",
|
||||
"contacts.search",
|
||||
"calendar.events",
|
||||
"reminders.list",
|
||||
"photos.latest",
|
||||
"motion.activity",
|
||||
]);
|
||||
expectDangerousCommandsDenied(allow);
|
||||
});
|
||||
|
||||
it("includes Android notifications and device diagnostics commands by default", () => {
|
||||
@@ -787,13 +819,15 @@ describe("resolveNodeCommandAllowlist", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(allow.has("notifications.list")).toBe(true);
|
||||
expect(allow.has("notifications.actions")).toBe(true);
|
||||
expect(allow.has("device.permissions")).toBe(true);
|
||||
expect(allow.has("device.health")).toBe(true);
|
||||
expect(allow.has("callLog.search")).toBe(true);
|
||||
expect(allow.has("system.notify")).toBe(true);
|
||||
expect(allow.has("sms.search")).toBe(false);
|
||||
expectAllowed(allow, [
|
||||
"notifications.list",
|
||||
"notifications.actions",
|
||||
"device.permissions",
|
||||
"device.health",
|
||||
"callLog.search",
|
||||
"system.notify",
|
||||
]);
|
||||
expectDenied(allow, ["sms.search"]);
|
||||
});
|
||||
|
||||
it("treats sms.search as dangerous by default", () => {
|
||||
@@ -826,20 +860,18 @@ describe("resolveNodeCommandAllowlist", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(allow.has("canvas.present")).toBe(false);
|
||||
expect(allow.has("canvas.a2ui.pushJSONL")).toBe(false);
|
||||
expect(allow.has("camera.list")).toBe(true);
|
||||
expect(allow.has("location.get")).toBe(true);
|
||||
expect(allow.has("device.info")).toBe(true);
|
||||
expect(allow.has("device.status")).toBe(true);
|
||||
expect(allow.has("screen.snapshot")).toBe(true);
|
||||
expect(allow.has("system.run")).toBe(true);
|
||||
expect(allow.has("system.which")).toBe(true);
|
||||
expect(allow.has("system.notify")).toBe(true);
|
||||
|
||||
for (const cmd of DEFAULT_DANGEROUS_NODE_COMMANDS) {
|
||||
expect(allow.has(cmd)).toBe(false);
|
||||
}
|
||||
expectDenied(allow, ["canvas.present", "canvas.a2ui.pushJSONL"]);
|
||||
expectAllowed(allow, [
|
||||
"camera.list",
|
||||
"location.get",
|
||||
"device.info",
|
||||
"device.status",
|
||||
"screen.snapshot",
|
||||
"system.run",
|
||||
"system.which",
|
||||
"system.notify",
|
||||
]);
|
||||
expectDangerousCommandsDenied(allow);
|
||||
});
|
||||
|
||||
it("can explicitly allow dangerous commands via allowCommands", () => {
|
||||
@@ -867,9 +899,8 @@ describe("resolveNodeCommandAllowlist", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(allow.has("system.run")).toBe(false);
|
||||
expect(allow.has("system.which")).toBe(false);
|
||||
expect(allow.has("system.notify")).toBe(true);
|
||||
expectDenied(allow, ["system.run", "system.which"]);
|
||||
expectAllowed(allow, ["system.notify"]);
|
||||
});
|
||||
|
||||
it("normalizes dotted-I platform values to iOS classification", () => {
|
||||
@@ -881,9 +912,8 @@ describe("resolveNodeCommandAllowlist", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(allow.has("system.run")).toBe(false);
|
||||
expect(allow.has("system.which")).toBe(false);
|
||||
expect(allow.has("device.info")).toBe(true);
|
||||
expectDenied(allow, ["system.run", "system.which"]);
|
||||
expectAllowed(allow, ["device.info"]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user