Skip to content

Commit 59d0e90

Browse files
issue-1294 - implement user version 3 api (#1333)
* Implement the User Version 3 API along with Unit Test Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
1 parent 54dbf07 commit 59d0e90

File tree

29 files changed

+1210
-19
lines changed

29 files changed

+1210
-19
lines changed

cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import org.cloudfoundry.client.v3.spaces.SpacesV3;
7777
import org.cloudfoundry.client.v3.stacks.StacksV3;
7878
import org.cloudfoundry.client.v3.tasks.Tasks;
79+
import org.cloudfoundry.client.v3.users.UsersV3;
7980
import org.cloudfoundry.reactor.ConnectionContext;
8081
import org.cloudfoundry.reactor.TokenProvider;
8182
import org.cloudfoundry.reactor.client.v2.applications.ReactorApplicationsV2;
@@ -136,6 +137,7 @@
136137
import org.cloudfoundry.reactor.client.v3.spaces.ReactorSpacesV3;
137138
import org.cloudfoundry.reactor.client.v3.stacks.ReactorStacksV3;
138139
import org.cloudfoundry.reactor.client.v3.tasks.ReactorTasks;
140+
import org.cloudfoundry.reactor.client.v3.users.ReactorUsersV3;
139141
import org.immutables.value.Value;
140142
import reactor.core.publisher.Mono;
141143

@@ -509,6 +511,12 @@ public Users users() {
509511
return new ReactorUsers(getConnectionContext(), getRootV2(), getTokenProvider(), getRequestTags());
510512
}
511513

514+
@Override
515+
@Value.Derived
516+
public UsersV3 usersV3() {
517+
return new ReactorUsersV3(getConnectionContext(), getRootV3(), getTokenProvider(), getRequestTags());
518+
}
519+
512520
/**
513521
* The connection context
514522
*/
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2013-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.cloudfoundry.reactor.client.v3.users;
18+
19+
import java.util.Map;
20+
import org.cloudfoundry.client.v3.users.*;
21+
import org.cloudfoundry.reactor.ConnectionContext;
22+
import org.cloudfoundry.reactor.TokenProvider;
23+
import org.cloudfoundry.reactor.client.v3.AbstractClientV3Operations;
24+
import reactor.core.publisher.Mono;
25+
26+
/**
27+
* The Reactor-based implementation of {@link UsersV3}
28+
*/
29+
public class ReactorUsersV3 extends AbstractClientV3Operations implements UsersV3 {
30+
31+
/**
32+
* Creates an instance
33+
*
34+
* @param connectionContext the {@link ConnectionContext} to use when communicating with the server
35+
* @param root the root URI of the server. Typically, something like {@code https://api.cloudfoundry.your.company.com}.
36+
* @param tokenProvider the {@link TokenProvider} to use when communicating with the server
37+
* @param requestTags map with custom http headers which will be added to web request
38+
*/
39+
public ReactorUsersV3(
40+
ConnectionContext connectionContext,
41+
Mono<String> root,
42+
TokenProvider tokenProvider,
43+
Map<String, String> requestTags) {
44+
super(connectionContext, root, tokenProvider, requestTags);
45+
}
46+
47+
@Override
48+
public Mono<CreateUserResponse> create(CreateUserRequest request) {
49+
return post(request, CreateUserResponse.class, builder -> builder.pathSegment("users"))
50+
.checkpoint();
51+
}
52+
53+
@Override
54+
public Mono<GetUserResponse> get(GetUserRequest request) {
55+
return get(
56+
request,
57+
GetUserResponse.class,
58+
builder -> builder.pathSegment("users", request.getUserId()))
59+
.checkpoint();
60+
}
61+
62+
@Override
63+
public Mono<ListUsersResponse> list(ListUsersRequest request) {
64+
return get(request, ListUsersResponse.class, builder -> builder.pathSegment("users"))
65+
.checkpoint();
66+
}
67+
68+
@Override
69+
public Mono<UpdateUserResponse> update(UpdateUserRequest request) {
70+
return patch(
71+
request,
72+
UpdateUserResponse.class,
73+
builder -> builder.pathSegment("users", request.getUserId()))
74+
.checkpoint();
75+
}
76+
77+
@Override
78+
public Mono<Void> delete(DeleteUserRequest request) {
79+
return delete(
80+
request,
81+
Void.class,
82+
builder -> builder.pathSegment("users", request.getUserId()))
83+
.checkpoint();
84+
}
85+
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package org.cloudfoundry.reactor.client.v3.users;
2+
3+
import static io.netty.handler.codec.http.HttpMethod.*;
4+
import static io.netty.handler.codec.http.HttpResponseStatus.*;
5+
6+
import java.time.Duration;
7+
import java.util.Collections;
8+
import java.util.Map;
9+
import org.cloudfoundry.client.v3.Link;
10+
import org.cloudfoundry.client.v3.Metadata;
11+
import org.cloudfoundry.client.v3.Pagination;
12+
import org.cloudfoundry.client.v3.users.*;
13+
import org.cloudfoundry.reactor.InteractionContext;
14+
import org.cloudfoundry.reactor.TestRequest;
15+
import org.cloudfoundry.reactor.TestResponse;
16+
import org.cloudfoundry.reactor.client.AbstractClientApiTest;
17+
import org.junit.jupiter.api.Test;
18+
import reactor.test.StepVerifier;
19+
20+
final class ReactorUsersV3Test extends AbstractClientApiTest {
21+
private final ReactorUsersV3 users =
22+
new ReactorUsersV3(
23+
CONNECTION_CONTEXT, this.root, TOKEN_PROVIDER, Collections.emptyMap());
24+
25+
@Test
26+
void create() {
27+
mockRequest(
28+
InteractionContext.builder()
29+
.request(
30+
TestRequest.builder()
31+
.method(POST)
32+
.path("/users")
33+
.payload("fixtures/client/v3/users/POST_request.json")
34+
.build())
35+
.response(
36+
TestResponse.builder()
37+
.status(CREATED)
38+
.payload("fixtures/client/v3/users/POST_response.json")
39+
.build())
40+
.build());
41+
42+
this.users
43+
.create(
44+
CreateUserRequest.builder()
45+
.userId("3a5d3d89-3f89-4f05-8188-8a2b298c79d5")
46+
.build())
47+
.as(StepVerifier::create)
48+
.expectNext(
49+
CreateUserResponse.builder()
50+
.from(
51+
expectedUserResource(
52+
Collections.emptyMap(), Collections.emptyMap()))
53+
.build())
54+
.expectComplete()
55+
.verify(Duration.ofSeconds(5));
56+
}
57+
58+
@Test
59+
void get() {
60+
mockRequest(
61+
InteractionContext.builder()
62+
.request(
63+
TestRequest.builder()
64+
.method(GET)
65+
.path("/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5")
66+
.build())
67+
.response(
68+
TestResponse.builder()
69+
.status(OK)
70+
.payload("fixtures/client/v3/users/GET_{id}_response.json")
71+
.build())
72+
.build());
73+
74+
this.users
75+
.get(
76+
GetUserRequest.builder()
77+
.userId("3a5d3d89-3f89-4f05-8188-8a2b298c79d5")
78+
.build())
79+
.as(StepVerifier::create)
80+
.expectNext(
81+
GetUserResponse.builder()
82+
.from(
83+
expectedUserResource(
84+
Collections.emptyMap(), Collections.emptyMap()))
85+
.build())
86+
.expectComplete()
87+
.verify(Duration.ofSeconds(5));
88+
}
89+
90+
@Test
91+
void list() {
92+
mockRequest(
93+
InteractionContext.builder()
94+
.request(TestRequest.builder().method(GET).path("/users").build())
95+
.response(
96+
TestResponse.builder()
97+
.status(OK)
98+
.payload("fixtures/client/v3/users/GET_response.json")
99+
.build())
100+
.build());
101+
102+
Link first =
103+
Link.builder().href("https://api.example.org/v3/users?page=1&per_page=1").build();
104+
Link last =
105+
Link.builder().href("https://api.example.org/v3/users?page=1&per_page=1").build();
106+
this.users
107+
.list(ListUsersRequest.builder().build())
108+
.as(StepVerifier::create)
109+
.expectNext(
110+
ListUsersResponse.builder()
111+
.pagination(
112+
Pagination.builder()
113+
.first(first)
114+
.last(last)
115+
.totalResults(1)
116+
.totalPages(1)
117+
.build())
118+
.resource(
119+
expectedUserResource(
120+
Collections.emptyMap(), Collections.emptyMap()))
121+
.build())
122+
.expectComplete()
123+
.verify(Duration.ofSeconds(5));
124+
}
125+
126+
@Test
127+
void update() {
128+
mockRequest(
129+
InteractionContext.builder()
130+
.request(
131+
TestRequest.builder()
132+
.method(PATCH)
133+
.path("/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5")
134+
.payload("fixtures/client/v3/users/PATCH_{id}_request.json")
135+
.build())
136+
.response(
137+
TestResponse.builder()
138+
.status(CREATED)
139+
.payload(
140+
"fixtures/client/v3/users/PATCH_{id}_response.json")
141+
.build())
142+
.build());
143+
144+
this.users
145+
.update(
146+
UpdateUserRequest.builder()
147+
.userId("3a5d3d89-3f89-4f05-8188-8a2b298c79d5")
148+
.metadata(
149+
Metadata.builder()
150+
.putAllAnnotations(
151+
Collections.singletonMap(
152+
"note", "detailed information"))
153+
.putAllLabels(
154+
Collections.singletonMap(
155+
"environment", "production"))
156+
.build())
157+
.build())
158+
.as(StepVerifier::create)
159+
.expectNext(
160+
UpdateUserResponse.builder()
161+
.from(
162+
expectedUserResource(
163+
Collections.singletonMap(
164+
"note", "detailed information"),
165+
Collections.singletonMap(
166+
"environment", "production")))
167+
.build())
168+
.expectComplete()
169+
.verify(Duration.ofSeconds(5));
170+
}
171+
172+
@Test
173+
void delete() {
174+
mockRequest(
175+
InteractionContext.builder()
176+
.request(
177+
TestRequest.builder()
178+
.method(DELETE)
179+
.path("/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5")
180+
.build())
181+
.response(TestResponse.builder().status(ACCEPTED).build())
182+
.build());
183+
184+
this.users
185+
.delete(
186+
DeleteUserRequest.builder()
187+
.userId("3a5d3d89-3f89-4f05-8188-8a2b298c79d5")
188+
.build())
189+
.as(StepVerifier::create)
190+
.expectComplete()
191+
.verify(Duration.ofSeconds(5));
192+
}
193+
194+
UserResource expectedUserResource(Map<String, String> labels, Map<String, String> annotations) {
195+
return UserResource.builder()
196+
.id("3a5d3d89-3f89-4f05-8188-8a2b298c79d5")
197+
.createdAt("2019-03-08T01:06:19Z")
198+
.updatedAt("2019-03-08T01:06:19Z")
199+
.username("some-name")
200+
.presentationName("some-name")
201+
.origin("uaa")
202+
.metadata(
203+
Metadata.builder()
204+
.putAllAnnotations(labels)
205+
.putAllLabels(annotations)
206+
.build())
207+
.link(
208+
"self",
209+
Link.builder()
210+
.href(
211+
"https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5")
212+
.build())
213+
.build();
214+
}
215+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"pagination": {
3+
"total_results": 1,
4+
"total_pages": 1,
5+
"first": {
6+
"href": "https://api.example.org/v3/users?page=1&per_page=1"
7+
},
8+
"last": {
9+
"href": "https://api.example.org/v3/users?page=1&per_page=1"
10+
},
11+
"next": null,
12+
"previous": null
13+
},
14+
"resources": [
15+
{
16+
"guid": "3a5d3d89-3f89-4f05-8188-8a2b298c79d5",
17+
"created_at": "2019-03-08T01:06:19Z",
18+
"updated_at": "2019-03-08T01:06:19Z",
19+
"username": "some-name",
20+
"presentation_name": "some-name",
21+
"origin": "uaa",
22+
"metadata": {
23+
"labels": {},
24+
"annotations": {}
25+
},
26+
"links": {
27+
"self": {
28+
"href": "https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5"
29+
}
30+
}
31+
}
32+
]
33+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"guid": "3a5d3d89-3f89-4f05-8188-8a2b298c79d5",
3+
"created_at": "2019-03-08T01:06:19Z",
4+
"updated_at": "2019-03-08T01:06:19Z",
5+
"username": "some-name",
6+
"presentation_name": "some-name",
7+
"origin": "uaa",
8+
"metadata": {
9+
"labels": {},
10+
"annotations": {}
11+
},
12+
"links": {
13+
"self": {
14+
"href": "https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5"
15+
}
16+
}
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"metadata": {
3+
"labels": {
4+
"environment": "production"
5+
},
6+
"annotations": {
7+
"note": "detailed information"
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)