up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
This commit is contained in:
@@ -1,271 +1,271 @@
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
var result2 = await _renderer.RenderAsync(template, notifyEvent);
|
||||
|
||||
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);
|
||||
|
||||
Assert.Equal(NotifyDeliveryFormat.Markdown, result.Format);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
var result2 = await _renderer.RenderAsync(template, notifyEvent);
|
||||
|
||||
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);
|
||||
|
||||
Assert.Equal(NotifyDeliveryFormat.Markdown, result.Format);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user