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
}