using System; using System.Text.Json; using PolyTrader.Core.Persistence.Ef; using PolyTrader.Tests.TestSupport; using PolyTraderSharp.Models; using PolyTraderSharp.Services; using Xunit; namespace PolyTrader.Tests { /// /// Sicherheitsnetz für das S-0-Datenfundament: JSONL-Log-Format (pur) und die /// Entscheidungsjournal-/Order-Event-Repos (EF InMemory, inkl. Enum-als-String-Roundtrip). /// public class AnalysisJournalTests { // ----- LogJson (pur) ----- [Fact] public void LogJson_produces_valid_single_line_json() { var e = new LogMessageEventArgs("Zeile1\nZeile2 mit \"Quotes\" und 0,95 €", LogLevel.Trade, "sig-123"); string line = LogJson.Format(e); Assert.DoesNotContain('\n', line); // eine Zeile (JSONL) using var doc = JsonDocument.Parse(line); // valides JSON Assert.Equal("Trade", doc.RootElement.GetProperty("level").GetString()); Assert.Equal("sig-123", doc.RootElement.GetProperty("cid").GetString()); Assert.Contains("Zeile2", doc.RootElement.GetProperty("msg").GetString()); } [Fact] public void LogJson_omits_empty_correlation_id() { string line = LogJson.Format(new LogMessageEventArgs("msg", LogLevel.Info)); using var doc = JsonDocument.Parse(line); Assert.False(doc.RootElement.TryGetProperty("cid", out _)); } [Fact] public void LogJson_parse_roundtrips_format() { var e = new LogMessageEventArgs("Nachricht äöü", LogLevel.Warning, "sig-9"); var p = LogJson.ParseLine(LogJson.Format(e)); Assert.NotNull(p); Assert.Equal("Warning", p!.Level); Assert.Equal("sig-9", p.Cid); Assert.Equal("Nachricht äöü", p.Message); } [Theory] [InlineData("")] [InlineData(" ")] [InlineData("kein json")] [InlineData("{kaputt")] public void LogJson_parse_returns_null_for_broken_lines(string line) { Assert.Null(LogJson.ParseLine(line)); } // ----- Repos (EF InMemory) ----- private static InMemoryContextFactory Factory() => new(o => new CoreDbContext(o)); [Fact] public void DecisionJournal_write_and_query_roundtrip() { var journal = new EfDecisionJournal(Factory()); journal.Write(new DecisionRecord { SignalId = "sig-1", ModuleName = "CopyTrading", AccountId = 1, TokenId = "tok", Side = "BUY", Decision = TradeDecision.Rejected, Reason = DecisionReason.MaxBuyPriceExceeded, SignalPrice = 0.97m, Message = "test" }); journal.Write(new DecisionRecord { SignalId = "sig-2", AccountId = 1, TokenId = "tok", Decision = TradeDecision.Executed, Reason = DecisionReason.OrderPlaced }); var rejects = journal.Query(d => d.Reason == DecisionReason.MaxBuyPriceExceeded); Assert.Single(rejects); Assert.Equal("sig-1", rejects[0].SignalId); Assert.Equal(TradeDecision.Rejected, rejects[0].Decision); // Enum-Roundtrip Assert.Equal(2, journal.Query(d => d.AccountId == 1).Count); } [Fact] public void OrderEventLog_write_and_query_roundtrip() { var log = new EfOrderEventLog(Factory()); log.Write(new OrderEventRecord { SignalId = "sig-1", AccountId = 1, TokenId = "tok", EventType = OrderEventType.LadderStep, Side = "SELL", Price = 0.42m, Response = "OK" }); var events = log.Query(e => e.SignalId == "sig-1"); Assert.Single(events); Assert.Equal(OrderEventType.LadderStep, events[0].EventType); } [Fact] public void CopySignal_gets_signal_id_automatically() { var a = new CopySignal(); var b = new CopySignal(); Assert.False(string.IsNullOrEmpty(a.SignalId)); Assert.NotEqual(a.SignalId, b.SignalId); } } }