using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using FsCheck; using FsCheck.Xunit; using MiAssessment.Core.Services; using MiAssessment.Model.Entities; using MiAssessment.Model.Models.Auth; using Microsoft.Extensions.Logging; using Moq; namespace MiAssessment.Tests.Services; public class JwtServicePropertyTests { private readonly JwtSettings _jwtSettings; private readonly Mock> _mockLogger; private readonly JwtService _jwtService; public JwtServicePropertyTests() { _jwtSettings = new JwtSettings { Secret = "your-secret-key-must-be-at-least-32-characters-long-for-hs256", Issuer = "MiAssessment", Audience = "MiAssessmentUsers", ExpirationMinutes = 1440, RefreshTokenExpirationDays = 7 }; _mockLogger = new Mock>(); _jwtService = new JwtService(_jwtSettings, _mockLogger.Object); } /// /// Property 7: Token验证与授权 /// For any valid token generated by the service, validation should succeed and return a valid ClaimsPrincipal. /// For any invalid token, validation should fail and return null. /// Validates: Requirements 3.3, 3.4 /// [Property(MaxTest = 100)] public bool ValidTokenShouldPassValidation(PositiveInt userId, NonEmptyString nickname) { // Create a valid user and generate a token var user = new User { Id = userId.Item, Nickname = nickname.Item, Uid = $"uid{userId.Item}", OpenId = $"openid{userId.Item}", HeadImg = "https://example.com/avatar.jpg", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; var validToken = _jwtService.GenerateToken(user); // Valid token should pass validation var validPrincipal = _jwtService.ValidateToken(validToken); var validTokenPasses = validPrincipal != null; // Invalid token should fail validation var invalidToken = "invalid.token.string"; var invalidPrincipal = _jwtService.ValidateToken(invalidToken); var invalidTokenFails = invalidPrincipal == null; // Null token should fail validation var nullPrincipal = _jwtService.ValidateToken(null!); var nullTokenFails = nullPrincipal == null; return validTokenPasses && invalidTokenFails && nullTokenFails; } /// /// Property 7 (continued): Token验证与授权 - Claim Extraction /// For any valid token, the extracted claims should match the original user data. /// Validates: Requirements 3.3, 3.4 /// [Property(MaxTest = 100)] public bool ValidatedTokenShouldContainCorrectClaims(PositiveInt userId, NonEmptyString nickname) { var user = new User { Id = userId.Item, Nickname = nickname.Item, Uid = $"uid{userId.Item}", OpenId = $"openid{userId.Item}", HeadImg = "https://example.com/avatar.jpg", CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; var token = _jwtService.GenerateToken(user); var principal = _jwtService.ValidateToken(token); if (principal == null) return false; // Check that the principal contains the correct user ID claim var userIdClaim = principal.FindFirst(ClaimTypes.NameIdentifier); var userIdMatches = userIdClaim != null && userIdClaim.Value == userId.Item.ToString(); // Check that the principal contains the correct nickname claim var nameClaim = principal.FindFirst(ClaimTypes.Name); var nicknameMatches = nameClaim != null && nameClaim.Value == nickname.Item; // Check that the principal contains the correct uid claim var uidClaim = principal.FindFirst("uid"); var uidMatches = uidClaim != null && uidClaim.Value == $"uid{userId.Item}"; return userIdMatches && nicknameMatches && uidMatches; } /// /// Property 7 (continued): Token验证与授权 - Unauthorized Access /// For any request with an invalid or missing token, the system should deny access. /// Validates: Requirements 3.3, 3.4 /// [Property(MaxTest = 100)] public bool InvalidTokenShouldDenyAccess(NonEmptyString invalidToken) { // Any random string should not validate as a token var principal = _jwtService.ValidateToken(invalidToken.Item); return principal == null; } }