Move 39 monitoring, events, and system endpoint test files from NATS.Server.Tests into a dedicated NATS.Server.Monitoring.Tests project. Update namespaces, replace private GetFreePort/ReadUntilAsync with TestUtilities shared helpers, add InternalsVisibleTo, and register in the solution file. All 439 tests pass.
287 lines
11 KiB
C#
287 lines
11 KiB
C#
// Port of Go server/events_test.go — auth error advisory publication tests.
|
|
// Go reference: golang/nats-server/server/events.go:2631 sendAuthErrorEvent.
|
|
//
|
|
// Tests cover: SendAuthErrorEvent counter, enqueue behaviour, record field
|
|
// preservation, SendConnectEvent, SendDisconnectEvent, and the supporting
|
|
// detail record types AuthErrorDetail, ConnectEventDetail, DisconnectEventDetail.
|
|
|
|
using System.Text.Json;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NATS.Server;
|
|
using NATS.Server.Events;
|
|
using NATS.Server.TestUtilities;
|
|
|
|
namespace NATS.Server.Monitoring.Tests.Events;
|
|
|
|
/// <summary>
|
|
/// Tests for <see cref="InternalEventSystem.SendAuthErrorEvent"/>,
|
|
/// <see cref="InternalEventSystem.SendConnectEvent"/>,
|
|
/// <see cref="InternalEventSystem.SendDisconnectEvent"/>, and the three
|
|
/// companion detail record types.
|
|
/// Go reference: events_test.go TestSystemAccountDisconnectBadLogin,
|
|
/// TestSystemAccountNewConnection.
|
|
/// </summary>
|
|
public class AuthErrorEventTests : IAsyncLifetime
|
|
{
|
|
private NatsServer _server = null!;
|
|
private int _port;
|
|
|
|
public async Task InitializeAsync()
|
|
{
|
|
_port = TestPortAllocator.GetFreePort();
|
|
_server = new NatsServer(new NatsOptions { Port = _port }, NullLoggerFactory.Instance);
|
|
_ = _server.StartAsync(CancellationToken.None);
|
|
await _server.WaitForReadyAsync();
|
|
}
|
|
|
|
public async Task DisposeAsync()
|
|
{
|
|
await _server.ShutdownAsync();
|
|
_server.Dispose();
|
|
}
|
|
|
|
|
|
// ========================================================================
|
|
// AuthErrorEventCount
|
|
// Go reference: events.go:2631 sendAuthErrorEvent — counter per advisory
|
|
// ========================================================================
|
|
|
|
/// <summary>
|
|
/// AuthErrorEventCount starts at zero before any advisories are sent.
|
|
/// Go reference: events_test.go TestSystemAccountDisconnectBadLogin.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AuthErrorEventCount_StartsAtZero()
|
|
{
|
|
// Go reference: events_test.go TestSystemAccountDisconnectBadLogin — no events at startup.
|
|
var es = _server.EventSystem!;
|
|
es.AuthErrorEventCount.ShouldBe(0L);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calling SendAuthErrorEvent once increments the counter to 1.
|
|
/// Go reference: events.go:2631 sendAuthErrorEvent — each call is one advisory.
|
|
/// </summary>
|
|
[Fact]
|
|
public void SendAuthErrorEvent_IncrementsCounter()
|
|
{
|
|
// Go reference: events_test.go TestSystemAccountDisconnectBadLogin.
|
|
var es = _server.EventSystem!;
|
|
var detail = new AuthErrorDetail(
|
|
ClientId: 42,
|
|
RemoteAddress: "127.0.0.1:5000",
|
|
AccountName: "$G",
|
|
UserName: "alice",
|
|
Reason: "Authorization Violation",
|
|
OccurredAt: DateTime.UtcNow);
|
|
|
|
es.SendAuthErrorEvent(_server.ServerId, detail);
|
|
|
|
es.AuthErrorEventCount.ShouldBe(1L);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Each call to SendAuthErrorEvent enqueues a message (counter grows by one per call).
|
|
/// Go reference: events.go:2687 sendInternalMsg — advisory is always enqueued.
|
|
/// </summary>
|
|
[Fact]
|
|
public void SendAuthErrorEvent_EnqueuesMessage()
|
|
{
|
|
// Go reference: events.go sendAuthErrorEvent publishes via sendInternalMsg.
|
|
var es = _server.EventSystem!;
|
|
var detail = new AuthErrorDetail(
|
|
ClientId: 7,
|
|
RemoteAddress: "10.0.0.1:4222",
|
|
AccountName: null,
|
|
UserName: null,
|
|
Reason: "Authentication Timeout",
|
|
OccurredAt: DateTime.UtcNow);
|
|
|
|
var before = es.AuthErrorEventCount;
|
|
es.SendAuthErrorEvent(_server.ServerId, detail);
|
|
var after = es.AuthErrorEventCount;
|
|
|
|
// The counter increment is the observable side-effect of the enqueue path.
|
|
(after - before).ShouldBe(1L);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sending multiple auth error events increments the counter for each.
|
|
/// Go reference: events.go:2631 sendAuthErrorEvent — cumulative count.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AuthErrorEventCount_MultipleSends_Incremented()
|
|
{
|
|
// Go reference: events_test.go TestSystemAccountDisconnectBadLogin.
|
|
var es = _server.EventSystem!;
|
|
var detail = new AuthErrorDetail(
|
|
ClientId: 1,
|
|
RemoteAddress: "192.168.1.1:9999",
|
|
AccountName: "myacc",
|
|
UserName: "bob",
|
|
Reason: "Bad credentials",
|
|
OccurredAt: DateTime.UtcNow);
|
|
|
|
var before = es.AuthErrorEventCount;
|
|
|
|
const int count = 5;
|
|
for (var i = 0; i < count; i++)
|
|
es.SendAuthErrorEvent(_server.ServerId, detail);
|
|
|
|
(es.AuthErrorEventCount - before).ShouldBe(count);
|
|
}
|
|
|
|
// ========================================================================
|
|
// SendConnectEvent
|
|
// Go reference: events.go postConnectEvent / sendConnect
|
|
// ========================================================================
|
|
|
|
/// <summary>
|
|
/// SendConnectEvent enqueues a message without throwing.
|
|
/// Go reference: events.go postConnectEvent — advisory fired on client connect.
|
|
/// </summary>
|
|
[Fact]
|
|
public void SendConnectEvent_EnqueuesMessage()
|
|
{
|
|
// Go reference: events_test.go TestSystemAccountNewConnection.
|
|
var es = _server.EventSystem!;
|
|
var detail = new ConnectEventDetail(
|
|
ClientId: 10,
|
|
RemoteAddress: "127.0.0.1:6000",
|
|
AccountName: "$G",
|
|
UserName: "user1",
|
|
ConnectedAt: DateTime.UtcNow);
|
|
|
|
var ex = Record.Exception(() => es.SendConnectEvent(_server.ServerId, detail));
|
|
ex.ShouldBeNull();
|
|
}
|
|
|
|
// ========================================================================
|
|
// SendDisconnectEvent
|
|
// Go reference: events.go postDisconnectEvent / sendDisconnect
|
|
// ========================================================================
|
|
|
|
/// <summary>
|
|
/// SendDisconnectEvent enqueues a message without throwing.
|
|
/// Go reference: events.go postDisconnectEvent — advisory fired on client disconnect.
|
|
/// </summary>
|
|
[Fact]
|
|
public void SendDisconnectEvent_EnqueuesMessage()
|
|
{
|
|
// Go reference: events_test.go TestSystemAccountNewConnection (disconnect part).
|
|
var es = _server.EventSystem!;
|
|
var detail = new DisconnectEventDetail(
|
|
ClientId: 20,
|
|
RemoteAddress: "127.0.0.1:7000",
|
|
AccountName: "$G",
|
|
Reason: "Client Closed",
|
|
DisconnectedAt: DateTime.UtcNow);
|
|
|
|
var ex = Record.Exception(() => es.SendDisconnectEvent(_server.ServerId, detail));
|
|
ex.ShouldBeNull();
|
|
}
|
|
|
|
// ========================================================================
|
|
// AuthErrorDetail record
|
|
// ========================================================================
|
|
|
|
/// <summary>
|
|
/// AuthErrorDetail preserves all fields passed to its constructor.
|
|
/// Go reference: events.go:2631 — all client fields captured in the advisory.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AuthErrorDetail_PreservesAllFields()
|
|
{
|
|
// Go reference: events_test.go TestSystemAccountDisconnectBadLogin.
|
|
var now = DateTime.UtcNow;
|
|
var detail = new AuthErrorDetail(
|
|
ClientId: 99,
|
|
RemoteAddress: "10.0.0.2:1234",
|
|
AccountName: "test-account",
|
|
UserName: "testuser",
|
|
Reason: "Authorization Violation",
|
|
OccurredAt: now);
|
|
|
|
detail.ClientId.ShouldBe(99UL);
|
|
detail.RemoteAddress.ShouldBe("10.0.0.2:1234");
|
|
detail.AccountName.ShouldBe("test-account");
|
|
detail.UserName.ShouldBe("testuser");
|
|
detail.Reason.ShouldBe("Authorization Violation");
|
|
detail.OccurredAt.ShouldBe(now);
|
|
}
|
|
|
|
/// <summary>
|
|
/// AuthErrorDetail accepts a non-empty Reason (the key advisory field).
|
|
/// Go reference: events.go:2631 sendAuthErrorEvent — reason is always set.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AuthErrorDetail_ReasonRequired()
|
|
{
|
|
// Go reference: events_test.go TestSystemAccountDisconnectBadLogin — reason distinguishes error types.
|
|
var detail = new AuthErrorDetail(
|
|
ClientId: 1,
|
|
RemoteAddress: "127.0.0.1:0",
|
|
AccountName: null,
|
|
UserName: null,
|
|
Reason: "Authentication Timeout",
|
|
OccurredAt: DateTime.UtcNow);
|
|
|
|
detail.Reason.ShouldNotBeNullOrEmpty();
|
|
detail.Reason.ShouldBe("Authentication Timeout");
|
|
}
|
|
|
|
// ========================================================================
|
|
// ConnectEventDetail record
|
|
// ========================================================================
|
|
|
|
/// <summary>
|
|
/// ConnectEventDetail preserves all constructor fields.
|
|
/// Go reference: events.go postConnectEvent — all fields captured on connect.
|
|
/// </summary>
|
|
[Fact]
|
|
public void ConnectEventDetail_PreservesFields()
|
|
{
|
|
// Go reference: events_test.go TestSystemAccountNewConnection.
|
|
var connectedAt = new DateTime(2026, 2, 25, 10, 0, 0, DateTimeKind.Utc);
|
|
var detail = new ConnectEventDetail(
|
|
ClientId: 55,
|
|
RemoteAddress: "192.168.0.5:8080",
|
|
AccountName: "prod-account",
|
|
UserName: "svc-user",
|
|
ConnectedAt: connectedAt);
|
|
|
|
detail.ClientId.ShouldBe(55UL);
|
|
detail.RemoteAddress.ShouldBe("192.168.0.5:8080");
|
|
detail.AccountName.ShouldBe("prod-account");
|
|
detail.UserName.ShouldBe("svc-user");
|
|
detail.ConnectedAt.ShouldBe(connectedAt);
|
|
}
|
|
|
|
// ========================================================================
|
|
// DisconnectEventDetail record
|
|
// ========================================================================
|
|
|
|
/// <summary>
|
|
/// DisconnectEventDetail preserves all constructor fields.
|
|
/// Go reference: events.go postDisconnectEvent — all fields captured on disconnect.
|
|
/// </summary>
|
|
[Fact]
|
|
public void DisconnectEventDetail_PreservesFields()
|
|
{
|
|
// Go reference: events_test.go TestSystemAccountNewConnection (disconnect part).
|
|
var disconnectedAt = new DateTime(2026, 2, 25, 11, 0, 0, DateTimeKind.Utc);
|
|
var detail = new DisconnectEventDetail(
|
|
ClientId: 77,
|
|
RemoteAddress: "172.16.0.3:3000",
|
|
AccountName: "staging-account",
|
|
Reason: "Slow Consumer",
|
|
DisconnectedAt: disconnectedAt);
|
|
|
|
detail.ClientId.ShouldBe(77UL);
|
|
detail.RemoteAddress.ShouldBe("172.16.0.3:3000");
|
|
detail.AccountName.ShouldBe("staging-account");
|
|
detail.Reason.ShouldBe("Slow Consumer");
|
|
detail.DisconnectedAt.ShouldBe(disconnectedAt);
|
|
}
|
|
}
|