using AssetManager.Data.Repositories;
using AssetManager.Infrastructure.Services;
using AssetManager.Models.DTOs;
using AssetManager.Services;
using FluentAssertions;
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
}