w4c-workflows-api/w4c-workflows-api.Tests/NodeParameterInterpolatorTests.cs
2026-09-11 19:04:50 +03:00

166 lines
5.4 KiB
C#

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<string, IReadOnlyList<FlowItem>>? 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<string>());
}
[Fact]
public void Equals_without_braces_is_still_literal_text()
{
var result = _interpolator.ResolveString("=plain text", Scope());
Assert.Equal("plain text", result!.GetValue<string>());
}
[Fact]
public void Whole_value_expression_preserves_type_string()
{
var result = _interpolator.ResolveString("={{ $json.name }}", Scope("""{"name":"Alice"}"""));
Assert.Equal("Alice", result!.GetValue<string>());
}
[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<string>());
}
[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<string>());
}
[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<string>());
Assert.Equal("eu", _interpolator.ResolveString("={{ $env.REGION }}", scope)!.GetValue<string>());
}
[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>
{
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>
{
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<string>());
Assert.Equal("true", resolved["options"]!["flag"]!.ToJsonString());
Assert.Equal("abc", resolved["list"]![0]!.GetValue<string>());
}
[Fact]
public void Unknown_accessor_throws()
{
Assert.Throws<NodeInterpolationException>(() =>
_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<string>());
}
}