258 lines
7.9 KiB
TypeScript
258 lines
7.9 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
FlowgraphError,
|
|
ConstructionError,
|
|
DuplicateNodeError,
|
|
DuplicateEdgeError,
|
|
NodeNotFoundError,
|
|
CycleError,
|
|
InvalidInputError,
|
|
InvalidTransitionError,
|
|
} from "../../src/error/index.js";
|
|
import type {
|
|
CallStatus,
|
|
ValidationError,
|
|
GraphValidationError,
|
|
TypeMismatch,
|
|
TypeIncompatError,
|
|
AnyValidationError,
|
|
} from "../../src/error/index.js";
|
|
|
|
describe("FlowgraphError", () => {
|
|
it("constructs with correct name and message", () => {
|
|
const err = new FlowgraphError("test");
|
|
expect(err).toBeInstanceOf(Error);
|
|
expect(err).toBeInstanceOf(FlowgraphError);
|
|
expect(err.name).toBe("FlowgraphError");
|
|
expect(err.message).toBe("test");
|
|
});
|
|
});
|
|
|
|
describe("ConstructionError", () => {
|
|
it("extends FlowgraphError", () => {
|
|
const err = new ConstructionError("construction failed");
|
|
expect(err).toBeInstanceOf(Error);
|
|
expect(err).toBeInstanceOf(FlowgraphError);
|
|
expect(err).toBeInstanceOf(ConstructionError);
|
|
expect(err.name).toBe("ConstructionError");
|
|
expect(err.message).toBe("construction failed");
|
|
});
|
|
});
|
|
|
|
describe("DuplicateNodeError", () => {
|
|
it("has key property and correct name", () => {
|
|
const err = new DuplicateNodeError("nodeA");
|
|
expect(err).toBeInstanceOf(ConstructionError);
|
|
expect(err).toBeInstanceOf(FlowgraphError);
|
|
expect(err.name).toBe("DuplicateNodeError");
|
|
expect(err.key).toBe("nodeA");
|
|
expect(err.message).toBe('Node with key "nodeA" already exists');
|
|
});
|
|
});
|
|
|
|
describe("DuplicateEdgeError", () => {
|
|
it("has source and target properties", () => {
|
|
const err = new DuplicateEdgeError("A", "B");
|
|
expect(err).toBeInstanceOf(ConstructionError);
|
|
expect(err.name).toBe("DuplicateEdgeError");
|
|
expect(err.source).toBe("A");
|
|
expect(err.target).toBe("B");
|
|
expect(err.message).toBe('Edge "A -> B" already exists');
|
|
});
|
|
});
|
|
|
|
describe("NodeNotFoundError", () => {
|
|
it("has key property", () => {
|
|
const err = new NodeNotFoundError("missing");
|
|
expect(err).toBeInstanceOf(ConstructionError);
|
|
expect(err.name).toBe("NodeNotFoundError");
|
|
expect(err.key).toBe("missing");
|
|
expect(err.message).toBe('Node "missing" not found in graph');
|
|
});
|
|
});
|
|
|
|
describe("CycleError", () => {
|
|
it("has cycles property", () => {
|
|
const cycles = [["A", "B", "C", "A"]];
|
|
const err = new CycleError(cycles);
|
|
expect(err).toBeInstanceOf(ConstructionError);
|
|
expect(err.name).toBe("CycleError");
|
|
expect(err.cycles).toEqual(cycles);
|
|
expect(err.message).toContain("cycle");
|
|
});
|
|
});
|
|
|
|
describe("InvalidInputError", () => {
|
|
it("has errors property with ValidationError array", () => {
|
|
const validationErrors: ValidationError[] = [
|
|
{ type: "schema", nodeKey: "n1", field: "status", message: "invalid status" },
|
|
];
|
|
const err = new InvalidInputError(validationErrors);
|
|
expect(err).toBeInstanceOf(ConstructionError);
|
|
expect(err.name).toBe("InvalidInputError");
|
|
expect(err.errors).toEqual(validationErrors);
|
|
expect(err.message).toBe("Invalid input: 1 validation error(s)");
|
|
});
|
|
});
|
|
|
|
describe("InvalidTransitionError", () => {
|
|
it("has requestId, from, to properties", () => {
|
|
const err = new InvalidTransitionError("req-1", "completed", "running");
|
|
expect(err).toBeInstanceOf(FlowgraphError);
|
|
expect(err).not.toBeInstanceOf(ConstructionError);
|
|
expect(err.name).toBe("InvalidTransitionError");
|
|
expect(err.requestId).toBe("req-1");
|
|
expect(err.from).toBe("completed");
|
|
expect(err.to).toBe("running");
|
|
expect(err.message).toContain("req-1");
|
|
expect(err.message).toContain("completed");
|
|
expect(err.message).toContain("running");
|
|
});
|
|
});
|
|
|
|
describe("CallStatus type", () => {
|
|
it("accepts valid statuses", () => {
|
|
const statuses: CallStatus[] = ["pending", "running", "completed", "failed", "aborted"];
|
|
expect(statuses).toHaveLength(5);
|
|
});
|
|
});
|
|
|
|
describe("ValidationError interface", () => {
|
|
it("can be constructed as a plain object", () => {
|
|
const ve: ValidationError = {
|
|
type: "schema",
|
|
nodeKey: "n1",
|
|
field: "status",
|
|
message: "invalid",
|
|
};
|
|
expect(ve.type).toBe("schema");
|
|
expect(ve.nodeKey).toBe("n1");
|
|
expect(ve.field).toBe("status");
|
|
expect(ve.message).toBe("invalid");
|
|
});
|
|
|
|
it("accepts optional value", () => {
|
|
const ve: ValidationError = {
|
|
type: "schema",
|
|
nodeKey: "n1",
|
|
field: "status",
|
|
message: "invalid",
|
|
value: 42,
|
|
};
|
|
expect(ve.value).toBe(42);
|
|
});
|
|
});
|
|
|
|
describe("GraphValidationError interface", () => {
|
|
it("supports cycle category", () => {
|
|
const ge: GraphValidationError = {
|
|
type: "graph",
|
|
category: "cycle",
|
|
details: { cycles: [["A", "B", "A"]] },
|
|
};
|
|
expect(ge.type).toBe("graph");
|
|
expect(ge.category).toBe("cycle");
|
|
});
|
|
|
|
it("supports dangling-reference category", () => {
|
|
const ge: GraphValidationError = {
|
|
type: "graph",
|
|
category: "dangling-reference",
|
|
details: { source: "A", target: "Z" },
|
|
};
|
|
expect(ge.category).toBe("dangling-reference");
|
|
});
|
|
|
|
it("supports orphan-node category", () => {
|
|
const ge: GraphValidationError = {
|
|
type: "graph",
|
|
category: "orphan-node",
|
|
details: { nodeKey: "lonely" },
|
|
};
|
|
expect(ge.category).toBe("orphan-node");
|
|
});
|
|
|
|
it("supports status-inconsistency category", () => {
|
|
const ge: GraphValidationError = {
|
|
type: "graph",
|
|
category: "status-inconsistency",
|
|
details: { nodeKey: "n1", parentKey: "p1", nodeStatus: "running", parentStatus: "completed" },
|
|
};
|
|
expect(ge.category).toBe("status-inconsistency");
|
|
});
|
|
});
|
|
|
|
describe("TypeMismatch interface", () => {
|
|
it("has path, expected, actual", () => {
|
|
const tm: TypeMismatch = { path: "$.input.name", expected: "string", actual: "number" };
|
|
expect(tm.path).toBe("$.input.name");
|
|
expect(tm.expected).toBe("string");
|
|
expect(tm.actual).toBe("number");
|
|
});
|
|
});
|
|
|
|
describe("TypeIncompatError interface", () => {
|
|
it("has required fields with compatible: false", () => {
|
|
const tie: TypeIncompatError = {
|
|
type: "type-compat",
|
|
sourceKey: "A",
|
|
targetKey: "B",
|
|
compatible: false,
|
|
mismatches: [{ path: "$.name", expected: "string", actual: "number" }],
|
|
};
|
|
expect(tie.type).toBe("type-compat");
|
|
expect(tie.compatible).toBe(false);
|
|
expect(tie.mismatches).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("AnyValidationError union", () => {
|
|
it("accepts ValidationError", () => {
|
|
const e: AnyValidationError = { type: "schema", nodeKey: "n1", field: "f", message: "bad" };
|
|
expect(e.type).toBe("schema");
|
|
});
|
|
|
|
it("accepts GraphValidationError", () => {
|
|
const e: AnyValidationError = { type: "graph", category: "cycle", details: {} };
|
|
expect(e.type).toBe("graph");
|
|
});
|
|
|
|
it("accepts TypeIncompatError", () => {
|
|
const e: AnyValidationError = {
|
|
type: "type-compat",
|
|
sourceKey: "A",
|
|
targetKey: "B",
|
|
compatible: false,
|
|
mismatches: [],
|
|
};
|
|
expect(e.type).toBe("type-compat");
|
|
});
|
|
});
|
|
|
|
describe("instanceof checks", () => {
|
|
it("DuplicateNodeError instanceof FlowgraphError", () => {
|
|
expect(new DuplicateNodeError("x")).toBeInstanceOf(FlowgraphError);
|
|
});
|
|
|
|
it("DuplicateEdgeError instanceof ConstructionError", () => {
|
|
expect(new DuplicateEdgeError("a", "b")).toBeInstanceOf(ConstructionError);
|
|
});
|
|
|
|
it("NodeNotFoundError instanceof ConstructionError", () => {
|
|
expect(new NodeNotFoundError("x")).toBeInstanceOf(ConstructionError);
|
|
});
|
|
|
|
it("CycleError instanceof ConstructionError", () => {
|
|
expect(new CycleError([["a", "b", "a"]])).toBeInstanceOf(ConstructionError);
|
|
});
|
|
|
|
it("InvalidInputError instanceof ConstructionError", () => {
|
|
expect(new InvalidInputError([])).toBeInstanceOf(ConstructionError);
|
|
});
|
|
|
|
it("InvalidTransitionError instanceof FlowgraphError but not ConstructionError", () => {
|
|
const err = new InvalidTransitionError("r1", "completed", "running");
|
|
expect(err).toBeInstanceOf(FlowgraphError);
|
|
expect(err).not.toBeInstanceOf(ConstructionError);
|
|
});
|
|
}); |