using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using PolyTraderSharp.Models;
using Xunit;
namespace PolyTrader.Tests.WebUi
{
///
/// Server-Einstellungen über das WebUI (#561): eine Freigabeliste ohne Geheimnisse, geprüfte Werte,
/// alles oder nichts.
///
public class ServerSettingsEndpointTests
{
private static ServerSettings WithSecrets() => new()
{
LicenseKey = "enc:v1:LIZENZ",
WatchdogToken = "enc:v1:WATCHDOG",
MullvadAccount = "1234567890",
PolygonRpcUrl = "wss://polygon-mainnet.g.alchemy.com/v2/ALCHEMY",
WatchdogUrl = "https://dc.example",
VpnLocation = "cz",
WatchdogIntervalSeconds = 60
};
private static JsonElement Field(JsonElement settings, string key) =>
settings.GetProperty("sections").EnumerateArray()
.SelectMany(s => s.GetProperty("fields").EnumerateArray())
.Single(f => f.GetProperty("key").GetString() == key);
private static bool HasField(JsonElement settings, string key) =>
settings.GetProperty("sections").EnumerateArray()
.SelectMany(s => s.GetProperty("fields").EnumerateArray())
.Any(f => f.GetProperty("key").GetString() == key);
[Fact]
public async Task Anzeige_enthaelt_die_Freigabeliste_aber_keine_Geheimnisfelder()
{
await using var web = await WebUiTestHost.StartAsync();
WithSecrets().Save(web.SettingsPath);
var settings = await web.GetJsonAsync("/api/settings/server");
Assert.True(Field(settings, "VpnEnabled").GetProperty("editable").GetBoolean());
Assert.False(Field(settings, "WatchdogUrl").GetProperty("editable").GetBoolean());
Assert.False(Field(settings, "MullvadCliPath").GetProperty("editable").GetBoolean());
Assert.True(Field(settings, "ApplicationTimeZoneId").GetProperty("requiresRestart").GetBoolean());
Assert.False(Field(settings, "UsePolymarketWebsockets").GetProperty("requiresRestart").GetBoolean());
foreach (var secret in new[] { "LicenseKey", "WatchdogToken", "MullvadAccount", "PolygonRpcUrl" })
Assert.False(HasField(settings, secret), secret);
var secrets = settings.GetProperty("secrets").EnumerateArray()
.ToDictionary(x => x.GetProperty("label").GetString()!, x => x.GetProperty("status").GetString()!);
Assert.Equal("gesetzt", secrets["Mullvad-Account"]);
Assert.Equal("gesetzt (verschlüsselt)", secrets["Lizenzschlüssel"]);
}
[Fact]
public async Task Aendern_schreibt_nur_die_Werte_und_zieht_den_laufenden_Stand_nach()
{
var live = WithSecrets();
await using var web = await WebUiTestHost.StartAsync(s => s.AddSingleton(live));
WithSecrets().Save(web.SettingsPath);
var (status, body) = await web.PostJsonAsync("/api/settings/server", new
{
values = new { VpnEnabled = true, WatchdogIntervalSeconds = 120, VpnLocation = "cz-prg", Theme = "dunkel" }
});
Assert.Equal(200, status);
Assert.True(body.GetProperty("restartRequired").GetBoolean()); // Theme wirkt erst nach Neustart
var written = ServerSettings.Load(web.SettingsPath);
Assert.True(written.VpnEnabled);
Assert.Equal(120, written.WatchdogIntervalSeconds);
Assert.Equal("cz-prg", written.VpnLocation);
Assert.Equal(AppThemeVariant.Dunkel, written.Theme);
Assert.Equal("enc:v1:LIZENZ", written.LicenseKey);
Assert.Equal("enc:v1:WATCHDOG", written.WatchdogToken);
Assert.Equal("1234567890", written.MullvadAccount);
Assert.Equal("wss://polygon-mainnet.g.alchemy.com/v2/ALCHEMY", written.PolygonRpcUrl);
Assert.True(live.VpnEnabled); // laufender Stand
Assert.Equal(120, live.WatchdogIntervalSeconds);
}
[Theory]
[InlineData("WatchdogUrl", "https://angreifer.example")]
[InlineData("MullvadCliPath", "/tmp/boese")]
[InlineData("LicenseKey", "neu")]
[InlineData("PolygonRpcUrl", "wss://x")]
public async Task Desktop_Felder_werden_mit_403_abgelehnt_und_nichts_geschrieben(string key, string value)
{
await using var web = await WebUiTestHost.StartAsync();
WithSecrets().Save(web.SettingsPath);
string before = File.ReadAllText(web.SettingsPath);
var values = new System.Collections.Generic.Dictionary { [key] = value, ["VpnEnabled"] = true };
var (status, body) = await web.PostJsonAsync("/api/settings/server", new { values });
Assert.Equal(403, status);
Assert.Contains("Desktop", body.GetProperty("error").GetString());
Assert.Equal(before, File.ReadAllText(web.SettingsPath));
}
[Theory]
[InlineData("VpnLocation", "cz account login 999")] // Argumente fuer das Mullvad-Programm einschleusen
[InlineData("VpnLocation", "cz;reboot")]
[InlineData("ApplicationTimeZoneId", "Mars/Olympus")]
[InlineData("WatchdogSource", "polytrader\\\" , \\\"x")]
public async Task Ungueltige_Texte_werden_abgelehnt(string key, string value)
{
await using var web = await WebUiTestHost.StartAsync();
WithSecrets().Save(web.SettingsPath);
string before = File.ReadAllText(web.SettingsPath);
var values = new System.Collections.Generic.Dictionary { [key] = value };
var (status, _) = await web.PostJsonAsync("/api/settings/server", new { values });
Assert.Equal(400, status);
Assert.Equal(before, File.ReadAllText(web.SettingsPath));
}
[Fact]
public async Task Ein_ungueltiger_Wert_verhindert_auch_die_gueltigen()
{
await using var web = await WebUiTestHost.StartAsync();
WithSecrets().Save(web.SettingsPath);
var (status, body) = await web.PostJsonAsync("/api/settings/server", new
{
values = new { VpnEnabled = true, WatchdogIntervalSeconds = 5 }
});
Assert.Equal(400, status);
Assert.Contains("Nichts gespeichert", body.GetProperty("error").GetString());
Assert.False(ServerSettings.Load(web.SettingsPath).VpnEnabled);
}
[Fact]
public async Task Unbekanntes_Feld_leerer_Rumpf_und_gueltige_Zeitzone()
{
await using var web = await WebUiTestHost.StartAsync();
var (unknown, _) = await web.PostJsonAsync("/api/settings/server", new { values = new { GibtEsNicht = 1 } });
var (empty, _) = await web.PostJsonAsync("/api/settings/server");
Assert.Equal(400, unknown);
Assert.Equal(400, empty);
Assert.False(File.Exists(web.SettingsPath));
var (ok, _) = await web.PostJsonAsync("/api/settings/server", new { values = new { ApplicationTimeZoneId = "Europe/Berlin" } });
Assert.Equal(200, ok);
Assert.Equal("Europe/Berlin", ServerSettings.Load(web.SettingsPath).ApplicationTimeZoneId);
}
[Fact]
public async Task Unlesbare_Datei_wird_weder_angezeigt_noch_ueberschrieben()
{
await using var web = await WebUiTestHost.StartAsync();
const string kaputt = "enc:v1:LIZENZ