diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 00000000..b0fed54e --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,18 @@ + + + true + + + + + + + + + + + + + + + diff --git a/src/NRedisStack/Auxiliary.cs b/src/NRedisStack/Auxiliary.cs index 3a508c2d..a67d7d39 100644 --- a/src/NRedisStack/Auxiliary.cs +++ b/src/NRedisStack/Auxiliary.cs @@ -1,4 +1,3 @@ -using NRedisStack.Core; using NRedisStack.RedisStackCommands; using StackExchange.Redis; @@ -6,13 +5,6 @@ namespace NRedisStack { public static class Auxiliary { - private static string? _libraryName = $"NRedisStack(.NET_v{Environment.Version})"; - private static bool _setInfo = true; - public static void ResetInfoDefaults() - { - _setInfo = true; - _libraryName = $"NRedisStack(.NET_v{Environment.Version})"; - } public static List MergeArgs(RedisKey key, params RedisValue[] items) { var args = new List(items.Length + 1) { key }; @@ -34,46 +26,17 @@ public static object[] AssembleNonNullArguments(params object?[] arguments) return args.ToArray(); } - // TODO: add all the signatures of GetDatabase - public static IDatabase GetDatabase(this ConnectionMultiplexer redis, - string? LibraryName) - { - var _db = redis.GetDatabase(); - if (LibraryName == null) // the user wants to disable the library name and version sending - _setInfo = false; - - else // the user set his own the library name - _libraryName = $"NRedisStack({LibraryName};.NET_v{Environment.Version})"; - - return _db; - } + // public static IDatabase GetDatabase(this ConnectionMultiplexer redis) => redis.GetDatabase("", ""); - private static void SetInfoInPipeline(this IDatabase db) - { - if (_libraryName == null) return; - Pipeline pipeline = new Pipeline(db); - _ = pipeline.Db.ClientSetInfoAsync(SetInfoAttr.LibraryName, _libraryName!); - _ = pipeline.Db.ClientSetInfoAsync(SetInfoAttr.LibraryVersion, GetNRedisStackVersion()); - pipeline.Execute(); - } public static RedisResult Execute(this IDatabase db, SerializedCommand command) { - if (_setInfo) - { - _setInfo = false; - db.SetInfoInPipeline(); - } return db.Execute(command.Command, command.Args); } public async static Task ExecuteAsync(this IDatabaseAsync db, SerializedCommand command) { - if (_setInfo) - { - _setInfo = false; - ((IDatabase)db).SetInfoInPipeline(); - } + var compareVersions = db.Multiplexer.GetServer(db.Multiplexer.GetEndPoints()[0]).Version.CompareTo(new Version(7, 1, 242)); return await db.ExecuteAsync(command.Command, command.Args); } diff --git a/src/NRedisStack/Configuration.cs b/src/NRedisStack/Configuration.cs new file mode 100644 index 00000000..7ff8905e --- /dev/null +++ b/src/NRedisStack/Configuration.cs @@ -0,0 +1,46 @@ +using System.Dynamic; +using StackExchange.Redis; + +namespace NRedisStack +{ + public class Configuration + { + public ConfigurationOptions Options { get; set; } = new ConfigurationOptions(); + + public static Configuration Parse(string redisConnectionString) => + new Configuration().DoParse(redisConnectionString); + + public static Configuration Parse(ConfigurationOptions options) => + new Configuration().DoParse(options); + + private Configuration DoParse(string redisConnectionString) + { + try // Redis URI parsing + { + Options = RedisUriParser.FromUri(redisConnectionString); + } + catch (UriFormatException) // StackExchange.Redis connection string parsing + { + Options = ConfigurationOptions.Parse(redisConnectionString); + } + SetLibName(Options); + return this; + } + + private Configuration DoParse(ConfigurationOptions options) + { + Options = options; + SetLibName(Options); + return this; + } + + internal static void SetLibName(ConfigurationOptions options) + { + if (options.LibraryName != null) // the user set his own the library name + options.LibraryName = $"NRedisStack({options.LibraryName};.NET_v{Environment.Version})"; + else // the default library name and version sending + options.LibraryName = $"NRedisStack(.NET_v{Environment.Version})"; + } + + } +} \ No newline at end of file diff --git a/src/NRedisStack/ConnectionManager.cs b/src/NRedisStack/ConnectionManager.cs new file mode 100644 index 00000000..12ea981a --- /dev/null +++ b/src/NRedisStack/ConnectionManager.cs @@ -0,0 +1,37 @@ +using StackExchange.Redis; + +namespace NRedisStack +{ + public static class ConnectionManager + { + public static IConnectionMultiplexer Connect(string redisConnectionString) + { + return Connect(Configuration.Parse(redisConnectionString)); + } + + public static async Task ConnectAsync(string redisConnectionString) + { + return await ConnectAsync(Configuration.Parse(redisConnectionString)); + } + + public static IConnectionMultiplexer Connect(Configuration configuration) + { + return ConnectionMultiplexer.Connect(configuration.Options); + } + + public static async Task ConnectAsync(Configuration configuration) + { + return await ConnectionMultiplexer.ConnectAsync(configuration.Options); + } + + public static IConnectionMultiplexer Connect(ConfigurationOptions options) + { + return Connect(Configuration.Parse(options)); + } + + public static async Task ConnectAsync(ConfigurationOptions options) + { + return await ConnectAsync(Configuration.Parse(options)); + } + } +} \ No newline at end of file diff --git a/src/NRedisStack/NRedisStack.csproj b/src/NRedisStack/NRedisStack.csproj index 58467115..5cab183f 100644 --- a/src/NRedisStack/NRedisStack.csproj +++ b/src/NRedisStack/NRedisStack.csproj @@ -15,9 +15,9 @@ - - - + + + diff --git a/src/NRedisStack/RedisUriParser.cs b/src/NRedisStack/RedisUriParser.cs new file mode 100644 index 00000000..b49ef428 --- /dev/null +++ b/src/NRedisStack/RedisUriParser.cs @@ -0,0 +1,158 @@ +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using StackExchange.Redis; + +[assembly: InternalsVisibleTo("NRedisStack.Tests")] + +namespace NRedisStack +{ + /// + /// URI parsing utility. + /// + internal static class RedisUriParser + { + internal static string defaultHost = "localhost"; + internal static int defaultPort = 6379; + + // The options: + internal const string + Timeout = "timeout", + ClientName = "clientname", + Sentinel_primary_name = "sentinel_primary_name", + Endpoint = "endpoint", + AllowAdmin = "allowadmin", + AbortConnect = "abortconnect", + AsyncTimeout = "asynctimeout", + Retry = "retry", + Protocol = "protocol", + LibraryName = "lib_name"; + // TODO: add library version when it will be available + + /// + /// Parses a Config options for StackExchange Redis from the URI. + /// + /// Redis Uri string + /// A configuration options result for SE.Redis. + internal static ConfigurationOptions FromUri(string redisUri) + { + var options = new ConfigurationOptions(); + + if (string.IsNullOrEmpty(redisUri)) + { + options.EndPoints.Add($"{defaultHost}:{defaultPort}"); + return options; + } + + var uri = new Uri(redisUri); + ParseHost(options, uri); + ParseUserInfo(options, uri); + ParseQueryArguments(options, uri); + ParseDefaultDatabase(options, uri); + options.Ssl = uri.Scheme == "rediss"; + options.AbortOnConnectFail = false; + return options; + } + + private static void ParseDefaultDatabase(ConfigurationOptions options, Uri uri) + { + if (string.IsNullOrEmpty(uri.AbsolutePath)) + { + return; + } + + var dbNumStr = Regex.Match(uri.AbsolutePath, "[0-9]+").Value; + int dbNum; + if (int.TryParse(dbNumStr, out dbNum)) + { + options.DefaultDatabase = dbNum; + } + } + + private static IList> ParseQuery(string query) => + query.Split('&').Select(x => + new KeyValuePair(x.Split('=').First(), x.Split('=').Last())).ToList(); + + private static void ParseUserInfo(ConfigurationOptions options, Uri uri) + { + if (string.IsNullOrEmpty(uri.UserInfo)) + { + return; + } + + var userInfo = uri.UserInfo.Split(':'); + + if (userInfo.Length > 1) + { + options.User = Uri.UnescapeDataString(userInfo[0]); + options.Password = Uri.UnescapeDataString(userInfo[1]); + } + + else + { + throw new FormatException("Username and password must be in the form username:password - if there is no username use the format :password"); + } + } + + + private static void ParseHost(ConfigurationOptions options, Uri uri) + { + var port = uri.Port >= 0 ? uri.Port : defaultPort; + var host = !string.IsNullOrEmpty(uri.Host) ? uri.Host : defaultHost; + options.EndPoints.Add($"{host}:{port}"); + } + + private static void ParseQueryArguments(ConfigurationOptions options, Uri uri) + { + if (string.IsNullOrEmpty(uri.Query)) + { + return; + } + + var queryArgs = ParseQuery(uri.Query.Substring(1)); + + var actions = new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + { Timeout, value => SetTimeoutOptions(options, value) }, + { ClientName, value => options.ClientName = value }, + { Sentinel_primary_name, value => options.ServiceName = value }, + { Endpoint, value => options.EndPoints.Add(value) }, + { AllowAdmin, value => options.AllowAdmin = bool.Parse(value) }, + { AbortConnect, value => options.AbortOnConnectFail = bool.Parse(value) }, + { AsyncTimeout, value => options.AsyncTimeout = int.Parse(value) }, + { Retry, value => options.ConnectRetry = int.Parse(value) }, + { Protocol, value => ParseRedisProtocol(options, value) }, + { LibraryName, value => options.LibraryName = value } + // TODO: add more options (especially the library version when it will be available) + }; + + foreach (var arg in queryArgs.Where(arg => actions.ContainsKey(arg.Key))) + { + actions[arg.Key.ToLower()](arg.Value); + } + } + + private static void ParseRedisProtocol(ConfigurationOptions options, string value) + { + switch (value) + { + case "2": + options.Protocol = RedisProtocol.Resp2; + break; + case "3": + options.Protocol = RedisProtocol.Resp3; ; + break; + default: + throw new FormatException("Invalid protocol specified"); + } + } + + private static void SetTimeoutOptions(ConfigurationOptions options, string value) + { + var timeout = int.Parse(value); + options.AsyncTimeout = timeout; + options.SyncTimeout = timeout; + options.ConnectTimeout = timeout; + } + + } +} diff --git a/tests/Doc/Doc.csproj b/tests/Doc/Doc.csproj index c8fef667..75b2b7a2 100644 --- a/tests/Doc/Doc.csproj +++ b/tests/Doc/Doc.csproj @@ -9,17 +9,19 @@ false Module + - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + + - \ No newline at end of file + diff --git a/tests/NRedisStack.Tests/Core Commands/CoreTests.cs b/tests/NRedisStack.Tests/Core Commands/CoreTests.cs deleted file mode 100644 index 9a3d763a..00000000 --- a/tests/NRedisStack.Tests/Core Commands/CoreTests.cs +++ /dev/null @@ -1,149 +0,0 @@ -using Xunit; -using NRedisStack.Core; -using NRedisStack; -using static NRedisStack.Auxiliary; -using StackExchange.Redis; -using System.Xml.Linq; -using System.Reflection; -using NRedisStack.RedisStackCommands; - - -namespace NRedisStack.Tests.Core; - -public class CoreTests : AbstractNRedisStackTest, IDisposable -{ - public CoreTests(RedisFixture redisFixture) : base(redisFixture) { } - - - [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] - public void TestSimpleSetInfo() - { - var redis = ConnectionMultiplexer.Connect("localhost"); - var db = redis.GetDatabase(); - db.Execute("FLUSHALL"); - - db.ClientSetInfo(SetInfoAttr.LibraryName, "TestLibraryName"); - db.ClientSetInfo(SetInfoAttr.LibraryVersion, "1.2.3"); - - var info = db.Execute("CLIENT", "INFO").ToString(); - Assert.EndsWith($"lib-name=TestLibraryName lib-ver=1.2.3\n", info); - } - - [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] - public async Task TestSimpleSetInfoAsync() - { - var redis = ConnectionMultiplexer.Connect("localhost"); - var db = redis.GetDatabase(); - db.Execute("FLUSHALL"); - - await db.ClientSetInfoAsync(SetInfoAttr.LibraryName, "TestLibraryName"); - await db.ClientSetInfoAsync(SetInfoAttr.LibraryVersion, "1.2.3"); - - var info = db.Execute("CLIENT", "INFO").ToString(); - Assert.EndsWith($"lib-name=TestLibraryName lib-ver=1.2.3\n", info); - } - - [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] - public void TestSetInfoDefaultValue() - { - ResetInfoDefaults(); // demonstrate first connection - var redis = ConnectionMultiplexer.Connect("localhost"); - var db = redis.GetDatabase(); - db.Execute("FLUSHALL"); - - db.Execute(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version. - - var info = db.Execute("CLIENT", "INFO").ToString(); - Assert.EndsWith($"lib-name=NRedisStack(.NET_v{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info); - } - - [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] - public async Task TestSetInfoDefaultValueAsync() - { - ResetInfoDefaults(); // demonstrate first connection - var redis = ConnectionMultiplexer.Connect("localhost"); - var db = redis.GetDatabase(); - db.Execute("FLUSHALL"); - - await db.ExecuteAsync(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version. - - var info = (await db.ExecuteAsync("CLIENT", "INFO")).ToString(); - Assert.EndsWith($"lib-name=NRedisStack(.NET_v{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info); - } - - [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] - public void TestSetInfoWithValue() - { - ResetInfoDefaults(); // demonstrate first connection - var redis = ConnectionMultiplexer.Connect("localhost"); - var db = redis.GetDatabase("MyLibraryName;v1.0.0"); - db.Execute("FLUSHALL"); - - db.Execute(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version. - - var info = db.Execute("CLIENT", "INFO").ToString(); - Assert.EndsWith($"NRedisStack(MyLibraryName;v1.0.0;.NET_v{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info); - } - - [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] - public async Task TestSetInfoWithValueAsync() - { - ResetInfoDefaults(); // demonstrate first connection - var redis = ConnectionMultiplexer.Connect("localhost"); - var db = redis.GetDatabase("MyLibraryName;v1.0.0"); - db.Execute("FLUSHALL"); - - await db.ExecuteAsync(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version. - - var info = (await db.ExecuteAsync("CLIENT", "INFO")).ToString(); - Assert.EndsWith($"NRedisStack(MyLibraryName;v1.0.0;.NET_v{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info); - } - - [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] - public void TestSetInfoNull() - { - ResetInfoDefaults(); // demonstrate first connection - var redis = ConnectionMultiplexer.Connect("localhost"); - var db = redis.GetDatabase(null); - - db.Execute("FLUSHALL"); - var infoBefore = db.Execute("CLIENT", "INFO").ToString(); - db.Execute(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version. - - var infoAfter = db.Execute("CLIENT", "INFO").ToString(); - // Find the indices of "lib-name=" in the strings - int infoAfterLibNameIndex = infoAfter!.IndexOf("lib-name="); - int infoBeforeLibNameIndex = infoBefore!.IndexOf("lib-name="); - - // Extract the sub-strings starting from "lib-name=" - string infoAfterLibNameToEnd = infoAfter.Substring(infoAfterLibNameIndex); - string infoBeforeLibNameToEnd = infoBefore.Substring(infoBeforeLibNameIndex); - - // Assert that the extracted sub-strings are equal - Assert.Equal(infoAfterLibNameToEnd, infoBeforeLibNameToEnd); - } - - [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] - public async Task TestSetInfoNullAsync() - { - ResetInfoDefaults(); // demonstrate first connection - var redis = ConnectionMultiplexer.Connect("localhost"); - var db = redis.GetDatabase(null); - - db.Execute("FLUSHALL"); - var infoBefore = (await db.ExecuteAsync("CLIENT", "INFO")).ToString(); - await db.ExecuteAsync(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version. - - var infoAfter = (await db.ExecuteAsync("CLIENT", "INFO")).ToString(); - // Find the indices of "lib-name=" in the strings - int infoAfterLibNameIndex = infoAfter!.IndexOf("lib-name="); - int infoBeforeLibNameIndex = infoBefore!.IndexOf("lib-name="); - - // Extract the sub-strings starting from "lib-name=" - string infoAfterLibNameToEnd = infoAfter.Substring(infoAfterLibNameIndex); - string infoBeforeLibNameToEnd = infoBefore.Substring(infoBeforeLibNameIndex); - - // Assert that the extracted sub-strings are equal - Assert.Equal(infoAfterLibNameToEnd, infoBeforeLibNameToEnd); - } -} \ No newline at end of file diff --git a/tests/NRedisStack.Tests/NRedisStack.Tests.csproj b/tests/NRedisStack.Tests/NRedisStack.Tests.csproj index e66bf0a1..a6d4452e 100644 --- a/tests/NRedisStack.Tests/NRedisStack.Tests.csproj +++ b/tests/NRedisStack.Tests/NRedisStack.Tests.csproj @@ -6,27 +6,20 @@ enable enable latest - false - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - + + + + + + + + + + diff --git a/tests/NRedisStack.Tests/NRedisStackConfigurationTests.cs b/tests/NRedisStack.Tests/NRedisStackConfigurationTests.cs new file mode 100644 index 00000000..58fb9747 --- /dev/null +++ b/tests/NRedisStack.Tests/NRedisStackConfigurationTests.cs @@ -0,0 +1,138 @@ +using Xunit; +using NRedisStack.Core; +using static NRedisStack.Auxiliary; +using System.Net; +using StackExchange.Redis; +using System.Runtime.InteropServices; + + +namespace NRedisStack.Tests.Core; + +public class NRedisStackConfigurationTests : AbstractNRedisStackTest, IDisposable +{ + public NRedisStackConfigurationTests(RedisFixture redisFixture) : base(redisFixture) { } + + // TODO: add tests + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] + public void TestConnectionWithConfigurationOptions() + { + var SEconfigOptions = new ConfigurationOptions() { EndPoints = { "localhost" } }; + var db = ConnectionManager.Connect(SEconfigOptions).GetDatabase(); + db.ClientSetInfo(SetInfoAttr.LibraryVersion, GetNRedisStackVersion()); // delete this line after the library version will be available and auto set + var info = db.Execute("CLIENT", "INFO").ToString(); + Assert.EndsWith($"lib-name=NRedisStack(.NET_v{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] + public async Task TestConnectionWithConfigurationOptionsAsync() + { + var SEconfigOptions = new ConfigurationOptions() { EndPoints = { "localhost" } }; + var db = (await ConnectionManager.ConnectAsync(SEconfigOptions)).GetDatabase(); + await db.ClientSetInfoAsync(SetInfoAttr.LibraryVersion, GetNRedisStackVersion()); // delete this line after the library version will be available and auto set + var info = db.Execute("CLIENT", "INFO").ToString(); + Assert.EndsWith($"lib-name=NRedisStack(.NET_v{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] + public void TestDefaultLibName() + { + var db = ConnectionManager.Connect("redis://localhost").GetDatabase(); + db.ClientSetInfo(SetInfoAttr.LibraryVersion, GetNRedisStackVersion()); // delete this line after the library version will be available and auto set + var info = db.Execute("CLIENT", "INFO").ToString(); + Assert.EndsWith($"lib-name=NRedisStack(.NET_v{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] + public async Task TestDefaultLibNameAsync() + { + var db = (await ConnectionManager.ConnectAsync("redis://localhost")).GetDatabase(); + await db.ClientSetInfoAsync(SetInfoAttr.LibraryVersion, GetNRedisStackVersion()); // delete this line after the library version will be available and auto set + var info = db.Execute("CLIENT", "INFO").ToString(); + Assert.EndsWith($"lib-name=NRedisStack(.NET_v{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] + public void TestLibNameSet() + { + var configuration = Configuration.Parse("redis://localhost?lib_name=MyLib"); + var db = ConnectionManager.Connect(configuration).GetDatabase(); + db.ClientSetInfo(SetInfoAttr.LibraryVersion, GetNRedisStackVersion()); // delete this line after the library version will be available and auto set + var info = db.Execute("CLIENT", "INFO").ToString(); + Assert.EndsWith($"lib-name=NRedisStack(MyLib;.NET_v{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] + public async Task TestLibNameSetAsync() + { + var configuration = Configuration.Parse("redis://localhost?lib_name=MyLib"); + var db = (await ConnectionManager.ConnectAsync(configuration)).GetDatabase(); + await db.ClientSetInfoAsync(SetInfoAttr.LibraryVersion, GetNRedisStackVersion()); // delete this line after the library version will be available and auto set + var info = db.Execute("CLIENT", "INFO").ToString(); + Assert.EndsWith($"lib-name=NRedisStack(MyLib;.NET_v{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] + public void TestDefaultLibNameStackExchangeString() + { + var db = ConnectionManager.Connect("localhost").GetDatabase(); // StackExchange.Redis connection string (without the redis:// at the start) + db.ClientSetInfo(SetInfoAttr.LibraryVersion, GetNRedisStackVersion()); // delete this line after the library version will be available and auto set + var info = db.Execute("CLIENT", "INFO").ToString(); + Assert.EndsWith($"lib-name=NRedisStack(.NET_v{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info); + } + + [SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")] + public async Task TestDefaultLibNameStackExchangeStringAsync() + { + var db = (await ConnectionManager.ConnectAsync("localhost")).GetDatabase(); // StackExchange.Redis connection string (without the redis:// at the start) + await db.ClientSetInfoAsync(SetInfoAttr.LibraryVersion, GetNRedisStackVersion()); // delete this line after the library version will be available and auto set + var info = db.Execute("CLIENT", "INFO").ToString(); + Assert.EndsWith($"lib-name=NRedisStack(.NET_v{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info); + } + + #region Configuration parsing tests + [Fact] + public void TestNRedisStackConfigurationOptions() + { + var configuration = Configuration.Parse("redis://username:password@localhost:6379?allowadmin=true&clientname=client&sentinel_primary_name=sentinel&retry=3&timeout=1000&abortconnect=false&asynctimeout=1000&protocol=2"); + Assert.Equal("username", configuration.Options.User); + Assert.Equal("password", configuration.Options.Password); + var endpoint = (DnsEndPoint)configuration.Options.EndPoints.First(); + Assert.Equal("localhost", endpoint.Host); + Assert.Equal(6379, endpoint.Port); + Assert.Equal("client", configuration.Options.ClientName); + Assert.Equal("sentinel", configuration.Options.ServiceName); + Assert.Equal(3, configuration.Options.ConnectRetry); + Assert.Equal(1000, configuration.Options.ConnectTimeout); + Assert.Equal(1000, configuration.Options.AsyncTimeout); + Assert.True(configuration.Options.AllowAdmin); + Assert.False(configuration.Options.AbortOnConnectFail); + } + + [Fact] + public void TestRespConfiguration() + { + var configuration = Configuration.Parse("redis://localhost:6379?protocol=2"); + Assert.Equal(RedisProtocol.Resp2, configuration.Options.Protocol); + configuration = Configuration.Parse("redis://localhost:6379?protocol=3"); + Assert.Equal(RedisProtocol.Resp3, configuration.Options.Protocol); + Assert.Throws(() => Configuration.Parse("redis://localhost:6379?protocol=0")); + } + + [Fact] + public void TestWithMultipleEndpoints() + { + var configuration = Configuration.Parse("rediss://username:password@localhost:6379?endpoint=notSoLocalHost:6379&endpoint=reallyNotSoLocalHost:6379"); + Assert.True(configuration.Options.EndPoints.Any(x => x is DnsEndPoint endpoint && endpoint.Host == "notSoLocalHost" && endpoint.Port == 6379)!); + Assert.True(configuration.Options.EndPoints.Any(x => x is DnsEndPoint endpoint && endpoint.Host == "reallyNotSoLocalHost" && endpoint.Port == 6379)!); + } + + [Fact] + public void TestEmptyUri() + { + var configuration = Configuration.Parse(""); + Assert.Equal("localhost", ((DnsEndPoint)configuration.Options.EndPoints.First()).Host); + Assert.Equal(6379, ((DnsEndPoint)configuration.Options.EndPoints.First()).Port); + } + #endregion +} \ No newline at end of file