From c8c500b21e86be1ba586298cbe283ee57e8908c1 Mon Sep 17 00:00:00 2001 From: currantw Date: Thu, 30 Jul 2026 11:40:37 -0700 Subject: [PATCH 01/11] fix(client): use Format.TryParseEndPoint in GetServers() to support DNS hostnames ConnectionMultiplexer.GetServers() used IPEndPoint.Parse() to parse cluster node addresses, which throws FormatException when the cluster topology contains DNS hostnames (e.g., AWS ElastiCache endpoints). Replace with Format.TryParseEndPoint which correctly handles both IP addresses (returning IPEndPoint) and DNS hostnames (returning DnsEndPoint). Closes #419 Signed-off-by: currantw --- sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs b/sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs index cd8e3fc4..17fff1a4 100644 --- a/sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs +++ b/sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs @@ -128,7 +128,15 @@ public IServer[] GetServers() if (_db!.IsCluster) { Dictionary info = _db.Command(Request.Info([]).ToMultiNodeValue(), Route.AllNodes).GetAwaiter().GetResult(); - return [.. info.Keys.Select(addr => new ValkeyServer(_db, IPEndPoint.Parse(addr)))]; + ValkeyServer[] servers = new ValkeyServer[info.Count]; + int i = 0; + foreach (string addr in info.Keys) + { + if (!Format.TryParseEndPoint(addr, out EndPoint? ep)) + throw new FormatException($"Could not parse endpoint address: '{addr}'"); + servers[i++] = new ValkeyServer(_db, ep); + } + return servers; } else { From ed10d5ed15a00d84e3df1134675dab5ef8eae274 Mon Sep 17 00:00:00 2001 From: currantw Date: Thu, 30 Jul 2026 13:19:49 -0700 Subject: [PATCH 02/11] test(integration): add DNS hostname topology tests for GetServers() Add integration tests that start a cluster with cluster-announce-hostname and verify GetServers()/GetEndPoints(false) correctly return DnsEndPoint instances. Also thread a `host` parameter through ServerManager and Server/ClusterServer to support passing --host to cluster_manager.py. These tests are gated behind VALKEY_GLIDE_DNS_TESTS_ENABLED and require the cluster_manager.py change from valkey-io/valkey-glide#6667. Signed-off-by: currantw --- .../DnsGetServersTests.cs | 90 +++++++++++++++++++ tests/Valkey.Glide.TestUtils/Server.cs | 8 +- tests/Valkey.Glide.TestUtils/ServerManager.cs | 8 +- 3 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 tests/Valkey.Glide.IntegrationTests/DnsGetServersTests.cs diff --git a/tests/Valkey.Glide.IntegrationTests/DnsGetServersTests.cs b/tests/Valkey.Glide.IntegrationTests/DnsGetServersTests.cs new file mode 100644 index 00000000..2c11214c --- /dev/null +++ b/tests/Valkey.Glide.IntegrationTests/DnsGetServersTests.cs @@ -0,0 +1,90 @@ +// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + +using System.Net; + +using Valkey.Glide.TestUtils; + +using static Valkey.Glide.TestUtils.Constants; + +namespace Valkey.Glide.IntegrationTests; + +/// +/// Tests that correctly handles DNS hostnames +/// in cluster topology responses (GitHub issue #419). +/// +public class DnsGetServersTests(DnsGetServersFixture fixture) : IClassFixture +{ + private const string DnsEnabledEnvVar = "VALKEY_GLIDE_DNS_TESTS_ENABLED"; + + [Fact] + public void GetServers_WithDnsHostnameTopology_ReturnsDnsEndPoints() + { + SkipIfDnsTestsNotEnabled(); + + var config = new ConfigurationOptions(); + config.EndPoints.Add(fixture.Server!.Address.Host, fixture.Server.Address.Port); + config.ResponseTimeout = 10000; + + using var conn = ConnectionMultiplexer.Connect(config); + + IServer[] servers = conn.GetServers(); + Assert.True(servers.Length > 0); + + foreach (IServer s in servers) + { + Assert.IsType(s.EndPoint); + Assert.Contains(HostnameTls, s.EndPoint.ToString()); + } + } + + [Fact] + public void GetEndPoints_WithDnsHostnameTopology_ReturnsDnsEndPoints() + { + SkipIfDnsTestsNotEnabled(); + + var config = new ConfigurationOptions(); + config.EndPoints.Add(fixture.Server!.Address.Host, fixture.Server.Address.Port); + config.ResponseTimeout = 10000; + + using var conn = ConnectionMultiplexer.Connect(config); + + EndPoint[] endpoints = conn.GetEndPoints(false); + Assert.True(endpoints.Length > 0); + + foreach (EndPoint ep in endpoints) + { + Assert.IsType(ep); + IServer found = conn.GetServer(ep); + Assert.Equal(ep, found.EndPoint); + } + } + + private static void SkipIfDnsTestsNotEnabled() + => Assert.SkipWhen( + string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DnsEnabledEnvVar)), + "DNS tests are disabled. See DEVELOPER.md for setup instructions."); +} + +/// +/// Fixture that starts a cluster with cluster-announce-hostname set to a DNS name. +/// +public class DnsGetServersFixture : IAsyncLifetime +{ + public ClusterServer? Server { get; private set; } + + public ValueTask InitializeAsync() + { + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("VALKEY_GLIDE_DNS_TESTS_ENABLED"))) + { + Server = new(host: HostnameTls); + } + + return ValueTask.CompletedTask; + } + + public ValueTask DisposeAsync() + { + Server?.Dispose(); + return ValueTask.CompletedTask; + } +} diff --git a/tests/Valkey.Glide.TestUtils/Server.cs b/tests/Valkey.Glide.TestUtils/Server.cs index b7c99560..ec8494c9 100644 --- a/tests/Valkey.Glide.TestUtils/Server.cs +++ b/tests/Valkey.Glide.TestUtils/Server.cs @@ -85,7 +85,8 @@ public abstract class Server : IDisposable protected Server( bool useClusterMode, bool useTls = false, - int? replicaCount = null) + int? replicaCount = null, + string? host = null) { UseTls = useTls; @@ -93,7 +94,8 @@ protected Server( name: _name, useClusterMode: useClusterMode, useTls: UseTls, - replicaCount: replicaCount).First(); + replicaCount: replicaCount, + host: host).First(); if (UseTls) { @@ -186,7 +188,7 @@ protected static async Task CreateClientAsync(Func> factory) /// /// Valkey cluster server. /// -public sealed class ClusterServer(bool useTls = false) : Server(useClusterMode: true, useTls: useTls) +public sealed class ClusterServer(bool useTls = false, string? host = null) : Server(useClusterMode: true, useTls: useTls, host: host) { #region Public Methods diff --git a/tests/Valkey.Glide.TestUtils/ServerManager.cs b/tests/Valkey.Glide.TestUtils/ServerManager.cs index 1995256a..9145caa7 100644 --- a/tests/Valkey.Glide.TestUtils/ServerManager.cs +++ b/tests/Valkey.Glide.TestUtils/ServerManager.cs @@ -67,7 +67,8 @@ public static IList
StartServer( string name, bool useClusterMode = false, bool useTls = false, - int? replicaCount = null) + int? replicaCount = null, + string? host = null) { // Build command arguments. List args = []; @@ -77,6 +78,11 @@ public static IList
StartServer( args.Add("--tls"); } + if (host != null) + { + args.AddRange(["--host", host]); + } + args.Add("start"); args.AddRange(["--prefix", name]); args.AddRange(["-r", (replicaCount ?? DefaultReplicaCount).ToString()]); From d273bf371b724ce31bc0b5d211e72a57ac7c7957 Mon Sep 17 00:00:00 2001 From: currantw Date: Thu, 30 Jul 2026 14:08:32 -0700 Subject: [PATCH 03/11] refactor: move DNS tests to DnsTests.cs and use List in GetServers() - Move GetServers/GetEndPoints DNS topology tests into existing DnsTests class with DnsTestsFixture (add DnsClusterServer to the fixture). - Use List instead of array+index in GetServers(). - Add host parameter to StandaloneServer for consistency. - Make TLS server startup non-fatal in DnsTestsFixture so non-TLS tests can still run when TLS servers fail to start. - Remove separate DnsGetServersTests.cs file. Signed-off-by: currantw --- .../Abstract/ConnectionMultiplexer.cs | 7 +- .../DnsGetServersTests.cs | 90 ------------------- .../Valkey.Glide.IntegrationTests/DnsTests.cs | 61 ++++++++++++- tests/Valkey.Glide.TestUtils/Server.cs | 3 +- 4 files changed, 64 insertions(+), 97 deletions(-) delete mode 100644 tests/Valkey.Glide.IntegrationTests/DnsGetServersTests.cs diff --git a/sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs b/sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs index 17fff1a4..aa8922ef 100644 --- a/sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs +++ b/sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs @@ -128,15 +128,14 @@ public IServer[] GetServers() if (_db!.IsCluster) { Dictionary info = _db.Command(Request.Info([]).ToMultiNodeValue(), Route.AllNodes).GetAwaiter().GetResult(); - ValkeyServer[] servers = new ValkeyServer[info.Count]; - int i = 0; + List servers = new(info.Count); foreach (string addr in info.Keys) { if (!Format.TryParseEndPoint(addr, out EndPoint? ep)) throw new FormatException($"Could not parse endpoint address: '{addr}'"); - servers[i++] = new ValkeyServer(_db, ep); + servers.Add(new ValkeyServer(_db, ep)); } - return servers; + return [.. servers]; } else { diff --git a/tests/Valkey.Glide.IntegrationTests/DnsGetServersTests.cs b/tests/Valkey.Glide.IntegrationTests/DnsGetServersTests.cs deleted file mode 100644 index 2c11214c..00000000 --- a/tests/Valkey.Glide.IntegrationTests/DnsGetServersTests.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 - -using System.Net; - -using Valkey.Glide.TestUtils; - -using static Valkey.Glide.TestUtils.Constants; - -namespace Valkey.Glide.IntegrationTests; - -/// -/// Tests that correctly handles DNS hostnames -/// in cluster topology responses (GitHub issue #419). -/// -public class DnsGetServersTests(DnsGetServersFixture fixture) : IClassFixture -{ - private const string DnsEnabledEnvVar = "VALKEY_GLIDE_DNS_TESTS_ENABLED"; - - [Fact] - public void GetServers_WithDnsHostnameTopology_ReturnsDnsEndPoints() - { - SkipIfDnsTestsNotEnabled(); - - var config = new ConfigurationOptions(); - config.EndPoints.Add(fixture.Server!.Address.Host, fixture.Server.Address.Port); - config.ResponseTimeout = 10000; - - using var conn = ConnectionMultiplexer.Connect(config); - - IServer[] servers = conn.GetServers(); - Assert.True(servers.Length > 0); - - foreach (IServer s in servers) - { - Assert.IsType(s.EndPoint); - Assert.Contains(HostnameTls, s.EndPoint.ToString()); - } - } - - [Fact] - public void GetEndPoints_WithDnsHostnameTopology_ReturnsDnsEndPoints() - { - SkipIfDnsTestsNotEnabled(); - - var config = new ConfigurationOptions(); - config.EndPoints.Add(fixture.Server!.Address.Host, fixture.Server.Address.Port); - config.ResponseTimeout = 10000; - - using var conn = ConnectionMultiplexer.Connect(config); - - EndPoint[] endpoints = conn.GetEndPoints(false); - Assert.True(endpoints.Length > 0); - - foreach (EndPoint ep in endpoints) - { - Assert.IsType(ep); - IServer found = conn.GetServer(ep); - Assert.Equal(ep, found.EndPoint); - } - } - - private static void SkipIfDnsTestsNotEnabled() - => Assert.SkipWhen( - string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DnsEnabledEnvVar)), - "DNS tests are disabled. See DEVELOPER.md for setup instructions."); -} - -/// -/// Fixture that starts a cluster with cluster-announce-hostname set to a DNS name. -/// -public class DnsGetServersFixture : IAsyncLifetime -{ - public ClusterServer? Server { get; private set; } - - public ValueTask InitializeAsync() - { - if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("VALKEY_GLIDE_DNS_TESTS_ENABLED"))) - { - Server = new(host: HostnameTls); - } - - return ValueTask.CompletedTask; - } - - public ValueTask DisposeAsync() - { - Server?.Dispose(); - return ValueTask.CompletedTask; - } -} diff --git a/tests/Valkey.Glide.IntegrationTests/DnsTests.cs b/tests/Valkey.Glide.IntegrationTests/DnsTests.cs index fe0ed9e3..f80d4078 100644 --- a/tests/Valkey.Glide.IntegrationTests/DnsTests.cs +++ b/tests/Valkey.Glide.IntegrationTests/DnsTests.cs @@ -1,5 +1,7 @@ // Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 +using System.Net; + using Valkey.Glide.TestUtils; using static Valkey.Glide.ConnectionConfiguration; @@ -63,6 +65,49 @@ public async Task Tls_WithHostnameNotInCertificate_Fails(bool useCluster) => await BuildClient(useCluster, useTls: true, HostnameNoTls)); } + [Fact] + public void GetServers_WithDnsHostnameTopology_ReturnsDnsEndPoints() + { + SkipIfDnsTestsNotEnabled(); + + var config = new ConfigurationOptions(); + config.EndPoints.Add(fixture.DnsClusterServer!.Address.Host, fixture.DnsClusterServer.Address.Port); + config.ResponseTimeout = 10000; + + using var conn = ConnectionMultiplexer.Connect(config); + + IServer[] servers = conn.GetServers(); + Assert.True(servers.Length > 0); + + foreach (IServer s in servers) + { + Assert.IsType(s.EndPoint); + Assert.Contains(HostnameTls, s.EndPoint.ToString()); + } + } + + [Fact] + public void GetEndPoints_WithDnsHostnameTopology_ReturnsDnsEndPoints() + { + SkipIfDnsTestsNotEnabled(); + + var config = new ConfigurationOptions(); + config.EndPoints.Add(fixture.DnsClusterServer!.Address.Host, fixture.DnsClusterServer.Address.Port); + config.ResponseTimeout = 10000; + + using var conn = ConnectionMultiplexer.Connect(config); + + EndPoint[] endpoints = conn.GetEndPoints(false); + Assert.True(endpoints.Length > 0); + + foreach (EndPoint ep in endpoints) + { + Assert.IsType(ep); + IServer found = conn.GetServer(ep); + Assert.Equal(ep, found.EndPoint); + } + } + #endregion #region Helpers @@ -128,6 +173,7 @@ public class DnsTestsFixture : IAsyncLifetime public StandaloneServer? StandaloneServer { get; private set; } public ClusterServer? TlsClusterServer { get; private set; } public StandaloneServer? TlsStandaloneServer { get; private set; } + public ClusterServer? DnsClusterServer { get; private set; } public ValueTask InitializeAsync() { @@ -136,8 +182,18 @@ public ValueTask InitializeAsync() { ClusterServer = new(useTls: false); StandaloneServer = new(useTls: false); - TlsClusterServer = new(useTls: true); - TlsStandaloneServer = new(useTls: true); + DnsClusterServer = new(host: HostnameTls); + + try + { + TlsClusterServer = new(useTls: true); + TlsStandaloneServer = new(useTls: true); + } + catch + { + // TLS servers may fail to start in some environments. + // Tests that require TLS will be skipped via null checks. + } } return ValueTask.CompletedTask; @@ -149,6 +205,7 @@ public ValueTask DisposeAsync() StandaloneServer?.Dispose(); TlsClusterServer?.Dispose(); TlsStandaloneServer?.Dispose(); + DnsClusterServer?.Dispose(); return ValueTask.CompletedTask; } diff --git a/tests/Valkey.Glide.TestUtils/Server.cs b/tests/Valkey.Glide.TestUtils/Server.cs index ec8494c9..9c0b04fa 100644 --- a/tests/Valkey.Glide.TestUtils/Server.cs +++ b/tests/Valkey.Glide.TestUtils/Server.cs @@ -244,7 +244,8 @@ public override async Task KillClientsAsync() /// public sealed class StandaloneServer( bool useTls = false, - int? replicaCount = null) : Server(useClusterMode: false, useTls: useTls, replicaCount: replicaCount) + int? replicaCount = null, + string? host = null) : Server(useClusterMode: false, useTls: useTls, replicaCount: replicaCount, host: host) { #region Public Methods From d1c9be64d5cfb5ae221760e2e961ee6c4e2cdd6d Mon Sep 17 00:00:00 2001 From: currantw Date: Thu, 30 Jul 2026 14:23:57 -0700 Subject: [PATCH 04/11] Minor GetServers cleanup Signed-off-by: currantw --- sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs b/sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs index aa8922ef..f4e11891 100644 --- a/sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs +++ b/sources/Valkey.Glide/Abstract/ConnectionMultiplexer.cs @@ -127,14 +127,18 @@ public IServer[] GetServers() // run INFO on all nodes, but disregard the node responses, we need node addresses only if (_db!.IsCluster) { - Dictionary info = _db.Command(Request.Info([]).ToMultiNodeValue(), Route.AllNodes).GetAwaiter().GetResult(); - List servers = new(info.Count); - foreach (string addr in info.Keys) + var servers = new List(); + + foreach (var addr in _db.Command(Request.Info([]).ToMultiNodeValue(), Route.AllNodes).GetAwaiter().GetResult().Keys) { if (!Format.TryParseEndPoint(addr, out EndPoint? ep)) + { throw new FormatException($"Could not parse endpoint address: '{addr}'"); + } + servers.Add(new ValkeyServer(_db, ep)); } + return [.. servers]; } else From 2bda1c1f2eebaff792e5c3f8ae6480f3032ceb26 Mon Sep 17 00:00:00 2001 From: currantw Date: Thu, 30 Jul 2026 14:30:56 -0700 Subject: [PATCH 05/11] Cleanup DnsTests Signed-off-by: currantw --- .../Valkey.Glide.IntegrationTests/DnsTests.cs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/tests/Valkey.Glide.IntegrationTests/DnsTests.cs b/tests/Valkey.Glide.IntegrationTests/DnsTests.cs index f80d4078..32bd16ce 100644 --- a/tests/Valkey.Glide.IntegrationTests/DnsTests.cs +++ b/tests/Valkey.Glide.IntegrationTests/DnsTests.cs @@ -66,45 +66,44 @@ public async Task Tls_WithHostnameNotInCertificate_Fails(bool useCluster) } [Fact] - public void GetServers_WithDnsHostnameTopology_ReturnsDnsEndPoints() + public void GetServers_WithDnsHostname_ReturnsDnsEndPoints() { SkipIfDnsTestsNotEnabled(); var config = new ConfigurationOptions(); - config.EndPoints.Add(fixture.DnsClusterServer!.Address.Host, fixture.DnsClusterServer.Address.Port); - config.ResponseTimeout = 10000; + var address = fixture.DnsClusterServer!.Address; + config.EndPoints.Add(address.Host, address.Port); using var conn = ConnectionMultiplexer.Connect(config); - IServer[] servers = conn.GetServers(); - Assert.True(servers.Length > 0); + var servers = conn.GetServers(); + Assert.NotEmpty(servers); - foreach (IServer s in servers) + foreach (var server in servers) { - Assert.IsType(s.EndPoint); - Assert.Contains(HostnameTls, s.EndPoint.ToString()); + _ = Assert.IsType(server.EndPoint); + Assert.Contains(HostnameTls, server.EndPoint.ToString()); } } [Fact] - public void GetEndPoints_WithDnsHostnameTopology_ReturnsDnsEndPoints() + public void GetEndPoints_WithDnsHostname_ReturnsDnsEndPoints() { SkipIfDnsTestsNotEnabled(); var config = new ConfigurationOptions(); - config.EndPoints.Add(fixture.DnsClusterServer!.Address.Host, fixture.DnsClusterServer.Address.Port); - config.ResponseTimeout = 10000; + var address = fixture.DnsClusterServer!.Address; + config.EndPoints.Add(address.Host, address.Port); using var conn = ConnectionMultiplexer.Connect(config); - EndPoint[] endpoints = conn.GetEndPoints(false); - Assert.True(endpoints.Length > 0); + var endpoints = conn.GetEndPoints(false); + Assert.NotEmpty(endpoints); - foreach (EndPoint ep in endpoints) + foreach (EndPoint endpoint in endpoints) { - Assert.IsType(ep); - IServer found = conn.GetServer(ep); - Assert.Equal(ep, found.EndPoint); + _ = Assert.IsType(endpoint); + Assert.Contains(HostnameTls, endpoint.ToString()); } } From 04c19e4a9ebbf0da17515545042f1537175e9056 Mon Sep 17 00:00:00 2001 From: currantw Date: Thu, 30 Jul 2026 14:33:04 -0700 Subject: [PATCH 06/11] style(test): replace Assert.True(x.Count > 0) with Assert.NotEmpty(x) Use idiomatic xUnit assertion across integration tests. Signed-off-by: currantw --- .../ClusterClientTests.cs | 4 ++-- .../ScriptingCommandTests.cs | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/Valkey.Glide.IntegrationTests/ClusterClientTests.cs b/tests/Valkey.Glide.IntegrationTests/ClusterClientTests.cs index 36ffa80c..d945e72e 100644 --- a/tests/Valkey.Glide.IntegrationTests/ClusterClientTests.cs +++ b/tests/Valkey.Glide.IntegrationTests/ClusterClientTests.cs @@ -360,7 +360,7 @@ public async Task TestClientId_WithRoute(GlideClusterClient client) // Test CLIENT ID with all nodes routing var allNodesResult = await client.ClientIdAsync(AllNodes); Assert.True(allNodesResult.HasMultiData); - Assert.True(allNodesResult.MultiValue.Count > 0); + Assert.NotEmpty(allNodesResult.MultiValue); foreach (var kvp in allNodesResult.MultiValue) { @@ -414,7 +414,7 @@ public async Task TestClientGetName_WithRoute(GlideClusterClient client) // Test CLIENT GETNAME with all nodes routing var allNodesResult = await client.ClientGetNameAsync(AllNodes); Assert.True(allNodesResult.HasMultiData); - Assert.True(allNodesResult.MultiValue.Count > 0); + Assert.NotEmpty(allNodesResult.MultiValue); foreach (var kvp in allNodesResult.MultiValue) { diff --git a/tests/Valkey.Glide.IntegrationTests/ScriptingCommandTests.cs b/tests/Valkey.Glide.IntegrationTests/ScriptingCommandTests.cs index 2ce477c9..c087ce73 100644 --- a/tests/Valkey.Glide.IntegrationTests/ScriptingCommandTests.cs +++ b/tests/Valkey.Glide.IntegrationTests/ScriptingCommandTests.cs @@ -1043,7 +1043,7 @@ public async Task FunctionStatsAsync_ReturnsStatistics(GlideClient client) Assert.NotNull(stats); Assert.NotNull(stats.Engines); - Assert.True(stats.Engines.Count > 0); + Assert.NotEmpty(stats.Engines); // Check LUA engine stats Assert.True(stats.Engines.ContainsKey("LUA")); @@ -1119,7 +1119,7 @@ public async Task FunctionDumpAsync_CreatesBackup(GlideClient client) byte[] backup = await client.FunctionDumpAsync(); Assert.NotNull(backup); - Assert.True(backup.Length > 0); + Assert.NotEmpty(backup); } [Theory(DisableDiscoveryEnumeration = true)] @@ -1246,7 +1246,7 @@ public async Task FCallAsync_WithAllPrimariesRouting_ExecutesOnAllPrimaries(Glid // Verify load succeeded (may be single or multi-value depending on cluster configuration) if (loadResult.HasMultiData) { - Assert.True(loadResult.MultiValue.Count > 0); + Assert.NotEmpty(loadResult.MultiValue); Assert.All(loadResult.MultiValue.Values, name => Assert.Equal(libName, name)); } else @@ -1260,7 +1260,7 @@ public async Task FCallAsync_WithAllPrimariesRouting_ExecutesOnAllPrimaries(Glid // Verify execution (may be single or multi-value depending on cluster configuration) if (result.HasMultiData) { - Assert.True(result.MultiValue.Count > 0); + Assert.NotEmpty(result.MultiValue); Assert.All(result.MultiValue.Values, r => Assert.Equal("Hello from primary", r.ToString())); } else @@ -1294,7 +1294,7 @@ public async Task FCallAsync_WithAllNodesRouting_ExecutesOnAllNodes(GlideCluster // Verify load succeeded (may be single or multi-value depending on cluster configuration) if (loadResult.HasMultiData) { - Assert.True(loadResult.MultiValue.Count > 0); + Assert.NotEmpty(loadResult.MultiValue); } else { @@ -1307,7 +1307,7 @@ public async Task FCallAsync_WithAllNodesRouting_ExecutesOnAllNodes(GlideCluster // Verify execution (may be single or multi-value depending on cluster configuration) if (result.HasMultiData) { - Assert.True(result.MultiValue.Count > 0); + Assert.NotEmpty(result.MultiValue); Assert.All(result.MultiValue.Values, r => Assert.Equal("Hello from node", r.ToString())); } else @@ -1425,7 +1425,7 @@ public async Task FunctionListAsync_WithRouting_ReturnsLibrariesFromSpecifiedNod // Verify list returned (may be single or multi-value depending on cluster configuration) if (result.HasMultiData) { - Assert.True(result.MultiValue.Count > 0); + Assert.NotEmpty(result.MultiValue); // Verify each node has the library foreach (var (node, libraries) in result.MultiValue) { @@ -1464,7 +1464,7 @@ public async Task FunctionStatsAsync_WithRouting_ReturnsPerNodeStats(GlideCluste // Verify stats returned (may be single or multi-value depending on cluster configuration) if (result.HasMultiData) { - Assert.True(result.MultiValue.Count > 0); + Assert.NotEmpty(result.MultiValue); // Verify each node has stats foreach (var (_, stats) in result.MultiValue) { @@ -1610,7 +1610,7 @@ public async Task ClusterValue_MultiNodeResults_HandlesCorrectly(GlideClusterCli { Assert.False(loadResult.HasSingleData); Assert.NotNull(loadResult.MultiValue); - Assert.True(loadResult.MultiValue.Count > 0); + Assert.NotEmpty(loadResult.MultiValue); // Verify each node address is a key in the dictionary foreach (var (nodeAddress, libraryName) in loadResult.MultiValue) From 0ef0dd86a1953af5af7c8cfe0918ba7abdb34153 Mon Sep 17 00:00:00 2001 From: currantw Date: Thu, 30 Jul 2026 14:57:54 -0700 Subject: [PATCH 07/11] chore(deps): bump valkey-glide submodule and fix FFI breaking change Bump valkey-glide submodule to cf3d56f08 which includes cluster-announce-hostname support in cluster_manager.py (#6667). Add missing `recovery_requests_queue_size` field to ConnectionRequest in ffi.rs to fix compilation against the updated glide-core. Signed-off-by: currantw --- rust/src/ffi.rs | 1 + valkey-glide | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/src/ffi.rs b/rust/src/ffi.rs index 1df9e85c..714fa3d1 100644 --- a/rust/src/ffi.rs +++ b/rust/src/ffi.rs @@ -375,6 +375,7 @@ pub(crate) unsafe fn create_connection_request( tcp_nodelay: false, periodic_checks: None, // TODO #485: Expose periodic_checks in ClusterClientConfiguration. inflight_requests_limit: None, // TODO #484: Expose inflight_requests_limit in ConnectionConfiguration. + recovery_requests_queue_size: None, address_resolver: None, client_circuit_breaker: None, }) diff --git a/valkey-glide b/valkey-glide index 6d9fae25..cf3d56f0 160000 --- a/valkey-glide +++ b/valkey-glide @@ -1 +1 @@ -Subproject commit 6d9fae25aef473cbfbdc253c0475d5499c959d1b +Subproject commit cf3d56f08075dec7f6bab38395a6d85050b493bb From 022ee99d69578c03191134c9c12827f75eda8060 Mon Sep 17 00:00:00 2001 From: currantw Date: Thu, 30 Jul 2026 15:00:47 -0700 Subject: [PATCH 08/11] fix(test): make DnsClusterServer startup non-fatal in DnsTestsFixture The DNS cluster server (using cluster-announce-hostname) may fail to start in CI environments where wait_for_all_topology_views times out because the CLUSTER SLOTS response reports IPv6 addresses instead of the announced hostname. Wrap in try/catch and skip tests when null. Signed-off-by: currantw --- tests/Valkey.Glide.IntegrationTests/DnsTests.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/Valkey.Glide.IntegrationTests/DnsTests.cs b/tests/Valkey.Glide.IntegrationTests/DnsTests.cs index 32bd16ce..c78494f0 100644 --- a/tests/Valkey.Glide.IntegrationTests/DnsTests.cs +++ b/tests/Valkey.Glide.IntegrationTests/DnsTests.cs @@ -69,6 +69,7 @@ public async Task Tls_WithHostnameNotInCertificate_Fails(bool useCluster) public void GetServers_WithDnsHostname_ReturnsDnsEndPoints() { SkipIfDnsTestsNotEnabled(); + Assert.SkipWhen(fixture.DnsClusterServer is null, "DNS cluster server not available."); var config = new ConfigurationOptions(); var address = fixture.DnsClusterServer!.Address; @@ -90,6 +91,7 @@ public void GetServers_WithDnsHostname_ReturnsDnsEndPoints() public void GetEndPoints_WithDnsHostname_ReturnsDnsEndPoints() { SkipIfDnsTestsNotEnabled(); + Assert.SkipWhen(fixture.DnsClusterServer is null, "DNS cluster server not available."); var config = new ConfigurationOptions(); var address = fixture.DnsClusterServer!.Address; @@ -181,7 +183,16 @@ public ValueTask InitializeAsync() { ClusterServer = new(useTls: false); StandaloneServer = new(useTls: false); - DnsClusterServer = new(host: HostnameTls); + + try + { + DnsClusterServer = new(host: HostnameTls); + } + catch + { + // DNS cluster may fail in environments where cluster-announce-hostname + // isn't properly supported (e.g. wait_for_all_topology_views timeout). + } try { @@ -191,7 +202,6 @@ public ValueTask InitializeAsync() catch { // TLS servers may fail to start in some environments. - // Tests that require TLS will be skipped via null checks. } } From deae3006ec34c79224b955d3f6f809ead77c3faf Mon Sep 17 00:00:00 2001 From: currantw Date: Thu, 30 Jul 2026 19:55:23 -0700 Subject: [PATCH 09/11] fix(test): address Copilot review comments on DnsTests - Add GetServer(endpoint) assertion to GetEndPoints test to verify returned DnsEndPoints are usable for server lookup. - Remove try/catch around DnsClusterServer and TLS server startup so failures are loud instead of silently skipped. - Remove unnecessary SkipWhen null guards. Signed-off-by: currantw --- .../Valkey.Glide.IntegrationTests/DnsTests.cs | 30 +++++-------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/tests/Valkey.Glide.IntegrationTests/DnsTests.cs b/tests/Valkey.Glide.IntegrationTests/DnsTests.cs index c78494f0..4c028095 100644 --- a/tests/Valkey.Glide.IntegrationTests/DnsTests.cs +++ b/tests/Valkey.Glide.IntegrationTests/DnsTests.cs @@ -69,7 +69,6 @@ public async Task Tls_WithHostnameNotInCertificate_Fails(bool useCluster) public void GetServers_WithDnsHostname_ReturnsDnsEndPoints() { SkipIfDnsTestsNotEnabled(); - Assert.SkipWhen(fixture.DnsClusterServer is null, "DNS cluster server not available."); var config = new ConfigurationOptions(); var address = fixture.DnsClusterServer!.Address; @@ -91,7 +90,6 @@ public void GetServers_WithDnsHostname_ReturnsDnsEndPoints() public void GetEndPoints_WithDnsHostname_ReturnsDnsEndPoints() { SkipIfDnsTestsNotEnabled(); - Assert.SkipWhen(fixture.DnsClusterServer is null, "DNS cluster server not available."); var config = new ConfigurationOptions(); var address = fixture.DnsClusterServer!.Address; @@ -102,10 +100,13 @@ public void GetEndPoints_WithDnsHostname_ReturnsDnsEndPoints() var endpoints = conn.GetEndPoints(false); Assert.NotEmpty(endpoints); - foreach (EndPoint endpoint in endpoints) + foreach (var endpoint in endpoints) { _ = Assert.IsType(endpoint); Assert.Contains(HostnameTls, endpoint.ToString()); + + var server = conn.GetServer(endpoint); + Assert.Equal(endpoint, server.EndPoint); } } @@ -183,26 +184,9 @@ public ValueTask InitializeAsync() { ClusterServer = new(useTls: false); StandaloneServer = new(useTls: false); - - try - { - DnsClusterServer = new(host: HostnameTls); - } - catch - { - // DNS cluster may fail in environments where cluster-announce-hostname - // isn't properly supported (e.g. wait_for_all_topology_views timeout). - } - - try - { - TlsClusterServer = new(useTls: true); - TlsStandaloneServer = new(useTls: true); - } - catch - { - // TLS servers may fail to start in some environments. - } + TlsClusterServer = new(useTls: true); + TlsStandaloneServer = new(useTls: true); + DnsClusterServer = new(host: HostnameTls); } return ValueTask.CompletedTask; From f1a2cd017b7fa7811016daf207249b25e383bdfa Mon Sep 17 00:00:00 2001 From: currantw Date: Thu, 30 Jul 2026 23:01:56 -0700 Subject: [PATCH 10/11] refactor(test): overhaul DNS tests with TLS coverage and CreateClientAsync(host) - Add optional `host` parameter to `CreateClientAsync` on Server, ClusterServer, and StandaloneServer to support connecting via a custom hostname. - Replace `BuildClient` helper in DnsTests with `GetServer` + `CreateClientAsync(host)`. - Pass `host: HostnameTls` to all 4 fixture servers so DNS hostname is used consistently. - Use `ClusterAndTlsMode` data member to test all cluster/TLS combinations for connection tests. - Use `TlsMode` data member for GetServers/GetEndPoints tests with `TrustIssuer` for TLS cert validation. - Add static constructor skip for class-level DNS test gating. Signed-off-by: currantw --- .../Valkey.Glide.IntegrationTests/DnsTests.cs | 182 ++++++++---------- tests/Valkey.Glide.TestUtils/Server.cs | 59 ++++-- 2 files changed, 124 insertions(+), 117 deletions(-) diff --git a/tests/Valkey.Glide.IntegrationTests/DnsTests.cs b/tests/Valkey.Glide.IntegrationTests/DnsTests.cs index 4c028095..c1e826df 100644 --- a/tests/Valkey.Glide.IntegrationTests/DnsTests.cs +++ b/tests/Valkey.Glide.IntegrationTests/DnsTests.cs @@ -4,7 +4,6 @@ using Valkey.Glide.TestUtils; -using static Valkey.Glide.ConnectionConfiguration; using static Valkey.Glide.Errors; using static Valkey.Glide.TestUtils.Client; using static Valkey.Glide.TestUtils.Constants; @@ -24,76 +23,104 @@ public class DnsTests(DnsTestsFixture fixture) : IClassFixture /// Environment variable for enabling DNS tests. /// See DEVELOPER.md for more details. /// - private const string DnsEnabledEnvVar = "VALKEY_GLIDE_DNS_TESTS_ENABLED"; + internal const string DnsEnabledEnvVar = "VALKEY_GLIDE_DNS_TESTS_ENABLED"; #endregion - #region Tests + #region Test Data - [Theory] - [MemberData(nameof(ClusterMode), MemberType = typeof(Data))] - public async Task ConnectWithValidHostname_Succeeds(bool useCluster) + public static TheoryData TlsMode => [true, false]; + + public static TheoryData ClusterAndTlsMode => new() { - SkipIfDnsTestsNotEnabled(); - await using var client = await BuildClient(useCluster, useTls: false, HostnameNoTls); - await AssertConnected(client); - } + { true, true }, + { true, false }, + { false, true }, + { false, false }, + }; - [Theory] - [MemberData(nameof(ClusterMode), MemberType = typeof(Data))] - public async Task ConnectWithInvalidHostname_Fails(bool useCluster) + #endregion + #region Constructor + + static DnsTests() { - SkipIfDnsTestsNotEnabled(); - _ = await Assert.ThrowsAsync(async () - => await BuildClient(useCluster, useTls: false, "NONEXISTENT.INVALID")); + Assert.SkipWhen( + string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DnsEnabledEnvVar)), + "DNS tests are disabled. See DEVELOPER.md for setup instructions."); } + #endregion + #region Tests + [Theory] - [MemberData(nameof(ClusterMode), MemberType = typeof(Data))] - public async Task Tls_WithHostnameInCertificate_Succeeds(bool useCluster) + [MemberData(nameof(ClusterAndTlsMode))] + public async Task Connect_WithValidHostname_Succeeds(bool useCluster, bool useTls) { - SkipIfDnsTestsNotEnabled(); - await using var client = await BuildClient(useCluster, useTls: true, HostnameTls); + var server = GetServer(useCluster, useTls); + var host = useTls ? HostnameTls : HostnameNoTls; + await using var client = await server.CreateClientAsync(host); + await AssertConnected(client); } + [Theory] + [MemberData(nameof(ClusterAndTlsMode))] + public async Task Connect_WithInvalidHostname_Fails(bool useCluster, bool useTls) + => _ = await Assert.ThrowsAsync(async () + => await GetServer(useCluster, useTls).CreateClientAsync("NONEXISTENT.INVALID")); + [Theory] [MemberData(nameof(ClusterMode), MemberType = typeof(Data))] - public async Task Tls_WithHostnameNotInCertificate_Fails(bool useCluster) - { - SkipIfDnsTestsNotEnabled(); - _ = await Assert.ThrowsAsync(async () - => await BuildClient(useCluster, useTls: true, HostnameNoTls)); - } + public async Task Connect_WithHostnameNotInCertificate_Fails(bool useCluster) + => _ = await Assert.ThrowsAsync(async () + => await GetServer(useCluster, useTls: true).CreateClientAsync(HostnameNoTls)); - [Fact] - public void GetServers_WithDnsHostname_ReturnsDnsEndPoints() + /// + /// Verifies that returns servers with + /// instances when the cluster topology reports DNS hostnames (via cluster-announce-hostname). + /// + /// #419 + [Theory] + [MemberData(nameof(TlsMode))] + public void GetServers_WithDnsHostname_ReturnsDnsEndPoints(bool useTls) { - SkipIfDnsTestsNotEnabled(); + var server = GetServer(useCluster: true, useTls); + var config = new ConfigurationOptions { Ssl = useTls }; + config.EndPoints.Add(server.Address.Host, server.Address.Port); - var config = new ConfigurationOptions(); - var address = fixture.DnsClusterServer!.Address; - config.EndPoints.Add(address.Host, address.Port); + if (useTls) + { + config.TrustIssuer(ServerManager.ServerCertificatePath); + } using var conn = ConnectionMultiplexer.Connect(config); var servers = conn.GetServers(); Assert.NotEmpty(servers); - foreach (var server in servers) + foreach (var s in servers) { - _ = Assert.IsType(server.EndPoint); - Assert.Contains(HostnameTls, server.EndPoint.ToString()); + _ = Assert.IsType(s.EndPoint); + Assert.Contains(HostnameTls, s.EndPoint.ToString()); } } - [Fact] - public void GetEndPoints_WithDnsHostname_ReturnsDnsEndPoints() + /// + /// Verifies that returns + /// instances when the cluster topology reports DNS hostnames (via cluster-announce-hostname). + /// + /// #419 + [Theory] + [MemberData(nameof(TlsMode))] + public void GetEndPoints_WithDnsHostname_ReturnsDnsEndPoints(bool useTls) { - SkipIfDnsTestsNotEnabled(); + var server = GetServer(useCluster: true, useTls); + var config = new ConfigurationOptions { Ssl = useTls }; + config.EndPoints.Add(server.Address.Host, server.Address.Port); - var config = new ConfigurationOptions(); - var address = fixture.DnsClusterServer!.Address; - config.EndPoints.Add(address.Host, address.Port); + if (useTls) + { + config.TrustIssuer(ServerManager.ServerCertificatePath); + } using var conn = ConnectionMultiplexer.Connect(config); @@ -105,63 +132,21 @@ public void GetEndPoints_WithDnsHostname_ReturnsDnsEndPoints() _ = Assert.IsType(endpoint); Assert.Contains(HostnameTls, endpoint.ToString()); - var server = conn.GetServer(endpoint); - Assert.Equal(endpoint, server.EndPoint); + var s = conn.GetServer(endpoint); + Assert.Equal(endpoint, s.EndPoint); } } #endregion #region Helpers - /// - /// Returns true if DNS tests are enabled. - /// - public static bool IsDnsTestsEnabled() - => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(DnsEnabledEnvVar)); - - /// - /// Skips the current test if DNS tests are not enabled. - /// - private static void SkipIfDnsTestsNotEnabled() - => Assert.SkipWhen( - !IsDnsTestsEnabled(), - $"DNS tests are disabled. See DEVELOPER.md for setup instructions."); - - /// - /// Builds and returns a client configured with the specified parameters. - /// - private async Task BuildClient(bool useCluster, bool useTls, string host) + private Server GetServer(bool useCluster, bool useTls) => (useCluster, useTls) switch { - if (useCluster) - { - var server = useTls ? fixture.TlsClusterServer! : fixture.ClusterServer!; - var builder = new ClusterClientConfigurationBuilder() - .WithAddress(host, server.Address.Port); - - if (useTls) - { - _ = builder.WithTls(); - _ = builder.WithTrustedCertificate(server.CertificateData!); - } - - return await GlideClusterClient.CreateClient(builder.Build()); - } - - else - { - var server = useTls ? fixture.TlsStandaloneServer! : fixture.StandaloneServer!; - var builder = new StandaloneClientConfigurationBuilder() - .WithAddress(host, server.Address.Port); - - if (useTls) - { - _ = builder.WithTls(); - _ = builder.WithTrustedCertificate(server.CertificateData!); - } - - return await GlideClient.CreateClient(builder.Build()); - } - } + (true, true) => fixture.TlsClusterServer!, + (true, false) => fixture.ClusterServer!, + (false, true) => fixture.TlsStandaloneServer!, + _ => fixture.StandaloneServer!, + }; #endregion } @@ -175,19 +160,13 @@ public class DnsTestsFixture : IAsyncLifetime public StandaloneServer? StandaloneServer { get; private set; } public ClusterServer? TlsClusterServer { get; private set; } public StandaloneServer? TlsStandaloneServer { get; private set; } - public ClusterServer? DnsClusterServer { get; private set; } public ValueTask InitializeAsync() { - // Only start the servers if DNS tests are enabled. - if (DnsTests.IsDnsTestsEnabled()) - { - ClusterServer = new(useTls: false); - StandaloneServer = new(useTls: false); - TlsClusterServer = new(useTls: true); - TlsStandaloneServer = new(useTls: true); - DnsClusterServer = new(host: HostnameTls); - } + ClusterServer = new(host: HostnameTls); + StandaloneServer = new(host: HostnameTls); + TlsClusterServer = new(useTls: true, host: HostnameTls); + TlsStandaloneServer = new(useTls: true, host: HostnameTls); return ValueTask.CompletedTask; } @@ -198,7 +177,6 @@ public ValueTask DisposeAsync() StandaloneServer?.Dispose(); TlsClusterServer?.Dispose(); TlsStandaloneServer?.Dispose(); - DnsClusterServer?.Dispose(); return ValueTask.CompletedTask; } diff --git a/tests/Valkey.Glide.TestUtils/Server.cs b/tests/Valkey.Glide.TestUtils/Server.cs index 9c0b04fa..200f5480 100644 --- a/tests/Valkey.Glide.TestUtils/Server.cs +++ b/tests/Valkey.Glide.TestUtils/Server.cs @@ -130,7 +130,8 @@ public void Dispose() /// /// Builds and returns a client for this server. /// - public abstract Task CreateClientAsync(); + /// Optional hostname. + public abstract Task CreateClientAsync(string? host = null); /// /// Updates the password for the server to the given value. @@ -202,18 +203,32 @@ public ClusterClientConfigurationBuilder CreateConfigBuilder() trustedCertificate: UseTls ? CertificateData : null, password: _password); - /// - public override async Task CreateClientAsync() - => await CreateClusterClientAsync(); + /// + public override async Task CreateClientAsync(string? host = null) + => await CreateClusterClientAsync(host); /// /// Builds and returns a cluster client for this server. /// - public async Task CreateClusterClientAsync() + /// Optional hostname. + public async Task CreateClusterClientAsync(string? host = null) { - var config = CreateConfigBuilder().Build(); - Task factory() => GlideClusterClient.CreateClient(config); - return await CreateClientAsync(factory); + var builder = new ClusterClientConfigurationBuilder() + .WithAddress(host ?? Address.Host, Address.Port); + + if (UseTls) + { + _ = builder.WithTls(); + _ = builder.WithTrustedCertificate(CertificateData!); + } + + if (_password != null) + { + _ = builder.WithAuthentication(password: _password); + } + + return await CreateClientAsync(() + => GlideClusterClient.CreateClient(builder.Build())); } public override async Task SetPasswordAsync(string password) @@ -259,18 +274,32 @@ public StandaloneClientConfigurationBuilder CreateConfigBuilder() trustedCertificate: UseTls ? CertificateData : null, password: _password); - /// - public override async Task CreateClientAsync() - => await CreateStandaloneClientAsync(); + /// + public override async Task CreateClientAsync(string? host = null) + => await CreateStandaloneClientAsync(host); /// /// Builds and returns a standalone client for this server. /// - public async Task CreateStandaloneClientAsync() + /// Optional hostname. + public async Task CreateStandaloneClientAsync(string? host = null) { - var config = CreateConfigBuilder().Build(); - Task factory() => GlideClient.CreateClient(config); - return await CreateClientAsync(factory); + var builder = new StandaloneClientConfigurationBuilder() + .WithAddress(host ?? Address.Host, Address.Port); + + if (UseTls) + { + _ = builder.WithTls(); + _ = builder.WithTrustedCertificate(CertificateData!); + } + + if (_password != null) + { + _ = builder.WithAuthentication(password: _password); + } + + return await CreateClientAsync(() + => GlideClient.CreateClient(builder.Build())); } public override async Task SetPasswordAsync(string password) From 678c82ba5ec25386c62ad60589561f6925dda43a Mon Sep 17 00:00:00 2001 From: currantw Date: Fri, 31 Jul 2026 11:36:21 -0700 Subject: [PATCH 11/11] Add explicit unpauses for flaky tests Signed-off-by: currantw --- .../ConnectionManagementCommandTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Valkey.Glide.IntegrationTests/ConnectionManagementCommandTests.cs b/tests/Valkey.Glide.IntegrationTests/ConnectionManagementCommandTests.cs index 232b7e1a..d6b0965c 100644 --- a/tests/Valkey.Glide.IntegrationTests/ConnectionManagementCommandTests.cs +++ b/tests/Valkey.Glide.IntegrationTests/ConnectionManagementCommandTests.cs @@ -168,6 +168,8 @@ public async Task TestClientPause_ReadsPausedUntilExpires(bool clusterMode) // Verify that read commands are blocked until the pause expires. _ = await client.GetAsync(key); Assert.True(sw.Elapsed >= pauseFor); + + await client.ClientUnpauseAsync(); } [Theory] @@ -187,6 +189,8 @@ public async Task TestClientPause_WritesPausedUntilExpires(bool clusterMode) // Verify that write commands are blocked until the pause expires. await client.SetAsync(key, "after"); Assert.True(sw.Elapsed >= pauseFor); + + await client.ClientUnpauseAsync(); } [Theory]