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"));
}
}