You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by bd...@apache.org on 2022/07/29 17:05:34 UTC
[directory-scimple] 01/01: hacking on client ideas
This is an automated email from the ASF dual-hosted git repository.
bdemers pushed a commit to branch client-hacking
in repository https://gitbox.apache.org/repos/asf/directory-scimple.git
commit d5fa41ef644368770d5d198f69ada9c36d864b6b
Author: Brian Demers <bd...@apache.org>
AuthorDate: Mon Jul 25 13:29:34 2022 -0400
hacking on client ideas
---
scim-client/pom.xml | 15 +
.../apache/directory/scim/client/ScimClient.java | 61 ++++
.../directory/scim/client/rest/BaseScimClient.java | 90 ++----
.../scim/client/rest/ResourceTypesClient.java | 18 +-
.../directory/scim/client/rest/ScimSelfClient.java | 28 +-
.../directory/scim/client/rest/ScimUserClient.java | 1 +
.../directory/scim/client/ScimClientTest.java | 106 +++++++
.../scim/client/rest/ScimUserClientTest.java | 317 +++++++++++++++++++++
.../scim/server/rest/SelfResourceImpl.java | 2 +-
.../scim/server/rest/SelfResourceImplTest.java | 4 +-
.../directory/scim/spec/protocol/SelfResource.java | 4 +-
11 files changed, 554 insertions(+), 92 deletions(-)
diff --git a/scim-client/pom.xml b/scim-client/pom.xml
index 4f16089..6c9e684 100644
--- a/scim-client/pom.xml
+++ b/scim-client/pom.xml
@@ -49,11 +49,26 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/scim-client/src/main/java/org/apache/directory/scim/client/ScimClient.java b/scim-client/src/main/java/org/apache/directory/scim/client/ScimClient.java
new file mode 100644
index 0000000..a0776f6
--- /dev/null
+++ b/scim-client/src/main/java/org/apache/directory/scim/client/ScimClient.java
@@ -0,0 +1,61 @@
+/*
+ * 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.directory.scim.client;
+
+import jakarta.ws.rs.client.Client;
+import org.apache.directory.scim.client.rest.*;
+
+/**
+ * The root client used to access a SCIM server.
+ *
+ */
+public class ScimClient {
+
+ private final Client client;
+ private final String baseUrl;
+
+ private final RestCall restCall;
+
+ public ScimClient(Client client, String baseUrl) {
+ this(client, baseUrl, null);
+ }
+
+ public ScimClient(Client client, String baseUrl, RestCall restCall) {
+ this.client = client;
+ this.baseUrl = baseUrl;
+ this.restCall = restCall;
+ }
+
+ public ScimUserClient userClient() {
+ return new ScimUserClient(client, baseUrl, restCall);
+ }
+
+ public ScimGroupClient groupClient() {
+ return new ScimGroupClient(client, baseUrl, restCall);
+ }
+
+ public ScimSelfClient selfClient() {
+ return new ScimSelfClient(client, baseUrl, restCall);
+ }
+
+ public ResourceTypesClient resourceTypesClient() {
+ return new ResourceTypesClient(client, baseUrl, restCall);
+ }
+}
diff --git a/scim-client/src/main/java/org/apache/directory/scim/client/rest/BaseScimClient.java b/scim-client/src/main/java/org/apache/directory/scim/client/rest/BaseScimClient.java
index 523b662..a1c9044 100644
--- a/scim-client/src/main/java/org/apache/directory/scim/client/rest/BaseScimClient.java
+++ b/scim-client/src/main/java/org/apache/directory/scim/client/rest/BaseScimClient.java
@@ -57,9 +57,13 @@ public abstract class BaseScimClient<T extends ScimResource> implements AutoClos
private final GenericType<ListResponse<T>> scimResourceListResponseGenericType;
private final WebTarget target;
private final InternalScimClient scimClient;
- private RestCall invoke = Invocation::invoke;
+ private final RestCall invoke;
public BaseScimClient(Client client, String baseUrl, Class<T> scimResourceClass, GenericType<ListResponse<T>> scimResourceListGenericType) {
+ this(client, baseUrl, scimResourceClass, scimResourceListGenericType, null);
+ }
+
+ public BaseScimClient(Client client, String baseUrl, Class<T> scimResourceClass, GenericType<ListResponse<T>> scimResourceListGenericType, RestCall invoke) {
ScimResourceType scimResourceType = scimResourceClass.getAnnotation(ScimResourceType.class);
String endpoint = scimResourceType != null ? scimResourceType.endpoint() : null;
@@ -71,12 +75,7 @@ public abstract class BaseScimClient<T extends ScimResource> implements AutoClos
this.scimResourceListResponseGenericType = scimResourceListGenericType;
this.target = this.client.target(baseUrl).path(endpoint);
this.scimClient = new InternalScimClient();
- }
-
- public BaseScimClient(Client client, String baseUrl, Class<T> scimResourceClass, GenericType<ListResponse<T>> scimResourceListGenericType, RestCall invoke) {
- this(client, baseUrl, scimResourceClass, scimResourceListGenericType);
-
- this.invoke = invoke;
+ this.invoke = invoke != null ? invoke : Invocation::invoke;
}
@Override
@@ -199,14 +198,10 @@ public abstract class BaseScimClient<T extends ScimResource> implements AutoClos
return new ScimException(restException.getError(), restException.getStatus());
}
- public RestCall getInvoke() {
+ protected RestCall getInvoke() {
return this.invoke;
}
- public void setInvoke(RestCall invoke) {
- this.invoke = invoke;
- }
-
private class InternalScimClient implements BaseResourceTypeResource<T> {
private static final String FILTER_QUERY_PARAM = "filter";
@@ -217,7 +212,6 @@ public abstract class BaseScimClient<T extends ScimResource> implements AutoClos
@Override
public Response getById(String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
- Response response;
Invocation request = BaseScimClient.this.target
.path(id)
.queryParam(ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(attributes))
@@ -225,18 +219,11 @@ public abstract class BaseScimClient<T extends ScimResource> implements AutoClos
.request(getContentType())
.buildGet();
- try {
- response = BaseScimClient.this.invoke.apply(request);
-
- return response;
- } catch (RestException restException) {
- throw toScimException(restException);
- }
+ return executeRequest(request);
}
@Override
public Response query(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes, FilterWrapper filter, AttributeReference sortBy, SortOrder sortOrder, Integer startIndex, Integer count) throws ScimException {
- Response response;
Invocation request = BaseScimClient.this.target
.queryParam(ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(attributes))
.queryParam(EXCLUDED_ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(excludedAttributes))
@@ -248,53 +235,32 @@ public abstract class BaseScimClient<T extends ScimResource> implements AutoClos
.request(getContentType())
.buildGet();
- try {
- response = BaseScimClient.this.invoke.apply(request);
-
- return response;
- } catch (RestException restException) {
- throw toScimException(restException);
- }
+ return executeRequest(request);
}
@Override
public Response create(T resource, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
- Response response;
Invocation request = BaseScimClient.this.target
.queryParam(ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(attributes))
.queryParam(EXCLUDED_ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(excludedAttributes))
.request(getContentType())
.buildPost(Entity.entity(resource, getContentType()));
- try {
- response = BaseScimClient.this.invoke.apply(request);
-
- return response;
- } catch (RestException restException) {
- throw toScimException(restException);
- }
+ return executeRequest(request);
}
@Override
public Response find(SearchRequest searchRequest) throws ScimException {
- Response response;
Invocation request = BaseScimClient.this.target
.path(".search")
.request(getContentType())
.buildPost(Entity.entity(searchRequest, getContentType()));
- try {
- response = BaseScimClient.this.invoke.apply(request);
-
- return response;
- } catch (RestException restException) {
- throw toScimException(restException);
- }
+ return executeRequest(request);
}
@Override
public Response update(T resource, String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
- Response response;
Invocation request = BaseScimClient.this.target
.path(id)
.queryParam(ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(attributes))
@@ -302,18 +268,11 @@ public abstract class BaseScimClient<T extends ScimResource> implements AutoClos
.request(getContentType())
.buildPut(Entity.entity(resource, getContentType()));
- try {
- response = BaseScimClient.this.invoke.apply(request);
-
- return response;
- } catch (RestException restException) {
- throw toScimException(restException);
- }
+ return executeRequest(request);
}
@Override
public Response patch(PatchRequest patchRequest, String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
- Response response;
Invocation request = BaseScimClient.this.target
.path(id)
.queryParam(ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(attributes))
@@ -321,30 +280,17 @@ public abstract class BaseScimClient<T extends ScimResource> implements AutoClos
.request(getContentType())
.build("PATCH", Entity.entity(patchRequest, getContentType()));
- try {
- response = BaseScimClient.this.invoke.apply(request);
-
- return response;
- } catch (RestException restException) {
- throw toScimException(restException);
- }
+ return executeRequest(request);
}
@Override
public Response delete(String id) throws ScimException {
- Response response;
Invocation request = BaseScimClient.this.target
.path(id)
.request(getContentType())
.buildDelete();
- try {
- response = BaseScimClient.this.invoke.apply(request);
-
- return response;
- } catch (RestException restException) {
- throw toScimException(restException);
- }
+ return executeRequest(request);
}
private AttributeReferenceListWrapper nullOutQueryParamIfListIsNullOrEmpty(AttributeReferenceListWrapper wrapper) {
@@ -358,6 +304,14 @@ public abstract class BaseScimClient<T extends ScimResource> implements AutoClos
return wrapper;
}
+
+ private Response executeRequest(Invocation invocation) throws ScimException {
+ try {
+ return BaseScimClient.this.invoke.apply(invocation);
+ } catch (RestException restClientException) {
+ throw toScimException(restClientException);
+ }
+ }
}
protected String getContentType() {
diff --git a/scim-client/src/main/java/org/apache/directory/scim/client/rest/ResourceTypesClient.java b/scim-client/src/main/java/org/apache/directory/scim/client/rest/ResourceTypesClient.java
index a1f247f..deb1ab3 100644
--- a/scim-client/src/main/java/org/apache/directory/scim/client/rest/ResourceTypesClient.java
+++ b/scim-client/src/main/java/org/apache/directory/scim/client/rest/ResourceTypesClient.java
@@ -24,6 +24,7 @@ import java.util.Optional;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.Response;
@@ -37,14 +38,19 @@ public class ResourceTypesClient implements AutoCloseable {
private final Client client;
private final WebTarget target;
- private final ResourceTypesResourceClient resourceTypesResourceClient = new ResourceTypesResourceClient();
+ private final ResourceTypesResourceClient resourceTypesResourceClient;
public ResourceTypesClient(Client client, String baseUrl) {
+ this(client, baseUrl, null);
+ }
+
+ public ResourceTypesClient(Client client, String baseUrl, RestCall invoke) {
this.client = client;
this.target = this.client.target(baseUrl).path("ResourceTypes");
+ this.resourceTypesResourceClient = new ResourceTypesResourceClient(invoke);
}
- public List<ResourceType> getAllResourceTypes(String filter) throws RestException {
+ public List<ResourceType> query(String filter) throws RestException {
List<ResourceType> resourceTypes;
Response response = this.resourceTypesResourceClient.getAllResourceTypes(filter);
@@ -58,7 +64,7 @@ public class ResourceTypesClient implements AutoCloseable {
return resourceTypes;
}
- public Optional<ResourceType> getResourceType(String name) throws RestException, ProcessingException, IllegalStateException {
+ public Optional<ResourceType> get(String name) throws RestException, ProcessingException, IllegalStateException {
Optional<ResourceType> resourceType;
Response response = this.resourceTypesResourceClient.getResourceType(name);
@@ -77,6 +83,12 @@ public class ResourceTypesClient implements AutoCloseable {
private class ResourceTypesResourceClient implements ResourceTypesResource {
+ private final RestCall invoke;
+
+ private ResourceTypesResourceClient(RestCall invoke) {
+ this.invoke = invoke != null ? null : Invocation::invoke;
+ }
+
@Override
public Response getAllResourceTypes(String filter) throws RestException {
Response response = ResourceTypesClient.this.target
diff --git a/scim-client/src/main/java/org/apache/directory/scim/client/rest/ScimSelfClient.java b/scim-client/src/main/java/org/apache/directory/scim/client/rest/ScimSelfClient.java
index 8580f12..6418f10 100644
--- a/scim-client/src/main/java/org/apache/directory/scim/client/rest/ScimSelfClient.java
+++ b/scim-client/src/main/java/org/apache/directory/scim/client/rest/ScimSelfClient.java
@@ -53,41 +53,41 @@ public class ScimSelfClient implements AutoCloseable {
this.invoke = invoke;
}
- public ScimUser getSelf(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
+ public ScimUser get(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
ScimUser self;
- Response response = this.selfResourceClient.getSelf(attributes, excludedAttributes);
+ Response response = this.selfResourceClient.get(attributes, excludedAttributes);
self = BaseScimClient.handleResponse(response, ScimUser.class, response::readEntity);
return self;
}
- public ScimUser getSelf() throws ScimException {
- ScimUser self = this.getSelf(null, null);
+ public ScimUser get() throws ScimException {
+ ScimUser self = this.get(null, null);
return self;
}
- public void updateSelf(ScimUser scimUser, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
+ public void update(ScimUser scimUser, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
Response response = this.selfResourceClient.update(scimUser, attributes, excludedAttributes);
BaseScimClient.handleResponse(response);
}
- public void updateSelf(ScimUser scimUser) throws ScimException {
- this.updateSelf(scimUser, null, null);
+ public void update(ScimUser scimUser) throws ScimException {
+ this.update(scimUser, null, null);
}
- public void patchSelf(PatchRequest patchRequest, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
+ public void patch(PatchRequest patchRequest, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
Response response = this.selfResourceClient.patch(patchRequest, attributes, excludedAttributes);
BaseScimClient.handleResponse(response);
}
- public void patchSelf(PatchRequest patchRequest) throws ScimException {
- this.patchSelf(patchRequest, null, null);
+ public void patch(PatchRequest patchRequest) throws ScimException {
+ this.patch(patchRequest, null, null);
}
- public void deleteSelf() throws ScimException {
+ public void delete() throws ScimException {
Response response = this.selfResourceClient.delete();
BaseScimClient.handleResponse(response);
@@ -97,10 +97,6 @@ public class ScimSelfClient implements AutoCloseable {
return this.invoke;
}
- public void setInvoke(RestCall invoke) {
- this.invoke = invoke;
- }
-
@Override
public void close() throws Exception {
this.client.close();
@@ -109,7 +105,7 @@ public class ScimSelfClient implements AutoCloseable {
private class SelfResourceClient implements SelfResource {
@Override
- public Response getSelf(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
+ public Response get(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
Response response;
Invocation request = ScimSelfClient.this.target
.queryParam(BaseScimClient.ATTRIBUTES_QUERY_PARAM, attributes)
diff --git a/scim-client/src/main/java/org/apache/directory/scim/client/rest/ScimUserClient.java b/scim-client/src/main/java/org/apache/directory/scim/client/rest/ScimUserClient.java
index d5554e5..8defd5f 100644
--- a/scim-client/src/main/java/org/apache/directory/scim/client/rest/ScimUserClient.java
+++ b/scim-client/src/main/java/org/apache/directory/scim/client/rest/ScimUserClient.java
@@ -20,6 +20,7 @@
package org.apache.directory.scim.client.rest;
import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.GenericType;
import org.apache.directory.scim.spec.protocol.data.ListResponse;
diff --git a/scim-client/src/test/java/org/apache/directory/scim/client/ScimClientTest.java b/scim-client/src/test/java/org/apache/directory/scim/client/ScimClientTest.java
new file mode 100644
index 0000000..faefe34
--- /dev/null
+++ b/scim-client/src/test/java/org/apache/directory/scim/client/ScimClientTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.directory.scim.client;
+
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.WebTarget;
+import org.apache.directory.scim.client.rest.ResourceTypesClient;
+import org.apache.directory.scim.client.rest.ScimGroupClient;
+import org.apache.directory.scim.client.rest.ScimSelfClient;
+import org.apache.directory.scim.client.rest.ScimUserClient;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ScimClientTest {
+
+ private static final String BASE_URL = "https://scim.example.com/test";
+
+ @Test
+ public void userClient() {
+ Client client = mock(Client.class);
+ ScimClient scimClient = new ScimClient(client, BASE_URL);
+ WebTarget webTarget = mock(WebTarget.class);
+ when(client.target(BASE_URL)).thenReturn(webTarget);
+ WebTarget usersTarget = mock(WebTarget.class);
+ ArgumentCaptor<String> pathCapture = ArgumentCaptor.forClass(String.class);
+ when(webTarget.path(pathCapture.capture())).thenReturn(usersTarget);
+
+ ScimUserClient userClient = scimClient.userClient();
+
+ assertThat(userClient).extracting("client").isSameAs(client);
+ assertThat(userClient).extracting("target").isSameAs(usersTarget);
+ assertThat(pathCapture.getValue()).isEqualTo("/Users");
+ }
+
+ @Test
+ public void groupClient() {
+ Client client = mock(Client.class);
+ ScimClient scimClient = new ScimClient(client, BASE_URL);
+ WebTarget webTarget = mock(WebTarget.class);
+ when(client.target(BASE_URL)).thenReturn(webTarget);
+ WebTarget usersTarget = mock(WebTarget.class);
+ ArgumentCaptor<String> pathCapture = ArgumentCaptor.forClass(String.class);
+ when(webTarget.path(pathCapture.capture())).thenReturn(usersTarget);
+
+ ScimGroupClient groupClient = scimClient.groupClient();
+
+ assertThat(groupClient).extracting("client").isSameAs(client);
+ assertThat(groupClient).extracting("target").isSameAs(usersTarget);
+ assertThat(pathCapture.getValue()).isEqualTo("/Groups");
+ }
+
+ @Test
+ public void typesClient() {
+ Client client = mock(Client.class);
+ ScimClient scimClient = new ScimClient(client, BASE_URL);
+ WebTarget webTarget = mock(WebTarget.class);
+ when(client.target(BASE_URL)).thenReturn(webTarget);
+ WebTarget usersTarget = mock(WebTarget.class);
+ ArgumentCaptor<String> pathCapture = ArgumentCaptor.forClass(String.class);
+ when(webTarget.path(pathCapture.capture())).thenReturn(usersTarget);
+
+ ResourceTypesClient typesClient = scimClient.resourceTypesClient();
+
+ assertThat(typesClient).extracting("client").isSameAs(client);
+ assertThat(typesClient).extracting("target").isSameAs(usersTarget);
+ assertThat(pathCapture.getValue()).isEqualTo("ResourceTypes");
+ }
+
+ @Test
+ public void selfClient() {
+ Client client = mock(Client.class);
+ ScimClient scimClient = new ScimClient(client, BASE_URL);
+ WebTarget webTarget = mock(WebTarget.class);
+ when(client.target(BASE_URL)).thenReturn(webTarget);
+ WebTarget usersTarget = mock(WebTarget.class);
+ ArgumentCaptor<String> pathCapture = ArgumentCaptor.forClass(String.class);
+ when(webTarget.path(pathCapture.capture())).thenReturn(usersTarget);
+
+ ScimSelfClient selfClient = scimClient.selfClient();
+
+ assertThat(selfClient).extracting("client").isSameAs(client);
+ assertThat(selfClient).extracting("target").isSameAs(usersTarget);
+ assertThat(pathCapture.getValue()).isEqualTo("Me");
+ }
+}
diff --git a/scim-client/src/test/java/org/apache/directory/scim/client/rest/ScimUserClientTest.java b/scim-client/src/test/java/org/apache/directory/scim/client/rest/ScimUserClientTest.java
new file mode 100644
index 0000000..30df2fc
--- /dev/null
+++ b/scim-client/src/test/java/org/apache/directory/scim/client/rest/ScimUserClientTest.java
@@ -0,0 +1,317 @@
+/*
+ * 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.directory.scim.client.rest;
+
+import jakarta.ws.rs.client.Client;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.client.Invocation;
+import jakarta.ws.rs.client.WebTarget;
+import jakarta.ws.rs.core.GenericType;
+import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.spec.protocol.Constants;
+import org.apache.directory.scim.spec.protocol.attribute.AttributeReference;
+import org.apache.directory.scim.spec.protocol.attribute.AttributeReferenceListWrapper;
+import org.apache.directory.scim.spec.protocol.data.ErrorResponse;
+import org.apache.directory.scim.spec.protocol.data.ListResponse;
+import org.apache.directory.scim.spec.protocol.data.PatchRequest;
+import org.apache.directory.scim.spec.protocol.data.SearchRequest;
+import org.apache.directory.scim.spec.protocol.exception.ScimException;
+import org.apache.directory.scim.spec.protocol.search.Filter;
+import org.apache.directory.scim.spec.protocol.search.SortOrder;
+import org.apache.directory.scim.spec.resources.ScimUser;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.*;
+import java.util.stream.IntStream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+public class ScimUserClientTest {
+
+ private static final String BASE_URL = "https://scim.example.com/test";
+
+ @Test
+ public void findTest() throws Exception {
+
+ ListResponse<ScimUser> mockListResponse = mock(ListResponse.class);
+ MockClient mockClient = new MockClient(BASE_URL, "POST", ok(mockListResponse));
+ SearchRequest searchRequest = new SearchRequest();
+
+ ScimUserClient userClient = new ScimUserClient(mockClient.client, BASE_URL);
+ ListResponse<ScimUser> listResponse = userClient.find(searchRequest);
+
+ assertThat(listResponse).isSameAs(mockListResponse);
+ assertThat(mockClient.requestPath()).isEqualTo("/Users/.search");
+ assertThat(mockClient.requestEntity()).isSameAs(searchRequest);
+ }
+
+ @Test
+ public void queryTest() throws Exception {
+
+ ListResponse<ScimUser> mockListResponse = mock(ListResponse.class);
+ MockClient mockClient = new MockClient(BASE_URL, "GET", ok(mockListResponse));
+ AttributeReferenceListWrapper attributes = mock(AttributeReferenceListWrapper.class);
+ AttributeReferenceListWrapper excludedAttributes = mock(AttributeReferenceListWrapper.class);
+ Filter filter = mock(Filter.class);
+ AttributeReference sortBy = mock(AttributeReference.class);
+
+ ScimUserClient userClient = new ScimUserClient(mockClient.client, BASE_URL);
+ ListResponse<ScimUser> listResponse = userClient.query(attributes, excludedAttributes, filter, sortBy, SortOrder.ASCENDING, 5, 42);
+
+ Map<String, Object> expectedQueryParams = new HashMap<>();
+ expectedQueryParams.put("attributes", null);
+ expectedQueryParams.put("count", 42);
+ expectedQueryParams.put("excludedAttributes", null);
+ expectedQueryParams.put("filter", filter);
+ expectedQueryParams.put("sortBy", sortBy);
+ expectedQueryParams.put("sortOrder", "ASCENDING");
+ expectedQueryParams.put("startIndex", 5);
+
+ assertThat(listResponse).isSameAs(mockListResponse);
+ assertThat(mockClient.requestPath()).isEqualTo("/Users");
+ assertThat(mockClient.queryParams()).isEqualTo(expectedQueryParams);
+ }
+
+ @Test
+ public void getById_found() throws Exception {
+ ScimUser entity = mock(ScimUser.class);
+ MockClient mockClient = new MockClient(BASE_URL, "GET", ok(entity));
+ ScimUserClient userClient = new ScimUserClient(mockClient.client, BASE_URL);
+ Optional<ScimUser> optionalResponse = userClient.getById("test-id");
+
+ assertThat(optionalResponse).isNotNull();
+ assertThat(optionalResponse.get()).isSameAs(entity);
+ assertThat(mockClient.requestPath()).isEqualTo("/Users/test-id");
+ }
+
+ @Test
+ public void getById_notFound() throws Exception {
+ MockClient mockClient = new MockClient(BASE_URL, "GET", notFound());
+
+ ScimUserClient userClient = new ScimUserClient(mockClient.client, BASE_URL);
+ Optional<ScimUser> optionalResponse = userClient.getById("test-id-not-found");
+
+ assertThat(optionalResponse).isEmpty();
+ assertThat(mockClient.requestPath()).isEqualTo("/Users/test-id-not-found");
+ }
+
+ @Test
+ public void getById_serverError() throws Exception {
+ MockClient mockClient = new MockClient(BASE_URL, "GET", error(Response.Status.INTERNAL_SERVER_ERROR));
+
+ ScimUserClient userClient = new ScimUserClient(mockClient.client, BASE_URL);
+ ScimException exception = expect(ScimException.class, () -> userClient.getById("test-id-error"));
+
+ assertThat(exception.getStatus()).isEqualTo(Response.Status.INTERNAL_SERVER_ERROR);
+ assertThat(mockClient.requestPath()).isEqualTo("/Users/test-id-error");
+ }
+
+ @Test
+ public void create_success() throws Exception {
+ ScimUser entity = mock(ScimUser.class);
+ ScimUser user = mock(ScimUser.class);
+ MockClient mockClient = new MockClient(BASE_URL, "POST", ok(entity));
+
+ ScimUserClient userClient = new ScimUserClient(mockClient.client, BASE_URL);
+ ScimUser response = userClient.create(user);
+
+ assertThat(response).isSameAs(entity);
+ assertThat(mockClient.requestEntity()).isSameAs(user);
+ assertThat(mockClient.requestPath()).isEqualTo("/Users");
+ }
+
+ @Test
+ public void create_fail() throws Exception {
+ ScimUser entity = mock(ScimUser.class);
+ MockClient mockClient = new MockClient(BASE_URL, "POST", error(Response.Status.INTERNAL_SERVER_ERROR));
+
+ ScimUserClient userClient = new ScimUserClient(mockClient.client, BASE_URL);
+ ScimException exception = expect(ScimException.class, () -> userClient.create(entity));
+
+ assertThat(exception.getStatus()).isEqualTo(Response.Status.INTERNAL_SERVER_ERROR);
+ assertThat(mockClient.requestEntity()).isSameAs(entity);
+ assertThat(mockClient.requestPath()).isEqualTo("/Users");
+ }
+
+ @Test
+ public void create_withRestCallFail() throws Exception {
+ ScimUser entity = mock(ScimUser.class);
+ MockClient mockClient = new MockClient(BASE_URL, "POST", error(Response.Status.INTERNAL_SERVER_ERROR));
+ ErrorResponse error = new ErrorResponse(Response.Status.CONFLICT, "expected test exception in create_withRestCall");
+ RestCall restCall = (invocation) -> { throw new RestException(409, error); };
+
+ ScimUserClient userClient = new ScimUserClient(mockClient.client, BASE_URL, restCall);
+ ScimException exception = expect(ScimException.class, () -> userClient.create(entity));
+
+ assertThat(exception.getStatus()).isEqualTo(Response.Status.CONFLICT);
+ assertThat(mockClient.requestEntity()).isSameAs(entity);
+ assertThat(mockClient.requestPath()).isEqualTo("/Users");
+ }
+
+ @Test
+ public void update_success() throws Exception {
+ ScimUser entity = mock(ScimUser.class);
+ ScimUser user = mock(ScimUser.class);
+ MockClient mockClient = new MockClient(BASE_URL, "PUT", ok(entity));
+
+ ScimUserClient userClient = new ScimUserClient(mockClient.client, BASE_URL);
+ ScimUser response = userClient.update("update-test", user);
+
+ assertThat(response).isSameAs(entity);
+ assertThat(mockClient.requestEntity()).isSameAs(user);
+ assertThat(mockClient.requestPath()).isEqualTo("/Users/update-test");
+ }
+
+ @Test
+ public void patch_success() throws Exception {
+ PatchRequest patch = mock(PatchRequest.class);
+ ScimUser entity = mock(ScimUser.class);
+ MockClient mockClient = new MockClient(BASE_URL, "PATCH", ok(entity));
+
+ ScimUserClient userClient = new ScimUserClient(mockClient.client, BASE_URL);
+ ScimUser response = userClient.patch("update-test", patch);
+
+ assertThat(response).isSameAs(entity);
+ assertThat(mockClient.requestEntity()).isSameAs(patch);
+ assertThat(mockClient.requestPath()).isEqualTo("/Users/update-test");
+ }
+
+ @Test
+ public void delete_success() throws Exception {
+ ScimUser entity = mock(ScimUser.class);
+ MockClient mockClient = new MockClient(BASE_URL, "DELETE", ok(entity));
+
+ ScimUserClient userClient = new ScimUserClient(mockClient.client, BASE_URL);
+ userClient.delete("test-delete");
+
+ assertThat(mockClient.requestPath()).isEqualTo("/Users/test-delete");
+ }
+
+ @Test
+ public void delete_error() throws Exception {
+ MockClient mockClient = new MockClient(BASE_URL, "DELETE", notFound());
+
+ ScimUserClient userClient = new ScimUserClient(mockClient.client, BASE_URL);
+ ScimException exception = expect(ScimException.class, () -> userClient.delete("test-delete-404"));
+
+ assertThat(exception.getStatus()).isEqualTo(Response.Status.NOT_FOUND);
+ assertThat(mockClient.requestPath()).isEqualTo("/Users/test-delete-404");
+ }
+
+ static Response ok(Object entity) {
+ Response response = mock(Response.class);
+ when(response.readEntity(any(GenericType.class))).thenReturn(entity); // list response
+ when(response.readEntity(any(Class.class))).thenReturn(entity); // single entity
+ when(response.getStatusInfo()).thenReturn(Response.Status.OK);
+ when(response.getStatus()).thenReturn(Response.Status.OK.getStatusCode());
+ return response;
+ }
+
+ static Response notFound() {
+ return error(Response.Status.NOT_FOUND);
+ }
+
+ static Response error(Response.Status status) {
+ Response response = mock(Response.class);
+ when(response.getStatusInfo()).thenReturn(status);
+ when(response.getStatus()).thenReturn(status.getStatusCode());
+ return response;
+ }
+
+ @FunctionalInterface
+ interface ThrowingRunnable {
+ void run() throws Exception;
+ }
+
+ <T extends Exception> T expect(Class<T> exceptionType, ThrowingRunnable runnable) {
+ try {
+ runnable.run();
+ } catch (Exception e) {
+ if (e.getClass().equals(exceptionType)) {
+ return (T) e;
+ }
+ throw new RuntimeException("Expected block to throw exception of type: " + exceptionType + " but was: " + e.getClass(), e);
+ }
+ throw new RuntimeException("Expected block to throw exception of type " + exceptionType);
+ }
+
+ static class MockClient {
+
+ Client client = mock(Client.class);
+ WebTarget webTarget = mock(WebTarget.class);
+ Invocation.Builder builder = mock(Invocation.Builder.class);
+ ArgumentCaptor<Entity> entityCaptor = ArgumentCaptor.forClass(Entity.class);
+ Invocation invocation = mock(Invocation.class);
+ ArgumentCaptor<String> pathCapture = ArgumentCaptor.forClass(String.class);
+ ArgumentCaptor<String> queryKeysCapture = ArgumentCaptor.forClass(String.class);
+ ArgumentCaptor<Object> queryValuesCapture = ArgumentCaptor.forClass(Object.class);
+
+ MockClient(String baseUrl, String method, Response response) {
+ when(client.target(baseUrl)).thenReturn(webTarget);
+ when(webTarget.path(pathCapture.capture())).thenReturn(webTarget);
+ when(webTarget.request(Constants.SCIM_CONTENT_TYPE)).thenReturn(builder);
+ when(webTarget.queryParam(queryKeysCapture.capture(), queryValuesCapture.capture())).thenReturn(webTarget);
+ when(invocation.invoke()).thenReturn(response);
+
+ switch(method){
+ case "POST":
+ when(builder.buildPost(entityCaptor.capture())).thenReturn(invocation);
+ break;
+ case "PUT":
+ when(builder.buildPut(entityCaptor.capture())).thenReturn(invocation);
+ break;
+ case "PATCH":
+ when(builder.build(eq("PATCH"), entityCaptor.capture())).thenReturn(invocation);
+ break;
+ case "GET":
+ when(builder.buildGet()).thenReturn(invocation);
+ break;
+ case "DELETE":
+ when(builder.buildDelete()).thenReturn(invocation);
+ break;
+ default:
+ throw new IllegalStateException("Unsupported method type '" + method + "', an update to the `MockClient` is needed");
+ }
+ }
+
+ String requestPath() {
+ return String.join("/", pathCapture.getAllValues());
+ }
+
+ Map<String, Object> queryParams() {
+ List<String> keys = queryKeysCapture.getAllValues();
+ List<Object> values = queryValuesCapture.getAllValues();
+
+ Map<String, Object> params = new LinkedHashMap<>(keys.size());
+ IntStream
+ .range(0, keys.size())
+ .forEach(i -> params.put(keys.get(i), values.get(i)));
+
+ return params;
+ }
+
+ Object requestEntity() {
+ return entityCaptor.getValue().getEntity();
+ }
+ }
+}
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/SelfResourceImpl.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/SelfResourceImpl.java
index 32c789e..51cfeb2 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/SelfResourceImpl.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/SelfResourceImpl.java
@@ -54,7 +54,7 @@ public class SelfResourceImpl implements SelfResource {
SecurityContext securityContext;
@Override
- public Response getSelf(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) {
+ public Response get(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) {
try {
String internalId = getInternalId();
return userResource.getById(internalId, attributes, excludedAttributes);
diff --git a/scim-server/src/test/java/org/apache/directory/scim/server/rest/SelfResourceImplTest.java b/scim-server/src/test/java/org/apache/directory/scim/server/rest/SelfResourceImplTest.java
index b5e8143..5b22241 100644
--- a/scim-server/src/test/java/org/apache/directory/scim/server/rest/SelfResourceImplTest.java
+++ b/scim-server/src/test/java/org/apache/directory/scim/server/rest/SelfResourceImplTest.java
@@ -58,7 +58,7 @@ public class SelfResourceImplTest {
selfResource.selfIdResolver = selfIdResolverInstance;
selfResource.securityContext = securityContext;
- Response response = selfResource.getSelf(null, null);
+ Response response = selfResource.get(null, null);
assertThat(response.getEntity(), instanceOf(ErrorResponse.class));
List<String> messages = ((ErrorResponse)response.getEntity()).getErrorMessageList();
assertThat(messages, hasItem("Caller SelfIdResolver not available"));
@@ -90,6 +90,6 @@ public class SelfResourceImplTest {
selfResource.userResource = userResource;
// the response is just a passed along from the UserResource, so just validate it is the same instance.
- assertThat(selfResource.getSelf(null, null), sameInstance(mockResponse));
+ assertThat(selfResource.get(null, null), sameInstance(mockResponse));
}
}
diff --git a/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/spec/protocol/SelfResource.java b/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/spec/protocol/SelfResource.java
index d040b41..1a40d48 100644
--- a/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/spec/protocol/SelfResource.java
+++ b/scim-spec/scim-spec-protocol/src/main/java/org/apache/directory/scim/spec/protocol/SelfResource.java
@@ -90,8 +90,8 @@ public interface SelfResource {
@ApiResponse(responseCode="500", description="Internal Server Error"),
@ApiResponse(responseCode="501", description="Not Implemented")
})
- default Response getSelf(@Parameter(name="attributes") @QueryParam("attributes") AttributeReferenceListWrapper attributes,
- @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws Exception {
+ default Response get(@Parameter(name="attributes") @QueryParam("attributes") AttributeReferenceListWrapper attributes,
+ @Parameter(name="excludedAttributes") @QueryParam("excludedAttributes") AttributeReferenceListWrapper excludedAttributes) throws Exception {
return Response.status(Status.NOT_IMPLEMENTED).build();
}