Skip to content

Commit

Permalink
Added the Mailgun adapter. #4
Browse files Browse the repository at this point in the history
  • Loading branch information
jezzsantos committed Jul 20, 2024
1 parent 4c61d6d commit 7661896
Show file tree
Hide file tree
Showing 21 changed files with 1,042 additions and 12 deletions.
225 changes: 225 additions & 0 deletions src/AncillaryInfrastructure.IntegrationTests/MailgunApiSpec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
using System.Net;
using AncillaryInfrastructure.IntegrationTests.Stubs;
using ApiHost1;
using Application.Persistence.Shared;
using Application.Persistence.Shared.ReadModels;
using Common.Extensions;
using Domain.Common;
using FluentAssertions;
using Infrastructure.Shared.ApplicationServices.External;
using Infrastructure.Web.Api.Common.Extensions;
using Infrastructure.Web.Api.Operations.Shared._3rdParties.Mailgun;
using Infrastructure.Web.Api.Operations.Shared.Ancillary;
using IntegrationTesting.WebApi.Common;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace AncillaryInfrastructure.IntegrationTests;

[Trait("Category", "Integration.API")]
[Collection("API")]
public class MailgunApiSpec : WebApiSpec<Program>
{
private readonly StubEmailDeliveryService _emailDeliveryService;

public MailgunApiSpec(WebApiSetup<Program> setup) : base(setup, OverrideDependencies)
{
EmptyAllRepositories();
_emailDeliveryService = setup.GetRequiredService<IEmailDeliveryService>().As<StubEmailDeliveryService>();
_emailDeliveryService.Reset();
}

[Fact]
public async Task WhenNotifyMailgunEventAndNotDeliveredEvent_ThenReturnsOk()
{
var result = await Api.PostAsync(new MailgunNotifyWebhookEventRequest
{
Signature = new MailgunSignature
{
Timestamp = "1",
Token = "atoken",
Signature = "bf106940253fa7477ba4b55a027126b70037ce9b00e67aa3bf4f5bab2775d3e1"
},
EventData = new MailgunEventData
{
Event = "anunknownevent"
}
});

result.StatusCode.Should().Be(HttpStatusCode.OK);
}

[Fact]
public async Task WhenNotifyMailgunEventAndDeliveredEvent_ThenReturnsOk()
{
var receiptId = await DeliveryEmailAsync();

var deliveredAt = DateTime.UtcNow.ToUnixSeconds();
var result = await Api.PostAsync(new MailgunNotifyWebhookEventRequest
{
Signature = new MailgunSignature
{
Timestamp = "1",
Token = "atoken",
Signature = "bf106940253fa7477ba4b55a027126b70037ce9b00e67aa3bf4f5bab2775d3e1"
},
EventData = new MailgunEventData
{
Event = MailgunConstants.Events.Delivered,
Timestamp = deliveredAt,
Message = new MailgunMessage
{
Headers = new MailgunMessageHeaders
{
MessageId = receiptId
}
}
}
});

result.StatusCode.Should().Be(HttpStatusCode.OK);

var login = await LoginUserAsync(LoginUser.Operator);
var deliveries = await Api.GetAsync(new SearchEmailDeliveriesRequest(),
req => req.SetJWTBearerToken(login.AccessToken));

deliveries.Content.Value.Emails![0].IsSent.Should().BeTrue();
deliveries.Content.Value.Emails![0].IsDelivered.Should().BeTrue();
deliveries.Content.Value.Emails![0].DeliveredAt.Should().Be(deliveredAt.FromUnixTimestamp());
deliveries.Content.Value.Emails![0].IsDeliveryFailed.Should().BeFalse();
deliveries.Content.Value.Emails![0].FailedDeliveryAt.Should().BeNull();
deliveries.Content.Value.Emails![0].FailedDeliveryReason.Should().BeNull();
}

[Fact]
public async Task WhenNotifyMailgunEventAndTemporaryFailedEvent_ThenReturnsOk()
{
var receiptId = await DeliveryEmailAsync();

var failedAt = DateTime.UtcNow.ToUnixSeconds();
var result = await Api.PostAsync(new MailgunNotifyWebhookEventRequest
{
Signature = new MailgunSignature
{
Timestamp = "1",
Token = "atoken",
Signature = "bf106940253fa7477ba4b55a027126b70037ce9b00e67aa3bf4f5bab2775d3e1"
},
EventData = new MailgunEventData
{
Event = MailgunConstants.Events.Failed,
Severity = MailgunConstants.Values.TemporarySeverity,
DeliveryStatus = new MailgunDeliveryStatus
{
Description = "areason"
},
Timestamp = failedAt,
Message = new MailgunMessage
{
Headers = new MailgunMessageHeaders
{
MessageId = receiptId
}
}
}
});

result.StatusCode.Should().Be(HttpStatusCode.OK);

var login = await LoginUserAsync(LoginUser.Operator);
var deliveries = await Api.GetAsync(new SearchEmailDeliveriesRequest(),
req => req.SetJWTBearerToken(login.AccessToken));

deliveries.Content.Value.Emails![0].IsSent.Should().BeTrue();
deliveries.Content.Value.Emails![0].IsDelivered.Should().BeFalse();
deliveries.Content.Value.Emails![0].DeliveredAt.Should().BeNull();
deliveries.Content.Value.Emails![0].IsDeliveryFailed.Should().BeFalse();
deliveries.Content.Value.Emails![0].FailedDeliveryAt.Should().BeNull();
deliveries.Content.Value.Emails![0].FailedDeliveryReason.Should().BeNull();
}

[Fact]
public async Task WhenNotifyMailgunEventAndPermanentFailedEvent_ThenReturnsOk()
{
var receiptId = await DeliveryEmailAsync();

var failedAt = DateTime.UtcNow.ToUnixSeconds();
var result = await Api.PostAsync(new MailgunNotifyWebhookEventRequest
{
Signature = new MailgunSignature
{
Timestamp = "1",
Token = "atoken",
Signature = "bf106940253fa7477ba4b55a027126b70037ce9b00e67aa3bf4f5bab2775d3e1"
},
EventData = new MailgunEventData
{
Event = MailgunConstants.Events.Failed,
Severity = MailgunConstants.Values.PermanentSeverity,
DeliveryStatus = new MailgunDeliveryStatus
{
Description = "areason"
},
Timestamp = failedAt,
Message = new MailgunMessage
{
Headers = new MailgunMessageHeaders
{
MessageId = receiptId
}
}
}
});

result.StatusCode.Should().Be(HttpStatusCode.OK);

var login = await LoginUserAsync(LoginUser.Operator);
var deliveries = await Api.GetAsync(new SearchEmailDeliveriesRequest(),
req => req.SetJWTBearerToken(login.AccessToken));

deliveries.Content.Value.Emails![0].IsSent.Should().BeTrue();
deliveries.Content.Value.Emails![0].IsDelivered.Should().BeFalse();
deliveries.Content.Value.Emails![0].DeliveredAt.Should().BeNull();
deliveries.Content.Value.Emails![0].IsDeliveryFailed.Should().BeTrue();
deliveries.Content.Value.Emails![0].FailedDeliveryAt.Should().Be(failedAt.FromUnixTimestamp());
deliveries.Content.Value.Emails![0].FailedDeliveryReason.Should().Be("areason");
}

private async Task<string> DeliveryEmailAsync()
{
_emailDeliveryService.SendingSucceeds = true;
var request = new SendEmailRequest
{
Message = new EmailMessage
{
MessageId = CreateMessageId(),
CallId = "acallid",
CallerId = "acallerid",
Html = new QueuedEmailHtmlMessage
{
Subject = "asubject",
HtmlBody = "anhtmlbody",
ToEmailAddress = "[email protected]",
ToDisplayName = "atodisplayname",
FromEmailAddress = "[email protected]",
FromDisplayName = "afromdisplayname"
}
}.ToJson()!
};
var delivered = await Api.PostAsync(request, req => req.SetHMACAuth(request, "asecret"));
delivered.Content.Value.IsSent.Should().BeTrue();
_emailDeliveryService.LastSubject.Should().Be("asubject");

return _emailDeliveryService.LastReceiptId;
}

private static void OverrideDependencies(IServiceCollection services)
{
services.AddSingleton<IEmailDeliveryService, StubEmailDeliveryService>();
}

private static string CreateMessageId()
{
return new MessageQueueIdFactory().Create("email");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"LocalMachineJsonFileStore": {
"RootPath": "./saastack/testing/ancillary"
}
},
"Mailgun": {
"WebhookSigningKey": "asecret"
}
}
}
151 changes: 151 additions & 0 deletions src/AncillaryInfrastructure.UnitTests/Api/3rdParties/MailgunApiSpec.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
using AncillaryApplication;
using AncillaryInfrastructure.Api._3rdParties;
using Application.Interfaces;
using Common;
using Common.Configuration;
using Common.Extensions;
using FluentAssertions;
using Infrastructure.Interfaces;
using Infrastructure.Shared.ApplicationServices.External;
using Infrastructure.Web.Api.Interfaces;
using Infrastructure.Web.Api.Operations.Shared._3rdParties.Mailgun;
using Moq;
using UnitTesting.Common;
using Xunit;

