using System.Text.Json; using ClawdDotNet.Core.Tools; using ClawdDotNet.Tools.FileRW; using Microsoft.Extensions.Logging.Abstractions; using Shouldly; namespace ClawdDotNet.Tools.Tests.FileRW; /// /// Test instance, 2026-10-02: a model sent destinationPath instead of path to write. /// The path resolved to the workspace root, the move onto the folder failed, and the error named /// the host's absolute folder. A missing path is the model's mistake and says so plainly. /// public sealed class MissingPathTests : IDisposable { private readonly string _workspace = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"), "Workspace"); public MissingPathTests() => Directory.CreateDirectory(_workspace); public void Dispose() { try { Directory.Delete(Path.GetDirectoryName(_workspace)!, recursive: true); } catch { /* cleanup is secondary */ } } private AgentToolContext Context() => new("desk", "inst", new Dictionary(), new NullStateStore(), NullLogger.Instance, CancellationToken.None, WorkspacePath: _workspace); [Theory] [InlineData("""{"action":"write","destinationPath":"wetter/x.txt","content":"x"}""", "destinationPath")] [InlineData("""{"action":"write","content":"x"}""", "'path' is required for write")] [InlineData("""{"action":"append","path":" ","content":"x"}""", "'path' is required for append")] [InlineData("""{"action":"read"}""", "'path' is required for read")] public async Task A_file_action_without_a_path_is_refused_without_naming_the_host_folder(string json, string expected) { var input = JsonDocument.Parse(json).RootElement; var tool = new FileRWTool(); var result = await tool.ExecuteAsync(input, Context(), CancellationToken.None); result.Success.ShouldBeFalse(); result.ErrorMessage.ShouldContain(expected); result.ErrorMessage!.ShouldNotContain(_workspace); tool.Preflight(input, Context()).ShouldNotBeNull("the check runs before staging as well"); Directory.EnumerateFileSystemEntries(_workspace).ShouldBeEmpty(); } [Fact] public async Task Listing_the_root_still_needs_no_path() { File.WriteAllText(Path.Combine(_workspace, "a.txt"), "x"); var result = await new FileRWTool().ExecuteAsync(JsonDocument.Parse("""{"action":"list"}""").RootElement, Context(), CancellationToken.None); result.Success.ShouldBeTrue(result.ErrorMessage); result.Content.ShouldContain("a.txt"); } }