diff --git a/src/EventStore.Client/ConnectionStringParseException.cs b/src/EventStore.Client/ConnectionStringParseException.cs new file mode 100644 index 000000000..bbef9d385 --- /dev/null +++ b/src/EventStore.Client/ConnectionStringParseException.cs @@ -0,0 +1,37 @@ +using System; + +namespace EventStore.Client { + public class ConnectionStringParseException : Exception { + public ConnectionStringParseException(string message) : base(message) { } + } + + public class NoSchemeException : ConnectionStringParseException { + public NoSchemeException() + : base("Could not parse scheme from connection string") { } + } + + public class InvalidSchemeException : ConnectionStringParseException { + public InvalidSchemeException(string scheme, string[] supportedSchemes) + : base($"Invalid scheme: '{scheme}'. Supported values are: {string.Join(",", supportedSchemes)}") { } + } + + public class InvalidUserCredentialsException : ConnectionStringParseException { + public InvalidUserCredentialsException(string userInfo) + : base($"Invalid user credentials: '{userInfo}'. Username & password must be delimited by a colon") { } + } + + public class InvalidHostException : ConnectionStringParseException { + public InvalidHostException(string host) + : base($"Invalid host: '{host}'") { } + } + + public class InvalidKeyValuePairException : ConnectionStringParseException { + public InvalidKeyValuePairException(string keyValuePair) + : base($"Invalid key/value pair: '{keyValuePair}'") { } + } + + public class InvalidSettingException : ConnectionStringParseException { + public InvalidSettingException(string message) : base(message) { } + } + +} diff --git a/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs new file mode 100644 index 000000000..fa8874c86 --- /dev/null +++ b/src/EventStore.Client/EventStoreClientSettings.ConnectionString.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; + +namespace EventStore.Client { + public partial class EventStoreClientSettings { + public static EventStoreClientSettings Create(string connectionString) { + return ConnectionStringParser.Parse(connectionString); + } + + private static class ConnectionStringParser { + private const string SchemeSeparator = "://"; + private const string UserInfoSeparator = "@"; + private const string Colon = ":"; + private const string Slash = "/"; + private const string Comma = ","; + private const string Ampersand = "&"; + private const string Equal = "="; + private const string QuestionMark = "?"; + private static readonly string[] Schemes = { "esdb" }; + private static readonly int DefaultPort = EventStoreClientConnectivitySettings.Default.Address.Port; + private static readonly bool DefaultUseTls = EventStoreClientConnectivitySettings.Default.UseHttps; + + private static readonly Dictionary SettingsType = new Dictionary (StringComparer.InvariantCultureIgnoreCase) { + {"ConnectionName", typeof(string)}, + {"MaxDiscoverAttempts", typeof(int)}, + {"DiscoveryInterval", typeof(int)}, + {"GossipTimeout", typeof(int)}, + {"NodePreference", typeof(string)}, + {"Tls", typeof(bool)}, + {"TlsVerifyCert", typeof(bool)}, + {"OperationTimeout", typeof(int)}, + {"ThrowOnAppendFailure", typeof(bool)} + }; + + public static EventStoreClientSettings Parse(string connectionString) { + var currentIndex = 0; + var schemeIndex = connectionString.IndexOf(SchemeSeparator, currentIndex, StringComparison.Ordinal); + if (schemeIndex == -1) + throw new NoSchemeException(); + var scheme = ParseScheme(connectionString.Substring(0, schemeIndex)); + + currentIndex = schemeIndex + SchemeSeparator.Length; + var userInfoIndex = connectionString.IndexOf(UserInfoSeparator, currentIndex, StringComparison.Ordinal); + (string user, string pass) userInfo = (null, null); + if (userInfoIndex != -1) { + userInfo = ParseUserInfo(connectionString.Substring(currentIndex, userInfoIndex - currentIndex)); + currentIndex = userInfoIndex + UserInfoSeparator.Length; + } + + var slashIndex = connectionString.IndexOf(Slash, currentIndex, StringComparison.Ordinal); + if (slashIndex == -1) + throw new ConnectionStringParseException("The connection string must contain a / (forward slash) after specifying the hosts"); + + var hosts = ParseHosts(connectionString.Substring(currentIndex, slashIndex - currentIndex)); + currentIndex = slashIndex + Slash.Length; + + var questionMarkIndex = connectionString.IndexOf(QuestionMark, currentIndex); + var options = new Dictionary(); + if (questionMarkIndex != -1) { + currentIndex = questionMarkIndex + QuestionMark.Length; + options = ParseKeyValuePairs(connectionString.Substring(currentIndex)); + } + + return CreateSettings(scheme, userInfo, hosts, options); + } + + private static EventStoreClientSettings CreateSettings(string scheme, (string user, string pass) userInfo, EndPoint[] hosts, Dictionary options) { + var settings = new EventStoreClientSettings { + ConnectivitySettings = EventStoreClientConnectivitySettings.Default, + OperationOptions = EventStoreClientOperationOptions.Default + }; + + if (userInfo != (null, null)) + settings.DefaultCredentials = new UserCredentials(userInfo.user, userInfo.pass); + + var typedOptions = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + foreach (var option in options) { + if (!SettingsType.TryGetValue(option.Key, out var type)) throw new InvalidSettingException($"Unknown option: {option.Key}"); + if(type == typeof(int)){ + if (!int.TryParse(option.Value, out var intValue)) + throw new InvalidSettingException($"{option.Key} must be an integer value"); + typedOptions.Add(option.Key, intValue); + } else if (type == typeof(bool)) { + if (!bool.TryParse(option.Value, out var boolValue)) + throw new InvalidSettingException($"{option.Key} must be either true or false"); + typedOptions.Add(option.Key, boolValue); + } else if (type == typeof(string)) { + typedOptions.Add(option.Key, option.Value); + } + } + + if (typedOptions.TryGetValue("ConnectionName", out object connectionName)) + settings.ConnectionName = (string) connectionName; + + var connSettings = settings.ConnectivitySettings; + + if (typedOptions.TryGetValue("MaxDiscoverAttempts", out object maxDiscoverAttempts)) + connSettings.MaxDiscoverAttempts = (int)maxDiscoverAttempts; + + if (typedOptions.TryGetValue("DiscoveryInterval", out object discoveryInterval)) + connSettings.DiscoveryInterval = TimeSpan.FromSeconds((int)discoveryInterval); + + if (typedOptions.TryGetValue("GossipTimeout", out object gossipTimeout)) + connSettings.GossipTimeout = TimeSpan.FromSeconds((int)gossipTimeout); + + if (typedOptions.TryGetValue("NodePreference", out object nodePreference)) { + var nodePreferenceLowerCase = ((string)nodePreference).ToLowerInvariant(); + switch (nodePreferenceLowerCase) { + case "leader": + connSettings.NodePreference = NodePreference.Leader; + break; + case "follower": + connSettings.NodePreference = NodePreference.Follower; + break; + case "random": + connSettings.NodePreference = NodePreference.Random; + break; + case "readonlyreplica": + connSettings.NodePreference = NodePreference.ReadOnlyReplica; + break; + default: + throw new InvalidSettingException($"Invalid NodePreference: {nodePreference}"); + } + } + + var useTls = DefaultUseTls; + if(typedOptions.TryGetValue("Tls", out object tls)) { + useTls = (bool)tls; + } + + if (typedOptions.TryGetValue("TlsVerifyCert", out object tlsVerifyCert)) { + if (!(bool)tlsVerifyCert) { + settings.CreateHttpMessageHandler = () => new SocketsHttpHandler { + SslOptions = { + RemoteCertificateValidationCallback = delegate { return true; } + } + }; + } + } + + if(typedOptions.TryGetValue("OperationTimeout", out object operationTimeout)) + settings.OperationOptions.TimeoutAfter = TimeSpan.FromSeconds((int) operationTimeout); + + if(typedOptions.TryGetValue("ThrowOnAppendFailure", out object throwOnAppendFailure)) + settings.OperationOptions.ThrowOnAppendFailure = (bool) throwOnAppendFailure; + + if (hosts.Length == 1) { + connSettings.Address = new Uri(hosts[0].ToHttpUrl(useTls?Uri.UriSchemeHttps:Uri.UriSchemeHttp)); + connSettings.UseHttps = useTls; //this is not required for single nodes but we set it for consistency + } else { + if (hosts.Any(x => x is DnsEndPoint)) + connSettings.DnsGossipSeeds = hosts.Select(x => new DnsEndPoint(x.GetHost(), x.GetPort())).ToArray(); + else + connSettings.IpGossipSeeds = hosts.Select(x => x as IPEndPoint).ToArray(); + + connSettings.UseHttps = useTls; + } + + return settings; + } + + private static string ParseScheme(string s) { + if (!Schemes.Contains(s)) throw new InvalidSchemeException(s, Schemes); + return s; + } + + private static (string,string) ParseUserInfo(string s) { + var tokens = s.Split(Colon); + if (tokens.Length != 2) throw new InvalidUserCredentialsException(s); + return (tokens[0], tokens[1]); + } + + private static EndPoint[] ParseHosts(string s) { + var hostsTokens = s.Split(Comma); + var hosts = new List(); + foreach (var hostToken in hostsTokens) { + var hostPortToken = hostToken.Split(Colon); + string host; + int port; + switch (hostPortToken.Length) + { + case 1: + host = hostPortToken[0]; + port = DefaultPort; + break; + case 2: + { + host = hostPortToken[0]; + if (!int.TryParse(hostPortToken[1], out port)) + throw new InvalidHostException(hostToken); + break; + } + default: + throw new InvalidHostException(hostToken); + } + + if (host.Length == 0) { + throw new InvalidHostException(hostToken); + } + + if (IPAddress.TryParse(host, out IPAddress ip)) { + hosts.Add(new IPEndPoint(ip, port)); + } else { + hosts.Add(new DnsEndPoint(host, port)); + } + } + + return hosts.ToArray(); + } + + private static Dictionary ParseKeyValuePairs(string s) { + var options = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + var optionsTokens = s.Split(Ampersand); + foreach (var optionToken in optionsTokens) { + var (key, val) = ParseKeyValuePair(optionToken); + options[key] = val; + } + return options; + } + + private static (string,string) ParseKeyValuePair(string s) { + var keyValueToken = s.Split(Equal); + if (keyValueToken.Length != 2) { + throw new InvalidKeyValuePairException(s); + } + + return (keyValueToken[0], keyValueToken[1]); + } + } + } +} diff --git a/src/EventStore.Client/EventStoreClientSettings.cs b/src/EventStore.Client/EventStoreClientSettings.cs index d4216a936..feba3f0c6 100644 --- a/src/EventStore.Client/EventStoreClientSettings.cs +++ b/src/EventStore.Client/EventStoreClientSettings.cs @@ -7,7 +7,7 @@ #nullable enable namespace EventStore.Client { - public class EventStoreClientSettings { + public partial class EventStoreClientSettings { public IEnumerable? Interceptors { get; set; } public string? ConnectionName { get; set; } public Func? CreateHttpMessageHandler { get; set; } diff --git a/test/EventStore.Client.Tests/ConnectionStringTests.cs b/test/EventStore.Client.Tests/ConnectionStringTests.cs new file mode 100644 index 000000000..15131dd78 --- /dev/null +++ b/test/EventStore.Client.Tests/ConnectionStringTests.cs @@ -0,0 +1,288 @@ +using System; +using System.Net; +using Xunit; + +namespace EventStore.Client { + public class ConnectionStringTests { + [Fact] + public void connection_string_with_no_schema() { + Assert.Throws(() => { + EventStoreClientSettings.Create(":so/mething/random"); + }); + } + + [Fact] + public void connection_string_with_invalid_scheme_should_throw() { + Assert.Throws(() => { + EventStoreClientSettings.Create("esdbwrong://"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("wrong://"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("badesdb://"); + }); + } + + [Fact] + public void connection_string_with_invalid_userinfo_should_throw() { + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://userpass@127.0.0.1/"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pa:ss@127.0.0.1/"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://us:er:pa:ss@127.0.0.1/"); + }); + } + + [Fact] + public void connection_string_without_forward_slash_after_host_should_throw() { + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1:1234"); + }); + } + + [Fact] + public void connection_string_with_invalid_host_should_throw() { + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1:abc/"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1:1234,127.0.0.2:abc,127.0.0.3:4321/"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1:abc:def/"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@localhost:1234,127.0.0.2:abc:def,127.0.0.3:4321/"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@localhost:1234,,127.0.0.3:4321/"); + }); + } + + [Fact] + public void connection_string_with_invalid_key_value_pair_should_throw() { + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?maxDiscoverAttempts=12=34"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?maxDiscoverAttempts1234"); + }); + } + + [Fact] + public void connection_string_with_invalid_settings_should_throw() { + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?unknown=1234"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?maxDiscoverAttempts=1234&hello=test"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?maxDiscoverAttempts=abcd"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?discoveryInterval=abcd"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?gossipTimeout=defg"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?tlsVerifyCert=truee"); + }); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?nodePreference=blabla"); + }); + } + + [Fact] + public void with_different_node_preferences() { + Assert.Equal(NodePreference.Leader, EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?nodePreference=leader").ConnectivitySettings.NodePreference); + Assert.Equal(NodePreference.Follower, EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?nodePreference=Follower").ConnectivitySettings.NodePreference); + Assert.Equal(NodePreference.Random, EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?nodePreference=rAndom").ConnectivitySettings.NodePreference); + Assert.Equal(NodePreference.ReadOnlyReplica, EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?nodePreference=ReadOnlyReplica").ConnectivitySettings.NodePreference); + + Assert.Throws(() => { + EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?nodePreference=invalid"); + }); + } + + [Fact] + public void with_valid_single_node_connection_string() { + EventStoreClientSettings settings; + + settings = EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1/?maxDiscoverAttempts=13&DiscoveryInterval=37&gossipTimeout=33&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false"); + Assert.Equal("user", settings.DefaultCredentials.Username); + Assert.Equal("pass", settings.DefaultCredentials.Password); + Assert.Equal("https://127.0.0.1:2113/",settings.ConnectivitySettings.Address.ToString()); + Assert.Empty(settings.ConnectivitySettings.GossipSeeds); + Assert.Null(settings.ConnectivitySettings.IpGossipSeeds); + Assert.Null(settings.ConnectivitySettings.DnsGossipSeeds); + Assert.True(settings.ConnectivitySettings.UseHttps); + Assert.Equal(13, settings.ConnectivitySettings.MaxDiscoverAttempts); + Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalSeconds); + Assert.Equal(33, settings.ConnectivitySettings.GossipTimeout.TotalSeconds); + Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); + Assert.NotNull(settings.CreateHttpMessageHandler); + + settings = EventStoreClientSettings.Create("esdb://127.0.0.1/?connectionName=test&maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tls=true&tlsVerifyCert=true&operationTimeout=330&throwOnAppendFailure=faLse"); + Assert.Null(settings.DefaultCredentials); + Assert.Equal("test", settings.ConnectionName); + Assert.Equal("https://127.0.0.1:2113/",settings.ConnectivitySettings.Address.ToString()); + Assert.Empty(settings.ConnectivitySettings.GossipSeeds); + Assert.Null(settings.ConnectivitySettings.IpGossipSeeds); + Assert.Null(settings.ConnectivitySettings.DnsGossipSeeds); + Assert.True(settings.ConnectivitySettings.UseHttps); + Assert.Equal(13, settings.ConnectivitySettings.MaxDiscoverAttempts); + Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalSeconds); + Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); + Assert.Null(settings.CreateHttpMessageHandler); + Assert.Equal(330, settings.OperationOptions.TimeoutAfter.Value.TotalSeconds); + Assert.False(settings.OperationOptions.ThrowOnAppendFailure); + + settings = EventStoreClientSettings.Create("esdb://hostname:4321/?tls=false"); + Assert.Null(settings.DefaultCredentials); + Assert.Equal("http://hostname:4321/",settings.ConnectivitySettings.Address.ToString()); + Assert.Empty(settings.ConnectivitySettings.GossipSeeds); + Assert.Null(settings.ConnectivitySettings.IpGossipSeeds); + Assert.Null(settings.ConnectivitySettings.DnsGossipSeeds); + Assert.False(settings.ConnectivitySettings.UseHttps); + Assert.Null(settings.CreateHttpMessageHandler); + } + + [Fact] + public void with_default_settings() { + EventStoreClientSettings settings; + settings = EventStoreClientSettings.Create("esdb://hostname:4321/"); + + Assert.Null(settings.ConnectionName); + Assert.Equal(EventStoreClientConnectivitySettings.Default.Address.Scheme, settings.ConnectivitySettings.Address.Scheme); + Assert.Equal(EventStoreClientConnectivitySettings.Default.DiscoveryInterval.TotalSeconds, settings.ConnectivitySettings.DiscoveryInterval.TotalSeconds); + Assert.Null(EventStoreClientConnectivitySettings.Default.DnsGossipSeeds); + Assert.Empty(EventStoreClientConnectivitySettings.Default.GossipSeeds); + Assert.Equal(EventStoreClientConnectivitySettings.Default.GossipTimeout.TotalSeconds, settings.ConnectivitySettings.GossipTimeout.TotalSeconds); + Assert.Null(EventStoreClientConnectivitySettings.Default.IpGossipSeeds); + Assert.Equal(EventStoreClientConnectivitySettings.Default.MaxDiscoverAttempts, settings.ConnectivitySettings.MaxDiscoverAttempts); + Assert.Equal(EventStoreClientConnectivitySettings.Default.NodePreference, settings.ConnectivitySettings.NodePreference); + Assert.Equal(EventStoreClientConnectivitySettings.Default.UseHttps, settings.ConnectivitySettings.UseHttps); + Assert.Equal(EventStoreClientOperationOptions.Default.TimeoutAfter.Value.TotalSeconds, settings.OperationOptions.TimeoutAfter.Value.TotalSeconds); + Assert.Equal(EventStoreClientOperationOptions.Default.ThrowOnAppendFailure, settings.OperationOptions.ThrowOnAppendFailure); + } + + [Fact] + public void with_valid_cluster_connection_string() { + EventStoreClientSettings settings; + + settings = EventStoreClientSettings.Create("esdb://user:pass@127.0.0.1,127.0.0.2:3321,127.0.0.3/?maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false"); + Assert.Equal("user", settings.DefaultCredentials.Username); + Assert.Equal("pass", settings.DefaultCredentials.Password); + Assert.NotEmpty(settings.ConnectivitySettings.GossipSeeds); + Assert.NotNull(settings.ConnectivitySettings.IpGossipSeeds); + Assert.Null(settings.ConnectivitySettings.DnsGossipSeeds); + Assert.True(settings.ConnectivitySettings.UseHttps); + Assert.True(settings.ConnectivitySettings.IpGossipSeeds.Length == 3 && + Equals(settings.ConnectivitySettings.IpGossipSeeds[0].Address, IPAddress.Parse("127.0.0.1")) && + Equals(settings.ConnectivitySettings.IpGossipSeeds[0].Port, 2113) && + Equals(settings.ConnectivitySettings.IpGossipSeeds[1].Address, IPAddress.Parse("127.0.0.2")) && + Equals(settings.ConnectivitySettings.IpGossipSeeds[1].Port, 3321) && + Equals(settings.ConnectivitySettings.IpGossipSeeds[2].Address, IPAddress.Parse("127.0.0.3")) && + Equals(settings.ConnectivitySettings.IpGossipSeeds[2].Port, 2113)); + Assert.Equal(13, settings.ConnectivitySettings.MaxDiscoverAttempts); + Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalSeconds); + Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); + Assert.NotNull(settings.CreateHttpMessageHandler); + + + settings = EventStoreClientSettings.Create("esdb://user:pass@host1,host2:3321,127.0.0.3/?tls=false&maxDiscoverAttempts=13&DiscoveryInterval=37&nOdEPrEfErence=FoLLoWer&tlsVerifyCert=false"); + Assert.Equal("user", settings.DefaultCredentials.Username); + Assert.Equal("pass", settings.DefaultCredentials.Password); + Assert.NotEmpty(settings.ConnectivitySettings.GossipSeeds); + Assert.Null(settings.ConnectivitySettings.IpGossipSeeds); + Assert.NotNull(settings.ConnectivitySettings.DnsGossipSeeds); + Assert.False(settings.ConnectivitySettings.UseHttps); + Assert.True(settings.ConnectivitySettings.DnsGossipSeeds.Length == 3 && + Equals(settings.ConnectivitySettings.DnsGossipSeeds[0].Host, "host1") && + Equals(settings.ConnectivitySettings.DnsGossipSeeds[0].Port, 2113) && + Equals(settings.ConnectivitySettings.DnsGossipSeeds[1].Host, "host2") && + Equals(settings.ConnectivitySettings.DnsGossipSeeds[1].Port, 3321) && + Equals(settings.ConnectivitySettings.DnsGossipSeeds[2].Host, "127.0.0.3") && + Equals(settings.ConnectivitySettings.DnsGossipSeeds[2].Port, 2113)); + Assert.Equal(13, settings.ConnectivitySettings.MaxDiscoverAttempts); + Assert.Equal(37, settings.ConnectivitySettings.DiscoveryInterval.TotalSeconds); + Assert.Equal(NodePreference.Follower, settings.ConnectivitySettings.NodePreference); + Assert.NotNull(settings.CreateHttpMessageHandler); + } + + [Fact] + public void with_different_tls_settings() { + EventStoreClientSettings settings; + + settings = EventStoreClientSettings.Create("esdb://127.0.0.1/"); + Assert.Equal(Uri.UriSchemeHttps, settings.ConnectivitySettings.Address.Scheme); + Assert.True(settings.ConnectivitySettings.UseHttps); + + settings = EventStoreClientSettings.Create("esdb://127.0.0.1/?tls=true"); + Assert.Equal(Uri.UriSchemeHttps, settings.ConnectivitySettings.Address.Scheme); + Assert.True(settings.ConnectivitySettings.UseHttps); + + settings = EventStoreClientSettings.Create("esdb://127.0.0.1/?tls=FaLsE"); + Assert.Equal(Uri.UriSchemeHttp, settings.ConnectivitySettings.Address.Scheme); + Assert.False(settings.ConnectivitySettings.UseHttps); + + settings = EventStoreClientSettings.Create("esdb://127.0.0.1,127.0.0.2:3321,127.0.0.3/"); + Assert.True(settings.ConnectivitySettings.UseHttps); + + settings = EventStoreClientSettings.Create("esdb://127.0.0.1,127.0.0.2:3321,127.0.0.3/?tls=true"); + Assert.True(settings.ConnectivitySettings.UseHttps); + + settings = EventStoreClientSettings.Create("esdb://127.0.0.1,127.0.0.2:3321,127.0.0.3/?tls=fAlSe"); + Assert.False(settings.ConnectivitySettings.UseHttps); + } + + [Fact] + public void with_different_tls_verify_cert_settings() { + EventStoreClientSettings settings; + + settings = EventStoreClientSettings.Create("esdb://127.0.0.1/"); + Assert.Null(settings.CreateHttpMessageHandler); + + settings = EventStoreClientSettings.Create("esdb://127.0.0.1/?tlsVerifyCert=TrUe"); + Assert.Null(settings.CreateHttpMessageHandler); + + settings = EventStoreClientSettings.Create("esdb://127.0.0.1/?tlsVerifyCert=FaLsE"); + Assert.NotNull(settings.CreateHttpMessageHandler); + + settings = EventStoreClientSettings.Create("esdb://127.0.0.1,127.0.0.2:3321,127.0.0.3/"); + Assert.Null(settings.CreateHttpMessageHandler); + + settings = EventStoreClientSettings.Create("esdb://127.0.0.1,127.0.0.2:3321,127.0.0.3/?tlsVerifyCert=true"); + Assert.Null(settings.CreateHttpMessageHandler); + + settings = EventStoreClientSettings.Create("esdb://127.0.0.1,127.0.0.2:3321,127.0.0.3/?tlsVerifyCert=false"); + Assert.NotNull(settings.CreateHttpMessageHandler); + } + + } +}