Skip to content

Commit 8451c9c

Browse files
committed
feat: flamingock springboot test support foundation
1 parent 862e1b9 commit 8451c9c

File tree

9 files changed

+468
-0
lines changed

9 files changed

+468
-0
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
val springBootVersion = "2.7.18"
2+
3+
dependencies {
4+
// Spring Boot Test
5+
api("org.springframework.boot:spring-boot-test:${springBootVersion}")
6+
api("org.springframework.boot:spring-boot-test-autoconfigure:${springBootVersion}")
7+
8+
// Flamingock Core Test Support
9+
api(project(":core:flamingock-test-support"))
10+
11+
// Test dependencies
12+
testImplementation("org.junit.jupiter:junit-jupiter")
13+
testImplementation("org.springframework.boot:spring-boot-starter:${springBootVersion}")
14+
testImplementation("org.springframework.boot:spring-boot-starter-test:${springBootVersion}")
15+
testImplementation("org.springframework.boot:spring-boot-autoconfigure:${springBootVersion}")
16+
testImplementation("org.mockito:mockito-core")
17+
testImplementation("org.mockito:mockito-inline")
18+
testImplementation(project(":core:flamingock-core"))
19+
testImplementation(project(":core:flamingock-test-support"))
20+
testImplementation(project(":utils:test-util"))
21+
testImplementation(project(":core:target-systems:nontransactional-target-system"))
22+
testImplementation(project(":platform-plugins:flamingock-springboot-integration"))
23+
testImplementation(project(":core:flamingock-core-api"))
24+
}
25+
26+
description = "Test Springboot support module for Flamingock framework"
27+
28+
29+
tasks.withType<JavaCompile>().configureEach {
30+
if (name.contains("Test", ignoreCase = true)) {
31+
options.compilerArgs.addAll(listOf(
32+
"-Asources=${projectDir}/src/test/java",
33+
"-Aresources=${projectDir}/src/test/resources"
34+
))
35+
}
36+
}
37+
38+
java {
39+
toolchain {
40+
languageVersion.set(JavaLanguageVersion.of(8))
41+
}
42+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2025 Flamingock (https://www.flamingock.io)
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+
package io.flamingock.springboot.support;
17+
18+
import org.springframework.boot.test.context.SpringBootTest;
19+
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Meta-annotation for Flamingock Spring Boot integration tests.
27+
*
28+
* <p>This annotation combines {@link SpringBootTest} with Flamingock test infrastructure,
29+
* allowing manual execution control within tests using {@link io.flamingock.support.FlamingockTestSupport}.</p>
30+
*
31+
* <p>The test configuration automatically provides an {@link io.flamingock.internal.core.builder.AbstractChangeRunnerBuilder}
32+
* bean that can be autowired and used with {@code FlamingockTestSupport} for BDD-style testing.</p>
33+
*
34+
* <h2>Usage Example</h2>
35+
* <pre>
36+
* &#64;FlamingockIntegrationTest
37+
* public class MyMigrationTest {
38+
*
39+
* &#64;Autowired
40+
* private AbstractChangeRunnerBuilder&lt;?, ?&gt; builder;
41+
*
42+
* &#64;Test
43+
* &#64;DisplayName("Should apply migration successfully")
44+
* void shouldApplyMigration() {
45+
* FlamingockTestSupport
46+
* .given(builder)
47+
* .andExistingAudit(
48+
* APPLIED(SetupChange.class)
49+
* )
50+
* .whenRun()
51+
* .thenExpectAuditSequenceStrict(
52+
* APPLIED(MyMigrationChange.class)
53+
* )
54+
* .verify();
55+
* }
56+
* }
57+
* </pre>
58+
*
59+
* <h2>Test Infrastructure</h2>
60+
* <p>Your test configuration should provide a builder bean. Example:</p>
61+
* <pre>
62+
* &#64;SpringBootApplication
63+
* public class TestApplication {
64+
* &#64;Bean
65+
* public InMemoryTestKit() {
66+
* return InMemoryTestKit.create();
67+
* }
68+
*
69+
* &#64;Bean
70+
* public AbstractChangeRunnerBuilder&lt;?, ?&gt; flamingockBuilder(InMemoryTestKit testKit) {
71+
* return testKit.createBuilder().addTargetSystem(myTargetSystem());
72+
* }
73+
* }
74+
* </pre>
75+
*
76+
* @see io.flamingock.support.FlamingockTestSupport
77+
* @see SpringBootTest
78+
*/
79+
@Target(ElementType.TYPE)
80+
@Retention(RetentionPolicy.RUNTIME)
81+
@SpringBootTest
82+
public @interface FlamingockIntegrationTest {
83+
84+
/**
85+
* Additional properties to be added to the Spring Environment.
86+
*
87+
* @return property name-value pairs
88+
*/
89+
String[] properties() default {};
90+
91+
/**
92+
* Application configuration classes to use for loading an ApplicationContext.
93+
*
94+
* @return configuration classes
95+
*/
96+
Class<?>[] classes() default {};
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
2+
/*
3+
* Copyright 2025 Flamingock (https://www.flamingock.io)
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package io.flamingock.springboot.support;
18+
19+
import io.flamingock.common.test.pipeline.CodeChangeTestDefinition;
20+
import io.flamingock.common.test.pipeline.PipelineTestHelper;
21+
import io.flamingock.internal.common.core.util.Deserializer;
22+
import io.flamingock.internal.core.builder.AbstractChangeRunnerBuilder;
23+
import io.flamingock.internal.core.runner.PipelineExecutionException;
24+
import io.flamingock.springboot.support.changes.*;
25+
import io.flamingock.support.FlamingockTestSupport;
26+
import org.junit.jupiter.api.DisplayName;
27+
import org.junit.jupiter.api.Test;
28+
import org.mockito.MockedStatic;
29+
import org.mockito.Mockito;
30+
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.core.env.Environment;
32+
import org.springframework.test.annotation.DirtiesContext;
33+
34+
import java.util.Collections;
35+
36+
import static io.flamingock.support.domain.AuditEntryDefinition.*;
37+
import static org.junit.jupiter.api.Assertions.*;
38+
39+
@FlamingockIntegrationTest(classes = TestApplication.class)
40+
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
41+
@DisplayName("Spring Boot Integration - FlamingockIntegrationTest Annotation")
42+
class FlamingockIntegrationTestAnnotationTest {
43+
44+
@Autowired
45+
private AbstractChangeRunnerBuilder<?, ?> builder;
46+
47+
@Autowired
48+
private Environment environment;
49+
50+
@Test
51+
@DisplayName("Should inject Flamingock builder as Spring bean")
52+
void shouldInjectFlamingockBuilder() {
53+
assertNotNull(builder, "Builder should be injected by Spring");
54+
}
55+
56+
@Test
57+
@DisplayName("Should provide test infrastructure via Spring beans")
58+
void shouldProvideTestInfrastructure() {
59+
assertNotNull(builder, "Builder bean should be provided by test configuration");
60+
assertNotNull(environment, "Environment should be available");
61+
}
62+
63+
@Test
64+
@DisplayName("Should execute non-transactional change")
65+
void shouldExecuteNonTransactionalChange() {
66+
try (MockedStatic<Deserializer> mocked = Mockito.mockStatic(Deserializer.class)) {
67+
mocked.when(Deserializer::readPreviewPipelineFromFile).thenReturn(
68+
PipelineTestHelper.getPreviewPipeline(
69+
new CodeChangeTestDefinition(_001__SpringBootTestChange.class, Collections.emptyList())
70+
)
71+
);
72+
73+
FlamingockTestSupport
74+
.given(builder)
75+
.whenRun()
76+
.thenExpectAuditSequenceStrict(
77+
APPLIED(_001__SpringBootTestChange.class)
78+
)
79+
.verify();
80+
}
81+
}
82+
83+
@Test
84+
@DisplayName("Should verify multiple changes execute in correct sequence with complete audit flow")
85+
void shouldVerifyMultipleChangesInSequence() {
86+
try (MockedStatic<Deserializer> mocked = Mockito.mockStatic(Deserializer.class)) {
87+
mocked.when(Deserializer::readPreviewPipelineFromFile).thenReturn(
88+
PipelineTestHelper.getPreviewPipeline(
89+
new CodeChangeTestDefinition(_001__SpringBootTestChange.class, Collections.emptyList()),
90+
new CodeChangeTestDefinition(_002__SpringBootTransactionalChange.class, Collections.emptyList())
91+
)
92+
);
93+
94+
FlamingockTestSupport
95+
.given(builder)
96+
.whenRun()
97+
.thenExpectAuditSequenceStrict(
98+
APPLIED(_001__SpringBootTestChange.class),
99+
APPLIED(_002__SpringBootTransactionalChange.class)
100+
)
101+
.verify();
102+
}
103+
}
104+
105+
@Test
106+
@DisplayName("Should verify failing transactional change triggers rollback with correct audit trail")
107+
void shouldVerifyFailingTransactionalChangeTriggersRollback() {
108+
try (MockedStatic<Deserializer> mocked = Mockito.mockStatic(Deserializer.class)) {
109+
mocked.when(Deserializer::readPreviewPipelineFromFile).thenReturn(
110+
PipelineTestHelper.getPreviewPipeline(
111+
new CodeChangeTestDefinition(_003__SpringBootFailingChange.class, Collections.emptyList(), Collections.emptyList())
112+
)
113+
);
114+
115+
FlamingockTestSupport
116+
.given(builder)
117+
.whenRun()
118+
.thenExpectException(PipelineExecutionException.class, ex ->
119+
assertTrue(ex.getMessage().contains("Intentional test failure"))
120+
)
121+
.andExpectAuditSequenceStrict(
122+
FAILED(_003__SpringBootFailingChange.class),
123+
ROLLED_BACK(_003__SpringBootFailingChange.class)
124+
)
125+
.verify();
126+
}
127+
}
128+
129+
@Test
130+
@DisplayName("Should verify already-applied changes are skipped on subsequent runs")
131+
void shouldVerifyAlreadyAppliedChangesAreSkipped() {
132+
try (MockedStatic<Deserializer> mocked = Mockito.mockStatic(Deserializer.class)) {
133+
mocked.when(Deserializer::readPreviewPipelineFromFile).thenReturn(
134+
PipelineTestHelper.getPreviewPipeline(
135+
new CodeChangeTestDefinition(_001__SpringBootTestChange.class, Collections.emptyList())
136+
)
137+
);
138+
139+
FlamingockTestSupport
140+
.given(builder)
141+
.andExistingAudit(
142+
APPLIED(_001__SpringBootTestChange.class)
143+
)
144+
.whenRun()
145+
.thenExpectAuditSequenceStrict(
146+
APPLIED(_001__SpringBootTestChange.class)
147+
)
148+
.verify();
149+
}
150+
}
151+
152+
@Test
153+
@DisplayName("Should verify transactional change executes successfully with correct audit entries")
154+
void shouldVerifyTransactionalChangeExecutesSuccessfully() {
155+
try (MockedStatic<Deserializer> mocked = Mockito.mockStatic(Deserializer.class)) {
156+
mocked.when(Deserializer::readPreviewPipelineFromFile).thenReturn(
157+
PipelineTestHelper.getPreviewPipeline(
158+
new CodeChangeTestDefinition(_002__SpringBootTransactionalChange.class, Collections.emptyList())
159+
)
160+
);
161+
162+
FlamingockTestSupport
163+
.given(builder)
164+
.whenRun()
165+
.thenExpectAuditSequenceStrict(
166+
APPLIED(_002__SpringBootTransactionalChange.class)
167+
)
168+
.verify();
169+
}
170+
}
171+
}
172+
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2025 Flamingock (https://www.flamingock.io)
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+
package io.flamingock.springboot.support;
17+
18+
import io.flamingock.core.kit.inmemory.InMemoryTestKit;
19+
import io.flamingock.internal.core.builder.AbstractChangeRunnerBuilder;
20+
import io.flamingock.targetsystem.nontransactional.NonTransactionalTargetSystem;
21+
import org.springframework.boot.SpringApplication;
22+
import org.springframework.boot.autoconfigure.SpringBootApplication;
23+
import org.springframework.context.annotation.Bean;
24+
25+
@SpringBootApplication(scanBasePackages = "io.flamingock.springboot.support")
26+
public class TestApplication {
27+
28+
public static void main(String[] args) {
29+
SpringApplication.run(TestApplication.class, args);
30+
}
31+
32+
@Bean
33+
public InMemoryTestKit inMemoryTestKit() {
34+
return InMemoryTestKit.create();
35+
}
36+
37+
@Bean
38+
public AbstractChangeRunnerBuilder<?, ?> flamingockBuilder(InMemoryTestKit testKit) {
39+
return testKit.createBuilder()
40+
.addTargetSystem(kafkaTargetSystem());
41+
}
42+
43+
@Bean
44+
public NonTransactionalTargetSystem kafkaTargetSystem() {
45+
return new NonTransactionalTargetSystem("kafka");
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2025 Flamingock (https://www.flamingock.io)
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+
package io.flamingock.springboot.support.changes;
17+
18+
import io.flamingock.api.annotations.Apply;
19+
import io.flamingock.api.annotations.Change;
20+
import io.flamingock.api.annotations.TargetSystem;
21+
22+
@Change(id = "springboot-test-change-1", transactional = false, author = "test-author")
23+
@TargetSystem(id = "kafka")
24+
public class _001__SpringBootTestChange {
25+
26+
@Apply
27+
public void apply() {
28+
System.out.println("Executing Spring Boot test change 1");
29+
}
30+
}
31+

0 commit comments

Comments
 (0)