using Xunit; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Moq; using System.Security.Claims; using System.Threading.Tasks; using System; using System.Collections.Generic; using System.Text.Json; using LittleShop.Controllers; using LittleShop.Services; using LittleShop.DTOs; namespace LittleShop.Tests.Unit; public class PushNotificationControllerTests { private readonly Mock _pushServiceMock; private readonly PushNotificationController _controller; private readonly Guid _testUserId = Guid.NewGuid(); public PushNotificationControllerTests() { _pushServiceMock = new Mock(); _controller = new PushNotificationController(_pushServiceMock.Object); // Setup controller context for authenticated user SetupControllerContext(); } private void SetupControllerContext() { var claims = new List { new Claim(ClaimTypes.NameIdentifier, _testUserId.ToString()), new Claim(ClaimTypes.Name, "testuser"), new Claim(ClaimTypes.Role, "Admin") }; var identity = new ClaimsIdentity(claims, "TestAuth"); var principal = new ClaimsPrincipal(identity); // Setup service provider with logger var services = new ServiceCollection(); services.AddLogging(); var serviceProvider = services.BuildServiceProvider(); var httpContext = new DefaultHttpContext { User = principal, Connection = { RemoteIpAddress = System.Net.IPAddress.Parse("192.168.1.1") }, RequestServices = serviceProvider }; _controller.ControllerContext = new ControllerContext { HttpContext = httpContext }; // Setup User-Agent header _controller.HttpContext.Request.Headers.Add("User-Agent", "TestBrowser/1.0"); } private string GetPropertyFromResult(object resultValue, string propertyName) { var jsonString = JsonSerializer.Serialize(resultValue); var response = JsonSerializer.Deserialize(jsonString); // Handle case where the result is a simple string value if (response.ValueKind == JsonValueKind.String) { return response.GetString()!; } return response.GetProperty(propertyName).GetString()!; } [Fact] public void GetVapidPublicKey_ReturnsPublicKey() { // Arrange var expectedKey = "test-public-key"; _pushServiceMock.Setup(s => s.GetVapidPublicKey()).Returns(expectedKey); // Act var result = _controller.GetVapidPublicKey(); // Assert var okResult = Assert.IsType(result); var jsonString = JsonSerializer.Serialize(okResult.Value); var response = JsonSerializer.Deserialize(jsonString); Assert.Equal(expectedKey, response.GetProperty("publicKey").GetString()); } [Fact] public void GetVapidPublicKey_ReturnsInternalServerError_OnException() { // Arrange _pushServiceMock.Setup(s => s.GetVapidPublicKey()).Throws(new Exception("Test exception")); // Act var result = _controller.GetVapidPublicKey(); // Assert var statusResult = Assert.IsType(result); Assert.Equal(500, statusResult.StatusCode); } [Fact] public async Task Subscribe_ReturnsOk_WhenSubscriptionSuccessful() { // Arrange var subscriptionDto = new PushSubscriptionDto { Endpoint = "https://fcm.googleapis.com/fcm/send/test", P256DH = "test-p256dh", Auth = "test-auth" }; _pushServiceMock .Setup(s => s.SubscribeUserAsync(_testUserId, subscriptionDto, "TestBrowser/1.0", "192.168.1.1")) .ReturnsAsync(true); // Act var result = await _controller.Subscribe(subscriptionDto); // Assert var okResult = Assert.IsType(result); var message = GetPropertyFromResult(okResult.Value!, "message"); Assert.Equal("Successfully subscribed to push notifications", message); } [Fact] public async Task Subscribe_ReturnsInternalServerError_WhenSubscriptionFails() { // Arrange var subscriptionDto = new PushSubscriptionDto { Endpoint = "https://fcm.googleapis.com/fcm/send/test", P256DH = "test-p256dh", Auth = "test-auth" }; _pushServiceMock .Setup(s => s.SubscribeUserAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(false); // Act var result = await _controller.Subscribe(subscriptionDto); // Assert var statusResult = Assert.IsType(result); Assert.Equal(500, statusResult.StatusCode); } [Fact] public async Task Subscribe_ReturnsUnauthorized_WhenUserIdInvalid() { // Arrange // Setup controller with invalid user ID var claims = new List { new Claim(ClaimTypes.NameIdentifier, "invalid-guid"), new Claim(ClaimTypes.Role, "Admin") }; var identity = new ClaimsIdentity(claims, "TestAuth"); var principal = new ClaimsPrincipal(identity); _controller.ControllerContext.HttpContext.User = principal; var subscriptionDto = new PushSubscriptionDto { Endpoint = "https://fcm.googleapis.com/fcm/send/test", P256DH = "test-p256dh", Auth = "test-auth" }; // Act var result = await _controller.Subscribe(subscriptionDto); // Assert var unauthorizedResult = Assert.IsType(result); var error = GetPropertyFromResult(unauthorizedResult.Value!, "error"); Assert.Equal("Invalid user ID", error); } [Fact] public async Task SubscribeCustomer_ReturnsOk_WhenSuccessful() { // Arrange var customerId = Guid.NewGuid(); var subscriptionDto = new PushSubscriptionDto { Endpoint = "https://fcm.googleapis.com/fcm/send/customer", P256DH = "customer-p256dh", Auth = "customer-auth" }; _pushServiceMock .Setup(s => s.SubscribeCustomerAsync(customerId, subscriptionDto, It.IsAny(), It.IsAny())) .ReturnsAsync(true); // Act var result = await _controller.SubscribeCustomer(subscriptionDto, customerId); // Assert var okResult = Assert.IsType(result); var message = GetPropertyFromResult(okResult.Value!, "message"); Assert.Equal("Successfully subscribed to push notifications", message); } [Fact] public async Task SubscribeCustomer_ReturnsBadRequest_WhenCustomerIdEmpty() { // Arrange var subscriptionDto = new PushSubscriptionDto { Endpoint = "https://fcm.googleapis.com/fcm/send/customer", P256DH = "customer-p256dh", Auth = "customer-auth" }; // Act var result = await _controller.SubscribeCustomer(subscriptionDto, Guid.Empty); // Assert var badRequestResult = Assert.IsType(result); var error = GetPropertyFromResult(badRequestResult.Value!, "error"); Assert.Equal("Invalid customer ID", error); } [Fact] public async Task Unsubscribe_ReturnsOk_WhenSuccessful() { // Arrange var unsubscribeDto = new UnsubscribeDto { Endpoint = "https://fcm.googleapis.com/fcm/send/test" }; _pushServiceMock.Setup(s => s.UnsubscribeAsync(unsubscribeDto.Endpoint)).ReturnsAsync(true); // Act var result = await _controller.Unsubscribe(unsubscribeDto); // Assert var okResult = Assert.IsType(result); var message = GetPropertyFromResult(okResult.Value!, "message"); Assert.Equal("Successfully unsubscribed from push notifications", message); } [Fact] public async Task Unsubscribe_ReturnsNotFound_WhenSubscriptionNotFound() { // Arrange var unsubscribeDto = new UnsubscribeDto { Endpoint = "https://fcm.googleapis.com/fcm/send/nonexistent" }; _pushServiceMock.Setup(s => s.UnsubscribeAsync(unsubscribeDto.Endpoint)).ReturnsAsync(false); // Act var result = await _controller.Unsubscribe(unsubscribeDto); // Assert var notFoundResult = Assert.IsType(result); var error = GetPropertyFromResult(notFoundResult.Value!, "error"); Assert.Equal("Subscription not found", error); } [Fact] public async Task SendTestNotification_ReturnsOk_WhenSuccessful() { // Arrange var testDto = new TestPushNotificationDto { Title = "Test Title", Body = "Test Body" }; _pushServiceMock .Setup(s => s.SendTestNotificationAsync(_testUserId.ToString(), testDto.Title, testDto.Body)) .ReturnsAsync(true); // Act var result = await _controller.SendTestNotification(testDto); // Assert var okResult = Assert.IsType(result); var message = GetPropertyFromResult(okResult.Value!, "message"); Assert.Equal("Test notification sent successfully", message); } [Fact] public async Task SendTestNotification_ReturnsInternalServerError_WhenFails() { // Arrange var testDto = new TestPushNotificationDto { Title = "Test Title", Body = "Test Body" }; _pushServiceMock .Setup(s => s.SendTestNotificationAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(false); // Act var result = await _controller.SendTestNotification(testDto); // Assert var statusResult = Assert.IsType(result); Assert.Equal(500, statusResult.StatusCode); var error = GetPropertyFromResult(statusResult.Value!, "error"); Assert.Contains("Failed to send test notification", error); } [Fact] public async Task SendBroadcastNotification_ReturnsOk_WhenSuccessful() { // Arrange var notificationDto = new PushNotificationDto { Title = "Broadcast Title", Body = "Broadcast Body" }; _pushServiceMock .Setup(s => s.SendNotificationToAllUsersAsync(notificationDto)) .ReturnsAsync(true); // Act var result = await _controller.SendBroadcastNotification(notificationDto); // Assert var okResult = Assert.IsType(result); var message = GetPropertyFromResult(okResult.Value!, "message"); Assert.Equal("Broadcast notification sent successfully", message); } [Fact] public async Task GetActiveSubscriptions_ReturnsOk_WithSubscriptions() { // Arrange var subscriptions = new List { new LittleShop.Models.PushSubscription { Id = 1, UserId = _testUserId, Endpoint = "https://fcm.googleapis.com/fcm/send/test-long-endpoint-that-should-be-truncated", SubscribedAt = DateTime.UtcNow, LastUsedAt = DateTime.UtcNow, UserAgent = "TestBrowser/1.0", User = new LittleShop.Models.User { Username = "testuser" } } }; _pushServiceMock.Setup(s => s.GetActiveSubscriptionsAsync()).ReturnsAsync(subscriptions); // Act var result = await _controller.GetActiveSubscriptions(); // Assert var okResult = Assert.IsType(result); var jsonString = JsonSerializer.Serialize(okResult.Value); var subscriptionArray = JsonSerializer.Deserialize(jsonString); Assert.NotNull(subscriptionArray); Assert.Single(subscriptionArray); } [Fact] public async Task CleanupExpiredSubscriptions_ReturnsOk_WithCleanupCount() { // Arrange _pushServiceMock.Setup(s => s.CleanupExpiredSubscriptionsAsync()).ReturnsAsync(5); // Act var result = await _controller.CleanupExpiredSubscriptions(); // Assert var okResult = Assert.IsType(result); var message = GetPropertyFromResult(okResult.Value!, "message"); Assert.Equal("Cleaned up 5 expired subscriptions", message); } }