using FsCheck;
using FsCheck.Xunit;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using MiAssessment.Core.Services;
using MiAssessment.Model.Data;
using MiAssessment.Model.Entities;
using Moq;
using Xunit;
namespace MiAssessment.Tests.Services;
///
/// TeamService 属性测试
/// 验证团队服务的列表查询过滤和排序正确性
///
public class TeamServicePropertyTests
{
private readonly Mock> _mockLogger = new();
#region Property 1: 列表查询过滤正确性
///
/// Property 1: 团队介绍只返回Position=2的宣传图
/// *For any* Team info query, the returned items SHALL only include records
/// with Position=2, Status=1, and IsDeleted=false.
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 15.1**
///
[Property(MaxTest = 100)]
public bool TeamInfoOnlyReturnsTeamPagePromotions(PositiveInt seed)
{
// Arrange: 创建包含不同Position的Promotion数据
using var dbContext = CreateDbContext();
// 创建团队页位置(Position=2)且启用的宣传图
for (int i = 0; i < 3; i++)
{
dbContext.Promotions.Add(CreatePromotion(seed.Get + i, position: 2, status: 1, isDeleted: false));
}
// 创建首页位置(Position=1)的宣传图
for (int i = 0; i < 2; i++)
{
dbContext.Promotions.Add(CreatePromotion(seed.Get + 100 + i, position: 1, status: 1, isDeleted: false));
}
dbContext.SaveChanges();
var service = new TeamService(dbContext, _mockLogger.Object);
// Act: 调用服务方法
var result = service.GetInfoAsync().GetAwaiter().GetResult();
// Assert: 验证返回结果只包含Position=2的记录
// 1. 返回的图片数应该等于团队页位置且启用且未删除的记录数
if (result.Images.Count != 3) return false;
// 2. 验证数据库中确实存在首页位置的记录
var allPromotions = dbContext.Promotions.ToList();
var homePagePromotions = allPromotions.Where(p => p.Position == 1 && p.Status == 1 && !p.IsDeleted).ToList();
if (homePagePromotions.Count != 2) return false;
// 3. 验证返回的图片URL都来自Position=2的记录
var teamPageImageUrls = allPromotions
.Where(p => p.Position == 2 && p.Status == 1 && !p.IsDeleted)
.Select(p => p.ImageUrl)
.ToHashSet();
return result.Images.All(img => teamPageImageUrls.Contains(img));
}
///
/// Property 1: 团队介绍只返回启用状态的记录
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 15.1**
///
[Property(MaxTest = 100)]
public bool TeamInfoOnlyReturnsEnabledRecords(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
// 创建团队页位置且启用的宣传图
var enabledPromotion = CreatePromotion(seed.Get, position: 2, status: 1, isDeleted: false);
dbContext.Promotions.Add(enabledPromotion);
// 创建团队页位置但禁用的宣传图
var disabledPromotion = CreatePromotion(seed.Get + 1, position: 2, status: 0, isDeleted: false);
dbContext.Promotions.Add(disabledPromotion);
dbContext.SaveChanges();
var service = new TeamService(dbContext, _mockLogger.Object);
// Act
var result = service.GetInfoAsync().GetAwaiter().GetResult();
// Assert: 禁用的记录不应出现在结果中
return result.Images.Count == 1 && result.Images[0] == enabledPromotion.ImageUrl;
}
///
/// Property 1: 团队介绍不返回已删除的记录
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 15.1**
///
[Property(MaxTest = 100)]
public bool TeamInfoExcludesDeletedRecords(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
// 创建团队页位置且启用的宣传图
var activePromotion = CreatePromotion(seed.Get, position: 2, status: 1, isDeleted: false);
dbContext.Promotions.Add(activePromotion);
// 创建团队页位置但已删除的宣传图(即使Status=1)
var deletedPromotion = CreatePromotion(seed.Get + 1, position: 2, status: 1, isDeleted: true);
dbContext.Promotions.Add(deletedPromotion);
dbContext.SaveChanges();
var service = new TeamService(dbContext, _mockLogger.Object);
// Act
var result = service.GetInfoAsync().GetAwaiter().GetResult();
// Assert: 已删除的记录不应出现在结果中
return result.Images.Count == 1 && result.Images[0] == activePromotion.ImageUrl;
}
///
/// Property 1: 团队介绍不返回首页位置的记录
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 15.1**
///
[Property(MaxTest = 100)]
public bool TeamInfoExcludesHomePageRecords(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
// 创建团队页位置的宣传图
var teamPromotion = CreatePromotion(seed.Get, position: 2, status: 1, isDeleted: false);
dbContext.Promotions.Add(teamPromotion);
// 创建首页位置的宣传图
var homePromotion = CreatePromotion(seed.Get + 1, position: 1, status: 1, isDeleted: false);
dbContext.Promotions.Add(homePromotion);
dbContext.SaveChanges();
var service = new TeamService(dbContext, _mockLogger.Object);
// Act
var result = service.GetInfoAsync().GetAwaiter().GetResult();
// Assert: 只返回团队页位置的记录
return result.Images.Count == 1 && result.Images[0] == teamPromotion.ImageUrl;
}
///
/// Property 1: 团队介绍按Sort字段降序排列
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 15.1**
///
[Property(MaxTest = 100)]
public bool TeamInfoSortedBySortDescending(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
var random = new Random(seed.Get);
// 创建具有不同Sort值的团队页宣传图
var promotions = new List();
for (int i = 0; i < 5; i++)
{
var sortValue = random.Next(1, 1000);
var promotion = CreatePromotionWithSort(seed.Get + i, sortValue);
promotions.Add(promotion);
dbContext.Promotions.Add(promotion);
}
dbContext.SaveChanges();
var service = new TeamService(dbContext, _mockLogger.Object);
// Act
var result = service.GetInfoAsync().GetAwaiter().GetResult();
// Assert: 验证列表按Sort降序排列
if (result.Images.Count < 2) return true;
// 获取按Sort降序排列的预期顺序
var expectedOrder = promotions
.OrderByDescending(p => p.Sort)
.Select(p => p.ImageUrl)
.ToList();
for (int i = 0; i < result.Images.Count; i++)
{
if (result.Images[i] != expectedOrder[i])
{
return false;
}
}
return true;
}
///
/// Property 1: 团队介绍第一个元素应该是Sort值最大的
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 15.1**
///
[Property(MaxTest = 100)]
public bool TeamInfoFirstItemHasHighestSort(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
var random = new Random(seed.Get);
// 创建具有不同Sort值的团队页宣传图
var promotions = new List();
for (int i = 0; i < 5; i++)
{
var sortValue = random.Next(1, 1000);
var promotion = CreatePromotionWithSort(seed.Get + i, sortValue);
promotions.Add(promotion);
dbContext.Promotions.Add(promotion);
}
dbContext.SaveChanges();
var service = new TeamService(dbContext, _mockLogger.Object);
// Act
var result = service.GetInfoAsync().GetAwaiter().GetResult();
// Assert: 第一个元素应该是Sort值最大的
if (result.Images.Count == 0) return true;
var maxSortPromotion = promotions.OrderByDescending(p => p.Sort).First();
return result.Images[0] == maxSortPromotion.ImageUrl;
}
#endregion
#region 综合属性测试
///
/// Property 1: 空数据库返回空列表
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 15.1**
///
[Fact]
public void EmptyDatabaseReturnsEmptyImageList()
{
// Arrange
using var dbContext = CreateDbContext();
var service = new TeamService(dbContext, _mockLogger.Object);
// Act
var result = service.GetInfoAsync().GetAwaiter().GetResult();
// Assert
Assert.Empty(result.Images);
}
///
/// Property 1: 所有记录都被过滤时返回空列表
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 15.1**
///
[Property(MaxTest = 50)]
public bool AllFilteredRecordsReturnsEmptyList(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
// 只创建不符合条件的记录
// 首页位置的宣传图(Position=1)
dbContext.Promotions.Add(CreatePromotion(seed.Get, position: 1, status: 1, isDeleted: false));
// 团队页位置但禁用的宣传图
dbContext.Promotions.Add(CreatePromotion(seed.Get + 1, position: 2, status: 0, isDeleted: false));
// 团队页位置但已删除的宣传图
dbContext.Promotions.Add(CreatePromotion(seed.Get + 2, position: 2, status: 1, isDeleted: true));
// 禁用且已删除的宣传图
dbContext.Promotions.Add(CreatePromotion(seed.Get + 3, position: 2, status: 0, isDeleted: true));
dbContext.SaveChanges();
var service = new TeamService(dbContext, _mockLogger.Object);
// Act
var result = service.GetInfoAsync().GetAwaiter().GetResult();
// Assert: 列表应该为空
return result.Images.Count == 0;
}
///
/// Property 1: 过滤和排序同时正确
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 15.1**
///
[Property(MaxTest = 100)]
public bool FilteringAndSortingWorkTogether(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
var random = new Random(seed.Get);
// 创建团队页位置且启用的宣传图(不同Sort值)
var teamPromotions = new List();
for (int i = 0; i < 3; i++)
{
var sortValue = random.Next(1, 1000);
var promotion = CreatePromotionWithSort(seed.Get + i, sortValue);
teamPromotions.Add(promotion);
dbContext.Promotions.Add(promotion);
}
// 创建首页位置的宣传图(高Sort值,不应出现在结果中)
var homePromotion = CreatePromotion(seed.Get + 100, position: 1, status: 1, isDeleted: false);
homePromotion.Sort = 9999; // 最高Sort值
dbContext.Promotions.Add(homePromotion);
// 创建团队页位置但禁用的宣传图(高Sort值,不应出现在结果中)
var disabledPromotion = CreatePromotion(seed.Get + 200, position: 2, status: 0, isDeleted: false);
disabledPromotion.Sort = 9998; // 第二高Sort值
dbContext.Promotions.Add(disabledPromotion);
// 创建团队页位置但已删除的宣传图(高Sort值,不应出现在结果中)
var deletedPromotion = CreatePromotion(seed.Get + 300, position: 2, status: 1, isDeleted: true);
deletedPromotion.Sort = 9997; // 第三高Sort值
dbContext.Promotions.Add(deletedPromotion);
dbContext.SaveChanges();
var service = new TeamService(dbContext, _mockLogger.Object);
// Act
var result = service.GetInfoAsync().GetAwaiter().GetResult();
// Assert:
// 1. 只返回团队页位置且启用且未删除的记录
if (result.Images.Count != 3) return false;
// 2. 不包含首页位置、禁用或已删除的记录
var excludedImageUrls = new HashSet
{
homePromotion.ImageUrl,
disabledPromotion.ImageUrl,
deletedPromotion.ImageUrl
};
if (result.Images.Any(img => excludedImageUrls.Contains(img)))
{
return false;
}
// 3. 按Sort降序排列
var expectedOrder = teamPromotions
.OrderByDescending(p => p.Sort)
.Select(p => p.ImageUrl)
.ToList();
for (int i = 0; i < result.Images.Count; i++)
{
if (result.Images[i] != expectedOrder[i])
{
return false;
}
}
return true;
}
///
/// Property 1: 排序稳定性
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 15.1**
///
[Property(MaxTest = 50)]
public bool TeamInfoSortingIsStable(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
// 创建具有相同Sort值的团队页宣传图
var sameSort = seed.Get % 100;
for (int i = 0; i < 3; i++)
{
var promotion = CreatePromotionWithSort(seed.Get + i, sameSort);
dbContext.Promotions.Add(promotion);
}
dbContext.SaveChanges();
var service = new TeamService(dbContext, _mockLogger.Object);
// Act: 多次调用应该返回相同的顺序
var result1 = service.GetInfoAsync().GetAwaiter().GetResult();
var result2 = service.GetInfoAsync().GetAwaiter().GetResult();
// Assert: 两次调用返回的顺序应该相同
if (result1.Images.Count != result2.Images.Count) return false;
for (int i = 0; i < result1.Images.Count; i++)
{
if (result1.Images[i] != result2.Images[i]) return false;
}
return true;
}
#endregion
#region 辅助方法
///
/// 创建内存数据库上下文
///
private MiAssessmentDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new MiAssessmentDbContext(options);
}
///
/// 创建测试宣传图
///
private Promotion CreatePromotion(int seed, int position, int status, bool isDeleted)
{
return new Promotion
{
Title = $"Test Promotion {seed}",
ImageUrl = $"https://example.com/promotion_{seed}.jpg",
Position = position,
Sort = seed % 100,
Status = status,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now,
IsDeleted = isDeleted
};
}
///
/// 创建指定Sort值的测试宣传图(团队页位置)
///
private Promotion CreatePromotionWithSort(int seed, int sort)
{
return new Promotion
{
Title = $"Test Promotion {seed}",
ImageUrl = $"https://example.com/promotion_{seed}.jpg",
Position = 2, // 团队页位置
Sort = sort,
Status = 1,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now,
IsDeleted = false
};
}
#endregion
}