feat(schema): add edge attribute schemas and CallResult schema

This commit is contained in:
2026-05-21 20:59:11 +00:00
parent c1c1c012d4
commit e2a7d889c6
3 changed files with 328 additions and 1 deletions

277
test/schema/edge.test.ts Normal file
View File

@@ -0,0 +1,277 @@
import { describe, it, expect } from "vitest";
import { Value } from "@alkdev/typebox/value";
import {
OperationEdgeAttrs,
type OperationEdgeAttrs as OperationEdgeAttrsType,
TriggeredEdgeAttrs,
DependencyEdgeAttrs,
TemplateEdgeAttrs,
type TemplateEdgeAttrs as TemplateEdgeAttrsType,
CallResultSchema,
type CallResult,
} from "../../src/schema/edge";
import { type OperationNodeAttrs } from "../../src/schema/node";
describe("OperationEdgeAttrs", () => {
const valid: OperationEdgeAttrsType = {
compatible: true,
};
it("accepts valid attributes with no optional fields", () => {
expect(Value.Check(OperationEdgeAttrs, valid)).toBe(true);
});
it("accepts compatible false", () => {
expect(Value.Check(OperationEdgeAttrs, { compatible: false })).toBe(true);
});
it("accepts valid attributes with all optional fields", () => {
const withOptional: OperationEdgeAttrsType = {
compatible: false,
detail: "Output schema mismatch",
mismatches: [
{ path: "/properties/name", expected: "string", actual: "number" },
],
};
expect(Value.Check(OperationEdgeAttrs, withOptional)).toBe(true);
});
it("accepts empty mismatches array", () => {
expect(
Value.Check(OperationEdgeAttrs, { compatible: true, mismatches: [] }),
).toBe(true);
});
it("rejects missing required compatible field", () => {
expect(Value.Check(OperationEdgeAttrs, {})).toBe(false);
});
it("rejects wrong type for compatible", () => {
expect(Value.Check(OperationEdgeAttrs, { compatible: "yes" })).toBe(false);
});
it("rejects wrong type for detail", () => {
expect(
Value.Check(OperationEdgeAttrs, { compatible: true, detail: 42 }),
).toBe(false);
});
it("rejects mismatches with missing required fields", () => {
expect(
Value.Check(OperationEdgeAttrs, {
compatible: false,
mismatches: [{ path: "/x" }],
}),
).toBe(false);
});
});
describe("TriggeredEdgeAttrs", () => {
it("accepts empty object", () => {
expect(Value.Check(TriggeredEdgeAttrs, {})).toBe(true);
});
it("rejects non-object", () => {
expect(Value.Check(TriggeredEdgeAttrs, null)).toBe(false);
expect(Value.Check(TriggeredEdgeAttrs, "x")).toBe(false);
});
});
describe("DependencyEdgeAttrs", () => {
it("accepts empty object", () => {
expect(Value.Check(DependencyEdgeAttrs, {})).toBe(true);
});
it("rejects non-object", () => {
expect(Value.Check(DependencyEdgeAttrs, null)).toBe(false);
expect(Value.Check(DependencyEdgeAttrs, 1)).toBe(false);
});
});
describe("TemplateEdgeAttrs", () => {
const valid: TemplateEdgeAttrsType = {
edgeType: "sequential",
};
it("accepts valid sequential edge", () => {
expect(Value.Check(TemplateEdgeAttrs, valid)).toBe(true);
});
it("accepts conditional edge", () => {
expect(
Value.Check(TemplateEdgeAttrs, { edgeType: "conditional" }),
).toBe(true);
});
it("rejected edgeType values are rejected", () => {
expect(
Value.Check(TemplateEdgeAttrs, { edgeType: "triggered" }),
).toBe(false);
expect(
Value.Check(TemplateEdgeAttrs, { edgeType: "depends_on" }),
).toBe(false);
expect(
Value.Check(TemplateEdgeAttrs, { edgeType: "typed" }),
).toBe(false);
});
it("accepts optional condition (string)", () => {
expect(
Value.Check(TemplateEdgeAttrs, {
edgeType: "conditional",
condition: "fetch-data",
}),
).toBe(true);
});
it("accepts optional condition (function)", () => {
expect(
Value.Check(TemplateEdgeAttrs, {
edgeType: "conditional",
condition: ((results: Record<string, unknown>) => true) as unknown,
}),
).toBe(true);
});
it("accepts optional negated", () => {
expect(
Value.Check(TemplateEdgeAttrs, {
edgeType: "conditional",
negated: true,
}),
).toBe(true);
});
it("accepts optional dataFlow", () => {
expect(
Value.Check(TemplateEdgeAttrs, {
edgeType: "sequential",
dataFlow: true,
}),
).toBe(true);
expect(
Value.Check(TemplateEdgeAttrs, {
edgeType: "sequential",
dataFlow: false,
}),
).toBe(true);
});
it("accepts all optional fields together", () => {
const full: TemplateEdgeAttrsType = {
edgeType: "conditional",
condition: "fetch-data",
negated: true,
dataFlow: true,
};
expect(Value.Check(TemplateEdgeAttrs, full)).toBe(true);
});
it("rejects missing required edgeType", () => {
expect(Value.Check(TemplateEdgeAttrs, {})).toBe(false);
});
it("rejects invalid edgeType", () => {
expect(Value.Check(TemplateEdgeAttrs, { edgeType: "parallel" })).toBe(
false,
);
});
it("rejects wrong type for negated", () => {
expect(
Value.Check(TemplateEdgeAttrs, { edgeType: "sequential", negated: 1 }),
).toBe(false);
});
it("rejects wrong type for dataFlow", () => {
expect(
Value.Check(TemplateEdgeAttrs, {
edgeType: "sequential",
dataFlow: "yes",
}),
).toBe(false);
});
});
describe("CallResultSchema", () => {
const valid: CallResult = {
status: "completed",
output: { label: "greeting" },
};
it("accepts valid result without optional error", () => {
expect(Value.Check(CallResultSchema, valid)).toBe(true);
});
it("accepts valid result with error", () => {
const withError: CallResult = {
status: "failed",
output: null,
error: { code: "INTERNAL", message: "Something went wrong" },
};
expect(Value.Check(CallResultSchema, withError)).toBe(true);
});
it("accepts error with optional details", () => {
const withDetails: CallResult = {
status: "failed",
output: null,
error: {
code: "INTERNAL",
message: "Error",
details: { stack: "..." },
},
};
expect(Value.Check(CallResultSchema, withDetails)).toBe(true);
});
it("accepts all valid statuses", () => {
for (const status of [
"idle",
"waiting",
"ready",
"running",
"completed",
"failed",
"skipped",
"aborted",
] as const) {
expect(Value.Check(CallResultSchema, { ...valid, status })).toBe(true);
}
});
it("rejects missing required status", () => {
expect(Value.Check(CallResultSchema, { output: {} })).toBe(false);
});
it("rejects invalid status", () => {
expect(
Value.Check(CallResultSchema, { status: "pending", output: null }),
).toBe(false);
});
it("rejects error with missing required fields", () => {
expect(
Value.Check(CallResultSchema, {
status: "failed",
output: null,
error: { code: "X" },
}),
).toBe(false);
});
});
describe("TemplateNodeAttrs type alias", () => {
it("TemplateNodeAttrs is an alias for OperationNodeAttrs type", () => {
const attrs: OperationNodeAttrs = {
name: "classify",
namespace: "task",
version: "1.0.0",
type: "query",
inputSchema: { type: "object" },
outputSchema: { type: "object" },
};
expect(attrs.name).toBe("classify");
expect(attrs.type).toBe("query");
});
});