mi-assessment/server/MiAssessment/tests/MiAssessment.Tests/Services/PlannerServicePropertyTests.cs
2026-02-09 14:45:06 +08:00

451 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// PlannerService 属性测试
/// 验证规划师服务的列表查询过滤和排序正确性
/// </summary>
public class PlannerServicePropertyTests
{
private readonly Mock<ILogger<PlannerService>> _mockLogger = new();
#region Property 1:
/// <summary>
/// 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**
/// </summary>
[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();
}
/// <summary>
/// Property 1: 规划师列表不返回已删除的记录
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 10.1**
/// </summary>
[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;
}
/// <summary>
/// Property 1: 规划师列表不返回禁用状态的记录
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 10.1**
/// </summary>
[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:
/// <summary>
/// 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**
/// </summary>
[Property(MaxTest = 100)]
public bool PlannerListSortedBySortDescending(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
var random = new Random(seed.Get);
// 创建具有不同Sort值的规划师
var sortValues = new List<int>();
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;
}
/// <summary>
/// Property 2: 规划师列表排序稳定性
///
/// **Feature: miniapp-api, Property 2: 列表查询排序正确性**
/// **Validates: Requirements 10.1**
/// </summary>
[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;
}
/// <summary>
/// Property 2: 规划师列表排序正确性 - 验证最大Sort值在最前
///
/// **Feature: miniapp-api, Property 2: 列表查询排序正确性**
/// **Validates: Requirements 10.1**
/// </summary>
[Property(MaxTest = 100)]
public bool PlannerListFirstItemHasHighestSort(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
var random = new Random(seed.Get);
// 创建具有不同Sort值的规划师
var planners = new List<Planner>();
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
/// <summary>
/// Property 1: 空数据库返回空列表
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 10.1**
/// </summary>
[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);
}
/// <summary>
/// Property 1: 所有记录都被过滤时返回空列表
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性**
/// **Validates: Requirements 10.1**
/// </summary>
[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;
}
/// <summary>
/// Property 1 & 2: 过滤和排序同时正确
///
/// **Feature: miniapp-api, Property 1: 列表查询过滤正确性, Property 2: 列表查询排序正确性**
/// **Validates: Requirements 10.1**
/// </summary>
[Property(MaxTest = 100)]
public bool FilteringAndSortingWorkTogether(PositiveInt seed)
{
// Arrange
using var dbContext = CreateDbContext();
var random = new Random(seed.Get);
// 创建启用的规划师不同Sort值
var enabledPlanners = new List<Planner>();
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
/// <summary>
/// 创建内存数据库上下文
/// </summary>
private MiAssessmentDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<MiAssessmentDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new MiAssessmentDbContext(options);
}
/// <summary>
/// 创建测试规划师
/// </summary>
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
};
}
/// <summary>
/// 创建指定Sort值的测试规划师
/// </summary>
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
}