106 lines
3.4 KiB
TypeScript
106 lines
3.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { isUiEnvelopeAllowed, parseUiEnvelope, parseUiRegistry } from "./validator";
|
|
import type { UIRegistry } from "./types";
|
|
|
|
const registry: UIRegistry = {
|
|
schema_version: "agent-ui-registry@1",
|
|
chart_grammars: ["echarts-safe-subset"],
|
|
components: [{ id: "HistoryPanel", supportedSurfaces: ["side_panel", "canvas"] }],
|
|
actions: [{ id: "zoom_to_map", supportedSurfaces: ["map_overlay"] }]
|
|
};
|
|
|
|
describe("UIEnvelope validation", () => {
|
|
it("fails closed when the backend registry is unavailable", () => {
|
|
const envelope = parseUiEnvelope({
|
|
kind: "chart",
|
|
schemaVersion: "agent-ui@1",
|
|
grammar: "echarts-safe-subset",
|
|
surface: "chat_inline",
|
|
spec: { chart_type: "line" },
|
|
data: { x_data: ["a"], series: [{ name: "s", data: [1] }] }
|
|
});
|
|
expect(envelope).not.toBeNull();
|
|
expect(envelope ? isUiEnvelopeAllowed(envelope, null) : true).toBe(false);
|
|
});
|
|
|
|
it("rejects registered components outside the frontend allowlist", () => {
|
|
const envelope = parseUiEnvelope({
|
|
kind: "registered_component",
|
|
schemaVersion: "agent-ui@1",
|
|
component: "UnsafePanel",
|
|
surface: "side_panel",
|
|
props: {}
|
|
});
|
|
|
|
expect(envelope).toBeNull();
|
|
});
|
|
|
|
it("rejects registered components on unsupported surfaces", () => {
|
|
const envelope = parseUiEnvelope({
|
|
kind: "registered_component",
|
|
schemaVersion: "agent-ui@1",
|
|
component: "HistoryPanel",
|
|
surface: "chat_inline",
|
|
props: { feature_infos: [["J1", "junction"]], data_type: "realtime" }
|
|
});
|
|
|
|
expect(envelope).not.toBeNull();
|
|
expect(envelope ? isUiEnvelopeAllowed(envelope, registry) : false).toBe(false);
|
|
});
|
|
|
|
it("accepts only the echarts-safe-subset chart grammar", () => {
|
|
expect(
|
|
parseUiEnvelope({
|
|
kind: "chart",
|
|
schemaVersion: "agent-ui@1",
|
|
grammar: "vega-lite",
|
|
surface: "chat_inline",
|
|
spec: { chart_type: "line" },
|
|
data: { x_data: ["a"], series: [{ name: "s", data: [1] }] }
|
|
})
|
|
).toBeNull();
|
|
|
|
const envelope = parseUiEnvelope({
|
|
kind: "chart",
|
|
schemaVersion: "agent-ui@1",
|
|
grammar: "echarts-safe-subset",
|
|
surface: "chat_inline",
|
|
spec: { chart_type: "line" },
|
|
data: { x_data: ["a"], series: [{ name: "s", data: [1] }] }
|
|
});
|
|
|
|
expect(envelope).not.toBeNull();
|
|
expect(envelope ? isUiEnvelopeAllowed(envelope, registry) : false).toBe(true);
|
|
});
|
|
|
|
it("rejects unsupported chart types and mismatched data", () => {
|
|
expect(parseUiEnvelope({
|
|
kind: "chart",
|
|
schemaVersion: "agent-ui@1",
|
|
grammar: "echarts-safe-subset",
|
|
surface: "chat_inline",
|
|
spec: { chart_type: "pie" },
|
|
data: { x_data: ["a"], series: [{ name: "s", data: [1] }] }
|
|
})).toBeNull();
|
|
expect(parseUiEnvelope({
|
|
kind: "chart",
|
|
schemaVersion: "agent-ui@1",
|
|
grammar: "echarts-safe-subset",
|
|
surface: "chat_inline",
|
|
spec: { chart_type: "line" },
|
|
data: { x_data: ["a", "b"], series: [{ name: "s", data: [1] }] }
|
|
})).toBeNull();
|
|
});
|
|
|
|
it("filters invalid registry surfaces before allowlist checks", () => {
|
|
const parsed = parseUiRegistry({
|
|
schema_version: "agent-ui-registry@1",
|
|
chart_grammars: ["echarts-safe-subset"],
|
|
components: [{ id: "HistoryPanel", supportedSurfaces: ["side_panel", "unsafe_surface"] }],
|
|
actions: []
|
|
});
|
|
|
|
expect(parsed?.components[0]?.supportedSurfaces).toEqual(["side_panel"]);
|
|
});
|
|
});
|