HaniBlindBox/server/HoneyBox/tests/HoneyBox.Tests/Services/WechatPayV3RequestPropertyTests.cs
2026-01-25 20:28:11 +08:00

368 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text.Json;
using FsCheck;
using FsCheck.Xunit;
using HoneyBox.Model.Models.Payment;
using Xunit;
namespace HoneyBox.Tests.Services;
/// <summary>
/// 微信支付 V3 请求字段完整性属性测试
/// **Feature: wechat-pay-v3-upgrade**
/// </summary>
public class WechatPayV3RequestPropertyTests
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
};
#region Property 6: V3
/// <summary>
/// **Feature: wechat-pay-v3-upgrade, Property 6: V3 请求字段完整性**
/// *For any* V3 JSAPI 下单请求,构建的请求体应该包含所有必要字段:
/// appid、mchid、description、out_trade_no、notify_url、amount、payer。
/// **Validates: Requirements 3.2**
/// </summary>
[Property(MaxTest = 100)]
public bool V3JsapiRequest_ShouldContainAllRequiredFields(
NonEmptyString appId,
NonEmptyString mchId,
NonEmptyString description,
NonEmptyString outTradeNo,
NonEmptyString notifyUrl,
PositiveInt totalAmount,
NonEmptyString openId)
{
// 创建 V3 JSAPI 请求
var request = new WechatPayV3JsapiRequest
{
AppId = appId.Get,
MchId = mchId.Get,
Description = description.Get,
OutTradeNo = outTradeNo.Get,
NotifyUrl = notifyUrl.Get,
Amount = new WechatPayV3Amount
{
Total = totalAmount.Get,
Currency = "CNY"
},
Payer = new WechatPayV3Payer
{
OpenId = openId.Get
}
};
// 序列化为 JSON
var json = JsonSerializer.Serialize(request, JsonOptions);
// 验证所有必要字段都存在
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
// 检查所有必要字段
var hasAppId = root.TryGetProperty("appid", out var appIdProp) && !string.IsNullOrEmpty(appIdProp.GetString());
var hasMchId = root.TryGetProperty("mchid", out var mchIdProp) && !string.IsNullOrEmpty(mchIdProp.GetString());
var hasDescription = root.TryGetProperty("description", out var descProp) && !string.IsNullOrEmpty(descProp.GetString());
var hasOutTradeNo = root.TryGetProperty("out_trade_no", out var tradeProp) && !string.IsNullOrEmpty(tradeProp.GetString());
var hasNotifyUrl = root.TryGetProperty("notify_url", out var notifyProp) && !string.IsNullOrEmpty(notifyProp.GetString());
var hasAmount = root.TryGetProperty("amount", out var amountProp) && amountProp.ValueKind == JsonValueKind.Object;
var hasPayer = root.TryGetProperty("payer", out var payerProp) && payerProp.ValueKind == JsonValueKind.Object;
// 检查 amount 子字段
var hasTotal = hasAmount && amountProp.TryGetProperty("total", out var totalProp) && totalProp.ValueKind == JsonValueKind.Number;
var hasCurrency = hasAmount && amountProp.TryGetProperty("currency", out var currencyProp) && !string.IsNullOrEmpty(currencyProp.GetString());
// 检查 payer 子字段
var hasOpenId = hasPayer && payerProp.TryGetProperty("openid", out var openIdProp) && !string.IsNullOrEmpty(openIdProp.GetString());
return hasAppId && hasMchId && hasDescription && hasOutTradeNo && hasNotifyUrl &&
hasAmount && hasTotal && hasCurrency && hasPayer && hasOpenId;
}
/// <summary>
/// **Feature: wechat-pay-v3-upgrade, Property 6: V3 请求字段完整性**
/// *For any* V3 JSAPI 请求,金额字段应该是正整数(单位:分)。
/// **Validates: Requirements 3.2**
/// </summary>
[Property(MaxTest = 100)]
public bool V3JsapiRequest_AmountShouldBePositiveInteger(PositiveInt totalAmount)
{
var request = new WechatPayV3JsapiRequest
{
AppId = "wx1234567890",
MchId = "1234567890",
Description = "测试商品",
OutTradeNo = "ORDER123456",
NotifyUrl = "https://example.com/notify",
Amount = new WechatPayV3Amount
{
Total = totalAmount.Get,
Currency = "CNY"
},
Payer = new WechatPayV3Payer
{
OpenId = "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"
}
};
var json = JsonSerializer.Serialize(request, JsonOptions);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
var amount = root.GetProperty("amount");
var total = amount.GetProperty("total").GetInt32();
return total > 0 && total == totalAmount.Get;
}
/// <summary>
/// **Feature: wechat-pay-v3-upgrade, Property 6: V3 请求字段完整性**
/// *For any* V3 JSAPI 请求,货币类型默认应该是 CNY。
/// **Validates: Requirements 3.2**
/// </summary>
[Property(MaxTest = 100)]
public bool V3JsapiRequest_CurrencyShouldDefaultToCNY(PositiveInt totalAmount)
{
var request = new WechatPayV3JsapiRequest
{
AppId = "wx1234567890",
MchId = "1234567890",
Description = "测试商品",
OutTradeNo = "ORDER123456",
NotifyUrl = "https://example.com/notify",
Amount = new WechatPayV3Amount
{
Total = totalAmount.Get
// Currency 使用默认值
},
Payer = new WechatPayV3Payer
{
OpenId = "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"
}
};
var json = JsonSerializer.Serialize(request, JsonOptions);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
var amount = root.GetProperty("amount");
var currency = amount.GetProperty("currency").GetString();
return currency == "CNY";
}
/// <summary>
/// **Feature: wechat-pay-v3-upgrade, Property 6: V3 请求字段完整性**
/// *For any* V3 JSAPI 请求,可选字段 attach 为 null 时不应该出现在 JSON 中。
/// **Validates: Requirements 3.2**
/// </summary>
[Fact]
public void V3JsapiRequest_NullAttach_ShouldNotAppearInJson()
{
var request = new WechatPayV3JsapiRequest
{
AppId = "wx1234567890",
MchId = "1234567890",
Description = "测试商品",
OutTradeNo = "ORDER123456",
NotifyUrl = "https://example.com/notify",
Amount = new WechatPayV3Amount { Total = 100, Currency = "CNY" },
Payer = new WechatPayV3Payer { OpenId = "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o" },
Attach = null // 可选字段为 null
};
var json = JsonSerializer.Serialize(request, JsonOptions);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
// attach 为 null 时不应该出现在 JSON 中
Assert.False(root.TryGetProperty("attach", out _));
}
/// <summary>
/// **Feature: wechat-pay-v3-upgrade, Property 6: V3 请求字段完整性**
/// *For any* V3 JSAPI 请求,可选字段 attach 有值时应该出现在 JSON 中。
/// **Validates: Requirements 3.2**
/// </summary>
[Property(MaxTest = 100)]
public bool V3JsapiRequest_NonNullAttach_ShouldAppearInJson(NonEmptyString attach)
{
var request = new WechatPayV3JsapiRequest
{
AppId = "wx1234567890",
MchId = "1234567890",
Description = "测试商品",
OutTradeNo = "ORDER123456",
NotifyUrl = "https://example.com/notify",
Amount = new WechatPayV3Amount { Total = 100, Currency = "CNY" },
Payer = new WechatPayV3Payer { OpenId = "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o" },
Attach = attach.Get
};
var json = JsonSerializer.Serialize(request, JsonOptions);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
// attach 有值时应该出现在 JSON 中
return root.TryGetProperty("attach", out var attachProp) &&
attachProp.GetString() == attach.Get;
}
#endregion
#region Property 7: V3
/// <summary>
/// **Feature: wechat-pay-v3-upgrade, Property 7: V3 支付参数完整性**
/// *For any* 成功的 V3 下单响应,返回给前端的支付参数应该包含:
/// timeStamp、nonceStr、package、signType(RSA)、paySign。
/// **Validates: Requirements 3.4**
/// </summary>
[Property(MaxTest = 100)]
public bool V3PayData_ShouldContainAllRequiredFields(
NonEmptyString appId,
NonEmptyString timeStamp,
NonEmptyString nonceStr,
NonEmptyString prepayId,
NonEmptyString paySign)
{
// 模拟 V3 支付数据
var payData = new WechatPayData
{
AppId = appId.Get,
TimeStamp = timeStamp.Get,
NonceStr = nonceStr.Get,
Package = $"prepay_id={prepayId.Get}",
SignType = "RSA",
PaySign = paySign.Get,
IsWeixin = 1
};
// 验证所有必要字段
return !string.IsNullOrEmpty(payData.AppId) &&
!string.IsNullOrEmpty(payData.TimeStamp) &&
!string.IsNullOrEmpty(payData.NonceStr) &&
!string.IsNullOrEmpty(payData.Package) &&
payData.Package.StartsWith("prepay_id=") &&
payData.SignType == "RSA" &&
!string.IsNullOrEmpty(payData.PaySign);
}
/// <summary>
/// **Feature: wechat-pay-v3-upgrade, Property 7: V3 支付参数完整性**
/// V3 支付参数的 signType 应该是 RSA而不是 V2 的 MD5
/// **Validates: Requirements 3.4**
/// </summary>
[Fact]
public void V3PayData_SignType_ShouldBeRSA()
{
var payData = new WechatPayData
{
AppId = "wx1234567890",
TimeStamp = "1609459200",
NonceStr = "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
Package = "prepay_id=wx201410272009395522657a690389285100",
SignType = "RSA", // V3 使用 RSA
PaySign = "base64signature",
IsWeixin = 1
};
Assert.Equal("RSA", payData.SignType);
}
/// <summary>
/// **Feature: wechat-pay-v3-upgrade, Property 7: V3 支付参数完整性**
/// V3 支付参数的 package 格式应该是 prepay_id=xxx。
/// **Validates: Requirements 3.4**
/// </summary>
[Property(MaxTest = 100)]
public bool V3PayData_Package_ShouldHaveCorrectFormat(NonEmptyString prepayId)
{
var package = $"prepay_id={prepayId.Get}";
return package.StartsWith("prepay_id=") &&
package.Length > "prepay_id=".Length;
}
#endregion
#region
/// <summary>
/// **Feature: wechat-pay-v3-upgrade, Property 6: V3 请求字段完整性**
/// V3 请求序列化后的 JSON 字段名应该使用 snake_case 格式。
/// **Validates: Requirements 3.2**
/// </summary>
[Fact]
public void V3JsapiRequest_Serialization_ShouldUseSnakeCase()
{
var request = new WechatPayV3JsapiRequest
{
AppId = "wx1234567890",
MchId = "1234567890",
Description = "测试商品",
OutTradeNo = "ORDER123456",
NotifyUrl = "https://example.com/notify",
Amount = new WechatPayV3Amount { Total = 100, Currency = "CNY" },
Payer = new WechatPayV3Payer { OpenId = "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o" }
};
var json = JsonSerializer.Serialize(request, JsonOptions);
// 验证使用 snake_case
Assert.Contains("\"appid\"", json);
Assert.Contains("\"mchid\"", json);
Assert.Contains("\"description\"", json);
Assert.Contains("\"out_trade_no\"", json);
Assert.Contains("\"notify_url\"", json);
Assert.Contains("\"amount\"", json);
Assert.Contains("\"payer\"", json);
Assert.Contains("\"total\"", json);
Assert.Contains("\"currency\"", json);
Assert.Contains("\"openid\"", json);
}
/// <summary>
/// **Feature: wechat-pay-v3-upgrade, Property 6: V3 请求字段完整性**
/// V3 请求序列化后应该是有效的 JSON。
/// **Validates: Requirements 3.2**
/// </summary>
[Property(MaxTest = 100)]
public bool V3JsapiRequest_Serialization_ShouldProduceValidJson(
NonEmptyString appId,
NonEmptyString mchId,
NonEmptyString description,
NonEmptyString outTradeNo,
NonEmptyString notifyUrl,
PositiveInt totalAmount,
NonEmptyString openId)
{
var request = new WechatPayV3JsapiRequest
{
AppId = appId.Get,
MchId = mchId.Get,
Description = description.Get,
OutTradeNo = outTradeNo.Get,
NotifyUrl = notifyUrl.Get,
Amount = new WechatPayV3Amount { Total = totalAmount.Get, Currency = "CNY" },
Payer = new WechatPayV3Payer { OpenId = openId.Get }
};
try
{
var json = JsonSerializer.Serialize(request, JsonOptions);
using var doc = JsonDocument.Parse(json);
return doc.RootElement.ValueKind == JsonValueKind.Object;
}
catch
{
return false;
}
}
#endregion
}