277 lines
7.1 KiB
TypeScript
277 lines
7.1 KiB
TypeScript
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");
|
|
});
|
|
}); |