using HoneyBox.Core.Interfaces;
using HoneyBox.Core.Services;
using HoneyBox.Model.Data;
using HoneyBox.Model.Entities;
using HoneyBox.Model.Models.Auth;
using HoneyBox.Model.Models.Payment;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace HoneyBox.Tests.Integration;
///
/// 支付回调服务集成测试
/// 测试回调处理流程和幂等性
/// Requirements: 2.1-2.9
///
public class PaymentNotifyServiceIntegrationTests
{
private readonly WechatPaySettings _settings;
private readonly AppSettings _appSettings;
private readonly Mock> _mockNotifyLogger;
private readonly Mock> _mockPayLogger;
private readonly Mock> _mockPaymentLogger;
private readonly Mock _mockConfigService;
private readonly Mock _mockWechatService;
private readonly Mock _mockRedisService;
public PaymentNotifyServiceIntegrationTests()
{
_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()
};
_appSettings = new AppSettings { IsTestEnvironment = false };
_mockNotifyLogger = new Mock>();
_mockPayLogger = new Mock>();
_mockPaymentLogger = 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 (PaymentNotifyService notifyService, WechatPayService wechatPayService, PaymentService paymentService)
CreateServices(HoneyBoxDbContext dbContext)
{
var options = Options.Create(_settings);
var wechatPayService = new WechatPayService(
dbContext,
new HttpClient(),
_mockPayLogger.Object,
_mockConfigService.Object,
_mockWechatService.Object,
_mockRedisService.Object,
options,
_appSettings);
var paymentService = new PaymentService(dbContext, _mockPaymentLogger.Object);
var mockLotteryEngine = new Mock();
var mockWechatPayV3Service = new Mock();
// Setup V3 service to detect V2 format for existing tests
mockWechatPayV3Service.Setup(x => x.DetectNotifyVersion(It.IsAny()))
.Returns(NotifyVersion.V2);
var notifyService = new PaymentNotifyService(
dbContext,
wechatPayService,
mockWechatPayV3Service.Object,
_mockConfigService.Object,
paymentService,
mockLotteryEngine.Object,
_mockNotifyLogger.Object);
return (notifyService, wechatPayService, paymentService);
}
private async Task CreateTestUserAsync(HoneyBoxDbContext dbContext, string openId = "test_openid_123456")
{
var user = new User
{
Id = 1,
OpenId = openId,
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;
}
private async Task CreateTestOrderAsync(HoneyBoxDbContext dbContext, int userId, string orderNum, byte orderType = 1)
{
var order = new Order
{
Id = 1,
UserId = userId,
OrderNum = orderNum,
OrderTotal = 30,
OrderZheTotal = 30,
Price = 10,
UseMoney = 10,
UseIntegral = 500,
UseMoney2 = 500,
GoodsId = 1,
GoodsTitle = "测试商品",
GoodsImgurl = "img.jpg",
GoodsPrice = 10,
PrizeNum = 3,
Num = 1,
OrderType = orderType,
Status = 0, // 待支付
Addtime = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
};
await dbContext.Orders.AddAsync(order);
await dbContext.SaveChangesAsync();
return order;
}
private string GenerateValidNotifyXml(string orderNo, string openId, int totalFee = 1000)
{
return $@"
{totalFee}
{totalFee}
";
}
#region 回调处理流程测试 (Requirements 2.1-2.3)
///
/// 测试回调处理 - 空数据
/// Requirements: 2.1
///
[Fact]
public async Task HandleWechatNotifyAsync_EmptyData_ReturnsFail()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var (notifyService, _, _) = CreateServices(dbContext);
// Act
var result = await notifyService.HandleWechatNotifyAsync("");
// Assert
Assert.False(result.Success);
Assert.Contains("空", result.Message);
}
///
/// 测试回调处理 - 无效XML
/// Requirements: 2.1
///
[Fact]
public async Task HandleWechatNotifyAsync_InvalidXml_ReturnsFail()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var (notifyService, _, _) = CreateServices(dbContext);
// Act
var result = await notifyService.HandleWechatNotifyAsync("invalid xml data");
// Assert
Assert.False(result.Success);
}
///
/// 测试回调处理 - 支付失败状态
/// Requirements: 2.1
///
[Fact]
public async Task HandleWechatNotifyAsync_PaymentFailed_ReturnsWithXmlResponse()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var (notifyService, _, _) = CreateServices(dbContext);
var xml = @"
";
// Act
var result = await notifyService.HandleWechatNotifyAsync(xml);
// Assert
// The response should contain XML regardless of success/failure
Assert.NotEmpty(result.XmlResponse);
Assert.Contains("", result.XmlResponse);
}
#endregion
#region 幂等性测试 (Requirements 2.8)
///
/// 测试幂等性检查 - 订单未处理
/// Requirements: 2.8
///
[Fact]
public async Task IsOrderProcessedAsync_OrderNotProcessed_ReturnsFalse()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var (notifyService, _, _) = CreateServices(dbContext);
// Act
var result = await notifyService.IsOrderProcessedAsync("TST_20250102123456");
// Assert
Assert.False(result);
}
///
/// 测试幂等性检查 - 订单已处理
/// Requirements: 2.8
///
[Fact]
public async Task IsOrderProcessedAsync_OrderAlreadyProcessed_ReturnsTrue()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var (notifyService, _, _) = CreateServices(dbContext);
// Add processed order notify record
var orderNotify = new OrderNotify
{
OrderNo = "TST_20250102123456",
Status = 1, // Processed
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
};
await dbContext.OrderNotifies.AddAsync(orderNotify);
await dbContext.SaveChangesAsync();
// Act
var result = await notifyService.IsOrderProcessedAsync("TST_20250102123456");
// Assert
Assert.True(result);
}
///
/// 测试记录回调通知 - 新记录
/// Requirements: 2.8
///
[Fact]
public async Task RecordNotifyAsync_NewRecord_CreatesSuccessfully()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var (notifyService, _, _) = CreateServices(dbContext);
var notifyData = new WechatNotifyData
{
OutTradeNo = "TST_20250102123456",
TransactionId = "wx20250102123456",
NonceStr = "test_nonce",
TotalFee = 1000,
Attach = "order_yfs",
OpenId = "test_openid_123456"
};
// Act
var result = await notifyService.RecordNotifyAsync("TST_20250102123456", notifyData);
// Assert
Assert.True(result);
var savedNotify = await dbContext.OrderNotifies.FirstOrDefaultAsync(n => n.OrderNo == "TST_20250102123456");
Assert.NotNull(savedNotify);
Assert.Equal("wx20250102123456", savedNotify.TransactionId);
Assert.Equal(10.00m, savedNotify.PayAmount); // 1000分 = 10元
}
///
/// 测试记录回调通知 - 更新现有记录
/// Requirements: 2.8
///
[Fact]
public async Task RecordNotifyAsync_ExistingRecord_UpdatesSuccessfully()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var (notifyService, _, _) = CreateServices(dbContext);
// Add existing record
var existingNotify = new OrderNotify
{
OrderNo = "TST_20250102123456",
Status = 0,
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
};
await dbContext.OrderNotifies.AddAsync(existingNotify);
await dbContext.SaveChangesAsync();
var notifyData = new WechatNotifyData
{
OutTradeNo = "TST_20250102123456",
TransactionId = "wx20250102123456_updated",
NonceStr = "test_nonce",
TotalFee = 2000,
Attach = "order_yfs",
OpenId = "test_openid_123456"
};
// Act
var result = await notifyService.RecordNotifyAsync("TST_20250102123456", notifyData);
// Assert
Assert.True(result);
var updatedNotify = await dbContext.OrderNotifies.FirstOrDefaultAsync(n => n.OrderNo == "TST_20250102123456");
Assert.NotNull(updatedNotify);
Assert.Equal("wx20250102123456_updated", updatedNotify.TransactionId);
Assert.Equal(20.00m, updatedNotify.PayAmount);
}
#endregion
#region 一番赏订单处理测试 (Requirements 2.3, 2.6, 2.7)
///
/// 测试一番赏订单处理 - 订单不存在
/// Requirements: 2.3
///
[Fact]
public async Task ProcessLotteryOrderAsync_OrderNotFound_ReturnsFalse()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var (notifyService, _, _) = CreateServices(dbContext);
// Act
var result = await notifyService.ProcessLotteryOrderAsync(999, 1, 1, 1);
// Assert
Assert.False(result);
}
///
/// 测试一番赏订单处理 - 成功处理
/// Requirements: 2.3, 2.6, 2.7
/// Note: This test is skipped because InMemory database does not support transactions
///
[Fact(Skip = "InMemory database does not support transactions")]
public async Task ProcessLotteryOrderAsync_ValidOrder_ProcessesSuccessfully()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var user = await CreateTestUserAsync(dbContext);
// Create order with valid lottery order type (1=一番赏)
var order = new Order
{
Id = 1,
UserId = user.Id,
OrderNum = "TST_20250102123456",
OrderTotal = 30,
OrderZheTotal = 30,
Price = 10,
UseMoney = 10,
UseIntegral = 500,
UseMoney2 = 500,
GoodsId = 1,
GoodsTitle = "测试商品",
GoodsImgurl = "img.jpg",
GoodsPrice = 10,
PrizeNum = 3,
Num = 1,
OrderType = 1, // 一番赏
Status = 0, // 待支付
Addtime = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
};
await dbContext.Orders.AddAsync(order);
await dbContext.SaveChangesAsync();
var (notifyService, _, _) = CreateServices(dbContext);
// Act
var result = await notifyService.ProcessLotteryOrderAsync(order.Id, user.Id, order.GoodsId, order.Num);
// Assert
Assert.True(result);
// Verify order status updated
var updatedOrder = await dbContext.Orders.FindAsync(order.Id);
Assert.NotNull(updatedOrder);
Assert.Equal(1, updatedOrder.Status); // Paid
Assert.True(updatedOrder.PayTime > 0);
}
#endregion
#region 无限赏订单处理测试 (Requirements 2.3, 2.6, 2.7)
///
/// 测试无限赏订单处理 - 订单不存在
/// Requirements: 2.3
///
[Fact]
public async Task ProcessInfiniteOrderAsync_OrderNotFound_ReturnsFalse()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var (notifyService, _, _) = CreateServices(dbContext);
// Act
var result = await notifyService.ProcessInfiniteOrderAsync(999, 1, 1);
// Assert
Assert.False(result);
}
///
/// 测试无限赏订单处理 - 成功处理
/// Requirements: 2.3, 2.6, 2.7
/// Note: This test is skipped because InMemory database does not support transactions
///
[Fact(Skip = "InMemory database does not support transactions")]
public async Task ProcessInfiniteOrderAsync_ValidOrder_ProcessesSuccessfully()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var user = await CreateTestUserAsync(dbContext);
var order = await CreateTestOrderAsync(dbContext, user.Id, "TST_20250102123456", orderType: 2);
var (notifyService, _, _) = CreateServices(dbContext);
// Act
var result = await notifyService.ProcessInfiniteOrderAsync(order.Id, user.Id, order.GoodsId);
// Assert
Assert.True(result);
// Verify order status updated
var updatedOrder = await dbContext.Orders.FindAsync(order.Id);
Assert.NotNull(updatedOrder);
Assert.Equal(1, updatedOrder.Status); // Paid
}
#endregion
#region 充值订单处理测试 (Requirements 2.4)
///
/// 测试充值订单处理 - 订单不存在
/// Requirements: 2.4
///
[Fact]
public async Task ProcessRechargeOrderAsync_OrderNotFound_ReturnsFalse()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var (notifyService, _, _) = CreateServices(dbContext);
// Act
var result = await notifyService.ProcessRechargeOrderAsync("NONEXISTENT_ORDER");
// Assert
Assert.False(result);
}
///
/// 测试充值订单处理 - 成功处理
/// Requirements: 2.4
/// Note: This test is skipped because InMemory database does not support transactions
///
[Fact(Skip = "InMemory database does not support transactions")]
public async Task ProcessRechargeOrderAsync_ValidOrder_ProcessesSuccessfully()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var user = await CreateTestUserAsync(dbContext);
// Create recharge order
var rechargeOrder = new UserRecharge
{
Id = 1,
UserId = user.Id,
OrderNum = "TST_RECHARGE_20250102",
Money = 50,
Status = 1, // Pending payment
CreatedAt = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
await dbContext.UserRecharges.AddAsync(rechargeOrder);
await dbContext.SaveChangesAsync();
var (notifyService, _, _) = CreateServices(dbContext);
var initialBalance = user.Money;
// Act
var result = await notifyService.ProcessRechargeOrderAsync("TST_RECHARGE_20250102");
// Assert
Assert.True(result);
// Verify recharge order status updated
var updatedRecharge = await dbContext.UserRecharges.FindAsync(rechargeOrder.Id);
Assert.NotNull(updatedRecharge);
Assert.Equal(2, updatedRecharge.Status); // Completed
// Verify user balance increased
var updatedUser = await dbContext.Users.FindAsync(user.Id);
Assert.NotNull(updatedUser);
Assert.Equal(initialBalance + 50, updatedUser.Money);
}
#endregion
#region 发货运费订单处理测试 (Requirements 2.5)
///
/// 测试发货运费订单处理 - 订单不存在
/// Requirements: 2.5
///
[Fact]
public async Task ProcessShippingFeeOrderAsync_OrderNotFound_ReturnsFalse()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var (notifyService, _, _) = CreateServices(dbContext);
// Act
var result = await notifyService.ProcessShippingFeeOrderAsync("NONEXISTENT_ORDER");
// Assert
Assert.False(result);
}
///
/// 测试发货运费订单处理 - 成功处理
/// Requirements: 2.5
/// Note: This test is skipped because InMemory database does not support transactions
///
[Fact(Skip = "InMemory database does not support transactions")]
public async Task ProcessShippingFeeOrderAsync_ValidOrder_ProcessesSuccessfully()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var user = await CreateTestUserAsync(dbContext);
// Create shipping fee order
var sendRecord = new OrderItemsSend
{
Id = 1,
UserId = user.Id,
SendNum = "FH_20250102123456",
Status = 0, // Pending payment
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
};
await dbContext.OrderItemsSends.AddAsync(sendRecord);
await dbContext.SaveChangesAsync();
var (notifyService, _, _) = CreateServices(dbContext);
// Act
var result = await notifyService.ProcessShippingFeeOrderAsync("FH_20250102123456");
// Assert
Assert.True(result);
// Verify send record status updated
var updatedSend = await dbContext.OrderItemsSends.FindAsync(sendRecord.Id);
Assert.NotNull(updatedSend);
Assert.Equal(1, updatedSend.Status); // Pending shipment
}
#endregion
#region XML响应测试 (Requirements 2.9)
///
/// 测试回调响应 - 成功响应格式
/// Requirements: 2.9
///
[Fact]
public async Task HandleWechatNotifyAsync_Success_ReturnsCorrectXmlResponse()
{
// Arrange
var dbContext = CreateInMemoryDbContext();
var (notifyService, _, _) = CreateServices(dbContext);
// Act - Even with empty data, we should get a proper XML response
var result = await notifyService.HandleWechatNotifyAsync("");
// Assert
Assert.NotEmpty(result.XmlResponse);
Assert.Contains("", result.XmlResponse);
Assert.Contains("", result.XmlResponse);
}
#endregion
}