using AssetManager.Data.Repositories; using AssetManager.Infrastructure.Services; using AssetManager.Models.DTOs; using AssetManager.Services; using FluentAssertions; using Microsoft.Extensions.Logging; using Moq; using SqlSugar; using Xunit; namespace AssetManager.Tests.Services; /// /// PortfolioService 单元测试 /// public class PortfolioServiceTests { private readonly Mock _dbMock; private readonly Mock _marketDataServiceMock; private readonly Mock _exchangeRateServiceMock; private readonly Mock _navServiceMock; private readonly Mock> _loggerMock; private readonly PortfolioService _service; public PortfolioServiceTests() { _dbMock = new Mock(); _marketDataServiceMock = new Mock(); _exchangeRateServiceMock = new Mock(); _navServiceMock = new Mock(); _loggerMock = new Mock>(); // Note: 实际测试需要更复杂的 SqlSugar mock 设置 // 这里展示测试结构和模式 } #region CreatePortfolio Tests [Fact(Skip = "需要 SqlSugar 完整 mock 设置")] public void CreatePortfolio_WithValidRequest_ShouldReturnResponse() { // Arrange var request = new CreatePortfolioRequest { Name = "测试组合", Currency = "CNY", Stocks = new List { new() { Code = "AAPL", Name = "Apple", Price = 150, Amount = 10 } } }; var userId = "user-001"; // Act // var response = _service.CreatePortfolio(request, userId); // Assert // response.Should().NotBeNull(); // response.Id.Should().StartWith("port-"); // response.Currency.Should().Be("CNY"); } [Fact(Skip = "需要 SqlSugar 完整 mock 设置")] public void CreatePortfolio_WithStrategy_ShouldLoadStrategyStocks() { // Arrange var request = new CreatePortfolioRequest { Name = "风险平价组合", Currency = "USD", StrategyId = "strategy-001" }; var userId = "user-001"; // Act & Assert - 验证策略配置加载逻辑 } #endregion #region GetPortfolios Tests [Fact(Skip = "需要 SqlSugar 完整 mock 设置")] public void GetPortfolios_ShouldReturnUserPortfolios() { // Arrange var userId = "user-001"; // Act // var result = _service.GetPortfolios(userId); // Assert // result.Should().NotBeNull(); } #endregion #region GetTotalAssets Tests [Fact(Skip = "需要 Repository mock 设置")] public async Task GetTotalAssetsAsync_ShouldConvertToTargetCurrency() { // Arrange var userId = "user-001"; _marketDataServiceMock .Setup(x => x.GetPriceAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new MarketPriceResponse { Price = 100, PreviousClose = 98 }); _exchangeRateServiceMock .Setup(x => x.ConvertAmountAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync((decimal amount, string _, string _) => amount * 7.2m); // Act // var result = await _service.GetTotalAssetsAsync(userId); // Assert // result.Should().NotBeNull(); } #endregion #region CreateTransaction Tests [Fact(Skip = "需要 SqlSugar 完整 mock 设置")] public async Task CreateTransaction_BuyNewStock_ShouldCreatePosition() { // Arrange var request = new CreateTransactionRequest { PortfolioId = "port-001", Type = "buy", StockCode = "AAPL", Amount = 10, Price = 150, Currency = "USD" }; var userId = "user-001"; // Act // var result = await _service.CreateTransaction(request, userId); // Assert // result.Should().NotBeNull(); } [Fact(Skip = "需要 SqlSugar 完整 mock 设置")] public async Task CreateTransaction_SellMoreThanHeld_ShouldThrowException() { // Arrange var request = new CreateTransactionRequest { PortfolioId = "port-001", Type = "sell", StockCode = "AAPL", Amount = 100, // 超过持仓 Price = 150, Currency = "USD" }; var userId = "user-001"; // Act & Assert // await Assert.ThrowsAsync(() => _service.CreateTransaction(request, userId)); } [Fact(Skip = "需要 SqlSugar 完整 mock 设置")] public async Task CreateTransaction_WrongCurrency_ShouldThrowException() { // Arrange var request = new CreateTransactionRequest { PortfolioId = "port-001", // 组合币种为 CNY Type = "buy", StockCode = "AAPL", Amount = 10, Price = 150, Currency = "USD" // 币种不匹配 }; var userId = "user-001"; // Act & Assert // await Assert.ThrowsAsync(() => _service.CreateTransaction(request, userId)); } #endregion }