summaryrefslogtreecommitdiff
path: root/tests/Agent.Core.Tests/InternalToolTests.cs
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-08-09 21:08:20 +0900
committertanaka takashi <isazaka@gmail.com>2026-08-09 21:08:20 +0900
commitb07b41cd435991cd254957af79055080c85b346e (patch)
tree27720d3ce14e4d0b84670a55ac26a2f877ce3a20 /tests/Agent.Core.Tests/InternalToolTests.cs
parent2fefd29e86c3b926d0cf4a362251d3f119688612 (diff)
feat: AI Agent Sandbox MCP Server v1を実装
Specifications、Design、ImplementationPlanの優先順位を整理し、単一Task実行モデル、typed option優先、設定可能なresource上限、local Ollamaを必須とするRelease Gateを文書化。 .NET 10/C# 14の5 production project、3 test project、E2E Clientを構築。typed YAML、en/ja locale、fixed/user prompt、stdioおよびloopback限定Streamable HTTP、agent_run/agent_status、Session、Ollama text/tool/Vision、TaskGraphとtoken accountingを実装。 Windows sandboxとしてfixed AppContainer、canonical/reparse検査、能力別ACL、path+SHA-256 executable approval、child-process restriction、Job Object、task journal、atomic file transaction、crash recoveryを実装。WFPのIPv4/IPv6 default denyとexact allow、dynamic session、temporary loopback exemption、管理者/BFE診断を追加。 Security/Recovery test、MCP/Ollama integration test、stdio/HTTP Operational E2E、Release Bundle生成・checksum・manifest・fresh extraction検査を追加。Requirement TraceabilityとPhase 0〜11の進捗、初回セットアップ可能なREADMEを整備。 Debug/Release/publish/objをrepository rootのbuild/へ集約し、旧project-local bin/objへの依存を除去。artifacts/とbuild/をGit管理外に設定し、clean checkoutでもsolution buildからE2E Clientまで生成可能にした。
Diffstat (limited to 'tests/Agent.Core.Tests/InternalToolTests.cs')
-rw-r--r--tests/Agent.Core.Tests/InternalToolTests.cs88
1 files changed, 88 insertions, 0 deletions
diff --git a/tests/Agent.Core.Tests/InternalToolTests.cs b/tests/Agent.Core.Tests/InternalToolTests.cs
new file mode 100644
index 0000000..1e241ce
--- /dev/null
+++ b/tests/Agent.Core.Tests/InternalToolTests.cs
@@ -0,0 +1,88 @@
+using System.Text.Json;
+using Agent.Core.Tools;
+
+namespace Agent.Core.Tests;
+
+public sealed class InternalToolTests
+{
+ [Fact]
+ public void Catalog_exposes_exactly_five_strict_tool_groups()
+ {
+ Assert.Equal(["file_search", "file_read", "file_edit", "process_run", "task_update"], InternalToolCatalog.All.Select(x => x.Name));
+ foreach (InternalToolDefinition tool in InternalToolCatalog.All) Assert.False(tool.Parameters.GetProperty("additionalProperties").GetBoolean());
+ }
+
+ [Theory]
+ [InlineData("file_read", "{}")]
+ [InlineData("file_read", "{\"path\":\"a\",\"mode\":\"whole\",\"unknown\":1}")]
+ [InlineData("file_read", "{\"path\":\"a\",\"mode\":\"invalid\"}")]
+ [InlineData("file_search", "{\"root\":\".\",\"mode\":\"text\",\"query\":\"x\",\"max_results\":1001}")]
+ [InlineData("process_run", "{\"executable\":\"x\",\"args\":\"bad\",\"cwd\":\".\"}")]
+ [InlineData("task_update", "{\"operation\":\"add\",\"description\":\"missing estimate\"}")]
+ public void Malformed_arguments_are_rejected(string tool, string json)
+ {
+ using JsonDocument document = JsonDocument.Parse(json);
+ Assert.Throws<ToolValidationException>(() => InternalToolValidator.Validate(tool, document.RootElement));
+ }
+
+ [Fact]
+ public async Task Policy_deny_has_no_backend_side_effect()
+ {
+ FakeInternalToolBackend backend = new();
+ InternalToolDispatcher dispatcher = new(new DenyByDefaultPolicy(), backend);
+ using JsonDocument document = JsonDocument.Parse("""{"path":"x","operation":"overwrite","content":"changed"}""");
+
+ ToolExecutionResult result = await dispatcher.DispatchAsync(Guid.NewGuid(), "file_edit", document.RootElement, TestContext.Current.CancellationToken);
+
+ Assert.False(result.Success);
+ Assert.False(result.ExternalEffect);
+ Assert.Empty(backend.Calls);
+ }
+
+ [Fact]
+ public async Task Allowed_call_reaches_fake_backend_only_after_validation_and_policy()
+ {
+ FakeInternalToolBackend backend = new();
+ InternalToolDispatcher dispatcher = new(new DenyByDefaultPolicy(new HashSet<string>(["file_read"], StringComparer.Ordinal)), backend);
+ using JsonDocument document = JsonDocument.Parse("""{"path":"x","mode":"whole"}""");
+
+ ToolExecutionResult result = await dispatcher.DispatchAsync(Guid.NewGuid(), "file_read", document.RootElement, TestContext.Current.CancellationToken);
+
+ Assert.True(result.Success);
+ Assert.Single(backend.Calls);
+ }
+
+ [Fact]
+ public void Guard_requires_effect_and_fails_on_fifth_identical_failure()
+ {
+ ToolUseGuard guard = new();
+ for (int attempt = 1; attempt < 5; attempt++) Assert.Equal(ToolUseGuardDecision.Retry, guard.Evaluate(true, false, false, "same").Decision);
+ ToolUseGuardResult fifth = guard.Evaluate(true, false, false, "same");
+ Assert.Equal(ToolUseGuardDecision.Fail, fifth.Decision);
+ Assert.Equal(5, fifth.IdenticalFailureCount);
+ }
+
+ [Fact]
+ public void Guard_does_not_force_tools_for_pure_analysis()
+ {
+ Assert.Equal(ToolUseGuardDecision.Complete, new ToolUseGuard().Evaluate(false, false, false).Decision);
+ }
+
+ [Fact]
+ public void Task_update_mutates_the_real_deterministic_graph_without_external_effect_evidence()
+ {
+ Guid scope = Guid.NewGuid();
+ Guid node = Guid.NewGuid();
+ var handler = new TaskGraphToolHandler();
+ handler.BeginTask(scope);
+ using JsonDocument add = JsonDocument.Parse($$"""{"operation":"add","task_id":"{{node}}","description":"Build fixture","depends_on":[],"budget_class":"1K","optimistic_tokens":10,"most_likely_tokens":20,"pessimistic_tokens":30}""");
+
+ ToolExecutionResult result = handler.Execute(scope, add.RootElement);
+
+ Assert.True(result.Success);
+ Assert.False(result.ExternalEffect);
+ Assert.Contains(node.ToString(), result.Output, StringComparison.OrdinalIgnoreCase);
+ handler.EndTask(scope);
+ Assert.Throws<InvalidOperationException>(() => handler.Execute(scope, add.RootElement));
+ }
+}