from __future__ import annotations import unittest from govoplan_core.core.definition_graphs import ( DefinitionEdge, DefinitionGraphConstraints, DefinitionGraphLibrary, DefinitionNode, DefinitionNodeCountConstraint, DefinitionNodeType, DefinitionPort, validate_definition_graph, ) def library(*, allow_cycles: bool) -> DefinitionGraphLibrary: return DefinitionGraphLibrary( id="test", version="0.1.0", category_labels={"control": "Control"}, node_types=( DefinitionNodeType( type="start", category="control", label="Start", description="Start", icon="play", ), DefinitionNodeType( type="step", category="control", label="Step", description="Step", icon="square", input_ports=(DefinitionPort(id="input", label="Input"),), ), DefinitionNodeType( type="end", category="control", label="End", description="End", icon="circle-stop", input_ports=(DefinitionPort(id="input", label="Input"),), output_ports=(), ), ), constraints=DefinitionGraphConstraints( allow_cycles=allow_cycles, node_counts=( DefinitionNodeCountConstraint( code="graph.start_count", label="start", minimum=1, maximum=1, node_types=("start",), ), DefinitionNodeCountConstraint( code="graph.end_count", label="end", minimum=1, node_types=("end",), ), ), ), ) class DefinitionGraphTests(unittest.TestCase): def test_library_driven_ports_and_counts_validate_a_definition(self) -> None: diagnostics = validate_definition_graph( library(allow_cycles=False), nodes=( DefinitionNode(id="start", type="start"), DefinitionNode(id="step", type="step"), DefinitionNode(id="end", type="end"), ), edges=( DefinitionEdge(id="edge-1", source="start", target="step"), DefinitionEdge(id="edge-2", source="step", target="end"), ), ) self.assertEqual((), diagnostics) def test_constraints_change_cycle_semantics_without_changing_graph_shape(self) -> None: nodes = ( DefinitionNode(id="start", type="start"), DefinitionNode(id="step", type="step"), DefinitionNode(id="retry", type="step"), DefinitionNode(id="end", type="end"), ) edges = ( DefinitionEdge(id="edge-1", source="start", target="step"), DefinitionEdge(id="edge-2", source="step", target="retry"), DefinitionEdge(id="edge-3", source="retry", target="step"), DefinitionEdge(id="edge-4", source="retry", target="end"), ) dag_codes = { item.code for item in validate_definition_graph( library(allow_cycles=False), nodes=nodes, edges=edges, ) } workflow_codes = { item.code for item in validate_definition_graph( library(allow_cycles=True), nodes=nodes, edges=edges, ) } self.assertIn("graph.cycle", dag_codes) self.assertNotIn("graph.cycle", workflow_codes) def test_unknown_ports_and_missing_required_inputs_are_explicit(self) -> None: diagnostics = validate_definition_graph( library(allow_cycles=False), nodes=( DefinitionNode(id="start", type="start"), DefinitionNode(id="end", type="end"), ), edges=( DefinitionEdge( id="edge", source="start", target="end", target_port="missing", ), ), ) self.assertTrue( {"edge.unknown_target_port", "node.input_required"}.issubset( {item.code for item in diagnostics} ) ) if __name__ == "__main__": unittest.main()