138 lines
5.3 KiB
C#
138 lines
5.3 KiB
C#
using FsCheck;
|
||
using FsCheck.Xunit;
|
||
using VendingMachine.Application.Services;
|
||
|
||
namespace VendingMachine.Tests;
|
||
|
||
/// <summary>
|
||
/// 会员与支付模块属性测试
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|