- 添加 BudgetItemCalculator 辅助类,实现明细项计算规则 - 收入:实际>0取实际,否则取预算 - 支出:取MAX(预算, 实际) - 硬性支出未发生:按天数折算 - 归档数据:直接使用实际值 - 实现月度和年度存款核心公式 - 月度:收入预算 + 本月年度收入 - 支出预算 - 本月年度支出 - 年度:归档已实收 + 未来收入预算 - 归档已实支 - 未来支出预算 - 定义存款明细数据结构 - SavingsDetail: 包含收入/支出明细列表和汇总 - BudgetDetailItem: 预算明细项(含计算用金额、计算说明等) - SavingsCalculationSummary: 计算汇总信息 - 新增单元测试 - BudgetItemCalculatorTest: 11个测试覆盖所有计算规则 - BudgetSavingsCalculationTest: 6个测试验证核心公式 测试结果:所有测试通过 (366 passed, 0 failed)
261 lines
6.7 KiB
C#
261 lines
6.7 KiB
C#
using Service.Budget;
|
||
|
||
namespace WebApi.Test.Budget;
|
||
|
||
/// <summary>
|
||
/// BudgetItemCalculator 单元测试
|
||
/// 测试明细项计算用金额的各种规则
|
||
/// </summary>
|
||
public class BudgetItemCalculatorTest : BaseTest
|
||
{
|
||
[Fact]
|
||
public void 收入项实际已发生_应返回实际值()
|
||
{
|
||
// Arrange
|
||
var budgetLimit = 10000m;
|
||
var actualAmount = 9500m;
|
||
|
||
// Act
|
||
var result = BudgetItemCalculator.CalculateEffectiveAmount(
|
||
BudgetCategory.Income,
|
||
budgetLimit,
|
||
actualAmount,
|
||
isMandatory: false,
|
||
isArchived: false,
|
||
new DateTime(2026, 2, 15),
|
||
BudgetPeriodType.Month
|
||
);
|
||
|
||
// Assert
|
||
result.Should().Be(9500m);
|
||
}
|
||
|
||
[Fact]
|
||
public void 收入项实际未发生_应返回预算值()
|
||
{
|
||
// Arrange
|
||
var budgetLimit = 5000m;
|
||
var actualAmount = 0m;
|
||
|
||
// Act
|
||
var result = BudgetItemCalculator.CalculateEffectiveAmount(
|
||
BudgetCategory.Income,
|
||
budgetLimit,
|
||
actualAmount,
|
||
isMandatory: false,
|
||
isArchived: false,
|
||
new DateTime(2026, 2, 15),
|
||
BudgetPeriodType.Month
|
||
);
|
||
|
||
// Assert
|
||
result.Should().Be(5000m);
|
||
}
|
||
|
||
[Fact]
|
||
public void 支出项普通情况_应返回MAX预算和实际()
|
||
{
|
||
// Arrange
|
||
var budgetLimit = 2000m;
|
||
var actualAmount = 2500m;
|
||
|
||
// Act
|
||
var result = BudgetItemCalculator.CalculateEffectiveAmount(
|
||
BudgetCategory.Expense,
|
||
budgetLimit,
|
||
actualAmount,
|
||
isMandatory: false,
|
||
isArchived: false,
|
||
new DateTime(2026, 2, 15),
|
||
BudgetPeriodType.Month
|
||
);
|
||
|
||
// Assert
|
||
result.Should().Be(2500m);
|
||
}
|
||
|
||
[Fact]
|
||
public void 支出项未超预算_应返回预算值()
|
||
{
|
||
// Arrange
|
||
var budgetLimit = 2000m;
|
||
var actualAmount = 1800m;
|
||
|
||
// Act
|
||
var result = BudgetItemCalculator.CalculateEffectiveAmount(
|
||
BudgetCategory.Expense,
|
||
budgetLimit,
|
||
actualAmount,
|
||
isMandatory: false,
|
||
isArchived: false,
|
||
new DateTime(2026, 2, 15),
|
||
BudgetPeriodType.Month
|
||
);
|
||
|
||
// Assert
|
||
result.Should().Be(2000m);
|
||
}
|
||
|
||
[Fact]
|
||
public void 支出项超预算_应返回实际值()
|
||
{
|
||
// Arrange
|
||
var budgetLimit = 2000m;
|
||
var actualAmount = 2500m;
|
||
|
||
// Act
|
||
var result = BudgetItemCalculator.CalculateEffectiveAmount(
|
||
BudgetCategory.Expense,
|
||
budgetLimit,
|
||
actualAmount,
|
||
isMandatory: false,
|
||
isArchived: false,
|
||
new DateTime(2026, 2, 15),
|
||
BudgetPeriodType.Month
|
||
);
|
||
|
||
// Assert
|
||
result.Should().Be(2500m);
|
||
}
|
||
|
||
[Fact]
|
||
public void 支出项硬性且实际为0_月度_应按天数折算()
|
||
{
|
||
// Arrange
|
||
var budgetLimit = 3000m;
|
||
var actualAmount = 0m;
|
||
var date = new DateTime(2026, 2, 15); // 2月共28天,当前15号
|
||
|
||
// Act
|
||
var result = BudgetItemCalculator.CalculateEffectiveAmount(
|
||
BudgetCategory.Expense,
|
||
budgetLimit,
|
||
actualAmount,
|
||
isMandatory: true,
|
||
isArchived: false,
|
||
date,
|
||
BudgetPeriodType.Month
|
||
);
|
||
|
||
// Assert
|
||
var expected = 3000m / 28 * 15; // ≈ 1607.14
|
||
result.Should().BeApproximately(expected, 0.01m);
|
||
}
|
||
|
||
[Fact]
|
||
public void 支出项硬性且实际为0_年度_应按天数折算()
|
||
{
|
||
// Arrange
|
||
var budgetLimit = 12000m;
|
||
var actualAmount = 0m;
|
||
var date = new DateTime(2026, 2, 15); // 2026年第46天(31+15)
|
||
|
||
// Act
|
||
var result = BudgetItemCalculator.CalculateEffectiveAmount(
|
||
BudgetCategory.Expense,
|
||
budgetLimit,
|
||
actualAmount,
|
||
isMandatory: true,
|
||
isArchived: false,
|
||
date,
|
||
BudgetPeriodType.Year
|
||
);
|
||
|
||
// Assert
|
||
var expected = 12000m / 365 * 46; // ≈ 1512.33
|
||
result.Should().BeApproximately(expected, 0.01m);
|
||
}
|
||
|
||
[Fact]
|
||
public void 支出项硬性且实际大于0_应返回MAX值()
|
||
{
|
||
// Arrange
|
||
var budgetLimit = 3000m;
|
||
var actualAmount = 3200m;
|
||
|
||
// Act
|
||
var result = BudgetItemCalculator.CalculateEffectiveAmount(
|
||
BudgetCategory.Expense,
|
||
budgetLimit,
|
||
actualAmount,
|
||
isMandatory: true,
|
||
isArchived: false,
|
||
new DateTime(2026, 2, 15),
|
||
BudgetPeriodType.Month
|
||
);
|
||
|
||
// Assert
|
||
result.Should().Be(3200m);
|
||
}
|
||
|
||
[Fact]
|
||
public void 归档数据_应直接返回实际值()
|
||
{
|
||
// Arrange
|
||
var budgetLimit = 2000m;
|
||
var actualAmount = 1800m;
|
||
|
||
// Act
|
||
var result = BudgetItemCalculator.CalculateEffectiveAmount(
|
||
BudgetCategory.Expense,
|
||
budgetLimit,
|
||
actualAmount,
|
||
isMandatory: false,
|
||
isArchived: true, // 归档数据
|
||
new DateTime(2026, 2, 15),
|
||
BudgetPeriodType.Month
|
||
);
|
||
|
||
// Assert
|
||
result.Should().Be(1800m); // 归档数据直接返回实际值,不走MAX逻辑
|
||
}
|
||
|
||
[Fact]
|
||
public void 闰年2月按天折算边界情况()
|
||
{
|
||
// Arrange
|
||
var budgetLimit = 3000m;
|
||
var actualAmount = 0m;
|
||
var date = new DateTime(2024, 2, 29); // 闰年2月29日
|
||
|
||
// Act
|
||
var result = BudgetItemCalculator.CalculateEffectiveAmount(
|
||
BudgetCategory.Expense,
|
||
budgetLimit,
|
||
actualAmount,
|
||
isMandatory: true,
|
||
isArchived: false,
|
||
date,
|
||
BudgetPeriodType.Month
|
||
);
|
||
|
||
// Assert
|
||
var expected = 3000m / 29 * 29; // = 3000
|
||
result.Should().Be(expected);
|
||
}
|
||
|
||
[Fact]
|
||
public void 平年2月按天折算边界情况()
|
||
{
|
||
// Arrange
|
||
var budgetLimit = 3000m;
|
||
var actualAmount = 0m;
|
||
var date = new DateTime(2026, 2, 28); // 平年2月28日
|
||
|
||
// Act
|
||
var result = BudgetItemCalculator.CalculateEffectiveAmount(
|
||
BudgetCategory.Expense,
|
||
budgetLimit,
|
||
actualAmount,
|
||
isMandatory: true,
|
||
isArchived: false,
|
||
date,
|
||
BudgetPeriodType.Month
|
||
);
|
||
|
||
// Assert
|
||
var expected = 3000m / 28 * 28; // = 3000
|
||
result.Should().Be(expected);
|
||
}
|
||
}
|