using HoneyBox.Core.Interfaces; using HoneyBox.Core.Services; using HoneyBox.Model.Data; using HoneyBox.Model.Entities; using HoneyBox.Model.Models.Payment; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; using Moq.Protected; using System.Net; using System.Text; using Xunit; namespace HoneyBox.Tests.Integration; /// /// 微信支付服务集成测试 /// 测试统一下单流程(模拟微信API)和签名生成验证 /// Requirements: 1.1-1.5, 7.1-7.4 /// public class WechatPayServiceIntegrationTests { private readonly WechatPaySettings _settings; private readonly Mock> _mockLogger; private readonly Mock _mockConfigService; private readonly Mock _mockWechatService; private readonly Mock _mockRedisService; public WechatPayServiceIntegrationTests() { _settings = new WechatPaySettings { DefaultMerchant = new WechatPayMerchantConfig { Name = "TestMerchant", MchId = "1234567890", AppId = "wx1234567890abcdef", Key = "test_secret_key_32_characters_ok", OrderPrefix = "TST", Weight = 1, NotifyUrl = "https://example.com/notify" }, Merchants = new List { new WechatPayMerchantConfig { Name = "Merchant1", MchId = "1111111111", AppId = "wx1111111111111111", Key = "merchant1_secret_key_32_chars_ok", OrderPrefix = "M01", Weight = 1 } }, NotifyBaseUrl = "https://example.com" }; _mockLogger = new Mock>(); _mockConfigService = new Mock(); _mockConfigService.Setup(x => x.GetMerchantByOrderNo(It.IsAny())) .Returns(_settings.DefaultMerchant); _mockWechatService = new Mock(); _mockRedisService = new Mock(); } private HoneyBoxDbContext CreateInMemoryDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning)) .Options; return new HoneyBoxDbContext(options); } private WechatPayService CreateWechatPayService(HoneyBoxDbContext dbContext, HttpClient? httpClient = null) { var options = Options.Create(_settings); return new WechatPayService( dbContext, httpClient ?? new HttpClient(), _mockLogger.Object, _mockConfigService.Object, _mockWechatService.Object, _mockRedisService.Object, options); } private async Task CreateTestUserAsync(HoneyBoxDbContext dbContext) { var user = new User { Id = 1, OpenId = "test_openid_123456", Uid = "test_uid", Nickname = "测试用户", HeadImg = "avatar.jpg", Mobile = "13800138000", Money = 100, Integral = 1000, Money2 = 500, IsTest = 0, Status = 1, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; await dbContext.Users.AddAsync(user); await dbContext.SaveChangesAsync(); return user; } #region 签名生成和验证测试 (Requirements 7.1-7.4) /// /// 测试签名生成 - 基本功能 /// Requirements: 7.1 /// [Fact] public void MakeSign_BasicParameters_GeneratesValidSignature() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateWechatPayService(dbContext); var parameters = new Dictionary { { "appid", "wx1234567890abcdef" }, { "mch_id", "1234567890" }, { "nonce_str", "test_nonce_string" }, { "body", "Test Payment" }, { "out_trade_no", "TST_20250102123456" }, { "total_fee", "100" } }; // Act var sign = service.MakeSign(parameters); // Assert Assert.NotNull(sign); Assert.Equal(32, sign.Length); // MD5 produces 32 hex characters Assert.Equal(sign, sign.ToUpper()); // Should be uppercase Assert.True(sign.All(c => "0123456789ABCDEF".Contains(c))); // Should be hex } /// /// 测试签名验证 - 正确签名 /// Requirements: 7.2 /// [Fact] public void VerifySign_ValidSignature_ReturnsTrue() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateWechatPayService(dbContext); var parameters = new Dictionary { { "appid", "wx1234567890abcdef" }, { "mch_id", "1234567890" }, { "nonce_str", "test_nonce" }, { "body", "Test Payment" } }; var sign = service.MakeSign(parameters); // Act var result = service.VerifySign(parameters, sign); // Assert Assert.True(result); } /// /// 测试签名验证 - 错误签名 /// Requirements: 7.4 /// [Fact] public void VerifySign_InvalidSignature_ReturnsFalse() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateWechatPayService(dbContext); var parameters = new Dictionary { { "appid", "wx1234567890abcdef" }, { "mch_id", "1234567890" }, { "nonce_str", "test_nonce" } }; // Act var result = service.VerifySign(parameters, "INVALID_SIGNATURE_12345678901234"); // Assert Assert.False(result); } /// /// 测试签名验证 - 空签名 /// Requirements: 7.4 /// [Fact] public void VerifySign_EmptySignature_ReturnsFalse() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateWechatPayService(dbContext); var parameters = new Dictionary { { "appid", "wx1234567890abcdef" }, { "mch_id", "1234567890" } }; // Act & Assert Assert.False(service.VerifySign(parameters, "")); Assert.False(service.VerifySign(parameters, null!)); } /// /// 测试多商户签名支持 /// Requirements: 7.3 /// [Fact] public void MakeSign_DifferentMerchantKeys_ProducesDifferentSignatures() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateWechatPayService(dbContext); var parameters = new Dictionary { { "appid", "wx1234567890abcdef" }, { "mch_id", "1234567890" }, { "nonce_str", "test_nonce" } }; var key1 = _settings.DefaultMerchant.Key; var key2 = _settings.Merchants[0].Key; // Act var sign1 = service.MakeSign(parameters, key1); var sign2 = service.MakeSign(parameters, key2); // Assert Assert.NotEqual(sign1, sign2); Assert.True(service.VerifySign(parameters, sign1, key1)); Assert.False(service.VerifySign(parameters, sign1, key2)); } #endregion #region 统一下单测试 (Requirements 1.1-1.5) /// /// 测试统一下单 - 用户不存在 /// Requirements: 1.1 /// [Fact] public async Task CreatePaymentAsync_UserNotFound_ReturnsError() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateWechatPayService(dbContext); var request = new WechatPayRequest { OrderNo = "TST_20250102123456", Amount = 10.00m, Body = "Test Payment", Attach = "order_yfs", UserId = 999 // Non-existent user }; // Act var result = await service.CreatePaymentAsync(request); // Assert Assert.Equal(0, result.Status); Assert.Contains("用户不存在", result.Msg); } /// /// 测试统一下单 - 用户OpenId为空 /// Requirements: 1.1 /// [Fact] public async Task CreatePaymentAsync_EmptyOpenId_ReturnsError() { // Arrange var dbContext = CreateInMemoryDbContext(); // Create user without OpenId var user = new User { Id = 1, OpenId = "", // Empty OpenId Uid = "test_uid", Nickname = "测试用户", HeadImg = "avatar.jpg", Status = 1, CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now }; await dbContext.Users.AddAsync(user); await dbContext.SaveChangesAsync(); var service = CreateWechatPayService(dbContext); var request = new WechatPayRequest { OrderNo = "TST_20250102123456", Amount = 10.00m, Body = "Test Payment", Attach = "order_yfs", UserId = 1 }; // Act var result = await service.CreatePaymentAsync(request); // Assert Assert.Equal(0, result.Status); Assert.Contains("OpenId", result.Msg); } /// /// 测试统一下单 - 成功场景(模拟微信API) /// Requirements: 1.1, 1.2, 1.4, 1.5 /// [Fact] public async Task CreatePaymentAsync_ValidRequest_ReturnsPaymentParams() { // Arrange var dbContext = CreateInMemoryDbContext(); await CreateTestUserAsync(dbContext); // Mock HTTP response from WeChat API var mockHandler = new Mock(); var wechatResponse = @" "; mockHandler.Protected() .Setup>( "SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) .ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(wechatResponse, Encoding.UTF8, "application/xml") }); var httpClient = new HttpClient(mockHandler.Object); var service = CreateWechatPayService(dbContext, httpClient); var request = new WechatPayRequest { OrderNo = "TST_20250102123456", Amount = 10.00m, Body = "Test Payment", Attach = "order_yfs", UserId = 1 }; // Act var result = await service.CreatePaymentAsync(request); // Assert Assert.Equal(1, result.Status); Assert.Equal("success", result.Msg); Assert.NotNull(result.Data); Assert.Equal("wx1234567890abcdef", result.Data.AppId); Assert.NotEmpty(result.Data.TimeStamp); Assert.NotEmpty(result.Data.NonceStr); Assert.Contains("prepay_id=", result.Data.Package); Assert.Equal("MD5", result.Data.SignType); Assert.NotEmpty(result.Data.PaySign); Assert.Equal(1, result.Data.IsWeixin); } /// /// 测试统一下单 - 微信API返回失败 /// Requirements: 1.3 /// [Fact] public async Task CreatePaymentAsync_WechatApiError_ReturnsError() { // Arrange var dbContext = CreateInMemoryDbContext(); await CreateTestUserAsync(dbContext); // Mock HTTP response with error var mockHandler = new Mock(); var wechatResponse = @" "; mockHandler.Protected() .Setup>( "SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) .ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(wechatResponse, Encoding.UTF8, "application/xml") }); var httpClient = new HttpClient(mockHandler.Object); var service = CreateWechatPayService(dbContext, httpClient); var request = new WechatPayRequest { OrderNo = "TST_20250102123456", Amount = 10.00m, Body = "Test Payment", Attach = "order_yfs", UserId = 1 }; // Act var result = await service.CreatePaymentAsync(request); // Assert Assert.Equal(0, result.Status); Assert.Contains("签名错误", result.Msg); } /// /// 测试统一下单 - 业务失败(如订单已支付) /// Requirements: 1.3 /// [Fact] public async Task CreatePaymentAsync_BusinessError_ReturnsError() { // Arrange var dbContext = CreateInMemoryDbContext(); await CreateTestUserAsync(dbContext); // Mock HTTP response with business error var mockHandler = new Mock(); var wechatResponse = @" "; mockHandler.Protected() .Setup>( "SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) .ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(wechatResponse, Encoding.UTF8, "application/xml") }); var httpClient = new HttpClient(mockHandler.Object); var service = CreateWechatPayService(dbContext, httpClient); var request = new WechatPayRequest { OrderNo = "TST_20250102123456", Amount = 10.00m, Body = "Test Payment", Attach = "order_yfs", UserId = 1 }; // Act var result = await service.CreatePaymentAsync(request); // Assert Assert.Equal(0, result.Status); Assert.Contains("已支付", result.Msg); } #endregion #region XML解析测试 /// /// 测试XML解析 - 正常回调数据 /// [Fact] public void ParseNotifyXml_ValidXml_ReturnsCorrectData() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateWechatPayService(dbContext); var xml = @" 1000 1000 "; // Act var result = service.ParseNotifyXml(xml); // Assert Assert.Equal("SUCCESS", result.ReturnCode); Assert.Equal("SUCCESS", result.ResultCode); Assert.Equal("wx1234567890abcdef", result.AppId); Assert.Equal("1234567890", result.MchId); Assert.Equal("test_openid_123456", result.OpenId); Assert.Equal(1000, result.TotalFee); Assert.Equal("TST_20250102123456", result.OutTradeNo); Assert.Equal("order_yfs", result.Attach); } /// /// 测试生成回调响应XML /// [Fact] public void GenerateNotifyResponseXml_Success_ReturnsCorrectXml() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateWechatPayService(dbContext); // Act var result = service.GenerateNotifyResponseXml("SUCCESS", "OK"); // Assert Assert.Contains("", result); Assert.Contains("", result); } #endregion #region 商户配置测试 (Requirements 1.5) /// /// 测试根据订单号获取商户配置 /// Requirements: 1.5 /// [Fact] public void GetMerchantByOrderNo_ValidOrderNo_ReturnsCorrectMerchant() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateWechatPayService(dbContext); // Act var merchant = service.GetMerchantByOrderNo("TST_20250102123456"); // Assert Assert.NotNull(merchant); Assert.Equal(_settings.DefaultMerchant.MchId, merchant.MchId); } /// /// 测试根据订单号获取商户密钥 /// Requirements: 1.5 /// [Fact] public void GetMerchantKeyByOrderNo_ValidOrderNo_ReturnsCorrectKey() { // Arrange var dbContext = CreateInMemoryDbContext(); var service = CreateWechatPayService(dbContext); // Act var key = service.GetMerchantKeyByOrderNo("TST_20250102123456"); // Assert Assert.Equal(_settings.DefaultMerchant.Key, key); } #endregion }