Every action/terminal node's config string fields are now run through
TemplateEngine.Render before the node sees them, so operators can pull
values from the incoming item into any config field.
- {input}, {input.a.b}, {input.tags[0]}, {input["weird.key"]}
- {{ / }} escape to literal braces
- Missing paths / null input → empty string (no 500s on absent query params)
- Unrecognised / malformed placeholders pass through so typos are visible
- Strings render raw; numbers/bools stringified; objects/arrays compact JSON
Interpolation runs executor-side (Executor.RunNodeAsync walks ConfigJson
and rewrites string leaves), so every node opts in for free. 20 new tests
(17 TemplateEngine + 2 Executor + 1 end-to-end webhook→WebResponse).
119 lines
3.7 KiB
C#
119 lines
3.7 KiB
C#
using System.Text.Json;
|
|
using VolcanicCore.Core;
|
|
|
|
namespace VolcanicCore.Tests;
|
|
|
|
public class TemplateEngineTests
|
|
{
|
|
private static JsonElement J(string json) => JsonDocument.Parse(json).RootElement;
|
|
|
|
[Fact]
|
|
public void No_placeholders_returns_the_template_unchanged()
|
|
{
|
|
Assert.Equal("hello world", TemplateEngine.Render("hello world", J("""{"x":1}""")));
|
|
}
|
|
|
|
[Fact]
|
|
public void Whole_input_renders_as_compact_json()
|
|
{
|
|
var input = J("""{"a":1,"b":"x"}""");
|
|
Assert.Equal("""got: {"a":1,"b":"x"}""", TemplateEngine.Render("got: {input}", input));
|
|
}
|
|
|
|
[Fact]
|
|
public void Top_level_string_field_renders_without_json_quotes()
|
|
{
|
|
Assert.Equal("value: hello", TemplateEngine.Render("value: {input.test}", J("""{"test":"hello"}""")));
|
|
}
|
|
|
|
[Fact]
|
|
public void Nested_path_lookup()
|
|
{
|
|
Assert.Equal("42", TemplateEngine.Render("{input.a.b.c}", J("""{"a":{"b":{"c":42}}}""")));
|
|
}
|
|
|
|
[Fact]
|
|
public void Array_index_lookup()
|
|
{
|
|
Assert.Equal("y", TemplateEngine.Render("{input.tags[1]}", J("""{"tags":["x","y","z"]}""")));
|
|
}
|
|
|
|
[Fact]
|
|
public void Missing_property_renders_as_empty_string()
|
|
{
|
|
Assert.Equal("value: ", TemplateEngine.Render("value: {input.missing}", J("""{"other":1}""")));
|
|
}
|
|
|
|
[Fact]
|
|
public void Missing_nested_path_renders_as_empty_string()
|
|
{
|
|
Assert.Equal("[]", TemplateEngine.Render("[{input.a.b.c}]", J("""{"a":{"b":{}}}""")));
|
|
}
|
|
|
|
[Fact]
|
|
public void Array_index_out_of_range_renders_as_empty_string()
|
|
{
|
|
Assert.Equal("<>", TemplateEngine.Render("<{input.tags[9]}>", J("""{"tags":["only"]}""")));
|
|
}
|
|
|
|
[Fact]
|
|
public void Numeric_and_boolean_and_null_leaves_render_as_their_string_form()
|
|
{
|
|
var input = J("""{"n":42,"f":1.5,"b":true,"z":null}""");
|
|
Assert.Equal("42|1.5|true|", TemplateEngine.Render("{input.n}|{input.f}|{input.b}|{input.z}", input));
|
|
}
|
|
|
|
[Fact]
|
|
public void Object_and_array_leaves_render_as_compact_json()
|
|
{
|
|
var input = J("""{"o":{"k":1},"a":[1,2]}""");
|
|
Assert.Equal("""{"k":1}/[1,2]""", TemplateEngine.Render("{input.o}/{input.a}", input));
|
|
}
|
|
|
|
[Fact]
|
|
public void Doubled_braces_render_as_literal_single_braces()
|
|
{
|
|
Assert.Equal("{not a template}", TemplateEngine.Render("{{not a template}}", J("{}")));
|
|
}
|
|
|
|
[Fact]
|
|
public void Doubled_braces_around_a_real_placeholder()
|
|
{
|
|
Assert.Equal("{hello}", TemplateEngine.Render("{{{input.x}}}", J("""{"x":"hello"}""")));
|
|
}
|
|
|
|
[Fact]
|
|
public void Null_input_renders_placeholders_as_empty_string()
|
|
{
|
|
Assert.Equal("hi ", TemplateEngine.Render("hi {input.name}", input: null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Whole_input_placeholder_with_null_input_renders_empty()
|
|
{
|
|
Assert.Equal("", TemplateEngine.Render("{input}", input: null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Unrecognised_placeholder_is_left_as_literal_text()
|
|
{
|
|
// Only `input`-rooted expressions are supported; anything else is passed
|
|
// through unchanged so operators notice their typo instead of silent-emptying.
|
|
Assert.Equal("{node.foo}", TemplateEngine.Render("{node.foo}", J("""{"foo":1}""")));
|
|
}
|
|
|
|
[Fact]
|
|
public void Unclosed_brace_is_left_as_literal_text()
|
|
{
|
|
Assert.Equal("start {input.x and more", TemplateEngine.Render("start {input.x and more", J("""{"x":1}""")));
|
|
}
|
|
|
|
[Fact]
|
|
public void Bracketed_string_key_lookup()
|
|
{
|
|
// Bracket form also works for property names, useful for keys with dots
|
|
// or dashes (`weird.key` can't be reached via dot syntax).
|
|
Assert.Equal("v", TemplateEngine.Render("""{input["weird.key"]}""", J("""{"weird.key":"v"}""")));
|
|
}
|
|
}
|