tests: add test for notification handler

This commit is contained in:
Stevan Freeborn
2025-06-02 11:11:36 -05:00
parent 7f9404dff3
commit 3d0321bc42
3 changed files with 205 additions and 3 deletions
@@ -8,7 +8,7 @@ internal static class NotificationHandler
[FromServices] IYouTubeDataApiClient youTubeDataApiClient, [FromServices] IYouTubeDataApiClient youTubeDataApiClient,
[FromServices] IDiscordRestClient discordRestClient, [FromServices] IDiscordRestClient discordRestClient,
[FromServices] IOptionsMonitor<DiscordNotificationOptions> discordNotificationOptions, [FromServices] IOptionsMonitor<DiscordNotificationOptions> discordNotificationOptions,
[FromServices] ILastPostedStreamStore lastPostedStream, [FromServices] ILastPostedStreamStore lastPostedStreamStore,
CancellationToken cancellationToken = default CancellationToken cancellationToken = default
) )
{ {
@@ -40,13 +40,13 @@ internal static class NotificationHandler
return Results.Ok(); return Results.Ok();
} }
if (lastPostedStream.HasValue(videoId)) if (lastPostedStreamStore.HasValue(videoId))
{ {
logger.LogInformation("Video ID {VideoId} has already been posted. Skipping notification.", videoId); logger.LogInformation("Video ID {VideoId} has already been posted. Skipping notification.", videoId);
return Results.Ok(); return Results.Ok();
} }
lastPostedStream.SetValue(videoId); lastPostedStreamStore.SetValue(videoId);
logger.LogInformation( logger.LogInformation(
"Video ID {VideoId} is a live stream. Creating discord message in channel {ChannelId}", "Video ID {VideoId} is a live stream. Creating discord message in channel {ChannelId}",
@@ -27,6 +27,7 @@
<CoverletOutput>./TestResults/Coverage/</CoverletOutput> <CoverletOutput>./TestResults/Coverage/</CoverletOutput>
<CoverletOutputFormat>cobertura</CoverletOutputFormat> <CoverletOutputFormat>cobertura</CoverletOutputFormat>
<Include>[StevesBot.Webhook]*</Include> <Include>[StevesBot.Webhook]*</Include>
<Exclude>[StevesBot.Webhook]System.Text.RegularExpressions.Generated.*</Exclude>
<ExcludeByFile>**/Program.cs,**/SubscriptionWorker.cs</ExcludeByFile> <ExcludeByFile>**/Program.cs,**/SubscriptionWorker.cs</ExcludeByFile>
</PropertyGroup> </PropertyGroup>
@@ -1,6 +1,207 @@
using System.Globalization;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using StevesBot.Library.Discord.Rest;
using StevesBot.Library.Discord.Rest.Requests;
using StevesBot.Webhook.YouTube.Handlers;
namespace StevesBot.Webhook.Tests.Unit; namespace StevesBot.Webhook.Tests.Unit;
public class NotificationHandlerTests public class NotificationHandlerTests
{ {
private readonly Mock<HttpRequest> _mockHttpRequest = new();
private readonly Mock<HttpContext> _mockHttpContext = new();
private readonly Mock<ILogger<Program>> _mockLogger = new();
private readonly Mock<IYouTubeDataApiClient> _mockYtDataApiClient = new();
private readonly Mock<IDiscordRestClient> _mockDiscordRestClient = new();
private readonly Mock<IOptionsMonitor<DiscordNotificationOptions>> _mockNotificationsOptions = new();
private readonly Mock<ILastPostedStreamStore> _mockLastPostedStore = new();
public NotificationHandlerTests()
{
_mockHttpContext
.Setup(static x => x.Request)
.Returns(_mockHttpRequest.Object);
}
[Fact]
public async Task HandleAsync_WhenVideoIdNotFound_ItShouldReturnBadRequest()
{
SetupMockRequestBodyStream("This is a test");
var result = await HandleAsync();
result.Should().BeOfType<BadRequest<string>>();
}
[Fact]
public async Task HandleAsync_WhenVideoIsNotFound_ItShouldReturnNotFound()
{
SetupMockRequestBodyStream("<yt:videoId>videoId</yt:videoId>");
_mockYtDataApiClient
.Setup(static x => x.GetVideoByIdAsync(
It.IsAny<string>(),
It.IsAny<string[]>(),
It.IsAny<CancellationToken>()
))
.ReturnsAsync(null as YouTubeVideo);
var result = await HandleAsync();
result.Should().BeOfType<NotFound>();
}
[Fact]
public async Task HandleAsync_WhenVideoIsFoundButIsNotALiveStream_ItShouldReturnOkButNotCreateDiscordMessage()
{
SetupMockRequestBodyStream("<yt:videoId>videoId</yt:videoId>");
_mockYtDataApiClient
.Setup(static x => x.GetVideoByIdAsync(
It.IsAny<string>(),
It.IsAny<string[]>(),
It.IsAny<CancellationToken>()
))
.ReturnsAsync(new YouTubeVideo());
var result = await HandleAsync();
result.Should().BeOfType<Ok>();
_mockLastPostedStore
.Verify(static x => x.SetValue(It.IsAny<string>()), Times.Never);
_mockDiscordRestClient
.Verify(
static x => x.CreateMessageAsync(
It.IsAny<string>(),
It.IsAny<CreateMessageRequest>(),
It.IsAny<CancellationToken>()
),
Times.Never
);
}
[Fact]
public async Task HandleAsync_WhenVideoIsFoundButItHasSameIdAsLastVideo_ItShouldReturnOkButNotCreateDiscordMessage()
{
var videoId = "video_id";
SetupMockRequestBodyStream($"<yt:videoId>{videoId}</yt:videoId>");
_mockYtDataApiClient
.Setup(static x => x.GetVideoByIdAsync(
It.IsAny<string>(),
It.IsAny<string[]>(),
It.IsAny<CancellationToken>()
))
.ReturnsAsync(new YouTubeVideo()
{
Id = videoId,
LiveStreamingDetails = new(),
Snippet = new()
{
LiveBroadcastContent = "live"
}
});
_mockLastPostedStore
.Setup(static x => x.HasValue(It.IsAny<string>()))
.Returns(true);
var result = await HandleAsync();
result.Should().BeOfType<Ok>();
_mockLastPostedStore
.Verify(static x => x.SetValue(It.IsAny<string>()), Times.Never);
_mockDiscordRestClient
.Verify(
static x => x.CreateMessageAsync(
It.IsAny<string>(),
It.IsAny<CreateMessageRequest>(),
It.IsAny<CancellationToken>()
),
Times.Never
);
}
[Fact]
public async Task HandleAsync_WhenVideoIsFoundAndItIsANewLiveStream_ItShouldReturnOkStoreTheIdAndCreateADiscordMessage()
{
var videoId = "video_id";
SetupMockRequestBodyStream($"<yt:videoId>{videoId}</yt:videoId>");
_mockYtDataApiClient
.Setup(static x => x.GetVideoByIdAsync(
It.IsAny<string>(),
It.IsAny<string[]>(),
It.IsAny<CancellationToken>()
))
.ReturnsAsync(new YouTubeVideo()
{
Id = videoId,
LiveStreamingDetails = new(),
Snippet = new()
{
LiveBroadcastContent = "live"
}
});
_mockLastPostedStore
.Setup(static x => x.HasValue(It.IsAny<string>()))
.Returns(false);
_mockNotificationsOptions
.Setup(x => x.CurrentValue)
.Returns(new DiscordNotificationOptions());
var result = await HandleAsync();
result.Should().BeOfType<Ok>();
_mockLastPostedStore
.Verify(
x => x.SetValue(
It.Is<string>(s => string.Equals(s, videoId, StringComparison.Ordinal))
),
Times.Once
);
_mockDiscordRestClient
.Verify(
static x => x.CreateMessageAsync(
It.IsAny<string>(),
It.IsAny<CreateMessageRequest>(),
It.IsAny<CancellationToken>()
),
Times.Once
);
}
private void SetupMockRequestBodyStream(string content)
{
var stream = new MemoryStream(Encoding.UTF8.GetBytes(content));
_mockHttpRequest.Setup(static x => x.Body).Returns(stream);
}
private Task<IResult> HandleAsync()
{
return NotificationHandler.HandleAsync(
_mockHttpContext.Object,
_mockLogger.Object,
_mockYtDataApiClient.Object,
_mockDiscordRestClient.Object,
_mockNotificationsOptions.Object,
_mockLastPostedStore.Object
);
}
} }