using FsCheck; using FsCheck.Xunit; using VendingMachine.Application.Services; namespace VendingMachine.Tests; /// /// 会员与支付模块属性测试 /// public class MembershipPropertyTests { // Feature: vending-machine-app, Property 7: 会员页按钮状态取决于会员类型 // 对于任意用户的会员类型(none/monthly/subscription),会员页按钮应遵循以下规则: // 无会员时显示开通按钮;已购单月会员时隐藏单月按钮仅显示订阅按钮;已购订阅会员时按钮置灰不可点击。 // **Validates: Requirements 4.3, 4.7, 4.8** [Property(MaxTest = 100)] public bool MembershipButtonState_None_ShowsBothButtons(int seed) { var state = IMembershipService.GetButtonState("none"); return state.ShowMonthlyButton && state.ShowSubscriptionButton && state.MonthlyButtonEnabled && state.SubscriptionButtonEnabled; } [Property(MaxTest = 100)] public bool MembershipButtonState_Monthly_HidesMonthlyButton(int seed) { var state = IMembershipService.GetButtonState("monthly"); return !state.ShowMonthlyButton && state.ShowSubscriptionButton && !state.MonthlyButtonEnabled && state.SubscriptionButtonEnabled; } [Property(MaxTest = 100)] public bool MembershipButtonState_Subscription_DisablesAllButtons(int seed) { var state = IMembershipService.GetButtonState("subscription"); return !state.MonthlyButtonEnabled && !state.SubscriptionButtonEnabled && state.SubscriptionButtonText == "已购买订阅会员"; } // Feature: vending-machine-app, Property 8: 平台与支付方式映射 // 对于任意设备平台,Android 应调用 Google Pay,iOS 应调用 Apple Pay。 // **Validates: Requirements 4.2** [Property(MaxTest = 100)] public bool PlatformPaymentMethod_Android_MapsToGooglePay(int seed) { return IMembershipService.GetPaymentMethod("android") == "Google Pay"; } [Property(MaxTest = 100)] public bool PlatformPaymentMethod_iOS_MapsToApplePay(int seed) { return IMembershipService.GetPaymentMethod("ios") == "Apple Pay"; } // Feature: vending-machine-app, Property 9: 积分保留与清空规则 // 对于任意会员到期的用户,如果当前时间距会员到期不超过3个月则积分保留,超过3个月则积分清空为0。 // **Validates: Requirements 4.4, 4.5** [Property(MaxTest = 100)] public bool PointsRetention_WhenExpiredWithin3Months_ShouldRetain(PositiveInt daysExpired) { // 限制在1-89天内(不超过3个月≈90天) var days = (daysExpired.Get % 89) + 1; var expireDate = DateTime.UtcNow.AddDays(-days); var now = DateTime.UtcNow; // 到期不超过3个月,积分应保留(不清空) return !IMembershipService.ShouldClearPoints(expireDate, now); } [Property(MaxTest = 100)] public bool PointsClear_WhenExpiredOver3Months_ShouldClear(PositiveInt extraDays) { // 到期超过3个月(91天+额外天数) var days = 91 + (extraDays.Get % 275); var expireDate = DateTime.UtcNow.AddDays(-days); var now = DateTime.UtcNow; // 到期超过3个月,积分应被清空 return IMembershipService.ShouldClearPoints(expireDate, now); } [Property(MaxTest = 100)] public bool PointsNotCleared_WhenMembershipNotExpired(PositiveInt daysUntilExpire) { // 会员未到期(未来1-365天) var days = (daysUntilExpire.Get % 365) + 1; var expireDate = DateTime.UtcNow.AddDays(days); var now = DateTime.UtcNow; // 会员未到期,积分不应被清空 return !IMembershipService.ShouldClearPoints(expireDate, now); } // Feature: vending-machine-app, Property 17: 支付错误处理 // 对于任意支付错误(无效凭证),App 应拒绝操作且用户状态保持不变。 // **Validates: Requirements 8.4** [Property(MaxTest = 100)] public bool PaymentError_EmptyReceipt_ShouldReject(NonEmptyString platform) { // 空凭证应被拒绝 return !IMembershipService.ValidateReceipt("", platform.Get); } [Property(MaxTest = 100)] public bool PaymentError_EmptyPlatform_ShouldReject(NonEmptyString receipt) { // 空平台应被拒绝 return !IMembershipService.ValidateReceipt(receipt.Get, ""); } [Property(MaxTest = 100)] public bool PaymentError_InvalidPlatform_ShouldReject(NonEmptyString receipt, NonEmptyString platform) { var p = platform.Get.ToLowerInvariant(); // 排除有效平台 if (p == "android" || p == "ios") return true; // 跳过有效平台 return !IMembershipService.ValidateReceipt(receipt.Get, platform.Get); } [Property(MaxTest = 100)] public bool PaymentValidation_ValidInput_ShouldAccept(NonEmptyString receipt, bool useAndroid) { var platform = useAndroid ? "android" : "ios"; var receiptStr = receipt.Get; // 跳过纯空白字符串(ValidateReceipt 正确地拒绝空白凭证) if (string.IsNullOrWhiteSpace(receiptStr)) return true; return IMembershipService.ValidateReceipt(receiptStr, platform); } }