using ClawdDotNet.Core.Config; using Shouldly; namespace ClawdDotNet.Web.Tests; /// /// Die Weboberfläche macht aus einer lokalen Anwendung einen Netzwerkdienst, der Agenten /// mit Datei-, Mail-, FTP- und Datenbankzugriff steuert. Was hier geprüft wird, sind /// keine Feinheiten der Konfiguration, sondern die Bedingungen, unter denen sie /// überhaupt hochkommen darf. /// public sealed class WebUiOptionsTests { private static InstanceConfig Instanz( bool enabled = true, string binding = "loopback", string token = "ein-hinreichend-langes-token", int port = 8080) => new() { WebUiEnabled = enabled, WebUiBinding = binding, WebUiToken = token, WebServerPort = port }; [Fact] public void Sie_ist_in_der_Vorbelegung_aus() { // Wer einen Netzwerkdienst will, schaltet ihn ein. Nicht andersherum. new InstanceConfig().WebUiEnabled.ShouldBeFalse(); new InstanceConfig().WebUiBinding.ShouldBe("loopback"); } [Fact] public void Abgeschaltet_ist_kein_Fehler() { WebUiOptions.TryCreate(Instanz(enabled: false), out var problem).ShouldBeNull(); problem.ShouldBeNull(); } [Fact] public void Ohne_Token_startet_sie_nicht_einmal_auf_Loopback() { // Auf einer Maschine, auf der Agenten Browser-Werkzeuge bedienen, ist // „localhost" keine Vertrauensgrenze. WebUiOptions.TryCreate(Instanz(token: ""), out var problem).ShouldBeNull(); problem.ShouldNotBeNull(); } [Fact] public void Alle_Adressen_ohne_Token_werden_abgelehnt_und_nicht_bemaengelt() { var options = WebUiOptions.TryCreate(Instanz(binding: "any", token: ""), out var problem); options.ShouldBeNull(); problem.ShouldNotBeNull(); problem!.ShouldContain("So wird sie nicht gestartet"); } [Fact] public void Ein_zu_kurzes_Token_zaehlt_nicht() { WebUiOptions.TryCreate(Instanz(token: "kurz"), out var problem).ShouldBeNull(); problem.ShouldNotBeNull(); } [Fact] public void Loopback_bindet_an_127_0_0_1() { var options = WebUiOptions.TryCreate(Instanz(port: 9001), out _); options.ShouldNotBeNull(); options!.Url.ShouldBe("http://127.0.0.1:9001"); options.LoopbackOnly.ShouldBeTrue(); } [Fact] public void Alles_ausser_any_bleibt_Loopback() { // Ein Tippfehler in der Instanzdatei darf die Oberflaeche nicht ins Netz stellen. WebUiOptions.TryCreate(Instanz(binding: "alle"), out _)!.LoopbackOnly.ShouldBeTrue(); WebUiOptions.TryCreate(Instanz(binding: ""), out _)!.LoopbackOnly.ShouldBeTrue(); } [Fact] public void Mit_Token_darf_sie_an_alle_Adressen() { var options = WebUiOptions.TryCreate(Instanz(binding: "ANY"), out _); options.ShouldNotBeNull(); options!.LoopbackOnly.ShouldBeFalse(); options.Url.ShouldStartWith("http://0.0.0.0:"); } [Theory] [InlineData(0)] [InlineData(-1)] [InlineData(70000)] public void Ein_unmoeglicher_Port_wird_abgelehnt(int port) { WebUiOptions.TryCreate(Instanz(port: port), out var problem).ShouldBeNull(); problem.ShouldNotBeNull(); } [Fact] public void Das_Token_steht_geschuetzt_in_der_Instanzdatei() { // Ohne den Eintrag in ConfigSecrets staende es im Klartext neben dem // geschuetzten OpenRouter-Schluessel. Core.Security.ConfigSecrets.IsSecretKey("webUiToken").ShouldBeTrue(); var config = Instanz(token: "ein-hinreichend-langes-token"); Core.Security.ConfigSecrets.Protect(config); config.WebUiToken.ShouldNotBe("ein-hinreichend-langes-token"); Core.Security.ConfigSecrets.Unprotect(config); config.WebUiToken.ShouldBe("ein-hinreichend-langes-token"); } }