using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; using CampusErrand.Data; using CampusErrand.Models.Dtos; using FsCheck; using FsCheck.Xunit; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; namespace CampusErrand.Tests; /// /// Property 25: Banner 创建校验 /// 对任意 Banner 创建或更新请求,图片地址不为空、链接类型为合法枚举值、链接地址不为空时应成功;否则应返回校验错误。 /// **Feature: login-and-homepage, Property 25: Banner 创建校验** /// /// public class BannerValidationPropertyTests : IDisposable { private const string JwtSecret = "YourSuperSecretKeyForJwtTokenGeneration_AtLeast32Chars!"; private const string JwtIssuer = "CampusErrand"; private const string JwtAudience = "CampusErrandApp"; private readonly List> _factories = []; private WebApplicationFactory CreateFactory(string dbName) { var factory = new WebApplicationFactory() .WithWebHostBuilder(builder => { builder.ConfigureServices(services => { var efDescriptors = services .Where(d => d.ServiceType.FullName?.Contains("EntityFrameworkCore") == true || d.ServiceType == typeof(DbContextOptions) || d.ServiceType == typeof(DbContextOptions) || d.ImplementationType?.FullName?.Contains("EntityFrameworkCore") == true) .ToList(); foreach (var d in efDescriptors) services.Remove(d); services.AddDbContext(options => options.UseInMemoryDatabase(dbName)); }); }); _factories.Add(factory); return factory; } private static string GenerateAdminToken() { var key = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey( System.Text.Encoding.UTF8.GetBytes(JwtSecret)); var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials( key, Microsoft.IdentityModel.Tokens.SecurityAlgorithms.HmacSha256); var claims = new[] { new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.NameIdentifier, "1"), new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, "Admin") }; var token = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken( issuer: JwtIssuer, audience: JwtAudience, claims: claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: credentials); return new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler().WriteToken(token); } /// /// 属性:合法的 Banner 请求(非空图片、合法链接类型、非空链接地址)应创建成功 /// [Property(MaxTest = 20)] public bool 合法Banner请求应创建成功(PositiveInt sortOrder, bool isExternal) { var dbName = $"banner_valid_{Guid.NewGuid()}"; using var factory = CreateFactory(dbName); var client = factory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GenerateAdminToken()); var request = new BannerRequest { ImageUrl = $"https://img.example.com/{sortOrder.Get}.png", LinkType = isExternal ? "External" : "Internal", LinkUrl = isExternal ? "https://example.com" : "/pages/index/index", SortOrder = sortOrder.Get, IsEnabled = true }; var response = client.PostAsJsonAsync("/api/admin/banners", request).Result; return response.StatusCode == HttpStatusCode.Created; } /// /// 属性:图片地址为空时应返回 400 /// [Property(MaxTest = 20)] public bool 图片地址为空时返回400(PositiveInt sortOrder) { var dbName = $"banner_noimg_{Guid.NewGuid()}"; using var factory = CreateFactory(dbName); var client = factory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GenerateAdminToken()); var request = new BannerRequest { ImageUrl = "", LinkType = "External", LinkUrl = "https://example.com", SortOrder = sortOrder.Get, IsEnabled = true }; var response = client.PostAsJsonAsync("/api/admin/banners", request).Result; return response.StatusCode == HttpStatusCode.BadRequest; } /// /// 属性:链接地址为空时应返回 400 /// [Property(MaxTest = 20)] public bool 链接地址为空时返回400(PositiveInt sortOrder) { var dbName = $"banner_nolink_{Guid.NewGuid()}"; using var factory = CreateFactory(dbName); var client = factory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GenerateAdminToken()); var request = new BannerRequest { ImageUrl = "https://img.example.com/test.png", LinkType = "External", LinkUrl = "", SortOrder = sortOrder.Get, IsEnabled = true }; var response = client.PostAsJsonAsync("/api/admin/banners", request).Result; return response.StatusCode == HttpStatusCode.BadRequest; } /// /// 属性:链接类型不合法时应返回 400 /// [Property(MaxTest = 20)] public bool 链接类型不合法时返回400(NonEmptyString invalidType) { // 跳过合法的枚举值(名称和数字形式) var val = invalidType.Get; if (val.Equals("External", StringComparison.OrdinalIgnoreCase) || val.Equals("Internal", StringComparison.OrdinalIgnoreCase)) return true; // 跳过能被解析为已定义枚举值的数字字符串 if (Enum.TryParse(val, true, out var parsed) && Enum.IsDefined(parsed)) return true; var dbName = $"banner_badtype_{Guid.NewGuid()}"; using var factory = CreateFactory(dbName); var client = factory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GenerateAdminToken()); var request = new BannerRequest { ImageUrl = "https://img.example.com/test.png", LinkType = val, LinkUrl = "https://example.com", SortOrder = 1, IsEnabled = true }; var response = client.PostAsJsonAsync("/api/admin/banners", request).Result; return response.StatusCode == HttpStatusCode.BadRequest; } public void Dispose() { foreach (var f in _factories) f.Dispose(); _factories.Clear(); } }