From 59c06403bcd57534e0ec7a345b47b8887f54d993 Mon Sep 17 00:00:00 2001 From: "joda (damzog)" <16135207+damzog@users.noreply.github.com> Date: Wed, 9 Jul 2025 13:51:12 +0200 Subject: [PATCH 1/7] New resource for v3/users --- .../CleanupCloudFoundryAfterClass.java | 27 +++++++++++++++++++ .../org/cloudfoundry/CloudFoundryCleaner.java | 25 ++++++++++++++++- .../IntegrationTestConfiguration.java | 4 +-- .../client/v2/ApplicationsTest.java | 2 ++ .../client/v2/ServiceBrokersTest.java | 2 ++ .../org/cloudfoundry/client/v3/AdminTest.java | 1 - .../client/v3/ApplicationsTest.java | 2 ++ .../client/v3/DeploymentsTest.java | 2 ++ .../cloudfoundry/client/v3/ProcessesTest.java | 2 ++ .../client/v3/ServiceBrokersTest.java | 2 ++ .../org/cloudfoundry/client/v3/TasksTest.java | 2 ++ .../operations/ApplicationsTest.java | 6 ++++- .../cloudfoundry/operations/RoutesTest.java | 2 ++ .../operations/ServiceAdminTest.java | 7 +++-- .../cloudfoundry/operations/ServicesTest.java | 2 ++ 15 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 integration-test/src/test/java/org/cloudfoundry/CleanupCloudFoundryAfterClass.java diff --git a/integration-test/src/test/java/org/cloudfoundry/CleanupCloudFoundryAfterClass.java b/integration-test/src/test/java/org/cloudfoundry/CleanupCloudFoundryAfterClass.java new file mode 100644 index 0000000000..88d8fd6303 --- /dev/null +++ b/integration-test/src/test/java/org/cloudfoundry/CleanupCloudFoundryAfterClass.java @@ -0,0 +1,27 @@ +package org.cloudfoundry; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.context.annotation.Bean; +import org.springframework.test.annotation.DirtiesContext; + +/** + * Meta-annotation to show that a test class will add too much to the CF instance, and that a full universe + * cleanup should occur. This is important because otherwise the integration tests create too many apps and + * blow up the memory quota. We do not want to recreate the full environment for EVERY test class because + * the process takes 30~60s, so in total it could add more than an hour to the integration tests. + *

+ * Technically, this is achieved by recreating a Spring ApplicationContext with {@link DirtiesContext}. The + * {@link CloudFoundryCleaner} bean will be destroyed, which triggers {@link CloudFoundryCleaner#clean()}. + * After that, every {@link Bean} in {@link IntegrationTestConfiguration} is recreated, including users, + * clients, organizations, etc: everything required to run a test. + *

+ * We use a meta-annotation instead of a raw {@link DirtiesContext} to make it clear what it does, rather + * than having to understand complicated lifecycle issues. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@DirtiesContext +public @interface CleanupCloudFoundryAfterClass {} diff --git a/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java b/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java index c4fb056ad2..d515a86b09 100644 --- a/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java +++ b/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java @@ -116,12 +116,16 @@ import org.cloudfoundry.util.ResourceUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.function.Tuples; import reactor.util.retry.Retry; -final class CloudFoundryCleaner { +final class CloudFoundryCleaner implements InitializingBean, DisposableBean { + + private static boolean cleanSlateEnvironment = false; private static final Logger LOGGER = LoggerFactory.getLogger("cloudfoundry-client.test"); @@ -162,6 +166,25 @@ final class CloudFoundryCleaner { this.uaaClient = uaaClient; } + /** + * Once at the beginning of the whole test suite, clean up the environment. It should only ever happen + * once, hence the static init variable. + */ + @Override + public void afterPropertiesSet() { + if (!cleanSlateEnvironment) { + LOGGER.info( + "Performing clean slate cleanup. Should happen once per integration test run."); + this.clean(); + cleanSlateEnvironment = true; + } + } + + @Override + public void destroy() { + this.clean(); + } + void clean() { Flux.empty() .thenMany( diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index c3954a978a..c0b3f44b30 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -37,7 +37,6 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; -import java.util.Random; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.info.GetInfoRequest; import org.cloudfoundry.client.v2.organizationquotadefinitions.CreateOrganizationQuotaDefinitionRequest; @@ -243,14 +242,13 @@ String clientSecret(NameFactory nameFactory) { return nameFactory.getClientSecret(); } - @Bean(initMethod = "clean", destroyMethod = "clean") + @Bean CloudFoundryCleaner cloudFoundryCleaner( @Qualifier("admin") CloudFoundryClient cloudFoundryClient, NameFactory nameFactory, @Qualifier("admin") NetworkingClient networkingClient, Version serverVersion, @Qualifier("admin") UaaClient uaaClient) { - return new CloudFoundryCleaner( cloudFoundryClient, nameFactory, networkingClient, serverVersion, uaaClient); } diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ApplicationsTest.java index 6cfbab97d8..9774568bd0 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ApplicationsTest.java @@ -36,6 +36,7 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.client.CloudFoundryClient; @@ -96,6 +97,7 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuple2; +@CleanupCloudFoundryAfterClass public final class ApplicationsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBrokersTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBrokersTest.java index 75486720fc..214577e4a9 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBrokersTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBrokersTest.java @@ -25,6 +25,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.ApplicationUtils; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.ServiceBrokerUtils; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.servicebrokers.CreateServiceBrokerRequest; @@ -43,6 +44,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@CleanupCloudFoundryAfterClass public final class ServiceBrokersTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/AdminTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/AdminTest.java index dd14124d1b..d185f15dea 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/AdminTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/AdminTest.java @@ -26,7 +26,6 @@ import org.cloudfoundry.util.JobUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; - import reactor.core.publisher.Mono; import reactor.test.StepVerifier; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ApplicationsTest.java index 3354b24ed7..ef3ea073c8 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ApplicationsTest.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.Map; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.client.CloudFoundryClient; @@ -106,6 +107,7 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuples; +@CleanupCloudFoundryAfterClass public final class ApplicationsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/DeploymentsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/DeploymentsTest.java index 61ec02076f..d823ad7b1c 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/DeploymentsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/DeploymentsTest.java @@ -22,6 +22,7 @@ import java.nio.file.Path; import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.client.CloudFoundryClient; @@ -51,6 +52,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_4) +@CleanupCloudFoundryAfterClass public final class DeploymentsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ProcessesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ProcessesTest.java index b509114793..e4a4184e85 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ProcessesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ProcessesTest.java @@ -22,6 +22,7 @@ import java.nio.file.Path; import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.client.CloudFoundryClient; @@ -42,6 +43,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_0) +@CleanupCloudFoundryAfterClass public final class ProcessesTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBrokersTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBrokersTest.java index f7c063c445..656a0b0efe 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBrokersTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBrokersTest.java @@ -25,6 +25,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.ApplicationUtils; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.ServiceBrokerUtils; @@ -49,6 +50,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_10) +@CleanupCloudFoundryAfterClass public final class ServiceBrokersTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/TasksTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/TasksTest.java index a4c962d0f1..0e3fdada8c 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/TasksTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/TasksTest.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.client.CloudFoundryClient; @@ -50,6 +51,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_1_12) +@CleanupCloudFoundryAfterClass public final class TasksTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 5eb9f34a57..2ffd49b285 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.logcache.v1.Envelope; @@ -93,6 +94,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@CleanupCloudFoundryAfterClass public final class ApplicationsTest extends AbstractIntegrationTest { private static final String DEFAULT_ROUTER_GROUP = "default-tcp"; @@ -109,7 +111,9 @@ public final class ApplicationsTest extends AbstractIntegrationTest { // To create a service in #pushBindService, the Service Broker must be installed first. // We ensure it is by loading the serviceBrokerId @Lazy bean. - @Qualifier("serviceBrokerId") @Autowired private Mono serviceBrokerId; + @Qualifier("serviceBrokerId") + @Autowired + private Mono serviceBrokerId; @Test public void copySource() throws IOException { diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java index 5b86191b22..19db1a53b1 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java @@ -27,6 +27,7 @@ import java.util.Optional; import java.util.function.Predicate; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.operations.applications.ApplicationHealthCheck; import org.cloudfoundry.operations.applications.PushApplicationRequest; import org.cloudfoundry.operations.domains.CreateDomainRequest; @@ -49,6 +50,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@CleanupCloudFoundryAfterClass public final class RoutesTest extends AbstractIntegrationTest { private static final String DEFAULT_ROUTER_GROUP = "default-tcp"; diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ServiceAdminTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ServiceAdminTest.java index 0d20e86188..7a8f1ec378 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ServiceAdminTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ServiceAdminTest.java @@ -21,6 +21,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.ServiceBrokerUtils; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.spaces.CreateSpaceRequest; @@ -34,11 +35,11 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; - import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@CleanupCloudFoundryAfterClass public final class ServiceAdminTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; @@ -57,7 +58,9 @@ public final class ServiceAdminTest extends AbstractIntegrationTest { // To create a service in #pushBindService, the Service Broker must be installed first. // We ensure it is by loading the serviceBrokerId @Lazy bean. - @Qualifier("serviceBrokerId") @Autowired private Mono serviceBrokerId; + @Qualifier("serviceBrokerId") + @Autowired + private Mono serviceBrokerId; @Test public void disableServiceAccess() { diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ServicesTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ServicesTest.java index 1d3355a4ba..520027128b 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ServicesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ServicesTest.java @@ -23,6 +23,7 @@ import java.nio.file.Path; import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.servicebindings.ListServiceBindingsRequest; import org.cloudfoundry.client.v2.servicebindings.ServiceBindingResource; @@ -64,6 +65,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@CleanupCloudFoundryAfterClass public final class ServicesTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; From 327e239adc390e0019ca30738756b437142072a8 Mon Sep 17 00:00:00 2001 From: "joda (damzog)" <16135207+damzog@users.noreply.github.com> Date: Wed, 16 Jul 2025 21:36:32 +0200 Subject: [PATCH 2/7] Adding user resource for v3 --- .../client/v3/users/ReactorUsersV3.java | 83 +++++++ .../client/v3/users/ReactorUsersTest.java | 215 ++++++++++++++++++ .../client/v3/users/GET_{id}_response.json | 17 ++ .../client/v3/users/PATCH_{id}_request.json | 10 + .../client/v3/users/PATCH_{id}_response.json | 21 ++ .../client/v3/users/POST_request.json | 3 + .../client/v3/users/POST_response.json | 17 ++ .../client/CloudFoundryClient.java | 6 + .../cloudfoundry/client/v3/users/User.java | 53 +++++ .../cloudfoundry/client/v3/users/UsersV3.java | 58 +++++ .../client/v3/users/_CreateUserRequest.java | 58 +++++ .../client/v3/users/_CreateUserResponse.java | 27 +++ .../client/v3/users/_DeleteUserRequest.java | 33 +++ .../client/v3/users/_GetUserRequest.java | 33 +++ .../client/v3/users/_GetUserResponse.java | 28 +++ .../client/v3/users/_UpdateUserRequest.java | 45 ++++ .../client/v3/users/_UpdateUserResponse.java | 27 +++ .../client/v3/users/_UserResource.java | 27 +++ .../v3/users/CreateUserRequestTest.java | 25 ++ .../v3/users/DeleteUserRequestTest.java | 35 +++ .../client/v3/users/GetUserRequestTest.java | 38 ++++ .../org/cloudfoundry/client/v3/UsersTest.java | 142 ++++++++++++ 22 files changed, 1001 insertions(+) create mode 100644 cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersV3.java create mode 100644 cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersTest.java create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/GET_{id}_response.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_request.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_response.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_request.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_response.json create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/User.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/UsersV3.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserResponse.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_DeleteUserRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserResponse.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserResponse.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UserResource.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/CreateUserRequestTest.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/DeleteUserRequestTest.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/GetUserRequestTest.java create mode 100644 integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersV3.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersV3.java new file mode 100644 index 0000000000..2d42f0fff4 --- /dev/null +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersV3.java @@ -0,0 +1,83 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.reactor.client.v3.users; + +import org.cloudfoundry.client.v3.users.*; +import org.cloudfoundry.reactor.ConnectionContext; +import org.cloudfoundry.reactor.TokenProvider; +import org.cloudfoundry.reactor.client.v3.AbstractClientV3Operations; +import reactor.core.publisher.Mono; + +import java.util.Map; + +/** + * The Reactor-based implementation of {@link UsersV3} + */ +public class ReactorUsersV3 extends AbstractClientV3Operations implements UsersV3 { + + /** + * Creates an instance + * + * @param connectionContext the {@link ConnectionContext} to use when communicating with the server + * @param root the root URI of the server. Typically, something like {@code https://api.cloudfoundry.your.company.com}. + * @param tokenProvider the {@link TokenProvider} to use when communicating with the server + * @param requestTags map with custom http headers which will be added to web request + */ + public ReactorUsersV3( + ConnectionContext connectionContext, + Mono root, + TokenProvider tokenProvider, + Map requestTags) { + super(connectionContext, root, tokenProvider, requestTags); + } + + @Override + public Mono create(CreateUserRequest request) { + return post( + request, + CreateUserResponse.class, + builder -> builder.pathSegment("users")) + .checkpoint(); + } + + @Override + public Mono get(GetUserRequest request) { + return get( + request, + GetUserResponse.class, + builder -> builder.pathSegment("users", request.getUserId())) + .checkpoint(); + } + + @Override + public Mono update(UpdateUserRequest request) { + return patch( + request, + UpdateUserResponse.class, + builder -> builder.pathSegment("users", request.getUserId())) + .checkpoint(); + } + + @Override + public Mono delete(DeleteUserRequest request) { + return delete( + request, + Void.class, + builder -> builder.pathSegment("users", request.getUserId())) + .checkpoint(); + } +} \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersTest.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersTest.java new file mode 100644 index 0000000000..91676e5c71 --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersTest.java @@ -0,0 +1,215 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.reactor.client.v3.users; + +import org.cloudfoundry.client.v3.Link; +import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.client.v3.users.*; +import org.cloudfoundry.reactor.InteractionContext; +import org.cloudfoundry.reactor.TestRequest; +import org.cloudfoundry.reactor.TestResponse; +import org.cloudfoundry.reactor.client.AbstractClientApiTest; +import org.junit.jupiter.api.Test; +import reactor.test.StepVerifier; + +import java.time.Duration; +import java.util.Collections; +import java.util.Map; + +import static io.netty.handler.codec.http.HttpMethod.*; +import static io.netty.handler.codec.http.HttpResponseStatus.*; + +final class ReactorUsersTest extends AbstractClientApiTest { + private final ReactorUsersV3 users = + new ReactorUsersV3(CONNECTION_CONTEXT, this.root, TOKEN_PROVIDER, Collections.emptyMap()); + + @Test + void create() { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(POST) + .path("/users") + .payload( + "fixtures/client/v3/users/POST_request.json") + .build()) + .response( + TestResponse.builder() + .status(CREATED) + .payload( + "fixtures/client/v3/users/POST_response.json") + .build()) + .build()); + + this.users + .create(CreateUserRequest.builder() + .userId("3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build() + ) + .as(StepVerifier::create) + .expectNext( + CreateUserResponse.builder() + .id("3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .createdAt("2019-03-08T01:06:19Z") + .updatedAt("2019-03-08T01:06:19Z") + .username("some-name") + .presentationName("some-name") + .origin("uaa") + .metadata( + Metadata.builder() + .putAllAnnotations(Collections.emptyMap()) + .putAllLabels(Collections.emptyMap()) + .build()) + .link( + "self", + Link.builder() + .href( + "https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build()) + .build() + ) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void get() { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(GET) + .path("/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build()) + .response( + TestResponse.builder() + .status(OK) + .payload( + "fixtures/client/v3/users/GET_{id}_response.json") + .build()) + .build()); + + this.users + .get(GetUserRequest.builder() + .userId("3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build() + ) + .as(StepVerifier::create) + .expectNext( + GetUserResponse.builder() + .id("3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .createdAt("2019-03-08T01:06:19Z") + .updatedAt("2019-03-08T01:06:19Z") + .username("some-name") + .presentationName("some-name") + .origin("uaa") + .metadata( + Metadata.builder() + .putAllAnnotations(Collections.emptyMap()) + .putAllLabels(Collections.emptyMap()) + .build()) + .link( + "self", + Link.builder() + .href( + "https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build()) + .build() + ) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void update() { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(PATCH) + .path("/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .payload( + "fixtures/client/v3/users/PATCH_{id}_request.json") + .build()) + .response( + TestResponse.builder() + .status(CREATED) + .payload( + "fixtures/client/v3/users/PATCH_{id}_response.json") + .build()) + .build()); + + this.users + .update(UpdateUserRequest.builder() + .userId("3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .metadata(Metadata.builder() + .putAllAnnotations(Map.of("note", "detailed information")) + .putAllLabels(Map.of("environment", "production")) + .build()) + .build() + ) + .as(StepVerifier::create) + .expectNext( + UpdateUserResponse.builder() + .id("3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .createdAt("2019-03-08T01:06:19Z") + .updatedAt("2019-03-08T01:06:19Z") + .username("some-name") + .presentationName("some-name") + .origin("uaa") + .metadata(Metadata.builder() + .putAllAnnotations(Map.of("note", "detailed information")) + .putAllLabels(Map.of("environment", "production")) + .build()) + .link( + "self", + Link.builder() + .href( + "https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build()) + .build() + ) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void delete() { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(DELETE) + .path("/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build()) + .response( + TestResponse.builder() + .status(ACCEPTED) + .build()) + .build()); + + this.users + .delete(DeleteUserRequest.builder() + .userId("3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build() + ) + .as(StepVerifier::create) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + +} diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/GET_{id}_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/GET_{id}_response.json new file mode 100644 index 0000000000..b60ce5ab33 --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/GET_{id}_response.json @@ -0,0 +1,17 @@ +{ + "guid": "3a5d3d89-3f89-4f05-8188-8a2b298c79d5", + "created_at": "2019-03-08T01:06:19Z", + "updated_at": "2019-03-08T01:06:19Z", + "username": "some-name", + "presentation_name": "some-name", + "origin": "uaa", + "metadata": { + "labels": {}, + "annotations": {} + }, + "links": { + "self": { + "href": "https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5" + } + } +} \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_request.json new file mode 100644 index 0000000000..52c4a1b610 --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_request.json @@ -0,0 +1,10 @@ +{ + "metadata": { + "labels": { + "environment": "production" + }, + "annotations": { + "note": "detailed information" + } + } +} \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_response.json new file mode 100644 index 0000000000..9275a05c46 --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_response.json @@ -0,0 +1,21 @@ +{ + "guid": "3a5d3d89-3f89-4f05-8188-8a2b298c79d5", + "created_at": "2019-03-08T01:06:19Z", + "updated_at": "2019-03-08T01:06:19Z", + "username": "some-name", + "presentation_name": "some-name", + "origin": "uaa", + "metadata": { + "labels": { + "environment": "production" + }, + "annotations": { + "note": "detailed information" + } + }, + "links": { + "self": { + "href": "https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5" + } + } +} diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_request.json new file mode 100644 index 0000000000..e5463b4edb --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_request.json @@ -0,0 +1,3 @@ +{ + "guid": "3a5d3d89-3f89-4f05-8188-8a2b298c79d5" +} \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_response.json new file mode 100644 index 0000000000..b60ce5ab33 --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_response.json @@ -0,0 +1,17 @@ +{ + "guid": "3a5d3d89-3f89-4f05-8188-8a2b298c79d5", + "created_at": "2019-03-08T01:06:19Z", + "updated_at": "2019-03-08T01:06:19Z", + "username": "some-name", + "presentation_name": "some-name", + "origin": "uaa", + "metadata": { + "labels": {}, + "annotations": {} + }, + "links": { + "self": { + "href": "https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5" + } + } +} \ No newline at end of file diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java index d3c0ec8164..b3caad6f24 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java @@ -72,6 +72,7 @@ import org.cloudfoundry.client.v3.spaces.SpacesV3; import org.cloudfoundry.client.v3.stacks.StacksV3; import org.cloudfoundry.client.v3.tasks.Tasks; +import org.cloudfoundry.client.v3.users.UsersV3; /** * Main entry point to the Cloud Foundry Client API @@ -363,4 +364,9 @@ public interface CloudFoundryClient { * Main entry point to the Cloud Foundry Users Client API */ Users users(); + + /** + * Main entry point to the Cloud Foundry Users Client API + */ + UsersV3 usersV3(); } diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/User.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/User.java new file mode 100644 index 0000000000..7517e40cb8 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/User.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3.users; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.cloudfoundry.Nullable; +import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.client.v3.Resource; + +/** + * Base class for responses that are users + */ +public abstract class User extends Resource { + + /** + * The username + */ + @JsonProperty("username") + public abstract String getUsername(); + + /** + * The presentation name + */ + @JsonProperty("presentation_name") + public abstract String getPresentationName(); + + /** + * The origin + */ + @JsonProperty("origin") + @Nullable + public abstract String getOrigin(); + + /** + * The metadata + */ + @JsonProperty("metadata") + @Nullable + public abstract Metadata getMetadata(); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/UsersV3.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/UsersV3.java new file mode 100644 index 0000000000..574fd48cb3 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/UsersV3.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3.users; + + +import reactor.core.publisher.Mono; + + +/** + * Main entry point to the Cloud Foundry Users V3 Client API + */ +public interface UsersV3 { + + /** + * Makes the Create a User request + * + * @param request the Create User request + * @return the response from the Create User request + */ + Mono create(CreateUserRequest request); + + /** + * Makes the Get a User request + * + * @param request the Get User request + * @return the response from the Get User request + */ + Mono get(GetUserRequest request); + + /** + * Makes the Update a Stack request + * + * @param request the Update User request + * @return the response from the Update User request + */ + Mono update(UpdateUserRequest request); + + /** + * Makes the Delete a User request + * + * @param request the Delete User request + * @return void + */ + Mono delete(DeleteUserRequest request); +} \ No newline at end of file diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserRequest.java new file mode 100644 index 0000000000..aaa9a537a9 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserRequest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3.users; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.cloudfoundry.Nullable; +import org.cloudfoundry.client.v3.Metadata; +import org.immutables.value.Value; + +/** + * The request payload for the Create User operation + */ +@JsonSerialize +@Value.Immutable +abstract class _CreateUserRequest { + + /** + * The user id + */ + @JsonProperty("guid") + @Nullable + abstract String getUserId(); + + /** + * The username + */ + @JsonProperty("username") + @Nullable + abstract String getUsername(); + + /** + * The origin + */ + @JsonProperty("origin") + @Nullable + abstract String getOrigin(); + + /** + * The metadata + */ + @JsonProperty("metadata") + @Nullable + abstract Metadata getMetadata(); +} \ No newline at end of file diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserResponse.java new file mode 100644 index 0000000000..2109e2526e --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserResponse.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3.users; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.immutables.value.Value; + +/** + * The response payload for the Create Stack operation + */ +@JsonDeserialize +@Value.Immutable +abstract class _CreateUserResponse extends User { +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_DeleteUserRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_DeleteUserRequest.java new file mode 100644 index 0000000000..1ce5d06c78 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_DeleteUserRequest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3.users; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.immutables.value.Value; + +/** + * The request payload for the Delete User operation + */ +@Value.Immutable +abstract class _DeleteUserRequest { + + /** + * The User id + */ + @JsonIgnore + abstract String getUserId(); + +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserRequest.java new file mode 100644 index 0000000000..6354513615 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserRequest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3.users; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.immutables.value.Value; + +/** + * The request payload for the Get User operation + */ +@Value.Immutable +abstract class _GetUserRequest { + + /** + * The user id + */ + @JsonIgnore + abstract String getUserId(); + +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserResponse.java new file mode 100644 index 0000000000..3d7258aaee --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserResponse.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3.users; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.immutables.value.Value; + +/** + * The response payload for the Get User operation + */ +@JsonDeserialize +@Value.Immutable +abstract class _GetUserResponse extends User { +} + diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserRequest.java new file mode 100644 index 0000000000..2d959eb48d --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserRequest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3.users; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.cloudfoundry.Nullable; +import org.cloudfoundry.client.v3.Metadata; +import org.immutables.value.Value; + +/** + * The request payload for the Update User operation + */ +@JsonSerialize +@Value.Immutable +abstract class _UpdateUserRequest { + + /** + * The stack id + */ + @JsonIgnore + abstract String getUserId(); + + /** + * The metadata + */ + @JsonProperty("metadata") + @Nullable + abstract Metadata getMetadata(); + +} \ No newline at end of file diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserResponse.java new file mode 100644 index 0000000000..11fee6b6d1 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserResponse.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3.users; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.immutables.value.Value; + +/** + * The response payload for the Update User operation + */ +@JsonDeserialize +@Value.Immutable +abstract class _UpdateUserResponse extends User { +} \ No newline at end of file diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UserResource.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UserResource.java new file mode 100644 index 0000000000..1ab8203e60 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UserResource.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3.users; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.immutables.value.Value; + +/** + * The Resource response payload for the List Organizations operation + */ +@JsonDeserialize +@Value.Immutable +abstract class _UserResource extends User { +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/CreateUserRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/CreateUserRequestTest.java new file mode 100644 index 0000000000..b6c22e1650 --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/CreateUserRequestTest.java @@ -0,0 +1,25 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3.users; + +import org.junit.jupiter.api.Test; + +class CreateUserRequestTest { + @Test + void valid() { + CreateUserRequest.builder().userId("test-user-id").build(); + } +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/DeleteUserRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/DeleteUserRequestTest.java new file mode 100644 index 0000000000..23ee0e0ea5 --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/DeleteUserRequestTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3.users; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class DeleteUserRequestTest { + + @Test + void noUserId() { + assertThrows( + IllegalStateException.class, + () -> DeleteUserRequest.builder().build()); + } + + @Test + void valid() { + DeleteUserRequest.builder().userId("test-stack-id").build(); + } +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/GetUserRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/GetUserRequestTest.java new file mode 100644 index 0000000000..4b353ec495 --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/GetUserRequestTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3.users; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +class GetUserRequestTest { + + @Test + void noUserId() { + assertThrows( + IllegalStateException.class, + () -> GetUserRequest.builder().build()); + } + + @Test + void valid() { + GetUserRequest.builder() + .userId("test-user-id") + .build(); + } +} + diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java new file mode 100644 index 0000000000..ddfc0b1592 --- /dev/null +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java @@ -0,0 +1,142 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * 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 org.cloudfoundry.client.v3; + + +import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CloudFoundryVersion; +import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.v3.users.*; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.time.Duration; + +import static org.assertj.core.api.Assertions.assertThat; + +@IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_4_v3) +public final class UsersTest extends AbstractIntegrationTest { + + @Autowired + private CloudFoundryClient cloudFoundryClient; + + public UsersTest(CloudFoundryClient cloudFoundryClient) { + this.cloudFoundryClient = cloudFoundryClient; + } + + private static Mono createUser( + CloudFoundryClient cloudFoundryClient, String userId) { + return cloudFoundryClient + .usersV3() + .create(CreateUserRequest.builder().userId(userId).build()); + } + + private static Mono getUser( + CloudFoundryClient cloudFoundryClient, String userId) { + return cloudFoundryClient + .usersV3() + .get(GetUserRequest.builder().userId(userId).build()); + } + + @Test + public void create() { + String userId = this.nameFactory.getUserId(); + + this.cloudFoundryClient + .usersV3() + .create(CreateUserRequest.builder() + .userId(userId) + .build() + ) + .single() + .as(StepVerifier::create) + .expectNextCount(1) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + @Test + public void get() { + String userId = this.nameFactory.getUserId(); + + createUser(this.cloudFoundryClient, userId) + .flatMap(createUserResponse -> + this.cloudFoundryClient.usersV3() + .get(GetUserRequest.builder() + .userId(userId) + .build())) + .map(GetUserResponse::getId) + .as(StepVerifier::create) + .expectNext(userId) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + @Test + public void update() { + String userId = this.nameFactory.getUserId(); + + createUser(this.cloudFoundryClient, userId) + .flatMap(createUserResponse -> + this.cloudFoundryClient.usersV3() + .update(UpdateUserRequest.builder() + .userId(userId) + .metadata(Metadata.builder() + .annotation( + "annotationKey", + "annotationValue") + .label( + "labelKey", + "labelValue") + .build()) + .build())) + .then(getUser(cloudFoundryClient, userId)) + .as(StepVerifier::create) + .consumeNextWith( + GetUserResponse -> { + Metadata metadata = GetUserResponse.getMetadata(); + assertThat(metadata.getAnnotations().get("annotationKey")) + .isEqualTo("annotationValue"); + assertThat(metadata.getLabels().get("labelKey")) + .isEqualTo("labelValue"); + }) + .expectComplete() + .verify(Duration.ofMinutes(5)); + + } + + @Test + public void delete() { + String userId = this.nameFactory.getUserId(); + + createUser(this.cloudFoundryClient, userId) + .flatMap( + createUserResponse -> + this.cloudFoundryClient + .usersV3() + .delete( + DeleteUserRequest.builder() + .userId(createUserResponse.getId()) + .build())) + .as(StepVerifier::create) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } +} From ee52ccd0d033061ef3f5685b441ab53cd4f3cd4d Mon Sep 17 00:00:00 2001 From: "joda (damzog)" <16135207+damzog@users.noreply.github.com> Date: Sat, 19 Jul 2025 10:15:04 +0200 Subject: [PATCH 3/7] Adding user resource for v3 --- .../reactor/client/_ReactorCloudFoundryClient.java | 8 ++++++++ .../main/java/org/cloudfoundry/client/v3/users/User.java | 2 ++ .../test/java/org/cloudfoundry/client/v3/UsersTest.java | 4 ---- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java index 319a1bcfd6..d6ae05f47e 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java @@ -73,6 +73,7 @@ import org.cloudfoundry.client.v3.spaces.SpacesV3; import org.cloudfoundry.client.v3.stacks.StacksV3; import org.cloudfoundry.client.v3.tasks.Tasks; +import org.cloudfoundry.client.v3.users.UsersV3; import org.cloudfoundry.reactor.ConnectionContext; import org.cloudfoundry.reactor.TokenProvider; import org.cloudfoundry.reactor.client.v2.applications.ReactorApplicationsV2; @@ -131,6 +132,7 @@ import org.cloudfoundry.reactor.client.v3.spaces.ReactorSpacesV3; import org.cloudfoundry.reactor.client.v3.stacks.ReactorStacksV3; import org.cloudfoundry.reactor.client.v3.tasks.ReactorTasks; +import org.cloudfoundry.reactor.client.v3.users.ReactorUsersV3; import org.immutables.value.Value; import reactor.core.publisher.Mono; @@ -491,6 +493,12 @@ public Users users() { return new ReactorUsers(getConnectionContext(), getRootV2(), getTokenProvider(), getRequestTags()); } + @Override + @Value.Derived + public UsersV3 usersV3() { + return new ReactorUsersV3(getConnectionContext(), getRootV3(), getTokenProvider(), getRequestTags()); + } + /** * The connection context */ diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/User.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/User.java index 7517e40cb8..9f60d78b49 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/User.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/User.java @@ -29,12 +29,14 @@ public abstract class User extends Resource { * The username */ @JsonProperty("username") + @Nullable public abstract String getUsername(); /** * The presentation name */ @JsonProperty("presentation_name") + @Nullable public abstract String getPresentationName(); /** diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java index ddfc0b1592..1dbdfa336d 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java @@ -37,10 +37,6 @@ public final class UsersTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; - public UsersTest(CloudFoundryClient cloudFoundryClient) { - this.cloudFoundryClient = cloudFoundryClient; - } - private static Mono createUser( CloudFoundryClient cloudFoundryClient, String userId) { return cloudFoundryClient From 60e70c30caadcf460de4384ef07cd3059c271346 Mon Sep 17 00:00:00 2001 From: "joda (damzog)" <16135207+damzog@users.noreply.github.com> Date: Sat, 19 Jul 2025 10:15:04 +0200 Subject: [PATCH 4/7] Adding user resource for v3 --- .../src/test/java/org/cloudfoundry/client/v3/UsersTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java index 1dbdfa336d..d7b15c6771 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java @@ -35,7 +35,7 @@ public final class UsersTest extends AbstractIntegrationTest { @Autowired - private CloudFoundryClient cloudFoundryClient; + private final CloudFoundryClient cloudFoundryClient; private static Mono createUser( CloudFoundryClient cloudFoundryClient, String userId) { From a97f9a563384b6405ccf57b723f12af8613965bb Mon Sep 17 00:00:00 2001 From: "joda (damzog)" <16135207+damzog@users.noreply.github.com> Date: Sun, 20 Jul 2025 19:39:50 +0200 Subject: [PATCH 5/7] Adding user resource for v3 Adding GrantType value for jwt-bearer grant type --- .../main/java/org/cloudfoundry/uaa/tokens/GrantType.java | 9 ++++++++- .../test/java/org/cloudfoundry/client/v3/UsersTest.java | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/tokens/GrantType.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/tokens/GrantType.java index 6dd626c52d..fec9b5d99f 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/tokens/GrantType.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/tokens/GrantType.java @@ -47,7 +47,12 @@ public enum GrantType { /** * The refresh token grant type */ - REFRESH_TOKEN("refresh_token"); + REFRESH_TOKEN("refresh_token"), + + /** + * The jwt-bearer token grant type + */ + JWT_BEARER("urn:ietf:params:oauth:grant-type:jwt-bearer"); private final String value; @@ -68,6 +73,8 @@ public static GrantType from(String s) { return PASSWORD; case "refresh_token": return REFRESH_TOKEN; + case "urn:ietf:params:oauth:grant-type:jwt-bearer": + return JWT_BEARER; default: throw new IllegalArgumentException(String.format("Unknown grant type: %s", s)); } diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java index d7b15c6771..1dbdfa336d 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java @@ -35,7 +35,7 @@ public final class UsersTest extends AbstractIntegrationTest { @Autowired - private final CloudFoundryClient cloudFoundryClient; + private CloudFoundryClient cloudFoundryClient; private static Mono createUser( CloudFoundryClient cloudFoundryClient, String userId) { From e520de8ea2c0d54365a279211e7d24f0c9d16ac0 Mon Sep 17 00:00:00 2001 From: "joda (damzog)" <16135207+damzog@users.noreply.github.com> Date: Sun, 20 Jul 2025 19:59:07 +0200 Subject: [PATCH 6/7] Adding user resource for v3 Adding GrantType value for jwt-bearer grant type Adding GrantType value for saml2-bearer grant type --- .../main/java/org/cloudfoundry/uaa/tokens/GrantType.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/tokens/GrantType.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/tokens/GrantType.java index fec9b5d99f..ed8ef366cf 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/tokens/GrantType.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/tokens/GrantType.java @@ -52,7 +52,12 @@ public enum GrantType { /** * The jwt-bearer token grant type */ - JWT_BEARER("urn:ietf:params:oauth:grant-type:jwt-bearer"); + JWT_BEARER("urn:ietf:params:oauth:grant-type:jwt-bearer"), + + /** + * The jwt-bearer token grant type + */ + SAML2_BEARER("urn:ietf:params:oauth:grant-type:saml2-bearer"); private final String value; @@ -75,6 +80,8 @@ public static GrantType from(String s) { return REFRESH_TOKEN; case "urn:ietf:params:oauth:grant-type:jwt-bearer": return JWT_BEARER; + case "urn:ietf:params:oauth:grant-type:saml2-bearer": + return SAML2_BEARER; default: throw new IllegalArgumentException(String.format("Unknown grant type: %s", s)); } From 10466b3a43279a94fca72641c792b9e41ee20bcc Mon Sep 17 00:00:00 2001 From: "joda (damzog)" <16135207+damzog@users.noreply.github.com> Date: Sun, 20 Jul 2025 20:08:38 +0200 Subject: [PATCH 7/7] Adding user resource for v3 Adding GrantType value for jwt-bearer grant type Adding GrantType value for saml2-bearer grant type Adding GrantType value for user token grant type --- .../java/org/cloudfoundry/uaa/tokens/GrantType.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/tokens/GrantType.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/tokens/GrantType.java index ed8ef366cf..1e9eb36cc4 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/tokens/GrantType.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/tokens/GrantType.java @@ -55,9 +55,14 @@ public enum GrantType { JWT_BEARER("urn:ietf:params:oauth:grant-type:jwt-bearer"), /** - * The jwt-bearer token grant type + * The saml2-bearer token grant type + */ + SAML2_BEARER("urn:ietf:params:oauth:grant-type:saml2-bearer"), + + /** + * The user token token grant type */ - SAML2_BEARER("urn:ietf:params:oauth:grant-type:saml2-bearer"); + USER_TOKEN("user_token"); private final String value; @@ -82,6 +87,8 @@ public static GrantType from(String s) { return JWT_BEARER; case "urn:ietf:params:oauth:grant-type:saml2-bearer": return SAML2_BEARER; + case "user_token": + return USER_TOKEN; default: throw new IllegalArgumentException(String.format("Unknown grant type: %s", s)); }