|
2 | 2 |
|
3 | 3 | import gov.cdc.nbs.report.pipeline.integration.support.config.DataSourceConfig; |
4 | 4 | import java.io.File; |
5 | | -import java.nio.file.Files; |
6 | | -import java.sql.Connection; |
7 | 5 | import java.time.Duration; |
8 | 6 | import java.util.ArrayList; |
9 | 7 | import java.util.Arrays; |
10 | | -import java.util.Comparator; |
11 | 8 | import java.util.List; |
12 | | -import java.util.Map; |
13 | | -import java.util.Objects; |
14 | 9 | 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; |
25 | 10 | import org.junit.jupiter.api.AfterAll; |
26 | 11 | import org.junit.jupiter.api.BeforeAll; |
27 | 12 | import org.junit.jupiter.api.Tag; |
28 | 13 | import org.junit.jupiter.api.TestInstance; |
29 | 14 | import org.junit.jupiter.api.TestInstance.Lifecycle; |
| 15 | +import org.slf4j.Logger; |
| 16 | +import org.slf4j.LoggerFactory; |
30 | 17 | import org.springframework.beans.factory.annotation.Autowired; |
31 | 18 | import org.springframework.beans.factory.annotation.Qualifier; |
32 | 19 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; |
33 | 20 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; |
34 | 21 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; |
35 | 22 | import org.springframework.context.annotation.Import; |
36 | | -import org.springframework.jdbc.core.JdbcTemplate; |
37 | 23 | import org.springframework.test.context.ActiveProfiles; |
38 | 24 | import org.testcontainers.containers.ComposeContainer; |
39 | 25 | import org.testcontainers.containers.wait.strategy.Wait; |
|
45 | 31 | @Import(DataSourceConfig.class) |
46 | 32 | @AutoConfigureTestDatabase(replace = Replace.NONE) |
47 | 33 | @TestInstance(Lifecycle.PER_CLASS) |
48 | | -@Slf4j |
49 | 34 | public abstract class UnitTest { |
50 | 35 |
|
51 | | - private static boolean started = false; |
| 36 | + private static final Logger log = LoggerFactory.getLogger(UnitTest.class); |
52 | 37 | private static final File base = new File("../docker-compose.yaml"); |
53 | 38 |
|
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; |
61 | 45 |
|
62 | 46 | @Autowired |
63 | 47 | @Qualifier("adminDataSource") |
64 | 48 | private DataSource adminDataSource; |
65 | 49 |
|
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); |
79 | 59 | } |
| 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)); |
80 | 68 | } |
81 | 69 |
|
82 | 70 | @BeforeAll |
83 | 71 | void setUp() throws Exception { |
84 | 72 | if (!started) { |
| 73 | + if (environment == null) { |
| 74 | + initializeEnvironment(); |
| 75 | + } |
85 | 76 | environment.start(); |
86 | 77 | started = true; |
87 | 78 | } |
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"); |
99 | 79 | } |
100 | 80 |
|
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(); |
148 | 86 | } |
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; |
178 | 89 | } |
179 | 90 | } |
180 | 91 | } |
0 commit comments