diff --git a/src/main/java/com/vonage/client/users/BaseUser.java b/src/main/java/com/vonage/client/users/BaseUser.java index 5f38a1772..73c457ac2 100644 --- a/src/main/java/com/vonage/client/users/BaseUser.java +++ b/src/main/java/com/vonage/client/users/BaseUser.java @@ -31,6 +31,10 @@ public class BaseUser implements Jsonable { BaseUser() { } + void setId(String id) { + this.id = id; + } + /** * Unique user ID. * diff --git a/src/main/java/com/vonage/client/users/ListUsersRequest.java b/src/main/java/com/vonage/client/users/ListUsersRequest.java index 8d3a072e4..4987a4cd8 100644 --- a/src/main/java/com/vonage/client/users/ListUsersRequest.java +++ b/src/main/java/com/vonage/client/users/ListUsersRequest.java @@ -15,6 +15,7 @@ */ package com.vonage.client.users; +import com.fasterxml.jackson.annotation.JsonValue; import com.vonage.client.QueryParamsRequest; import com.vonage.client.common.HalLinks; import java.net.URI; @@ -25,12 +26,12 @@ * Query parameters for {@link UsersClient#listUsers(ListUsersRequest)}. */ public final class ListUsersRequest implements QueryParamsRequest { - private final int pageSize; + private final Integer pageSize; private final SortOrder order; private final String name, cursor; private ListUsersRequest(Builder builder) { - if ((pageSize = builder.pageSize) < 1 || pageSize > 100) { + if ((pageSize = builder.pageSize) != null && (pageSize < 1 || pageSize > 100)) { throw new IllegalArgumentException("Page size must be between 1 and 100."); } order = builder.order; @@ -47,7 +48,9 @@ static String parseCursor(URI cursor) { @Override public Map makeParams() { LinkedHashMap params = new LinkedHashMap<>(4); - params.put("page_size", String.valueOf(pageSize)); + if (pageSize != null) { + params.put("page_size", pageSize.toString()); + } if (order != null) { params.put("order", order.toString()); } @@ -60,6 +63,42 @@ public Map makeParams() { return params; } + /** + * + * + * @return + */ + public Integer getPageSize() { + return pageSize; + } + + /** + * + * + * @return + */ + public SortOrder getOrder() { + return order; + } + + /** + * + * + * @return + */ + public String getName() { + return name; + } + + /** + * + * + * @return + */ + public String getCursor() { + return cursor; + } + /** * Entry point for constructing an instance of this class. * @@ -70,7 +109,7 @@ public static Builder builder() { } public static class Builder { - private int pageSize = 10; + private Integer pageSize; private SortOrder order; private String name; private URI cursor; @@ -141,4 +180,25 @@ public ListUsersRequest build() { return new ListUsersRequest(this); } } + + /** + * Represents the sort order for events. + */ + public enum SortOrder { + /** + * Ascending + */ + ASC, + + /** + * Descending + */ + DESC; + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(); + } + } } diff --git a/src/main/java/com/vonage/client/users/User.java b/src/main/java/com/vonage/client/users/User.java index d0b69ee25..502943a74 100644 --- a/src/main/java/com/vonage/client/users/User.java +++ b/src/main/java/com/vonage/client/users/User.java @@ -90,6 +90,13 @@ public Channels getChannels() { return channels; } + + public static User fromJson(String json) { + User user = new User(); + user.updateFromJson(json); + return user; + } + /** * Entry point for creating an instance of this class. * diff --git a/src/main/java/com/vonage/client/users/UsersClient.java b/src/main/java/com/vonage/client/users/UsersClient.java index d37977d8c..a1e1216a1 100644 --- a/src/main/java/com/vonage/client/users/UsersClient.java +++ b/src/main/java/com/vonage/client/users/UsersClient.java @@ -93,20 +93,22 @@ public User createUser(User user) throws UsersResponseException { /** * Update an existing user. * - * @param user The application properties for the user to be updated with. + * @param userId Unique ID of the user to update. + * @param user The properties for the user to be updated with. * * @return The user which has been updated. * * @throws UsersResponseException If there was an error processing the request. */ - public User updateUser(User user) throws UsersResponseException { - return updateUser.execute(validateUser(user)); + public User updateUser(String userId, User user) throws UsersResponseException { + validateUser(user).setId(validateUserId(userId)); + return updateUser.execute(user); } /** * Retrieve a user. * - * @param userId The unique ID of the user to retrieve. + * @param userId Unique ID of the user to retrieve. * * @return The corresponding user. * @@ -119,7 +121,7 @@ public User getUser(String userId) throws UsersResponseException { /** * Delete a user. * - * @param userId Unique ID of the user to delete as a string. + * @param userId Unique ID of the user to delete. * * @throws UsersResponseException If there was an error processing the request. */ diff --git a/src/test/java/com/vonage/client/application/ApplicationClientTest.java b/src/test/java/com/vonage/client/application/ApplicationClientTest.java index 8b41f15d3..e3d0eadfe 100644 --- a/src/test/java/com/vonage/client/application/ApplicationClientTest.java +++ b/src/test/java/com/vonage/client/application/ApplicationClientTest.java @@ -184,13 +184,13 @@ public void testGetApplication() throws Exception { public void testDeleteApplication() throws Exception { String request = SAMPLE_APPLICATION_ID.toString(); stubResponseAndRun(204, () -> client.deleteApplication(request)); - assertThrows(NullPointerException.class, () -> client.getApplication(null)); - assertThrows(IllegalArgumentException.class, () -> client.getApplication("abc123")); + assertThrows(NullPointerException.class, () -> client.deleteApplication(null)); + assertThrows(IllegalArgumentException.class, () -> client.deleteApplication("abc123")); assert400ResponseException(() -> client.deleteApplication(request)); } @Test - public void testListApplicationWithOneResult() throws Exception { + public void testListApplicationsWithOneResult() throws Exception { stubResponse(200, "{\n" + " \"page_size\": 10,\n" + " \"page\": 5,\n" + @@ -251,7 +251,7 @@ public void testListApplicationWithOneResult() throws Exception { } @Test - public void testListApplicationWithMultipleResults() throws Exception { + public void testListApplicationsWithMultipleResults() throws Exception { String json = "{\n" + " \"page_size\": 10,\n" + " \"page\": 1,\n" + @@ -318,7 +318,7 @@ public void testListApplicationWithMultipleResults() throws Exception { } @Test - public void testListApplicationWithNoResults() throws Exception { + public void testListApplicationsWithNoResults() throws Exception { String json = "{\"page\":1,\"_embedded\":{\"applications\":[]}}"; ApplicationList hal = stubResponseAndGet(json, () -> client.listApplications(ListApplicationRequest.builder().page(1).build()) diff --git a/src/main/java/com/vonage/client/users/SortOrder.java b/src/test/java/com/vonage/client/users/UserTest.java similarity index 70% rename from src/main/java/com/vonage/client/users/SortOrder.java rename to src/test/java/com/vonage/client/users/UserTest.java index 3a0b8bab9..1a2d7a87a 100644 --- a/src/main/java/com/vonage/client/users/SortOrder.java +++ b/src/test/java/com/vonage/client/users/UserTest.java @@ -15,25 +15,6 @@ */ package com.vonage.client.users; -import com.fasterxml.jackson.annotation.JsonValue; +public class UserTest { -/** - * Represents the sort order for events. - */ -public enum SortOrder { - /** - * Ascending - */ - ASC, - - /** - * Descending - */ - DESC; - - @JsonValue - @Override - public String toString() { - return name().toLowerCase(); - } } diff --git a/src/test/java/com/vonage/client/users/UsersClientTest.java b/src/test/java/com/vonage/client/users/UsersClientTest.java new file mode 100644 index 000000000..98d719b3a --- /dev/null +++ b/src/test/java/com/vonage/client/users/UsersClientTest.java @@ -0,0 +1,356 @@ +/* + * Copyright 2023 Vonage + * + * Licensed 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 com.vonage.client.users; + +import com.vonage.client.ClientTest; +import com.vonage.client.DynamicEndpointTestSpec; +import com.vonage.client.RestEndpoint; +import com.vonage.client.auth.AuthMethod; +import com.vonage.client.auth.JWTAuthMethod; +import com.vonage.client.common.HttpMethod; +import static org.junit.Assert.*; +import org.junit.Test; +import org.junit.function.ThrowingRunnable; +import java.util.*; +import java.util.function.Consumer; + +public class UsersClientTest extends ClientTest { + static final String SAMPLE_USER_ID = "USR-" + UUID.randomUUID(), + SAMPLE_USER = "{\n" + + " \"id\": \""+SAMPLE_USER_ID+"\",\n" + + " \"name\": \"my_user_name\",\n" + + " \"display_name\": \"My User Name\",\n" + + " \"image_url\": \"https://example.com/image.png\",\n" + + " \"properties\": {\n" + + " \"custom_data\": {\n" + + " \"custom_key\": \"custom_value\"\n" + + " }\n" + + " },\n" + + " \"channels\": {\n" + + " \"pstn\": [\n" + + " {\n" + + " \"number\": 123457\n" + + " }\n" + + " ],\n" + + " \"sip\": [\n" + + " {\n" + + " \"uri\": \"sip:4442138907@sip.example.com;transport=tls\",\n" + + " \"username\": \"New SIP\",\n" + + " \"password\": \"P4s5w0rd\"\n" + + " }\n" + + " ],\n" + + " \"vbc\": [\n" + + " {\n" + + " \"extension\": \"403\"\n" + + " }\n" + + " ],\n" + + " \"websocket\": [\n" + + " {\n" + + " \"uri\": \"wss://example.com/socket\",\n" + + " \"content-type\": \"audio/l16;rate=16000\",\n" + + " \"headers\": {\n" + + " \"customer_id\": \"ABC123\"\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"sms\": [\n" + + " {\n" + + " \"number\": \"447700900001\"\n" + + " }\n" + + " ],\n" + + " \"mms\": [\n" + + " {\n" + + " \"number\": \"447700900002\"\n" + + " }\n" + + " ],\n" + + " \"whatsapp\": [\n" + + " {\n" + + " \"number\": \"447700900003\"\n" + + " }\n" + + " ],\n" + + " \"viber\": [\n" + + " {\n" + + " \"number\": \"447700900004\"\n" + + " }\n" + + " ],\n" + + " \"messenger\": [\n" + + " {\n" + + " \"id\": \"12345abcd\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"_links\": {\n" + + " \"self\": {\n" + + " \"href\": \"https://api.nexmo.com/v1/users/"+SAMPLE_USER_ID+"\"\n" + + " }\n" + + " }\n" + + "}"; + + public UsersClientTest() { + client = new UsersClient(wrapper); + } + + static void assertEqualsSampleUser(User response) { + assertNotNull(response); + assertEquals(SAMPLE_USER_ID, response.getId()); + } + + void assertUserIdValidation(final Consumer method) throws Exception { + assertThrows(NullPointerException.class, () -> method.accept(null)); + assertThrows(IllegalArgumentException.class, () -> method.accept("")); + assertThrows(IllegalArgumentException.class, () -> method.accept(UUID.randomUUID().toString())); + assertThrows(IllegalArgumentException.class, () -> method.accept("abc123")); + assertThrows(IllegalArgumentException.class, () -> method.accept("USR-a1b2c3d4e5")); + assert400ResponseException(() -> method.accept(SAMPLE_USER_ID)); + } + + void assert400ResponseException(ThrowingRunnable invocation) throws Exception { + String response = "{\n" + + " \"type\": \"https://developer.nexmo.com/api-errors/application#payload-validation\",\n" + + " \"title\": \"Bad Request\",\n" + + " \"detail\": \"The request failed due to validation errors\",\n" + + " \"invalid_parameters\": [\n" + + " {\n" + + " \"name\": \"capabilities.voice.webhooks.answer_url.http_method\",\n" + + " \"reason\": \"must be one of: GET, POST\"\n" + + " }\n" + + " ],\n" + + " \"instance\": \"797a8f199c45014ab7b08bfe9cc1c12c\"\n" + + "}"; + assertApiResponseException(400, response, UsersResponseException.class, invocation); + } + + @Test + public void testCreateUser() throws Exception { + stubResponse(201, SAMPLE_USER); + User request = User.builder().name("Test user").build(); + assertEqualsSampleUser(client.createUser(request)); + assertThrows(NullPointerException.class, () -> client.createUser(null)); + assert400ResponseException(() -> client.createUser(request)); + } + + @Test + public void testUpdateUser() throws Exception { + stubResponse(200, SAMPLE_USER); + User request = User.builder().displayName("New friendlyname").build(); + assertEqualsSampleUser(client.updateUser(SAMPLE_USER_ID, request)); + assertUserIdValidation(id -> client.updateUser(id, request)); + assertThrows(NullPointerException.class, () -> client.updateUser(SAMPLE_USER_ID, null)); + assert400ResponseException(() -> client.updateUser(SAMPLE_USER_ID, request)); + } + + @Test + public void testGetUser() throws Exception { + stubResponse(200, SAMPLE_USER); + assertEqualsSampleUser(client.getUser(SAMPLE_USER_ID)); + assertUserIdValidation(client::getUser); + } + + @Test + public void testDeleteUser() throws Exception { + stubResponseAndRun(204, () -> client.deleteUser(SAMPLE_USER_ID)); + assertUserIdValidation(client::deleteUser); + } + + @Test + public void testListUsersWithOneResult() throws Exception { + + } + + @Test + public void testListUsersWithMultipleResults() throws Exception { + + } + + @Test + public void testListUsersWithNoResults() throws Exception { + String json = "{\"page\":1,\"_embedded\":{\"users\":[]}}"; + ListUsersResponse hal = stubResponseAndGet(json, () -> + client.listUsers(ListUsersRequest.builder().build()) + ); + assertEquals(0, hal.getUsers().size()); + assertEquals(1, hal.getPage().intValue()); + assertEquals(0, stubResponseAndGet(json, client::listUsers).size()); + assertNotNull(stubResponseAndGet(json, () -> client.listUsers(null))); + assert400ResponseException(client::listUsers); + } + + static abstract class UserEndpointTestSpec extends DynamicEndpointTestSpec { + + @Override + protected Collection> expectedAuthMethods() { + return Collections.singletonList(JWTAuthMethod.class); + } + + @Override + protected Class expectedResponseExceptionType() { + return UsersResponseException.class; + } + + @Override + protected String expectedDefaultBaseUri() { + return "https://api.nexmo.com"; + } + + @Override + protected String expectedEndpointUri(T request) { + String base = "/v1/users", suffix; + if (request instanceof String) { + suffix = (String) request; + } + else if (request instanceof User && HttpMethod.PATCH.equals(expectedHttpMethod())) { + suffix = ((User) request).getId(); + } + else { + suffix = null; + } + return suffix != null ? base + "/" + suffix : base; + } + + @Override + protected String sampleRequestBodyString() { + return null; + } + } + + @Test + public void testListUsersEndpoint() throws Exception { + new UserEndpointTestSpec() { + + @Override + protected RestEndpoint endpoint() { + return client.listUsers; + } + + @Override + protected HttpMethod expectedHttpMethod() { + return HttpMethod.GET; + } + + @Override + protected ListUsersRequest sampleRequest() { + return ListUsersRequest.builder().pageSize(25).build(); + } + + @Override + protected Map sampleQueryParams() { + ListUsersRequest request = sampleRequest(); + Map params = new LinkedHashMap<>(); + params.put("page_size", String.valueOf(request.getPageSize())); + return params; + } + } + .runTests(); + } + + @Test + public void testCreateUserEndpoint() throws Exception { + new UserEndpointTestSpec() { + + @Override + protected RestEndpoint endpoint() { + return client.createUser; + } + + @Override + protected HttpMethod expectedHttpMethod() { + return HttpMethod.POST; + } + + @Override + protected User sampleRequest() { + return User.builder().name("Test_user").build(); + } + + @Override + protected String sampleRequestBodyString() { + return "{\"name\":\"Test_user\"}"; + } + } + .runTests(); + } + + @Test + public void testGetUserEndpoint() throws Exception { + new UserEndpointTestSpec() { + + @Override + protected RestEndpoint endpoint() { + return client.getUser; + } + + @Override + protected HttpMethod expectedHttpMethod() { + return HttpMethod.GET; + } + + @Override + protected String sampleRequest() { + return SAMPLE_USER_ID; + } + } + .runTests(); + } + + @Test + public void testUpdateUserEndpoint() throws Exception { + new UserEndpointTestSpec() { + + @Override + protected RestEndpoint endpoint() { + return client.updateUser; + } + + @Override + protected HttpMethod expectedHttpMethod() { + return HttpMethod.PATCH; + } + + @Override + protected User sampleRequest() { + return User.fromJson(sampleRequestBodyString()); + } + + @Override + protected String sampleRequestBodyString() { + return "{\"id\":\""+SAMPLE_USER_ID+"\"}"; + } + } + .runTests(); + } + + @Test + public void testDeleteUserEndpoint() throws Exception { + new UserEndpointTestSpec() { + + @Override + protected RestEndpoint endpoint() { + return client.deleteUser; + } + + @Override + protected HttpMethod expectedHttpMethod() { + return HttpMethod.DELETE; + } + + @Override + protected String sampleRequest() { + return SAMPLE_USER_ID; + } + } + .runTests(); + } +}