Phase 1: Modul-System formalisiert (IModule + ModuleRegistry)

- Core/Modules/IModule.cs: Vertrag (Key, DisplayName, Description, Version,
  RegisterServices, InitializeAsync, GetWorkers, CreateWindow)
- Core/Modules/ModuleRegistry.cs
- CongressTradingModule auf IModule umgestellt (instanzbasiert, Platzhalter-Fenster)
- Program.cs iteriert ueber IModule[] + Registry; Form1 initialisiert Module ueber Registry
- Testbarkeit: WorkerBase DB-Log-Seam (virtuell); CapitolTradesScraper.ParseTradesFromHtml
  extrahiert (offline gegen ct_raw.html testbar)
- Tests: ModuleRegistry, CongressTradingModule, WorkerBase, CapitolTradesScraper -> 15/15 gruen

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
This commit is contained in:
Richard
2026-07-27 10:07:47 +02:00
parent 9abad277c2
commit af398353f0
13 changed files with 449 additions and 35 deletions
@@ -0,0 +1,57 @@
using FluentAssertions;
using IBKRTrader.Core.Logging;
using IBKRTrader.Modules.CongressTrading.Scraper;
namespace IBKRTrader.Tests.Modules;
[Trait("cat", "unit")]
public class CapitolTradesScraperTests
{
private static string LoadFixture()
{
var path = Path.Combine(AppContext.BaseDirectory, "Fixtures", "ct_raw.html");
File.Exists(path).Should().BeTrue($"Fixture fehlt: {path}");
return File.ReadAllText(path);
}
[Fact]
public void ParseTradesFromHtml_ExtractsTradesFromRscStream()
{
var scraper = new CapitolTradesScraper(new LoggingService());
var (trades, memberIds) = scraper.ParseTradesFromHtml(LoadFixture());
trades.Should().NotBeEmpty("die Fixture enthält einen RSC-Datenstrom mit Trades");
memberIds.Should().NotBeEmpty();
var first = trades[0];
first.TradeId.Should().NotBeNullOrWhiteSpace();
first.TradeType.Should().NotBeNullOrWhiteSpace();
}
[Fact]
public void ParseTradesFromHtml_MapsMemberSnapshotConsistently()
{
var scraper = new CapitolTradesScraper(new LoggingService());
var (trades, memberIds) = scraper.ParseTradesFromHtml(LoadFixture());
// Jeder Member-Snapshot muss eine BioId haben, die auch im MemberBioIds-Set liegt.
foreach (var trade in trades.Where(t => t.MemberSnapshot is not null))
{
trade.MemberSnapshot!.BioId.Should().NotBeNullOrWhiteSpace();
memberIds.Should().Contain(trade.MemberBioId);
}
}
[Fact]
public void ParseTradesFromHtml_EmptyHtml_ReturnsNoTrades()
{
var scraper = new CapitolTradesScraper(new LoggingService());
var (trades, memberIds) = scraper.ParseTradesFromHtml("<html><body>nichts</body></html>");
trades.Should().BeEmpty();
memberIds.Should().BeEmpty();
}
}