mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 07:13:12 +00:00
106 lines
4 KiB
C#
106 lines
4 KiB
C#
|
|
using System.Net;
|
||
|
|
using Api.SeaHavenIndustries.Helper;
|
||
|
|
using Microsoft.Extensions.Configuration;
|
||
|
|
using Microsoft.Extensions.Logging;
|
||
|
|
using Moq;
|
||
|
|
using SendGrid;
|
||
|
|
using SendGrid.Helpers.Mail;
|
||
|
|
using Xunit;
|
||
|
|
|
||
|
|
namespace Api.SeaHavenIndustries.Tests;
|
||
|
|
|
||
|
|
public sealed class SendMessageTests
|
||
|
|
{
|
||
|
|
private const string Recipient = "taylor@example.com";
|
||
|
|
private const string Subject = "You're invited to Seahaven";
|
||
|
|
private const string RejectionBody = "{\"errors\":[{\"message\":\"taylor@example.com does not exist\"}]}";
|
||
|
|
|
||
|
|
[Theory]
|
||
|
|
[InlineData(HttpStatusCode.OK, true)]
|
||
|
|
[InlineData(HttpStatusCode.Accepted, true)]
|
||
|
|
[InlineData(HttpStatusCode.BadRequest, false)]
|
||
|
|
[InlineData(HttpStatusCode.Unauthorized, false)]
|
||
|
|
[InlineData(HttpStatusCode.TooManyRequests, false)]
|
||
|
|
[InlineData(HttpStatusCode.InternalServerError, false)]
|
||
|
|
public async Task EverySendPath_ReportsDeliveryOnlyForASuccessStatus(HttpStatusCode status, bool delivered)
|
||
|
|
{
|
||
|
|
var sender = new StubbedSendMessage(status, new CapturingLogger());
|
||
|
|
|
||
|
|
Assert.Equal(delivered, await sender.SendEMail(Recipient, Subject, "<p>Hi</p>"));
|
||
|
|
Assert.Equal(delivered, await sender.SendEmailAsync(Recipient, Subject, "<p>Hi</p>"));
|
||
|
|
Assert.Equal(delivered, await sender.SendEMailAttachment(Recipient, Subject, new byte[] { 1, 2, 3 }));
|
||
|
|
Assert.Equal(delivered, await sender.SendDispatchEmail(Recipient, Subject, "<p>Hi</p>", null));
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task ARejectedSend_LogsOnlyTheStatusCode()
|
||
|
|
{
|
||
|
|
var logger = new CapturingLogger();
|
||
|
|
var sender = new StubbedSendMessage(HttpStatusCode.BadRequest, logger);
|
||
|
|
|
||
|
|
Assert.False(await sender.SendEMail(Recipient, Subject, "<p>secret link</p>"));
|
||
|
|
|
||
|
|
var entry = Assert.Single(logger.Entries);
|
||
|
|
Assert.Contains("400", entry);
|
||
|
|
Assert.DoesNotContain(Recipient, entry);
|
||
|
|
Assert.DoesNotContain(Subject, entry);
|
||
|
|
Assert.DoesNotContain("secret link", entry);
|
||
|
|
Assert.DoesNotContain("errors", entry);
|
||
|
|
}
|
||
|
|
|
||
|
|
[Fact]
|
||
|
|
public async Task AFailedSend_LogsOnlyTheExceptionType()
|
||
|
|
{
|
||
|
|
var logger = new CapturingLogger();
|
||
|
|
var sender = new StubbedSendMessage(
|
||
|
|
new HttpRequestException($"could not reach SendGrid for {Recipient}"),
|
||
|
|
logger);
|
||
|
|
|
||
|
|
Assert.False(await sender.SendEMail(Recipient, Subject, "<p>Hi</p>"));
|
||
|
|
|
||
|
|
var entry = Assert.Single(logger.Entries);
|
||
|
|
Assert.Contains(nameof(HttpRequestException), entry);
|
||
|
|
Assert.DoesNotContain(Recipient, entry);
|
||
|
|
}
|
||
|
|
|
||
|
|
private sealed class StubbedSendMessage : SendMessage
|
||
|
|
{
|
||
|
|
private readonly Mock<ISendGridClient> _client = new();
|
||
|
|
|
||
|
|
public StubbedSendMessage(HttpStatusCode status, ILogger<SendMessage> logger)
|
||
|
|
: base(new ConfigurationBuilder().Build(), logger)
|
||
|
|
{
|
||
|
|
_client
|
||
|
|
.Setup(client => client.SendEmailAsync(It.IsAny<SendGridMessage>(), It.IsAny<CancellationToken>()))
|
||
|
|
.ReturnsAsync(() => new Response(status, new StringContent(RejectionBody), null));
|
||
|
|
}
|
||
|
|
|
||
|
|
public StubbedSendMessage(Exception failure, ILogger<SendMessage> logger)
|
||
|
|
: base(new ConfigurationBuilder().Build(), logger)
|
||
|
|
{
|
||
|
|
_client
|
||
|
|
.Setup(client => client.SendEmailAsync(It.IsAny<SendGridMessage>(), It.IsAny<CancellationToken>()))
|
||
|
|
.ThrowsAsync(failure);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override ISendGridClient CreateClient(string? apiKey) => _client.Object;
|
||
|
|
}
|
||
|
|
|
||
|
|
private sealed class CapturingLogger : ILogger<SendMessage>
|
||
|
|
{
|
||
|
|
public List<string> Entries { get; } = new();
|
||
|
|
|
||
|
|
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
||
|
|
|
||
|
|
public bool IsEnabled(LogLevel logLevel) => true;
|
||
|
|
|
||
|
|
public void Log<TState>(
|
||
|
|
LogLevel logLevel,
|
||
|
|
EventId eventId,
|
||
|
|
TState state,
|
||
|
|
Exception? exception,
|
||
|
|
Func<TState, Exception?, string> formatter) =>
|
||
|
|
Entries.Add($"{formatter(state, exception)} {exception}");
|
||
|
|
}
|
||
|
|
}
|