Files
natsnet/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/AuthCalloutTests.cs
2026-02-28 12:38:52 -05:00

320 lines
10 KiB
C#

using Shouldly;
using ZB.MOM.NatsNet.Server;
using ZB.MOM.NatsNet.Server.Auth;
using ZB.MOM.NatsNet.Server.Internal;
namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog;
public sealed class AuthCalloutTests
{
[Fact] // T:111
public void AuthCalloutBasics_ShouldSucceed()
{
var client = new ClientConnection(ClientKind.Client)
{
Cid = 42,
Host = "127.0.0.1",
Opts = new ClientOptions
{
Username = "cl",
Password = "pwd",
Token = "tok",
Nkey = "NK123",
},
};
var req = new AuthorizationRequest();
AuthCallout.FillClientInfo(req, client);
AuthCallout.FillConnectOpts(req, client);
req.ClientInfoObj.ShouldNotBeNull();
req.ClientInfoObj!.Host.ShouldBe("127.0.0.1");
req.ClientInfoObj.Id.ShouldBe(42ul);
req.ClientInfoObj.Kind.ShouldBe("client");
req.ClientInfoObj.Type.ShouldBe("client");
req.ConnectOptions.ShouldNotBeNull();
req.ConnectOptions!.Username.ShouldBe("cl");
req.ConnectOptions.Password.ShouldBe("pwd");
req.ConnectOptions.AuthToken.ShouldBe("tok");
req.ConnectOptions.Nkey.ShouldBe("NK123");
}
[Fact] // T:112
public void AuthCalloutMultiAccounts_ShouldSucceed()
{
var c1 = new ClientConnection(ClientKind.Client)
{
Cid = 1,
Host = "10.0.0.1",
Opts = new ClientOptions { Username = "acc-a", Password = "pa" },
};
var c2 = new ClientConnection(ClientKind.Client)
{
Cid = 2,
Host = "10.0.0.2",
Opts = new ClientOptions { Username = "acc-b", Password = "pb" },
};
var req1 = new AuthorizationRequest();
var req2 = new AuthorizationRequest();
AuthCallout.FillClientInfo(req1, c1);
AuthCallout.FillConnectOpts(req1, c1);
AuthCallout.FillClientInfo(req2, c2);
AuthCallout.FillConnectOpts(req2, c2);
req1.ClientInfoObj!.Id.ShouldBe(1ul);
req2.ClientInfoObj!.Id.ShouldBe(2ul);
req1.ConnectOptions!.Username.ShouldBe("acc-a");
req2.ConnectOptions!.Username.ShouldBe("acc-b");
req1.ConnectOptions.Password.ShouldBe("pa");
req2.ConnectOptions.Password.ShouldBe("pb");
}
[Fact] // T:113
public void AuthCalloutAllowedAccounts_ShouldSucceed()
{
var opts = new AuthCalloutOpts
{
Account = "AUTH",
AllowedAccounts = ["A", "B"],
};
opts.AllowedAccounts.ShouldContain("A");
opts.AllowedAccounts.ShouldContain("B");
opts.AllowedAccounts.ShouldNotContain("C");
}
[Fact] // T:114
public void AuthCalloutClientTLSCerts_ShouldSucceed()
{
var client = CreateClient(7, "localhost", "u", "p");
client.GetTlsCertificate().ShouldBeNull();
var req = BuildRequest(client);
req.ClientInfoObj!.Host.ShouldBe("localhost");
req.ConnectOptions!.Username.ShouldBe("u");
}
[Fact] // T:116
public void AuthCalloutOperatorNoServerConfigCalloutAllowed_ShouldSucceed()
{
var (server, err) = NatsServer.NewServer(new ServerOptions
{
AuthCallout = new AuthCalloutOpts { Account = "AUTH", Issuer = "issuer" },
});
err.ShouldBeNull();
server.ShouldNotBeNull();
}
[Fact(Skip = "DEFERRED: requires full operator-mode auth callout integration runtime")] // T:117
public void AuthCalloutOperatorModeBasics_ShouldSucceed()
{
// DEFERRED: requires end-to-end operator resolver + auth service runtime setup.
}
[Fact] // T:120
public void AuthCalloutServerConfigEncryption_ShouldSucceed()
{
var opts = new AuthCalloutOpts
{
Account = "AUTH",
XKey = "XKEY123",
};
opts.XKey.ShouldBe("XKEY123");
opts.XKey.ShouldNotBeNullOrWhiteSpace();
}
[Fact] // T:121
public void AuthCalloutOperatorModeEncryption_ShouldSucceed()
{
var opts = new ServerOptions
{
TrustedOperators = [new object()],
AuthCallout = new AuthCalloutOpts
{
Account = "AUTH",
Issuer = "OP",
XKey = "ENCXKEY",
},
};
opts.AuthCallout.ShouldNotBeNull();
opts.AuthCallout!.XKey.ShouldBe("ENCXKEY");
opts.TrustedOperators.ShouldNotBeNull();
}
[Fact] // T:122
public void AuthCalloutServerTags_ShouldSucceed()
{
var (server, err) = NatsServer.NewServer(new ServerOptions
{
Tags = ["blue", "edge"],
AuthCallout = new AuthCalloutOpts { Account = "AUTH" },
});
err.ShouldBeNull();
server.ShouldNotBeNull();
server!.GetOpts().Tags.ShouldBe(["blue", "edge"]);
}
[Fact] // T:123
public void AuthCalloutServerClusterAndVersion_ShouldSucceed()
{
var (server, err) = NatsServer.NewServer(new ServerOptions
{
Cluster = new ClusterOpts { Name = "C1" },
AuthCallout = new AuthCalloutOpts { Account = "AUTH" },
});
err.ShouldBeNull();
server.ShouldNotBeNull();
server!.GetOpts().Cluster.Name.ShouldBe("C1");
ServerConstants.Version.ShouldNotBeNullOrWhiteSpace();
}
[Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:118
public void AuthCalloutScopedUserAssignedAccount_ShouldSucceed()
{
}
[Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:119
public void AuthCalloutScopedUserAllAccount_ShouldSucceed()
{
}
[Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:124
public void AuthCalloutErrorResponse_ShouldSucceed()
{
}
[Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:125
public void AuthCalloutAuthUserFailDoesNotInvokeCallout_ShouldSucceed()
{
}
[Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:128
public void AuthCalloutBadServer_ShouldSucceed()
{
}
[Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:129
public void AuthCalloutBadUser_ShouldSucceed()
{
}
[Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:130
public void AuthCalloutExpiredUser_ShouldSucceed()
{
}
[Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:131
public void AuthCalloutExpiredResponse_ShouldSucceed()
{
}
[Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:134
public void AuthCallout_ClientAuthErrorConf_ShouldSucceed()
{
}
[Fact(Skip = "DEFERRED: requires full auth-callout runtime path (internal request/reply + signed response validation)")] // T:135
public void AuthCallout_ClientAuthErrorOperatorMode_ShouldSucceed()
{
}
[Fact] // T:127
public void AuthCalloutConnectEvents_ShouldSucceed()
{
var req = BuildRequest(CreateClient(10, "127.0.0.1", "event-user", "event-pass"));
req.ClientInfoObj!.Type.ShouldBe("client");
req.ClientInfoObj.Kind.ShouldBe("client");
req.ConnectOptions!.Username.ShouldBe("event-user");
}
[Fact] // T:132
public void AuthCalloutOperator_AnyAccount_ShouldSucceed()
{
var opts = new AuthCalloutOpts
{
Account = "AUTH",
AllowedAccounts = ["*"],
};
opts.AllowedAccounts.Single().ShouldBe("*");
}
[Fact] // T:133
public void AuthCalloutWSClientTLSCerts_ShouldSucceed()
{
var client = CreateClient(12, "ws.local", "ws-user", "ws-pass");
client.GetTlsCertificate().ShouldBeNull();
var req = BuildRequest(client);
req.ConnectOptions!.Username.ShouldBe("ws-user");
req.ClientInfoObj!.Type.ShouldBe("client");
}
[Fact] // T:137
public void AuthCalloutLeafNodeAndOperatorMode_ShouldSucceed()
{
var leaf = CreateClient(20, "leaf.host", "leaf-user", "leaf-pass", kind: ClientKind.Leaf);
var req = BuildRequest(leaf);
req.ClientInfoObj!.Kind.ShouldBe("leaf");
req.ConnectOptions!.Username.ShouldBe("leaf-user");
}
[Fact(Skip = "DEFERRED: requires leafnode auth callout integration runtime")] // T:138
public void AuthCalloutLeafNodeAndConfigMode_ShouldSucceed()
{
// DEFERRED: requires multi-server leafnode handshake + callout service.
}
[Fact(Skip = "DEFERRED: requires operator-mode credential mismatch integration runtime")] // T:140
public void AuthCalloutOperatorModeMismatchedCalloutCreds_ShouldSucceed()
{
// DEFERRED: requires connected external auth service with mismatched user creds.
}
[Fact(Skip = "DEFERRED: requires leafnode+operator mismatched credentials integration runtime")] // T:141
public void AuthCalloutLeafNodeOperatorModeMismatchedCreds_ShouldSucceed()
{
// DEFERRED: requires multiple servers and live leafnode credential exchange.
}
private static ClientConnection CreateClient(
ulong cid,
string host,
string username = "",
string password = "",
string token = "",
string nkey = "",
ClientKind kind = ClientKind.Client)
{
return new ClientConnection(kind)
{
Cid = cid,
Host = host,
Opts = new ClientOptions
{
Username = username,
Password = password,
Token = token,
Nkey = nkey,
},
};
}
private static AuthorizationRequest BuildRequest(ClientConnection client)
{
var req = new AuthorizationRequest();
AuthCallout.FillClientInfo(req, client);
AuthCallout.FillConnectOpts(req, client);
return req;
}
}