1631786ce2
Add push contracts, provider delivery and channel diagnostics. Link email channels to services and document migration and validation. Ref: IT-1074
79 lines
4.3 KiB
C#
79 lines
4.3 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text.Json;
|
|
using HrynCo.NotificationService.Contracts.Messages;
|
|
using HrynCo.NotificationService.DAL.Abstract.Push;
|
|
using HrynCo.NotificationService.DAL.Abstract.PushChannels;
|
|
using HrynCo.NotificationService.DAL.Abstract.Repositories;
|
|
using HrynCo.NotificationService.DAL.Abstract.Templates;
|
|
using HrynCo.NotificationService.Worker.Services.Push;
|
|
using NSubstitute;
|
|
|
|
namespace HrynCo.NotificationService.Services.Tests.Push;
|
|
|
|
public sealed class FcmPushSenderTests
|
|
{
|
|
[Fact]
|
|
public async Task ResolvesCurrentServiceNameToActiveChannelAndUsesOnlyServerCredentials()
|
|
{
|
|
var services = Substitute.For<INotificationServiceRepository>();
|
|
var channels = Substitute.For<IPushChannelRepository>();
|
|
var client = Substitute.For<IFirebasePushClient>();
|
|
var service = new NotificationServiceInfo { Id = Guid.NewGuid(), Name = "Invemory-Renamed" };
|
|
var channel = new PushChannel { Id = Guid.NewGuid(), ServiceId = service.Id, FirebaseProjectId = "test-project", IsActive = true };
|
|
services.GetByNameAsync(service.Name, Arg.Any<CancellationToken>()).Returns(service);
|
|
channels.GetActiveForServiceAsync(service.Id, Arg.Any<CancellationToken>()).Returns(channel);
|
|
using var rsa = RSA.Create(2048);
|
|
var json = JsonSerializer.Serialize(new
|
|
{
|
|
type = "service_account", project_id = "test-project", client_email = "test@example.invalid",
|
|
private_key = rsa.ExportPkcs8PrivateKeyPem(), token_uri = "https://untrusted.invalid/token"
|
|
});
|
|
channels.GetCredentialsAsync(channel.Id, Arg.Any<CancellationToken>()).Returns(json);
|
|
client.SendAsync(Arg.Any<Guid>(), Arg.Any<SendPushMessageData>(), Arg.Any<FirebaseServiceAccountCredentials>(), Arg.Any<CancellationToken>())
|
|
.Returns(new PushDeliveryOutcome(null));
|
|
var result = await new FcmPushSender(services, channels, client).SendAsync(Guid.NewGuid(), Data(service.Name), CancellationToken.None);
|
|
Assert.Null(result.Error);
|
|
await client.Received(1).SendAsync(Arg.Any<Guid>(), Arg.Any<SendPushMessageData>(),
|
|
Arg.Is<FirebaseServiceAccountCredentials>(c => c.ProjectId == "test-project" && c.ClientEmail == "test@example.invalid"), Arg.Any<CancellationToken>());
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false, false)]
|
|
[InlineData(true, false)]
|
|
[InlineData(true, true)]
|
|
public async Task MissingServiceInactiveChannelOrInvalidCredentialsDoNotContactFirebase(bool exists, bool active)
|
|
{
|
|
var services = Substitute.For<INotificationServiceRepository>();
|
|
var channels = Substitute.For<IPushChannelRepository>();
|
|
var client = Substitute.For<IFirebasePushClient>();
|
|
var service = new NotificationServiceInfo { Id = Guid.NewGuid(), Name = "Invemory" };
|
|
if (exists) services.GetByNameAsync(service.Name, Arg.Any<CancellationToken>()).Returns(service);
|
|
if (active)
|
|
{
|
|
var channel = new PushChannel { Id = Guid.NewGuid(), ServiceId = service.Id, FirebaseProjectId = "test-project", IsActive = true };
|
|
channels.GetActiveForServiceAsync(service.Id, Arg.Any<CancellationToken>()).Returns(channel);
|
|
channels.GetCredentialsAsync(channel.Id, Arg.Any<CancellationToken>()).Returns("secret-must-not-leak");
|
|
}
|
|
var result = await new FcmPushSender(services, channels, client).SendAsync(Guid.NewGuid(), Data(service.Name), CancellationToken.None);
|
|
Assert.NotNull(result.Error);
|
|
Assert.DoesNotContain("secret-must-not-leak", result.Error);
|
|
await client.DidNotReceiveWithAnyArgs().SendAsync(default, default!, default!, default);
|
|
}
|
|
|
|
[Fact]
|
|
public void PublicPayloadHasNoFirebaseAuthorizationFields()
|
|
{
|
|
var json = JsonSerializer.Serialize(Data("Invemory"));
|
|
Assert.DoesNotContain("Fcm", json);
|
|
Assert.DoesNotContain("Credentials", json);
|
|
Assert.DoesNotContain("AccessToken", json);
|
|
}
|
|
|
|
private static SendPushMessageData Data(string serviceName) => new()
|
|
{
|
|
RegistrationId = Guid.NewGuid(), BindingId = Guid.NewGuid(), UserId = Guid.NewGuid(),
|
|
ServiceName = serviceName, Title = "Test", Body = "Test", Token = "test-device-token",
|
|
TokenHash = new string('a', 64), ExpiresAt = DateTimeOffset.UtcNow.AddMinutes(10)
|
|
};
|
|
}
|