using System.Globalization; 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); } } [Theory] [InlineData("de-DE")] // Tag.Monat.Jahr [InlineData("th-TH")] // buddhistischer Kalender [InlineData("fa-IR")] // persischer Kalender [InlineData("ar-SA")] // Hidschri-Kalender [InlineData("")] // Invariant – so verhält sich ein Container ohne LANG public void ParseTradesFromHtml_LiefertDieselbenDaten_UnabhaengigVonDerHostKultur(string culture) { // Die Datumsfelder kamen ueber DateOnly.TryParse OHNE Formatanbieter herein, also mit der // Kultur des Rechners. Bei Kulturen mit eigenem Kalender ist das nicht theoretisch: // aus "2026-08-04" wurde unter th-TH das Jahr 1483 und unter fa-IR das Jahr 2647, // unter ar-SA schlug das Parsen ganz fehl. de-DE und en-US kommen mit ISO klar - genau // deshalb faellt so ein Fehler auf dem Entwicklungsrechner nie auf. var previous = CultureInfo.CurrentCulture; try { CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(culture); var (trades, _) = new CapitolTradesScraper(new LoggingService()).ParseTradesFromHtml(LoadFixture()); trades.Should().NotBeEmpty(); trades.Should().Contain(t => t.TradeDate != null, "die Fixture enthaelt Handelsdaten im Format yyyy-MM-dd"); // Alle geparsten Daten muessen plausibel sein – ein kulturbedingter Tag/Monat-Dreher // faellt hier auf, weil Tage > 12 sonst gar nicht parsen wuerden. foreach (var t in trades.Where(t => t.TradeDate != null)) t.TradeDate!.Value.Year.Should().BeInRange(2000, 2100); } finally { CultureInfo.CurrentCulture = previous; } } [Fact] public void ParseTradesFromHtml_EmptyHtml_ReturnsNoTrades() { var scraper = new CapitolTradesScraper(new LoggingService()); var (trades, memberIds) = scraper.ParseTradesFromHtml("nichts"); trades.Should().BeEmpty(); memberIds.Should().BeEmpty(); } }