namespace AncillaryInfrastructure.UnitTests.Api._3rdParties;

[Trait("Category", "Unit")]
public class MailgunApiSpec
{
private readonly Mock<IAncillaryApplication> _ancillaryApplication;
private readonly MailgunApi _api;
private readonly MailgunSignature _signature;

public MailgunApiSpec()
{
var callerFactory = new Mock<ICallerContextFactory>();
callerFactory.Setup(cf => cf.Create())
.Returns(Mock.Of<ICallerContext>());
_ancillaryApplication = new Mock<IAncillaryApplication>();
var settings = new Mock<IConfigurationSettings>();
settings.Setup(s => s.Platform.GetString(MailgunConstants.WebhookSigningKeySettingName, It.IsAny<string>()))
.Returns("asecret");
_signature = new MailgunSignature
{
Timestamp = "1",
Token = "atoken",
Signature = "bf106940253fa7477ba4b55a027126b70037ce9b00e67aa3bf4f5bab2775d3e1"
};

_api = new MailgunApi(callerFactory.Object, _ancillaryApplication.Object, settings.Object);
}

[Fact]
public async Task WhenNotifyMailgunEventAndInvalidSignature_ThenReturnsError()
{
var result = await _api.NotifyEmailDeliveryReceipt(new MailgunNotifyWebhookEventRequest
{
Signature = new MailgunSignature()
}, CancellationToken.None);

result().Should().BeError(ErrorCode.NotAuthenticated);
_ancillaryApplication.Verify(app => app.ConfirmEmailDeliveredAsync(It.IsAny<ICallerContext>(),
It.IsAny<string>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()), Times.Never);
}

[Fact]
public async Task WhenNotifyMailgunEventAndUnhandledEvent_ThenReturnsEmptyResponse()
{
var result = await _api.NotifyEmailDeliveryReceipt(new MailgunNotifyWebhookEventRequest
{
Signature = _signature,
EventData = new MailgunEventData
{
Event = "anunknownevent"
}
}, CancellationToken.None);

result().Value.Should().BeOfType<EmptyResponse>();
_ancillaryApplication.Verify(app => app.ConfirmEmailDeliveredAsync(It.IsAny<ICallerContext>(),
It.IsAny<string>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()), Times.Never);
}

[Fact]
public async Task WhenNotifyMailgunEventAndWithNoReceiptId_ThenReturnsEmptyResponse()
{
var result = await _api.NotifyEmailDeliveryReceipt(new MailgunNotifyWebhookEventRequest
{
Signature = _signature,
EventData = new MailgunEventData
{
Event = MailgunConstants.Events.Delivered,
Message = new MailgunMessage()
}
}, CancellationToken.None);

result().Value.Should().BeOfType<EmptyResponse>();
_ancillaryApplication.Verify(app => app.ConfirmEmailDeliveredAsync(It.IsAny<ICallerContext>(),
It.IsAny<string>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()), Times.Never);
}

[Fact]
public async Task WhenNotifyMailgunEventAndWithDeliveredEvent_ThenReturnsEmptyResponse()
{
_ancillaryApplication.Setup(app => app.ConfirmEmailDeliveredAsync(It.IsAny<ICallerContext>(),
It.IsAny<string>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Ok);
var deliveredAt = DateTime.UtcNow.ToNearestSecond().ToUnixSeconds();
var result = await _api.NotifyEmailDeliveryReceipt(new MailgunNotifyWebhookEventRequest
{
Signature = _signature,
EventData = new MailgunEventData
{
Event = MailgunConstants.Events.Delivered,
Message = new MailgunMessage
{
Headers = new MailgunMessageHeaders
{
MessageId = "areceiptid"
}
},
Timestamp = deliveredAt
}
}, CancellationToken.None);

result().Value.Should().BeOfType<EmptyResponse>();
_ancillaryApplication.Verify(app => app.ConfirmEmailDeliveredAsync(It.IsAny<ICallerContext>(),
"areceiptid", deliveredAt.FromUnixTimestamp(), It.IsAny<CancellationToken>()));
}

[Fact]
public async Task WhenNotifyMailgunEventAndWithFailedEvent_ThenReturnsEmptyResponse()
{
_ancillaryApplication.Setup(app => app.ConfirmEmailDeliveredAsync(It.IsAny<ICallerContext>(),
It.IsAny<string>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(Result.Ok);
var failedAt = DateTime.UtcNow.ToNearestSecond().ToUnixSeconds();
var result = await _api.NotifyEmailDeliveryReceipt(new MailgunNotifyWebhookEventRequest
{
Signature = _signature,
EventData = new MailgunEventData
{
Event = MailgunConstants.Events.Failed,
Message = new MailgunMessage
{
Headers = new MailgunMessageHeaders
{
MessageId = "areceiptid"
}
},
Timestamp = failedAt,
Severity = MailgunConstants.Values.PermanentSeverity,
Reason = "areason"
}
}, CancellationToken.None);

result().Value.Should().BeOfType<EmptyResponse>();
_ancillaryApplication.Verify(app => app.ConfirmEmailDeliveryFailedAsync(It.IsAny<ICallerContext>(),
"areceiptid", failedAt.FromUnixTimestamp(), "areason", It.IsAny<CancellationToken>()));
}
}
Loading

0 comments on commit 7661896

Please sign in to comment.