Regel 3 verlangt, dass die Buchzuordnung serverseitig gesetzt wird und TradeSignal.SourceModule nicht vertrauenswuerdig ist. Bisher war nur der unbekannte Name abgefangen; ein Modul konnte weiterhin das Kuerzel eines ANDEREN registrierten Moduls eintragen und dessen Cap, dessen Konzentration und dessen GuV belasten. Jedes Modul bekommt jetzt einen an sein Buch gebundenen IModuleExecution-Zugang statt des gemeinsamen IExecutionService. Der Zugang ueberschreibt die Herkunft mit seinem eigenen Buch - was im Signal steht, spielt keine Rolle mehr. Der Zugang steht ausdruecklich NICHT im Container: was dort steht, kann jedes Modul aufloesen, auch den Zugang eines anderen. Der Host uebergibt stattdessen eine Fabrik beim Registrieren (neue Ueberladung von IModule.RegisterServices mit Vorgabe, damit ein nicht handelndes Modul nichts davon wissen muss), und das Modul reicht den erzeugten Zugang direkt an seine Strategie. Regel 4 - Module bekommen den Broker nicht per DI - war eingehalten, aber nicht erzwungen. Jetzt zwei Tests ueber alle vier Modul-Assemblies: der eine prueft per Reflection Konstruktorparameter, Felder und Eigenschaften auf IBrokerClient, IBrokerPortfolioReader und IExecutionService; der andere durchsucht die Modulquellen nach dem zweiten Weg, dem Griff in den Container. Beide Gegenproben mit einem absichtlichen Regelbruch gefahren: jeder Test schlaegt bei genau seinem Weg an. Getrennte Container je Modul waeren der harte Weg, aendern aber die Modularchitektur. Der Fall, um den es geht, ist ein NEUES Modul, das den Core versehentlich umgeht - und den faengt der Test beim Uebersetzen. 404 Tests gruen, auf Windows und auf der Test-VM. Behebt #474, damit ist auch #302 vollstaendig. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
72 lines
2.4 KiB
C#
72 lines
2.4 KiB
C#
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 IModuleExecution _execution = Substitute.For<IModuleExecution>();
|
|
|
|
private CongressTradingStrategy CreateSut()
|
|
{
|
|
_execution.ExecuteAsync(Arg.Any<TradeSignal>(), Arg.Any<CancellationToken>())
|
|
.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<TradeSignal>(s => s.Side == expected && s.Symbol == "AAPL" && s.SourceModule == "CT"),
|
|
Arg.Any<CancellationToken>());
|
|
}
|
|
|
|
[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<TradeSignal>(), Arg.Any<CancellationToken>());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EmptyTicker_DoesNotExecute()
|
|
{
|
|
var sut = CreateSut();
|
|
|
|
await sut.HandleNewTradeAsync(Trade("buy", ticker: ""));
|
|
|
|
await _execution.DidNotReceive().ExecuteAsync(Arg.Any<TradeSignal>(), Arg.Any<CancellationToken>());
|
|
}
|
|
|
|
[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();
|
|
}
|
|
}
|