Files
EmailBill/WebApi.Test/Application/TransactionStatisticsApplicationTest.cs
SunCheng a7954f55ad feat: remove V1 calendar/budget/stats modules
- 删除 V1 前端页面 (CalendarView, BudgetView, statisticsV1)
- 移除 V1 路由配置 (/calendar, /budget, /)
- 清理路由守卫中的 V1 版本切换逻辑
- 移除设置页面中的版本切换功能
- 更新底部导航和登录重定向到 V2 路由
- 移除 App.vue 中 V1 页面的缓存配置
- 删除后端 TransactionRecordController.GetDailyStatisticsAsync (Obsolete)
- 删除 TransactionStatisticsController.GetBalanceStatisticsAsync
- 保留 V2 仍在使用的共享 API (GetUncoveredCategories, GetArchiveSummary, GetDailyStatistics)
- 保留 V2 使用的全局事件监听机制
- 所有测试通过 (210/210)

Breaking Change: V1 API 端点和路由将不可用
2026-02-14 00:01:44 +08:00

276 lines
8.9 KiB
C#

using Application.Dto.Statistics;
using Service.Transaction;
namespace WebApi.Test.Application;
/// <summary>
/// TransactionStatisticsApplication 单元测试
/// </summary>
public class TransactionStatisticsApplicationTest : BaseApplicationTest
{
private readonly ITransactionStatisticsService _statisticsService;
private readonly IConfigService _configService;
private readonly TransactionStatisticsApplication _application;
public TransactionStatisticsApplicationTest()
{
_statisticsService = Substitute.For<ITransactionStatisticsService>();
_configService = Substitute.For<IConfigService>();
_application = new TransactionStatisticsApplication(_statisticsService, _configService);
}
#region GetDailyStatisticsAsync Tests
[Fact]
public async Task GetDailyStatisticsAsync_有效数据_应返回每日统计()
{
// Arrange
var year = 2026;
var month = 2;
var dailyStats = new Dictionary<string, (int count, decimal expense, decimal income, decimal saving)>
{
{ "2026-02-10", (3, 500m, 1000m, 100m) },
{ "2026-02-11", (5, 800m, 2000m, 200m) }
};
_configService.GetConfigByKeyAsync<string>("SavingsCategories").Returns("储蓄");
_statisticsService.GetDailyStatisticsAsync(year, month, "储蓄").Returns(dailyStats);
// Act
var result = await _application.GetDailyStatisticsAsync(year, month);
// Assert
result.Should().HaveCount(2);
result.Should().Contain(s => s.Day == 10 && s.Income == 1000m && s.Expense == 500m && s.Count == 3 && s.Saving == 100m);
result.Should().Contain(s => s.Day == 11 && s.Income == 2000m && s.Expense == 800m && s.Count == 5 && s.Saving == 200m);
}
#endregion
#region GetWeeklyStatisticsAsync Tests
[Fact]
public async Task GetWeeklyStatisticsAsync_有效日期范围_应返回周统计()
{
// Arrange
var startDate = new DateTime(2026, 2, 1);
var endDate = new DateTime(2026, 2, 7);
var weeklyStats = new Dictionary<string, (int count, decimal expense, decimal income, decimal saving)>
{
{ "2026-02-01", (2, 200m, 500m, 50m) },
{ "2026-02-07", (3, 300m, 800m, 100m) }
};
_configService.GetConfigByKeyAsync<string>("SavingsCategories").Returns("储蓄");
_statisticsService.GetDailyStatisticsByRangeAsync(startDate, endDate, "储蓄").Returns(weeklyStats);
// Act
var result = await _application.GetWeeklyStatisticsAsync(startDate, endDate);
// Assert
result.Should().HaveCount(2);
result.Should().Contain(s => s.Day == 1 && s.Income == 500m && s.Expense == 200m);
result.Should().Contain(s => s.Day == 7 && s.Income == 800m && s.Expense == 300m);
}
#endregion
#region GetRangeStatisticsAsync Tests
[Fact]
public async Task GetRangeStatisticsAsync_有效日期范围_应返回汇总统计()
{
// Arrange
var startDate = new DateTime(2026, 2, 1);
var endDate = new DateTime(2026, 2, 28);
var rangeStats = new Dictionary<string, (int count, decimal expense, decimal income, decimal saving)>
{
{ "2026-02-01", (3, 500m, 1000m, 0m) },
{ "2026-02-02", (4, 800m, 2000m, 0m) },
{ "2026-02-03", (2, 300m, 0m, 0m) }
};
_statisticsService.GetDailyStatisticsByRangeAsync(startDate, endDate, null).Returns(rangeStats);
// Act
var result = await _application.GetRangeStatisticsAsync(startDate, endDate);
// Assert
result.Year.Should().Be(2026);
result.Month.Should().Be(2);
result.TotalIncome.Should().Be(3000m);
result.TotalExpense.Should().Be(1600m);
result.Balance.Should().Be(1400m);
result.TotalCount.Should().Be(9);
result.ExpenseCount.Should().Be(3);
result.IncomeCount.Should().Be(2);
}
#endregion
#region GetMonthlyStatisticsAsync Tests
[Fact]
public async Task GetMonthlyStatisticsAsync_有效年月_应返回月度统计()
{
// Arrange
var year = 2026;
var month = 2;
var monthlyStats = new MonthlyStatistics
{
Year = year,
Month = month,
TotalIncome = 5000m,
TotalExpense = 3000m,
Balance = 2000m,
IncomeCount = 10,
ExpenseCount = 15,
TotalCount = 25
};
_statisticsService.GetMonthlyStatisticsAsync(year, month).Returns(monthlyStats);
// Act
var result = await _application.GetMonthlyStatisticsAsync(year, month);
// Assert
result.Should().NotBeNull();
result.Year.Should().Be(year);
result.Month.Should().Be(month);
result.TotalIncome.Should().Be(5000m);
result.TotalExpense.Should().Be(3000m);
result.Balance.Should().Be(2000m);
}
#endregion
#region GetCategoryStatisticsAsync Tests
[Fact]
public async Task GetCategoryStatisticsAsync_有效参数_应返回分类统计()
{
// Arrange
var year = 2026;
var month = 2;
var type = TransactionType.Expense;
var categoryStats = new List<CategoryStatistics>
{
new() { Classify = "餐饮", Amount = 1000m, Count = 10 },
new() { Classify = "交通", Amount = 500m, Count = 5 }
};
_statisticsService.GetCategoryStatisticsAsync(year, month, type).Returns(categoryStats);
// Act
var result = await _application.GetCategoryStatisticsAsync(year, month, type);
// Assert
result.Should().HaveCount(2);
result.Should().Contain(s => s.Classify == "餐饮" && s.Amount == 1000m);
result.Should().Contain(s => s.Classify == "交通" && s.Amount == 500m);
}
#endregion
#region GetCategoryStatisticsByDateRangeAsync Tests
[Fact]
public async Task GetCategoryStatisticsByDateRangeAsync_有效日期字符串_应返回分类统计()
{
// Arrange
var startDate = "2026-02-01";
var endDate = "2026-02-28";
var type = TransactionType.Expense;
var categoryStats = new List<CategoryStatistics>
{
new() { Classify = "餐饮", Amount = 1500m, Count = 15 }
};
_statisticsService.GetCategoryStatisticsByDateRangeAsync(
DateTime.Parse(startDate),
DateTime.Parse(endDate),
type
).Returns(categoryStats);
// Act
var result = await _application.GetCategoryStatisticsByDateRangeAsync(startDate, endDate, type);
// Assert
result.Should().HaveCount(1);
result[0].Classify.Should().Be("餐饮");
result[0].Amount.Should().Be(1500m);
}
[Fact]
public async Task GetCategoryStatisticsByDateRangeAsync_无效日期格式_应抛出异常()
{
// Arrange
var startDate = "invalid-date";
var endDate = "2026-02-28";
var type = TransactionType.Expense;
// Act & Assert
await Assert.ThrowsAsync<FormatException>(() =>
_application.GetCategoryStatisticsByDateRangeAsync(startDate, endDate, type));
}
#endregion
#region GetTrendStatisticsAsync Tests
[Fact]
public async Task GetTrendStatisticsAsync_有效参数_应返回趋势统计()
{
// Arrange
var startYear = 2026;
var startMonth = 1;
var monthCount = 3;
var trendStats = new List<TrendStatistics>
{
new() { Year = 2026, Month = 1, Income = 5000m, Expense = 3000m },
new() { Year = 2026, Month = 2, Income = 6000m, Expense = 3500m },
new() { Year = 2026, Month = 3, Income = 5500m, Expense = 3200m }
};
_statisticsService.GetTrendStatisticsAsync(startYear, startMonth, monthCount).Returns(trendStats);
// Act
var result = await _application.GetTrendStatisticsAsync(startYear, startMonth, monthCount);
// Assert
result.Should().HaveCount(3);
result[0].Year.Should().Be(2026);
result[0].Month.Should().Be(1);
}
#endregion
#region GetReasonGroupsAsync Tests
[Fact]
public async Task GetReasonGroupsAsync_有效分页参数_应返回分组数据()
{
// Arrange
var pageIndex = 1;
var pageSize = 10;
var reasonGroups = new List<ReasonGroupDto>
{
new() { Reason = "餐饮", Count = 20, TotalAmount = 1500m },
new() { Reason = "交通", Count = 15, TotalAmount = 800m }
};
var total = 50;
_statisticsService.GetReasonGroupsAsync(pageIndex, pageSize).Returns((reasonGroups, total));
// Act
var result = await _application.GetReasonGroupsAsync(pageIndex, pageSize);
// Assert
result.list.Should().HaveCount(2);
result.total.Should().Be(50);
result.list[0].Reason.Should().Be("餐饮");
}
#endregion
}