using NATS.Server.Auth; using NATS.Server.WebSocket; namespace NATS.Server.Transport.Tests.WebSocket; public class WebSocketOptionsValidatorParityBatch2Tests { [Fact] public void Validate_rejects_tls_listener_without_cert_key_when_not_no_tls() { var opts = new NatsOptions { WebSocket = new WebSocketOptions { Port = 8080, NoTls = false, }, }; var result = WebSocketOptionsValidator.Validate(opts); result.IsValid.ShouldBeFalse(); result.Errors.ShouldContain(e => e.Contains("TLS", StringComparison.OrdinalIgnoreCase)); } [Fact] public void Validate_rejects_invalid_allowed_origins() { var opts = new NatsOptions { WebSocket = new WebSocketOptions { Port = 8080, NoTls = true, AllowedOrigins = ["not-a-uri"], }, }; var result = WebSocketOptionsValidator.Validate(opts); result.IsValid.ShouldBeFalse(); result.Errors.ShouldContain(e => e.Contains("allowed origin", StringComparison.OrdinalIgnoreCase)); } [Fact] public void Validate_rejects_no_auth_user_not_present_in_configured_users() { var opts = new NatsOptions { Users = [new User { Username = "alice", Password = "x" }], WebSocket = new WebSocketOptions { Port = 8080, NoTls = true, NoAuthUser = "bob", }, }; var result = WebSocketOptionsValidator.Validate(opts); result.IsValid.ShouldBeFalse(); result.Errors.ShouldContain(e => e.Contains("NoAuthUser", StringComparison.OrdinalIgnoreCase)); } [Fact] public void Validate_rejects_username_or_token_when_users_or_nkeys_are_set() { var opts = new NatsOptions { Users = [new User { Username = "alice", Password = "x" }], WebSocket = new WebSocketOptions { Port = 8080, NoTls = true, Username = "ws-user", }, }; var result = WebSocketOptionsValidator.Validate(opts); result.IsValid.ShouldBeFalse(); result.Errors.ShouldContain(e => e.Contains("users", StringComparison.OrdinalIgnoreCase)); } [Fact] public void Validate_rejects_jwt_cookie_without_trusted_operators() { var opts = new NatsOptions { WebSocket = new WebSocketOptions { Port = 8080, NoTls = true, JwtCookie = "jwt", }, }; var result = WebSocketOptionsValidator.Validate(opts); result.IsValid.ShouldBeFalse(); result.Errors.ShouldContain(e => e.Contains("JwtCookie", StringComparison.OrdinalIgnoreCase)); } [Fact] public void Validate_rejects_reserved_response_headers_override() { var opts = new NatsOptions { TrustedKeys = ["OP1"], WebSocket = new WebSocketOptions { Port = 8080, NoTls = true, Headers = new Dictionary { ["Sec-WebSocket-Accept"] = "bad", }, }, }; var result = WebSocketOptionsValidator.Validate(opts); result.IsValid.ShouldBeFalse(); result.Errors.ShouldContain(e => e.Contains("reserved", StringComparison.OrdinalIgnoreCase)); } [Fact] public void Validate_rejects_tls_pinned_certs_when_websocket_tls_is_disabled() { var opts = new NatsOptions { TlsPinnedCerts = ["ABCDEF0123"], WebSocket = new WebSocketOptions { Port = 8080, NoTls = true, }, }; var result = WebSocketOptionsValidator.Validate(opts); result.IsValid.ShouldBeFalse(); result.Errors.ShouldContain(e => e.Contains("TLSPinnedCerts", StringComparison.OrdinalIgnoreCase)); } [Fact] public void Validate_accepts_valid_minimal_configuration() { var opts = new NatsOptions { TrustedKeys = ["OP1"], Users = [new User { Username = "alice", Password = "x" }], WebSocket = new WebSocketOptions { Port = 8080, NoTls = true, NoAuthUser = "alice", AllowedOrigins = ["https://app.example.com"], JwtCookie = "jwt", Headers = new Dictionary { ["X-App-Version"] = "1", }, }, }; var result = WebSocketOptionsValidator.Validate(opts); result.IsValid.ShouldBeTrue(); result.Errors.ShouldBeEmpty(); } }