using ClawdDotNet.App.Services; using ClawdDotNet.Core.Runtimes; using Shouldly; namespace ClawdDotNet.App.Tests; /// /// #712: A runtime setting is checked when it is saved — not at the next run, where a list /// of hosts that does not parse would only show as a failed run at night. /// public sealed class RuntimeSettingsTests { [Fact] public void A_sound_runtime_passes() => AgentAdministration.ValidateRuntime(new AgentRuntimeConfig { Kind = "claude-code", AllowedTools = ["Read", "Bash(npm test)"], Egress = ["pypi.org", "*.pythonhosted.org"] }).ShouldBeNull(); [Theory] [InlineData("codex")] [InlineData("http")] public void A_runtime_that_is_not_built_is_refused(string kind) => AgentAdministration.ValidateRuntime(new AgentRuntimeConfig { Kind = kind })!.ShouldContain("Unknown runtime"); [Fact] public void An_option_in_disguise_is_not_a_tool_name() => AgentAdministration.ValidateRuntime(new AgentRuntimeConfig { Kind = "claude-code", AllowedTools = ["--dangerously-skip-permissions"] }).ShouldNotBeNull(); [Fact] public void A_host_that_is_not_one_is_refused() => AgentAdministration.ValidateRuntime(new AgentRuntimeConfig { Kind = "claude-code", Egress = ["https://example.org/path"] }).ShouldNotBeNull(); [Fact] public void The_command_runtime_needs_a_command() => AgentAdministration.ValidateRuntime(new AgentRuntimeConfig { Kind = "command" })!.ShouldContain("command"); [Theory] [InlineData(0, 30)] [InlineData(501, 30)] [InlineData(30, 0)] [InlineData(30, 241)] public void Turns_and_time_stay_in_bounds(int turns, int minutes) => AgentAdministration.ValidateRuntime(new AgentRuntimeConfig { Kind = "claude-code", MaxTurns = turns, TimeoutMinutes = minutes }).ShouldNotBeNull(); [Fact] public void The_claude_code_keys_are_stored_protected() { var config = new Core.Config.InstanceConfig { ClaudeCode = new ClaudeCodeSettings { ApiKey = "sk-ant-plain", OAuthToken = "sk-ant-oat-plain" } }; Core.Security.ConfigSecrets.Protect(config); config.ClaudeCode.ApiKey.ShouldNotBe("sk-ant-plain"); config.ClaudeCode.OAuthToken.ShouldNotBe("sk-ant-oat-plain"); Core.Security.ConfigSecrets.Unprotect(config); config.ClaudeCode.ApiKey.ShouldBe("sk-ant-plain"); config.ClaudeCode.OAuthToken.ShouldBe("sk-ant-oat-plain"); } }