This is an automated email from the ASF dual-hosted git repository.

maciej pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new 29a27cc00 feat(java): add missing methods to async TCP UsersClient 
(#2837)
29a27cc00 is described below

commit 29a27cc0081b9b1310b3dda157df7513e2909e30
Author: Jonathon Henderson <[email protected]>
AuthorDate: Sun Mar 1 11:23:21 2026 +0000

    feat(java): add missing methods to async TCP UsersClient (#2837)
    
    Closes #2826
---
 .../org/apache/iggy/client/async/UsersClient.java  | 115 ++++++++++
 .../iggy/client/async/tcp/AsyncTcpConnection.java  |  56 +++++
 .../iggy/client/async/tcp/UsersTcpClient.java      |  95 +++++++-
 .../iggy/client/async/tcp/UsersTcpClientTest.java  | 239 +++++++++++++++++++++
 4 files changed, 502 insertions(+), 3 deletions(-)

diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/UsersClient.java
 
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/UsersClient.java
index 796e21ed6..4ccbce11e 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/UsersClient.java
+++ 
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/UsersClient.java
@@ -19,8 +19,15 @@
 
 package org.apache.iggy.client.async;
 
+import org.apache.iggy.identifier.UserId;
 import org.apache.iggy.user.IdentityInfo;
+import org.apache.iggy.user.Permissions;
+import org.apache.iggy.user.UserInfo;
+import org.apache.iggy.user.UserInfoDetails;
+import org.apache.iggy.user.UserStatus;
 
+import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -53,6 +60,114 @@ import java.util.concurrent.CompletableFuture;
  */
 public interface UsersClient {
 
+    /**
+     * Get the details of a user by the ID provided.
+     *
+     * @see #getUser(UserId)
+     */
+    default CompletableFuture<Optional<UserInfoDetails>> getUser(Long userId) {
+        return getUser(UserId.of(userId));
+    }
+
+    /**
+     * Get the details of a user by the ID provided.
+     *
+     * @param userId the ID of the user to retrieve information about.
+     * @return a {@link CompletableFuture} that completes with the user's 
{@link UserInfoDetails} on success
+     */
+    CompletableFuture<Optional<UserInfoDetails>> getUser(UserId userId);
+
+    /**
+     * Get a list of the users currently registered.
+     *
+     * @return A {@link CompletableFuture} that completes with a list of 
{@link UserInfo} about each of the registered users.
+     */
+    CompletableFuture<List<UserInfo>> getUsers();
+
+    /**
+     * Create a user with the details provided.
+     *
+     * @param username The username of the new user.
+     * @param password The password of the new user.
+     * @param status The status of the new user.
+     * @param permissions An optional set of permissions for the new user.
+     * @return A {@link CompletableFuture} that completes with a {@link 
UserInfoDetails} that gives information about the newly created user.
+     */
+    CompletableFuture<UserInfoDetails> createUser(
+            String username, String password, UserStatus status, 
Optional<Permissions> permissions);
+
+    /**
+     * Delete the user with the given ID.
+     *
+     * @see #deleteUser(UserId)
+     */
+    default CompletableFuture<Void> deleteUser(Long userId) {
+        return deleteUser(UserId.of(userId));
+    }
+
+    /**
+     * Delete the user with the given ID.
+     *
+     * @param userId The ID of the user to delete.
+     * @return A {@link CompletableFuture} that completes but yields no value.
+     */
+    CompletableFuture<Void> deleteUser(UserId userId);
+
+    /**
+     * Update the user identified by the given userId, setting (if provided) 
their username and or status.
+     *
+     * @see #updateUser(UserId, Optional, Optional)
+     */
+    default CompletableFuture<Void> updateUser(Long userId, Optional<String> 
username, Optional<UserStatus> status) {
+        return updateUser(UserId.of(userId), username, status);
+    }
+
+    /**
+     * Update the user identified by the given userId, setting (if provided) 
their username and or status.
+     * @param userId The ID of the user to update.
+     * @param username The new username of the user, or an empty optional if 
no update is required.
+     * @param status The new status of the user, or an empty optional if no 
update is required.
+     * @return A {@link CompletableFuture} that completes but yields no value.
+     */
+    CompletableFuture<Void> updateUser(UserId userId, Optional<String> 
username, Optional<UserStatus> status);
+
+    /**
+     * Update the permissions of the user identified by the provided userId.
+     *
+     * @see #updatePermissions(UserId, Optional)
+     */
+    default CompletableFuture<Void> updatePermissions(Long userId, 
Optional<Permissions> permissions) {
+        return updatePermissions(UserId.of(userId), permissions);
+    }
+
+    /**
+     * Update the permissions of the user identified by the provided userId.
+     *
+     * @param userId The ID of the user of which to update permissions
+     * @param permissions The new permissions of the user
+     * @return A {@link CompletableFuture} that completes but yields no value.
+     */
+    CompletableFuture<Void> updatePermissions(UserId userId, 
Optional<Permissions> permissions);
+
+    /**
+     * Change the password of the user identifier by the given userId.
+     *
+     * @see #changePassword(UserId, String, String)
+     */
+    default CompletableFuture<Void> changePassword(Long userId, String 
currentPassword, String newPassword) {
+        return changePassword(UserId.of(userId), currentPassword, newPassword);
+    }
+
+    /**
+     * Change the password of the user identifier by the given userId.
+     *
+     * @param userId The ID of the user whose password should be changed.
+     * @param currentPassword The current password of the user
+     * @param newPassword The new password of the user
+     * @return A {@link CompletableFuture} that completes but yields no value.
+     */
+    CompletableFuture<Void> changePassword(UserId userId, String 
currentPassword, String newPassword);
+
     /**
      * Logs in to the Iggy server with the specified credentials.
      *
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/tcp/AsyncTcpConnection.java
 
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/tcp/AsyncTcpConnection.java
index d316fcf1c..a49ebc7f8 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/tcp/AsyncTcpConnection.java
+++ 
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/tcp/AsyncTcpConnection.java
@@ -34,18 +34,23 @@ import io.netty.channel.socket.SocketChannel;
 import io.netty.channel.socket.nio.NioSocketChannel;
 import io.netty.handler.ssl.SslContext;
 import io.netty.handler.ssl.SslContextBuilder;
+import org.apache.iggy.exception.IggyEmptyResponseException;
 import org.apache.iggy.exception.IggyNotConnectedException;
 import org.apache.iggy.exception.IggyServerException;
 import org.apache.iggy.exception.IggyTlsException;
+import org.apache.iggy.serde.CommandCode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.net.ssl.SSLException;
 import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Function;
 
 /**
  * Async TCP connection using Netty for non-blocking I/O.
@@ -136,6 +141,57 @@ public class AsyncTcpConnection {
         return future;
     }
 
+    public <T> CompletableFuture<T> exchangeForEntity(
+            CommandCode commandCode, ByteBuf payload, Function<ByteBuf, T> 
func) {
+        return send(commandCode, payload).thenApply(response -> {
+            try {
+                if (!response.isReadable()) {
+                    throw new 
IggyEmptyResponseException(commandCode.toString());
+                }
+                return func.apply(response);
+            } finally {
+                response.release();
+            }
+        });
+    }
+
+    public <T> CompletableFuture<List<T>> exchangeForList(
+            CommandCode commandCode, ByteBuf payload, Function<ByteBuf, T> 
func) {
+        return send(commandCode, payload).thenApply(response -> {
+            try {
+                var result = new ArrayList<T>();
+                while (response.isReadable()) {
+                    result.add(func.apply(response));
+                }
+                return result;
+            } finally {
+                response.release();
+            }
+        });
+    }
+
+    public <T> CompletableFuture<Optional<T>> exchangeForOptional(
+            CommandCode commandCode, ByteBuf payload, Function<ByteBuf, T> 
func) {
+        return send(commandCode, payload).thenApply(response -> {
+            try {
+                if (response.isReadable()) {
+                    return Optional.of(func.apply(response));
+                }
+                return Optional.empty();
+            } finally {
+                response.release();
+            }
+        });
+    }
+
+    public CompletableFuture<Void> sendAndRelease(CommandCode commandCode, 
ByteBuf payload) {
+        return send(commandCode, payload).thenAccept(response -> 
response.release());
+    }
+
+    public CompletableFuture<ByteBuf> send(CommandCode commandCode, ByteBuf 
payload) {
+        return send(commandCode.getValue(), payload);
+    }
+
     /**
      * Sends a command asynchronously and returns the response.
      * Uses Netty's EventLoop to ensure thread-safe sequential request 
processing with FIFO response matching.
diff --git 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/tcp/UsersTcpClient.java
 
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/tcp/UsersTcpClient.java
index 7960302ec..632f45d6d 100644
--- 
a/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/tcp/UsersTcpClient.java
+++ 
b/foreign/java/java-sdk/src/main/java/org/apache/iggy/client/async/tcp/UsersTcpClient.java
@@ -22,15 +22,23 @@ package org.apache.iggy.client.async.tcp;
 import io.netty.buffer.Unpooled;
 import org.apache.iggy.IggyVersion;
 import org.apache.iggy.client.async.UsersClient;
-import org.apache.iggy.serde.BytesSerializer;
+import org.apache.iggy.identifier.UserId;
+import org.apache.iggy.serde.BytesDeserializer;
 import org.apache.iggy.serde.CommandCode;
 import org.apache.iggy.user.IdentityInfo;
+import org.apache.iggy.user.Permissions;
+import org.apache.iggy.user.UserInfo;
+import org.apache.iggy.user.UserInfoDetails;
+import org.apache.iggy.user.UserStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.List;
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 
+import static org.apache.iggy.serde.BytesSerializer.toBytes;
+
 /**
  * Async TCP implementation of users client.
  */
@@ -43,14 +51,95 @@ public class UsersTcpClient implements UsersClient {
         this.connection = connection;
     }
 
+    @Override
+    public CompletableFuture<Optional<UserInfoDetails>> getUser(UserId userId) 
{
+        var payload = toBytes(userId);
+        return connection.exchangeForOptional(CommandCode.User.GET, payload, 
BytesDeserializer::readUserInfoDetails);
+    }
+
+    @Override
+    public CompletableFuture<List<UserInfo>> getUsers() {
+        var payload = Unpooled.EMPTY_BUFFER;
+        return connection.exchangeForList(CommandCode.User.GET_ALL, payload, 
BytesDeserializer::readUserInfo);
+    }
+
+    @Override
+    public CompletableFuture<UserInfoDetails> createUser(
+            String username, String password, UserStatus status, 
Optional<Permissions> permissions) {
+        var payload = Unpooled.buffer();
+        payload.writeBytes(toBytes(username));
+        payload.writeBytes(toBytes(password));
+        payload.writeByte(status.asCode());
+        permissions.ifPresentOrElse(
+                perms -> {
+                    payload.writeByte(1);
+                    var permissionBytes = toBytes(perms);
+                    payload.writeIntLE(permissionBytes.readableBytes());
+                    payload.writeBytes(permissionBytes);
+                },
+                () -> payload.writeByte(0));
+
+        return connection.exchangeForEntity(CommandCode.User.CREATE, payload, 
BytesDeserializer::readUserInfoDetails);
+    }
+
+    @Override
+    public CompletableFuture<Void> deleteUser(UserId userId) {
+        var payload = toBytes(userId);
+        return connection.sendAndRelease(CommandCode.User.DELETE, payload);
+    }
+
+    @Override
+    public CompletableFuture<Void> updateUser(UserId userId, Optional<String> 
username, Optional<UserStatus> status) {
+        var payload = toBytes(userId);
+        username.ifPresentOrElse(
+                un -> {
+                    payload.writeByte(1);
+                    payload.writeBytes(toBytes(un));
+                },
+                () -> payload.writeByte(0));
+        status.ifPresentOrElse(
+                s -> {
+                    payload.writeByte(1);
+                    payload.writeByte(s.asCode());
+                },
+                () -> payload.writeByte(0));
+
+        return connection.sendAndRelease(CommandCode.User.UPDATE, payload);
+    }
+
+    @Override
+    public CompletableFuture<Void> updatePermissions(UserId userId, 
Optional<Permissions> permissions) {
+        var payload = toBytes(userId);
+
+        permissions.ifPresentOrElse(
+                perms -> {
+                    payload.writeByte(1);
+                    var permissionBytes = toBytes(perms);
+                    payload.writeIntLE(permissionBytes.readableBytes());
+                    payload.writeBytes(permissionBytes);
+                },
+                () -> payload.writeByte(0));
+
+        return connection.sendAndRelease(CommandCode.User.UPDATE_PERMISSIONS, 
payload);
+    }
+
+    @Override
+    public CompletableFuture<Void> changePassword(UserId userId, String 
currentPassword, String newPassword) {
+        var payload = toBytes(userId);
+        payload.writeBytes(toBytes(currentPassword));
+        payload.writeBytes(toBytes(newPassword));
+
+        return connection.sendAndRelease(CommandCode.User.CHANGE_PASSWORD, 
payload);
+    }
+
     @Override
     public CompletableFuture<IdentityInfo> login(String username, String 
password) {
         String version = IggyVersion.getInstance().getUserAgent();
         String context = IggyVersion.getInstance().toString();
 
         var payload = Unpooled.buffer();
-        var usernameBytes = BytesSerializer.toBytes(username);
-        var passwordBytes = BytesSerializer.toBytes(password);
+        var usernameBytes = toBytes(username);
+        var passwordBytes = toBytes(password);
 
         payload.writeBytes(usernameBytes);
         payload.writeBytes(passwordBytes);
diff --git 
a/foreign/java/java-sdk/src/test/java/org/apache/iggy/client/async/tcp/UsersTcpClientTest.java
 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/client/async/tcp/UsersTcpClientTest.java
new file mode 100644
index 000000000..19194b9cb
--- /dev/null
+++ 
b/foreign/java/java-sdk/src/test/java/org/apache/iggy/client/async/tcp/UsersTcpClientTest.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iggy.client.async.tcp;
+
+import org.apache.iggy.client.BaseIntegrationTest;
+import org.apache.iggy.identifier.UserId;
+import org.apache.iggy.user.GlobalPermissions;
+import org.apache.iggy.user.IdentityInfo;
+import org.apache.iggy.user.Permissions;
+import org.apache.iggy.user.UserInfo;
+import org.apache.iggy.user.UserStatus;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class UsersTcpClientTest extends BaseIntegrationTest {
+
+    private static final Logger log = 
LoggerFactory.getLogger(UsersTcpClientTest.class);
+    private static final String USERNAME = "iggy";
+    private static final String PASSWORD = "iggy";
+
+    private static AsyncIggyTcpClient client;
+    private static IdentityInfo loggedInUser;
+
+    @BeforeAll
+    public static void setup() throws Exception {
+        log.info("Setting up async client for integration tests");
+        client = new AsyncIggyTcpClient(serverHost(), serverTcpPort());
+
+        // Connect and login
+        loggedInUser = client.connect()
+                .thenCompose(v -> {
+                    log.info("Connected to Iggy server");
+                    return client.users().login(USERNAME, PASSWORD);
+                })
+                .get(5, TimeUnit.SECONDS);
+
+        log.info("Successfully logged in as: {}", USERNAME);
+    }
+
+    @Test
+    void shouldLogIn() {
+        assertThat(loggedInUser).isNotNull();
+        assertThat(loggedInUser.userId()).isEqualTo(0L);
+    }
+
+    @Test
+    void shouldGetUserWhenUserExists() throws Exception {
+        var userDetails = client.users().getUser(0L).get(5, TimeUnit.SECONDS);
+
+        assertThat(userDetails).isPresent();
+        assertThat(userDetails.get().id()).isEqualTo(0L);
+        assertThat(userDetails.get().username()).isEqualTo(USERNAME);
+    }
+
+    @Test
+    void shouldGetEmptyOptionalWhenUserDoesNotExist() throws Exception {
+        var userDetails = client.users().getUser(123456L).get(5, 
TimeUnit.SECONDS);
+
+        assertThat(userDetails).isNotPresent();
+    }
+
+    @Test
+    void shouldGetUsers() throws Exception {
+        var users = client.users().getUsers().get(5, TimeUnit.SECONDS);
+
+        assertThat(users).isNotEmpty();
+        assertThat(users).hasSize(1);
+        assertThat(users.get(0).username()).isEqualTo(USERNAME);
+    }
+
+    @Test
+    void shouldCreateAndDeleteUsers() throws Exception {
+        var users = client.users().getUsers().get(5, TimeUnit.SECONDS);
+        assertThat(users).hasSize(1);
+
+        var globalPermissions =
+                new GlobalPermissions(true, false, false, false, false, false, 
false, false, false, false);
+        var permissions = Optional.of(new Permissions(globalPermissions, 
Map.of()));
+
+        var foo = client.users()
+                .createUser("foo", "foo", UserStatus.Active, permissions)
+                .get(5, TimeUnit.SECONDS);
+        assertThat(foo).isNotNull();
+        assertThat(foo.permissions()).isPresent();
+        
assertThat(foo.permissions().get().global()).isEqualTo(globalPermissions);
+
+        var bar = client.users()
+                .createUser("bar", "bar", UserStatus.Active, permissions)
+                .get(5, TimeUnit.SECONDS);
+        assertThat(bar).isNotNull();
+        assertThat(bar.permissions()).isPresent();
+        
assertThat(bar.permissions().get().global()).isEqualTo(globalPermissions);
+
+        users = client.users().getUsers().get(5, TimeUnit.SECONDS);
+
+        assertThat(users).hasSize(3);
+        
assertThat(users).map(UserInfo::username).containsExactlyInAnyOrder(USERNAME, 
"foo", "bar");
+
+        client.users().deleteUser(foo.id()).get(5, TimeUnit.SECONDS);
+        users = client.users().getUsers().get(5, TimeUnit.SECONDS);
+        assertThat(users).hasSize(2);
+
+        client.users().deleteUser(UserId.of(bar.id())).get(5, 
TimeUnit.SECONDS);
+        users = client.users().getUsers().get(5, TimeUnit.SECONDS);
+        assertThat(users).hasSize(1);
+    }
+
+    @Test
+    void shouldUpdateUser() throws Exception {
+        var created = client.users()
+                .createUser("test", "test", UserStatus.Active, 
Optional.empty())
+                .get(5, TimeUnit.SECONDS);
+
+        client.users()
+                .updateUser(created.id(), Optional.of("foo"), 
Optional.of(UserStatus.Inactive))
+                .get(5, TimeUnit.SECONDS);
+
+        var user = client.users().getUser(created.id()).get(5, 
TimeUnit.SECONDS);
+        assertThat(user).isPresent();
+        assertThat(user.get().username()).isEqualTo("foo");
+        assertThat(user.get().status()).isEqualTo(UserStatus.Inactive);
+
+        client.users()
+                .updateUser(created.id(), Optional.empty(), 
Optional.of(UserStatus.Active))
+                .get(5, TimeUnit.SECONDS);
+
+        user = client.users().getUser(created.id()).get(5, TimeUnit.SECONDS);
+        assertThat(user).isPresent();
+        assertThat(user.get().username()).isEqualTo("foo");
+        assertThat(user.get().status()).isEqualTo(UserStatus.Active);
+
+        client.users()
+                .updateUser(UserId.of(created.id()), Optional.of("test"), 
Optional.empty())
+                .get(5, TimeUnit.SECONDS);
+
+        user = client.users().getUser(created.id()).get(5, TimeUnit.SECONDS);
+        assertThat(user).isPresent();
+        assertThat(user.get().username()).isEqualTo("test");
+        assertThat(user.get().status()).isEqualTo(UserStatus.Active);
+
+        client.users().deleteUser(created.id()).get(5, TimeUnit.SECONDS);
+
+        var users = client.users().getUsers().get(5, TimeUnit.SECONDS);
+        assertThat(users).hasSize(1);
+    }
+
+    @Test
+    void shouldUpdatePermissions() throws Exception {
+        var created = client.users()
+                .createUser("test", "test", UserStatus.Active, 
Optional.empty())
+                .get(5, TimeUnit.SECONDS);
+
+        var allPermissions = new Permissions(
+                new GlobalPermissions(true, true, true, true, true, true, 
true, true, true, true), Map.of());
+        var noPermissions = new Permissions(
+                new GlobalPermissions(false, false, false, false, false, 
false, false, false, false, false), Map.of());
+
+        client.users()
+                .updatePermissions(created.id(), Optional.of(allPermissions))
+                .get(5, TimeUnit.SECONDS);
+
+        var user = client.users().getUser(created.id()).get(5, 
TimeUnit.SECONDS);
+        assertThat(user).isPresent();
+        assertThat(user.get().permissions()).isPresent();
+        assertThat(user.get().permissions().get()).isEqualTo(allPermissions);
+
+        client.users()
+                .updatePermissions(UserId.of(created.id()), 
Optional.of(noPermissions))
+                .get(5, TimeUnit.SECONDS);
+
+        user = client.users().getUser(created.id()).get(5, TimeUnit.SECONDS);
+        assertThat(user).isPresent();
+        assertThat(user.get().permissions()).isPresent();
+        assertThat(user.get().permissions().get()).isEqualTo(noPermissions);
+
+        client.users().deleteUser(created.id()).get(5, TimeUnit.SECONDS);
+
+        var users = client.users().getUsers().get(5, TimeUnit.SECONDS);
+        assertThat(users).hasSize(1);
+    }
+
+    @Test
+    void shouldChangePassword() throws Exception {
+        var newUser = client.users()
+                .createUser("test", "test", UserStatus.Active, 
Optional.empty())
+                .get(5, TimeUnit.SECONDS);
+        client.users().logout().get(5, TimeUnit.SECONDS);
+
+        var identity = client.users().login("test", "test").get(5, 
TimeUnit.SECONDS);
+        assertThat(identity).isNotNull();
+        assertThat(identity.userId()).isEqualTo(newUser.id());
+
+        client.users().changePassword(identity.userId(), "test", 
"foobar").get(5, TimeUnit.SECONDS);
+        client.users().logout().get(5, TimeUnit.SECONDS);
+        identity = client.users().login("test", "foobar").get(5, 
TimeUnit.SECONDS);
+        assertThat(identity).isNotNull();
+        assertThat(identity.userId()).isEqualTo(newUser.id());
+
+        client.users()
+                .changePassword(UserId.of(identity.userId()), "foobar", 
"barfoo")
+                .get(5, TimeUnit.SECONDS);
+        client.users().logout().get(5, TimeUnit.SECONDS);
+        identity = client.users().login("test", "barfoo").get(5, 
TimeUnit.SECONDS);
+        assertThat(identity).isNotNull();
+        assertThat(identity.userId()).isEqualTo(newUser.id());
+
+        client.users().logout().get(5, TimeUnit.SECONDS);
+        client.users().login(USERNAME, PASSWORD).get(5, TimeUnit.SECONDS);
+        client.users().deleteUser(newUser.id()).get(5, TimeUnit.SECONDS);
+
+        var users = client.users().getUsers().get(5, TimeUnit.SECONDS);
+        assertThat(users).hasSize(1);
+    }
+}

Reply via email to