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(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(); var sender = new FcmPushSender(Substitute.For(), Substitute.For(), client, Substitute.For()); 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(() => 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(() => PushTemplateRenderer.Render(template, data with { TemplateData = data.TemplateData! with { Actions = new Dictionary() } })); } [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(), Actions = new Dictionary { ["openBasket"] = new() } } }; }