summaryrefslogtreecommitdiff
path: root/tests/Agent.Core.Tests/InternalToolTests.cs
diff options
context:
space:
mode:
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));
+ }
+}