Forge
csharpc0b81713
1using System.Net;
2using System.Text.Json;
3using AgentNotes.Core;
4using AgentNotesMcp.Status;
5
6namespace AgentNotesMcp.Tests;
7
8public sealed class AgentNotesStatusTests
9{
10 [Fact]
11 public void Bind_resolve_forces_loopback_for_non_loopback_config()
12 {
13 var (host, warning) = AgentNotesStatusBind.Resolve("0.0.0.0");
14 Assert.Equal(AgentNotesStatusBind.Loopback, host);
15 Assert.NotNull(warning);
16 }
17
18 [Fact]
19 public void Html_renderer_includes_version_and_config()
20 {
21 using var scope = InstallStatusFixture(enabled: false, port: 17341, out _);
22 var snapshot = AgentNotesStatusSnapshot.Create(
23 new NotesStorage(),
24 DateTimeOffset.UtcNow,
25 "http://127.0.0.1:17341",
26 bindWarning: null,
27 workspacePath: null,
28 verbose: false);
29
30 var html = AgentNotesStatusHtmlRenderer.Render(snapshot, workspaceQuery: null);
31
32 Assert.Contains("agent-notes-mcp", html, StringComparison.Ordinal);
33 Assert.Contains("2.1.0", html, StringComparison.Ordinal);
34 Assert.Contains("memory_health", html, StringComparison.OrdinalIgnoreCase);
35 Assert.Contains("tools-strip", html, StringComparison.Ordinal);
36 Assert.Contains("href=\"/tools\"", html, StringComparison.Ordinal);
37 Assert.DoesNotContain("<ul class=\"tools\">", html, StringComparison.Ordinal);
38 }
39
40 [Fact]
41 public void Tools_page_renders_catalog_table()
42 {
43 var tools = ToolCatalog.ListSummaries();
44 var html = AgentNotesStatusHtmlRenderer.RenderToolsPage(tools);
45
46 Assert.Contains("tools-catalog", html, StringComparison.Ordinal);
47 Assert.Contains("<code>memory_health</code>", html, StringComparison.Ordinal);
48 var memoryHealth = tools.First(t => t.Name == "memory_health");
49 Assert.Contains(memoryHealth.Description, html, StringComparison.Ordinal);
50 }
51
52 [Fact]
53 public void Ring_buffer_keeps_at_most_default_capacity()
54 {
55 AgentNotesToolCallRingBuffer.ClearForTests();
56 var args = new Dictionary<string, JsonElement>
57 {
58 ["workspace_path"] = JsonSerializer.SerializeToElement("D:/ws")
59 };
60
61 for (var i = 0; i < AgentNotesToolCallRingBuffer.DefaultCapacity + 10; i++)
62 AgentNotesToolCallRingBuffer.Record("read_agent_notes", args, $"ok-{i}", false, i);
63
64 var snap = AgentNotesToolCallRingBuffer.Snapshot();
65 Assert.Equal(AgentNotesToolCallRingBuffer.DefaultCapacity, snap.Count);
66 Assert.Equal("read_agent_notes", snap[0].ToolName);
67 Assert.Contains("ok-73", snap[0].ResultPreview, StringComparison.Ordinal);
68 }
69
70 [Fact]
71 public async Task Hot_preview_returns_section_sizes()
72 {
73 var port = GetFreeTcpPort();
74 using var scope = InstallStatusFixture(enabled: true, port, out var knowledgeRoot);
75 var workspace = Path.Combine(Path.GetTempPath(), "AgentNotesStatusTests", "ws-" + Guid.NewGuid().ToString("N"));
76 Directory.CreateDirectory(workspace);
77 File.WriteAllText(
78 Path.Combine(knowledgeRoot, "agent-notes.md"),
79 """
80 <!-- section:active-scope -->
81 mixed
82 <!-- /section:active-scope -->
83 <!-- section:door-to-singularity -->
84 x
85 <!-- /section:door-to-singularity -->
86 """);
87
88 var storage = new NotesStorage();
89 Assert.True(AgentNotesStatusHost.TryStartBackground(storage, DateTimeOffset.UtcNow, out var host, out var url, out _));
90 try
91 {
92 using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
93 var json = await client.GetStringAsync($"{url}/hot-preview?workspace_path={Uri.EscapeDataString(workspace)}");
94 using var doc = JsonDocument.Parse(json);
95 Assert.True(doc.RootElement.GetProperty("hot_context").GetProperty("chars").GetInt32() > 0);
96 Assert.True(doc.RootElement.GetProperty("sections").GetArrayLength() > 0);
97 }
98 finally
99 {
100 await host!.StopAsync();
101 }
102 }
103
104 [Fact]
105 public async Task Status_host_health_and_json_when_enabled()
106 {
107 var port = GetFreeTcpPort();
108 using var scope = InstallStatusFixture(enabled: true, port, out var knowledgeRoot);
109 var storage = new NotesStorage();
110 var startedAt = DateTimeOffset.UtcNow;
111
112 Assert.True(AgentNotesStatusHost.TryStartBackground(storage, startedAt, out var host, out var url, out var error));
113 Assert.Null(error);
114 Assert.NotNull(host);
115 Assert.NotNull(url);
116
117 try
118 {
119 using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
120
121 var health = await client.GetStringAsync($"{url}/health");
122 Assert.Equal("OK", health);
123
124 var toolsHtml = await client.GetStringAsync($"{url}/tools");
125 Assert.Contains("tools-catalog", toolsHtml, StringComparison.Ordinal);
126
127 var json = await client.GetStringAsync($"{url}/status.json?verbose=1");
128 using var doc = JsonDocument.Parse(json);
129 Assert.Equal("2.1.0", doc.RootElement.GetProperty("mcp_version").GetString());
130 Assert.True(doc.RootElement.GetProperty("knowledge").GetProperty("primary_root").GetString()?.Contains("AgentNotesStatusTests", StringComparison.Ordinal) == true
131 || doc.RootElement.GetProperty("knowledge").GetProperty("primary_root").GetString()?.Length > 0);
132 }
133 finally
134 {
135 await host!.StopAsync();
136 }
137 }
138
139 private static AgentNotesTestToml.RuntimeScope InstallStatusFixture(bool enabled, int port, out string knowledgeRoot)
140 {
141 knowledgeRoot = Path.Combine(Path.GetTempPath(), "AgentNotesStatusTests", Guid.NewGuid().ToString("N"));
142 Directory.CreateDirectory(Path.Combine(knowledgeRoot, "knowledge", "work", "local"));
143 File.WriteAllText(Path.Combine(knowledgeRoot, "agent-notes.md"), "<!-- section:active-scope -->\n<!-- /section:active-scope -->\n");
144 File.WriteAllText(Path.Combine(knowledgeRoot, "knowledge", "work", "local", "workspace-scope-map-v1.md"), "# map\n");
145
146 var toml = $"""
147 version = 1
148 [knowledge]
149 primary = "test"
150 [knowledge.roots]
151 test = "{knowledgeRoot.Replace('\\', '/')}"
152 [workspace]
153 default_scope = "door-to-singularity"
154 scope_map = "work/local/workspace-scope-map-v1.md"
155 scope_aliases = "work/local/scope-alias-map-v1.md"
156 [status]
157 enabled = {(enabled ? "true" : "false")}
158 port = {port}
159 bind = "127.0.0.1"
160 """;
161 var path = Path.Combine(Path.GetTempPath(), "AgentNotesMcpTests", $"status-{Guid.NewGuid():N}.toml");
162 Directory.CreateDirectory(Path.GetDirectoryName(path)!);
163 File.WriteAllText(path, toml);
164 var settings = LocalSettingsLoader.Load(path);
165 AgentNotesRuntime.Initialize(settings, path);
166 return new AgentNotesTestToml.RuntimeScope();
167 }
168
169 private static int GetFreeTcpPort()
170 {
171 using var listener = new System.Net.Sockets.TcpListener(IPAddress.Loopback, 0);
172 listener.Start();
173 var port = ((IPEndPoint)listener.LocalEndpoint).Port;
174 listener.Stop();
175 return port;
176 }
177}
178
View only · write via MCP/CIDE