using FsCheck; using FsCheck.Xunit; using Xunit; using XiangYi.Application.Interfaces; using XiangYi.Application.Services; using XiangYi.Core.Enums; namespace XiangYi.Application.Tests.Services; /// /// OrderService属性测试 - 订单创建完整性 /// public class OrderCreateCompletenessPropertyTests { private static readonly System.Random _random = new(); /// /// **Feature: backend-api, Property 16: 订单创建完整性** /// **Validates: Requirements 7.1, 8.1** /// /// *For any* 创建的订单, 应包含订单号、用户ID、金额、状态等必要字段 /// [Property(MaxTest = 100)] public Property Order_ShouldHaveAllRequiredFields() { var userIdArb = Gen.Choose(1, int.MaxValue).Select(x => (long)x).ToArbitrary(); return Prop.ForAll( userIdArb, userId => { var orderNo = "M" + DateTime.Now.ToString("yyyyMMddHHmmss") + _random.Next(1000, 9999).ToString("D4"); var amount = 1299m; var orderType = 1; var productName = "不限时会员"; var isComplete = OrderService.ValidateOrderCompleteness( orderNo, userId, amount, orderType, productName); return isComplete; }); } /// /// 订单创建 - 订单号不能为空 /// [Property(MaxTest = 100)] public Property Order_WithEmptyOrderNo_ShouldBeIncomplete() { var userIdArb = Gen.Choose(1, int.MaxValue).Select(x => (long)x).ToArbitrary(); return Prop.ForAll( userIdArb, userId => { var amount = 1299m; var orderType = 1; var productName = "不限时会员"; var isCompleteWithNull = OrderService.ValidateOrderCompleteness( null, userId, amount, orderType, productName); var isCompleteWithEmpty = OrderService.ValidateOrderCompleteness( "", userId, amount, orderType, productName); return !isCompleteWithNull && !isCompleteWithEmpty; }); } /// /// 订单创建 - 用户ID必须大于0 /// [Property(MaxTest = 100)] public Property Order_WithInvalidUserId_ShouldBeIncomplete() { var invalidUserIdArb = Gen.Choose(-100, 0).Select(x => (long)x).ToArbitrary(); return Prop.ForAll( invalidUserIdArb, userId => { var orderNo = "M" + DateTime.Now.ToString("yyyyMMddHHmmss") + _random.Next(1000, 9999).ToString("D4"); var amount = 1299m; var orderType = 1; var productName = "不限时会员"; var isComplete = OrderService.ValidateOrderCompleteness( orderNo, userId, amount, orderType, productName); return !isComplete; }); } /// /// 订单创建 - 金额必须大于0 /// [Property(MaxTest = 100)] public Property Order_WithInvalidAmount_ShouldBeIncomplete() { var invalidAmountArb = Gen.Choose(-100, 0).Select(x => (decimal)x).ToArbitrary(); return Prop.ForAll( invalidAmountArb, amount => { var orderNo = "M" + DateTime.Now.ToString("yyyyMMddHHmmss") + _random.Next(1000, 9999).ToString("D4"); var userId = 1L; var orderType = 1; var productName = "不限时会员"; var isComplete = OrderService.ValidateOrderCompleteness( orderNo, userId, amount, orderType, productName); return !isComplete; }); } /// /// 订单创建 - 订单类型必须有效 /// [Property(MaxTest = 100)] public Property Order_WithInvalidOrderType_ShouldBeIncomplete() { var invalidOrderTypeArb = Gen.OneOf( Gen.Choose(-10, 0), Gen.Choose(3, 10) ).ToArbitrary(); return Prop.ForAll( invalidOrderTypeArb, orderType => { var orderNo = "M" + DateTime.Now.ToString("yyyyMMddHHmmss") + _random.Next(1000, 9999).ToString("D4"); var userId = 1L; var amount = 1299m; var productName = "不限时会员"; var isComplete = OrderService.ValidateOrderCompleteness( orderNo, userId, amount, orderType, productName); return !isComplete; }); } /// /// 订单创建 - 商品名称不能为空 /// [Property(MaxTest = 100)] public Property Order_WithEmptyProductName_ShouldBeIncomplete() { var userIdArb = Gen.Choose(1, int.MaxValue).Select(x => (long)x).ToArbitrary(); return Prop.ForAll( userIdArb, userId => { var orderNo = "M" + DateTime.Now.ToString("yyyyMMddHHmmss") + _random.Next(1000, 9999).ToString("D4"); var amount = 1299m; var orderType = 1; var isCompleteWithNull = OrderService.ValidateOrderCompleteness( orderNo, userId, amount, orderType, null); var isCompleteWithEmpty = OrderService.ValidateOrderCompleteness( orderNo, userId, amount, orderType, ""); return !isCompleteWithNull && !isCompleteWithEmpty; }); } /// /// 订单号生成 - 会员订单以M开头 /// [Property(MaxTest = 100)] public Property OrderNo_ForMembership_ShouldStartWithM() { var suffixArb = Gen.Choose(1000, 9999).ToArbitrary(); return Prop.ForAll( suffixArb, suffix => { var timestamp = DateTime.Now; var orderNo = OrderService.GenerateOrderNoForTest( (int)OrderType.Membership, timestamp, suffix); return orderNo.StartsWith("M"); }); } /// /// 订单号生成 - 实名认证订单以R开头 /// [Property(MaxTest = 100)] public Property OrderNo_ForRealName_ShouldStartWithR() { var suffixArb = Gen.Choose(1000, 9999).ToArbitrary(); return Prop.ForAll( suffixArb, suffix => { var timestamp = DateTime.Now; var orderNo = OrderService.GenerateOrderNoForTest( (int)OrderType.RealName, timestamp, suffix); return orderNo.StartsWith("R"); }); } /// /// 订单号生成 - 长度应该足够 /// [Property(MaxTest = 100)] public Property OrderNo_ShouldHaveSufficientLength() { var orderTypeArb = Gen.Choose(1, 2).ToArbitrary(); return Prop.ForAll( orderTypeArb, orderType => { var timestamp = DateTime.Now; var suffix = _random.Next(1000, 9999); var orderNo = OrderService.GenerateOrderNoForTest(orderType, timestamp, suffix); return orderNo.Length >= 10; }); } } /// /// OrderService属性测试 - 支付回调幂等性 /// public class PayCallbackIdempotencyPropertyTests { private static readonly System.Random _random = new(); /// /// **Feature: backend-api, Property 17: 支付回调幂等性** /// **Validates: Requirements 7.2** /// /// *For any* 微信支付回调, 重复调用不应重复开通会员或更新订单状态 /// [Property(MaxTest = 100)] public Property PayCallback_ShouldBeIdempotent() { var statusArb = Gen.Choose(1, 4).ToArbitrary(); return Prop.ForAll( statusArb, currentStatus => { var tradeStates = new[] { "SUCCESS", "NOTPAY", "CLOSED", "REFUND" }; foreach (var tradeState in tradeStates) { var statusAfterFirst = OrderService.CalculateStatusAfterCallback(currentStatus, tradeState); var statusAfterSecond = OrderService.CalculateStatusAfterCallback(statusAfterFirst, tradeState); if (statusAfterFirst != statusAfterSecond) return false; } return true; }); } /// /// 支付回调 - 只有待支付状态才处理 /// [Property(MaxTest = 100)] public Property PayCallback_OnlyPendingStatus_ShouldProcess() { var nonPendingStatusArb = Gen.Choose(2, 4).ToArbitrary(); return Prop.ForAll( nonPendingStatusArb, currentStatus => { var shouldProcess = OrderService.ShouldProcessPayCallback(currentStatus, "SUCCESS"); var statusAfter = OrderService.CalculateStatusAfterCallback(currentStatus, "SUCCESS"); return !shouldProcess && statusAfter == currentStatus; }); } /// /// 支付回调 - 待支付状态且支付成功才处理 /// [Property(MaxTest = 100)] public Property PayCallback_PendingAndSuccess_ShouldProcess() { return Prop.ForAll( Arb.Default.PositiveInt(), _ => { var pendingStatus = 1; var successTradeState = "SUCCESS"; var shouldProcess = OrderService.ShouldProcessPayCallback(pendingStatus, successTradeState); var statusAfter = OrderService.CalculateStatusAfterCallback(pendingStatus, successTradeState); return shouldProcess && statusAfter == 2; }); } /// /// 支付回调 - 非成功交易状态不处理 /// [Property(MaxTest = 100)] public Property PayCallback_NonSuccessTradeState_ShouldNotProcess() { var nonSuccessTradeStateArb = Gen.Elements("NOTPAY", "CLOSED", "REFUND", "PAYERROR").ToArbitrary(); return Prop.ForAll( nonSuccessTradeStateArb, tradeState => { var pendingStatus = 1; var shouldProcess = OrderService.ShouldProcessPayCallback(pendingStatus, tradeState); var statusAfter = OrderService.CalculateStatusAfterCallback(pendingStatus, tradeState); return !shouldProcess && statusAfter == pendingStatus; }); } /// /// 支付回调 - 已支付订单重复回调不改变状态 /// [Property(MaxTest = 100)] public Property PayCallback_AlreadyPaid_ShouldNotChangeStatus() { var tradeStateArb = Gen.Elements("SUCCESS", "NOTPAY", "CLOSED", "REFUND").ToArbitrary(); return Prop.ForAll( tradeStateArb, tradeState => { var paidStatus = 2; var shouldProcess = OrderService.ShouldProcessPayCallback(paidStatus, tradeState); var statusAfter = OrderService.CalculateStatusAfterCallback(paidStatus, tradeState); return !shouldProcess && statusAfter == paidStatus; }); } /// /// 支付回调 - 已取消订单不处理 /// [Property(MaxTest = 100)] public Property PayCallback_CancelledOrder_ShouldNotProcess() { var tradeStateArb = Gen.Elements("SUCCESS", "NOTPAY", "CLOSED", "REFUND").ToArbitrary(); return Prop.ForAll( tradeStateArb, tradeState => { var cancelledStatus = 3; var shouldProcess = OrderService.ShouldProcessPayCallback(cancelledStatus, tradeState); var statusAfter = OrderService.CalculateStatusAfterCallback(cancelledStatus, tradeState); return !shouldProcess && statusAfter == cancelledStatus; }); } /// /// 支付回调 - 已退款订单不处理 /// [Property(MaxTest = 100)] public Property PayCallback_RefundedOrder_ShouldNotProcess() { var tradeStateArb = Gen.Elements("SUCCESS", "NOTPAY", "CLOSED", "REFUND").ToArbitrary(); return Prop.ForAll( tradeStateArb, tradeState => { var refundedStatus = 4; var shouldProcess = OrderService.ShouldProcessPayCallback(refundedStatus, tradeState); var statusAfter = OrderService.CalculateStatusAfterCallback(refundedStatus, tradeState); return !shouldProcess && statusAfter == refundedStatus; }); } /// /// 支付回调 - 多次重复回调状态一致 /// [Property(MaxTest = 100)] public Property PayCallback_MultipleCallbacks_ShouldBeConsistent() { var statusArb = Gen.Choose(1, 4).ToArbitrary(); return Prop.ForAll( statusArb, initialStatus => { var tradeStates = new[] { "SUCCESS", "NOTPAY", "CLOSED", "REFUND" }; foreach (var tradeState in tradeStates) { var currentStatus = initialStatus; var repeatCount = _random.Next(2, 10); for (int i = 0; i < repeatCount; i++) { currentStatus = OrderService.CalculateStatusAfterCallback(currentStatus, tradeState); } var expectedStatus = OrderService.CalculateStatusAfterCallback(initialStatus, tradeState); if (currentStatus != expectedStatus) return false; } return true; }); } }