summaryrefslogtreecommitdiff
path: root/tests/Agent.Core.Tests/ObservabilityTests.cs
blob: c259eb0f0599771706c8381a0c962cbe0c44db05 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
using Agent.App.Logging;
using Agent.Core.Localization;
using Agent.Mcp;
using System.Diagnostics;
using System.Text;

namespace Agent.Core.Tests;

public sealed class ObservabilityTests
{
    [Fact]
    public void Ready_is_always_visible_even_when_log_threshold_is_fatal()
    {
        using var directory = new TestDirectory();
        LocaleCatalog locale = LocaleCatalog.Load(Path.GetFullPath("config/locales"), "en");
        using (var application = new RollingFileLog(directory.Path, 1024 * 1024, 2, "fatal"))
        using (var observer = new McpHostLogObserver(application, locale, directory.Path, false, 1024 * 1024, 2))
            observer.Ready(new McpReadyInfo("http", "http://127.0.0.1:8080/mcp", 42, "2026-07-28", true, true));

        Assert.Contains("Agent is ready", File.ReadAllText(Path.Combine(directory.Path, "application.log")), StringComparison.Ordinal);
    }

    [Fact]
    public void Ready_and_http_request_are_logged_without_sensitive_payloads()
    {
        using var directory = new TestDirectory();
        LocaleCatalog locale = LocaleCatalog.Load(Path.GetFullPath("config/locales"), "en");
        using (var application = new RollingFileLog(directory.Path, 1024 * 1024, 2))
        {
            using var observer = new McpHostLogObserver(application, locale, directory.Path, true, 1024 * 1024, 2);
            observer.Ready(new McpReadyInfo("http", "http://127.0.0.1:8080/mcp", 42, "2026-07-28", true, true));
            observer.HttpRequestStarted(new McpHttpRequestStartInfo("connection", "trace", "127.0.0.1", 50123, "POST", "/mcp"));
            observer.HttpRequest(new McpHttpRequestInfo(DateTimeOffset.UtcNow, "connection", "trace", "127.0.0.1", 50123,
                    "127.0.0.1", 8080, "POST", "/mcp", 200, 12.5, "application/json", 100, "test-client",
                    "2026-07-28", "tools/list", "agent_run"));
        }

        string applicationText = File.ReadAllText(Path.Combine(directory.Path, "application.log"));
        Assert.Contains("Agent is ready", applicationText, StringComparison.Ordinal);
        Assert.Contains("remote=127.0.0.1:50123", applicationText, StringComparison.Ordinal);
        string httpText = File.ReadAllText(Path.Combine(directory.Path, "http.jsonl"));
        Assert.Contains("\"method\":\"POST\"", httpText, StringComparison.Ordinal);
        Assert.Contains("\"remote_ip\":\"127.0.0.1\"", httpText, StringComparison.Ordinal);
        Assert.DoesNotContain("Authorization", httpText, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("Cookie", httpText, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain("prompt", httpText, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void Http_request_callbacks_do_not_wait_for_a_blocked_console_writer()
    {
        using var directory = new TestDirectory();
        using var console = new BlockingTextWriter();
        LocaleCatalog locale = LocaleCatalog.Load(Path.GetFullPath("config/locales"), "en");
        using var application = new RollingFileLog(directory.Path, 1024 * 1024, 2, consoleWriter: console);
        using var observer = new McpHostLogObserver(application, locale, directory.Path, true, 1024 * 1024, 2);

        observer.HttpRequestStarted(new McpHttpRequestStartInfo("connection", "trace-1", "127.0.0.1", 50123, "POST", "/mcp"));
        Assert.True(console.WaitUntilBlocked(TimeSpan.FromSeconds(5)), "Background writer did not reach the blocking console.");

        var stopwatch = Stopwatch.StartNew();
        observer.HttpRequest(new McpHttpRequestInfo(DateTimeOffset.UtcNow, "connection", "trace-2", "127.0.0.1", 50124,
            "127.0.0.1", 8080, "POST", "/mcp", 200, 12.5, "application/json", 100, "test-client",
            "2026-07-28", "tools/list", null));
        stopwatch.Stop();

        Assert.True(stopwatch.Elapsed < TimeSpan.FromMilliseconds(250), $"HTTP callback blocked for {stopwatch.Elapsed}.");
        console.Release();
    }

    private sealed class BlockingTextWriter : TextWriter
    {
        private readonly ManualResetEventSlim _entered = new(false);
        private readonly ManualResetEventSlim _release = new(false);

        public override Encoding Encoding => Encoding.UTF8;

        public override void WriteLine(string? value)
        {
            _entered.Set();
            _release.Wait(TimeSpan.FromSeconds(30));
        }

        public bool WaitUntilBlocked(TimeSpan timeout) => _entered.Wait(timeout);
        public void Release() => _release.Set();

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _release.Set();
                _entered.Dispose();
                _release.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}