campus-errand/server.tests/BannerValidationPropertyTests.cs
2026-03-01 05:01:47 +08:00

184 lines
7.1 KiB
C#

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;
/// <summary>
/// Property 25: Banner 创建校验
/// 对任意 Banner 创建或更新请求,图片地址不为空、链接类型为合法枚举值、链接地址不为空时应成功;否则应返回校验错误。
/// **Feature: login-and-homepage, Property 25: Banner 创建校验**
/// **Validates: Requirements 30.6, 30.7**
/// </summary>
public class BannerValidationPropertyTests : IDisposable
{
private const string JwtSecret = "YourSuperSecretKeyForJwtTokenGeneration_AtLeast32Chars!";
private const string JwtIssuer = "CampusErrand";
private const string JwtAudience = "CampusErrandApp";
private readonly List<WebApplicationFactory<Program>> _factories = [];
private WebApplicationFactory<Program> CreateFactory(string dbName)
{
var factory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
var efDescriptors = services
.Where(d =>
d.ServiceType.FullName?.Contains("EntityFrameworkCore") == true
|| d.ServiceType == typeof(DbContextOptions<AppDbContext>)
|| d.ServiceType == typeof(DbContextOptions)
|| d.ImplementationType?.FullName?.Contains("EntityFrameworkCore") == true)
.ToList();
foreach (var d in efDescriptors) services.Remove(d);
services.AddDbContext<AppDbContext>(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);
}
/// <summary>
/// 属性:合法的 Banner 请求(非空图片、合法链接类型、非空链接地址)应创建成功
/// </summary>
[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;
}
/// <summary>
/// 属性:图片地址为空时应返回 400
/// </summary>
[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;
}
/// <summary>
/// 属性:链接地址为空时应返回 400
/// </summary>
[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;
}
/// <summary>
/// 属性:链接类型不合法时应返回 400
/// </summary>
[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<CampusErrand.Models.LinkType>(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();
}
}