Skip to content

Commit 794f80a

Browse files
committed
Migrate removed LocalStack Service enum and getEndpointOverride in Testcontainers 2.x
Testcontainers 2.x removed the nested `LocalStackContainer.Service` enum and `getEndpointOverride(...)`. The existing `Testcontainers2Migration` only renamed the type, leaving `LocalStackContainer.Service.SQS` and `getEndpointOverride(...)` as unresolved references (Java and Kotlin alike). Add a `Testcontainers2LocalStack` step (run before the type rename) that replaces `Service` constants with their service-name strings and `getEndpointOverride(service)` with `getEndpoint()`.
1 parent 5b719c2 commit 794f80a

5 files changed

Lines changed: 505 additions & 0 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (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+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
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+
package org.openrewrite.java.testing.testcontainers;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.jspecify.annotations.Nullable;
21+
import org.openrewrite.*;
22+
import org.openrewrite.java.JavaVisitor;
23+
import org.openrewrite.java.search.UsesType;
24+
import org.openrewrite.java.tree.J;
25+
import org.openrewrite.java.tree.JavaType;
26+
import org.openrewrite.java.tree.TypeUtils;
27+
import org.openrewrite.marker.Markers;
28+
29+
import java.util.Locale;
30+
31+
@Value
32+
@EqualsAndHashCode(callSuper = false)
33+
public class ReplaceLocalStackService extends Recipe {
34+
35+
private static final String LOCALSTACK_CONTAINER = "org.testcontainers.containers.localstack.LocalStackContainer";
36+
private static final String SERVICE = LOCALSTACK_CONTAINER + "$Service";
37+
38+
String displayName = "Replace `LocalStackContainer.Service` enum values with service name strings";
39+
40+
String description = "Testcontainers 2.x removed the nested `LocalStackContainer.Service` enum. " +
41+
"Replace references such as `LocalStackContainer.Service.SQS` with the equivalent LocalStack " +
42+
"service name string literal (e.g. `\"sqs\"`), so calls like `withServices(...)` compile against " +
43+
"the Testcontainers 2.x `withServices(String...)` API.";
44+
45+
@Override
46+
public TreeVisitor<?, ExecutionContext> getVisitor() {
47+
return Preconditions.check(new UsesType<>(LOCALSTACK_CONTAINER, false), new JavaVisitor<ExecutionContext>() {
48+
@Override
49+
public J visitFieldAccess(J.FieldAccess fieldAccess, ExecutionContext ctx) {
50+
J j = super.visitFieldAccess(fieldAccess, ctx);
51+
if (!(j instanceof J.FieldAccess)) {
52+
return j;
53+
}
54+
J.FieldAccess fa = (J.FieldAccess) j;
55+
if (!TypeUtils.isOfClassType(fa.getType(), SERVICE)) {
56+
return fa;
57+
}
58+
String serviceName = serviceName(fa.getSimpleName());
59+
if (serviceName == null) {
60+
return fa;
61+
}
62+
return new J.Literal(Tree.randomId(), fa.getPrefix(), Markers.EMPTY,
63+
serviceName, "\"" + serviceName + "\"", null, JavaType.Primitive.String);
64+
}
65+
});
66+
}
67+
68+
/**
69+
* Maps a {@code LocalStackContainer.Service} enum constant to the LocalStack service name expected by the
70+
* Testcontainers 2.x {@code withServices(String...)} API, or {@code null} for anything that is not a known
71+
* service constant (e.g. the {@code Service} type reference itself). Most constants lowercase directly; only
72+
* a few have a divergent service name.
73+
*/
74+
private static @Nullable String serviceName(String enumConstant) {
75+
switch (enumConstant) {
76+
case "API_GATEWAY":
77+
return "apigateway";
78+
case "DYNAMODB_STREAMS":
79+
return "dynamodbstreams";
80+
case "CLOUDWATCHLOGS":
81+
return "logs";
82+
case "CLOUDFORMATION":
83+
case "CLOUDWATCH":
84+
case "DYNAMODB":
85+
case "EC2":
86+
case "FIREHOSE":
87+
case "IAM":
88+
case "KINESIS":
89+
case "KMS":
90+
case "LAMBDA":
91+
case "REDSHIFT":
92+
case "ROUTE53":
93+
case "S3":
94+
case "SECRETSMANAGER":
95+
case "SES":
96+
case "SNS":
97+
case "SQS":
98+
case "SSM":
99+
case "STEPFUNCTIONS":
100+
case "STS":
101+
return enumConstant.toLowerCase(Locale.ROOT);
102+
default:
103+
return null;
104+
}
105+
}
106+
}

src/main/resources/META-INF/rewrite/recipes.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@ maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.tes
240240
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testcontainers.ExplicitContainerImages,Explicit container images and versions,Replace implicit default container images and versions with explicit versions.,29,Testcontainers,Testing,Java,Recipes for [Testcontainers](https://testcontainers.com/) integration testing with Docker.,,Basic building blocks for transforming Java code.,,
241241
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testcontainers.Testcontainers2Dependencies,Rename Testcontainers dependencies,Change Testcontainers dependencies to adopt the new consistent `testcontainers-` prefix.,63,Testcontainers,Testing,Java,Recipes for [Testcontainers](https://testcontainers.com/) integration testing with Docker.,,Basic building blocks for transforming Java code.,,
242242
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testcontainers.Testcontainers2ContainerClasses,Testcontainers 2 container classes,Change Testcontainers container classes to their new package locations in Testcontainers 2.x.,54,Testcontainers,Testing,Java,Recipes for [Testcontainers](https://testcontainers.com/) integration testing with Docker.,,Basic building blocks for transforming Java code.,,
243+
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testcontainers.ReplaceLocalStackService,Replace `LocalStackContainer.Service` enum values with service name strings,"Testcontainers 2.x removed the nested `LocalStackContainer.Service` enum. Replace references such as `LocalStackContainer.Service.SQS` with the equivalent LocalStack service name string literal (e.g. `""sqs""`), so calls like `withServices(...)` compile against the Testcontainers 2.x `withServices(String...)` API.",1,Testcontainers,Testing,Java,Recipes for [Testcontainers](https://testcontainers.com/) integration testing with Docker.,,Basic building blocks for transforming Java code.,,
244+
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testcontainers.Testcontainers2LocalStack,Migrate removed `LocalStackContainer` members to Testcontainers 2.x,"Testcontainers 2.x removed the nested `LocalStackContainer.Service` enum and the `getEndpointOverride(...)` method. Replace `LocalStackContainer.Service` constants with the equivalent service name strings and `getEndpointOverride(service)` with `getEndpoint()`, so code continues to compile against Testcontainers 2.x. This runs while the type is still `org.testcontainers.containers.localstack.LocalStackContainer`, before it is renamed.",4,Testcontainers,Testing,Java,Recipes for [Testcontainers](https://testcontainers.com/) integration testing with Docker.,,Basic building blocks for transforming Java code.,,
243245
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testng.TestNgAssertEqualsToAssertThat,TestNG `assertEquals` to AssertJ,Convert TestNG-style `assertEquals()` to AssertJ's `assertThat().isEqualTo()`.,1,TestNG,Testing,Java,Recipes for migrating from [TestNG](https://testng.org/) to JUnit.,,Basic building blocks for transforming Java code.,,
244246
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testng.TestNgAssertNotEqualsToAssertThat,TestNG `assertNotEquals` to AssertJ,Convert TestNG-style `assertNotEquals()` to AssertJ's `assertThat().isNotEqualTo()`.,1,TestNG,Testing,Java,Recipes for migrating from [TestNG](https://testng.org/) to JUnit.,,Basic building blocks for transforming Java code.,,
245247
maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.testng.TestNgToAssertj,Migrate TestNG assertions to AssertJ,Convert assertions from `org.testng.Assert` to `org.assertj.core.api.Assertions`.,48,TestNG,Testing,Java,Recipes for migrating from [TestNG](https://testng.org/) to JUnit.,,Basic building blocks for transforming Java code.,,

src/main/resources/META-INF/rewrite/testcontainers.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,30 @@ recipeList:
7878
oldFullyQualifiedTypeName: org.testcontainers.containers.DockerComposeContainer
7979
newFullyQualifiedTypeName: org.testcontainers.containers.ComposeContainer
8080
- org.openrewrite.java.testing.testcontainers.Testcontainers2Dependencies
81+
- org.openrewrite.java.testing.testcontainers.Testcontainers2LocalStack
8182
- org.openrewrite.java.testing.testcontainers.Testcontainers2ContainerClasses
8283
---
8384
type: specs.openrewrite.org/v1beta/recipe
85+
name: org.openrewrite.java.testing.testcontainers.Testcontainers2LocalStack
86+
displayName: Migrate removed `LocalStackContainer` members to Testcontainers 2.x
87+
description: >-
88+
Testcontainers 2.x removed the nested `LocalStackContainer.Service` enum and the
89+
`getEndpointOverride(...)` method. Replace `LocalStackContainer.Service` constants with the
90+
equivalent service name strings and `getEndpointOverride(service)` with `getEndpoint()`, so code
91+
continues to compile against Testcontainers 2.x. This runs while the type is still
92+
`org.testcontainers.containers.localstack.LocalStackContainer`, before it is renamed.
93+
preconditions:
94+
- org.openrewrite.Singleton
95+
recipeList:
96+
- org.openrewrite.java.testing.testcontainers.ReplaceLocalStackService
97+
- org.openrewrite.java.ChangeMethodName:
98+
methodPattern: org.testcontainers.containers.localstack.LocalStackContainer getEndpointOverride(..)
99+
newMethodName: getEndpoint
100+
- org.openrewrite.java.DeleteMethodArgument:
101+
methodPattern: org.testcontainers.containers.localstack.LocalStackContainer getEndpoint(..)
102+
argumentIndex: 0
103+
---
104+
type: specs.openrewrite.org/v1beta/recipe
84105
name: org.openrewrite.java.testing.testcontainers.GetHostMigration
85106
displayName: Replace `ContainerState.getContainerIpAddress()` with `getHost()`
86107
description: Replace `org.testcontainers.containers.ContainerState.getContainerIpAddress()` with `getHost()`.
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (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+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
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+
package org.openrewrite.java.testing.testcontainers;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.openrewrite.DocumentExample;
20+
import org.openrewrite.InMemoryExecutionContext;
21+
import org.openrewrite.java.JavaParser;
22+
import org.openrewrite.kotlin.KotlinParser;
23+
import org.openrewrite.test.RecipeSpec;
24+
import org.openrewrite.test.RewriteTest;
25+
26+
import static org.openrewrite.java.Assertions.java;
27+
import static org.openrewrite.kotlin.Assertions.kotlin;
28+
29+
class ReplaceLocalStackServiceTest implements RewriteTest {
30+
31+
@Override
32+
public void defaults(RecipeSpec spec) {
33+
spec
34+
.recipe(new ReplaceLocalStackService())
35+
.parser(JavaParser.fromJavaVersion()
36+
.classpathFromResources(new InMemoryExecutionContext(), "testcontainers-localstack", "testcontainers-2"))
37+
.parser(KotlinParser.builder()
38+
.classpathFromResources(new InMemoryExecutionContext(), "testcontainers-localstack", "testcontainers-2"));
39+
}
40+
41+
@DocumentExample
42+
@Test
43+
void withSingleService() {
44+
rewriteRun(
45+
//language=java
46+
java(
47+
"""
48+
import org.testcontainers.containers.localstack.LocalStackContainer;
49+
50+
class A {
51+
LocalStackContainer localstack = new LocalStackContainer()
52+
.withServices(LocalStackContainer.Service.SQS);
53+
}
54+
""",
55+
"""
56+
import org.testcontainers.containers.localstack.LocalStackContainer;
57+
58+
class A {
59+
LocalStackContainer localstack = new LocalStackContainer()
60+
.withServices("sqs");
61+
}
62+
"""
63+
),
64+
//language=kotlin
65+
kotlin(
66+
"""
67+
import org.testcontainers.containers.localstack.LocalStackContainer
68+
69+
class A {
70+
val localstack: LocalStackContainer = LocalStackContainer()
71+
.withServices(LocalStackContainer.Service.SQS)
72+
}
73+
""",
74+
"""
75+
import org.testcontainers.containers.localstack.LocalStackContainer
76+
77+
class A {
78+
val localstack: LocalStackContainer = LocalStackContainer()
79+
.withServices("sqs")
80+
}
81+
"""
82+
)
83+
);
84+
}
85+
86+
@Test
87+
void withMultipleServices() {
88+
rewriteRun(
89+
//language=java
90+
java(
91+
"""
92+
import org.testcontainers.containers.localstack.LocalStackContainer;
93+
94+
class A {
95+
LocalStackContainer localstack = new LocalStackContainer()
96+
.withServices(LocalStackContainer.Service.SQS,
97+
LocalStackContainer.Service.SNS,
98+
LocalStackContainer.Service.S3);
99+
}
100+
""",
101+
"""
102+
import org.testcontainers.containers.localstack.LocalStackContainer;
103+
104+
class A {
105+
LocalStackContainer localstack = new LocalStackContainer()
106+
.withServices("sqs",
107+
"sns",
108+
"s3");
109+
}
110+
"""
111+
)
112+
);
113+
}
114+
115+
@Test
116+
void nonTrivialServiceNames() {
117+
rewriteRun(
118+
//language=java
119+
java(
120+
"""
121+
import org.testcontainers.containers.localstack.LocalStackContainer;
122+
123+
class A {
124+
LocalStackContainer localstack = new LocalStackContainer()
125+
.withServices(LocalStackContainer.Service.API_GATEWAY,
126+
LocalStackContainer.Service.DYNAMODB_STREAMS,
127+
LocalStackContainer.Service.CLOUDWATCHLOGS);
128+
}
129+
""",
130+
"""
131+
import org.testcontainers.containers.localstack.LocalStackContainer;
132+
133+
class A {
134+
LocalStackContainer localstack = new LocalStackContainer()
135+
.withServices("apigateway",
136+
"dynamodbstreams",
137+
"logs");
138+
}
139+
"""
140+
)
141+
);
142+
}
143+
144+
@Test
145+
void nonTrivialServiceNamesKotlin() {
146+
rewriteRun(
147+
//language=kotlin
148+
kotlin(
149+
"""
150+
import org.testcontainers.containers.localstack.LocalStackContainer
151+
152+
class A {
153+
val localstack: LocalStackContainer = LocalStackContainer()
154+
.withServices(LocalStackContainer.Service.API_GATEWAY,
155+
LocalStackContainer.Service.DYNAMODB_STREAMS,
156+
LocalStackContainer.Service.CLOUDWATCHLOGS)
157+
}
158+
""",
159+
"""
160+
import org.testcontainers.containers.localstack.LocalStackContainer
161+
162+
class A {
163+
val localstack: LocalStackContainer = LocalStackContainer()
164+
.withServices("apigateway",
165+
"dynamodbstreams",
166+
"logs")
167+
}
168+
"""
169+
)
170+
);
171+
}
172+
}

0 commit comments

Comments
 (0)