using System; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using PolyTrader.Web.Configuration; using PolyTraderSharp.Services; using Xunit; namespace PolyTrader.Tests.WebUi { /// /// Log-Viewer für vergangene Tage über das WebUI (#564). /// public sealed class LogViewerEndpointTests : IDisposable { private readonly string _dir = Path.Combine(Path.GetTempPath(), "polytrader-logs-" + Guid.NewGuid().ToString("N")); public LogViewerEndpointTests() => Directory.CreateDirectory(_dir); public void Dispose() { try { Directory.Delete(_dir, recursive: true); } catch { } } private void WriteDay(string date, params (string Msg, LogLevel Level, string Cid)[] lines) => WriteRaw(date, lines.Select(l => LogJson.Format(new LogMessageEventArgs(l.Msg, l.Level, l.Cid))).ToArray()); private void WriteRaw(string date, params string[] lines) => File.WriteAllLines(Path.Combine(_dir, date + ".jsonl"), lines, new UTF8Encoding(false)); private Task StartAsync() => WebUiTestHost.StartAsync(s => s.AddSingleton(new LogDirectory(_dir))); [Fact] public async Task Tagesliste_zeigt_nur_Tagesdateien_neueste_zuerst() { WriteDay("2026-09-12", ("a", LogLevel.Info, "")); WriteDay("2026-09-13", ("b", LogLevel.Info, "")); File.WriteAllText(Path.Combine(_dir, "2026-09-13-Info.log"), "[12:00:00] b"); File.WriteAllText(Path.Combine(_dir, "notizen.jsonl"), "{}"); await using var web = await StartAsync(); var files = await web.GetJsonAsync("/api/logs/files"); Assert.Equal(new[] { "2026-09-13", "2026-09-12" }, files.EnumerateArray().Select(f => f.GetProperty("date").GetString()).ToArray()); } [Fact] public async Task Filter_nach_Level_Korrelations_ID_und_Text() { WriteDay("2026-09-12", ("Start", LogLevel.Info, ""), ("Signal abgelehnt", LogLevel.Warning, "sig-42"), ("VPN weg", LogLevel.Error, "")); await using var web = await StartAsync(); var all = await web.GetJsonAsync("/api/logs/file?date=2026-09-12"); var warnings = await web.GetJsonAsync("/api/logs/file?date=2026-09-12&level=warning"); var byCid = await web.GetJsonAsync("/api/logs/file?date=2026-09-12&cid=SIG-42"); var byText = await web.GetJsonAsync("/api/logs/file?date=2026-09-12&q=vpn"); Assert.Equal(3, all.GetProperty("total").GetInt32()); Assert.Equal("Signal abgelehnt", Assert.Single(warnings.GetProperty("items").EnumerateArray()).GetProperty("msg").GetString()); Assert.Equal("sig-42", Assert.Single(byCid.GetProperty("items").EnumerateArray()).GetProperty("cid").GetString()); Assert.Equal("Error", Assert.Single(byText.GetProperty("items").EnumerateArray()).GetProperty("level").GetString()); } [Fact] public async Task Kaputte_Zeilen_werden_uebersprungen_und_gezaehlt() { WriteRaw("2026-09-12", LogJson.Format(new LogMessageEventArgs("gut", LogLevel.Info)), "{kaputt", "", LogJson.Format(new LogMessageEventArgs("auch gut", LogLevel.Info))); await using var web = await StartAsync(); var result = await web.GetJsonAsync("/api/logs/file?date=2026-09-12"); Assert.Equal(2, result.GetProperty("total").GetInt32()); Assert.Equal(1, result.GetProperty("unreadableLines").GetInt32()); // Leerzeilen zaehlen nicht } [Fact] public async Task Seitenaufteilung() { WriteDay("2026-09-12", Enumerable.Range(1, 120).Select(i => ($"Zeile {i}", LogLevel.Info, "")).ToArray()); await using var web = await StartAsync(); var last = await web.GetJsonAsync("/api/logs/file?date=2026-09-12&pageSize=50&page=9"); Assert.Equal(3, last.GetProperty("totalPages").GetInt32()); Assert.Equal(3, last.GetProperty("page").GetInt32()); // auf die letzte Seite begrenzt Assert.Equal(20, last.GetProperty("items").GetArrayLength()); Assert.Equal("Zeile 101", last.GetProperty("items")[0].GetProperty("msg").GetString()); } [Theory] [InlineData("../../etc/passwd")] [InlineData("2026-09-12/../../geheim")] [InlineData("2026-02-30")] [InlineData("")] public async Task Ungueltiges_Datum_400(string date) { WriteDay("2026-09-12", ("a", LogLevel.Info, "")); await using var web = await StartAsync(); using var res = await web.Client.GetAsync("/api/logs/file?date=" + Uri.EscapeDataString(date)); Assert.Equal(400, (int)res.StatusCode); } [Fact] public async Task Tag_ohne_Datei_404() { await using var web = await StartAsync(); using var res = await web.Client.GetAsync("/api/logs/file?date=2020-01-01"); Assert.Equal(404, (int)res.StatusCode); } } }