using System.Text.Json.Nodes; using w4c_workflows.Models.Nodes; using w4c_workflows.Services.Nodes.Interpolation; using Xunit; namespace w4c_workflows.Tests; public class NodeParameterInterpolatorTests { private readonly NodeParameterInterpolator _interpolator = new(); private static InterpolationScope Scope( string itemJson = "{}", string parametersJson = "{}", string environmentJson = "{}", Func>? nodeItems = null) => new() { Item = JsonNode.Parse(itemJson)!.AsObject(), Parameters = JsonNode.Parse(parametersJson)!.AsObject(), Environment = JsonNode.Parse(environmentJson)!.AsObject(), ItemIndex = 2, RunIndex = 1, NodeItems = nodeItems, }; [Fact] public void Plain_string_is_a_literal() { var result = _interpolator.ResolveString("hello", Scope()); Assert.Equal("hello", result!.GetValue()); } [Fact] public void Equals_without_braces_is_still_literal_text() { var result = _interpolator.ResolveString("=plain text", Scope()); Assert.Equal("plain text", result!.GetValue()); } [Fact] public void Whole_value_expression_preserves_type_string() { var result = _interpolator.ResolveString("={{ $json.name }}", Scope("""{"name":"Alice"}""")); Assert.Equal("Alice", result!.GetValue()); } [Fact] public void Whole_value_expression_preserves_type_number() { var result = _interpolator.ResolveString("={{ $json.count }}", Scope("""{"count":3}""")); Assert.Equal("3", result!.ToJsonString()); } [Fact] public void Whole_value_expression_preserves_type_boolean() { var result = _interpolator.ResolveString("={{ $json.ok }}", Scope("""{"ok":true}""")); Assert.Equal("true", result!.ToJsonString()); } [Fact] public void Embedded_expression_produces_an_interpolated_string() { var result = _interpolator.ResolveString("=Hi {{ $json.name }}, #{{ $json.count }}", Scope("""{"name":"Alice","count":3}""")); Assert.Equal("Hi Alice, #3", result!.GetValue()); } [Fact] public void Nested_paths_and_array_indexes_resolve() { var result = _interpolator.ResolveString( "={{ $json.orders[0].id }}", Scope("""{"orders":[{"id":42}]}""")); Assert.Equal("42", result!.ToJsonString()); } [Fact] public void Bracket_notation_resolves() { var result = _interpolator.ResolveString( """={{ $json["odd name"] }}""", Scope("""{"odd name":"value"}""")); Assert.Equal("value", result!.GetValue()); } [Fact] public void Parameter_and_environment_roots_resolve() { var scope = Scope(parametersJson: """{"mode":"manual"}""", environmentJson: """{"REGION":"eu"}"""); Assert.Equal("manual", _interpolator.ResolveString("={{ $parameter.mode }}", scope)!.GetValue()); Assert.Equal("eu", _interpolator.ResolveString("={{ $env.REGION }}", scope)!.GetValue()); } [Fact] public void Index_roots_resolve() { var scope = Scope(); Assert.Equal("2", _interpolator.ResolveString("={{ $itemIndex }}", scope)!.ToJsonString()); Assert.Equal("1", _interpolator.ResolveString("={{ $runIndex }}", scope)!.ToJsonString()); } [Fact] public void Node_reference_returns_the_first_item_json() { var scope = Scope(nodeItems: _ => new List { FlowItem.FromJson(new JsonObject { ["id"] = 7 }), }); Assert.Equal("7", _interpolator.ResolveString("""={{ $("Prep").id }}""", scope)!.ToJsonString()); } [Fact] public void Items_reference_returns_an_array() { var scope = Scope(nodeItems: _ => new List { FlowItem.FromJson(new JsonObject { ["id"] = 7 }), FlowItem.FromJson(new JsonObject { ["id"] = 8 }), }); Assert.Equal("""[{"id":7},{"id":8}]""", _interpolator.ResolveString("""={{ $items("Prep") }}""", scope)!.ToJsonString()); } [Fact] public void Object_parameters_are_resolved_recursively() { var parameters = JsonNode.Parse(""" { "url": "https://x/{{ $json.id }}", "options": { "flag": "={{ $json.ok }}" }, "list": ["={{ $json.id }}"] } """)!.AsObject(); var resolved = _interpolator.ResolveObject(parameters, Scope("""{"id":"abc","ok":true}""")); Assert.Equal("https://x/abc", resolved["url"]!.GetValue()); Assert.Equal("true", resolved["options"]!["flag"]!.ToJsonString()); Assert.Equal("abc", resolved["list"]![0]!.GetValue()); } [Fact] public void Unknown_accessor_throws() { Assert.Throws(() => _interpolator.ResolveString("={{ $nope.x }}", Scope())); } [Fact] public void Missing_path_resolves_to_null_without_throwing() { var result = _interpolator.ResolveString("={{ $json.missing.deep }}", Scope("""{"other":1}""")); Assert.Null(result); } [Fact] public void Case_sensitive_literal_stays_lowercase() { var result = _interpolator.ResolveString("={{ $json.name }}", Scope("""{"name":"aB"}""")); Assert.Equal("aB", result!.GetValue()); } }