Files
RichardandClaude Opus 5.5 77062facc9 Stufe 4c (#764): Bezeichner auf Englisch - Typen, Methoden, Felder, Parameter, Locals, Testnamen
Mit Roslyn umbenannt (Verweise folgen, Zeichenketten bleiben unberuehrt).
Nicht umbenannt, weil gespeichert oder ueber die API gebunden (Stufe 4e):
Properties, Record-Parameter, Enum-Member sowie die Parameter der
Endpunkt-Handler (Routen-/Query-Bindung ueber den Namen). Die JSON-Felder
anonymer Objekte bleiben ausdruecklich benannt (JsonFilter: pfad).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:18:43 +02:00

123 lines
3.9 KiB
C#

using ClawdDotNet.Core.Config;
using Shouldly;
namespace ClawdDotNet.Web.Tests;
/// <summary>
/// The web UI turns a local application into a network service that controls agents
/// with file, mail, FTP and database access. What is checked here are
/// not subtleties of configuration, but the conditions under which it
/// may come up at all.
/// </summary>
public sealed class WebUiOptionsTests
{
private static InstanceConfig Instance(
bool enabled = true,
string binding = "loopback",
string token = "ein-hinreichend-langes-token",
int port = 7001)
=> new()
{
WebUiEnabled = enabled,
WebUiBinding = binding,
WebUiToken = token,
WebServerPort = port
};
[Fact]
public void It_is_off_by_default()
{
// Whoever wants a network service switches it on. Not the other way round.
new InstanceConfig().WebUiEnabled.ShouldBeFalse();
new InstanceConfig().WebUiBinding.ShouldBe("loopback");
new InstanceConfig().WebServerPort.ShouldBe(7001);
}
[Fact]
public void Switched_off_is_not_an_error()
{
WebUiOptions.TryCreate(Instance(enabled: false), out var problem).ShouldBeNull();
problem.ShouldBeNull();
}
[Fact]
public void Without_token_it_does_not_start_even_on_loopback()
{
// On a machine on which agents operate browser tools,
// "localhost" is no trust boundary.
WebUiOptions.TryCreate(Instance(token: ""), out var problem).ShouldBeNull();
problem.ShouldNotBeNull();
}
[Fact]
public void All_addresses_without_token_are_rejected_and_not_flagged()
{
var options = WebUiOptions.TryCreate(Instance(binding: "any", token: ""), out var problem);
options.ShouldBeNull();
problem.ShouldNotBeNull();
problem!.ShouldContain("So wird sie nicht gestartet");
}
[Fact]
public void A_too_short_token_does_not_count()
{
WebUiOptions.TryCreate(Instance(token: "kurz"), out var problem).ShouldBeNull();
problem.ShouldNotBeNull();
}
[Fact]
public void Loopback_binds_to_127_0_0_1()
{
var options = WebUiOptions.TryCreate(Instance(port: 9001), out _);
options.ShouldNotBeNull();
options!.Url.ShouldBe("http://127.0.0.1:9001");
options.LoopbackOnly.ShouldBeTrue();
}
[Fact]
public void Everything_except_any_stays_loopback()
{
// A typo in the instance file must not put the user interface onto the network.
WebUiOptions.TryCreate(Instance(binding: "alle"), out _)!.LoopbackOnly.ShouldBeTrue();
WebUiOptions.TryCreate(Instance(binding: ""), out _)!.LoopbackOnly.ShouldBeTrue();
}
[Fact]
public void With_token_it_may_reach_all_addresses()
{
var options = WebUiOptions.TryCreate(Instance(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 An_impossible_port_is_rejected(int port)
{
WebUiOptions.TryCreate(Instance(port: port), out var problem).ShouldBeNull();
problem.ShouldNotBeNull();
}
[Fact]
public void The_token_is_stored_protected_in_the_instance_file()
{
// Without the entry in ConfigSecrets it would be in plain text next to the
// protected OpenRouter key.
Core.Security.ConfigSecrets.IsSecretKey("webUiToken").ShouldBeTrue();
var config = Instance(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");
}
}