272 lines
8.6 KiB
C#
272 lines
8.6 KiB
C#
using System.Text.Json.Nodes;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using StellaOps.Notify.Models;
|
|
using StellaOps.Notifier.Worker.Dispatch;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Notifier.Tests.Dispatch;
|
|
|
|
public sealed class SimpleTemplateRendererTests
|
|
{
|
|
private readonly SimpleTemplateRenderer _renderer;
|
|
|
|
public SimpleTemplateRendererTests()
|
|
{
|
|
_renderer = new SimpleTemplateRenderer(NullLogger<SimpleTemplateRenderer>.Instance);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RenderAsync_SimpleVariableSubstitution_ReplacesVariables()
|
|
{
|
|
var template = NotifyTemplate.Create(
|
|
templateId: "tpl-1",
|
|
tenantId: "tenant-a",
|
|
channelType: NotifyChannelType.Slack,
|
|
key: "test-template",
|
|
locale: "en",
|
|
body: "Hello {{actor}}, event {{kind}} occurred.");
|
|
|
|
var notifyEvent = NotifyEvent.Create(
|
|
eventId: Guid.NewGuid(),
|
|
kind: "policy.violation",
|
|
tenant: "tenant-a",
|
|
ts: DateTimeOffset.UtcNow,
|
|
payload: new JsonObject(),
|
|
actor: "admin@example.com",
|
|
version: "1");
|
|
|
|
var result = await _renderer.RenderAsync(template, notifyEvent, CancellationToken.None);
|
|
|
|
Assert.Contains("Hello admin@example.com", result.Body);
|
|
Assert.Contains("event policy.violation occurred", result.Body);
|
|
Assert.NotEmpty(result.BodyHash);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RenderAsync_PayloadVariables_FlattenedAndAvailable()
|
|
{
|
|
var template = NotifyTemplate.Create(
|
|
templateId: "tpl-2",
|
|
tenantId: "tenant-a",
|
|
channelType: NotifyChannelType.Webhook,
|
|
key: "payload-test",
|
|
locale: "en",
|
|
body: "Image: {{image}}, Severity: {{severity}}");
|
|
|
|
var payload = new JsonObject
|
|
{
|
|
["image"] = "registry.local/api:v1.0",
|
|
["severity"] = "critical"
|
|
};
|
|
|
|
var notifyEvent = NotifyEvent.Create(
|
|
eventId: Guid.NewGuid(),
|
|
kind: "scan.complete",
|
|
tenant: "tenant-a",
|
|
ts: DateTimeOffset.UtcNow,
|
|
payload: payload,
|
|
version: "1");
|
|
|
|
var result = await _renderer.RenderAsync(template, notifyEvent, CancellationToken.None);
|
|
|
|
Assert.Contains("Image: registry.local/api:v1.0", result.Body);
|
|
Assert.Contains("Severity: critical", result.Body);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RenderAsync_NestedPayloadVariables_SupportsDotNotation()
|
|
{
|
|
var template = NotifyTemplate.Create(
|
|
templateId: "tpl-3",
|
|
tenantId: "tenant-a",
|
|
channelType: NotifyChannelType.Slack,
|
|
key: "nested-test",
|
|
locale: "en",
|
|
body: "Package: {{package.name}} v{{package.version}}");
|
|
|
|
var payload = new JsonObject
|
|
{
|
|
["package"] = new JsonObject
|
|
{
|
|
["name"] = "lodash",
|
|
["version"] = "4.17.21"
|
|
}
|
|
};
|
|
|
|
var notifyEvent = NotifyEvent.Create(
|
|
eventId: Guid.NewGuid(),
|
|
kind: "vulnerability.found",
|
|
tenant: "tenant-a",
|
|
ts: DateTimeOffset.UtcNow,
|
|
payload: payload,
|
|
version: "1");
|
|
|
|
var result = await _renderer.RenderAsync(template, notifyEvent, CancellationToken.None);
|
|
|
|
Assert.Contains("Package: lodash v4.17.21", result.Body);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RenderAsync_SensitiveKeys_AreRedacted()
|
|
{
|
|
var template = NotifyTemplate.Create(
|
|
templateId: "tpl-4",
|
|
tenantId: "tenant-a",
|
|
channelType: NotifyChannelType.Webhook,
|
|
key: "redact-test",
|
|
locale: "en",
|
|
body: "Token: {{apikey}}, User: {{username}}");
|
|
|
|
var payload = new JsonObject
|
|
{
|
|
["apikey"] = "secret-token-12345",
|
|
["username"] = "testuser"
|
|
};
|
|
|
|
var notifyEvent = NotifyEvent.Create(
|
|
eventId: Guid.NewGuid(),
|
|
kind: "auth.event",
|
|
tenant: "tenant-a",
|
|
ts: DateTimeOffset.UtcNow,
|
|
payload: payload,
|
|
version: "1");
|
|
|
|
var result = await _renderer.RenderAsync(template, notifyEvent, CancellationToken.None);
|
|
|
|
Assert.Contains("[REDACTED]", result.Body);
|
|
Assert.Contains("User: testuser", result.Body);
|
|
Assert.DoesNotContain("secret-token-12345", result.Body);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RenderAsync_MissingVariables_ReplacedWithEmptyString()
|
|
{
|
|
var template = NotifyTemplate.Create(
|
|
templateId: "tpl-5",
|
|
tenantId: "tenant-a",
|
|
channelType: NotifyChannelType.Slack,
|
|
key: "missing-test",
|
|
locale: "en",
|
|
body: "Value: {{nonexistent}}-end");
|
|
|
|
var notifyEvent = NotifyEvent.Create(
|
|
eventId: Guid.NewGuid(),
|
|
kind: "test.event",
|
|
tenant: "tenant-a",
|
|
ts: DateTimeOffset.UtcNow,
|
|
payload: new JsonObject(),
|
|
version: "1");
|
|
|
|
var result = await _renderer.RenderAsync(template, notifyEvent, CancellationToken.None);
|
|
|
|
Assert.Equal("Value: -end", result.Body);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RenderAsync_EachBlock_IteratesOverArray()
|
|
{
|
|
var template = NotifyTemplate.Create(
|
|
templateId: "tpl-6",
|
|
tenantId: "tenant-a",
|
|
channelType: NotifyChannelType.Slack,
|
|
key: "each-test",
|
|
locale: "en",
|
|
body: "Items:{{#each items}} {{this}}{{/each}}");
|
|
|
|
var payload = new JsonObject
|
|
{
|
|
["items"] = new JsonArray("alpha", "beta", "gamma")
|
|
};
|
|
|
|
var notifyEvent = NotifyEvent.Create(
|
|
eventId: Guid.NewGuid(),
|
|
kind: "list.event",
|
|
tenant: "tenant-a",
|
|
ts: DateTimeOffset.UtcNow,
|
|
payload: payload,
|
|
version: "1");
|
|
|
|
var result = await _renderer.RenderAsync(template, notifyEvent, CancellationToken.None);
|
|
|
|
Assert.Contains("alpha", result.Body);
|
|
Assert.Contains("beta", result.Body);
|
|
Assert.Contains("gamma", result.Body);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RenderAsync_SubjectFromMetadata_RendersSubject()
|
|
{
|
|
var template = NotifyTemplate.Create(
|
|
templateId: "tpl-7",
|
|
tenantId: "tenant-a",
|
|
channelType: NotifyChannelType.Webhook,
|
|
key: "subject-test",
|
|
locale: "en",
|
|
body: "Body content",
|
|
metadata: new[] { new KeyValuePair<string, string>("subject", "Alert: {{kind}}") });
|
|
|
|
var notifyEvent = NotifyEvent.Create(
|
|
eventId: Guid.NewGuid(),
|
|
kind: "critical.alert",
|
|
tenant: "tenant-a",
|
|
ts: DateTimeOffset.UtcNow,
|
|
payload: new JsonObject(),
|
|
version: "1");
|
|
|
|
var result = await _renderer.RenderAsync(template, notifyEvent, CancellationToken.None);
|
|
|
|
Assert.Equal("Alert: critical.alert", result.Subject);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RenderAsync_BodyHash_IsConsistent()
|
|
{
|
|
var template = NotifyTemplate.Create(
|
|
templateId: "tpl-8",
|
|
tenantId: "tenant-a",
|
|
channelType: NotifyChannelType.Slack,
|
|
key: "hash-test",
|
|
locale: "en",
|
|
body: "Static content");
|
|
|
|
var notifyEvent = NotifyEvent.Create(
|
|
eventId: Guid.NewGuid(),
|
|
kind: "test.event",
|
|
tenant: "tenant-a",
|
|
ts: DateTimeOffset.UtcNow,
|
|
payload: new JsonObject(),
|
|
version: "1");
|
|
|
|
var result1 = await _renderer.RenderAsync(template, notifyEvent, CancellationToken.None);
|
|
var result2 = await _renderer.RenderAsync(template, notifyEvent, CancellationToken.None);
|
|
|
|
Assert.Equal(result1.BodyHash, result2.BodyHash);
|
|
Assert.Equal(64, result1.BodyHash.Length); // SHA256 hex
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RenderAsync_Format_PreservedFromTemplate()
|
|
{
|
|
var template = NotifyTemplate.Create(
|
|
templateId: "tpl-9",
|
|
tenantId: "tenant-a",
|
|
channelType: NotifyChannelType.Slack,
|
|
key: "format-test",
|
|
locale: "en",
|
|
body: "Content",
|
|
format: NotifyDeliveryFormat.Markdown);
|
|
|
|
var notifyEvent = NotifyEvent.Create(
|
|
eventId: Guid.NewGuid(),
|
|
kind: "test.event",
|
|
tenant: "tenant-a",
|
|
ts: DateTimeOffset.UtcNow,
|
|
payload: new JsonObject(),
|
|
version: "1");
|
|
|
|
var result = await _renderer.RenderAsync(template, notifyEvent, CancellationToken.None);
|
|
|
|
Assert.Equal(NotifyDeliveryFormat.Markdown, result.Format);
|
|
}
|
|
}
|