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;
///
/// PlannerService 属性测试
/// 验证规划师服务的列表查询过滤和排序正确性
///
public class PlannerServicePropertyTests
{
private readonly Mock> _mockLogger = new();
#region Property 1: 列表查询过滤正确性
///
/// Property 1: 规划师列表只返回启用状态的记录
/// *For any* Planner list query, the returned items SHALL only include records
/// with Status=1 and IsDeleted=false.
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 10.1**
///
[Property(MaxTest = 100)]
public bool PlannerListOnlyReturnsEnabledRecords(PositiveInt seed)
{
// Arrange: 创建包含不同状态的Planner数据
using var dbContext = CreateDbContext();
// 创建启用的规划师
for (int i = 0; i < 3; i++)
{
dbContext.Planners.Add(CreatePlanner(seed.Get + i, status: 1, isDeleted: false));
}
// 创建禁用的规划师
for (int i = 0; i < 2; i++)
{
dbContext.Planners.Add(CreatePlanner(seed.Get + 100 + i, status: 0, isDeleted: false));
}
// 创建已删除的规划师
for (int i = 0; i < 2; i++)
{
dbContext.Planners.Add(CreatePlanner(seed.Get + 200 + i, status: 1, isDeleted: true));
}
dbContext.SaveChanges();
var service = new PlannerService(dbContext, _mockLogger.Object);
// Act: 调用服务方法
var result = service.GetListAsync().GetAwaiter().GetResult();
// Assert: 验证返回结果只包含Status=1且未删除的记录
// 1. 返回的记录数应该等于启用且未删除的记录数
if (result.Count != 3) return false;
// 2. 验证数据库中确实存在被过滤掉的记录
var allPlanners = dbContext.Planners.ToList();
var disabledPlanners = allPlanners.Where(p => p.Status == 0 && !p.IsDeleted).ToList();
var deletedPlanners = allPlanners.Where(p => p.IsDeleted).ToList();
if (disabledPlanners.Count != 2 || deletedPlanners.Count != 2) return false;
// 3. 验证返回的记录中不包含禁用或已删除的记录
var returnedIds = result.Select(p => p.Id).ToHashSet();
var disabledIds = disabledPlanners.Select(p => p.Id).ToHashSet();
var deletedIds = deletedPlanners.Select(p => p.Id).ToHashSet();
return !returnedIds.Intersect(disabledIds).Any() && !returnedIds.Intersect(deletedIds).Any();
}
///
/// Property 1: 规划师列表不返回已删除的记录
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 10.1**
///
[Property(MaxTest = 100)]
public bool PlannerListExcludesDeletedRecords(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
// 创建一个启用的规划师
var activePlanner = CreatePlanner(seed.Get, status: 1, isDeleted: false);
dbContext.Planners.Add(activePlanner);
// 创建已删除的规划师(即使Status=1)
var deletedPlanner = CreatePlanner(seed.Get + 1, status: 1, isDeleted: true);
dbContext.Planners.Add(deletedPlanner);
dbContext.SaveChanges();
var service = new PlannerService(dbContext, _mockLogger.Object);
// Act
var result = service.GetListAsync().GetAwaiter().GetResult();
// Assert: 已删除的记录不应出现在结果中
return result.Count == 1 && result[0].Id == activePlanner.Id;
}
///
/// Property 1: 规划师列表不返回禁用状态的记录
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 10.1**
///
[Property(MaxTest = 100)]
public bool PlannerListExcludesDisabledRecords(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
// 创建一个启用的规划师
var enabledPlanner = CreatePlanner(seed.Get, status: 1, isDeleted: false);
dbContext.Planners.Add(enabledPlanner);
// 创建禁用的规划师
var disabledPlanner = CreatePlanner(seed.Get + 1, status: 0, isDeleted: false);
dbContext.Planners.Add(disabledPlanner);
dbContext.SaveChanges();
var service = new PlannerService(dbContext, _mockLogger.Object);
// Act
var result = service.GetListAsync().GetAwaiter().GetResult();
// Assert: 禁用的记录不应出现在结果中
return result.Count == 1 && result[0].Id == enabledPlanner.Id;
}
#endregion
#region Property 2: 列表查询排序正确性
///
/// Property 2: 规划师列表按Sort字段降序排列
/// *For any* Planner list query, the returned items SHALL be ordered by Sort
/// field in descending order.
///
/// **Feature: miniapp-api, Property 2: 列表查询排序正确性**
/// **Validates: Requirements 10.1**
///
[Property(MaxTest = 100)]
public bool PlannerListSortedBySortDescending(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
var random = new Random(seed.Get);
// 创建具有不同Sort值的规划师
var sortValues = new List();
for (int i = 0; i < 5; i++)
{
var sortValue = random.Next(1, 1000);
sortValues.Add(sortValue);
dbContext.Planners.Add(CreatePlannerWithSort(seed.Get + i, sortValue));
}
dbContext.SaveChanges();
var service = new PlannerService(dbContext, _mockLogger.Object);
// Act
var result = service.GetListAsync().GetAwaiter().GetResult();
// Assert: 验证列表按Sort降序排列
if (result.Count < 2) return true;
for (int i = 0; i < result.Count - 1; i++)
{
// 获取当前和下一个规划师的Sort值
var currentPlanner = dbContext.Planners.First(p => p.Id == result[i].Id);
var nextPlanner = dbContext.Planners.First(p => p.Id == result[i + 1].Id);
if (currentPlanner.Sort < nextPlanner.Sort)
{
return false;
}
}
return true;
}
///
/// Property 2: 规划师列表排序稳定性
///
/// **Feature: miniapp-api, Property 2: 列表查询排序正确性**
/// **Validates: Requirements 10.1**
///
[Property(MaxTest = 50)]
public bool PlannerListSortingIsStable(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
// 创建具有相同Sort值的规划师
var sameSort = seed.Get % 100;
for (int i = 0; i < 3; i++)
{
var planner = CreatePlannerWithSort(seed.Get + i, sameSort);
dbContext.Planners.Add(planner);
}
dbContext.SaveChanges();
var service = new PlannerService(dbContext, _mockLogger.Object);
// Act: 多次调用应该返回相同的顺序
var result1 = service.GetListAsync().GetAwaiter().GetResult();
var result2 = service.GetListAsync().GetAwaiter().GetResult();
// Assert: 两次调用返回的顺序应该相同
if (result1.Count != result2.Count) return false;
for (int i = 0; i < result1.Count; i++)
{
if (result1[i].Id != result2[i].Id) return false;
}
return true;
}
///
/// Property 2: 规划师列表排序正确性 - 验证最大Sort值在最前
///
/// **Feature: miniapp-api, Property 2: 列表查询排序正确性**
/// **Validates: Requirements 10.1**
///
[Property(MaxTest = 100)]
public bool PlannerListFirstItemHasHighestSort(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
var random = new Random(seed.Get);
// 创建具有不同Sort值的规划师
var planners = new List();
for (int i = 0; i < 5; i++)
{
var sortValue = random.Next(1, 1000);
var planner = CreatePlannerWithSort(seed.Get + i, sortValue);
planners.Add(planner);
dbContext.Planners.Add(planner);
}
dbContext.SaveChanges();
var service = new PlannerService(dbContext, _mockLogger.Object);
// Act
var result = service.GetListAsync().GetAwaiter().GetResult();
// Assert: 第一个元素应该是Sort值最大的
if (result.Count == 0) return true;
var maxSort = planners.Max(p => p.Sort);
var firstPlanner = dbContext.Planners.First(p => p.Id == result[0].Id);
return firstPlanner.Sort == maxSort;
}
#endregion
#region 综合属性测试
///
/// Property 1: 空数据库返回空列表
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 10.1**
///
[Fact]
public void EmptyDatabaseReturnsEmptyList()
{
// Arrange
using var dbContext = CreateDbContext();
var service = new PlannerService(dbContext, _mockLogger.Object);
// Act
var planners = service.GetListAsync().GetAwaiter().GetResult();
// Assert
Assert.Empty(planners);
}
///
/// Property 1: 所有记录都被过滤时返回空列表
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 10.1**
///
[Property(MaxTest = 50)]
public bool AllFilteredRecordsReturnsEmptyList(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
// 只创建不符合条件的记录
// 禁用的规划师
dbContext.Planners.Add(CreatePlanner(seed.Get, status: 0, isDeleted: false));
// 已删除的规划师
dbContext.Planners.Add(CreatePlanner(seed.Get + 1, status: 1, isDeleted: true));
// 禁用且已删除的规划师
dbContext.Planners.Add(CreatePlanner(seed.Get + 2, status: 0, isDeleted: true));
dbContext.SaveChanges();
var service = new PlannerService(dbContext, _mockLogger.Object);
// Act
var planners = service.GetListAsync().GetAwaiter().GetResult();
// Assert: 列表应该为空
return planners.Count == 0;
}
///
/// Property 1 & 2: 过滤和排序同时正确
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性, Property 2: 列表查询排序正确性**
/// **Validates: Requirements 10.1**
///
[Property(MaxTest = 100)]
public bool FilteringAndSortingWorkTogether(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
var random = new Random(seed.Get);
// 创建启用的规划师(不同Sort值)
var enabledPlanners = new List();
for (int i = 0; i < 3; i++)
{
var sortValue = random.Next(1, 1000);
var planner = CreatePlannerWithSort(seed.Get + i, sortValue);
enabledPlanners.Add(planner);
dbContext.Planners.Add(planner);
}
// 创建禁用的规划师(高Sort值,不应出现在结果中)
var disabledPlanner = CreatePlanner(seed.Get + 100, status: 0, isDeleted: false);
disabledPlanner.Sort = 9999; // 最高Sort值
dbContext.Planners.Add(disabledPlanner);
// 创建已删除的规划师(高Sort值,不应出现在结果中)
var deletedPlanner = CreatePlanner(seed.Get + 200, status: 1, isDeleted: true);
deletedPlanner.Sort = 9998; // 第二高Sort值
dbContext.Planners.Add(deletedPlanner);
dbContext.SaveChanges();
var service = new PlannerService(dbContext, _mockLogger.Object);
// Act
var result = service.GetListAsync().GetAwaiter().GetResult();
// Assert:
// 1. 只返回启用且未删除的记录
if (result.Count != 3) return false;
// 2. 不包含禁用或已删除的记录
var returnedIds = result.Select(p => p.Id).ToHashSet();
if (returnedIds.Contains(disabledPlanner.Id) || returnedIds.Contains(deletedPlanner.Id))
{
return false;
}
// 3. 按Sort降序排列
for (int i = 0; i < result.Count - 1; i++)
{
var currentPlanner = dbContext.Planners.First(p => p.Id == result[i].Id);
var nextPlanner = dbContext.Planners.First(p => p.Id == result[i + 1].Id);
if (currentPlanner.Sort < nextPlanner.Sort)
{
return false;
}
}
return true;
}
#endregion
#region 辅助方法
///
/// 创建内存数据库上下文
///
private MiAssessmentDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new MiAssessmentDbContext(options);
}
///
/// 创建测试规划师
///
private Planner CreatePlanner(int seed, int status, bool isDeleted)
{
return new Planner
{
Name = $"Test Planner {seed}",
Avatar = $"https://example.com/avatar_{seed}.jpg",
Introduction = $"Introduction for planner {seed}",
Price = 100.00m + (seed % 100),
Sort = seed % 100,
Status = status,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now,
IsDeleted = isDeleted
};
}
///
/// 创建指定Sort值的测试规划师
///
private Planner CreatePlannerWithSort(int seed, int sort)
{
return new Planner
{
Name = $"Test Planner {seed}",
Avatar = $"https://example.com/avatar_{seed}.jpg",
Introduction = $"Introduction for planner {seed}",
Price = 100.00m + (seed % 100),
Sort = sort,
Status = 1,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now,
IsDeleted = false
};
}
#endregion
}