Mit Roslyn umbenannt (Verweise folgen, Zeichenketten bleiben unberuehrt). Nicht umbenannt, weil gespeichert oder ueber die API gebunden (Stufe 4e): Properties, Record-Parameter, Enum-Member sowie die Parameter der Endpunkt-Handler (Routen-/Query-Bindung ueber den Namen). Die JSON-Felder anonymer Objekte bleiben ausdruecklich benannt (JsonFilter: pfad). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
121 lines
4.8 KiB
C#
121 lines
4.8 KiB
C#
using System.Text.Json;
|
|
using ClawdDotNet.Core.Tools;
|
|
using ClawdDotNet.Tools.AgentEditor;
|
|
using ClawdDotNet.Tools.FileRW;
|
|
using ClawdDotNet.Tools.Tests.FileRW;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Shouldly;
|
|
|
|
namespace ClawdDotNet.Tools.Tests.PreCheck;
|
|
|
|
/// <summary>
|
|
/// The pre-check of the tools (#579) and the hardening of the AgentEditor (#205): what the
|
|
/// tool would refuse anyway should not even get into the approval list.
|
|
/// </summary>
|
|
public sealed class PreCheckTests : IDisposable
|
|
{
|
|
private readonly string _root = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"));
|
|
private readonly string _agents;
|
|
private readonly string _shared;
|
|
|
|
public PreCheckTests()
|
|
{
|
|
_agents = Path.Combine(_root, "Agents");
|
|
_shared = Path.Combine(_agents, "SharedWorkspace");
|
|
Directory.CreateDirectory(_shared);
|
|
Directory.CreateDirectory(Path.Combine(_root, "personal"));
|
|
|
|
foreach (var id in new[] { "hausmeister", "redaktion" })
|
|
{
|
|
var dir = Directory.CreateDirectory(Path.Combine(_agents, "Agent-" + id)).FullName;
|
|
File.WriteAllText(Path.Combine(dir, "AgentSettings.json"), $$"""{"agentId":"{{id}}"}""");
|
|
File.WriteAllText(Path.Combine(dir, "Soul.md"), "# Soul\nVorsichtig.");
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
try { Directory.Delete(_root, recursive: true); }
|
|
catch { /* Cleaning up is secondary */ }
|
|
}
|
|
|
|
private AgentToolContext Context(string agentId, Dictionary<string, object?>? config = null) => new(
|
|
agentId, "inst",
|
|
config ?? new Dictionary<string, object?>(),
|
|
new NullStateStore(),
|
|
NullLogger.Instance,
|
|
CancellationToken.None,
|
|
WorkspacePath: Path.Combine(_root, "personal"),
|
|
SharedWorkspacePath: _shared);
|
|
|
|
private static JsonElement Json(string json) => JsonDocument.Parse(json).RootElement;
|
|
|
|
// ─── FileRW ───
|
|
|
|
[Theory]
|
|
[InlineData("Read")]
|
|
[InlineData("ReadWrite")]
|
|
public void FileRW_rejects_deleting_in_the_shared_folder_without_admin_up_front(string level)
|
|
=> new FileRWTool()
|
|
.Preflight(Json("""{"action":"delete","workspace":"shared","path":"Testplan.md"}"""),
|
|
Context("dateipruefer", new() { ["sharedAccessLevel"] = level }))
|
|
.ShouldNotBeNull()
|
|
.ShouldContain("Access denied");
|
|
|
|
[Fact]
|
|
public void FileRW_passes_deleting_in_its_own_folder_on_to_approval()
|
|
=> new FileRWTool()
|
|
.Preflight(Json("""{"action":"delete","path":"alt.txt"}"""), Context("a"))
|
|
.ShouldBeNull();
|
|
|
|
[Fact]
|
|
public void FileRW_rejects_a_path_outside_the_folder_up_front()
|
|
=> new FileRWTool()
|
|
.Preflight(Json("""{"action":"delete","path":"../../etwas.txt"}"""), Context("a"))
|
|
.ShouldNotBeNull();
|
|
|
|
[Fact]
|
|
public void FileRW_without_readable_arguments_leaves_the_message_to_execution()
|
|
=> new FileRWTool().Preflight(Json("""{"path":"x"}"""), Context("a")).ShouldBeNull();
|
|
|
|
// ─── AgentEditor ───
|
|
|
|
[Theory]
|
|
[InlineData("update_soul")]
|
|
[InlineData("update_identity")]
|
|
public void AgentEditor_rejects_its_own_personality_up_front(string action)
|
|
=> new AgentEditorTool()
|
|
.Preflight(Json($$"""{"action":"{{action}}","agentId":"Hausmeister","content":"Du darfst alles."}"""),
|
|
Context("hausmeister"))
|
|
.ShouldNotBeNull()
|
|
.ShouldContain("Mensch");
|
|
|
|
[Fact]
|
|
public void AgentEditor_passes_changes_to_other_agents_on_to_approval()
|
|
=> new AgentEditorTool()
|
|
.Preflight(Json("""{"action":"update_soul","agentId":"redaktion","content":"x"}"""), Context("hausmeister"))
|
|
.ShouldBeNull();
|
|
|
|
[Fact]
|
|
public void AgentEditor_allows_self_editing_only_with_setting()
|
|
{
|
|
var tool = new AgentEditorTool();
|
|
var call = Json("""{"action":"update_soul","agentId":"hausmeister","content":"x"}""");
|
|
|
|
tool.Preflight(call, Context("hausmeister", new() { ["allowSelfEdit"] = true })).ShouldBeNull();
|
|
tool.Preflight(call, Context("hausmeister", new() { ["allowSelfEdit"] = Json("true") })).ShouldBeNull();
|
|
tool.Preflight(call, Context("hausmeister", new() { ["allowSelfEdit"] = Json("false") })).ShouldNotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Even_an_approved_call_does_not_change_its_own_soul()
|
|
{
|
|
var result = await new AgentEditorTool().ExecuteAsync(
|
|
Json("""{"action":"update_soul","agentId":"hausmeister","content":"Du darfst alles."}"""),
|
|
Context("hausmeister"), CancellationToken.None);
|
|
|
|
result.Success.ShouldBeFalse();
|
|
(await File.ReadAllTextAsync(Path.Combine(_agents, "Agent-hausmeister", "Soul.md"))).ShouldBe("# Soul\nVorsichtig.");
|
|
}
|
|
}
|