737 lines
27 KiB
C#
737 lines
27 KiB
C#
using FsCheck;
|
|
using FsCheck.Xunit;
|
|
using HoneyBox.Admin.Business.Models;
|
|
using HoneyBox.Admin.Business.Models.User;
|
|
using HoneyBox.Admin.Business.Services;
|
|
using HoneyBox.Model.Data;
|
|
using HoneyBox.Model.Entities;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
|
using Microsoft.Extensions.Logging;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace HoneyBox.Tests.Services;
|
|
|
|
/// <summary>
|
|
/// 用户管理前端模块属性测试
|
|
/// Feature: user-management-frontend
|
|
/// </summary>
|
|
public class UserManagementFrontendPropertyTests
|
|
{
|
|
private readonly Mock<ILogger<UserBusinessService>> _mockLogger = new();
|
|
|
|
#region Property 1: 搜索参数正确传递
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 1: 搜索参数正确传递**
|
|
/// For any user list search request, when the admin inputs search conditions,
|
|
/// the API call query parameters should exactly match the user input search conditions.
|
|
/// **Validates: Requirements 1.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool SearchParameters_ShouldFilterCorrectly_ByNickname(PositiveInt seed)
|
|
{
|
|
var nicknames = new[] { "Alice", "Bob", "Charlie", "David", "Eve" };
|
|
var searchNickname = nicknames[seed.Get % nicknames.Length];
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create users with different nicknames
|
|
foreach (var name in nicknames)
|
|
{
|
|
dbContext.Users.Add(CreateTestUser(name));
|
|
dbContext.Users.Add(CreateTestUser($"{name}Smith"));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new UserListRequest { Nickname = searchNickname };
|
|
var result = service.GetUserListAsync(request).GetAwaiter().GetResult();
|
|
|
|
// All returned users should contain the search nickname
|
|
return result.List.All(u => u.Nickname != null && u.Nickname.Contains(searchNickname));
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 1: 搜索参数正确传递**
|
|
/// For any user list search request with mobile filter,
|
|
/// all returned users should have matching mobile numbers.
|
|
/// **Validates: Requirements 1.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool SearchParameters_ShouldFilterCorrectly_ByMobile(PositiveInt seed)
|
|
{
|
|
var mobilePrefix = $"138{(seed.Get % 100):D2}";
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create users with different mobile numbers
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
var user = CreateTestUser($"User{i}");
|
|
user.Mobile = i < 5 ? $"{mobilePrefix}{i:D6}" : $"139{i:D8}";
|
|
dbContext.Users.Add(user);
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new UserListRequest { Mobile = mobilePrefix };
|
|
var result = service.GetUserListAsync(request).GetAwaiter().GetResult();
|
|
|
|
// All returned users should have mobile containing the prefix
|
|
return result.List.All(u => u.Mobile != null && u.Mobile.Contains(mobilePrefix));
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 1: 搜索参数正确传递**
|
|
/// For any user list search request with parent ID filter,
|
|
/// all returned users should have the specified parent ID.
|
|
/// **Validates: Requirements 1.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool SearchParameters_ShouldFilterCorrectly_ByParentId(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create parent user
|
|
var parent = CreateTestUser("Parent");
|
|
dbContext.Users.Add(parent);
|
|
dbContext.SaveChanges();
|
|
|
|
// Create subordinate users
|
|
var subordinateCount = (seed.Get % 5) + 1;
|
|
for (int i = 0; i < subordinateCount; i++)
|
|
{
|
|
var child = CreateTestUser($"Child{i}");
|
|
child.Pid = parent.Id;
|
|
dbContext.Users.Add(child);
|
|
}
|
|
|
|
// Create other users without parent
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
dbContext.Users.Add(CreateTestUser($"Other{i}"));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new UserListRequest { ParentId = parent.Id };
|
|
var result = service.GetUserListAsync(request).GetAwaiter().GetResult();
|
|
|
|
// All returned users should have the specified parent ID
|
|
return result.Total == subordinateCount &&
|
|
result.List.All(u => u.ParentId == parent.Id);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Property 2: 分页参数正确传递
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 2: 分页参数正确传递**
|
|
/// For any pagination request, the returned list should have at most pageSize items,
|
|
/// and the page and pageSize in response should match the request.
|
|
/// **Validates: Requirements 1.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool PaginationParameters_ShouldReturnCorrectPageSize(PositiveInt seed)
|
|
{
|
|
var userCount = (seed.Get % 30) + 10; // 10 to 39 users
|
|
var pageSize = (seed.Get % 10) + 1; // 1 to 10 per page
|
|
var page = (seed.Get % 5) + 1; // page 1 to 5
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test users
|
|
for (int i = 0; i < userCount; i++)
|
|
{
|
|
dbContext.Users.Add(CreateTestUser($"User{i}"));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new UserListRequest { Page = page, PageSize = pageSize };
|
|
var result = service.GetUserListAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Verify pagination parameters are correctly passed
|
|
return result.Total == userCount &&
|
|
result.List.Count <= pageSize &&
|
|
result.Page == page &&
|
|
result.PageSize == pageSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 2: 分页参数正确传递**
|
|
/// The total count should remain consistent regardless of which page is requested.
|
|
/// **Validates: Requirements 1.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool PaginationParameters_TotalShouldBeConsistentAcrossPages(PositiveInt seed)
|
|
{
|
|
var userCount = (seed.Get % 20) + 15; // 15 to 34 users
|
|
var pageSize = 5;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test users
|
|
for (int i = 0; i < userCount; i++)
|
|
{
|
|
dbContext.Users.Add(CreateTestUser($"User{i}"));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
// Get multiple pages
|
|
var page1 = service.GetUserListAsync(new UserListRequest { Page = 1, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
var page2 = service.GetUserListAsync(new UserListRequest { Page = 2, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
var page3 = service.GetUserListAsync(new UserListRequest { Page = 3, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
|
|
// Total should be consistent across all pages
|
|
return page1.Total == page2.Total &&
|
|
page2.Total == page3.Total &&
|
|
page1.Total == userCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 2: 分页参数正确传递**
|
|
/// Different pages should return different users (no overlap).
|
|
/// **Validates: Requirements 1.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool PaginationParameters_DifferentPagesShouldNotOverlap(PositiveInt seed)
|
|
{
|
|
var userCount = (seed.Get % 15) + 20; // 20 to 34 users
|
|
var pageSize = 5;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test users
|
|
for (int i = 0; i < userCount; i++)
|
|
{
|
|
dbContext.Users.Add(CreateTestUser($"User{i}"));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
// Get first two pages
|
|
var page1 = service.GetUserListAsync(new UserListRequest { Page = 1, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
var page2 = service.GetUserListAsync(new UserListRequest { Page = 2, PageSize = pageSize }).GetAwaiter().GetResult();
|
|
|
|
// User IDs should not overlap between pages
|
|
var page1Ids = page1.List.Select(u => u.Id).ToHashSet();
|
|
var page2Ids = page2.List.Select(u => u.Id).ToHashSet();
|
|
|
|
return !page1Ids.Overlaps(page2Ids);
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region Property 3: 资金变动参数验证
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 3: 资金变动参数验证**
|
|
/// When operation type is "subtract" and amount is greater than user's current balance,
|
|
/// the system should throw an exception instead of executing the subtraction.
|
|
/// **Validates: Requirements 2.3**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool MoneyChangeValidation_ShouldRejectInsufficientBalance(PositiveInt seed)
|
|
{
|
|
var initialBalance = (seed.Get % 100) + 10; // 10 to 109
|
|
var subtractAmount = initialBalance + (seed.Get % 50) + 1; // Always more than balance
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test user with limited balance
|
|
var user = CreateTestUser("TestUser");
|
|
user.Money = initialBalance;
|
|
dbContext.Users.Add(user);
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new UserMoneyChangeRequest
|
|
{
|
|
Type = MoneyChangeType.Balance,
|
|
Amount = subtractAmount,
|
|
Operation = OperationType.Subtract,
|
|
Remark = "Property test - insufficient balance"
|
|
};
|
|
|
|
try
|
|
{
|
|
service.ChangeUserMoneyAsync(user.Id, request, 1).GetAwaiter().GetResult();
|
|
return false; // Should have thrown exception
|
|
}
|
|
catch (BusinessException ex)
|
|
{
|
|
// Verify user balance was not changed
|
|
var updatedUser = dbContext.Users.Find(user.Id);
|
|
return ex.Message.Contains("余额不足") && updatedUser!.Money == initialBalance;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 3: 资金变动参数验证**
|
|
/// When operation type is "add", the balance should increase by exactly the specified amount.
|
|
/// **Validates: Requirements 2.3**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool MoneyChangeValidation_AddShouldIncreaseBalanceExactly(PositiveInt seed)
|
|
{
|
|
var initialBalance = (seed.Get % 500) + 100; // 100 to 599
|
|
var addAmount = (seed.Get % 100) + 1; // 1 to 100
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test user
|
|
var user = CreateTestUser("TestUser");
|
|
user.Money = initialBalance;
|
|
dbContext.Users.Add(user);
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new UserMoneyChangeRequest
|
|
{
|
|
Type = MoneyChangeType.Balance,
|
|
Amount = addAmount,
|
|
Operation = OperationType.Add,
|
|
Remark = "Property test - add balance"
|
|
};
|
|
|
|
service.ChangeUserMoneyAsync(user.Id, request, 1).GetAwaiter().GetResult();
|
|
|
|
// Refresh user from database
|
|
var updatedUser = dbContext.Users.Find(user.Id);
|
|
return updatedUser!.Money == initialBalance + addAmount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 3: 资金变动参数验证**
|
|
/// When operation type is "subtract" and amount is less than or equal to balance,
|
|
/// the balance should decrease by exactly the specified amount.
|
|
/// **Validates: Requirements 2.3**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool MoneyChangeValidation_SubtractShouldDecreaseBalanceExactly(PositiveInt seed)
|
|
{
|
|
var initialBalance = (seed.Get % 500) + 200; // 200 to 699
|
|
var subtractAmount = (seed.Get % 100) + 1; // 1 to 100 (always less than balance)
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test user
|
|
var user = CreateTestUser("TestUser");
|
|
user.Money = initialBalance;
|
|
dbContext.Users.Add(user);
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new UserMoneyChangeRequest
|
|
{
|
|
Type = MoneyChangeType.Balance,
|
|
Amount = subtractAmount,
|
|
Operation = OperationType.Subtract,
|
|
Remark = "Property test - subtract balance"
|
|
};
|
|
|
|
service.ChangeUserMoneyAsync(user.Id, request, 1).GetAwaiter().GetResult();
|
|
|
|
// Refresh user from database
|
|
var updatedUser = dbContext.Users.Find(user.Id);
|
|
return updatedUser!.Money == initialBalance - subtractAmount;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Property 4: 用户状态切换一致性
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 4: 用户状态切换一致性**
|
|
/// Ban operation should set status to 0, unban operation should set status to 1,
|
|
/// and the user list should display the correct status after the operation.
|
|
/// **Validates: Requirements 3.1, 3.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool UserStatusToggle_BanShouldSetStatusToZero(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test user with status 1 (active)
|
|
var user = CreateTestUser("TestUser");
|
|
user.Status = 1;
|
|
dbContext.Users.Add(user);
|
|
dbContext.SaveChanges();
|
|
|
|
// Ban the user (set status to 0)
|
|
var result = service.SetUserStatusAsync(user.Id, 0).GetAwaiter().GetResult();
|
|
if (!result) return false;
|
|
|
|
// Verify status was set to 0
|
|
var updatedUser = dbContext.Users.Find(user.Id);
|
|
return updatedUser!.Status == 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 4: 用户状态切换一致性**
|
|
/// Unban operation should set status to 1.
|
|
/// **Validates: Requirements 3.1, 3.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool UserStatusToggle_UnbanShouldSetStatusToOne(PositiveInt seed)
|
|
{
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test user with status 0 (banned)
|
|
var user = CreateTestUser("TestUser");
|
|
user.Status = 0;
|
|
dbContext.Users.Add(user);
|
|
dbContext.SaveChanges();
|
|
|
|
// Unban the user (set status to 1)
|
|
var result = service.SetUserStatusAsync(user.Id, 1).GetAwaiter().GetResult();
|
|
if (!result) return false;
|
|
|
|
// Verify status was set to 1
|
|
var updatedUser = dbContext.Users.Find(user.Id);
|
|
return updatedUser!.Status == 1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 4: 用户状态切换一致性**
|
|
/// Status toggle should be idempotent - setting the same status multiple times
|
|
/// should result in the same final state.
|
|
/// **Validates: Requirements 3.1, 3.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool UserStatusToggle_ShouldBeIdempotent(PositiveInt seed)
|
|
{
|
|
var targetStatus = seed.Get % 2; // 0 or 1
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test user
|
|
var user = CreateTestUser("TestUser");
|
|
user.Status = (byte)(1 - targetStatus); // Start with opposite status
|
|
dbContext.Users.Add(user);
|
|
dbContext.SaveChanges();
|
|
|
|
// Set status multiple times
|
|
service.SetUserStatusAsync(user.Id, targetStatus).GetAwaiter().GetResult();
|
|
service.SetUserStatusAsync(user.Id, targetStatus).GetAwaiter().GetResult();
|
|
service.SetUserStatusAsync(user.Id, targetStatus).GetAwaiter().GetResult();
|
|
|
|
// Verify status is correct
|
|
var updatedUser = dbContext.Users.Find(user.Id);
|
|
return updatedUser!.Status == targetStatus;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 4: 用户状态切换一致性**
|
|
/// User list should reflect the correct status after status change.
|
|
/// **Validates: Requirements 3.1, 3.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool UserStatusToggle_ListShouldReflectCorrectStatus(PositiveInt seed)
|
|
{
|
|
var targetStatus = seed.Get % 2; // 0 or 1
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test user
|
|
var user = CreateTestUser("TestUser");
|
|
user.Status = (byte)(1 - targetStatus);
|
|
dbContext.Users.Add(user);
|
|
dbContext.SaveChanges();
|
|
|
|
// Change status
|
|
service.SetUserStatusAsync(user.Id, targetStatus).GetAwaiter().GetResult();
|
|
|
|
// Get user list and verify status
|
|
var request = new UserListRequest { UserId = user.Id };
|
|
var result = service.GetUserListAsync(request).GetAwaiter().GetResult();
|
|
|
|
return result.List.Count == 1 && result.List[0].Status == targetStatus;
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region Property 5: 盈亏计算正确性
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 5: 盈亏计算正确性**
|
|
/// Profit/loss amount should equal: User payment - Shipping amount - Backpack amount - Remaining DaDa coupons,
|
|
/// and profit status should correctly display based on whether the amount is positive or negative.
|
|
/// **Validates: Requirements 6.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool ProfitLossCalculation_ShouldBeCorrect(PositiveInt seed)
|
|
{
|
|
// Generate test data
|
|
var useMoney = (seed.Get % 1000) + 100m; // 100 to 1099
|
|
var fhMoney = (seed.Get % 500) + 50m; // 50 to 549
|
|
var bbMoney = (seed.Get % 300) + 20m; // 20 to 319
|
|
var syMoney = (seed.Get % 100) + 10m; // 10 to 109
|
|
|
|
// Calculate expected profit/loss
|
|
var expectedYueMoney = useMoney - fhMoney - bbMoney - syMoney;
|
|
var expectedStatus = expectedYueMoney >= 0 ? "盈利" : "亏损";
|
|
|
|
// Create a ProfitLossItem and verify calculation
|
|
var item = new ProfitLossItem
|
|
{
|
|
UserId = 1,
|
|
Uid = "TEST001",
|
|
Nickname = "TestUser",
|
|
UseMoney = useMoney,
|
|
FhMoney = fhMoney,
|
|
BbMoney = bbMoney,
|
|
SyMoney = syMoney,
|
|
YueMoney = expectedYueMoney,
|
|
ProfitStatus = expectedStatus
|
|
};
|
|
|
|
// Verify the calculation formula
|
|
var calculatedYueMoney = item.UseMoney - item.FhMoney - item.BbMoney - item.SyMoney;
|
|
var calculatedStatus = calculatedYueMoney >= 0 ? "盈利" : "亏损";
|
|
|
|
return item.YueMoney == calculatedYueMoney && item.ProfitStatus == calculatedStatus;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 5: 盈亏计算正确性**
|
|
/// When user payment equals total deductions, profit/loss should be zero and status should be "盈利".
|
|
/// **Validates: Requirements 6.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool ProfitLossCalculation_ZeroShouldBeProfit(PositiveInt seed)
|
|
{
|
|
var useMoney = (seed.Get % 1000) + 100m;
|
|
var fhMoney = useMoney / 3;
|
|
var bbMoney = useMoney / 3;
|
|
var syMoney = useMoney - fhMoney - bbMoney; // Make total equal to useMoney
|
|
|
|
var yueMoney = useMoney - fhMoney - bbMoney - syMoney;
|
|
var status = yueMoney >= 0 ? "盈利" : "亏损";
|
|
|
|
// Zero or positive should be "盈利"
|
|
return yueMoney == 0 && status == "盈利";
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 5: 盈亏计算正确性**
|
|
/// When deductions exceed payment, profit/loss should be negative and status should be "亏损".
|
|
/// **Validates: Requirements 6.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool ProfitLossCalculation_NegativeShouldBeLoss(PositiveInt seed)
|
|
{
|
|
var useMoney = (seed.Get % 100) + 50m; // 50 to 149
|
|
var fhMoney = useMoney + (seed.Get % 50) + 10m; // Always more than useMoney
|
|
var bbMoney = 0m;
|
|
var syMoney = 0m;
|
|
|
|
var yueMoney = useMoney - fhMoney - bbMoney - syMoney;
|
|
var status = yueMoney >= 0 ? "盈利" : "亏损";
|
|
|
|
// Negative should be "亏损"
|
|
return yueMoney < 0 && status == "亏损";
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Property 6: API响应格式一致性
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 6: API响应格式一致性**
|
|
/// For any backend API response, the response format should conform to the unified ApiResponse structure.
|
|
/// **Validates: Requirements 10.1-10.10**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool ApiResponseFormat_PagedResultShouldHaveConsistentStructure(PositiveInt seed)
|
|
{
|
|
var userCount = (seed.Get % 20) + 5;
|
|
var page = (seed.Get % 3) + 1;
|
|
var pageSize = (seed.Get % 10) + 5;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test users
|
|
for (int i = 0; i < userCount; i++)
|
|
{
|
|
dbContext.Users.Add(CreateTestUser($"User{i}"));
|
|
}
|
|
dbContext.SaveChanges();
|
|
|
|
var request = new UserListRequest { Page = page, PageSize = pageSize };
|
|
var result = service.GetUserListAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Verify PagedResult structure
|
|
return result != null &&
|
|
result.List != null &&
|
|
result.Total >= 0 &&
|
|
result.Page == page &&
|
|
result.PageSize == pageSize &&
|
|
result.TotalPages == (int)Math.Ceiling((double)result.Total / result.PageSize);
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 6: API响应格式一致性**
|
|
/// User box API should return consistent PagedResult structure.
|
|
/// **Validates: Requirements 10.1**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool ApiResponseFormat_UserBoxShouldHaveConsistentStructure(PositiveInt seed)
|
|
{
|
|
var page = (seed.Get % 3) + 1;
|
|
var pageSize = (seed.Get % 10) + 5;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test user
|
|
var user = CreateTestUser("TestUser");
|
|
dbContext.Users.Add(user);
|
|
dbContext.SaveChanges();
|
|
|
|
var query = new UserBoxQuery { Page = page, PageSize = pageSize };
|
|
var result = service.GetUserBoxAsync(user.Id, query).GetAwaiter().GetResult();
|
|
|
|
// Verify PagedResult structure
|
|
return result != null &&
|
|
result.List != null &&
|
|
result.Total >= 0 &&
|
|
result.Page == page &&
|
|
result.PageSize == pageSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 6: API响应格式一致性**
|
|
/// User orders API should return consistent PagedResult structure.
|
|
/// **Validates: Requirements 10.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool ApiResponseFormat_UserOrdersShouldHaveConsistentStructure(PositiveInt seed)
|
|
{
|
|
var page = (seed.Get % 3) + 1;
|
|
var pageSize = (seed.Get % 10) + 5;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test user
|
|
var user = CreateTestUser("TestUser");
|
|
dbContext.Users.Add(user);
|
|
dbContext.SaveChanges();
|
|
|
|
var query = new UserOrderQuery { Page = page, PageSize = pageSize };
|
|
var result = service.GetUserOrdersAsync(user.Id, query).GetAwaiter().GetResult();
|
|
|
|
// Verify PagedResult structure
|
|
return result != null &&
|
|
result.List != null &&
|
|
result.Total >= 0 &&
|
|
result.Page == page &&
|
|
result.PageSize == pageSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 6: API响应格式一致性**
|
|
/// Money detail API should return consistent PagedResult structure.
|
|
/// **Validates: Requirements 10.3**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool ApiResponseFormat_MoneyDetailShouldHaveConsistentStructure(PositiveInt seed)
|
|
{
|
|
var page = (seed.Get % 3) + 1;
|
|
var pageSize = (seed.Get % 10) + 5;
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
// Create test user
|
|
var user = CreateTestUser("TestUser");
|
|
dbContext.Users.Add(user);
|
|
dbContext.SaveChanges();
|
|
|
|
var query = new MoneyDetailQuery { Page = page, PageSize = pageSize };
|
|
var result = service.GetUserMoneyDetailAsync(user.Id, query).GetAwaiter().GetResult();
|
|
|
|
// Verify PagedResult structure
|
|
return result != null &&
|
|
result.List != null &&
|
|
result.Total >= 0 &&
|
|
result.Page == page &&
|
|
result.PageSize == pageSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// **Feature: user-management-frontend, Property 6: API响应格式一致性**
|
|
/// Login stats API should return consistent LoginStatsResponse structure.
|
|
/// **Validates: Requirements 10.6**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool ApiResponseFormat_LoginStatsShouldHaveConsistentStructure(PositiveInt seed)
|
|
{
|
|
var types = new[] { "day", "week", "month" };
|
|
var type = types[seed.Get % types.Length];
|
|
|
|
using var dbContext = CreateDbContext();
|
|
var service = new UserBusinessService(dbContext, _mockLogger.Object);
|
|
|
|
var query = new LoginStatsQuery { Type = type };
|
|
var result = service.GetLoginStatsAsync(query).GetAwaiter().GetResult();
|
|
|
|
// Verify LoginStatsResponse structure
|
|
return result != null &&
|
|
result.Labels != null &&
|
|
result.Values != null &&
|
|
result.Labels.Count == result.Values.Count &&
|
|
result.TotalLogins >= 0;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
private HoneyBoxDbContext CreateDbContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<HoneyBoxDbContext>()
|
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
|
.ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning))
|
|
.Options;
|
|
|
|
return new HoneyBoxDbContext(options);
|
|
}
|
|
|
|
private User CreateTestUser(string nickname)
|
|
{
|
|
return new User
|
|
{
|
|
OpenId = Guid.NewGuid().ToString("N"),
|
|
Uid = $"UID{DateTime.Now.Ticks}{Guid.NewGuid():N}".Substring(0, 20),
|
|
Nickname = nickname,
|
|
HeadImg = "https://example.com/avatar.png",
|
|
Mobile = $"138{new Random().Next(10000000, 99999999)}",
|
|
Money = 100,
|
|
Integral = 50,
|
|
Score = 30,
|
|
Status = 1,
|
|
IsTest = 0,
|
|
Pid = 0,
|
|
Vip = 1,
|
|
CreatedAt = DateTime.Now,
|
|
UpdatedAt = DateTime.Now
|
|
};
|
|
}
|
|
|
|
#endregion
|
|
}
|