Files
hrynco-notification-service/HrynCo.NotificationService.Services.Tests/Push/PushPayloadVersionTests.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

106 lines
6.0 KiB
C#

using System.Text.Json;
using HrynCo.NotificationService.Contracts.Messages;
using HrynCo.NotificationService.DAL.Abstract.Repositories;
using HrynCo.NotificationService.DAL.Abstract.PushChannels;
using HrynCo.NotificationService.DAL.Abstract.Templates;
using HrynCo.NotificationService.Worker.Services.Push;
using Hrynco.RabbitMq;
using NSubstitute;
namespace HrynCo.NotificationService.Services.Tests.Push;
public sealed class PushPayloadVersionTests
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void OriginalPayloadDeserializesWithoutTemplateFieldsAndKeepsExactLegacyOutput(bool explicitVersion)
{
var json = JsonSerializer.SerializeToNode(new
{
registrationId = Guid.NewGuid(), bindingId = Guid.NewGuid(), userId = Guid.NewGuid(),
serviceName = "Invemory", title = "Original title", body = "Original body",
token = "device-token", tokenHash = new string('a', 64), expiresAt = DateTimeOffset.UtcNow.AddMinutes(5)
})!;
if (explicitVersion) json["version"] = 1;
var data = json.Deserialize<SendPushMessageData>(new JsonSerializerOptions(JsonSerializerDefaults.Web))!;
var message = new SendPushMessage { Data = data, CorrelationContext = new CorrelationContext { CorrelationId = Guid.NewGuid().ToString() } };
Assert.True(PushMessageValidator.IsValid(message));
Assert.Null(data.TemplateData);
var id = Guid.NewGuid();
var payload = FirebasePushClient.BuildPayload(id, new RenderedPushMessageData(data, data.Title!, data.Body!, null, []));
Assert.Equal(7, payload.Count);
Assert.Equal("1", payload["version"]);
Assert.Equal("/home", payload["route"]);
Assert.Equal(id.ToString("D"), payload["messageId"]);
Assert.Equal(data.BindingId.ToString("D"), payload["bindingId"]);
Assert.Equal("Original title", payload["title"]);
Assert.Equal("Original body", payload["body"]);
Assert.False(payload.ContainsKey("actions"));
}
[Theory]
[InlineData(0)]
[InlineData(3)]
[InlineData(-1)]
public async Task UnsupportedVersionNeverFallsBackOrContactsFirebase(int version)
{
var data = Data() with { Version = version, Title = "Legacy", Body = "Legacy" };
Assert.False(PushMessageValidator.IsValidContent(data));
var client = Substitute.For<IFirebasePushClient>();
var sender = new FcmPushSender(Substitute.For<INotificationServiceRepository>(), Substitute.For<IPushChannelRepository>(), client, Substitute.For<IPushTemplateDefinitionRepository>());
var result = await sender.SendAsync(Guid.NewGuid(), data, default);
Assert.Contains("Unsupported push payload version", result.Error);
await client.DidNotReceiveWithAnyArgs().SendAsync(default, default!, default!, default);
Assert.Throws<ArgumentOutOfRangeException>(() => FirebasePushClient.BuildPayload(Guid.NewGuid(), new RenderedPushMessageData(data, "Title", "Body", null, [])));
}
[Fact]
public void SectionActionHasNoObjectIdAndVersionTwoNeverContainsLegacyRoute()
{
var template = new PushTemplateDefinition { Key = "Section", Languages = [new PushTemplateTranslation
{
LanguageCode = "uk", Title = "Зміни", Body = "Перегляньте зміни",
ActionsJson = "{\"defaultAction\":\"openBasket\",\"buttons\":[{\"action\":\"openBasket\",\"label\":\"Перейти в розділ\"}]}"
}] };
var data = Data();
var rendered = PushTemplateRenderer.Render(template, data);
Assert.Null(Assert.Single(rendered.Buttons).ObjectId);
var payload = FirebasePushClient.BuildPayload(Guid.NewGuid(), rendered);
Assert.Equal("2", payload["version"]);
Assert.False(payload.ContainsKey("route"));
using var actions = JsonDocument.Parse(payload["actions"]);
var button = actions.RootElement.GetProperty("buttons")[0];
Assert.Equal("openBasket", button.GetProperty("action").GetString());
Assert.Equal("Перейти в розділ", button.GetProperty("label").GetString());
Assert.False(button.TryGetProperty("objectId", out _));
Assert.Throws<EmailTemplateWriteException>(() => PushTemplateRenderer.Render(template, data with { TemplateData = data.TemplateData! with { Actions = new Dictionary<string, PushActionMetadata>() } }));
}
[Fact]
public void TestSendBuildsActualButtonPayloadWithRealBindingAndPreservesSimpleLegacyTest()
{
var binding = Guid.NewGuid();
var test = new PushChannelTestMessage { Token = "test-token", Version = 2, BindingId = binding, ActionKey = "openBasket", ButtonLabel = "Перейти в розділ" };
var current = FirebasePushClient.BuildTestMessage(test);
Assert.Null(current.Notification);
Assert.Equal("2", current.Data["version"]);
Assert.Equal(binding.ToString("D"), current.Data["bindingId"]);
using var actions = JsonDocument.Parse(current.Data["actions"]);
Assert.Equal("openBasket", actions.RootElement.GetProperty("buttons")[0].GetProperty("action").GetString());
Assert.False(actions.RootElement.GetProperty("buttons")[0].TryGetProperty("objectId", out _));
var legacy = FirebasePushClient.BuildTestMessage(test with { Version = 1, BindingId = null });
Assert.NotNull(legacy.Notification);
Assert.True(legacy.Data is null || legacy.Data.Count == 0);
Assert.NotNull((test with { BindingId = null }).Validate());
Assert.NotNull((test with { Version = 9 }).Validate());
}
private static SendPushMessageData Data() => new()
{
RegistrationId = Guid.NewGuid(), BindingId = Guid.NewGuid(), UserId = Guid.NewGuid(), ServiceName = "Invemory",
Version = 2, TemplateKey = "Section", LanguageCode = "uk", ExpiresAt = DateTimeOffset.UtcNow.AddMinutes(5),
TemplateData = new PushTemplateData { Bindings = new Dictionary<string, string>(), Actions = new Dictionary<string, PushActionMetadata> { ["openBasket"] = new() } }
};
}