using System.Security.Claims; using ColaFlow.API.Hubs; using FluentAssertions; using Microsoft.AspNetCore.SignalR; using Moq; namespace ColaFlow.API.Tests.Hubs; public class BaseHubTests { private readonly Mock _mockClients; private readonly Mock _mockGroups; private readonly Mock _mockContext; private readonly TestHub _hub; public BaseHubTests() { _mockClients = new Mock(); _mockGroups = new Mock(); _mockContext = new Mock(); _hub = new TestHub { Clients = _mockClients.Object, Groups = _mockGroups.Object, Context = _mockContext.Object }; } [Fact] public void GetCurrentUserId_WithValidSubClaim_ReturnsUserId() { // Arrange var userId = Guid.NewGuid(); var claims = new[] { new Claim("sub", userId.ToString()) }; _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims))); // Act var result = _hub.GetCurrentUserIdPublic(); // Assert result.Should().Be(userId); } [Fact] public void GetCurrentUserId_WithValidUserIdClaim_ReturnsUserId() { // Arrange var userId = Guid.NewGuid(); var claims = new[] { new Claim("user_id", userId.ToString()) }; _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims))); // Act var result = _hub.GetCurrentUserIdPublic(); // Assert result.Should().Be(userId); } [Fact] public void GetCurrentUserId_WithMissingClaim_ThrowsUnauthorizedException() { // Arrange var claims = new[] { new Claim("some_other_claim", "value") }; _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims))); // Act & Assert var act = () => _hub.GetCurrentUserIdPublic(); act.Should().Throw() .WithMessage("User ID not found in token"); } [Fact] public void GetCurrentUserId_WithInvalidGuid_ThrowsUnauthorizedException() { // Arrange var claims = new[] { new Claim("sub", "not-a-guid") }; _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims))); // Act & Assert var act = () => _hub.GetCurrentUserIdPublic(); act.Should().Throw() .WithMessage("User ID not found in token"); } [Fact] public void GetCurrentUserId_WithNullUser_ThrowsUnauthorizedException() { // Arrange _mockContext.Setup(c => c.User).Returns((ClaimsPrincipal)null!); // Act & Assert var act = () => _hub.GetCurrentUserIdPublic(); act.Should().Throw() .WithMessage("User ID not found in token"); } [Fact] public void GetCurrentTenantId_WithValidClaim_ReturnsTenantId() { // Arrange var tenantId = Guid.NewGuid(); var claims = new[] { new Claim("tenant_id", tenantId.ToString()) }; _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims))); // Act var result = _hub.GetCurrentTenantIdPublic(); // Assert result.Should().Be(tenantId); } [Fact] public void GetCurrentTenantId_WithMissingClaim_ThrowsUnauthorizedException() { // Arrange var claims = new[] { new Claim("some_other_claim", "value") }; _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims))); // Act & Assert var act = () => _hub.GetCurrentTenantIdPublic(); act.Should().Throw() .WithMessage("Tenant ID not found in token"); } [Fact] public void GetCurrentTenantId_WithInvalidGuid_ThrowsUnauthorizedException() { // Arrange var claims = new[] { new Claim("tenant_id", "not-a-guid") }; _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims))); // Act & Assert var act = () => _hub.GetCurrentTenantIdPublic(); act.Should().Throw() .WithMessage("Tenant ID not found in token"); } [Fact] public void GetTenantGroupName_ReturnsCorrectFormat() { // Arrange var tenantId = Guid.NewGuid(); // Act var result = _hub.GetTenantGroupNamePublic(tenantId); // Assert result.Should().Be($"tenant-{tenantId}"); } [Fact] public async Task OnConnectedAsync_WithValidClaims_AutoJoinsTenantGroup() { // Arrange var userId = Guid.NewGuid(); var tenantId = Guid.NewGuid(); var connectionId = "test-connection-id"; var claims = new[] { new Claim("sub", userId.ToString()), new Claim("tenant_id", tenantId.ToString()) }; _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims))); _mockContext.Setup(c => c.ConnectionId).Returns(connectionId); // Act await _hub.OnConnectedAsync(); // Assert _mockGroups.Verify(g => g.AddToGroupAsync( connectionId, $"tenant-{tenantId}", default), Times.Once); } [Fact] public async Task OnConnectedAsync_WithMissingTenantId_AbortsConnection() { // Arrange var userId = Guid.NewGuid(); var claims = new[] { new Claim("sub", userId.ToString()) }; _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims))); _mockContext.Setup(c => c.ConnectionId).Returns("test-connection-id"); // Act await _hub.OnConnectedAsync(); // Assert _mockContext.Verify(c => c.Abort(), Times.Once); _mockGroups.Verify(g => g.AddToGroupAsync( It.IsAny(), It.IsAny(), default), Times.Never); } [Fact] public async Task OnConnectedAsync_WithMissingUserId_AbortsConnection() { // Arrange var tenantId = Guid.NewGuid(); var claims = new[] { new Claim("tenant_id", tenantId.ToString()) }; _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims))); _mockContext.Setup(c => c.ConnectionId).Returns("test-connection-id"); // Act await _hub.OnConnectedAsync(); // Assert _mockContext.Verify(c => c.Abort(), Times.Once); _mockGroups.Verify(g => g.AddToGroupAsync( It.IsAny(), It.IsAny(), default), Times.Never); } [Fact] public async Task OnDisconnectedAsync_WithValidClaims_CompletesSuccessfully() { // Arrange var userId = Guid.NewGuid(); var tenantId = Guid.NewGuid(); var claims = new[] { new Claim("sub", userId.ToString()), new Claim("tenant_id", tenantId.ToString()) }; _mockContext.Setup(c => c.User).Returns(new ClaimsPrincipal(new ClaimsIdentity(claims))); // Act var act = async () => await _hub.OnDisconnectedAsync(null); // Assert await act.Should().NotThrowAsync(); } // Test Hub implementation to expose protected methods private class TestHub : BaseHub { public Guid GetCurrentUserIdPublic() => GetCurrentUserId(); public Guid GetCurrentTenantIdPublic() => GetCurrentTenantId(); public string GetTenantGroupNamePublic(Guid tenantId) => GetTenantGroupName(tenantId); } }