import { describe, it, expect } from "vitest"; import { Value } from "@alkdev/typebox/value"; import { Type } from "@alkdev/typebox"; import { SerializedGraph, OperationGraphSerialized, type OperationGraphSerialized as OperationGraphSerializedType, CallGraphSerialized, type CallGraphSerialized as CallGraphSerializedType, type FlowGraphSerialized, } from "../../src/schema/graph"; describe("SerializedGraph", () => { it("produces a valid TypeBox schema with custom node/edge/graph attrs", () => { const schema = SerializedGraph( Type.Object({ label: Type.String() }), Type.Object({ weight: Type.Number() }), Type.Object({ name: Type.String() }), ); const valid = { attributes: { name: "test-graph" }, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [{ key: "a", attributes: { label: "node-a" } }], edges: [ { key: "a->b", source: "a", target: "b", attributes: { weight: 1 } }, ], }; expect(Value.Check(schema, valid)).toBe(true); }); it("rejects missing required fields", () => { const schema = SerializedGraph( Type.Object({ label: Type.String() }), Type.Object({}), Type.Object({}), ); expect(Value.Check(schema, {})).toBe(false); expect(Value.Check(schema, { attributes: {} })).toBe(false); expect(Value.Check(schema, { attributes: {}, options: {} })).toBe(false); }); it("rejects wrong options type", () => { const schema = SerializedGraph( Type.Object({}), Type.Object({}), Type.Object({}), ); expect( Value.Check(schema, { attributes: {}, options: { type: "undirected", multi: false, allowSelfLoops: false }, nodes: [], edges: [], }), ).toBe(false); expect( Value.Check(schema, { attributes: {}, options: { type: "directed", multi: true, allowSelfLoops: false }, nodes: [], edges: [], }), ).toBe(false); expect( Value.Check(schema, { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: true }, nodes: [], edges: [], }), ).toBe(false); }); it("rejects invalid node attributes", () => { const schema = SerializedGraph( Type.Object({ label: Type.String() }), Type.Object({}), Type.Object({}), ); expect( Value.Check(schema, { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [{ key: "a", attributes: { label: 42 } }], edges: [], }), ).toBe(false); }); it("rejects invalid edge attributes", () => { const schema = SerializedGraph( Type.Object({}), Type.Object({ weight: Type.Number() }), Type.Object({}), ); expect( Value.Check(schema, { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [], edges: [ { key: "a->b", source: "a", target: "b", attributes: { weight: "heavy" } }, ], }), ).toBe(false); }); it("rejects missing edge key/source/target", () => { const schema = SerializedGraph( Type.Object({}), Type.Object({}), Type.Object({}), ); expect( Value.Check(schema, { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [], edges: [{ source: "a", target: "b", attributes: {} }], }), ).toBe(false); expect( Value.Check(schema, { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [], edges: [{ key: "a->b", target: "b", attributes: {} }], }), ).toBe(false); }); it("accepts empty nodes and edges arrays", () => { const schema = SerializedGraph( Type.Object({}), Type.Object({}), Type.Object({}), ); expect( Value.Check(schema, { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [], edges: [], }), ).toBe(true); }); }); describe("OperationGraphSerialized", () => { const valid: OperationGraphSerializedType = { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [ { key: "task.classify", attributes: { name: "classify", namespace: "task", version: "1.0.0", type: "query", inputSchema: { type: "object" }, outputSchema: { type: "object" }, }, }, ], edges: [ { key: "task.classify->task.enrich", source: "task.classify", target: "task.enrich", attributes: { compatible: true }, }, ], }; it("accepts valid operation graph serialization", () => { expect(Value.Check(OperationGraphSerialized, valid)).toBe(true); }); it("accepts operation graph with empty nodes and edges", () => { expect( Value.Check(OperationGraphSerialized, { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [], edges: [], }), ).toBe(true); }); it("rejects operation node with missing required fields", () => { expect( Value.Check(OperationGraphSerialized, { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [{ key: "x", attributes: { name: "x" } }], edges: [], }), ).toBe(false); }); it("rejects operation edge missing required compatible field", () => { expect( Value.Check(OperationGraphSerialized, { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [], edges: [ { key: "a->b", source: "a", target: "b", attributes: {}, }, ], }), ).toBe(false); }); it("rejects wrong options type", () => { expect( Value.Check(OperationGraphSerialized, { attributes: {}, options: { type: "undirected", multi: false, allowSelfLoops: false }, nodes: [], edges: [], }), ).toBe(false); }); }); describe("CallGraphSerialized", () => { const valid: CallGraphSerializedType = { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [ { key: "req_abc123", attributes: { requestId: "req_abc123", operationId: "task.classify", status: "pending", input: { text: "hello" }, }, }, ], edges: [ { key: "req_abc123->req_def456", source: "req_abc123", target: "req_def456", attributes: {}, }, ], }; it("accepts valid call graph serialization", () => { expect(Value.Check(CallGraphSerialized, valid)).toBe(true); }); it("accepts call graph with depends_on edge key convention", () => { const withDepends: CallGraphSerializedType = { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [ { key: "req_abc123", attributes: { requestId: "req_abc123", operationId: "task.classify", status: "completed", input: {}, }, }, { key: "req_def456", attributes: { requestId: "req_def456", operationId: "task.enrich", status: "running", input: {}, }, }, ], edges: [ { key: "req_abc123->req_def456", source: "req_abc123", target: "req_def456", attributes: {}, }, { key: "req_abc123->req_def456:depends_on", source: "req_abc123", target: "req_def456", attributes: {}, }, ], }; expect(Value.Check(CallGraphSerialized, withDepends)).toBe(true); }); it("accepts call graph with empty nodes and edges", () => { expect( Value.Check(CallGraphSerialized, { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [], edges: [], }), ).toBe(true); }); it("rejects call node with missing required fields", () => { expect( Value.Check(CallGraphSerialized, { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [{ key: "x", attributes: { requestId: "x" } }], edges: [], }), ).toBe(false); }); it("rejects call node with invalid status", () => { expect( Value.Check(CallGraphSerialized, { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [ { key: "req_1", attributes: { requestId: "req_1", operationId: "task.x", status: "idle", input: {}, }, }, ], edges: [], }), ).toBe(false); }); it("rejects wrong options type", () => { expect( Value.Check(CallGraphSerialized, { attributes: {}, options: { type: "undirected", multi: false, allowSelfLoops: false }, nodes: [], edges: [], }), ).toBe(false); }); }); describe("FlowGraphSerialized", () => { it("FlowGraphSerialized is a type alias for the union", () => { const op: FlowGraphSerialized = { attributes: {}, options: { type: "directed", multi: false, allowSelfLoops: false }, nodes: [], edges: [], }; expect(op.options.type).toBe("directed"); }); });