Terminal-Fenster jetzt mit TabControl (via Designer): Tab 'Live' = bisherige Anzeige,
Tab 'Log Viewer' = JSONL-Tagesdateien menschenlesbar aufbereitet:
- Filter: Datum (DateTimePicker), Level, CorrelationId (SignalId), Suchtext.
- Doppelklick auf eine CID laedt die komplette Signal-Kette ('zeig mir alles zu diesem Signal').
- LogJson.ParseLine (pur, Gegenstueck zu Format): tolerant gegen kaputte Zeilen (null statt Crash);
Viewer ueberspringt sie. UI-Schutz bei sehr grossen Tagen (20k-Limit).
Tests: +5 (Parse-Roundtrip, kaputte Zeilen). Build 0 Fehler, 341 Tests gruen, --smoke-ui komplett gruen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
114 lines
4.2 KiB
C#
114 lines
4.2 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Sicherheitsnetz für das S-0-Datenfundament: JSONL-Log-Format (pur) und die
|
|
/// Entscheidungsjournal-/Order-Event-Repos (EF InMemory, inkl. Enum-als-String-Roundtrip).
|
|
/// </summary>
|
|
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<CoreDbContext> 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);
|
|
}
|
|
}
|
|
}
|