using Agent.App.Cli; using Agent.App.Configuration; using Agent.App; using Agent.Core.Localization; using Agent.Core.Skills; using Agent.App.Sandbox; using Agent.Windows.Security; namespace Agent.Core.Tests; public sealed class ConfigurationTests { [Fact] public void ValidYamlLoadsTypedConfiguration() { using var directory = new TestDirectory(); string config = CreateValidConfiguration(directory); AgentConfiguration result = ConfigurationLoader.Load(config); Assert.Equal(2, result.SchemaVersion); Assert.Equal("model", result.Ollama.Model); Assert.Equal("none", result.Sandbox.Network.Access); } [Fact] public void UnknownYamlPropertyIsFatal() { using var directory = new TestDirectory(); string config = CreateValidConfiguration(directory); System.IO.File.AppendAllText(config, "unknown_root: true\n"); Assert.Throws(() => ConfigurationLoader.Load(config)); } [Fact] public void InvalidSchemaIsFatal() { using var directory = new TestDirectory(); string config = CreateValidConfiguration(directory); System.IO.File.WriteAllText(config, System.IO.File.ReadAllText(config).Replace("schema_version: 2", "schema_version: 1", StringComparison.Ordinal)); Assert.Throws(() => ConfigurationLoader.Load(config)); } [Theory] [InlineData("true")] [InlineData("false")] [InlineData("low")] [InlineData("medium")] [InlineData("high")] [InlineData("max")] public void Ollama_think_accepts_native_boolean_and_level_values(string value) { using var directory = new TestDirectory(); string config = CreateValidConfiguration(directory); System.IO.File.WriteAllText(config, System.IO.File.ReadAllText(config).Replace(" model: model", $" model: model\n think: {value}", StringComparison.Ordinal)); AgentConfiguration result = ConfigurationLoader.Load(config); Assert.Equal(value is "true" or "false" ? bool.Parse(value) : value, result.Ollama.Think); } [Fact] public void Ollama_think_rejects_unknown_level() { using var directory = new TestDirectory(); string config = CreateValidConfiguration(directory); System.IO.File.WriteAllText(config, System.IO.File.ReadAllText(config).Replace(" model: model", " model: model\n think: extreme", StringComparison.Ordinal)); ConfigurationException error = Assert.Throws(() => ConfigurationLoader.Load(config)); Assert.Contains("ollama.think", error.Message, StringComparison.Ordinal); } [Fact] public void Removed_commands_and_configuration_overrides_are_rejected() { Assert.Throws(() => CommandLine.Parse(["serve"])); Assert.Throws(() => CommandLine.Parse(["check"])); Assert.Throws(() => CommandLine.Parse(["executable", "list"])); Assert.Throws(() => CommandLine.Parse(["sandbox", "show"])); Assert.Throws(() => CommandLine.Parse(["--lang", "ja"])); Assert.Throws(() => CommandLine.Parse(["--transport", "http"])); } [Fact] public void CommandLine_starts_server_when_command_is_omitted() { Assert.Equal(AppCommand.Start, CommandLine.Parse([]).Command); CommandLine withPath = CommandLine.Parse(["custom.yaml"]); Assert.Equal(AppCommand.Start, withPath.Command); Assert.Equal("custom.yaml", withPath.ConfigPath); } [Fact] public void Local_configuration_recursively_merges_maps_and_replaces_sequences() { using var directory = new TestDirectory(); string config = CreateValidConfiguration(directory); directory.File("agent.local.yaml", """ application: language: ja sandbox: workspaces: [C:/local-only] """); AgentConfiguration result = ConfigurationLoader.Load(config); Assert.Equal("ja", result.Application.Language); Assert.Equal(["C:/local-only"], result.Sandbox.Workspaces); Assert.Equal("model", result.Ollama.Model); } [Fact] public void Unknown_local_configuration_property_is_fatal() { using var directory = new TestDirectory(); string config = CreateValidConfiguration(directory); directory.File("agent.local.yaml", "unknown_local: true\n"); Assert.Throws(() => ConfigurationLoader.Load(config)); } [Fact] public void Legacy_filesystem_and_process_schema_is_rejected_with_migration_diagnostic() { using var directory = new TestDirectory(); string config = CreateValidConfiguration(directory); directory.File("agent.local.yaml", $$""" sandbox: filesystem: execute: [C:/legacy/tool.exe] process: approvals: [] """); ConfigurationException error = Assert.Throws(() => ConfigurationLoader.Load(config)); Assert.Contains("Legacy sandbox.filesystem/sandbox.process", error.Message, StringComparison.Ordinal); Assert.Contains("agent-mcp apply", error.Message, StringComparison.Ordinal); } [Fact] public void Semantic_sandbox_rejects_readonly_write_mount_and_conflicting_network_endpoints() { using var directory = new TestDirectory(); string config = CreateValidConfiguration(directory); directory.File("agent.local.yaml", """ sandbox: profile: readonly mounts: - path: C:/data access: read_write network: access: none local_endpoints: - port: 11434 """); ConfigurationException error = Assert.Throws(() => ConfigurationLoader.Load(config)); Assert.Contains("readonly", error.Message, StringComparison.OrdinalIgnoreCase); } [Fact] public void Apply_and_show_accept_only_a_positional_config_path_and_apply_yes() { CommandLine apply = CommandLine.Parse(["apply", "agent.yaml", "--yes"]); Assert.Equal(AppCommand.Apply, apply.Command); Assert.Equal("agent.yaml", apply.ConfigPath); Assert.True(apply.Yes); CommandLine show = CommandLine.Parse(["show", "agent.yaml"]); Assert.Equal(AppCommand.Show, show.Command); Assert.False(show.Yes); Assert.Throws(() => CommandLine.Parse(["show", "--yes"])); Assert.Throws(() => CommandLine.Parse(["apply", "a.yaml", "b.yaml"])); } [Fact] public void Shell_classification_accepts_only_the_documented_command_interpreters() { Assert.True(ExecutableResolver.IsSupportedShell("C:/Windows/System32/cmd.exe")); Assert.True(ExecutableResolver.IsSupportedShell("C:/Program Files/PowerShell/7/pwsh.exe")); Assert.False(ExecutableResolver.IsSupportedShell("C:/tools/python.exe")); } [Fact] public void Applied_snapshot_is_invalidated_by_configuration_change() { using var directory = new TestDirectory(); string configPath = directory.File("agent.yaml", "schema_version: 2"); string workspace = Path.Combine(directory.Path, "workspace"); Directory.CreateDirectory(workspace); string stores = Path.Combine(directory.Path, "stores"); var configuration = new AgentConfiguration { Sandbox = new SandboxOptions { Profile = "development", Workspaces = [workspace] } }; var store = new AppliedConfigurationStore(configPath, stores, protectDirectory: false); var resolver = new AppliedSandboxConfiguration(configPath, configuration, store); _ = resolver.Apply(); _ = resolver.Compile(Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups")); configuration.Sandbox.Resources.Profile = "extended"; ConfigurationException error = Assert.Throws(() => resolver.Compile(Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"))); Assert.Equal("sandbox.configuration_not_applied", error.Code); } [Fact] public void Applied_snapshot_never_refreshes_a_changed_executable_identity() { using var directory = new TestDirectory(); string configPath = directory.File("agent.yaml", "schema_version: 2"); string executable = directory.File("fixture.exe", "first"); string stores = Path.Combine(directory.Path, "stores"); var configuration = new AgentConfiguration(); configuration.Sandbox.Execution.Entries.Add(new() { Alias = "fixture", Path = executable }); var store = new AppliedConfigurationStore(configPath, stores, protectDirectory: false); var resolver = new AppliedSandboxConfiguration(configPath, configuration, store); _ = resolver.Apply(); File.WriteAllText(executable, "second"); ConfigurationException error = Assert.Throws(() => resolver.Compile(Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"))); Assert.Equal("sandbox.configuration_not_applied", error.Code); Assert.Single(store.Load()!.Execution); Assert.Empty(Directory.EnumerateFiles(stores, "*.tmp")); } [Fact] public void Reapply_replaces_the_complete_snapshot_and_removes_deleted_entries() { using var directory = new TestDirectory(); string configPath = directory.File("agent.yaml", "schema_version: 2"); string executable = directory.File("fixture.exe", "first"); string stores = Path.Combine(directory.Path, "stores"); var configuration = new AgentConfiguration(); configuration.Sandbox.Execution.Entries.Add(new() { Alias = "fixture", Path = executable }); var store = new AppliedConfigurationStore(configPath, stores, protectDirectory: false); var applied = new AppliedSandboxConfiguration(configPath, configuration, store); _ = applied.Apply(); Assert.Single(store.Load()!.Execution); configuration.Sandbox.Execution.Entries.Clear(); _ = applied.Apply(); AppliedConfigurationDocument document = store.Load()!; Assert.Empty(document.Execution); Assert.Equal("none", document.Network.Access); Assert.Equal(4096, document.Resources.MemoryMb); } [Fact] public void Applied_store_rejects_malformed_records() { using var directory = new TestDirectory(); string configPath = directory.File("agent.yaml", "schema_version: 2"); var store = new AppliedConfigurationStore(configPath, Path.Combine(directory.Path, "stores"), protectDirectory: false); Directory.CreateDirectory(Path.GetDirectoryName(store.StorePath)!); File.WriteAllText(store.StorePath, """ { "schemaVersion": 2, "configPath": "relative/agent.yaml", "configFingerprint": "not-a-hash", "filesystem": [], "execution": [{ "alias": "Python With Spaces", "classification": "unknown", "logicalPath": "python.exe", "resolvedPath": "relative/python.exe", "sha256": "not-a-hash", "logicalRuntimeRoot": null, "resolvedRuntimeRoot": null }], "appliedAt": "2026-08-12T00:00:00Z" } """); Assert.Throws(() => store.Load()); } [Fact] public void Narrow_nested_mount_is_rejected_instead_of_silently_widened() { using var directory = new TestDirectory(); string configPath = directory.File("agent.yaml", "schema_version: 2"); string workspace = Path.Combine(directory.Path, "workspace"); string nested = Path.Combine(workspace, "read-only-data"); Directory.CreateDirectory(nested); string stores = Path.Combine(directory.Path, "stores"); var configuration = new AgentConfiguration { Sandbox = new SandboxOptions { Profile = "development", Workspaces = [workspace], Mounts = [new MountOptions { Path = nested, Access = "read_only" }] } }; var resolver = new AppliedSandboxConfiguration(configPath, configuration, new AppliedConfigurationStore(configPath, stores, protectDirectory: false)); _ = resolver.Apply(); ConfigurationException error = Assert.Throws(() => resolver.Compile(Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups"))); Assert.Contains("cannot be enforced", error.Message, StringComparison.Ordinal); } [Fact] public void Execution_entry_inside_development_workspace_inherits_workspace_capability() { using var directory = new TestDirectory(); string configPath = directory.File("agent.yaml", "schema_version: 2"); string workspace = Path.Combine(directory.Path, "workspace"); Directory.CreateDirectory(workspace); string executable = Path.Combine(workspace, "tool.exe"); File.WriteAllText(executable, "fixture"); var configuration = new AgentConfiguration { Sandbox = new SandboxOptions { Profile = "development", Workspaces = [workspace] } }; configuration.Sandbox.Execution.Entries.Add(new() { Alias = "tool", Path = executable }); var applied = new AppliedSandboxConfiguration(configPath, configuration, new AppliedConfigurationStore(configPath, Path.Combine(directory.Path, "stores"), protectDirectory: false)); _ = applied.Apply(); Agent.Windows.Tools.EffectiveSandboxPolicy policy = applied.Compile( Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups")); Assert.Contains(policy.EntryExecutables, item => item.Alias == "tool"); Assert.Contains(policy.Files, item => item.Path.Equals(workspace, StringComparison.OrdinalIgnoreCase) && item.Capabilities.HasFlag(FileCapability.Execute)); } [Fact] public void Profile_network_and_resource_configuration_compile_to_effective_policy() { using var directory = new TestDirectory(); string configPath = directory.File("agent.yaml", "schema_version: 2"); string workspace = Path.Combine(directory.Path, "workspace"); Directory.CreateDirectory(workspace); string stores = Path.Combine(directory.Path, "stores"); var configuration = new AgentConfiguration { Sandbox = new SandboxOptions { Profile = "development", Workspaces = [workspace], Network = new NetworkOptions { Access = "local", LocalEndpoints = [new LocalEndpointOptions { Port = 11434 }] }, Resources = new ResourceOptions { Profile = "constrained", ProcessLimit = 6, ExecutionTimeoutSeconds = 450 } } }; var resolver = new AppliedSandboxConfiguration(configPath, configuration, new AppliedConfigurationStore(configPath, stores, protectDirectory: false)); _ = resolver.Apply(); Agent.Windows.Tools.EffectiveSandboxPolicy policy = resolver.Compile( Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups")); Assert.Contains(workspace, policy.ArtifactRoots, StringComparer.OrdinalIgnoreCase); Assert.Contains(policy.Files, item => item.Path.Equals(workspace, StringComparison.OrdinalIgnoreCase) && item.Capabilities == (FileCapability.Read | FileCapability.Write | FileCapability.Delete | FileCapability.Execute)); Assert.Equal(6u, policy.JobLimits.MaxProcesses); Assert.Equal(1024UL * 1024 * 1024, policy.JobLimits.MaxJobMemoryBytes); Assert.Equal(450, policy.ProcessTimeoutSeconds); Assert.Contains(policy.NetworkAllowances, item => item.Host == "127.0.0.1" && item.Ports.SequenceEqual([11434])); Assert.Contains(policy.NetworkAllowances, item => item.Host == "::1" && item.Ports.SequenceEqual([11434])); } [Fact] public void Readonly_profile_never_creates_an_artifact_root_or_execute_capability() { using var directory = new TestDirectory(); string configPath = directory.File("agent.yaml", "schema_version: 2"); string workspace = Path.Combine(directory.Path, "workspace"); Directory.CreateDirectory(workspace); string stores = Path.Combine(directory.Path, "stores"); var configuration = new AgentConfiguration { Sandbox = new SandboxOptions { Profile = "readonly", Workspaces = [workspace] } }; var resolver = new AppliedSandboxConfiguration(configPath, configuration, new AppliedConfigurationStore(configPath, stores, protectDirectory: false)); _ = resolver.Apply(); Agent.Windows.Tools.EffectiveSandboxPolicy policy = resolver.Compile( Path.Combine(directory.Path, "journals"), Path.Combine(directory.Path, "backups")); Assert.Empty(policy.ArtifactRoots); Assert.Contains(policy.Files, item => item.Path.Equals(workspace, StringComparison.OrdinalIgnoreCase) && item.Capabilities == FileCapability.Read); } [Fact] public void Runtime_configuration_values_are_propagated() { var configuration = new AgentConfiguration { Agent = new RuntimeOptions { MaxIterations = 41, SameFailureLimit = 4 }, Planner = new PlannerOptions { MaxTasks = 17 }, Ollama = new OllamaConfiguration { Model = "model-x", Options = new OllamaGenerationOptions { NumCtx = 12345 } }, Limits = new LimitOptions { ContextSummaryRatio = 0.65 } }; LocaleCatalog locale = LocaleCatalog.Load(Path.GetFullPath("config/locales"), "ja"); Agent.Core.Runtime.AgentRuntimeSettings settings = Program.CreateRuntimeSettings(configuration, "prompt", "ja", [], locale); Assert.Equal(41, settings.MaxIterations); Assert.Equal(4, settings.SameFailureLimit); Assert.Equal(17, settings.MaxTasks); Assert.Equal(12345, settings.NumCtx); Assert.Equal(0.65, settings.ContextSummaryThreshold); Assert.Equal("model-x", settings.Model); } [Fact] public void Startup_errors_follow_the_cli_locale() { string message = Program.FormatStartupError([], new CommandLineException("bad option")); Assert.Contains("Invalid command line", message, StringComparison.Ordinal); Assert.Contains("bad option", message, StringComparison.Ordinal); string sandbox = Program.FormatStartupError([], new ConfigurationException( "internal", code: "sandbox.configuration_not_applied", category: "sandbox", target: "C:/config/agent.yaml", resolution: "internal resolution")); Assert.Contains("not been applied", sandbox, StringComparison.Ordinal); Assert.Contains("agent-mcp apply", sandbox, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("internal resolution", sandbox, StringComparison.Ordinal); } [Fact] public void SkillLoader_loads_standard_skill_directories_only() { using var directory = new TestDirectory(); // Legacy single markdown — should be ignored (not loaded). directory.File("skills/legacy.md", "legacy skill"); // Standard skill directory with frontmatter. directory.File("skills/python/SKILL.md", "---\nname: python\ndescription: Python execution skill.\n---\n# Python\nInstructions here."); // Non-SKILL.md files in skill directory should not be loaded as skills. directory.File("skills/python/reference.md", "must not be loaded as a skill"); var skills = SkillLoader.Load(Path.Combine(directory.Path, "skills")); Assert.Single(skills); Assert.Equal("python", skills[0].Name); Assert.Equal("Python execution skill.", skills[0].Description); Assert.Contains("Instructions here.", skills[0].Instructions); } [Fact] public void SkillLoader_rejects_missing_frontmatter() { using var directory = new TestDirectory(); directory.File("skills/no-frontmatter/SKILL.md", "# No frontmatter\nJust body."); Assert.Throws(() => SkillLoader.Load(Path.Combine(directory.Path, "skills"))); } [Fact] public void SkillLoader_rejects_name_mismatch() { using var directory = new TestDirectory(); directory.File("skills/wrong-name/SKILL.md", "---\nname: different-name\ndescription: A skill.\n---\nBody."); Assert.Throws(() => SkillLoader.Load(Path.Combine(directory.Path, "skills"))); } [Fact] public void SkillLoader_rejects_duplicate_names() { using var directory = new TestDirectory(); directory.File("skills/dup-skill/SKILL.md", "---\nname: dup-skill\ndescription: First.\n---\nBody1."); // Create a second directory with the same skill name (via symlink or junction is not needed; // just create another dir that also has name: dup-skill in frontmatter). directory.File("skills/dup-skill-copy/SKILL.md", "---\nname: dup-skill\ndescription: Second.\n---\nBody2."); Assert.Throws(() => SkillLoader.Load(Path.Combine(directory.Path, "skills"))); } private static string CreateValidConfiguration(TestDirectory directory) { directory.File("prompts/user-system.md", string.Empty); Directory.CreateDirectory(System.IO.Path.Combine(directory.Path, "config/locales")); Directory.CreateDirectory(System.IO.Path.Combine(directory.Path, "skills")); return directory.File("agent.yaml", """ schema_version: 2 application: language: en agent: user_system_prompt: prompts/user-system.md ollama: endpoint: http://127.0.0.1:11434 model: model localization: directory: config/locales fallback: en skills: directory: skills """); } }