Files
hrynco-notification-service/HrynCo.NotificationService.Services.Tests/Push/PushTemplateRendererTests.cs
T
agrynco aba4173827 feat(templates): add localized push templates and actions
Share template keys across email and push with independent editors and revisions. Render localized actions and preserve version-one delivery.

Ref: IT-1627
2026-10-31 09:38:05 +02:00

91 lines
4.1 KiB
C#

using System.Text.Json;
using HrynCo.NotificationService.Contracts.Messages;
using HrynCo.NotificationService.DAL.Abstract.Templates;
using HrynCo.NotificationService.Worker.Services.Push;
namespace HrynCo.NotificationService.Services.Tests.Push;
public sealed class PushTemplateRendererTests
{
[Fact]
public void RendersLocalizedLabelsAndOpaqueObjectIdsWithoutReinterpretingValues()
{
var result = PushTemplateRenderer.Render(Template(), Message());
Assert.Equal("Hello {{Injected}}", result.Title);
var button = Assert.Single(result.Buttons);
Assert.Equal("View {{Injected}}", button.Label);
Assert.Equal("need-42", button.ObjectId);
Assert.Equal("open", result.DefaultAction!.Action);
using var payload = JsonDocument.Parse(FirebasePushClient.BuildPayload(Guid.NewGuid(), result)["actions"]);
Assert.Equal("need-42", payload.RootElement.GetProperty("buttons")[0].GetProperty("objectId").GetString());
Assert.Equal("open", payload.RootElement.GetProperty("defaultAction").GetProperty("action").GetString());
}
[Fact]
public void UsesEnglishFallbackAndRejectsMissingFallback()
{
var message = Message() with { LanguageCode = "uk" };
Assert.Equal("Hello {{Injected}}", PushTemplateRenderer.Render(Template(), message).Title);
var template = Template();
template.Languages = [];
Assert.Throws<EmailTemplateWriteException>(() => PushTemplateRenderer.Render(template, message));
}
[Fact]
public void MissingBindingOrConfiguredActionFailsInsteadOfSendingIncompleteNotification()
{
var message = Message();
Assert.Throws<EmailTemplateWriteException>(() => PushTemplateRenderer.Render(Template(), message with
{
TemplateData = message.TemplateData! with { Bindings = new Dictionary<string, string>() }
}));
Assert.Throws<EmailTemplateWriteException>(() => PushTemplateRenderer.Render(Template(), message with
{
TemplateData = message.TemplateData! with { Actions = new Dictionary<string, PushActionMetadata>() }
}));
}
[Fact]
public void TruncationKeepsUnicodeTextElementsIntact()
{
var message = Message() with
{
TemplateData = Message().TemplateData! with { Bindings = new Dictionary<string, string> { ["Name"] = string.Concat(Enumerable.Repeat("😀", 100)) } }
};
var result = PushTemplateRenderer.Render(Template(), message);
Assert.True(result.Title.Length <= 100);
Assert.EndsWith("…", result.Title);
Assert.False(char.IsHighSurrogate(result.Title[^2]));
}
[Theory]
[InlineData("{\"buttons\":null}")]
[InlineData("{\"buttons\":[{\"action\":\"open\",\"label\":\"\"}]}")]
[InlineData("{\"buttons\":[{\"action\":\"open\",\"label\":\"Open\"},{\"action\":\"open\",\"label\":\"Again\"}]}")]
[InlineData("{\"defaultAction\":\"https://example.invalid\"}")]
[InlineData("{\"unknown\":true}")]
public void RejectsInvalidActionDefinitions(string json) =>
Assert.Throws<EmailTemplateWriteException>(() => PushTemplateActionValidation.Parse(json));
private static PushTemplateDefinition Template() => new()
{
Key = "Shared", Variables = [new EmailTemplateVariable { Name = "Name", Required = true }],
Languages = [new PushTemplateTranslation
{
LanguageCode = "en", Title = "Hello {{Name}}", Body = "Shared item",
ActionsJson = "{\"defaultAction\":\"open\",\"buttons\":[{\"action\":\"open\",\"label\":\"View {{Name}}\"}]}"
}]
};
private static SendPushMessageData Message() => new()
{
RegistrationId = Guid.NewGuid(), BindingId = Guid.NewGuid(), UserId = Guid.NewGuid(),
ServiceName = "Example", Version = 2, TemplateKey = "Shared", LanguageCode = "en", ExpiresAt = DateTimeOffset.UtcNow.AddMinutes(5),
TemplateData = new PushTemplateData
{
Bindings = new Dictionary<string, string> { ["Name"] = "{{Injected}}" },
Actions = new Dictionary<string, PushActionMetadata> { ["open"] = new() { ObjectId = "need-42" } }
}
};
}