From 3d0321bc42f9afacef521ab0ae52d90eabe45c4f Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Mon, 2 Jun 2025 11:11:36 -0500 Subject: [PATCH] tests: add test for notification handler --- .../YouTube/Handlers/NotificationHandler.cs | 6 +- .../StevesBot.Webhook.Tests.csproj | 1 + .../Unit/NotificationHandlerTests.cs | 201 ++++++++++++++++++ 3 files changed, 205 insertions(+), 3 deletions(-) diff --git a/src/src/StevesBot.Webhook/YouTube/Handlers/NotificationHandler.cs b/src/src/StevesBot.Webhook/YouTube/Handlers/NotificationHandler.cs index 7d809f7..b8b3680 100644 --- a/src/src/StevesBot.Webhook/YouTube/Handlers/NotificationHandler.cs +++ b/src/src/StevesBot.Webhook/YouTube/Handlers/NotificationHandler.cs @@ -8,7 +8,7 @@ internal static class NotificationHandler [FromServices] IYouTubeDataApiClient youTubeDataApiClient, [FromServices] IDiscordRestClient discordRestClient, [FromServices] IOptionsMonitor discordNotificationOptions, - [FromServices] ILastPostedStreamStore lastPostedStream, + [FromServices] ILastPostedStreamStore lastPostedStreamStore, CancellationToken cancellationToken = default ) { @@ -40,13 +40,13 @@ internal static class NotificationHandler return Results.Ok(); } - if (lastPostedStream.HasValue(videoId)) + if (lastPostedStreamStore.HasValue(videoId)) { logger.LogInformation("Video ID {VideoId} has already been posted. Skipping notification.", videoId); return Results.Ok(); } - lastPostedStream.SetValue(videoId); + lastPostedStreamStore.SetValue(videoId); logger.LogInformation( "Video ID {VideoId} is a live stream. Creating discord message in channel {ChannelId}", diff --git a/src/tests/StevesBot.Webhook.Tests/StevesBot.Webhook.Tests.csproj b/src/tests/StevesBot.Webhook.Tests/StevesBot.Webhook.Tests.csproj index eedb9b8..b2ad87a 100644 --- a/src/tests/StevesBot.Webhook.Tests/StevesBot.Webhook.Tests.csproj +++ b/src/tests/StevesBot.Webhook.Tests/StevesBot.Webhook.Tests.csproj @@ -27,6 +27,7 @@ ./TestResults/Coverage/ cobertura [StevesBot.Webhook]* + [StevesBot.Webhook]System.Text.RegularExpressions.Generated.* **/Program.cs,**/SubscriptionWorker.cs diff --git a/src/tests/StevesBot.Webhook.Tests/Unit/NotificationHandlerTests.cs b/src/tests/StevesBot.Webhook.Tests/Unit/NotificationHandlerTests.cs index 64e8ccc..8321783 100644 --- a/src/tests/StevesBot.Webhook.Tests/Unit/NotificationHandlerTests.cs +++ b/src/tests/StevesBot.Webhook.Tests/Unit/NotificationHandlerTests.cs @@ -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; public class NotificationHandlerTests { + private readonly Mock _mockHttpRequest = new(); + private readonly Mock _mockHttpContext = new(); + private readonly Mock> _mockLogger = new(); + private readonly Mock _mockYtDataApiClient = new(); + private readonly Mock _mockDiscordRestClient = new(); + private readonly Mock> _mockNotificationsOptions = new(); + private readonly Mock _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>(); + } + + [Fact] + public async Task HandleAsync_WhenVideoIsNotFound_ItShouldReturnNotFound() + { + SetupMockRequestBodyStream("videoId"); + + _mockYtDataApiClient + .Setup(static x => x.GetVideoByIdAsync( + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync(null as YouTubeVideo); + + var result = await HandleAsync(); + + result.Should().BeOfType(); + } + + [Fact] + public async Task HandleAsync_WhenVideoIsFoundButIsNotALiveStream_ItShouldReturnOkButNotCreateDiscordMessage() + { + SetupMockRequestBodyStream("videoId"); + + _mockYtDataApiClient + .Setup(static x => x.GetVideoByIdAsync( + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync(new YouTubeVideo()); + + var result = await HandleAsync(); + + result.Should().BeOfType(); + + _mockLastPostedStore + .Verify(static x => x.SetValue(It.IsAny()), Times.Never); + + _mockDiscordRestClient + .Verify( + static x => x.CreateMessageAsync( + It.IsAny(), + It.IsAny(), + It.IsAny() + ), + Times.Never + ); + } + + [Fact] + public async Task HandleAsync_WhenVideoIsFoundButItHasSameIdAsLastVideo_ItShouldReturnOkButNotCreateDiscordMessage() + { + var videoId = "video_id"; + + SetupMockRequestBodyStream($"{videoId}"); + + _mockYtDataApiClient + .Setup(static x => x.GetVideoByIdAsync( + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync(new YouTubeVideo() + { + Id = videoId, + LiveStreamingDetails = new(), + Snippet = new() + { + LiveBroadcastContent = "live" + } + }); + + _mockLastPostedStore + .Setup(static x => x.HasValue(It.IsAny())) + .Returns(true); + + var result = await HandleAsync(); + + result.Should().BeOfType(); + + _mockLastPostedStore + .Verify(static x => x.SetValue(It.IsAny()), Times.Never); + + _mockDiscordRestClient + .Verify( + static x => x.CreateMessageAsync( + It.IsAny(), + It.IsAny(), + It.IsAny() + ), + Times.Never + ); + } + + [Fact] + public async Task HandleAsync_WhenVideoIsFoundAndItIsANewLiveStream_ItShouldReturnOkStoreTheIdAndCreateADiscordMessage() + { + var videoId = "video_id"; + + SetupMockRequestBodyStream($"{videoId}"); + + _mockYtDataApiClient + .Setup(static x => x.GetVideoByIdAsync( + It.IsAny(), + It.IsAny(), + It.IsAny() + )) + .ReturnsAsync(new YouTubeVideo() + { + Id = videoId, + LiveStreamingDetails = new(), + Snippet = new() + { + LiveBroadcastContent = "live" + } + }); + + _mockLastPostedStore + .Setup(static x => x.HasValue(It.IsAny())) + .Returns(false); + + _mockNotificationsOptions + .Setup(x => x.CurrentValue) + .Returns(new DiscordNotificationOptions()); + + var result = await HandleAsync(); + + result.Should().BeOfType(); + + _mockLastPostedStore + .Verify( + x => x.SetValue( + It.Is(s => string.Equals(s, videoId, StringComparison.Ordinal)) + ), + Times.Once + ); + + _mockDiscordRestClient + .Verify( + static x => x.CreateMessageAsync( + It.IsAny(), + It.IsAny(), + It.IsAny() + ), + 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 HandleAsync() + { + return NotificationHandler.HandleAsync( + _mockHttpContext.Object, + _mockLogger.Object, + _mockYtDataApiClient.Object, + _mockDiscordRestClient.Object, + _mockNotificationsOptions.Object, + _mockLastPostedStore.Object + ); + } } \ No newline at end of file