using FluentAssertions; using IBKRTrader.Core.Logging; using IBKRTrader.Core.Trading; using IBKRTrader.Modules.CongressTrading.Models; using IBKRTrader.Modules.CongressTrading.Strategy; using NSubstitute; namespace IBKRTrader.Tests.Modules; [Trait("cat", "unit")] public class CongressTradingStrategyTests { private readonly IExecutionService _execution = Substitute.For(); private CongressTradingStrategy CreateSut() { _execution.ExecuteAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(ExecutionResult.Skip("test"))); return new CongressTradingStrategy(_execution, new LoggingService()); } private static CongressTrade Trade(string type, string ticker = "AAPL") => new() { TradeId = "T1", MemberBioId = "W1", IssuerName = "Apple", Ticker = ticker, TradeType = type }; [Theory] [InlineData("buy", TradeSide.Buy)] [InlineData("BUY", TradeSide.Buy)] [InlineData("sell", TradeSide.Sell)] public async Task NewTrade_EmitsSignal_WithMappedSide(string type, TradeSide expected) { var sut = CreateSut(); await sut.HandleNewTradeAsync(Trade(type)); await _execution.Received(1).ExecuteAsync( Arg.Is(s => s.Side == expected && s.Symbol == "AAPL" && s.SourceModule == "CT"), Arg.Any()); } [Theory] [InlineData("exchange")] [InlineData("receive")] [InlineData("")] public async Task NonBuySell_TradeType_DoesNotExecute(string type) { var sut = CreateSut(); await sut.HandleNewTradeAsync(Trade(type)); await _execution.DidNotReceive().ExecuteAsync(Arg.Any(), Arg.Any()); } [Fact] public async Task EmptyTicker_DoesNotExecute() { var sut = CreateSut(); await sut.HandleNewTradeAsync(Trade("buy", ticker: "")); await _execution.DidNotReceive().ExecuteAsync(Arg.Any(), Arg.Any()); } [Fact] public void MapSide_IsCaseInsensitive_AndIgnoresUnknown() { CongressTradingStrategy.MapSide("Buy").Should().Be(TradeSide.Buy); CongressTradingStrategy.MapSide("SELL").Should().Be(TradeSide.Sell); CongressTradingStrategy.MapSide("exchange").Should().BeNull(); CongressTradingStrategy.MapSide(null).Should().BeNull(); } }