using ClawdDotNet.App.Services; using ClawdDotNet.App.Settings; using Shouldly; namespace ClawdDotNet.App.Tests.Services; /// /// #764: The settings the WebUI reads and writes — what the desktop settings page edited. /// public sealed class SettingsAdministrationTests { [Fact] public void The_snapshot_never_carries_a_secret() { using var host = new TestHost(); host.Host.Instance.OpenRouterApiKey = "sk-or-secret"; host.Host.Settings.AppSettings.DeploymentcenterToken = "dc-secret"; var snapshot = host.Host.SettingsAdmin.Snapshot(); snapshot.OpenRouterApiKeySet.ShouldBeTrue(); snapshot.DeploymentcenterTokenSet.ShouldBeTrue(); System.Text.Json.JsonSerializer.Serialize(snapshot).ShouldNotContain("secret"); } [Fact] public void Instance_settings_survive_a_restart() { using var host = new TestHost(); host.Host.Directories.SaveInstanceConfig(host.Host.InstancePath, host.Host.Instance); var result = host.Host.SettingsAdmin.Update(new SettingsUpdate( InstanceName: "Renamed", AutostartByManager: true, DailyCostUsd: 2.5m, DailyTokens: 900, AutoBackupEnabled: true, AutoBackupTime: "4:30", BackupKeepCount: 7)); result.Ok.ShouldBeTrue(result.Message); var reloaded = host.Host.Directories.LoadInstanceConfig(host.Host.InstancePath); reloaded.InstanceName.ShouldBe("Renamed"); reloaded.AutostartByManager.ShouldBeTrue(); reloaded.Budget.DailyCostUsd.ShouldBe(2.5m); reloaded.Budget.DailyTokens.ShouldBe(900); reloaded.AutoBackupEnabled.ShouldBeTrue(); reloaded.AutoBackupTime.ShouldBe("04:30"); reloaded.BackupKeepCount.ShouldBe(7); } [Fact] public void An_empty_secret_keeps_the_one_there_is() { // The form never gets the key back, so it sends an empty field. That must not wipe it. using var host = new TestHost(); host.Host.Instance.OpenRouterApiKey = "sk-or-keep"; host.Host.Settings.AppSettings.DeploymentcenterToken = "dc-keep"; host.Host.SettingsAdmin.Update(new SettingsUpdate(OpenRouterApiKey: "", DeploymentcenterToken: " ")).Ok.ShouldBeTrue(); host.Host.Instance.OpenRouterApiKey.ShouldBe("sk-or-keep"); host.Host.Settings.AppSettings.DeploymentcenterToken.ShouldBe("dc-keep"); } [Fact] public void A_new_key_says_it_needs_a_restart() { // The engine is built at start; without saying so the save button would seem to do nothing. using var host = new TestHost(); var result = host.Host.SettingsAdmin.Update(new SettingsUpdate(OpenRouterApiKey: "sk-or-new")); result.Ok.ShouldBeTrue(); result.Message.ShouldContain("restart"); host.Host.Instance.OpenRouterApiKey.ShouldBe("sk-or-new"); } [Fact] public void Application_settings_are_written_to_settings_json() { using var host = new TestHost(); host.Host.SettingsAdmin.Update(new SettingsUpdate( MinimumLogLevel: "warn", UpdateChannel: "beta", ErrorReportingEnabled: false)).Ok.ShouldBeTrue(); var root = Path.GetDirectoryName(host.Host.Settings.SettingsPath)!; var reloaded = new SettingsManager(root); reloaded.Load(); reloaded.AppSettings.MinimumLogLevel.ShouldBe("Warn"); reloaded.AppSettings.UpdateChannel.ShouldBe("beta"); reloaded.AppSettings.ErrorReportingEnabled.ShouldBeFalse(); } [Theory] [InlineData("time")] [InlineData("level")] [InlineData("url")] [InlineData("channel")] [InlineData("name")] [InlineData("keep")] public void One_bad_value_changes_nothing(string bad) { using var host = new TestHost(); var update = bad switch { "time" => new SettingsUpdate(DailyTokens: 5, AutoBackupTime: "25:00"), "level" => new SettingsUpdate(DailyTokens: 5, MinimumLogLevel: "Chatty"), "url" => new SettingsUpdate(DailyTokens: 5, OpenRouterBaseUrl: "file:///etc/passwd"), "channel" => new SettingsUpdate(DailyTokens: 5, UpdateChannel: "nightly"), "name" => new SettingsUpdate(DailyTokens: 5, InstanceName: " "), _ => new SettingsUpdate(DailyTokens: 5, BackupKeepCount: -1) }; host.Host.SettingsAdmin.Update(update).Ok.ShouldBeFalse(); host.Host.Instance.Budget.DailyTokens.ShouldBe(0); host.Changes.ShouldBeEmpty(); } }