- 系统配置管理模块 (Config) - 内容管理模块 (Banner, Promotion) - 测评管理模块 (Type, Question, Category, Mapping, Conclusion) - 用户管理模块 (User) - 订单管理模块 (Order) - 规划师管理模块 (Planner) - 分销管理模块 (InviteCode, Commission, Withdrawal) - 数据统计仪表盘模块 (Dashboard) - 权限控制集成 - 服务注册配置 全部381个测试通过
201 lines
7.4 KiB
C#
201 lines
7.4 KiB
C#
using FsCheck;
|
|
using FsCheck.Xunit;
|
|
using MiAssessment.Admin.Business.Data;
|
|
using MiAssessment.Admin.Business.Models.Distribution;
|
|
using MiAssessment.Admin.Business.Services;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace MiAssessment.Tests.Services;
|
|
|
|
/// <summary>
|
|
/// DistributionService 属性测试
|
|
/// </summary>
|
|
public class DistributionServicePropertyTests
|
|
{
|
|
private readonly Mock<ILogger<DistributionService>> _mockLogger = new();
|
|
|
|
#region Property 13: Invite Code Generation Uniqueness
|
|
|
|
/// <summary>
|
|
/// **Feature: admin-business-modules, Property 13: Invite Code Generation Uniqueness**
|
|
/// For any batch of generated invite codes, all codes SHALL be unique 5-character uppercase letter strings.
|
|
/// **Validates: Requirements 14.3, 14.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool GenerateInviteCodes_AllCodesShouldBeUnique(PositiveInt seed)
|
|
{
|
|
var count = (seed.Get % 50) + 1; // 1 to 50 codes
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new DistributionService(dbContext, _mockLogger.Object);
|
|
|
|
var result = service.GenerateInviteCodesAsync(count).GetAwaiter().GetResult();
|
|
|
|
// All codes should be unique
|
|
var uniqueCodes = result.Codes.Distinct().Count();
|
|
return uniqueCodes == result.Codes.Count && result.Codes.Count == count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: admin-business-modules, Property 13: Invite Code Generation Uniqueness**
|
|
/// For any batch of generated invite codes, all codes SHALL be 5-character uppercase letter strings.
|
|
/// **Validates: Requirements 14.3, 14.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool GenerateInviteCodes_AllCodesShouldBe5CharUppercaseLetters(PositiveInt seed)
|
|
{
|
|
var count = (seed.Get % 30) + 1; // 1 to 30 codes
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new DistributionService(dbContext, _mockLogger.Object);
|
|
|
|
var result = service.GenerateInviteCodesAsync(count).GetAwaiter().GetResult();
|
|
|
|
// All codes should be 5 characters and uppercase letters only
|
|
return result.Codes.All(code =>
|
|
code.Length == 5 &&
|
|
code.All(c => c >= 'A' && c <= 'Z'));
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: admin-business-modules, Property 13: Invite Code Generation Uniqueness**
|
|
/// For any batch of generated invite codes, all codes in the same batch SHALL share the same BatchNo.
|
|
/// **Validates: Requirements 14.3, 14.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool GenerateInviteCodes_AllCodesShouldShareSameBatchNo(PositiveInt seed)
|
|
{
|
|
var count = (seed.Get % 20) + 1; // 1 to 20 codes
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new DistributionService(dbContext, _mockLogger.Object);
|
|
|
|
var result = service.GenerateInviteCodesAsync(count).GetAwaiter().GetResult();
|
|
|
|
// Verify BatchNo is not empty
|
|
if (string.IsNullOrEmpty(result.BatchNo))
|
|
return false;
|
|
|
|
// Verify all codes in database have the same BatchNo
|
|
var codesInDb = dbContext.InviteCodes
|
|
.Where(c => result.Codes.Contains(c.Code))
|
|
.ToList();
|
|
|
|
return codesInDb.Count == count &&
|
|
codesInDb.All(c => c.BatchNo == result.BatchNo);
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: admin-business-modules, Property 13: Invite Code Generation Uniqueness**
|
|
/// Multiple batches of generated invite codes should not have duplicate codes across batches.
|
|
/// **Validates: Requirements 14.3, 14.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 50)]
|
|
public bool GenerateInviteCodes_MultipleBatchesShouldNotHaveDuplicates(PositiveInt seed)
|
|
{
|
|
var batch1Count = (seed.Get % 10) + 5; // 5 to 14 codes
|
|
var batch2Count = (seed.Get % 10) + 5; // 5 to 14 codes
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new DistributionService(dbContext, _mockLogger.Object);
|
|
|
|
// Generate first batch
|
|
var result1 = service.GenerateInviteCodesAsync(batch1Count).GetAwaiter().GetResult();
|
|
|
|
// Generate second batch
|
|
var result2 = service.GenerateInviteCodesAsync(batch2Count).GetAwaiter().GetResult();
|
|
|
|
// Verify no duplicates between batches
|
|
var allCodes = result1.Codes.Concat(result2.Codes).ToList();
|
|
var uniqueCodes = allCodes.Distinct().Count();
|
|
|
|
return uniqueCodes == allCodes.Count &&
|
|
result1.BatchNo != result2.BatchNo;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: admin-business-modules, Property 13: Invite Code Generation Uniqueness**
|
|
/// Generated invite codes should be persisted to the database with correct initial status.
|
|
/// **Validates: Requirements 14.3, 14.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 50)]
|
|
public bool GenerateInviteCodes_ShouldPersistWithCorrectStatus(PositiveInt seed)
|
|
{
|
|
var count = (seed.Get % 15) + 1; // 1 to 15 codes
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new DistributionService(dbContext, _mockLogger.Object);
|
|
|
|
var result = service.GenerateInviteCodesAsync(count).GetAwaiter().GetResult();
|
|
|
|
// Verify all codes are persisted with status = 1 (未分配)
|
|
var codesInDb = dbContext.InviteCodes
|
|
.Where(c => result.Codes.Contains(c.Code))
|
|
.ToList();
|
|
|
|
return codesInDb.Count == count &&
|
|
codesInDb.All(c => c.Status == 1 && !c.IsDeleted);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Additional Uniqueness Tests
|
|
|
|
/// <summary>
|
|
/// **Feature: admin-business-modules, Property 13: Invite Code Generation Uniqueness**
|
|
/// When generating codes with existing codes in database, new codes should not duplicate existing ones.
|
|
/// **Validates: Requirements 14.3, 14.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 50)]
|
|
public bool GenerateInviteCodes_ShouldNotDuplicateExistingCodesInDb(PositiveInt seed)
|
|
{
|
|
var existingCount = (seed.Get % 5) + 1; // 1 to 5 existing codes
|
|
var newCount = (seed.Get % 10) + 1; // 1 to 10 new codes
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new DistributionService(dbContext, _mockLogger.Object);
|
|
|
|
// First, generate some existing codes
|
|
var existingResult = service.GenerateInviteCodesAsync(existingCount).GetAwaiter().GetResult();
|
|
|
|
// Then generate new codes
|
|
var newResult = service.GenerateInviteCodesAsync(newCount).GetAwaiter().GetResult();
|
|
|
|
// Verify no overlap between batches
|
|
var overlap = existingResult.Codes.Intersect(newResult.Codes).Count();
|
|
|
|
return overlap == 0 &&
|
|
existingResult.Codes.Count == existingCount &&
|
|
newResult.Codes.Count == newCount;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
private AdminBusinessDbContext CreateDbContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<AdminBusinessDbContext>()
|
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
|
.Options;
|
|
|
|
return new AdminBusinessDbContext(options);
|
|
}
|
|
|
|
private static string GenerateRandomCode(Random random)
|
|
{
|
|
const string charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
var chars = new char[5];
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
chars[i] = charset[random.Next(charset.Length)];
|
|
}
|
|
return new string(chars);
|
|
}
|
|
|
|
#endregion
|
|
}
|