Skip to content

Commit ca80857

Browse files
committed
test(pipeline): use containerized Liquibase for unit tests
Refactor the `UnitTest` base class to utilize the `liquibase` container for database migrations and onboarding, aligning its behavior with `FunctionalTest`. This change removes the need for manual Liquibase orchestration within the test code. - Update `FunctionalTest` wait strategies to include SQL Server healthchecks and Liquibase migration completion logs. - Modify `DataSourceConfig` to throw a `RuntimeException` instead of logging a warning when a custom compose file is not found. - Simplify `UnitTest` by removing manual migration and onboarding script execution logic. - Standardize environment initialization and container management across unit and functional test suites.
1 parent 5693a21 commit ca80857

3 files changed

Lines changed: 48 additions & 138 deletions

File tree

reporting-pipeline-service/src/test/java/gov/cdc/nbs/report/pipeline/integration/functional/FunctionalTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,34 +24,34 @@
2424
@Tag("Functional")
2525
public abstract class FunctionalTest {
2626

27-
private static final Logger logger = LoggerFactory.getLogger(FunctionalTest.class);
28-
private static final Slf4jLogConsumer consumer = new Slf4jLogConsumer(logger);
29-
private static boolean started = false;
30-
27+
private static final Logger log = LoggerFactory.getLogger(FunctionalTest.class);
28+
private static final Slf4jLogConsumer consumer = new Slf4jLogConsumer(log);
3129
private static final File base = new File("../docker-compose.yaml");
3230

31+
private static boolean started = false;
3332
private static ComposeContainer environment;
3433

3534
@Autowired
3635
@Qualifier("customComposeFile")
37-
private static File customComposeFile;
36+
private File customComposeFile;
3837

3938
@SuppressWarnings("resource")
40-
static void initializeEnvironment() {
41-
DockerImageName dockerImage = DockerImageName.parse("docker:25.0.5");
39+
void initializeEnvironment() {
4240
List<File> composeFiles = new ArrayList<>(Arrays.asList(base));
4341
if (customComposeFile != null) {
44-
logger.info(
42+
log.info(
4543
"Using custom compose override. Base: {}, Custom: {}",
4644
base.getPath(),
4745
customComposeFile.getPath());
4846
composeFiles.add(customComposeFile);
4947
}
5048
environment =
51-
new ComposeContainer(dockerImage, composeFiles)
49+
new ComposeContainer(DockerImageName.parse("docker:25.0.5"), composeFiles)
5250
// List specific services to prevent launching wildfly container
5351
.withServices("nbs-mssql", "liquibase", "kafka", "debezium", "kafka-connect")
52+
.waitingFor("nbs-mssql", Wait.forHealthcheck())
5453
.waitingFor("debezium", Wait.forHealthcheck())
54+
.waitingFor("liquibase", Wait.forLogMessage(".*Migrations complete.*\\n", 1))
5555
.waitingFor("kafka-connect", Wait.forHealthcheck())
5656
// Pull logs from the containers for better debugging
5757
.withLogConsumer("nbs-mssql", consumer)
@@ -64,7 +64,7 @@ static void initializeEnvironment() {
6464
}
6565

6666
@BeforeAll
67-
static void setUp() {
67+
void setUp() {
6868
// Start up necessary containers if they are not already running.
6969
// ComposeContainer does not allow container reuse natively
7070
if (!started) {
@@ -77,7 +77,7 @@ static void setUp() {
7777
}
7878

7979
@AfterAll
80-
static void tearDown() {
80+
void tearDown() {
8181
if (started) {
8282
if (environment == null) {
8383
initializeEnvironment();

reporting-pipeline-service/src/test/java/gov/cdc/nbs/report/pipeline/integration/support/config/DataSourceConfig.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ public File customFunctionalCompose(Environment env) {
7676
return parentDirFile;
7777
}
7878

79-
log.warn("Custom compose file '{}' not found in current or parent directory.", filePath);
80-
return null;
79+
throw new RuntimeException("Custom compose file '%s' could not be found.".formatted(filePath));
8180
}
8281
}

reporting-pipeline-service/src/test/java/gov/cdc/nbs/report/pipeline/integration/unit/UnitTest.java

Lines changed: 36 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,24 @@
22

33
import gov.cdc.nbs.report.pipeline.integration.support.config.DataSourceConfig;
44
import java.io.File;
5-
import java.nio.file.Files;
6-
import java.sql.Connection;
75
import java.time.Duration;
86
import java.util.ArrayList;
97
import java.util.Arrays;
10-
import java.util.Comparator;
118
import java.util.List;
12-
import java.util.Map;
13-
import java.util.Objects;
149
import javax.sql.DataSource;
15-
import liquibase.Scope;
16-
import liquibase.command.CommandScope;
17-
import liquibase.command.core.UpdateCommandStep;
18-
import liquibase.database.Database;
19-
import liquibase.database.DatabaseFactory;
20-
import liquibase.database.jvm.JdbcConnection;
21-
import liquibase.resource.CompositeResourceAccessor;
22-
import liquibase.resource.DirectoryResourceAccessor;
23-
import liquibase.resource.ResourceAccessor;
24-
import lombok.extern.slf4j.Slf4j;
2510
import org.junit.jupiter.api.AfterAll;
2611
import org.junit.jupiter.api.BeforeAll;
2712
import org.junit.jupiter.api.Tag;
2813
import org.junit.jupiter.api.TestInstance;
2914
import org.junit.jupiter.api.TestInstance.Lifecycle;
15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
3017
import org.springframework.beans.factory.annotation.Autowired;
3118
import org.springframework.beans.factory.annotation.Qualifier;
3219
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
3320
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
3421
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
3522
import org.springframework.context.annotation.Import;
36-
import org.springframework.jdbc.core.JdbcTemplate;
3723
import org.springframework.test.context.ActiveProfiles;
3824
import org.testcontainers.containers.ComposeContainer;
3925
import org.testcontainers.containers.wait.strategy.Wait;
@@ -45,136 +31,61 @@
4531
@Import(DataSourceConfig.class)
4632
@AutoConfigureTestDatabase(replace = Replace.NONE)
4733
@TestInstance(Lifecycle.PER_CLASS)
48-
@Slf4j
4934
public abstract class UnitTest {
5035

51-
private static boolean started = false;
36+
private static final Logger log = LoggerFactory.getLogger(UnitTest.class);
5237
private static final File base = new File("../docker-compose.yaml");
5338

54-
@SuppressWarnings("resource")
55-
private static final ComposeContainer environment =
56-
new ComposeContainer(DockerImageName.parse("docker:25.0.5"), base)
57-
.withServices("nbs-mssql")
58-
.waitingFor("nbs-mssql", Wait.forHealthcheck())
59-
// Set the maximum startup timeout all the waits set are bounded to
60-
.withStartupTimeout(Duration.ofMinutes(5));
39+
private static boolean started = false;
40+
private static ComposeContainer environment;
41+
42+
@Autowired
43+
@Qualifier("customComposeFile")
44+
private File customComposeFile;
6145

6246
@Autowired
6347
@Qualifier("adminDataSource")
6448
private DataSource adminDataSource;
6549

66-
// The root from which ALL db files (changelogs and sql) can be found
67-
private static final String RESOURCE_ROOT = "../liquibase-service/src/main/resources";
68-
69-
// Relative paths from RESOURCE_ROOT
70-
private static final String MIGRATION_DIR = RESOURCE_ROOT + "/db";
71-
private static final String ONBOARDING_DIR =
72-
RESOURCE_ROOT + "/db/001-master/02_onboarding_script_data_load";
73-
74-
@AfterAll
75-
void tearDown() throws Exception {
76-
if (started) {
77-
environment.stop();
78-
started = false;
50+
@SuppressWarnings("resource")
51+
void initializeEnvironment() {
52+
List<File> composeFiles = new ArrayList<>(Arrays.asList(base));
53+
if (customComposeFile != null) {
54+
log.warn(
55+
"Using custom compose override. Base: {}, Custom: {}",
56+
base.getPath(),
57+
customComposeFile.getPath());
58+
composeFiles.add(customComposeFile);
7959
}
60+
environment =
61+
new ComposeContainer(DockerImageName.parse("docker:25.0.5"), composeFiles)
62+
.withServices("nbs-mssql")
63+
.waitingFor("nbs-mssql", Wait.forHealthcheck())
64+
.withServices("liquibase")
65+
.waitingFor("liquibase", Wait.forLogMessage(".*Migrations complete.*\\n", 1))
66+
// Set the maximum startup timeout all the waits set are bounded to
67+
.withStartupTimeout(Duration.ofMinutes(5));
8068
}
8169

8270
@BeforeAll
8371
void setUp() throws Exception {
8472
if (!started) {
73+
if (environment == null) {
74+
initializeEnvironment();
75+
}
8576
environment.start();
8677
started = true;
8778
}
88-
// checkDatabaseConnection();
89-
log.info("Starting Migration Phase...");
90-
91-
applyMigration("NBS_ODSE", "db.odse.admin.tasks.changelog-16.1.yaml");
92-
applyMigration("NBS_ODSE", "db.odse.changelog-16.1.yaml");
93-
applyMigration("NBS_SRTE", "db.srte.admin.tasks.changelog-16.1.yaml");
94-
applyMigration("rdb_modern", "db.rdb.changelog-16.1.yaml");
95-
applyMigration("rdb_modern", "db.rdb_modern.changelog-16.1.yaml");
96-
97-
log.info("Starting Onboarding Phase...");
98-
applyOnboardingScripts("NBS_ODSE");
9979
}
10080

101-
private void applyMigration(String dbName, String changelogFile) throws Exception {
102-
try (Connection connection = adminDataSource.getConnection()) {
103-
connection.setCatalog(dbName);
104-
105-
// 1. Setup the Database wrapper
106-
Database database =
107-
DatabaseFactory.getInstance()
108-
.findCorrectDatabaseImplementation(new JdbcConnection(connection));
109-
110-
// 2. Build the recursive Search Path (Accessors)
111-
// This is necessary as all the migrations are organized in nested
112-
// subdirectories, but the changelog files only reference the migrations
113-
// by their filename, and not their relative path.
114-
List<DirectoryResourceAccessor> accessors = new ArrayList<>();
115-
accessors.add(new DirectoryResourceAccessor(new File(RESOURCE_ROOT)));
116-
try (var stream = Files.walk(new File(MIGRATION_DIR).toPath())) {
117-
stream
118-
.filter(Files::isDirectory)
119-
.map(
120-
path -> {
121-
try {
122-
return new DirectoryResourceAccessor(path.toFile());
123-
} catch (Exception e) {
124-
return null;
125-
}
126-
})
127-
.filter(Objects::nonNull)
128-
.forEach(accessors::add);
129-
}
130-
131-
// 3. Execute the Command
132-
log.debug("Applying: " + changelogFile + " to " + dbName);
133-
try (var compositeAccessor =
134-
new CompositeResourceAccessor(accessors.toArray(ResourceAccessor[]::new))) {
135-
Scope.child(
136-
Map.of(Scope.Attr.resourceAccessor.name(), compositeAccessor),
137-
() -> {
138-
new CommandScope(UpdateCommandStep.COMMAND_NAME)
139-
.addArgumentValue(
140-
UpdateCommandStep.CHANGELOG_FILE_ARG, "db/changelog/" + changelogFile)
141-
.addArgumentValue("database", database)
142-
.execute();
143-
});
144-
} finally {
145-
if (database != null) {
146-
database.close();
147-
}
81+
@AfterAll
82+
void tearDown() throws Exception {
83+
if (started) {
84+
if (environment == null) {
85+
initializeEnvironment();
14886
}
149-
}
150-
}
151-
152-
private void applyOnboardingScripts(String dbName) throws Exception {
153-
File onboardingDir = new File(ONBOARDING_DIR);
154-
155-
if (!onboardingDir.exists()) {
156-
throw new RuntimeException(
157-
"Onboarding directory not found: " + onboardingDir.getAbsolutePath());
158-
}
159-
160-
File[] sqlFiles = onboardingDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".sql"));
161-
if (sqlFiles == null || sqlFiles.length == 0) {
162-
log.error("No SQL files found in onboarding directory: {}", ONBOARDING_DIR);
163-
return;
164-
}
165-
Arrays.sort(sqlFiles, Comparator.comparing(File::getName));
166-
167-
// Create a local JdbcTemplate to gain access to the low-level .execute() method
168-
JdbcTemplate jdbcTemplate = new JdbcTemplate(adminDataSource);
169-
170-
for (File sqlFile : sqlFiles) {
171-
log.debug("Executing SQL: " + sqlFile.getName());
172-
String content = Files.readString(sqlFile.toPath());
173-
174-
Arrays.stream(content.split("(?i)\\bGO\\b"))
175-
.map(String::trim)
176-
.filter(batch -> !batch.isEmpty())
177-
.forEach(batch -> jdbcTemplate.execute(batch));
87+
environment.stop();
88+
started = false;
17889
}
17990
}
18091
}

0 commit comments

Comments
 (0)