using System.Text.Json.Nodes; using w4c_workflows.Models.Nodes; using w4c_workflows.Services.Nodes; using Xunit; namespace w4c_workflows.Tests; /// /// S6: hot structures are precomputed once — adjacency, the display-name index, /// the catalog's flattened views and the per-blueprint parameter index — instead /// of being rebuilt per lookup. These assert identity so a regression to LINQ /// Where/ToList per call is caught. /// public class NodeGraphPrecomputeTests { private static NodeBlueprint Blueprint(string type, string displayName) => new() { Type = type, DisplayName = displayName, }; private static NodeGraph Graph() { var a = new NodeGraphNode { Id = "a", Blueprint = Blueprint("core.noop", "Alpha"), Parameters = new JsonObject() }; var b = new NodeGraphNode { Id = "b", Blueprint = Blueprint("core.set", "Beta"), Parameters = new JsonObject() }; return new NodeGraph { Nodes = new[] { a, b }, Edges = new[] { new NodeGraphEdge { FromNodeId = "a", FromOutput = 0, ToNodeId = "b", ToInput = 0 }, new NodeGraphEdge { FromNodeId = "a", FromOutput = 0, ToNodeId = "b", ToInput = 1 }, new NodeGraphEdge { FromNodeId = "b", FromOutput = 0, ToNodeId = "a", ToInput = 0, IsLoopBack = true }, }, EntryNodeId = "a", }; } [Fact] public void EdgesFrom_and_EdgesTo_are_cached_and_filter_correctly() { var graph = Graph(); Assert.Same(graph.EdgesFrom("a", 0), graph.EdgesFrom("a", 0)); Assert.Same(graph.EdgesTo("b"), graph.EdgesTo("b")); Assert.Equal(2, graph.EdgesFrom("a", 0).Count()); Assert.Empty(graph.EdgesFrom("a", 1)); Assert.Equal(2, graph.EdgesTo("b").Count()); Assert.Single(graph.EdgesTo("a")); } [Fact] public void FindByDisplayName_resolves_the_first_matching_node() { var graph = Graph(); Assert.Equal("b", graph.FindByDisplayName("Beta")?.Id); Assert.Equal("a", graph.FindByDisplayName("Alpha")?.Id); Assert.Null(graph.FindByDisplayName("Missing")); } [Fact] public void Catalog_flattened_views_are_cached() { var zip = new NodeBlueprint { Type = "z.zip", DisplayName = "Zip", Categories = new List { "Utility" }, }; var api = new NodeBlueprint { Type = "a.api", DisplayName = "Api", Categories = new List { "Utility", "Network" }, }; var catalog = new NodeBlueprintCatalog(new[] { zip, api }); Assert.Same(catalog.All, catalog.All); Assert.Same(catalog.Categories, catalog.Categories); Assert.Equal(new[] { "Network", "Utility" }, catalog.Categories); } [Fact] public void Blueprint_parameter_index_resolves_top_level_and_nested() { var blueprint = new NodeBlueprint { Type = "x.collection", DisplayName = "Collection", Parameters = new List { new() { Name = "mode", DisplayName = "Mode", Type = NodeParameterType.String, }, new() { Name = "fields", DisplayName = "Fields", Type = NodeParameterType.FixedCollection, Fields = new List { new() { Name = "nestedName", DisplayName = "Name", Type = NodeParameterType.String }, }, }, }, }; Assert.True(blueprint.HasParameter("mode")); Assert.True(blueprint.HasParameter("nestedName")); Assert.False(blueprint.HasParameter("missing")); // FindParameter stays top-level so nested fields keep their existing // (unvalidated) compile behaviour. Assert.Equal("mode", blueprint.FindParameter("mode")?.Name); Assert.Null(blueprint.FindParameter("nestedName")); } }