using Agent.App.Cli; using Agent.App.Configuration; using Agent.App; using Agent.Core.Localization; using Agent.Core.Skills; 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(1, result.SchemaVersion); Assert.Equal("model", result.Ollama.Model); Assert.Equal("deny", result.Sandbox.Network.Mode); } [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: 1", "schema_version: 2", StringComparison.Ordinal)); Assert.Throws(() => ConfigurationLoader.Load(config)); } [Fact] public void CommandLineLanguageOverrideIsCaptured() { CommandLine result = CommandLine.Parse(["serve", "--lang", "ja", "--transport", "http"]); Assert.Equal("ja", result.Language); Assert.Equal("http", result.Transport); } [Fact] public void CommandLine_defaults_to_serve_when_command_is_omitted() { Assert.Equal(AppCommand.Serve, CommandLine.Parse([]).Command); CommandLine withOption = CommandLine.Parse(["--lang", "ja"]); Assert.Equal(AppCommand.Serve, withOption.Command); Assert.Equal("ja", withOption.Language); } [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: filesystem: read: [C:/local-only] """); AgentConfiguration result = ConfigurationLoader.Load(config); Assert.Equal("ja", result.Application.Language); Assert.Equal(["C:/local-only"], result.Sandbox.Filesystem.Read); 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 Execute_file_without_process_approval_reports_canonical_keys() { using var directory = new TestDirectory(); string config = CreateValidConfiguration(directory); string executable = directory.File("tools/tool.exe", "fixture").Replace('\\', '/'); directory.File("agent.local.yaml", $$""" sandbox: filesystem: execute: [{{executable}}] """); ConfigurationException error = Assert.Throws(() => ConfigurationLoader.Load(config)); Assert.Contains("sandbox.filesystem.execute", error.Message, StringComparison.Ordinal); Assert.Contains("sandbox.process.approvals", error.Message, StringComparison.Ordinal); Assert.Contains(Path.GetFullPath(executable), error.Message, StringComparison.OrdinalIgnoreCase); } [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(["--lang", "ja"], new CommandLineException("bad option")); Assert.Contains("コマンドラインが不正です", message, StringComparison.Ordinal); Assert.Contains("bad option", message, 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: 1 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 """); } }