using FsCheck; using FsCheck.Xunit; using MiAssessment.Admin.Business.Services; using MiAssessment.Model.Data; using MiAssessment.Model.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Moq; using Xunit; namespace MiAssessment.Tests.Services; /// /// DashboardService 属性测试 /// public class DashboardServicePropertyTests { private readonly Mock> _mockLogger = new(); #region Property 15: Dashboard Statistics Accuracy /// /// **Feature: admin-business-migration, Property 15: Dashboard Statistics Accuracy** /// For any dashboard overview request, the today's registrations count should match /// the actual count of users registered today. /// Validates: Requirements 8.1 /// [Property(MaxTest = 100)] public bool DashboardOverview_TodayRegistrations_ShouldMatchActualCount(PositiveInt todayCount, PositiveInt yesterdayCount) { var actualTodayCount = todayCount.Get % 20; // 0-19 users today var actualYesterdayCount = yesterdayCount.Get % 20; // 0-19 users yesterday var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; using var dbContext = new MiAssessmentDbContext(options); var service = new DashboardService(dbContext, _mockLogger.Object); // Seed today's users var today = DateTime.Today; for (int i = 1; i <= actualTodayCount; i++) { dbContext.Users.Add(new User { Id = i, Uid = $"TODAY{i:D3}", Nickname = $"今日用户{i}", Mobile = $"1380013800{i}", OpenId = $"openid_today_{i}", HeadImg = $"http://test.com/head{i}.jpg", CreatedAt = today.AddHours(i % 24), UpdatedAt = DateTime.Now }); } // Seed yesterday's users for (int i = 1; i <= actualYesterdayCount; i++) { var id = actualTodayCount + i; dbContext.Users.Add(new User { Id = id, Uid = $"YEST{i:D3}", Nickname = $"昨日用户{i}", Mobile = $"1390013900{i}", OpenId = $"openid_yest_{i}", HeadImg = $"http://test.com/head{id}.jpg", CreatedAt = today.AddDays(-1).AddHours(i % 24), UpdatedAt = DateTime.Now }); } dbContext.SaveChanges(); // Get dashboard overview var result = service.GetOverviewAsync().GetAwaiter().GetResult(); // Verify today's registrations count return result.TodayRegistrations == actualTodayCount; } /// /// **Feature: admin-business-migration, Property 15: Dashboard Statistics Accuracy** /// For any dashboard overview request, the today's consumption should match /// the sum of prices from today's paid orders. /// Validates: Requirements 8.2 /// [Property(MaxTest = 100)] public bool DashboardOverview_TodayConsumption_ShouldMatchActualSum(PositiveInt todayOrderCount, PositiveInt yesterdayOrderCount) { var actualTodayCount = todayOrderCount.Get % 15; // 0-14 orders today var actualYesterdayCount = yesterdayOrderCount.Get % 15; // 0-14 orders yesterday var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; using var dbContext = new MiAssessmentDbContext(options); var service = new DashboardService(dbContext, _mockLogger.Object); // Seed a user dbContext.Users.Add(new User { Id = 1, Uid = "U001", Nickname = "测试用户", Mobile = "13800138001", OpenId = "openid1", HeadImg = "http://test.com/head.jpg", CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }); dbContext.SaveChanges(); // Seed today's orders var today = DateTime.Today; decimal expectedTodayConsumption = 0; for (int i = 1; i <= actualTodayCount; i++) { var price = 100 * i; expectedTodayConsumption += price; dbContext.Orders.Add(CreateOrder(1, $"TODAY{i:D3}", price, today.AddHours(i % 24))); } // Seed yesterday's orders for (int i = 1; i <= actualYesterdayCount; i++) { var price = 200 * i; dbContext.Orders.Add(CreateOrder(1, $"YEST{i:D3}", price, today.AddDays(-1).AddHours(i % 24))); } dbContext.SaveChanges(); // Get dashboard overview var result = service.GetOverviewAsync().GetAwaiter().GetResult(); // Verify today's consumption return result.TodayConsumption == expectedTodayConsumption; } /// /// **Feature: admin-business-migration, Property 15: Dashboard Statistics Accuracy** /// For any dashboard overview request, the total users count should match /// the actual count of all users in the database. /// Validates: Requirements 8.1 /// [Property(MaxTest = 100)] public bool DashboardOverview_TotalUsers_ShouldMatchActualCount(PositiveInt userCount) { var actualUserCount = (userCount.Get % 50) + 1; // 1-50 users var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; using var dbContext = new MiAssessmentDbContext(options); var service = new DashboardService(dbContext, _mockLogger.Object); // Seed users for (int i = 1; i <= actualUserCount; i++) { dbContext.Users.Add(new User { Id = i, Uid = $"U{i:D3}", Nickname = $"用户{i}", Mobile = $"1380013800{i % 10}", OpenId = $"openid{i}", HeadImg = $"http://test.com/head{i}.jpg", CreatedAt = DateTime.Now.AddDays(-i % 30), UpdatedAt = DateTime.Now }); } dbContext.SaveChanges(); // Get dashboard overview var result = service.GetOverviewAsync().GetAwaiter().GetResult(); // Verify total users count return result.TotalUsers == actualUserCount; } /// /// **Feature: admin-business-migration, Property 15: Dashboard Statistics Accuracy** /// For any dashboard overview request, the total orders count should match /// the actual count of paid orders in the database. /// Validates: Requirements 8.2 /// [Property(MaxTest = 100)] public bool DashboardOverview_TotalOrders_ShouldMatchActualCount(PositiveInt paidCount, PositiveInt unpaidCount) { var actualPaidCount = paidCount.Get % 30; // 0-29 paid orders var actualUnpaidCount = unpaidCount.Get % 20; // 0-19 unpaid orders var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; using var dbContext = new MiAssessmentDbContext(options); var service = new DashboardService(dbContext, _mockLogger.Object); // Seed a user dbContext.Users.Add(new User { Id = 1, Uid = "U001", Nickname = "测试用户", Mobile = "13800138001", OpenId = "openid1", HeadImg = "http://test.com/head.jpg", CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }); dbContext.SaveChanges(); // Seed paid orders (status = 1) for (int i = 1; i <= actualPaidCount; i++) { dbContext.Orders.Add(CreateOrder(1, $"PAID{i:D3}", 100, DateTime.Now, 1)); } // Seed unpaid orders (status = 0) for (int i = 1; i <= actualUnpaidCount; i++) { dbContext.Orders.Add(CreateOrder(1, $"UNPAID{i:D3}", 100, DateTime.Now, 0)); } dbContext.SaveChanges(); // Get dashboard overview var result = service.GetOverviewAsync().GetAwaiter().GetResult(); // Verify total orders count (only paid orders) return result.TotalOrders == actualPaidCount; } #endregion #region Helper Methods private Order CreateOrder(int userId, string orderNum, decimal price, DateTime createdAt, byte status = 1) { return new Order { UserId = userId, OrderNum = orderNum, Price = price, UseMoney = 0, UseIntegral = 0, UseScore = 0, Status = status, CreatedAt = createdAt, UpdatedAt = DateTime.Now, GoodsId = 1, GoodsTitle = "商品", GoodsPrice = price, OrderTotal = price, OrderZheTotal = price, Zhe = 1, Num = 1, PrizeNum = 1, Addtime = (int)DateTimeOffset.Now.ToUnixTimeSeconds() }; } #endregion }