feat(alerting): MustacheRenderer with literal fallback on missing vars

Sentinel-substitution approach: unresolved {{x.y.z}} tokens are replaced
with a unique NUL-delimited sentinel before Mustache compilation, rendered
as opaque text, then post-replaced with the original {{x.y.z}} literal.
Malformed templates (unclosed {{) are caught and return the raw template.
Never throws. 9 unit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-19 19:27:05 +02:00
parent c53f642838
commit 92a74e7b8d
2 changed files with 169 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
package com.cameleer.server.app.alerting.notify;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class MustacheRendererTest {
private MustacheRenderer renderer;
@BeforeEach
void setUp() {
renderer = new MustacheRenderer();
}
@Test
void simpleVariable_rendersValue() {
var ctx = Map.<String, Object>of("name", "production");
assertThat(renderer.render("Env: {{name}}", ctx)).isEqualTo("Env: production");
}
@Test
void nestedPath_rendersValue() {
var ctx = Map.<String, Object>of(
"alert", Map.of("state", "FIRING"));
assertThat(renderer.render("State: {{alert.state}}", ctx)).isEqualTo("State: FIRING");
}
@Test
void missingVariable_rendersLiteralMustache() {
var ctx = Map.<String, Object>of("known", "yes");
String result = renderer.render("{{known}} and {{missing.path}}", ctx);
assertThat(result).isEqualTo("yes and {{missing.path}}");
}
@Test
void missingVariable_exactLiteralNoPadding() {
// The rendered literal must be exactly {{x}} — no surrounding whitespace or delimiter residue.
String result = renderer.render("{{unknown}}", Map.of());
assertThat(result).isEqualTo("{{unknown}}");
}
@Test
void malformedTemplate_returnsRawTemplate() {
String broken = "Hello {{unclosed";
String result = renderer.render(broken, Map.of());
assertThat(result).isEqualTo(broken);
}
@Test
void nullTemplate_returnsEmptyString() {
assertThat(renderer.render(null, Map.of())).isEmpty();
}
@Test
void emptyTemplate_returnsEmptyString() {
assertThat(renderer.render("", Map.of())).isEmpty();
}
@Test
void mixedResolvedAndUnresolved_rendersCorrectly() {
var ctx = Map.<String, Object>of(
"env", Map.of("slug", "prod"),
"alert", Map.of("id", "abc-123"));
String tmpl = "{{env.slug}} / {{alert.id}} / {{alert.resolvedAt}}";
String result = renderer.render(tmpl, ctx);
assertThat(result).isEqualTo("prod / abc-123 / {{alert.resolvedAt}}");
}
@Test
void plainText_noTokens_returnsAsIs() {
assertThat(renderer.render("No tokens here.", Map.of())).isEqualTo("No tokens here.");
}
}