608 lines
23 KiB
C#
608 lines
23 KiB
C#
using FsCheck;
|
|
using FsCheck.Xunit;
|
|
using MiAssessment.Core.Interfaces;
|
|
using MiAssessment.Core.Services;
|
|
using MiAssessment.Model.Data;
|
|
using MiAssessment.Model.Entities;
|
|
using MiAssessment.Model.Models.Payment;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace MiAssessment.Tests.Core;
|
|
|
|
/// <summary>
|
|
/// PaymentOrderService 属性测试
|
|
/// Feature: framework-template
|
|
///
|
|
/// Property 1: Payment Order Creation Integrity
|
|
/// *For any* payment order creation request with valid user ID, order type, and amount, the created order SHALL have:
|
|
/// - A unique order number that does not exist in the database
|
|
/// - The order_type field correctly set to the requested type
|
|
/// - The amount field correctly set to the requested amount
|
|
/// - The biz_data field correctly storing the provided JSON data
|
|
/// **Validates: Requirements 5.1, 5.2, 5.3**
|
|
///
|
|
/// Property 2: Payment Order State Transition
|
|
/// *For any* payment order that receives a successful payment callback:
|
|
/// - The status SHALL be updated from 0 (pending) to 1 (paid)
|
|
/// - The paid_at timestamp SHALL be set
|
|
/// - The transaction_id SHALL be recorded
|
|
/// - The reward processing SHALL be triggered
|
|
/// - If reward succeeds, reward_status SHALL be 1 and reward_at SHALL be set
|
|
/// - If reward fails, reward_status SHALL be 2
|
|
/// **Validates: Requirements 5.4, 5.5, 5.6, 5.7**
|
|
/// </summary>
|
|
public class PaymentOrderServicePropertyTests
|
|
{
|
|
private readonly Mock<ILogger<PaymentOrderService>> _mockLogger = new();
|
|
|
|
/// <summary>
|
|
/// 订单状态:待支付
|
|
/// </summary>
|
|
private const byte StatusPending = 0;
|
|
|
|
/// <summary>
|
|
/// 订单状态:已支付
|
|
/// </summary>
|
|
private const byte StatusPaid = 1;
|
|
|
|
/// <summary>
|
|
/// 奖励状态:未发放
|
|
/// </summary>
|
|
private const byte RewardStatusPending = 0;
|
|
|
|
/// <summary>
|
|
/// 奖励状态:已发放
|
|
/// </summary>
|
|
private const byte RewardStatusSuccess = 1;
|
|
|
|
/// <summary>
|
|
/// 奖励状态:发放失败
|
|
/// </summary>
|
|
private const byte RewardStatusFailed = 2;
|
|
|
|
#region Property 1: Payment Order Creation Integrity
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 1: Payment Order Creation Integrity
|
|
///
|
|
/// Property 1.1: Created order has unique order number
|
|
/// A unique order number that does not exist in the database
|
|
///
|
|
/// **Validates: Requirements 5.1**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool CreateOrder_ShouldGenerateUniqueOrderNo(PositiveInt userId, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
var service = CreateService(dbContext);
|
|
|
|
var request1 = CreateValidRequest(userId.Get, seed.Get);
|
|
var request2 = CreateValidRequest(userId.Get, seed.Get + 1);
|
|
|
|
// Act: Create two orders
|
|
var order1 = service.CreateOrderAsync(request1).GetAwaiter().GetResult();
|
|
var order2 = service.CreateOrderAsync(request2).GetAwaiter().GetResult();
|
|
|
|
// Assert: Order numbers should be unique
|
|
return order1.OrderNo != order2.OrderNo;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 1: Payment Order Creation Integrity
|
|
///
|
|
/// Property 1.2: Created order has correct order_type
|
|
/// The order_type field correctly set to the requested type
|
|
///
|
|
/// **Validates: Requirements 5.2**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool CreateOrder_ShouldSetCorrectOrderType(PositiveInt userId, NonEmptyString orderType, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
var service = CreateService(dbContext);
|
|
|
|
// Generate a valid order type (alphanumeric with underscores)
|
|
var validOrderType = GenerateValidOrderType(orderType.Get, seed.Get);
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
request.OrderType = validOrderType;
|
|
|
|
// Act
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Assert: Order type should match the request
|
|
return order.OrderType == validOrderType;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 1: Payment Order Creation Integrity
|
|
///
|
|
/// Property 1.3: Created order has correct amount
|
|
/// The amount field correctly set to the requested amount
|
|
///
|
|
/// **Validates: Requirements 5.3**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool CreateOrder_ShouldSetCorrectAmount(PositiveInt userId, PositiveInt amountCents, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
var service = CreateService(dbContext);
|
|
|
|
// Generate a valid amount (convert cents to decimal to avoid floating point issues)
|
|
var amount = Math.Round((decimal)(amountCents.Get % 100000 + 1) / 100, 2);
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
request.Amount = amount;
|
|
|
|
// Act
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Assert: Amount should match the request
|
|
return order.Amount == amount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 1: Payment Order Creation Integrity
|
|
///
|
|
/// Property 1.4: Created order has correct biz_data
|
|
/// The biz_data field correctly storing the provided JSON data
|
|
///
|
|
/// **Validates: Requirements 5.3**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool CreateOrder_ShouldSetCorrectBizData(PositiveInt userId, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
var service = CreateService(dbContext);
|
|
|
|
// Generate valid JSON biz_data
|
|
var bizData = $"{{\"product_id\":{seed.Get},\"quantity\":{(seed.Get % 10) + 1}}}";
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
request.BizData = bizData;
|
|
|
|
// Act
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Assert: BizData should match the request
|
|
return order.BizData == bizData;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 1: Payment Order Creation Integrity
|
|
///
|
|
/// Property 1.5: Created order has initial status of pending (0)
|
|
/// The created order should have status = 0 (pending)
|
|
///
|
|
/// **Validates: Requirements 5.1**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool CreateOrder_ShouldHavePendingStatus(PositiveInt userId, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
var service = CreateService(dbContext);
|
|
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
|
|
// Act
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Assert: Status should be pending (0)
|
|
return order.Status == StatusPending;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 1: Payment Order Creation Integrity
|
|
///
|
|
/// Property 1.6: Created order has initial reward_status of pending (0)
|
|
/// The created order should have reward_status = 0 (not issued)
|
|
///
|
|
/// **Validates: Requirements 5.1**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool CreateOrder_ShouldHavePendingRewardStatus(PositiveInt userId, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
var service = CreateService(dbContext);
|
|
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
|
|
// Act
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Assert: RewardStatus should be pending (0)
|
|
return order.RewardStatus == RewardStatusPending;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 1: Payment Order Creation Integrity
|
|
///
|
|
/// Property 1.7: Order number does not exist in database before creation
|
|
/// A unique order number that does not exist in the database
|
|
///
|
|
/// **Validates: Requirements 5.1**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool CreateOrder_OrderNoShouldNotExistBeforeCreation(PositiveInt userId, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
var service = CreateService(dbContext);
|
|
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
|
|
// Act
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
// Verify: The order number should exist exactly once in the database
|
|
var count = dbContext.PaymentOrders.Count(o => o.OrderNo == order.OrderNo);
|
|
|
|
// Assert: Should exist exactly once
|
|
return count == 1;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Property 2: Payment Order State Transition
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 2: Payment Order State Transition
|
|
///
|
|
/// Property 2.1: Payment success updates status from 0 to 1
|
|
/// The status SHALL be updated from 0 (pending) to 1 (paid)
|
|
///
|
|
/// **Validates: Requirements 5.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool HandlePaymentSuccess_ShouldUpdateStatusToPaid(PositiveInt userId, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
var service = CreateService(dbContext);
|
|
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}";
|
|
|
|
// Act
|
|
var result = service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult();
|
|
|
|
// Refresh the order from database
|
|
var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult();
|
|
|
|
// Assert: Status should be updated to paid (1)
|
|
return result && updatedOrder != null && updatedOrder.Status == StatusPaid;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 2: Payment Order State Transition
|
|
///
|
|
/// Property 2.2: Payment success sets paid_at timestamp
|
|
/// The paid_at timestamp SHALL be set
|
|
///
|
|
/// **Validates: Requirements 5.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool HandlePaymentSuccess_ShouldSetPaidAtTimestamp(PositiveInt userId, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
var service = CreateService(dbContext);
|
|
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
var beforePayment = DateTime.Now.AddSeconds(-1);
|
|
var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}";
|
|
|
|
// Act
|
|
service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult();
|
|
|
|
// Refresh the order from database
|
|
var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult();
|
|
var afterPayment = DateTime.Now.AddSeconds(1);
|
|
|
|
// Assert: PaidAt should be set and within reasonable time range
|
|
return updatedOrder != null &&
|
|
updatedOrder.PaidAt.HasValue &&
|
|
updatedOrder.PaidAt.Value >= beforePayment &&
|
|
updatedOrder.PaidAt.Value <= afterPayment;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 2: Payment Order State Transition
|
|
///
|
|
/// Property 2.3: Payment success records transaction_id
|
|
/// The transaction_id SHALL be recorded
|
|
///
|
|
/// **Validates: Requirements 5.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool HandlePaymentSuccess_ShouldRecordTransactionId(PositiveInt userId, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
var service = CreateService(dbContext);
|
|
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}";
|
|
|
|
// Act
|
|
service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult();
|
|
|
|
// Refresh the order from database
|
|
var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult();
|
|
|
|
// Assert: TransactionId should be recorded
|
|
return updatedOrder != null && updatedOrder.TransactionId == transactionId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 2: Payment Order State Transition
|
|
///
|
|
/// Property 2.4: Payment success with successful reward handler sets reward_status to 1
|
|
/// If reward succeeds, reward_status SHALL be 1 and reward_at SHALL be set
|
|
///
|
|
/// **Validates: Requirements 5.5, 5.6**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool HandlePaymentSuccess_WithSuccessfulReward_ShouldSetRewardStatusToSuccess(PositiveInt userId, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
|
|
var orderType = $"test_reward_success_{seed.Get % 100}";
|
|
var rewardData = $"{{\"reward_id\":{seed.Get}}}";
|
|
|
|
// Create a mock reward handler that succeeds
|
|
var mockHandler = new Mock<IPaymentRewardHandler>();
|
|
mockHandler.Setup(h => h.OrderType).Returns(orderType);
|
|
mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny<PaymentOrder>()))
|
|
.ReturnsAsync(RewardResult.Ok(rewardData));
|
|
|
|
var service = CreateService(dbContext, new[] { mockHandler.Object });
|
|
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
request.OrderType = orderType;
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}";
|
|
|
|
// Act
|
|
service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult();
|
|
|
|
// Refresh the order from database
|
|
var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult();
|
|
|
|
// Assert: RewardStatus should be success (1) and RewardAt should be set
|
|
return updatedOrder != null &&
|
|
updatedOrder.RewardStatus == RewardStatusSuccess &&
|
|
updatedOrder.RewardAt.HasValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 2: Payment Order State Transition
|
|
///
|
|
/// Property 2.5: Payment success with failed reward handler sets reward_status to 2
|
|
/// If reward fails, reward_status SHALL be 2
|
|
///
|
|
/// **Validates: Requirements 5.7**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool HandlePaymentSuccess_WithFailedReward_ShouldSetRewardStatusToFailed(PositiveInt userId, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
|
|
var orderType = $"test_reward_fail_{seed.Get % 100}";
|
|
var errorMessage = $"Reward processing failed for seed {seed.Get}";
|
|
|
|
// Create a mock reward handler that fails
|
|
var mockHandler = new Mock<IPaymentRewardHandler>();
|
|
mockHandler.Setup(h => h.OrderType).Returns(orderType);
|
|
mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny<PaymentOrder>()))
|
|
.ReturnsAsync(RewardResult.Fail(errorMessage));
|
|
|
|
var service = CreateService(dbContext, new[] { mockHandler.Object });
|
|
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
request.OrderType = orderType;
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}";
|
|
|
|
// Act
|
|
service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult();
|
|
|
|
// Refresh the order from database
|
|
var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult();
|
|
|
|
// Assert: RewardStatus should be failed (2)
|
|
return updatedOrder != null && updatedOrder.RewardStatus == RewardStatusFailed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 2: Payment Order State Transition
|
|
///
|
|
/// Property 2.6: Payment success triggers reward processing
|
|
/// The reward processing SHALL be triggered
|
|
///
|
|
/// **Validates: Requirements 5.5**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool HandlePaymentSuccess_ShouldTriggerRewardProcessing(PositiveInt userId, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
|
|
var orderType = $"test_trigger_{seed.Get % 100}";
|
|
var rewardProcessed = false;
|
|
|
|
// Create a mock reward handler that tracks if it was called
|
|
var mockHandler = new Mock<IPaymentRewardHandler>();
|
|
mockHandler.Setup(h => h.OrderType).Returns(orderType);
|
|
mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny<PaymentOrder>()))
|
|
.Callback(() => rewardProcessed = true)
|
|
.ReturnsAsync(RewardResult.Ok());
|
|
|
|
var service = CreateService(dbContext, new[] { mockHandler.Object });
|
|
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
request.OrderType = orderType;
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}";
|
|
|
|
// Act
|
|
service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult();
|
|
|
|
// Assert: Reward handler should have been called
|
|
return rewardProcessed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 2: Payment Order State Transition
|
|
///
|
|
/// Property 2.7: Idempotent payment success handling
|
|
/// Processing the same payment twice should not change the order state
|
|
///
|
|
/// **Validates: Requirements 5.4**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool HandlePaymentSuccess_ShouldBeIdempotent(PositiveInt userId, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
var service = CreateService(dbContext);
|
|
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}";
|
|
|
|
// Act: Process payment twice
|
|
var result1 = service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult();
|
|
var result2 = service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult();
|
|
|
|
// Refresh the order from database
|
|
var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult();
|
|
|
|
// Assert: Both calls should succeed and order should be in paid state
|
|
return result1 && result2 && updatedOrder != null && updatedOrder.Status == StatusPaid;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Feature: framework-template, Property 2: Payment Order State Transition
|
|
///
|
|
/// Property 2.8: Successful reward stores reward_data
|
|
/// If reward succeeds, reward_data SHALL contain the reward information
|
|
///
|
|
/// **Validates: Requirements 5.6**
|
|
/// </summary>
|
|
[Property(MaxTest = 100)]
|
|
public bool HandlePaymentSuccess_WithSuccessfulReward_ShouldStoreRewardData(PositiveInt userId, PositiveInt seed)
|
|
{
|
|
// Arrange
|
|
using var dbContext = CreateDbContext();
|
|
|
|
var orderType = $"test_reward_data_{seed.Get % 100}";
|
|
var rewardData = $"{{\"diamonds\":{seed.Get % 1000 + 100},\"bonus\":{seed.Get % 50}}}";
|
|
|
|
// Create a mock reward handler that returns reward data
|
|
var mockHandler = new Mock<IPaymentRewardHandler>();
|
|
mockHandler.Setup(h => h.OrderType).Returns(orderType);
|
|
mockHandler.Setup(h => h.ProcessRewardAsync(It.IsAny<PaymentOrder>()))
|
|
.ReturnsAsync(RewardResult.Ok(rewardData));
|
|
|
|
var service = CreateService(dbContext, new[] { mockHandler.Object });
|
|
|
|
var request = CreateValidRequest(userId.Get, seed.Get);
|
|
request.OrderType = orderType;
|
|
var order = service.CreateOrderAsync(request).GetAwaiter().GetResult();
|
|
|
|
var transactionId = $"TX_{seed.Get}_{DateTime.Now.Ticks}";
|
|
|
|
// Act
|
|
service.HandlePaymentSuccessAsync(order.OrderNo, transactionId, order.Amount).GetAwaiter().GetResult();
|
|
|
|
// Refresh the order from database
|
|
var updatedOrder = service.GetOrderByNoAsync(order.OrderNo).GetAwaiter().GetResult();
|
|
|
|
// Assert: RewardData should be stored
|
|
return updatedOrder != null && updatedOrder.RewardData == rewardData;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
/// <summary>
|
|
/// 创建内存数据库上下文
|
|
/// </summary>
|
|
private MiAssessmentDbContext CreateDbContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<MiAssessmentDbContext>()
|
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
|
.Options;
|
|
|
|
return new MiAssessmentDbContext(options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建 PaymentOrderService 实例
|
|
/// </summary>
|
|
private PaymentOrderService CreateService(MiAssessmentDbContext dbContext, IEnumerable<IPaymentRewardHandler>? handlers = null)
|
|
{
|
|
return new PaymentOrderService(
|
|
dbContext,
|
|
handlers ?? Array.Empty<IPaymentRewardHandler>(),
|
|
_mockLogger.Object);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建有效的支付订单请求
|
|
/// </summary>
|
|
private CreatePaymentOrderRequest CreateValidRequest(int userId, int seed)
|
|
{
|
|
return new CreatePaymentOrderRequest
|
|
{
|
|
UserId = Math.Max(1, userId), // Ensure positive user ID
|
|
OrderType = $"test_order_{seed % 100}",
|
|
Title = $"测试订单 {seed}",
|
|
Amount = Math.Round((decimal)((seed % 10000) + 100) / 100, 2), // 1.00 to 101.00
|
|
PayMethod = "wechat",
|
|
BizData = $"{{\"seed\":{seed}}}"
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 生成有效的订单类型(只包含字母、数字和下划线)
|
|
/// </summary>
|
|
private string GenerateValidOrderType(string input, int seed)
|
|
{
|
|
// Filter to only alphanumeric and underscore characters
|
|
var filtered = new string(input.Where(c => char.IsLetterOrDigit(c) || c == '_').ToArray());
|
|
|
|
// Ensure it's not empty and has reasonable length
|
|
if (string.IsNullOrEmpty(filtered))
|
|
{
|
|
filtered = "order";
|
|
}
|
|
|
|
// Limit length and add seed for uniqueness
|
|
var maxLength = Math.Min(filtered.Length, 20);
|
|
return $"{filtered.Substring(0, maxLength)}_{seed % 1000}";
|
|
}
|
|
|
|
#endregion
|
|
}
|