Skip to content

Commit b6ad7ec

Browse files
committed
Step 4: Add Integration Tests
Signed-off-by: currantw <[email protected]>
1 parent e4b5b8d commit b6ad7ec

File tree

3 files changed

+286
-26
lines changed

3 files changed

+286
-26
lines changed

rust/src/lib.rs

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,7 @@ pub unsafe extern "C-unwind" fn refresh_iam_token(
824824
pub unsafe extern "C-unwind" fn update_connection_password(
825825
client_ptr: *const c_void,
826826
callback_index: usize,
827-
password: *const c_char,
827+
password_ptr: *const c_char,
828828
immediate_auth: bool,
829829
) {
830830
// Build client and add panic guard.
@@ -840,32 +840,45 @@ pub unsafe extern "C-unwind" fn update_connection_password(
840840
callback_index,
841841
};
842842

843-
let password_opt = if password.is_null() {
843+
// Build password option.
844+
let password = if password_ptr.is_null() {
844845
None
845846
} else {
846-
Some(
847-
unsafe { CStr::from_ptr(password) }
848-
.to_str()
849-
.expect("Can not read password argument.")
850-
.to_owned(),
851-
)
847+
match unsafe { CStr::from_ptr(password_ptr).to_str() } {
848+
Ok(password_str) => {
849+
if password_str.is_empty() {
850+
None
851+
} else {
852+
Some(password_str.into())
853+
}
854+
}
855+
Err(_) => {
856+
unsafe {
857+
report_error(
858+
core.failure_callback,
859+
callback_index,
860+
"Invalid password argument".into(),
861+
RequestErrorType::Unspecified,
862+
);
863+
}
864+
panic_guard.panicked = false;
865+
return;
866+
}
867+
}
852868
};
853869

870+
// Run password update.
854871
client.runtime.spawn(async move {
855872
let mut async_panic_guard = PanicGuard {
856873
panicked: true,
857874
failure_callback: core.failure_callback,
858875
callback_index,
859876
};
860877

861-
let result = core
862-
.client
863-
.clone()
864-
.update_connection_password(password_opt, immediate_auth)
865-
.await;
878+
let result = core.client.clone().update_connection_password(password, immediate_auth).await;
866879
match result {
867-
Ok(_) => {
868-
let response = ResponseValue::from_value(redis::Value::Okay);
880+
Ok(value) => {
881+
let response = ResponseValue::from_value(value);
869882
let ptr = Box::into_raw(Box::new(response));
870883
unsafe { (core.success_callback)(callback_index, ptr) };
871884
}

sources/Valkey.Glide/BaseClient.cs

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,26 @@ public async Task RefreshIamTokenAsync()
5959
}
6060

6161
/// <summary>
62-
/// Update the current connection with a new password or remove the password.
62+
/// Update the current connection with a new password.
6363
/// </summary>
64-
/// <param name="password">The new password to update the connection with, or <c>null</c> to remove the password</param>
65-
/// <param name="immediateAuth">If <c>true</c>, authenticate immediately with the new password</param>
66-
/// <returns>A task that completes when the password update finishes</returns>
67-
/// <exception cref="RequestException">Thrown when <paramref name="immediateAuth"/> is <c>true</c> and <paramref name="password"/> is <c>null</c></exception>
68-
public async Task UpdateConnectionPasswordAsync(string? password, bool immediateAuth = false)
64+
/// <param name="password">The new password to update the connection with</param>
65+
/// <param name="immediateAuth">If <c>true</c>, re-authenticate immediately after updating password</param>
66+
/// <exception cref="ArgumentException">Thrown if <paramref name="password"/> is <c>null</c> or empty.</exception>
67+
/// <returns>A task that completes when the password is updated</returns>
68+
public async Task UpdateConnectionPasswordAsync(string password, bool immediateAuth = false)
6969
{
70+
if (password == null)
71+
{
72+
throw new ArgumentException("Password cannot be null", nameof(password));
73+
}
74+
75+
if (password.Length == 0)
76+
{
77+
throw new ArgumentException("Password cannot be empty", nameof(password));
78+
}
79+
7080
Message message = MessageContainer.GetMessageForCall();
71-
IntPtr passwordPtr = (password == null) ? IntPtr.Zero : Marshal.StringToHGlobalAnsi(password);
81+
IntPtr passwordPtr = Marshal.StringToHGlobalAnsi(password);
7282
try
7383
{
7484
UpdateConnectionPasswordFfi(ClientPointer, (ulong)message.Index, passwordPtr, immediateAuth);
@@ -84,10 +94,28 @@ public async Task UpdateConnectionPasswordAsync(string? password, bool immediate
8494
}
8595
finally
8696
{
87-
if (passwordPtr != IntPtr.Zero)
88-
{
89-
Marshal.FreeHGlobal(passwordPtr);
90-
}
97+
Marshal.FreeHGlobal(passwordPtr);
98+
}
99+
}
100+
101+
/// <summary>
102+
/// Clear the password from the current connection.
103+
/// </summary>
104+
/// <param name="immediateAuth">If <c>true</c>, re-authenticate immediately after clearing password</param>
105+
/// <returns>A task that completes when the password is cleared</returns>
106+
public async Task ClearConnectionPasswordAsync(bool immediateAuth = false)
107+
{
108+
Message message = MessageContainer.GetMessageForCall();
109+
110+
UpdateConnectionPasswordFfi(ClientPointer, (ulong)message.Index, IntPtr.Zero, immediateAuth);
111+
IntPtr response = await message;
112+
try
113+
{
114+
HandleResponse(response);
115+
}
116+
finally
117+
{
118+
FreeResponse(response);
91119
}
92120
}
93121
#endregion public methods
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0
2+
3+
using static Valkey.Glide.ConnectionConfiguration;
4+
using static Valkey.Glide.Errors;
5+
6+
namespace Valkey.Glide.IntegrationTests;
7+
8+
public class UpdateConnectionPasswordTests(TestConfiguration config)
9+
{
10+
public TestConfiguration Config { get; } = config;
11+
12+
private static readonly string Password = "PASSWORD";
13+
private static readonly string InvalidPassword = "INVALID";
14+
private static readonly GlideString[] KillClientCommandArgs = ["CLIENT", "KILL", "TYPE", "NORMAL"];
15+
16+
[Fact]
17+
public async Task UpdateConnectionPassword_Standalone_DelayAuth()
18+
{
19+
string serverName = $"test_{Guid.NewGuid():N}";
20+
21+
try
22+
{
23+
// Start server and build clients.
24+
var addresses = ServerManager.StartStandaloneServer(serverName);
25+
var config = new StandaloneClientConfigurationBuilder()
26+
.WithAddress(addresses[0].host, addresses[0].port).Build();
27+
28+
using var client = await GlideClient.CreateClient(config);
29+
using var adminClient = await GlideClient.CreateClient(config);
30+
31+
VerifyConnection(client);
32+
VerifyConnection(adminClient);
33+
34+
// Update client connection password.
35+
await client.UpdateConnectionPasswordAsync(Password, immediateAuth: false);
36+
37+
VerifyConnection(client); // No reconnect
38+
39+
// Update server password and kill all clients.
40+
await adminClient.ConfigSetAsync("requirepass", Password);
41+
await adminClient.CustomCommand(KillClientCommandArgs);
42+
Task.Delay(1000).Wait();
43+
44+
VerifyConnection(client); // Reconnect
45+
46+
// Clear client connection password.
47+
await client.ClearConnectionPasswordAsync(immediateAuth: false);
48+
49+
VerifyConnection(client); // No reconnect
50+
51+
// Clear server password and kill all clients.
52+
await adminClient.ConfigSetAsync("requirepass", "");
53+
await adminClient.CustomCommand(KillClientCommandArgs);
54+
Task.Delay(1000).Wait();
55+
56+
VerifyConnection(client); // Reconnect
57+
}
58+
finally
59+
{
60+
ServerManager.StopServer(serverName);
61+
}
62+
}
63+
64+
[Fact]
65+
public async Task UpdateConnectionPassword_Standalone_ImmediateAuth()
66+
{
67+
string serverName = $"test_{Guid.NewGuid():N}";
68+
69+
try
70+
{
71+
// Start server and build client.
72+
var addresses = ServerManager.StartStandaloneServer(serverName);
73+
var config = new StandaloneClientConfigurationBuilder()
74+
.WithAddress(addresses[0].host, addresses[0].port).Build();
75+
76+
using var client = await GlideClient.CreateClient(config);
77+
78+
VerifyConnection(client);
79+
80+
// Update server password.
81+
await client.ConfigSetAsync("requirepass", Password);
82+
Task.Delay(1000).Wait();
83+
84+
// Update client connection password.
85+
await client.UpdateConnectionPasswordAsync(Password, immediateAuth: true);
86+
87+
VerifyConnection(client);
88+
89+
// Clear server password.
90+
await client.ConfigSetAsync("requirepass", "");
91+
Task.Delay(1000).Wait();
92+
93+
// Clear client connection password.
94+
await client.ClearConnectionPasswordAsync(immediateAuth: false);
95+
96+
VerifyConnection(client);
97+
}
98+
finally
99+
{
100+
ServerManager.StopServer(serverName);
101+
}
102+
}
103+
104+
[Fact]
105+
public async Task UpdateConnectionPassword_Standalone_InvalidPassword()
106+
{
107+
using var client = TestConfiguration.DefaultStandaloneClient();
108+
await Assert.ThrowsAsync<ArgumentException>(() => client.UpdateConnectionPasswordAsync(null!, immediateAuth: true));
109+
await Assert.ThrowsAsync<ArgumentException>(() => client.UpdateConnectionPasswordAsync("", immediateAuth: true));
110+
await Assert.ThrowsAsync<RequestException>(() => client.UpdateConnectionPasswordAsync(InvalidPassword, immediateAuth: true));
111+
}
112+
113+
[Fact]
114+
public async Task UpdateConnectionPassword_Cluster_DelayAuth()
115+
{
116+
string serverName = $"test_{Guid.NewGuid():N}";
117+
118+
try
119+
{
120+
// Start cluster and build clients.
121+
var addresses = ServerManager.StartClusterServer(serverName);
122+
var config = new ClusterClientConfigurationBuilder()
123+
.WithAddress(addresses[0].host, addresses[0].port).Build();
124+
125+
using var client = await GlideClusterClient.CreateClient(config);
126+
using var adminClient = await GlideClusterClient.CreateClient(config);
127+
128+
VerifyConnection(client);
129+
VerifyConnection(adminClient);
130+
131+
// Update client connection password.
132+
await client.UpdateConnectionPasswordAsync(Password, immediateAuth: false);
133+
134+
VerifyConnection(client); // No reconnect
135+
136+
// Update server password and kill all clients.
137+
await adminClient.ConfigSetAsync("requirepass", Password);
138+
await adminClient.CustomCommand(KillClientCommandArgs);
139+
Task.Delay(1000).Wait();
140+
141+
VerifyConnection(client); // Reconnect
142+
143+
// Clear client connection password.
144+
await client.ClearConnectionPasswordAsync(immediateAuth: false);
145+
146+
VerifyConnection(client); // No reconnect
147+
148+
// Clear server password and kill all clients.
149+
await adminClient.ConfigSetAsync("requirepass", "");
150+
await adminClient.CustomCommand(KillClientCommandArgs);
151+
Task.Delay(1000).Wait();
152+
153+
VerifyConnection(client); // Reconnect
154+
}
155+
finally
156+
{
157+
ServerManager.StopServer(serverName);
158+
}
159+
}
160+
161+
[Fact]
162+
public async Task UpdateConnectionPassword_Cluster_ImmediateAuth()
163+
{
164+
string serverName = $"test_{Guid.NewGuid():N}";
165+
166+
try
167+
{
168+
// Start cluster and build client.
169+
var addresses = ServerManager.StartClusterServer(serverName);
170+
var config = new ClusterClientConfigurationBuilder()
171+
.WithAddress(addresses[0].host, addresses[0].port).Build();
172+
173+
using var client = await GlideClusterClient.CreateClient(config);
174+
175+
VerifyConnection(client);
176+
177+
// Update server password.
178+
await client.ConfigSetAsync("requirepass", Password, Route.AllNodes);
179+
Task.Delay(1000).Wait();
180+
181+
// Update client connection password.
182+
await client.UpdateConnectionPasswordAsync(Password, immediateAuth: true);
183+
184+
VerifyConnection(client);
185+
186+
// Clear server password.
187+
await client.ConfigSetAsync("requirepass", "", Route.AllNodes);
188+
Task.Delay(1000).Wait();
189+
190+
// Clear client connection password.
191+
await client.ClearConnectionPasswordAsync(immediateAuth: false);
192+
193+
VerifyConnection(client);
194+
}
195+
finally
196+
{
197+
ServerManager.StopServer(serverName);
198+
}
199+
}
200+
201+
[Fact]
202+
public async Task UpdateConnectionPassword_Cluster_InvalidPassword()
203+
{
204+
using var client = TestConfiguration.DefaultClusterClient();
205+
await Assert.ThrowsAsync<ArgumentException>(() => client.UpdateConnectionPasswordAsync(null!, immediateAuth: true));
206+
await Assert.ThrowsAsync<ArgumentException>(() => client.UpdateConnectionPasswordAsync("", immediateAuth: true));
207+
await Assert.ThrowsAsync<RequestException>(() => client.UpdateConnectionPasswordAsync(InvalidPassword, immediateAuth: true));
208+
}
209+
210+
private static async void VerifyConnection(GlideClient client)
211+
{
212+
Assert.True(await client.PingAsync() > TimeSpan.Zero);
213+
}
214+
215+
private static async void VerifyConnection(GlideClusterClient client)
216+
{
217+
Assert.True(await client.PingAsync() > TimeSpan.Zero);
218+
}
219+
}

0 commit comments

Comments
 (0)