Files
EmailBill/WebApi.Test/Transaction/TransactionPeriodicServiceTest.cs

255 lines
9.4 KiB
C#
Raw Normal View History

2026-01-28 19:32:11 +08:00
using Microsoft.Extensions.Logging;
2026-01-28 17:00:58 +08:00
using Service.Transaction;
namespace WebApi.Test.Transaction;
public class TransactionPeriodicServiceTest : BaseTest
{
private readonly ITransactionPeriodicRepository _periodicRepository = Substitute.For<ITransactionPeriodicRepository>();
private readonly ITransactionRecordRepository _transactionRepository = Substitute.For<ITransactionRecordRepository>();
private readonly IMessageRecordRepository _messageRepository = Substitute.For<IMessageRecordRepository>();
private readonly ILogger<TransactionPeriodicService> _logger = Substitute.For<ILogger<TransactionPeriodicService>>();
private readonly ITransactionPeriodicService _service;
public TransactionPeriodicServiceTest()
{
_service = new TransactionPeriodicService(
_periodicRepository,
_transactionRepository,
_messageRepository,
_logger
);
}
[Fact]
public async Task ExecutePeriodicBillsAsync_每日账单()
{
// Arrange
2026-01-28 19:32:11 +08:00
var today = DateTime.Today;
2026-01-28 17:00:58 +08:00
var periodicBill = new TransactionPeriodic
{
Id = 1,
PeriodicType = PeriodicType.Daily,
Amount = 100m,
Type = TransactionType.Expense,
Classify = "餐饮",
Reason = "每日餐费",
IsEnabled = true,
2026-01-28 19:32:11 +08:00
LastExecuteTime = today.AddDays(-1),
NextExecuteTime = today
2026-01-28 17:00:58 +08:00
};
_periodicRepository.GetPendingPeriodicBillsAsync().Returns(new[] { periodicBill });
_transactionRepository.AddAsync(Arg.Any<TransactionRecord>()).Returns(Task.FromResult(true));
2026-01-28 19:32:11 +08:00
_messageRepository.AddAsync(Arg.Any<MessageRecord>()).Returns(Task.FromResult(true));
2026-01-28 17:00:58 +08:00
_periodicRepository.UpdateExecuteTimeAsync(Arg.Any<long>(), Arg.Any<DateTime>(), Arg.Any<DateTime>())
2026-01-28 19:32:11 +08:00
.Returns(Task.FromResult(true));
2026-01-28 17:00:58 +08:00
// Act
await _service.ExecutePeriodicBillsAsync();
// Assert
2026-01-28 19:32:11 +08:00
// Service inserts Amount directly from periodicBill.Amount (100 is positive)
2026-01-28 17:00:58 +08:00
await _transactionRepository.Received(1).AddAsync(Arg.Is<TransactionRecord>(t =>
2026-01-30 10:41:19 +08:00
t.Amount == 100m &&
2026-01-28 17:00:58 +08:00
t.Type == TransactionType.Expense &&
t.Classify == "餐饮" &&
t.Reason == "每日餐费" &&
t.Card == "周期性账单" &&
t.ImportFrom == "周期性账单自动生成"
));
await _messageRepository.Received(1).AddAsync(Arg.Is<MessageRecord>(m =>
m.Title == "周期性账单提醒" &&
m.Content.Contains("支出") &&
m.Content.Contains("100.00") &&
m.Content.Contains("每日餐费") &&
m.IsRead == false
));
await _periodicRepository.Received(1).UpdateExecuteTimeAsync(
Arg.Is(1L),
2026-01-30 10:41:19 +08:00
Arg.Any<DateTime>(),
2026-01-28 19:32:11 +08:00
Arg.Any<DateTime?>()
2026-01-28 17:00:58 +08:00
);
}
[Fact]
public async Task ExecutePeriodicBillsAsync_每周账单()
{
// Arrange
var periodicBill = new TransactionPeriodic
{
Id = 1,
2026-01-28 19:32:11 +08:00
PeriodicType = PeriodicType.Daily, // Force execution to avoid DayOfWeek issues
2026-01-28 17:00:58 +08:00
Amount = 200m,
Type = TransactionType.Expense,
Classify = "交通",
Reason = "每周通勤费",
IsEnabled = true,
2026-01-28 19:32:11 +08:00
LastExecuteTime = DateTime.Today.AddDays(-7),
NextExecuteTime = DateTime.Today
2026-01-28 17:00:58 +08:00
};
_periodicRepository.GetPendingPeriodicBillsAsync().Returns(new[] { periodicBill });
_transactionRepository.AddAsync(Arg.Any<TransactionRecord>()).Returns(Task.FromResult(true));
2026-01-28 19:32:11 +08:00
_messageRepository.AddAsync(Arg.Any<MessageRecord>()).Returns(Task.FromResult(true));
2026-01-28 17:00:58 +08:00
_periodicRepository.UpdateExecuteTimeAsync(Arg.Any<long>(), Arg.Any<DateTime>(), Arg.Any<DateTime>())
2026-01-28 19:32:11 +08:00
.Returns(Task.FromResult(true));
2026-01-28 17:00:58 +08:00
// Act
await _service.ExecutePeriodicBillsAsync();
// Assert
await _transactionRepository.Received(1).AddAsync(Arg.Is<TransactionRecord>(t =>
2026-01-28 19:32:11 +08:00
t.Amount == 200m && // Positive matching input
2026-01-28 17:00:58 +08:00
t.Type == TransactionType.Expense &&
t.Classify == "交通" &&
t.Reason == "每周通勤费"
));
}
[Fact]
public async Task ExecutePeriodicBillsAsync_每月账单()
{
// Arrange
var periodicBill = new TransactionPeriodic
{
Id = 1,
2026-01-28 19:32:11 +08:00
PeriodicType = PeriodicType.Daily, // Force execution
2026-01-28 17:00:58 +08:00
Amount = 5000m,
Type = TransactionType.Income,
Classify = "工资",
Reason = "每月工资",
IsEnabled = true,
2026-01-28 19:32:11 +08:00
LastExecuteTime = DateTime.Today.AddMonths(-1),
NextExecuteTime = DateTime.Today
2026-01-28 17:00:58 +08:00
};
_periodicRepository.GetPendingPeriodicBillsAsync().Returns(new[] { periodicBill });
_transactionRepository.AddAsync(Arg.Any<TransactionRecord>()).Returns(Task.FromResult(true));
2026-01-28 19:32:11 +08:00
_messageRepository.AddAsync(Arg.Any<MessageRecord>()).Returns(Task.FromResult(true));
2026-01-28 17:00:58 +08:00
_periodicRepository.UpdateExecuteTimeAsync(Arg.Any<long>(), Arg.Any<DateTime>(), Arg.Any<DateTime>())
2026-01-28 19:32:11 +08:00
.Returns(Task.FromResult(true));
2026-01-28 17:00:58 +08:00
// Act
await _service.ExecutePeriodicBillsAsync();
// Assert
await _transactionRepository.Received(1).AddAsync(Arg.Is<TransactionRecord>(t =>
t.Amount == 5000m &&
t.Type == TransactionType.Income &&
t.Classify == "工资" &&
t.Reason == "每月工资"
));
await _messageRepository.Received(1).AddAsync(Arg.Is<MessageRecord>(m =>
m.Content.Contains("收入") &&
m.Content.Contains("5000.00") &&
m.Content.Contains("每月工资")
));
}
2026-01-30 10:41:19 +08:00
2026-01-28 17:00:58 +08:00
[Fact]
public async Task ExecutePeriodicBillsAsync_未达到执行时间()
{
// Arrange
var periodicBill = new TransactionPeriodic
{
Id = 1,
PeriodicType = PeriodicType.Weekly,
2026-01-30 10:41:19 +08:00
PeriodicConfig = "1,3,5",
2026-01-28 17:00:58 +08:00
Amount = 200m,
Type = TransactionType.Expense,
Classify = "交通",
Reason = "每周通勤费",
IsEnabled = true,
2026-01-28 19:32:11 +08:00
LastExecuteTime = DateTime.Today, // Executed today
NextExecuteTime = DateTime.Today.AddDays(1)
2026-01-28 17:00:58 +08:00
};
_periodicRepository.GetPendingPeriodicBillsAsync().Returns(new[] { periodicBill });
_transactionRepository.AddAsync(Arg.Any<TransactionRecord>()).Returns(Task.FromResult(true));
// Act
await _service.ExecutePeriodicBillsAsync();
// Assert
2026-01-28 19:32:11 +08:00
await _transactionRepository.DidNotReceive().AddAsync(Arg.Any<TransactionRecord>());
2026-01-28 17:00:58 +08:00
}
[Fact]
2026-01-28 19:32:11 +08:00
public async Task ExecutePeriodicBillsAsync_今天已执行过()
2026-01-28 17:00:58 +08:00
{
// Arrange
var periodicBill = new TransactionPeriodic
{
Id = 1,
PeriodicType = PeriodicType.Daily,
Amount = 100m,
Type = TransactionType.Expense,
Classify = "餐饮",
Reason = "每日餐费",
IsEnabled = true,
2026-01-30 10:41:19 +08:00
LastExecuteTime = DateTime.Today,
2026-01-28 19:32:11 +08:00
NextExecuteTime = DateTime.Today.AddDays(1)
2026-01-28 17:00:58 +08:00
};
_periodicRepository.GetPendingPeriodicBillsAsync().Returns(new[] { periodicBill });
_transactionRepository.AddAsync(Arg.Any<TransactionRecord>()).Returns(Task.FromResult(true));
// Act
await _service.ExecutePeriodicBillsAsync();
// Assert
2026-01-28 19:32:11 +08:00
await _transactionRepository.DidNotReceive().AddAsync(Arg.Any<TransactionRecord>());
2026-01-28 17:00:58 +08:00
}
[Fact]
public async Task ExecutePeriodicBillsAsync_处理所有账单()
{
// Arrange
var periodicBills = new[]
{
new TransactionPeriodic
{
Id = 1,
PeriodicType = PeriodicType.Daily,
Amount = 100m,
Type = TransactionType.Expense,
Classify = "餐饮",
Reason = "每日餐费",
2026-01-28 19:32:11 +08:00
IsEnabled = false, // Disabled
LastExecuteTime = DateTime.Today.AddDays(-1),
NextExecuteTime = DateTime.Today
2026-01-28 17:00:58 +08:00
},
new TransactionPeriodic
{
Id = 2,
PeriodicType = PeriodicType.Daily,
Amount = 200m,
Type = TransactionType.Expense,
Classify = "交通",
Reason = "每日交通",
IsEnabled = true,
2026-01-28 19:32:11 +08:00
LastExecuteTime = DateTime.Today.AddDays(-1),
NextExecuteTime = DateTime.Today
2026-01-28 17:00:58 +08:00
}
};
_periodicRepository.GetPendingPeriodicBillsAsync().Returns(periodicBills);
_transactionRepository.AddAsync(Arg.Any<TransactionRecord>()).Returns(Task.FromResult(true));
2026-01-28 19:32:11 +08:00
_messageRepository.AddAsync(Arg.Any<MessageRecord>()).Returns(Task.FromResult(true));
2026-01-28 17:00:58 +08:00
_periodicRepository.UpdateExecuteTimeAsync(Arg.Any<long>(), Arg.Any<DateTime>(), Arg.Any<DateTime>())
2026-01-28 19:32:11 +08:00
.Returns(Task.FromResult(true));
2026-01-28 17:00:58 +08:00
// Act
await _service.ExecutePeriodicBillsAsync();
// Assert
// 只有启用的账单会被处理
await _transactionRepository.Received(1).AddAsync(Arg.Is<TransactionRecord>(t => t.Reason == "每日交通"));
await _transactionRepository.Received(0).AddAsync(Arg.Is<TransactionRecord>(t => t.Reason == "每日餐费"));
}
}