208 lines
9.9 KiB
Java
208 lines
9.9 KiB
Java
package com.kaidi.finance.setup;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.kaidi.setup.SetupApplication;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.sql.Connection;
|
|
import java.sql.DriverManager;
|
|
import java.sql.ResultSet;
|
|
import java.sql.Statement;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.Map;
|
|
import java.util.UUID;
|
|
import org.flywaydb.core.Flyway;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.boot.test.context.SpringBootTest;
|
|
import org.springframework.boot.test.web.client.TestRestTemplate;
|
|
import org.springframework.boot.test.web.server.LocalServerPort;
|
|
import org.springframework.http.HttpEntity;
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.test.context.DynamicPropertyRegistry;
|
|
import org.springframework.test.context.DynamicPropertySource;
|
|
import org.testcontainers.containers.MySQLContainer;
|
|
import org.testcontainers.junit.jupiter.Container;
|
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
|
|
|
@SpringBootTest(classes = SetupApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
|
@Testcontainers(disabledWithoutDocker = true)
|
|
class SetupApplicationIntegrationTest {
|
|
|
|
private static final String SETUP_CODE = "SETUP-CODE-TEST";
|
|
private static final String SETUP_CODE_SHA256 =
|
|
"9158568c96987884c8141d363daceffda86bb17083054b36105934090aa7e166";
|
|
private static final Path STATE_ROOT = Path.of("target", "setup-integration-" + UUID.randomUUID());
|
|
|
|
@Container
|
|
static final MySQLContainer<?> MYSQL = new MySQLContainer<>("mysql:8.4")
|
|
.withDatabaseName("kaidi_finance")
|
|
.withUsername("kaidi")
|
|
.withPassword("setup-database-password");
|
|
|
|
@DynamicPropertySource
|
|
static void properties(DynamicPropertyRegistry registry) {
|
|
registry.add("finance.setup.enabled", () -> true);
|
|
registry.add("finance.setup.token-sha256", () -> SETUP_CODE_SHA256);
|
|
registry.add("finance.setup.env-file", () -> STATE_ROOT.resolve("application.env").toString());
|
|
registry.add("finance.setup.marker-file", () -> STATE_ROOT.resolve("locked").toString());
|
|
registry.add("finance.setup.restart-after-complete", () -> false);
|
|
registry.add("spring.profiles.active", () -> "setup");
|
|
}
|
|
|
|
@Autowired
|
|
private TestRestTemplate rest;
|
|
|
|
@LocalServerPort
|
|
private int port;
|
|
|
|
@Test
|
|
void firstRunTestsDatabaseCreatesAdministratorAndLocksWizard() throws Exception {
|
|
seedRecoverableV068CollationFailure();
|
|
|
|
ResponseEntity<JsonNode> initial = rest.getForEntity(url("/api/v1/setup/status"), JsonNode.class);
|
|
assertThat(initial.getStatusCode()).isEqualTo(HttpStatus.OK);
|
|
assertThat(initial.getBody().path("data").path("required").asBoolean()).isTrue();
|
|
assertThat(initial.getBody().path("data").path("ready").asBoolean()).isFalse();
|
|
|
|
Map<String, Object> database = databaseRequest("wrong-code");
|
|
ResponseEntity<JsonNode> denied = rest.postForEntity(url("/api/v1/setup/test-connection"),
|
|
new HttpEntity<>(database), JsonNode.class);
|
|
assertThat(denied.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
|
assertThat(denied.getBody().path("code").asText()).isEqualTo("SETUP_CODE_INVALID");
|
|
|
|
database.put("setupCode", SETUP_CODE);
|
|
ResponseEntity<JsonNode> tested = rest.postForEntity(url("/api/v1/setup/test-connection"),
|
|
new HttpEntity<>(database), JsonNode.class);
|
|
assertThat(tested.getStatusCode()).isEqualTo(HttpStatus.OK);
|
|
assertThat(tested.getBody().path("data").path("successful").asBoolean()).isTrue();
|
|
assertThat(tested.getBody().path("data").path("schemaReady").asBoolean()).isTrue();
|
|
assertThat(tested.getBody().path("data").path("message").asText())
|
|
.contains("排序规则和完整迁移权限验证通过");
|
|
|
|
try (Connection connection = DriverManager.getConnection(MYSQL.getJdbcUrl(), MYSQL.getUsername(),
|
|
MYSQL.getPassword()); Statement statement = connection.createStatement()) {
|
|
try (ResultSet result = statement.executeQuery("""
|
|
SELECT
|
|
(SELECT COUNT(*) FROM information_schema.tables
|
|
WHERE table_schema = DATABASE() AND table_name LIKE 'kaidi_setup_probe_%')
|
|
+
|
|
(SELECT COUNT(*) FROM information_schema.routines
|
|
WHERE routine_schema = DATABASE() AND routine_name LIKE 'kaidi_setup_probe_%')
|
|
""")) {
|
|
result.next();
|
|
assertThat(result.getInt(1)).isZero();
|
|
}
|
|
try (ResultSet result = statement.executeQuery("""
|
|
SELECT default_collation_name
|
|
FROM information_schema.schemata
|
|
WHERE schema_name = DATABASE()
|
|
""")) {
|
|
result.next();
|
|
assertThat(result.getString(1)).isEqualTo("utf8mb4_0900_ai_ci");
|
|
}
|
|
try (ResultSet result = statement.executeQuery("""
|
|
SELECT COUNT(*)
|
|
FROM flyway_schema_history
|
|
WHERE success = FALSE
|
|
""")) {
|
|
result.next();
|
|
assertThat(result.getInt(1)).isZero();
|
|
}
|
|
}
|
|
|
|
Map<String, Object> complete = new LinkedHashMap<>(database);
|
|
complete.put("adminUsername", "setup-admin");
|
|
complete.put("adminDisplayName", "首次安装管理员");
|
|
complete.put("adminPassword", "SetupAdmin@2026Strong");
|
|
complete.put("adminPasswordConfirmation", "SetupAdmin@2026Strong");
|
|
ResponseEntity<JsonNode> completed = rest.postForEntity(url("/api/v1/setup/complete"),
|
|
new HttpEntity<>(complete), JsonNode.class);
|
|
assertThat(completed.getStatusCode()).isEqualTo(HttpStatus.OK);
|
|
assertThat(completed.getBody().path("data").path("completed").asBoolean()).isTrue();
|
|
|
|
try (Connection connection = DriverManager.getConnection(MYSQL.getJdbcUrl(), MYSQL.getUsername(),
|
|
MYSQL.getPassword()); Statement statement = connection.createStatement()) {
|
|
try (ResultSet result = statement.executeQuery("""
|
|
SELECT COUNT(*)
|
|
FROM iam_user user_account
|
|
JOIN iam_user_role user_role ON user_role.user_id = user_account.id
|
|
JOIN iam_role role ON role.id = user_role.role_id
|
|
WHERE user_account.username = 'setup-admin' AND role.code = 'SYSTEM_ADMIN'
|
|
""")) {
|
|
result.next();
|
|
assertThat(result.getInt(1)).isEqualTo(1);
|
|
}
|
|
try (ResultSet result = statement.executeQuery("""
|
|
SELECT COUNT(*)
|
|
FROM iam_scope scope_grant
|
|
JOIN iam_user user_account ON user_account.id = scope_grant.user_id
|
|
WHERE user_account.username = 'setup-admin' AND scope_grant.scope_type = 'GLOBAL'
|
|
""")) {
|
|
result.next();
|
|
assertThat(result.getInt(1)).isGreaterThan(0);
|
|
}
|
|
}
|
|
|
|
String environment = Files.readString(STATE_ROOT.resolve("application.env"));
|
|
assertThat(environment).contains("FINANCE_SETUP_ENABLED=false")
|
|
.contains("DB_URL=\"jdbc:mysql://")
|
|
.contains("DB_USERNAME=\"" + MYSQL.getUsername() + "\"")
|
|
.doesNotContain("SetupAdmin@2026Strong")
|
|
.doesNotContain(SETUP_CODE);
|
|
assertThat(Files.exists(STATE_ROOT.resolve("locked"))).isTrue();
|
|
|
|
ResponseEntity<JsonNode> locked = rest.getForEntity(url("/api/v1/setup/status"), JsonNode.class);
|
|
assertThat(locked.getBody().path("data").path("required").asBoolean()).isFalse();
|
|
assertThat(locked.getBody().path("data").path("locked").asBoolean()).isTrue();
|
|
assertThat(locked.getBody().path("data").path("ready").asBoolean()).isFalse();
|
|
}
|
|
|
|
private void seedRecoverableV068CollationFailure() throws Exception {
|
|
try (Connection connection = DriverManager.getConnection(MYSQL.getJdbcUrl(), MYSQL.getUsername(),
|
|
MYSQL.getPassword()); Statement statement = connection.createStatement()) {
|
|
statement.execute("ALTER DATABASE `" + MYSQL.getDatabaseName()
|
|
+ "` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci");
|
|
}
|
|
assertThatThrownBy(() -> Flyway.configure()
|
|
.dataSource(MYSQL.getJdbcUrl(), MYSQL.getUsername(), MYSQL.getPassword())
|
|
.locations("classpath:db/migration")
|
|
.target("68")
|
|
.load()
|
|
.migrate())
|
|
.hasMessageContaining("V068__source_table_column_validation_metadata.sql");
|
|
try (Connection connection = DriverManager.getConnection(MYSQL.getJdbcUrl(), MYSQL.getUsername(),
|
|
MYSQL.getPassword()); Statement statement = connection.createStatement();
|
|
ResultSet result = statement.executeQuery("""
|
|
SELECT COUNT(*)
|
|
FROM flyway_schema_history
|
|
WHERE version = '68'
|
|
AND script = 'V068__source_table_column_validation_metadata.sql'
|
|
AND success = FALSE
|
|
""")) {
|
|
result.next();
|
|
assertThat(result.getInt(1)).isEqualTo(1);
|
|
}
|
|
}
|
|
|
|
private Map<String, Object> databaseRequest(String setupCode) {
|
|
Map<String, Object> request = new LinkedHashMap<>();
|
|
request.put("setupCode", setupCode);
|
|
request.put("databaseType", "MYSQL");
|
|
request.put("host", MYSQL.getHost());
|
|
request.put("port", MYSQL.getFirstMappedPort());
|
|
request.put("database", MYSQL.getDatabaseName());
|
|
request.put("username", MYSQL.getUsername());
|
|
request.put("password", MYSQL.getPassword());
|
|
return request;
|
|
}
|
|
|
|
private String url(String path) {
|
|
return "http://127.0.0.1:" + port + path;
|
|
}
|
|
}
|