All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 4m27s
Docker Build & Deploy / Deploy to Production (push) Successful in 7s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
771 lines
22 KiB
C#
771 lines
22 KiB
C#
using Application.Dto.Transaction;
|
|
using Service.AI;
|
|
|
|
namespace WebApi.Test.Application;
|
|
|
|
/// <summary>
|
|
/// TransactionApplication 单元测试
|
|
/// </summary>
|
|
public class TransactionApplicationTest : BaseApplicationTest
|
|
{
|
|
private readonly ITransactionRecordRepository _transactionRepository;
|
|
private readonly ISmartHandleService _smartHandleService;
|
|
private readonly TransactionApplication _application;
|
|
|
|
public TransactionApplicationTest()
|
|
{
|
|
_transactionRepository = Substitute.For<ITransactionRecordRepository>();
|
|
_smartHandleService = Substitute.For<ISmartHandleService>();
|
|
_application = new TransactionApplication(_transactionRepository, _smartHandleService);
|
|
}
|
|
|
|
#region GetByIdAsync Tests
|
|
|
|
[Fact]
|
|
public async Task GetByIdAsync_存在的记录_应返回交易详情()
|
|
{
|
|
// Arrange
|
|
var record = new TransactionRecord
|
|
{
|
|
Id = 1,
|
|
Reason = "测试交易",
|
|
Amount = 100,
|
|
Type = TransactionType.Expense
|
|
};
|
|
_transactionRepository.GetByIdAsync(1).Returns(record);
|
|
|
|
// Act
|
|
var result = await _application.GetByIdAsync(1);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result.Id.Should().Be(1);
|
|
result.Reason.Should().Be("测试交易");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetByIdAsync_不存在的记录_应抛出NotFoundException()
|
|
{
|
|
// Arrange
|
|
_transactionRepository.GetByIdAsync(999).Returns((TransactionRecord?)null);
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<NotFoundException>(() => _application.GetByIdAsync(999));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CreateAsync Tests
|
|
|
|
[Fact]
|
|
public async Task CreateAsync_有效请求_应成功创建()
|
|
{
|
|
// Arrange
|
|
var request = new CreateTransactionRequest
|
|
{
|
|
OccurredAt = "2026-02-10",
|
|
Reason = "测试支出",
|
|
Amount = 100,
|
|
Type = TransactionType.Expense,
|
|
Classify = "餐饮"
|
|
};
|
|
_transactionRepository.AddAsync(Arg.Any<TransactionRecord>()).Returns(true);
|
|
|
|
// Act
|
|
await _application.CreateAsync(request);
|
|
|
|
// Assert
|
|
await _transactionRepository.Received(1).AddAsync(Arg.Is<TransactionRecord>(
|
|
t => t.Reason == "测试支出" && t.Amount == 100
|
|
));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateAsync_无效日期格式_应抛出ValidationException()
|
|
{
|
|
// Arrange
|
|
var request = new CreateTransactionRequest
|
|
{
|
|
OccurredAt = "invalid-date",
|
|
Reason = "测试",
|
|
Amount = 100,
|
|
Type = TransactionType.Expense
|
|
};
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() => _application.CreateAsync(request));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateAsync_Repository添加失败_应抛出BusinessException()
|
|
{
|
|
// Arrange
|
|
var request = new CreateTransactionRequest
|
|
{
|
|
OccurredAt = "2026-02-10",
|
|
Reason = "测试",
|
|
Amount = 100,
|
|
Type = TransactionType.Expense
|
|
};
|
|
_transactionRepository.AddAsync(Arg.Any<TransactionRecord>()).Returns(false);
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<BusinessException>(() => _application.CreateAsync(request));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region UpdateAsync Tests
|
|
|
|
[Fact]
|
|
public async Task UpdateAsync_有效请求_应成功更新()
|
|
{
|
|
// Arrange
|
|
var existingRecord = new TransactionRecord
|
|
{
|
|
Id = 1,
|
|
Reason = "旧原因",
|
|
Amount = 50
|
|
};
|
|
_transactionRepository.GetByIdAsync(1).Returns(existingRecord);
|
|
_transactionRepository.UpdateAsync(Arg.Any<TransactionRecord>()).Returns(true);
|
|
|
|
var request = new UpdateTransactionRequest
|
|
{
|
|
Id = 1,
|
|
Reason = "新原因",
|
|
Amount = 100,
|
|
Balance = 0,
|
|
Type = TransactionType.Expense,
|
|
Classify = "餐饮"
|
|
};
|
|
|
|
// Act
|
|
await _application.UpdateAsync(request);
|
|
|
|
// Assert
|
|
await _transactionRepository.Received(1).UpdateAsync(Arg.Is<TransactionRecord>(
|
|
t => t.Id == 1 && t.Reason == "新原因" && t.Amount == 100
|
|
));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateAsync_记录不存在_应抛出NotFoundException()
|
|
{
|
|
// Arrange
|
|
_transactionRepository.GetByIdAsync(999).Returns((TransactionRecord?)null);
|
|
|
|
var request = new UpdateTransactionRequest
|
|
{
|
|
Id = 999,
|
|
Amount = 100,
|
|
Balance = 0,
|
|
Type = TransactionType.Expense
|
|
};
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<NotFoundException>(() => _application.UpdateAsync(request));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region DeleteByIdAsync Tests
|
|
|
|
[Fact]
|
|
public async Task DeleteByIdAsync_成功删除_不应抛出异常()
|
|
{
|
|
// Arrange
|
|
_transactionRepository.DeleteAsync(1).Returns(true);
|
|
|
|
// Act
|
|
await _application.DeleteByIdAsync(1);
|
|
|
|
// Assert
|
|
await _transactionRepository.Received(1).DeleteAsync(1);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DeleteByIdAsync_删除失败_应抛出BusinessException()
|
|
{
|
|
// Arrange
|
|
_transactionRepository.DeleteAsync(999).Returns(false);
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<BusinessException>(() => _application.DeleteByIdAsync(999));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetListAsync Tests
|
|
|
|
[Fact]
|
|
public async Task GetListAsync_基本查询_应返回分页结果()
|
|
{
|
|
// Arrange
|
|
var request = new TransactionQueryRequest
|
|
{
|
|
PageIndex = 1,
|
|
PageSize = 10
|
|
};
|
|
var transactions = new List<TransactionRecord>
|
|
{
|
|
new() { Id = 1, Reason = "测试1", Amount = 100, Type = TransactionType.Expense },
|
|
new() { Id = 2, Reason = "测试2", Amount = 200, Type = TransactionType.Income }
|
|
};
|
|
_transactionRepository.QueryAsync(
|
|
pageIndex: 1, pageSize: 10).Returns(transactions);
|
|
_transactionRepository.CountAsync().Returns(2);
|
|
|
|
// Act
|
|
var result = await _application.GetListAsync(request);
|
|
|
|
// Assert
|
|
result.Data.Should().HaveCount(2);
|
|
result.Total.Should().Be(2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetListAsync_按分类筛选_应返回过滤结果()
|
|
{
|
|
// Arrange
|
|
var request = new TransactionQueryRequest
|
|
{
|
|
Classify = "餐饮,交通",
|
|
PageIndex = 1,
|
|
PageSize = 10
|
|
};
|
|
var transactions = new List<TransactionRecord>
|
|
{
|
|
new() { Id = 1, Reason = "午餐", Amount = 50, Type = TransactionType.Expense, Classify = "餐饮" }
|
|
};
|
|
_transactionRepository.QueryAsync(
|
|
classifies: Arg.Is<string[]>(c => c != null && c.Contains("餐饮")),
|
|
pageIndex: 1,
|
|
pageSize: 10).Returns(transactions);
|
|
_transactionRepository.CountAsync(
|
|
classifies: Arg.Is<string[]>(c => c != null && c.Contains("餐饮"))).Returns(1);
|
|
|
|
// Act
|
|
var result = await _application.GetListAsync(request);
|
|
|
|
// Assert
|
|
result.Data.Should().HaveCount(1);
|
|
result.Data[0].Classify.Should().Be("餐饮");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetListAsync_按类型筛选_应返回对应类型()
|
|
{
|
|
// Arrange
|
|
var request = new TransactionQueryRequest
|
|
{
|
|
Type = (int)TransactionType.Expense,
|
|
PageIndex = 1,
|
|
PageSize = 10
|
|
};
|
|
var transactions = new List<TransactionRecord>
|
|
{
|
|
new() { Id = 1, Amount = 100, Type = TransactionType.Expense }
|
|
};
|
|
_transactionRepository.QueryAsync(
|
|
type: TransactionType.Expense,
|
|
pageIndex: 1,
|
|
pageSize: 10).Returns(transactions);
|
|
_transactionRepository.CountAsync(
|
|
type: TransactionType.Expense).Returns(1);
|
|
|
|
// Act
|
|
var result = await _application.GetListAsync(request);
|
|
|
|
// Assert
|
|
result.Data.Should().HaveCount(1);
|
|
result.Data[0].Type.Should().Be(TransactionType.Expense);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetByEmailIdAsync Tests
|
|
|
|
[Fact]
|
|
public async Task GetByEmailIdAsync_有关联记录_应返回列表()
|
|
{
|
|
// Arrange
|
|
var emailId = 100L;
|
|
var transactions = new List<TransactionRecord>
|
|
{
|
|
new() { Id = 1, EmailMessageId = emailId, Amount = 100 },
|
|
new() { Id = 2, EmailMessageId = emailId, Amount = 200 }
|
|
};
|
|
_transactionRepository.GetByEmailIdAsync(emailId).Returns(transactions);
|
|
|
|
// Act
|
|
var result = await _application.GetByEmailIdAsync(emailId);
|
|
|
|
// Assert
|
|
result.Should().HaveCount(2);
|
|
await _transactionRepository.Received(1).GetByEmailIdAsync(emailId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetByEmailIdAsync_无关联记录_应返回空列表()
|
|
{
|
|
// Arrange
|
|
_transactionRepository.GetByEmailIdAsync(999).Returns(new List<TransactionRecord>());
|
|
|
|
// Act
|
|
var result = await _application.GetByEmailIdAsync(999);
|
|
|
|
// Assert
|
|
result.Should().BeEmpty();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetByDateAsync Tests
|
|
|
|
[Fact]
|
|
public async Task GetByDateAsync_指定日期_应返回当天记录()
|
|
{
|
|
// Arrange
|
|
var date = new DateTime(2026, 2, 10);
|
|
var expectedStart = date.Date;
|
|
var expectedEnd = expectedStart.AddDays(1);
|
|
var transactions = new List<TransactionRecord>
|
|
{
|
|
new() { Id = 1, OccurredAt = date, Amount = 100 }
|
|
};
|
|
_transactionRepository.QueryAsync(
|
|
startDate: expectedStart,
|
|
endDate: expectedEnd).Returns(transactions);
|
|
|
|
// Act
|
|
var result = await _application.GetByDateAsync(date);
|
|
|
|
// Assert
|
|
result.Should().HaveCount(1);
|
|
result[0].OccurredAt.Date.Should().Be(date.Date);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetUnconfirmedListAsync and GetUnconfirmedCountAsync Tests
|
|
|
|
[Fact]
|
|
public async Task GetUnconfirmedListAsync_有未确认记录_应返回列表()
|
|
{
|
|
// Arrange
|
|
var unconfirmedRecords = new List<TransactionRecord>
|
|
{
|
|
new() { Id = 1, Amount = 100, UnconfirmedClassify = "待确认分类" },
|
|
new() { Id = 2, Amount = 200, UnconfirmedType = TransactionType.Expense }
|
|
};
|
|
_transactionRepository.GetUnconfirmedRecordsAsync().Returns(unconfirmedRecords);
|
|
|
|
// Act
|
|
var result = await _application.GetUnconfirmedListAsync();
|
|
|
|
// Assert
|
|
result.Should().HaveCount(2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetUnconfirmedCountAsync_应返回未确认记录数量()
|
|
{
|
|
// Arrange
|
|
var unconfirmedRecords = new List<TransactionRecord>
|
|
{
|
|
new() { Id = 1, UnconfirmedClassify = "待确认" },
|
|
new() { Id = 2, UnconfirmedClassify = "待确认" }
|
|
};
|
|
_transactionRepository.GetUnconfirmedRecordsAsync().Returns(unconfirmedRecords);
|
|
|
|
// Act
|
|
var result = await _application.GetUnconfirmedCountAsync();
|
|
|
|
// Assert
|
|
result.Should().Be(2);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region GetUnclassifiedCountAsync and GetUnclassifiedAsync Tests
|
|
|
|
[Fact]
|
|
public async Task GetUnclassifiedCountAsync_应返回未分类数量()
|
|
{
|
|
// Arrange
|
|
_transactionRepository.CountAsync().Returns(5);
|
|
|
|
// Act
|
|
var result = await _application.GetUnclassifiedCountAsync();
|
|
|
|
// Assert
|
|
result.Should().Be(5);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetUnclassifiedAsync_指定页大小_应返回未分类记录()
|
|
{
|
|
// Arrange
|
|
var pageSize = 10;
|
|
var unclassifiedRecords = new List<TransactionRecord>
|
|
{
|
|
new() { Id = 1, Amount = 100, Classify = string.Empty },
|
|
new() { Id = 2, Amount = 200, Classify = string.Empty }
|
|
};
|
|
_transactionRepository.GetUnclassifiedAsync(pageSize).Returns(unclassifiedRecords);
|
|
|
|
// Act
|
|
var result = await _application.GetUnclassifiedAsync(pageSize);
|
|
|
|
// Assert
|
|
result.Should().HaveCount(2);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ConfirmAllUnconfirmedAsync Tests
|
|
|
|
[Fact]
|
|
public async Task ConfirmAllUnconfirmedAsync_有效ID列表_应返回确认数量()
|
|
{
|
|
// Arrange
|
|
var ids = new long[] { 1, 2, 3 };
|
|
_transactionRepository.ConfirmAllUnconfirmedAsync(ids).Returns(3);
|
|
|
|
// Act
|
|
var result = await _application.ConfirmAllUnconfirmedAsync(ids);
|
|
|
|
// Assert
|
|
result.Should().Be(3);
|
|
await _transactionRepository.Received(1).ConfirmAllUnconfirmedAsync(ids);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ConfirmAllUnconfirmedAsync_空ID列表_应抛出ValidationException()
|
|
{
|
|
// Arrange
|
|
var emptyIds = Array.Empty<long>();
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() =>
|
|
_application.ConfirmAllUnconfirmedAsync(emptyIds));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ConfirmAllUnconfirmedAsync_NullID列表_应抛出ValidationException()
|
|
{
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() =>
|
|
_application.ConfirmAllUnconfirmedAsync(null!));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region SmartClassifyAsync Tests
|
|
|
|
[Fact]
|
|
public async Task SmartClassifyAsync_有效ID列表_应调用Service()
|
|
{
|
|
// Arrange
|
|
var ids = new long[] { 1, 2 };
|
|
var chunkReceived = false;
|
|
Action<(string, string)> onChunk = chunk => { chunkReceived = true; };
|
|
|
|
// Act
|
|
await _application.SmartClassifyAsync(ids, onChunk);
|
|
|
|
// Assert
|
|
await _smartHandleService.Received(1).SmartClassifyAsync(ids, onChunk);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SmartClassifyAsync_空ID列表_应抛出ValidationException()
|
|
{
|
|
// Arrange
|
|
var emptyIds = Array.Empty<long>();
|
|
Action<(string, string)> onChunk = _ => { };
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() =>
|
|
_application.SmartClassifyAsync(emptyIds, onChunk));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SmartClassifyAsync_NullID列表_应抛出ValidationException()
|
|
{
|
|
// Arrange
|
|
Action<(string, string)> onChunk = _ => { };
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() =>
|
|
_application.SmartClassifyAsync(null!, onChunk));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ParseOneLineAsync Tests
|
|
|
|
[Fact]
|
|
public async Task ParseOneLineAsync_有效文本_应返回解析结果()
|
|
{
|
|
// Arrange
|
|
var text = "午餐花了50块";
|
|
var parseResult = new TransactionParseResult(
|
|
OccurredAt: DateTime.Now.ToString("yyyy-MM-dd"),
|
|
Classify: "餐饮",
|
|
Amount: 50,
|
|
Reason: "午餐",
|
|
Type: TransactionType.Expense
|
|
);
|
|
_smartHandleService.ParseOneLineBillAsync(text).Returns(parseResult);
|
|
|
|
// Act
|
|
var result = await _application.ParseOneLineAsync(text);
|
|
|
|
// Assert
|
|
result.Should().NotBeNull();
|
|
result!.Amount.Should().Be(50);
|
|
result.Classify.Should().Be("餐饮");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ParseOneLineAsync_空文本_应抛出ValidationException()
|
|
{
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() =>
|
|
_application.ParseOneLineAsync(string.Empty));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ParseOneLineAsync_空白文本_应抛出ValidationException()
|
|
{
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() =>
|
|
_application.ParseOneLineAsync(" "));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ParseOneLineAsync_解析失败返回null_应抛出BusinessException()
|
|
{
|
|
// Arrange
|
|
_smartHandleService.ParseOneLineBillAsync(Arg.Any<string>()).Returns((TransactionParseResult?)null);
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<BusinessException>(() =>
|
|
_application.ParseOneLineAsync("测试文本"));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region AnalyzeBillAsync Tests
|
|
|
|
[Fact]
|
|
public async Task AnalyzeBillAsync_有效输入_应调用Service()
|
|
{
|
|
// Arrange
|
|
var userInput = "本月支出分析";
|
|
var chunkReceived = false;
|
|
Action<string> onChunk = chunk => { chunkReceived = true; };
|
|
|
|
// Act
|
|
await _application.AnalyzeBillAsync(userInput, onChunk);
|
|
|
|
// Assert
|
|
await _smartHandleService.Received(1).AnalyzeBillAsync(userInput, onChunk);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AnalyzeBillAsync_空输入_应抛出ValidationException()
|
|
{
|
|
// Arrange
|
|
Action<string> onChunk = _ => { };
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() =>
|
|
_application.AnalyzeBillAsync(string.Empty, onChunk));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AnalyzeBillAsync_空白输入_应抛出ValidationException()
|
|
{
|
|
// Arrange
|
|
Action<string> onChunk = _ => { };
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() =>
|
|
_application.AnalyzeBillAsync(" ", onChunk));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region BatchUpdateClassifyAsync Tests
|
|
|
|
[Fact]
|
|
public async Task BatchUpdateClassifyAsync_有效项目列表_应返回成功数量()
|
|
{
|
|
// Arrange
|
|
var items = new List<BatchUpdateClassifyItem>
|
|
{
|
|
new() { Id = 1, Classify = "餐饮", Type = TransactionType.Expense },
|
|
new() { Id = 2, Classify = "交通", Type = TransactionType.Expense }
|
|
};
|
|
var record1 = new TransactionRecord { Id = 1, Amount = 100 };
|
|
var record2 = new TransactionRecord { Id = 2, Amount = 200 };
|
|
|
|
_transactionRepository.GetByIdAsync(1).Returns(record1);
|
|
_transactionRepository.GetByIdAsync(2).Returns(record2);
|
|
_transactionRepository.UpdateAsync(Arg.Any<TransactionRecord>()).Returns(true);
|
|
|
|
// Act
|
|
var result = await _application.BatchUpdateClassifyAsync(items);
|
|
|
|
// Assert
|
|
result.Should().Be(2);
|
|
await _transactionRepository.Received(2).UpdateAsync(Arg.Any<TransactionRecord>());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BatchUpdateClassifyAsync_部分记录不存在_应只更新存在的记录()
|
|
{
|
|
// Arrange
|
|
var items = new List<BatchUpdateClassifyItem>
|
|
{
|
|
new() { Id = 1, Classify = "餐饮" },
|
|
new() { Id = 999, Classify = "交通" }
|
|
};
|
|
var record1 = new TransactionRecord { Id = 1, Amount = 100 };
|
|
|
|
_transactionRepository.GetByIdAsync(1).Returns(record1);
|
|
_transactionRepository.GetByIdAsync(999).Returns((TransactionRecord?)null);
|
|
_transactionRepository.UpdateAsync(Arg.Any<TransactionRecord>()).Returns(true);
|
|
|
|
// Act
|
|
var result = await _application.BatchUpdateClassifyAsync(items);
|
|
|
|
// Assert
|
|
result.Should().Be(1);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BatchUpdateClassifyAsync_空列表_应抛出ValidationException()
|
|
{
|
|
// Arrange
|
|
var emptyList = new List<BatchUpdateClassifyItem>();
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() =>
|
|
_application.BatchUpdateClassifyAsync(emptyList));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BatchUpdateClassifyAsync_Null列表_应抛出ValidationException()
|
|
{
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() =>
|
|
_application.BatchUpdateClassifyAsync(null!));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BatchUpdateClassifyAsync_更新应清除待确认状态()
|
|
{
|
|
// Arrange
|
|
var items = new List<BatchUpdateClassifyItem>
|
|
{
|
|
new() { Id = 1, Classify = "餐饮", Type = TransactionType.Expense }
|
|
};
|
|
var record = new TransactionRecord
|
|
{
|
|
Id = 1,
|
|
Amount = 100,
|
|
UnconfirmedClassify = "待确认",
|
|
UnconfirmedType = TransactionType.Income
|
|
};
|
|
|
|
_transactionRepository.GetByIdAsync(1).Returns(record);
|
|
_transactionRepository.UpdateAsync(Arg.Any<TransactionRecord>()).Returns(true);
|
|
|
|
// Act
|
|
await _application.BatchUpdateClassifyAsync(items);
|
|
|
|
// Assert
|
|
await _transactionRepository.Received(1).UpdateAsync(Arg.Is<TransactionRecord>(
|
|
r => r.UnconfirmedClassify == null && r.UnconfirmedType == null
|
|
));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region BatchUpdateByReasonAsync Tests
|
|
|
|
[Fact]
|
|
public async Task BatchUpdateByReasonAsync_有效请求_应返回更新数量()
|
|
{
|
|
// Arrange
|
|
var request = new BatchUpdateByReasonRequest
|
|
{
|
|
Reason = "午餐",
|
|
Type = TransactionType.Expense,
|
|
Classify = "餐饮"
|
|
};
|
|
_transactionRepository.BatchUpdateByReasonAsync("午餐", TransactionType.Expense, "餐饮")
|
|
.Returns(5);
|
|
|
|
// Act
|
|
var result = await _application.BatchUpdateByReasonAsync(request);
|
|
|
|
// Assert
|
|
result.Should().Be(5);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BatchUpdateByReasonAsync_空摘要_应抛出ValidationException()
|
|
{
|
|
// Arrange
|
|
var request = new BatchUpdateByReasonRequest
|
|
{
|
|
Reason = string.Empty,
|
|
Type = TransactionType.Expense,
|
|
Classify = "餐饮"
|
|
};
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() =>
|
|
_application.BatchUpdateByReasonAsync(request));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BatchUpdateByReasonAsync_空分类_应抛出ValidationException()
|
|
{
|
|
// Arrange
|
|
var request = new BatchUpdateByReasonRequest
|
|
{
|
|
Reason = "午餐",
|
|
Type = TransactionType.Expense,
|
|
Classify = string.Empty
|
|
};
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() =>
|
|
_application.BatchUpdateByReasonAsync(request));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task BatchUpdateByReasonAsync_空白摘要_应抛出ValidationException()
|
|
{
|
|
// Arrange
|
|
var request = new BatchUpdateByReasonRequest
|
|
{
|
|
Reason = " ",
|
|
Type = TransactionType.Expense,
|
|
Classify = "餐饮"
|
|
};
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ValidationException>(() =>
|
|
_application.BatchUpdateByReasonAsync(request));
|
|
}
|
|
|
|
#endregion
|
|
}
|