| 1 | using AgentNotes.Core; |
| 2 | |
| 3 | namespace AgentNotesMcp.Tests; |
| 4 | |
| 5 | public sealed class LocalSettingsLoaderTests |
| 6 | { |
| 7 | [Fact] |
| 8 | public void Bootstrap_MissingConfig_ReturnsExitCode2() |
| 9 | { |
| 10 | using var clear = TestEnvVarScope.Clear(AgentNotesBootstrap.ConfigEnvVar); |
| 11 | var code = AgentNotesBootstrap.TryLoadSettings([], out var settings, out var error); |
| 12 | Assert.Equal(AgentNotesBootstrap.ExitMissingConfig, code); |
| 13 | Assert.Null(settings); |
| 14 | Assert.Contains("--config", error, StringComparison.OrdinalIgnoreCase); |
| 15 | } |
| 16 | |
| 17 | [Fact] |
| 18 | public void Loader_ResolvesPrimaryFromNamedRoot() |
| 19 | { |
| 20 | using var root = TempKnowledgeRoot.Create(); |
| 21 | var tomlPath = AgentNotesTestToml.WriteFromEmbeddedTemplate(root.Path); |
| 22 | var settings = LocalSettingsLoader.Load(tomlPath); |
| 23 | Assert.Equal(root.Path, settings.PrimaryKnowledgeRoot); |
| 24 | Assert.Equal("door-to-singularity", settings.Workspace.DefaultScope); |
| 25 | Assert.Equal("work/local/workspace-scope-map-v1.md", settings.Workspace.ScopeMapRelative); |
| 26 | } |
| 27 | |
| 28 | [Fact] |
| 29 | public void Loader_WithoutWorkspaceSection_UsesEmbeddedNeutralExample() |
| 30 | { |
| 31 | using var root = TempKnowledgeRoot.Create(); |
| 32 | var toml = $""" |
| 33 | version = 1 |
| 34 | |
| 35 | [knowledge] |
| 36 | primary = "test" |
| 37 | |
| 38 | [knowledge.roots] |
| 39 | test = "{root.Path.Replace('\\', '/')}" |
| 40 | """; |
| 41 | var path = Path.Combine(Path.GetTempPath(), "AgentNotesMcpTests", $"cfg-{Guid.NewGuid():N}.toml"); |
| 42 | Directory.CreateDirectory(Path.GetDirectoryName(path)!); |
| 43 | File.WriteAllText(path, toml); |
| 44 | var settings = LocalSettingsLoader.Load(path); |
| 45 | Assert.Equal("example", settings.Workspace.DefaultScope); |
| 46 | Assert.Equal("example/workspace-scope-map-v1.md", settings.Workspace.ScopeMapRelative); |
| 47 | } |
| 48 | |
| 49 | [Fact] |
| 50 | public void Loader_RejectsInvalidWorkspaceScopeMapPath() |
| 51 | { |
| 52 | using var root = TempKnowledgeRoot.Create(); |
| 53 | var toml = $""" |
| 54 | version = 1 |
| 55 | |
| 56 | [knowledge] |
| 57 | primary = "test" |
| 58 | |
| 59 | [knowledge.roots] |
| 60 | test = "{root.Path.Replace('\\', '/')}" |
| 61 | |
| 62 | [workspace] |
| 63 | scope_map = "../evil-map.md" |
| 64 | """; |
| 65 | var path = Path.Combine(Path.GetTempPath(), "AgentNotesMcpTests", $"cfg-{Guid.NewGuid():N}.toml"); |
| 66 | Directory.CreateDirectory(Path.GetDirectoryName(path)!); |
| 67 | File.WriteAllText(path, toml); |
| 68 | var ex = Assert.Throws<InvalidOperationException>(() => LocalSettingsLoader.Load(path)); |
| 69 | Assert.Contains("relative under knowledge/", ex.Message, StringComparison.OrdinalIgnoreCase); |
| 70 | } |
| 71 | |
| 72 | [Fact] |
| 73 | public void GetNotesPath_UsesPrimaryRootFromRuntime() |
| 74 | { |
| 75 | using var root = TempKnowledgeRoot.Create(); |
| 76 | using var runtime = AgentNotesTestToml.Install(AgentNotesTestToml.WriteFromEmbeddedTemplate(root.Path)); |
| 77 | var storage = new NotesStorage(); |
| 78 | var notesPath = storage.GetNotesPath(Path.Combine(root.Path, "any-workspace")); |
| 79 | Assert.Equal(Path.Combine(root.Path, "agent-notes.md"), notesPath); |
| 80 | } |
| 81 | |
| 82 | [Fact] |
| 83 | public void ResolveKnowledgeRoot_UsesRuntimeWithoutToolArgument() |
| 84 | { |
| 85 | using var root = TempKnowledgeRoot.Create(); |
| 86 | using var runtime = AgentNotesTestToml.Install(AgentNotesTestToml.WriteFromEmbeddedTemplate(root.Path)); |
| 87 | var resolved = NotesStorage.ResolveKnowledgeRoot(null); |
| 88 | Assert.Equal(root.Path, resolved); |
| 89 | } |
| 90 | |
| 91 | private sealed class TestEnvVarScope : IDisposable |
| 92 | { |
| 93 | private readonly string _name; |
| 94 | private readonly string? _previous; |
| 95 | |
| 96 | private TestEnvVarScope(string name, string? previous) |
| 97 | { |
| 98 | _name = name; |
| 99 | _previous = previous; |
| 100 | } |
| 101 | |
| 102 | internal static TestEnvVarScope Clear(string name) |
| 103 | { |
| 104 | var previous = Environment.GetEnvironmentVariable(name); |
| 105 | Environment.SetEnvironmentVariable(name, null); |
| 106 | return new TestEnvVarScope(name, previous); |
| 107 | } |
| 108 | |
| 109 | public void Dispose() => Environment.SetEnvironmentVariable(_name, _previous); |
| 110 | } |
| 111 | |
| 112 | internal sealed class TempKnowledgeRoot : IDisposable |
| 113 | { |
| 114 | internal string Path { get; } |
| 115 | |
| 116 | private TempKnowledgeRoot(string path) => Path = path; |
| 117 | |
| 118 | internal static TempKnowledgeRoot Create() |
| 119 | { |
| 120 | var path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "AgentNotesMcpTests", "kb", Guid.NewGuid().ToString("N")); |
| 121 | Directory.CreateDirectory(System.IO.Path.Combine(path, "knowledge", "work", "local")); |
| 122 | NotesStorageTests.SeedTestScopeAliasDefaultsForRoot(path); |
| 123 | return new TempKnowledgeRoot(path); |
| 124 | } |
| 125 | |
| 126 | public void Dispose() |
| 127 | { |
| 128 | try |
| 129 | { |
| 130 | if (Directory.Exists(Path)) |
| 131 | Directory.Delete(Path, recursive: true); |
| 132 | } |
| 133 | catch (Exception ex) |
| 134 | { |
| 135 | throw new InvalidOperationException($"Failed to cleanup: {Path}", ex); |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | } |
| 140 | |