Skip to content

Commit e340c95

Browse files
committed
Support skipping CF API V2 tests
TODO: I am unsure about this one. Needs more exploration
1 parent a8f7c09 commit e340c95

56 files changed

Lines changed: 300 additions & 41 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ Name | Description
298298
`TEST_PROXY_USERNAME` | _(Optional)_ The username for a proxy to route all requests through
299299
`TEST_SKIPSSLVALIDATION` | _(Optional)_ Whether to skip SSL validation when connecting to the Cloud Foundry instance. Defaults to `false`.
300300
`UAA_API_REQUEST_LIMIT` | _(Optional)_ If your UAA server does rate limiting and returns 429 errors, set this variable to the smallest limit configured there. Whether your server limits UAA calls is shown in the log, together with the location of the configuration file on the server. Defaults to `0` (no limit).
301+
`SKIP_V2_TESTS` | _(Optional)_ Set to `true` to skip all V2 API integration tests. Useful when the Cloud Foundry V2 API is rate-limited or unavailable. When enabled, tests annotated with `@RequiresV2Api` will be skipped, including V3 tests that depend on V2 API calls (e.g., service broker creation). Defaults to `false`.
301302

302303
If you do not have access to a CloudFoundry instance with admin access, you can run one locally using [bosh-deployment](https://github.com/cloudfoundry/bosh-deployment) & [cf-deployment](https://github.com/cloudfoundry/cf-deployment/) and Virtualbox.
303304

integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java

Lines changed: 72 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import org.cloudfoundry.client.v3.Metadata;
7777
import org.cloudfoundry.client.v3.Relationship;
7878
import org.cloudfoundry.client.v3.applications.Application;
79+
import org.cloudfoundry.client.v3.applications.ApplicationResource;
7980
import org.cloudfoundry.client.v3.applications.DeleteApplicationRequest;
8081
import org.cloudfoundry.client.v3.applications.ListApplicationsRequest;
8182
import org.cloudfoundry.client.v3.serviceinstances.ListSharedSpacesRelationshipRequest;
@@ -129,6 +130,12 @@ final class CloudFoundryCleaner implements InitializingBean, DisposableBean {
129130

130131
private static final Logger LOGGER = LoggerFactory.getLogger("cloudfoundry-client.test");
131132

133+
private static final boolean RUN_V2_CLEANUP = isRunV2Tests();
134+
135+
private static boolean isRunV2Tests() {
136+
return !"true".equalsIgnoreCase(System.getenv("SKIP_V2_TESTS"));
137+
}
138+
132139
private static final Map<String, Boolean> STANDARD_FEATURE_FLAGS =
133140
FluentMap.<String, Boolean>builder()
134141
.entry("app_bits_upload", true)
@@ -186,6 +193,32 @@ public void destroy() {
186193
}
187194

188195
void clean() {
196+
if (!RUN_V2_CLEANUP) {
197+
LOGGER.info("Skipping V2 API cleanup operations (SKIP_V2_TESTS=true)");
198+
// Only run V3 and UAA cleanup operations
199+
Flux.empty()
200+
.thenMany(
201+
Mono.when( // No prerequisites - V3/UAA only
202+
cleanClients(this.uaaClient, this.nameFactory),
203+
cleanGroups(this.uaaClient, this.nameFactory),
204+
cleanIdentityProviders(this.uaaClient, this.nameFactory),
205+
cleanIdentityZones(this.uaaClient, this.nameFactory),
206+
cleanUsersV3(this.cloudFoundryClient, this.nameFactory)))
207+
.thenMany(
208+
Mono.when(
209+
cleanApplicationsV3(
210+
this.cloudFoundryClient,
211+
this.nameFactory,
212+
false), // runV2Calls = false (V3-only path)
213+
cleanUsers(this.uaaClient, this.nameFactory)))
214+
.retryWhen(Retry.max(5).filter(SSLException.class::isInstance))
215+
.doOnSubscribe(s -> LOGGER.debug(">> CLEANUP (V3 only) <<"))
216+
.doOnComplete(() -> LOGGER.debug("<< CLEANUP (V3 only) >>"))
217+
.then()
218+
.block(Duration.ofMinutes(30));
219+
return;
220+
}
221+
189222
Flux.empty()
190223
.thenMany(
191224
Mono.when( // Before Routes
@@ -218,7 +251,8 @@ void clean() {
218251
Mono.when(
219252
cleanApplicationsV3(
220253
this.cloudFoundryClient,
221-
this.nameFactory), // After Routes, cannot run with
254+
this.nameFactory,
255+
true), // After Routes, cannot run with
222256
// other cleanApps
223257
cleanUsers(this.uaaClient, this.nameFactory) // After CF Users
224258
))
@@ -241,32 +275,43 @@ void clean() {
241275
}
242276

243277
private static Flux<Void> cleanApplicationsV3(
244-
CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) {
245-
return PaginationUtils.requestClientV3Resources(
246-
page ->
247-
cloudFoundryClient
248-
.applicationsV3()
249-
.list(ListApplicationsRequest.builder().page(page).build()))
250-
.filter(application -> nameFactory.isApplicationName(application.getName()))
251-
.delayUntil(
252-
application ->
253-
removeApplicationServiceBindings(cloudFoundryClient, application))
254-
.flatMap(
255-
application ->
256-
cloudFoundryClient
257-
.applicationsV3()
258-
.delete(
259-
DeleteApplicationRequest.builder()
260-
.applicationId(application.getId())
261-
.build())
262-
.then()
263-
.doOnError(
264-
t ->
265-
LOGGER.error(
266-
"Unable to delete V3 application"
267-
+ " {}",
268-
application.getName(),
269-
t)));
278+
CloudFoundryClient cloudFoundryClient, NameFactory nameFactory, boolean runV2Calls) {
279+
Flux<ApplicationResource> apps =
280+
PaginationUtils.requestClientV3Resources(
281+
page ->
282+
cloudFoundryClient
283+
.applicationsV3()
284+
.list(
285+
ListApplicationsRequest.builder()
286+
.page(page)
287+
.build()))
288+
.filter(
289+
application ->
290+
nameFactory.isApplicationName(application.getName()));
291+
292+
if (runV2Calls) {
293+
apps =
294+
apps.delayUntil(
295+
application ->
296+
removeApplicationServiceBindings(
297+
cloudFoundryClient, application));
298+
}
299+
300+
return apps.flatMap(
301+
application ->
302+
cloudFoundryClient
303+
.applicationsV3()
304+
.delete(
305+
DeleteApplicationRequest.builder()
306+
.applicationId(application.getId())
307+
.build())
308+
.then()
309+
.doOnError(
310+
t ->
311+
LOGGER.error(
312+
"Unable to delete V3 application" + " {}",
313+
application.getName(),
314+
t)));
270315
}
271316

272317
private static Flux<Void> cleanBuildpacks(

integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
import org.springframework.beans.factory.annotation.Qualifier;
8686
import org.springframework.beans.factory.annotation.Value;
8787
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
88+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8889
import org.springframework.context.annotation.Bean;
8990
import org.springframework.context.annotation.Configuration;
9091
import org.springframework.context.annotation.DependsOn;
@@ -350,6 +351,7 @@ RandomNameFactory nameFactory() {
350351

351352
@Bean(initMethod = "block")
352353
@DependsOn("cloudFoundryCleaner")
354+
@ConditionalOnProperty(name = "SKIP_V2_TESTS", havingValue = "false", matchIfMissing = true)
353355
Mono<String> metricRegistrarServiceInstance(
354356
CloudFoundryClient cloudFoundryClient, Mono<String> spaceId, NameFactory nameFactory) {
355357
return spaceId.flatMap(
@@ -377,6 +379,7 @@ NetworkingClient networkingClient(
377379

378380
@Bean(initMethod = "block")
379381
@DependsOn("cloudFoundryCleaner")
382+
@ConditionalOnProperty(name = "SKIP_V2_TESTS", havingValue = "false", matchIfMissing = true)
380383
Mono<String> organizationId(
381384
CloudFoundryClient cloudFoundryClient,
382385
String organizationName,
@@ -516,6 +519,7 @@ String serviceName(NameFactory nameFactory) {
516519

517520
@Bean(initMethod = "block")
518521
@DependsOn("cloudFoundryCleaner")
522+
@ConditionalOnProperty(name = "SKIP_V2_TESTS", havingValue = "false", matchIfMissing = true)
519523
Mono<String> spaceId(
520524
CloudFoundryClient cloudFoundryClient, Mono<String> organizationId, String spaceName) {
521525
return organizationId
@@ -542,6 +546,7 @@ String spaceName(NameFactory nameFactory) {
542546

543547
@Bean(initMethod = "block")
544548
@DependsOn("cloudFoundryCleaner")
549+
@ConditionalOnProperty(name = "SKIP_V2_TESTS", havingValue = "false", matchIfMissing = true)
545550
Mono<String> stackId(CloudFoundryClient cloudFoundryClient, Mono<String> stackName) {
546551
return stackName
547552
.flux()
@@ -570,6 +575,7 @@ Mono<String> stackId(CloudFoundryClient cloudFoundryClient, Mono<String> stackNa
570575
*/
571576
@Bean(initMethod = "block")
572577
@DependsOn("cloudFoundryCleaner")
578+
@ConditionalOnProperty(name = "SKIP_V2_TESTS", havingValue = "false", matchIfMissing = true)
573579
Mono<String> stackName(CloudFoundryClient cloudFoundryClient) {
574580
return PaginationUtils.requestClientV2Resources(
575581
page ->
@@ -587,6 +593,7 @@ Mono<String> stackName(CloudFoundryClient cloudFoundryClient) {
587593
@Lazy
588594
@Bean(initMethod = "block")
589595
@DependsOn("cloudFoundryCleaner")
596+
@ConditionalOnProperty(name = "SKIP_V2_TESTS", havingValue = "false", matchIfMissing = true)
590597
Mono<ApplicationUtils.ApplicationMetadata> testLogCacheApp(
591598
CloudFoundryClient cloudFoundryClient,
592599
Mono<String> spaceId,
@@ -623,6 +630,7 @@ String testLogCacheAppName(NameFactory nameFactory) {
623630

624631
@Lazy
625632
@Bean
633+
@ConditionalOnProperty(name = "SKIP_V2_TESTS", havingValue = "false", matchIfMissing = true)
626634
TestLogCacheEndpoints testLogCacheEndpoints(
627635
ConnectionContext connectionContext,
628636
TokenProvider tokenProvider,
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2026 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;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Inherited;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
26+
import org.junit.jupiter.api.extension.ExecutionCondition;
27+
import org.junit.jupiter.api.extension.ExtendWith;
28+
import org.junit.jupiter.api.extension.ExtensionContext;
29+
30+
/**
31+
* Annotation to mark tests that require the V2 API. Tests annotated with this
32+
* will be skipped if the environment variable {@code SKIP_V2_TESTS} is set to "true".
33+
*
34+
* <p>Usage:
35+
* <pre>
36+
* &#64;RequiresV2Api
37+
* public class MyV2Test extends AbstractIntegrationTest {
38+
* // ...
39+
* }
40+
* </pre>
41+
*
42+
* <p>To skip V2 tests, set the environment variable:
43+
* <pre>
44+
* export SKIP_V2_TESTS=true
45+
* </pre>
46+
*/
47+
@Target({ElementType.METHOD, ElementType.TYPE})
48+
@Retention(RetentionPolicy.RUNTIME)
49+
@Documented
50+
@Inherited
51+
@ExtendWith(RequiresV2Api.V2ApiCondition.class)
52+
public @interface RequiresV2Api {
53+
54+
/**
55+
* JUnit 5 ExecutionCondition that checks if V2 tests should be skipped.
56+
*/
57+
class V2ApiCondition implements ExecutionCondition {
58+
59+
private static final String SKIP_V2_TESTS_ENV = "SKIP_V2_TESTS";
60+
61+
@Override
62+
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
63+
if ("true".equalsIgnoreCase(System.getenv(SKIP_V2_TESTS_ENV))) {
64+
return ConditionEvaluationResult.disabled(
65+
"V2 API tests are disabled via "
66+
+ SKIP_V2_TESTS_ENV
67+
+ " environment variable");
68+
}
69+
return ConditionEvaluationResult.enabled("V2 API tests are enabled");
70+
}
71+
}
72+
}

integration-test/src/test/java/org/cloudfoundry/client/v2/ApplicationsTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.cloudfoundry.CleanupCloudFoundryAfterClass;
4040
import org.cloudfoundry.CloudFoundryVersion;
4141
import org.cloudfoundry.IfCloudFoundryVersion;
42+
import org.cloudfoundry.RequiresV2Api;
4243
import org.cloudfoundry.client.CloudFoundryClient;
4344
import org.cloudfoundry.client.v2.applications.AbstractApplicationResource;
4445
import org.cloudfoundry.client.v2.applications.ApplicationEnvironmentRequest;
@@ -98,6 +99,7 @@
9899
import reactor.util.function.Tuple2;
99100

100101
@CleanupCloudFoundryAfterClass
102+
@RequiresV2Api
101103
public final class ApplicationsTest extends AbstractIntegrationTest {
102104

103105
@Autowired private CloudFoundryClient cloudFoundryClient;

integration-test/src/test/java/org/cloudfoundry/client/v2/BlobstoresTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.time.Duration;
2020
import org.cloudfoundry.AbstractIntegrationTest;
2121
import org.cloudfoundry.ApplicationUtils;
22+
import org.cloudfoundry.RequiresV2Api;
2223
import org.cloudfoundry.client.CloudFoundryClient;
2324
import org.cloudfoundry.client.v2.blobstores.DeleteBlobstoreBuildpackCachesRequest;
2425
import org.cloudfoundry.util.JobUtils;
@@ -27,6 +28,7 @@
2728
import reactor.core.publisher.Mono;
2829
import reactor.test.StepVerifier;
2930

31+
@RequiresV2Api
3032
public final class BlobstoresTest extends AbstractIntegrationTest {
3133

3234
@Autowired private CloudFoundryClient cloudFoundryClient;

integration-test/src/test/java/org/cloudfoundry/client/v2/BuildpacksTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.nio.file.Path;
2121
import java.time.Duration;
2222
import org.cloudfoundry.AbstractIntegrationTest;
23+
import org.cloudfoundry.RequiresV2Api;
2324
import org.cloudfoundry.client.CloudFoundryClient;
2425
import org.cloudfoundry.client.v2.buildpacks.BuildpackEntity;
2526
import org.cloudfoundry.client.v2.buildpacks.BuildpackResource;
@@ -40,6 +41,7 @@
4041
import reactor.core.publisher.Mono;
4142
import reactor.test.StepVerifier;
4243

44+
@RequiresV2Api
4345
public final class BuildpacksTest extends AbstractIntegrationTest {
4446

4547
@Autowired private CloudFoundryClient cloudFoundryClient;

integration-test/src/test/java/org/cloudfoundry/client/v2/DomainsTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.time.Duration;
2424
import java.util.function.Consumer;
2525
import org.cloudfoundry.AbstractIntegrationTest;
26+
import org.cloudfoundry.RequiresV2Api;
2627
import org.cloudfoundry.client.CloudFoundryClient;
2728
import org.cloudfoundry.client.v2.applications.CreateApplicationRequest;
2829
import org.cloudfoundry.client.v2.applications.CreateApplicationResponse;
@@ -59,6 +60,7 @@
5960
import reactor.util.function.Tuple2;
6061

6162
@SuppressWarnings("deprecation")
63+
@RequiresV2Api
6264
public final class DomainsTest extends AbstractIntegrationTest {
6365

6466
@Autowired private CloudFoundryClient cloudFoundryClient;

integration-test/src/test/java/org/cloudfoundry/client/v2/EventsTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.time.Duration;
2020
import org.cloudfoundry.AbstractIntegrationTest;
21+
import org.cloudfoundry.RequiresV2Api;
2122
import org.cloudfoundry.client.CloudFoundryClient;
2223
import org.cloudfoundry.client.v2.events.EventResource;
2324
import org.cloudfoundry.client.v2.events.GetEventRequest;
@@ -29,6 +30,7 @@
2930
import reactor.core.publisher.Mono;
3031
import reactor.test.StepVerifier;
3132

33+
@RequiresV2Api
3234
public final class EventsTest extends AbstractIntegrationTest {
3335

3436
@Autowired private CloudFoundryClient cloudFoundryClient;

integration-test/src/test/java/org/cloudfoundry/client/v2/FeatureFlagsTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.Set;
2727
import java.util.stream.Collectors;
2828
import org.cloudfoundry.AbstractIntegrationTest;
29+
import org.cloudfoundry.RequiresV2Api;
2930
import org.cloudfoundry.client.CloudFoundryClient;
3031
import org.cloudfoundry.client.v2.featureflags.FeatureFlagEntity;
3132
import org.cloudfoundry.client.v2.featureflags.GetFeatureFlagRequest;
@@ -38,6 +39,7 @@
3839
import reactor.test.StepVerifier;
3940
import reactor.util.function.Tuples;
4041

42+
@RequiresV2Api
4143
public final class FeatureFlagsTest extends AbstractIntegrationTest {
4244

4345
private static final List<String> coreFeatureFlagNameList =

0 commit comments

Comments
 (0)