using System.ComponentModel.DataAnnotations; using MAX.Framework.Commons; using Store.Users.Entities; namespace Store.Users.Tests; public sealed class UserRecordTests { private static readonly DateTime Time = new(2025, 1, 15, 12, 0, 0); [Fact] public async Task PointsQuery_AppliesAccountOperationShopAndInclusiveTimeRange() { using var context = new UserTestContext(); await context.Db.Insert(new[] { Points("main", "充值", "shop-1", Time), Points("main", "充值", "shop-2", Time), Points("main", "消费", "shop-1", Time), Points("main", "充值", "shop-1", Time.AddSeconds(-1)), Points("other", "充值", "shop-1", Time) }).ExecuteAffrowsAsync(); var page = await context.Users.Points("main", new() { Operation = "充值", ShopId = "shop-1", StartTime = Time, EndTime = Time }, default); var record = Assert.Single(page.Items); Assert.Equal(1, page.Total); Assert.Equal("main", record.UserName); Assert.Equal(Time, record.AddTime); } [Fact] public async Task PointsQuery_PaginatesWithStableDescendingIdOrder() { using var context = new UserTestContext(); await context.Db.Insert(new[] { Points("main", "充值", "shop-1", Time), Points("main", "充值", "shop-2", Time), Points("main", "充值", "shop-3", Time) }).ExecuteAffrowsAsync(); var page = await context.Users.Points("main", new() { Page = 2, PageSize = 1 }, default); Assert.Equal(3, page.Total); Assert.Equal("shop-2", Assert.Single(page.Items).ShopId); } [Fact] public async Task BalanceQuery_FiltersTypeOrderAndTime_AndExcludesDeletedRecords() { using var context = new UserTestContext(); await context.Db.Insert(new[] { Payment("main", 1, "order", Time), Payment("main", 0, "order", Time), Payment("main", 1, "different", Time), Payment("main", 1, "order", Time.AddSeconds(1)), Payment("other", 1, "order", Time), Payment("main", 1, "order", Time, deleted: true) }).ExecuteAffrowsAsync(); var page = await context.Users.Balance("main", new() { PaymentType = 1, OrderId = "order", StartTime = Time, EndTime = Time }, default); var record = Assert.Single(page.Items); Assert.Equal(1, page.Total); Assert.Equal("main", record.UserName); Assert.Equal(0, record.IsDeleted); } [Theory] [InlineData(true)] [InlineData(false)] public async Task OrdinaryUser_CannotQuerySomeoneElsesFundsHistory(bool points) { using var context = new UserTestContext(); context.CurrentUser.UserName = "main"; var error = await Assert.ThrowsAsync(async () => { if (points) await context.Users.Points("other", new(), default); else await context.Users.Balance("other", new(), default); }); Assert.Equal(403, error.Code); } [Theory] [InlineData(true)] [InlineData(false)] public async Task ReversedDateRange_IsRejected(bool points) { using var context = new UserTestContext(); await Assert.ThrowsAsync(async () => { var request = new Store.Users.Contracts.Dtos.UserRecordSearchRequest { StartTime = Time, EndTime = Time.AddSeconds(-1) }; if (points) await context.Users.Points("main", request, default); else await context.Users.Balance("main", request, default); }); } private static UserPointsRecordEntity Points(string user, string operation, string shop, DateTime time) => new() { UserName = user, UserNick = user, OptionType = operation, OperatorType = "管理员", OperatorName = "admin", DeltaPoints = 10m, NewPoints = 10m, ShopId = shop, AddTime = time }; private static UserPaymentRecordEntity Payment(string user, int type, string order, DateTime time, bool deleted = false) => new() { UserName = user, Type = type, Amount = 10m, Balance = 10m, OrderId = order, CreateTime = time, CreateBy = "admin", UpdateBy = "", PlatformCode = "", Remark = "", CounterpartyAccountName = "", RefundFlowNo = "", IsDeleted = deleted ? 1 : 0 }; }