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

973 lines
30 KiB
C#
Raw Normal View History

2026-01-28 17:00:58 +08:00
using Service.Transaction;
namespace WebApi.Test.Transaction;
public class TransactionStatisticsServiceTest : BaseTest
{
private readonly ITransactionRecordRepository _transactionRepository = Substitute.For<ITransactionRecordRepository>();
private readonly ITransactionStatisticsService _service;
public TransactionStatisticsServiceTest()
{
// 默认配置 QueryAsync 返回空列表
_transactionRepository.QueryAsync(
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<DateTime>(),
Arg.Any<DateTime>(),
Arg.Any<TransactionType>(),
Arg.Any<string[]>(),
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<bool>())
.ReturnsForAnyArgs(new List<TransactionRecord>());
_service = new TransactionStatisticsService(
_transactionRepository
);
}
private void ConfigureQueryAsync(List<TransactionRecord> data)
{
_transactionRepository.QueryAsync(
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<DateTime>(),
Arg.Any<DateTime>(),
Arg.Any<TransactionType>(),
Arg.Any<string[]>(),
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<bool>())
.ReturnsForAnyArgs(data);
}
[Fact]
public async Task GetDailyStatisticsAsync_基本测试()
{
// Arrange
var year = 2024;
var month = 1;
var testData = new List<TransactionRecord>
{
new()
{
Id = 1,
OccurredAt = new DateTime(2024, 1, 1, 10, 0, 0),
Amount = -100m,
Type = TransactionType.Expense,
Classify = "餐饮",
Reason = "午餐"
},
new()
{
Id = 2,
OccurredAt = new DateTime(2024, 1, 1, 15, 0, 0),
Amount = -50m,
Type = TransactionType.Expense,
Classify = "交通",
Reason = "地铁"
},
new()
{
Id = 3,
OccurredAt = new DateTime(2024, 1, 2, 9, 0, 0),
Amount = 5000m,
Type = TransactionType.Income,
Classify = "工资",
Reason = "工资收入"
}
};
ConfigureQueryAsync(testData);
// Act
var result = await _service.GetDailyStatisticsAsync(year, month);
// Assert
result.Should().HaveCount(2);
result.Should().ContainKey("2024-01-01");
result.Should().ContainKey("2024-01-02");
result["2024-01-01"].count.Should().Be(2);
result["2024-01-01"].expense.Should().Be(150m);
result["2024-01-01"].income.Should().Be(0m);
result["2024-01-02"].count.Should().Be(1);
result["2024-01-02"].expense.Should().Be(0m);
result["2024-01-02"].income.Should().Be(5000m);
}
[Fact]
public async Task GetDailyStatisticsAsync_带储蓄分类()
{
// Arrange
var year = 2024;
var month = 1;
var savingClassify = "投资,存款";
var testData = new List<TransactionRecord>
{
new()
{
Id = 1,
OccurredAt = new DateTime(2024, 1, 1, 10, 0, 0),
Amount = -100m,
Type = TransactionType.Expense,
Classify = "餐饮",
Reason = "午餐"
},
new()
{
Id = 2,
OccurredAt = new DateTime(2024, 1, 1, 15, 0, 0),
Amount = -1000m,
Type = TransactionType.Expense,
Classify = "投资",
Reason = "基金定投"
},
new()
{
Id = 3,
OccurredAt = new DateTime(2024, 1, 2, 9, 0, 0),
Amount = -500m,
Type = TransactionType.Expense,
Classify = "存款",
Reason = "银行存款"
}
};
_transactionRepository.QueryAsync(
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<DateTime>(),
Arg.Any<DateTime>(),
Arg.Any<TransactionType>(),
Arg.Any<string[]>(),
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<bool>())
.ReturnsForAnyArgs(testData);
// Act
var result = await _service.GetDailyStatisticsAsync(year, month, savingClassify);
// Assert
result.Should().HaveCount(2);
result["2024-01-01"].count.Should().Be(2);
result["2024-01-01"].expense.Should().Be(1100m);
result["2024-01-01"].income.Should().Be(0m);
result["2024-01-01"].saving.Should().Be(1000m);
result["2024-01-02"].count.Should().Be(1);
result["2024-01-02"].expense.Should().Be(500m);
result["2024-01-02"].income.Should().Be(0m);
result["2024-01-02"].saving.Should().Be(500m);
}
[Fact]
public async Task GetDailyStatisticsByRangeAsync_基本测试()
{
// Arrange
var startDate = new DateTime(2024, 1, 1);
var endDate = new DateTime(2024, 1, 5);
var testData = new List<TransactionRecord>
{
new()
{
Id = 1,
OccurredAt = new DateTime(2024, 1, 3, 10, 0, 0),
Amount = -100m,
Type = TransactionType.Expense,
Classify = "餐饮",
Reason = "午餐"
}
};
_transactionRepository.QueryAsync(
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<DateTime>(),
Arg.Any<DateTime>(),
Arg.Any<TransactionType>(),
Arg.Any<string[]>(),
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<bool>())
.ReturnsForAnyArgs(testData);
// Act
var result = await _service.GetDailyStatisticsByRangeAsync(startDate, endDate);
// Assert
result.Should().HaveCount(1);
result.Should().ContainKey("2024-01-03");
result["2024-01-03"].count.Should().Be(1);
result["2024-01-03"].expense.Should().Be(100m);
}
[Fact]
public async Task GetMonthlyStatisticsAsync_基本测试()
{
// Arrange
var year = 2024;
var month = 1;
var testData = new List<TransactionRecord>
{
new()
{
Id = 1,
OccurredAt = new DateTime(2024, 1, 1, 10, 0, 0),
Amount = -100m,
Type = TransactionType.Expense,
Classify = "餐饮",
Reason = "午餐"
},
new()
{
Id = 2,
OccurredAt = new DateTime(2024, 1, 2, 15, 0, 0),
Amount = -50m,
Type = TransactionType.Expense,
Classify = "交通",
Reason = "地铁"
},
new()
{
Id = 3,
OccurredAt = new DateTime(2024, 1, 5, 9, 0, 0),
Amount = 5000m,
Type = TransactionType.Income,
Classify = "工资",
Reason = "工资收入"
},
new()
{
Id = 4,
OccurredAt = new DateTime(2024, 1, 10, 9, 0, 0),
Amount = 2000m,
Type = TransactionType.Income,
Classify = "奖金",
Reason = "奖金收入"
}
};
_transactionRepository.QueryAsync(
year,
month,
Arg.Any<DateTime>(),
Arg.Any<DateTime>(),
Arg.Any<TransactionType>(),
Arg.Any<string[]>(),
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<bool>())
.ReturnsForAnyArgs(testData);
// Act
var result = await _service.GetMonthlyStatisticsAsync(year, month);
// Assert
result.Year.Should().Be(year);
result.Month.Should().Be(month);
result.TotalExpense.Should().Be(150m);
result.TotalIncome.Should().Be(7000m);
result.Balance.Should().Be(6850m);
result.ExpenseCount.Should().Be(2);
result.IncomeCount.Should().Be(2);
result.TotalCount.Should().Be(4);
}
[Fact]
public async Task GetMonthlyStatisticsAsync_无数据()
{
// Arrange
var year = 2024;
var month = 2;
_transactionRepository.QueryAsync(
year,
month,
Arg.Any<DateTime>(),
Arg.Any<DateTime>(),
Arg.Any<TransactionType>(),
Arg.Any<string[]>(),
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<bool>())
.Returns(new List<TransactionRecord>());
// Act
var result = await _service.GetMonthlyStatisticsAsync(year, month);
// Assert
result.Year.Should().Be(year);
result.Month.Should().Be(month);
result.TotalExpense.Should().Be(0m);
result.TotalIncome.Should().Be(0m);
result.Balance.Should().Be(0m);
result.ExpenseCount.Should().Be(0);
result.IncomeCount.Should().Be(0);
result.TotalCount.Should().Be(0);
}
[Fact]
public async Task GetCategoryStatisticsAsync_支出分类()
{
// Arrange
var year = 2024;
var month = 1;
var type = TransactionType.Expense;
var testData = new List<TransactionRecord>
{
new()
{
Id = 1,
OccurredAt = new DateTime(2024, 1, 1, 10, 0, 0),
Amount = -100m,
Type = TransactionType.Expense,
Classify = "餐饮",
Reason = "午餐"
},
new()
{
Id = 2,
OccurredAt = new DateTime(2024, 1, 2, 15, 0, 0),
Amount = -50m,
Type = TransactionType.Expense,
Classify = "餐饮",
Reason = "晚餐"
},
new()
{
Id = 3,
OccurredAt = new DateTime(2024, 1, 3, 9, 0, 0),
Amount = -200m,
Type = TransactionType.Expense,
Classify = "交通",
Reason = "打车"
},
new()
{
Id = 4,
OccurredAt = new DateTime(2024, 1, 5, 9, 0, 0),
Amount = 5000m,
Type = TransactionType.Income,
Classify = "工资",
Reason = "工资收入"
}
};
_transactionRepository.QueryAsync(
year,
month,
Arg.Any<DateTime>(),
Arg.Any<DateTime>(),
type,
Arg.Any<string[]>(),
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<bool>())
.ReturnsForAnyArgs(testData);
// Act
var result = await _service.GetCategoryStatisticsAsync(year, month, type);
// Assert
result.Should().HaveCount(2);
var dining = result.First(c => c.Classify == "餐饮");
dining.Amount.Should().Be(150m);
dining.Count.Should().Be(2);
dining.Percent.Should().Be(42.9m);
var transport = result.First(c => c.Classify == "交通");
transport.Amount.Should().Be(200m);
transport.Count.Should().Be(1);
transport.Percent.Should().Be(57.1m);
}
[Fact]
public async Task GetCategoryStatisticsAsync_收入分类()
{
// Arrange
var year = 2024;
var month = 1;
var type = TransactionType.Income;
var testData = new List<TransactionRecord>
{
new()
{
Id = 1,
OccurredAt = new DateTime(2024, 1, 1, 10, 0, 0),
Amount = 5000m,
Type = TransactionType.Income,
Classify = "工资",
Reason = "工资收入"
},
new()
{
Id = 2,
OccurredAt = new DateTime(2024, 1, 2, 15, 0, 0),
Amount = 1000m,
Type = TransactionType.Income,
Classify = "奖金",
Reason = "绩效奖金"
},
new()
{
Id = 3,
OccurredAt = new DateTime(2024, 1, 3, 9, 0, 0),
Amount = -100m,
Type = TransactionType.Expense,
Classify = "餐饮",
Reason = "午餐"
}
};
_transactionRepository.QueryAsync(
year,
month,
Arg.Any<DateTime>(),
Arg.Any<DateTime>(),
type,
Arg.Any<string[]>(),
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<bool>())
.ReturnsForAnyArgs(testData);
// Act
var result = await _service.GetCategoryStatisticsAsync(year, month, type);
// Assert
result.Should().HaveCount(2);
var salary = result.First(c => c.Classify == "工资");
salary.Amount.Should().Be(5000m);
salary.Count.Should().Be(1);
salary.Percent.Should().Be(83.3m);
var bonus = result.First(c => c.Classify == "奖金");
bonus.Amount.Should().Be(1000m);
bonus.Count.Should().Be(1);
bonus.Percent.Should().Be(16.7m);
}
[Fact]
public async Task GetTrendStatisticsAsync_多个月份()
{
// Arrange
var startYear = 2024;
var startMonth = 1;
var monthCount = 3;
var mockData = new Dictionary<int, List<TransactionRecord>>
{
[1] = new List<TransactionRecord>
{
new() { Id = 1, OccurredAt = new DateTime(2024, 1, 1), Amount = -1000m, Type = TransactionType.Expense },
new() { Id = 2, OccurredAt = new DateTime(2024, 1, 5), Amount = 5000m, Type = TransactionType.Income }
},
[2] = new List<TransactionRecord>
{
new() { Id = 3, OccurredAt = new DateTime(2024, 2, 1), Amount = -1500m, Type = TransactionType.Expense },
new() { Id = 4, OccurredAt = new DateTime(2024, 2, 5), Amount = 5000m, Type = TransactionType.Income }
},
[3] = new List<TransactionRecord>
{
new() { Id = 5, OccurredAt = new DateTime(2024, 3, 1), Amount = -2000m, Type = TransactionType.Expense },
new() { Id = 6, OccurredAt = new DateTime(2024, 3, 5), Amount = 5000m, Type = TransactionType.Income }
}
};
_transactionRepository.QueryAsync(Arg.Any<int>(), Arg.Any<int>(), Arg.Any<DateTime>(), Arg.Any<DateTime>(), Arg.Any<TransactionType>(), Arg.Any<string[]>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<bool>())
.Returns(args =>
{
var month = (int)args[1];
if (mockData.ContainsKey(month))
{
return mockData[month];
}
return new List<TransactionRecord>();
});
// Act
var result = await _service.GetTrendStatisticsAsync(startYear, startMonth, monthCount);
// Assert
result.Should().HaveCount(3);
result[0].Year.Should().Be(2024);
result[0].Month.Should().Be(1);
result[0].Expense.Should().Be(1000m);
result[0].Income.Should().Be(5000m);
result[0].Balance.Should().Be(4000m);
result[1].Year.Should().Be(2024);
result[1].Month.Should().Be(2);
result[1].Expense.Should().Be(1500m);
result[1].Income.Should().Be(5000m);
result[1].Balance.Should().Be(3500m);
result[2].Year.Should().Be(2024);
result[2].Month.Should().Be(3);
result[2].Expense.Should().Be(2000m);
result[2].Income.Should().Be(5000m);
result[2].Balance.Should().Be(3000m);
}
[Fact]
public async Task GetTrendStatisticsAsync_跨年()
{
// Arrange
var startYear = 2024;
var startMonth = 11;
var monthCount = 4;
_transactionRepository.QueryAsync(Arg.Any<int>(), Arg.Any<int>(), Arg.Any<DateTime>(), Arg.Any<DateTime>(), Arg.Any<TransactionType>(), Arg.Any<string[]>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<int>(), Arg.Any<bool>())
.Returns(new List<TransactionRecord>());
// Act
var result = await _service.GetTrendStatisticsAsync(startYear, startMonth, monthCount);
// Assert
result.Should().HaveCount(4);
result[0].Year.Should().Be(2024);
result[0].Month.Should().Be(11);
result[1].Year.Should().Be(2024);
result[1].Month.Should().Be(12);
result[2].Year.Should().Be(2025);
result[2].Month.Should().Be(1);
result[3].Year.Should().Be(2025);
result[3].Month.Should().Be(2);
}
[Fact]
public async Task GetReasonGroupsAsync_基本测试()
{
// Arrange
var testData = new List<TransactionRecord>
{
new()
{
Id = 1,
Reason = "麦当劳",
Classify = "",
Amount = -50m,
Type = TransactionType.Expense,
OccurredAt = new DateTime(2024, 1, 1)
},
new()
{
Id = 2,
Reason = "麦当劳",
Classify = "",
Amount = -80m,
Type = TransactionType.Expense,
OccurredAt = new DateTime(2024, 1, 2)
},
new()
{
Id = 3,
Reason = "肯德基",
Classify = "",
Amount = -60m,
Type = TransactionType.Expense,
OccurredAt = new DateTime(2024, 1, 3)
},
new()
{
Id = 4,
Reason = "麦当劳",
Classify = "快餐",
Amount = -45m,
Type = TransactionType.Expense,
OccurredAt = new DateTime(2024, 1, 4)
}
};
_transactionRepository.QueryAsync(
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<DateTime>(),
Arg.Any<DateTime>(),
Arg.Any<TransactionType>(),
Arg.Any<string[]>(),
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<bool>())
.ReturnsForAnyArgs(testData);
// Act
var (list, total) = await _service.GetReasonGroupsAsync();
// Assert
total.Should().Be(2);
list.Should().HaveCount(2);
var mcdonalds = list.First(g => g.Reason == "麦当劳");
mcdonalds.Count.Should().Be(2);
mcdonalds.TotalAmount.Should().Be(130m);
mcdonalds.SampleType.Should().Be(TransactionType.Expense);
mcdonalds.SampleClassify.Should().Be("");
mcdonalds.TransactionIds.Should().Contain(1L);
mcdonalds.TransactionIds.Should().Contain(2L);
var kfc = list.First(g => g.Reason == "肯德基");
kfc.Count.Should().Be(1);
kfc.TotalAmount.Should().Be(60m);
}
[Fact]
public async Task GetClassifiedByKeywordsWithScoreAsync_基本匹配()
{
// Arrange
var keywords = new List<string> { "餐饮", "午餐" };
var testData = new List<TransactionRecord>
{
new()
{
Id = 1,
Reason = "今天午餐吃得很饱",
Classify = "餐饮",
OccurredAt = new DateTime(2024, 1, 1),
Amount = -50m
},
new()
{
Id = 2,
Reason = "餐饮支出",
Classify = "餐饮",
OccurredAt = new DateTime(2024, 1, 2),
Amount = -80m
},
new()
{
Id = 3,
Reason = "交通费",
Classify = "交通",
OccurredAt = new DateTime(2024, 1, 3),
Amount = -10m
}
};
_transactionRepository.GetClassifiedByKeywordsAsync(Arg.Any<List<string>>(), Arg.Any<int>())
.ReturnsForAnyArgs(testData);
// Act
var result = await _service.GetClassifiedByKeywordsWithScoreAsync(keywords, minMatchRate: 0.3, limit: 10);
// Assert
result.Should().HaveCount(2);
var first = result[0];
// 第一个结果应该是相关性分数最高的,可能是 Id=2"今天午餐吃得很饱"匹配两个关键词)
first.record.Id.Should().BeOneOf(1L, 2L);
first.relevanceScore.Should().BeGreaterThan(0.5);
var second = result[1];
second.record.Id.Should().BeOneOf(1L, 2L);
second.record.Id.Should().NotBe(first.record.Id);
second.relevanceScore.Should().BeGreaterThan(0.3);
}
[Fact]
public async Task GetClassifiedByKeywordsWithScoreAsync_精确匹配加分()
{
// Arrange
var keywords = new List<string> { "午餐" };
var testData = new List<TransactionRecord>
{
new()
{
Id = 1,
Reason = "午餐",
Classify = "餐饮",
OccurredAt = new DateTime(2024, 1, 1),
Amount = -50m
},
new()
{
Id = 2,
Reason = "今天中午吃了一顿午餐",
Classify = "餐饮",
OccurredAt = new DateTime(2024, 1, 2),
Amount = -80m
}
};
_transactionRepository.GetClassifiedByKeywordsAsync(Arg.Any<List<string>>(), Arg.Any<int>())
.ReturnsForAnyArgs(testData);
// Act
var result = await _service.GetClassifiedByKeywordsWithScoreAsync(keywords, minMatchRate: 0.3, limit: 10);
// Assert
result.Should().HaveCount(2);
// 精确匹配应该得分更高
result[0].record.Id.Should().Be(1);
result[0].relevanceScore.Should().BeGreaterThan(result[1].relevanceScore);
}
[Fact]
public async Task GetFilteredTrendStatisticsAsync_按日分组()
{
// Arrange
var startDate = new DateTime(2024, 1, 1);
var endDate = new DateTime(2024, 1, 5);
var type = TransactionType.Expense;
var classifies = new[] { "餐饮", "交通" };
var testData = new List<TransactionRecord>
{
new()
{
Id = 1,
OccurredAt = new DateTime(2024, 1, 1, 10, 0, 0),
Amount = -100m,
Type = TransactionType.Expense,
Classify = "餐饮"
},
new()
{
Id = 2,
OccurredAt = new DateTime(2024, 1, 1, 15, 0, 0),
Amount = -50m,
Type = TransactionType.Expense,
Classify = "交通"
},
new()
{
Id = 3,
OccurredAt = new DateTime(2024, 1, 2, 10, 0, 0),
Amount = -80m,
Type = TransactionType.Expense,
Classify = "餐饮"
}
};
_transactionRepository.QueryAsync(
Arg.Any<int>(),
Arg.Any<int>(),
startDate,
endDate,
type,
classifies,
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<bool>())
.ReturnsForAnyArgs(testData);
// Act
var result = await _service.GetFilteredTrendStatisticsAsync(startDate, endDate, type, classifies, groupByMonth: false);
// Assert
result.Should().HaveCount(2);
result.Should().ContainKey(new DateTime(2024, 1, 1));
result.Should().ContainKey(new DateTime(2024, 1, 2));
result[new DateTime(2024, 1, 1)].Should().Be(150m);
result[new DateTime(2024, 1, 2)].Should().Be(80m);
}
[Fact]
public async Task GetFilteredTrendStatisticsAsync_按月分组()
{
// Arrange
var startDate = new DateTime(2024, 1, 1);
var endDate = new DateTime(2024, 3, 31);
var type = TransactionType.Expense;
var classifies = new[] { "餐饮" };
var testData = new List<TransactionRecord>
{
new()
{
Id = 1,
OccurredAt = new DateTime(2024, 1, 15),
Amount = -1000m,
Type = TransactionType.Expense,
Classify = "餐饮"
},
new()
{
Id = 2,
OccurredAt = new DateTime(2024, 2, 15),
Amount = -1500m,
Type = TransactionType.Expense,
Classify = "餐饮"
},
new()
{
Id = 3,
OccurredAt = new DateTime(2024, 3, 15),
Amount = -2000m,
Type = TransactionType.Expense,
Classify = "餐饮"
}
};
_transactionRepository.QueryAsync(
Arg.Any<int>(),
Arg.Any<int>(),
startDate,
endDate,
type,
classifies,
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<bool>())
.ReturnsForAnyArgs(testData);
// Act
var result = await _service.GetFilteredTrendStatisticsAsync(startDate, endDate, type, classifies, groupByMonth: true);
// Assert
result.Should().HaveCount(3);
result.Should().ContainKey(new DateTime(2024, 1, 1));
result.Should().ContainKey(new DateTime(2024, 2, 1));
result.Should().ContainKey(new DateTime(2024, 3, 1));
result[new DateTime(2024, 1, 1)].Should().Be(1000m);
result[new DateTime(2024, 2, 1)].Should().Be(1500m);
result[new DateTime(2024, 3, 1)].Should().Be(2000m);
}
[Fact]
public async Task GetAmountGroupByClassifyAsync_基本测试()
{
// Arrange
var startTime = new DateTime(2024, 1, 1);
var endTime = new DateTime(2024, 1, 31);
var testData = new List<TransactionRecord>
{
new()
{
Id = 1,
Amount = -100m,
Type = TransactionType.Expense,
Classify = "餐饮"
},
new()
{
Id = 2,
Amount = -50m,
Type = TransactionType.Expense,
Classify = "餐饮"
},
new()
{
Id = 3,
Amount = 5000m,
Type = TransactionType.Income,
Classify = "工资"
},
new()
{
Id = 4,
Amount = -200m,
Type = TransactionType.Expense,
Classify = "交通"
}
};
_transactionRepository.QueryAsync(
Arg.Any<int>(),
Arg.Any<int>(),
startTime,
endTime,
Arg.Any<TransactionType>(),
Arg.Any<string[]>(),
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<bool>())
.ReturnsForAnyArgs(testData);
// Act
var result = await _service.GetAmountGroupByClassifyAsync(startTime, endTime);
// Assert
result.Should().HaveCount(3);
result[("餐饮", TransactionType.Expense)].Should().Be(-150m);
result[("工资", TransactionType.Income)].Should().Be(5000m);
result[("交通", TransactionType.Expense)].Should().Be(-200m);
}
[Fact]
public async Task GetAmountGroupByClassifyAsync_相同分类不同类型()
{
// Arrange
var startTime = new DateTime(2024, 1, 1);
var endTime = new DateTime(2024, 1, 31);
var testData = new List<TransactionRecord>
{
new()
{
Id = 1,
Amount = -100m,
Type = TransactionType.Expense,
Classify = "兼职"
},
new()
{
Id = 2,
Amount = 500m,
Type = TransactionType.Income,
Classify = "兼职"
}
};
_transactionRepository.QueryAsync(
Arg.Any<int>(),
Arg.Any<int>(),
startTime,
endTime,
Arg.Any<TransactionType>(),
Arg.Any<string[]>(),
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<int>(),
Arg.Any<int>(),
Arg.Any<bool>())
.ReturnsForAnyArgs(testData);
// Act
var result = await _service.GetAmountGroupByClassifyAsync(startTime, endTime);
// Assert
result.Should().HaveCount(2);
result[("兼职", TransactionType.Expense)].Should().Be(-100m);
result[("兼职", TransactionType.Income)].Should().Be(500m);
}
}