1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.fit.core;
20
21 import static org.awaitility.Awaitility.await;
22 import static org.junit.jupiter.api.Assertions.assertEquals;
23 import static org.junit.jupiter.api.Assertions.assertNotEquals;
24 import static org.junit.jupiter.api.Assertions.assertNotNull;
25 import static org.junit.jupiter.api.Assertions.assertTrue;
26
27 import com.fasterxml.jackson.core.JsonProcessingException;
28 import com.fasterxml.jackson.core.type.TypeReference;
29 import java.io.ByteArrayInputStream;
30 import java.io.IOException;
31 import java.net.URI;
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.UUID;
36 import java.util.concurrent.TimeUnit;
37 import java.util.concurrent.atomic.AtomicReference;
38 import javax.ws.rs.HttpMethod;
39 import javax.ws.rs.core.HttpHeaders;
40 import javax.ws.rs.core.MediaType;
41 import javax.ws.rs.core.Response;
42 import org.apache.cxf.jaxrs.client.Client;
43 import org.apache.cxf.jaxrs.client.WebClient;
44 import org.apache.syncope.client.lib.batch.BatchRequest;
45 import org.apache.syncope.client.lib.batch.BatchResponse;
46 import org.apache.syncope.common.lib.request.GroupCR;
47 import org.apache.syncope.common.lib.request.StringReplacePatchItem;
48 import org.apache.syncope.common.lib.request.UserCR;
49 import org.apache.syncope.common.lib.request.UserUR;
50 import org.apache.syncope.common.lib.to.GroupTO;
51 import org.apache.syncope.common.lib.to.ProvisioningResult;
52 import org.apache.syncope.common.lib.to.UserTO;
53 import org.apache.syncope.common.rest.api.Preference;
54 import org.apache.syncope.common.rest.api.RESTHeaders;
55 import org.apache.syncope.common.rest.api.batch.BatchPayloadGenerator;
56 import org.apache.syncope.common.rest.api.batch.BatchPayloadParser;
57 import org.apache.syncope.common.rest.api.batch.BatchRequestItem;
58 import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
59 import org.apache.syncope.common.rest.api.service.GroupService;
60 import org.apache.syncope.common.rest.api.service.ResourceService;
61 import org.apache.syncope.common.rest.api.service.UserService;
62 import org.apache.syncope.fit.AbstractITCase;
63 import org.junit.jupiter.api.Test;
64
65 public class BatchITCase extends AbstractITCase {
66
67 private static String requestBody(final String boundary) throws JsonProcessingException {
68 List<BatchRequestItem> reqItems = new ArrayList<>();
69
70
71 UserCR userCR = UserITCase.getUniqueSample("batch@syncope.apache.org");
72 assertNotEquals("/odd", userCR.getRealm());
73 String createUserPayload = YAML_MAPPER.writeValueAsString(userCR);
74
75 BatchRequestItem createUser = new BatchRequestItem();
76 createUser.setMethod(HttpMethod.POST);
77 createUser.setRequestURI("/users");
78 createUser.setHeaders(new HashMap<>());
79 createUser.getHeaders().put(HttpHeaders.ACCEPT, List.of(RESTHeaders.APPLICATION_YAML));
80 createUser.getHeaders().put(HttpHeaders.CONTENT_TYPE, List.of(RESTHeaders.APPLICATION_YAML));
81 createUser.getHeaders().put(HttpHeaders.CONTENT_LENGTH, List.of(createUserPayload.length()));
82 createUser.setContent(createUserPayload);
83 reqItems.add(createUser);
84
85
86 GroupCR groupCR = GroupITCase.getBasicSample("batch");
87 String createGroupPayload = XML_MAPPER.writeValueAsString(groupCR);
88
89 BatchRequestItem createGroup = new BatchRequestItem();
90 createGroup.setMethod(HttpMethod.POST);
91 createGroup.setRequestURI("/groups");
92 createGroup.setHeaders(new HashMap<>());
93 createGroup.getHeaders().put(HttpHeaders.ACCEPT, List.of(MediaType.APPLICATION_XML));
94 createGroup.getHeaders().put(HttpHeaders.CONTENT_TYPE, List.of(MediaType.APPLICATION_XML));
95 createGroup.getHeaders().put(HttpHeaders.CONTENT_LENGTH, List.of(createGroupPayload.length()));
96 createGroup.setContent(createGroupPayload);
97 reqItems.add(createGroup);
98
99
100 UserUR userUR = new UserUR();
101 userUR.setKey(userCR.getUsername());
102 userUR.setRealm(new StringReplacePatchItem.Builder().value("/odd").build());
103 String updateUserPayload = JSON_MAPPER.writeValueAsString(userUR);
104
105 BatchRequestItem updateUser = new BatchRequestItem();
106 updateUser.setMethod(HttpMethod.PATCH);
107 updateUser.setRequestURI("/users/" + userCR.getUsername());
108 updateUser.setHeaders(new HashMap<>());
109 updateUser.getHeaders().put(RESTHeaders.PREFER, List.of(Preference.RETURN_NO_CONTENT.toString()));
110 updateUser.getHeaders().put(HttpHeaders.ACCEPT, List.of(MediaType.APPLICATION_JSON));
111 updateUser.getHeaders().put(HttpHeaders.CONTENT_TYPE, List.of(MediaType.APPLICATION_JSON));
112 updateUser.getHeaders().put(HttpHeaders.CONTENT_LENGTH, List.of(updateUserPayload.length()));
113 updateUser.setContent(updateUserPayload);
114 reqItems.add(updateUser);
115
116
117 BatchRequestItem endpointNotFound = new BatchRequestItem();
118 endpointNotFound.setMethod(HttpMethod.PATCH);
119 endpointNotFound.setRequestURI("/missing");
120 reqItems.add(endpointNotFound);
121
122
123 BatchRequestItem groupNotFound = new BatchRequestItem();
124 groupNotFound.setMethod(HttpMethod.DELETE);
125 groupNotFound.setRequestURI("/groups/" + UUID.randomUUID());
126 reqItems.add(groupNotFound);
127
128
129 BatchRequestItem deleteGroup = new BatchRequestItem();
130 deleteGroup.setMethod(HttpMethod.DELETE);
131 deleteGroup.setRequestURI("/groups/" + groupCR.getName());
132 reqItems.add(deleteGroup);
133
134 String body = BatchPayloadGenerator.generate(reqItems, boundary);
135 LOG.debug("Batch request body:\n{}", body);
136
137 return body;
138 }
139
140 private static void check(final List<BatchResponseItem> resItems) throws IOException {
141 assertEquals(6, resItems.size());
142
143 assertEquals(Response.Status.CREATED.getStatusCode(), resItems.get(0).getStatus());
144 assertNotNull(resItems.get(0).getHeaders().get(HttpHeaders.LOCATION));
145 assertNotNull(resItems.get(0).getHeaders().get(HttpHeaders.ETAG));
146 assertNotNull(resItems.get(0).getHeaders().get(RESTHeaders.DOMAIN));
147 assertNotNull(resItems.get(0).getHeaders().get(RESTHeaders.RESOURCE_KEY));
148 assertEquals(RESTHeaders.APPLICATION_YAML, resItems.get(0).getHeaders().get(HttpHeaders.CONTENT_TYPE).get(0));
149 ProvisioningResult<UserTO> user = YAML_MAPPER.readValue(
150 resItems.get(0).getContent(), new TypeReference<>() {
151 });
152 assertNotNull(user.getEntity().getKey());
153
154 assertEquals(Response.Status.CREATED.getStatusCode(), resItems.get(1).getStatus());
155 assertNotNull(resItems.get(1).getHeaders().get(HttpHeaders.LOCATION));
156 assertNotNull(resItems.get(1).getHeaders().get(HttpHeaders.ETAG));
157 assertNotNull(resItems.get(1).getHeaders().get(RESTHeaders.DOMAIN));
158 assertNotNull(resItems.get(1).getHeaders().get(RESTHeaders.RESOURCE_KEY));
159 assertEquals(MediaType.APPLICATION_XML, resItems.get(1).getHeaders().get(HttpHeaders.CONTENT_TYPE).get(0));
160
161 ProvisioningResult<GroupTO> group = XML_MAPPER.readValue(
162 resItems.get(1).getContent(), new TypeReference<>() {
163 });
164 assertNotNull(group.getEntity().getKey());
165
166 assertEquals(Response.Status.NO_CONTENT.getStatusCode(), resItems.get(2).getStatus());
167 assertNotNull(resItems.get(2).getHeaders().get(RESTHeaders.DOMAIN));
168 assertEquals(
169 Preference.RETURN_NO_CONTENT.toString(),
170 resItems.get(2).getHeaders().get(RESTHeaders.PREFERENCE_APPLIED).get(0));
171
172 assertEquals(Response.Status.NOT_FOUND.getStatusCode(), resItems.get(3).getStatus());
173
174 assertEquals(Response.Status.NOT_FOUND.getStatusCode(), resItems.get(4).getStatus());
175 assertNotNull(resItems.get(4).getHeaders().get(RESTHeaders.DOMAIN));
176 assertNotNull(resItems.get(4).getHeaders().get(RESTHeaders.ERROR_CODE));
177 assertNotNull(resItems.get(4).getHeaders().get(RESTHeaders.ERROR_INFO));
178 assertEquals(MediaType.APPLICATION_JSON, resItems.get(4).getHeaders().get(HttpHeaders.CONTENT_TYPE).get(0));
179
180 assertEquals(Response.Status.OK.getStatusCode(), resItems.get(5).getStatus());
181 assertNotNull(resItems.get(5).getHeaders().get(RESTHeaders.DOMAIN));
182 assertEquals(MediaType.APPLICATION_JSON, resItems.get(5).getHeaders().get(HttpHeaders.CONTENT_TYPE).get(0));
183 group = JSON_MAPPER.readValue(
184 resItems.get(5).getContent(), new TypeReference<>() {
185 });
186 assertNotNull(group);
187 }
188
189 @Test
190 public void webClientSync() throws IOException {
191 String boundary = "--batch_" + UUID.randomUUID().toString();
192
193 Response response = WebClient.create(ADDRESS).path("batch").
194 header(HttpHeaders.AUTHORIZATION, "Bearer " + ADMIN_CLIENT.getJWT()).
195 type(RESTHeaders.multipartMixedWith(boundary.substring(2))).
196 post(requestBody(boundary));
197 assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
198 assertTrue(response.getMediaType().toString().
199 startsWith(RESTHeaders.multipartMixedWith(boundary.substring(2))));
200
201 String body = response.readEntity(String.class);
202 LOG.debug("Batch response body:\n{}", body);
203
204 check(BatchPayloadParser.parse(
205 new ByteArrayInputStream(body.getBytes()),
206 response.getMediaType(),
207 new BatchResponseItem()));
208 }
209
210 @Test
211 public void webClientAsync() throws IOException {
212 String boundary = "--batch_" + UUID.randomUUID().toString();
213
214
215 Response response = WebClient.create(ADDRESS).path("batch").
216 header(HttpHeaders.AUTHORIZATION, "Bearer " + ADMIN_CLIENT.getJWT()).
217 header(RESTHeaders.PREFER, Preference.RESPOND_ASYNC).
218 type(RESTHeaders.multipartMixedWith(boundary.substring(2))).
219 post(requestBody(boundary));
220 assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus());
221 assertTrue(response.getMediaType().toString().
222 startsWith(RESTHeaders.multipartMixedWith(boundary.substring(2))));
223 assertEquals(Preference.RESPOND_ASYNC.toString(), response.getHeaderString(RESTHeaders.PREFERENCE_APPLIED));
224 URI monitor = response.getLocation();
225 assertNotNull(monitor);
226
227 WebClient client = WebClient.create(monitor).
228 header(HttpHeaders.AUTHORIZATION, "Bearer " + ADMIN_CLIENT.getJWT()).
229 type(RESTHeaders.multipartMixedWith(boundary.substring(2)));
230
231 AtomicReference<Response> holder = new AtomicReference<>();
232 await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).until(() -> {
233 try {
234 holder.set(client.get());
235 return holder.get().getStatus() != Response.Status.ACCEPTED.getStatusCode();
236 } catch (Exception e) {
237 return false;
238 }
239 });
240 response = holder.get();
241 assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
242 assertTrue(response.getMediaType().toString().
243 startsWith(RESTHeaders.multipartMixedWith(boundary.substring(2))));
244
245 String body = response.readEntity(String.class);
246 LOG.debug("Batch response body:\n{}", body);
247
248 check(BatchPayloadParser.parse(
249 new ByteArrayInputStream(body.getBytes()),
250 response.getMediaType(),
251 new BatchResponseItem()));
252
253
254 response = WebClient.create(monitor).
255 header(HttpHeaders.AUTHORIZATION, "Bearer " + ADMIN_CLIENT.getJWT()).
256 type(RESTHeaders.multipartMixedWith(boundary.substring(2))).get();
257 assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus());
258 }
259
260 private static BatchRequest batchRequest() {
261 BatchRequest batchRequest = ADMIN_CLIENT.batch();
262
263
264 UserService batchUserService = batchRequest.getService(UserService.class);
265 Client client = WebClient.client(batchUserService).reset();
266 client.type(RESTHeaders.APPLICATION_YAML).accept(RESTHeaders.APPLICATION_YAML);
267 UserCR userCR = UserITCase.getUniqueSample("batch@syncope.apache.org");
268 assertNotEquals("/odd", userCR.getRealm());
269 batchUserService.create(userCR);
270
271
272 GroupService batchGroupService = batchRequest.getService(GroupService.class);
273 client = WebClient.client(batchGroupService).reset();
274 client.type(MediaType.APPLICATION_XML).accept(MediaType.APPLICATION_XML);
275 GroupCR groupCR = GroupITCase.getBasicSample("batch");
276 batchGroupService.create(groupCR);
277
278
279 client = WebClient.client(batchUserService).reset();
280 client.type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
281 client.header(RESTHeaders.PREFER, Preference.RETURN_NO_CONTENT.toString());
282 UserUR userUR = new UserUR();
283 userUR.setKey(userCR.getUsername());
284 userUR.setRealm(new StringReplacePatchItem.Builder().value("/odd").build());
285 batchUserService.update(userUR);
286
287
288 batchRequest.getService(ResourceService.class).read(UUID.randomUUID().toString());
289
290
291 client = WebClient.client(batchGroupService).reset();
292 client.type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
293 batchGroupService.delete(UUID.randomUUID().toString());
294
295
296 batchGroupService.delete(groupCR.getName());
297
298 return batchRequest;
299 }
300
301 @Test
302 public void syncopeClientSync() throws IOException {
303 BatchResponse batchResponse = batchRequest().commit();
304
305 Response response = batchResponse.getResponse();
306 assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
307 assertTrue(response.getMediaType().toString().startsWith(RESTHeaders.MULTIPART_MIXED));
308
309 check(batchResponse.getItems());
310 }
311
312 @Test
313 public void syncopeClientAsync() throws IOException {
314
315 BatchResponse batchResponse = batchRequest().commit(true);
316
317 Response response = batchResponse.getResponse();
318 assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus());
319 assertTrue(response.getMediaType().toString().startsWith(RESTHeaders.MULTIPART_MIXED));
320
321 await().atMost(10, TimeUnit.SECONDS).pollInterval(1, TimeUnit.SECONDS).
322 until(() -> batchResponse.poll().getStatus() == Response.Status.OK.getStatusCode());
323
324 check(batchResponse.getItems());
325
326
327 response = batchResponse.poll();
328 assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus());
329 }
330 }