Forge
csharp4405de34
1using System.Text;
2using AgentNotes.Core;
3using CascadeIDE.Models;
4using CascadeIDE.Services;
5using Xunit;
6
7namespace CascadeIDE.Tests;
8
9public sealed class McpAgentNotesServiceMultiRootTests
10{
11 [Fact]
12 public void ReadKnowledgeFile_UsesReadOnlyRoot_ByKnowledgeRootId()
13 {
14 using var primary = CreatePrimaryRoot();
15 using var group = CreateGroupKbRoot();
16 using var scope = InstallRuntime(primary.Path, group.Path, out var settings);
17 var svc = new McpAgentNotesService(() => settings);
18
19 var text = svc.ReadKnowledgeFile("group/smoke-test-v1.md", knowledgeRootId: "group");
20 Assert.Contains("group-kb smoke", text, StringComparison.Ordinal);
21 }
22
23 [Fact]
24 public void WriteKnowledgeFile_ToReadOnlyRoot_ReturnsError()
25 {
26 using var primary = CreatePrimaryRoot();
27 using var group = CreateGroupKbRoot();
28 using var scope = InstallRuntime(primary.Path, group.Path, out var settings);
29 var svc = new McpAgentNotesService(() => settings);
30
31 var result = svc.WriteKnowledgeFile("group/evil.md", "nope", knowledgePath: null, saveRevision: false, knowledgeRootId: "group");
32 Assert.Contains("read-only", result, StringComparison.OrdinalIgnoreCase);
33 }
34
35 private static PrimaryRoot CreatePrimaryRoot()
36 {
37 var path = Path.Combine(Path.GetTempPath(), "CascadeIdeAnTests", "primary", Guid.NewGuid().ToString("N"));
38 Directory.CreateDirectory(Path.Combine(path, "knowledge", "work", "local"));
39 File.WriteAllText(Path.Combine(path, "agent-notes.md"), "# hot\n", Encoding.UTF8);
40 File.WriteAllText(Path.Combine(path, "knowledge", "work", "local", "workspace-scope-map-v1.md"), "# map\n", Encoding.UTF8);
41 return new PrimaryRoot(path);
42 }
43
44 private static GroupKbRoot CreateGroupKbRoot()
45 {
46 var path = Path.Combine(Path.GetTempPath(), "CascadeIdeAnTests", "group", Guid.NewGuid().ToString("N"));
47 Directory.CreateDirectory(Path.Combine(path, "knowledge", "group"));
48 File.WriteAllText(
49 Path.Combine(path, "knowledge", "group", "smoke-test-v1.md"),
50 "# Group KB smoke\n\ngroup-kb smoke test content.\n",
51 Encoding.UTF8);
52 File.WriteAllText(Path.Combine(path, "agent-notes.md"), "# Group hot (stub)\n", Encoding.UTF8);
53 return new GroupKbRoot(path);
54 }
55
56 private static RuntimeScope InstallRuntime(string primaryPath, string groupPath, out CascadeIdeSettings settings)
57 {
58 var tomlPath = Path.Combine(Path.GetTempPath(), "CascadeIdeAnTests", $"cfg-{Guid.NewGuid():N}.toml");
59 Directory.CreateDirectory(Path.GetDirectoryName(tomlPath)!);
60 File.WriteAllText(
61 tomlPath,
62 $"""
63 version = 1
64
65 [knowledge]
66 primary = "test"
67
68 [knowledge.roots]
69 test = "{primaryPath.Replace('\\', '/')}"
70
71 [[knowledge.read_only]]
72 id = "group"
73 path = "{groupPath.Replace('\\', '/')}"
74
75 [workspace]
76 default_scope = "door-to-singularity"
77 scope_map = "work/local/workspace-scope-map-v1.md"
78 scope_aliases = "work/local/scope-alias-map-v1.md"
79
80 [status]
81 enabled = false
82 port = 17341
83 bind = "127.0.0.1"
84 """,
85 Encoding.UTF8);
86
87 AgentNotesRuntimeLoader.Reset();
88 settings = new CascadeIdeSettings { AgentNotes = new AgentNotesSettings { ConfigPath = tomlPath } };
89 Assert.True(AgentNotesRuntimeLoader.EnsureInitialized(settings));
90 return new RuntimeScope(tomlPath);
91 }
92
93 private sealed class PrimaryRoot(string path) : IDisposable
94 {
95 internal string Path { get; } = path;
96
97 public void Dispose() => TryDelete(Path);
98 }
99
100 private sealed class GroupKbRoot(string path) : IDisposable
101 {
102 internal string Path { get; } = path;
103
104 public void Dispose() => TryDelete(Path);
105 }
106
107 private sealed class RuntimeScope(string tomlPath) : IDisposable
108 {
109 public void Dispose()
110 {
111 AgentNotesRuntimeLoader.Reset();
112 TryDelete(tomlPath);
113 }
114 }
115
116 private static void TryDelete(string path)
117 {
118 try
119 {
120 if (File.Exists(path))
121 File.Delete(path);
122 else if (Directory.Exists(path))
123 Directory.Delete(path, recursive: true);
124 }
125 catch
126 {
127 // best-effort temp cleanup
128 }
129 }
130}
131
View only · write via MCP/CIDE