Forge
csharpc0b81713
1using AgentNotes.Core;
2using System.Text;
3
4namespace AgentNotesMcp.Tests;
5
6public sealed class MultiRootKnowledgeTests
7{
8 [Fact]
9 public void ReadKnowledgeFile_UsesReadOnlyRoot_ByKnowledgeRootId()
10 {
11 using var primary = LocalSettingsLoaderTests.TempKnowledgeRoot.Create();
12 using var group = CreateGroupKbRoot();
13 using var runtime = InstallWithReadOnly(primary.Path, group.Path);
14 var storage = new NotesStorage();
15
16 var text = storage.ReadKnowledgeFile(null, "group/smoke-test-v1.md", knowledgeRootId: "group");
17 Assert.Contains("group-kb smoke", text, StringComparison.Ordinal);
18 }
19
20 [Fact]
21 public void WriteKnowledgeFile_ToReadOnlyRoot_Throws()
22 {
23 using var primary = LocalSettingsLoaderTests.TempKnowledgeRoot.Create();
24 using var group = CreateGroupKbRoot();
25 using var runtime = InstallWithReadOnly(primary.Path, group.Path);
26 var storage = new NotesStorage();
27
28 var ex = Assert.Throws<InvalidOperationException>(() =>
29 storage.WriteKnowledgeFile(null, "group/evil.md", "nope", knowledgeRootId: "group"));
30 Assert.Contains("read-only", ex.Message, StringComparison.OrdinalIgnoreCase);
31 }
32
33 [Fact]
34 public void WriteKnowledgeFile_ToReadOnlyPath_Throws()
35 {
36 using var primary = LocalSettingsLoaderTests.TempKnowledgeRoot.Create();
37 using var group = CreateGroupKbRoot();
38 using var runtime = InstallWithReadOnly(primary.Path, group.Path);
39 var storage = new NotesStorage();
40
41 var ex = Assert.Throws<InvalidOperationException>(() =>
42 storage.WriteKnowledgeFile(group.Path, "group/evil.md", "nope"));
43 Assert.Contains("read-only", ex.Message, StringComparison.OrdinalIgnoreCase);
44 }
45
46 [Fact]
47 public void KnowledgePath_And_KnowledgeRootId_AreMutuallyExclusive()
48 {
49 using var primary = LocalSettingsLoaderTests.TempKnowledgeRoot.Create();
50 using var group = CreateGroupKbRoot();
51 using var runtime = InstallWithReadOnly(primary.Path, group.Path);
52
53 var ex = Assert.Throws<ArgumentException>(() =>
54 NotesStorage.ResolveKnowledgeRoot(primary.Path, "group"));
55 Assert.Contains("not both", ex.Message, StringComparison.OrdinalIgnoreCase);
56 }
57
58 internal static AgentNotesTestToml.RuntimeScope InstallWithReadOnlyPublic(string primaryPath, string groupPath) =>
59 InstallWithReadOnly(primaryPath, groupPath);
60
61 internal static GroupKbRoot CreateGroupKbRootPublic() => CreateGroupKbRoot();
62
63 private static AgentNotesTestToml.RuntimeScope InstallWithReadOnly(
64 string primaryPath,
65 string groupPath)
66 {
67 var toml = $"""
68 version = 1
69
70 [knowledge]
71 primary = "test"
72
73 [knowledge.roots]
74 test = "{primaryPath.Replace('\\', '/')}"
75
76 [[knowledge.read_only]]
77 id = "group"
78 path = "{groupPath.Replace('\\', '/')}"
79
80 [workspace]
81 default_scope = "door-to-singularity"
82 scope_map = "work/local/workspace-scope-map-v1.md"
83 scope_aliases = "work/local/scope-alias-map-v1.md"
84
85 [status]
86 enabled = false
87 port = 17341
88 bind = "127.0.0.1"
89 """;
90 var path = Path.Combine(Path.GetTempPath(), "AgentNotesMcpTests", $"cfg-{Guid.NewGuid():N}.toml");
91 Directory.CreateDirectory(Path.GetDirectoryName(path)!);
92 File.WriteAllText(path, toml, Encoding.UTF8);
93 return AgentNotesTestToml.Install(path);
94 }
95
96 private static GroupKbRoot CreateGroupKbRoot()
97 {
98 var path = Path.Combine(Path.GetTempPath(), "AgentNotesMcpTests", "group-kb", Guid.NewGuid().ToString("N"));
99 Directory.CreateDirectory(Path.Combine(path, "knowledge", "group"));
100 File.WriteAllText(
101 Path.Combine(path, "knowledge", "group", "smoke-test-v1.md"),
102 "# Group KB smoke\n\ngroup-kb smoke test content.\n",
103 Encoding.UTF8);
104 File.WriteAllText(Path.Combine(path, "agent-notes.md"), "# Group hot (stub)\n", Encoding.UTF8);
105 return new GroupKbRoot(path);
106 }
107
108 internal sealed class GroupKbRoot(string path) : IDisposable
109 {
110 internal string Path { get; } = path;
111
112 public void Dispose()
113 {
114 try
115 {
116 if (Directory.Exists(Path))
117 Directory.Delete(Path, recursive: true);
118 }
119 catch (Exception ex)
120 {
121 throw new InvalidOperationException($"Failed to cleanup group-kb: {Path}", ex);
122 }
123 }
124 }
125}
126
View only · write via MCP/CIDE