forked from baskbuddy/zhangshun
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
11 KiB
200 lines
11 KiB
package com.mydreamjob.integration;
|
|
|
|
import org.flywaydb.core.Flyway;
|
|
import org.flywaydb.core.api.MigrationVersion;
|
|
import org.flywaydb.core.api.output.MigrateResult;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.testcontainers.DockerClientFactory;
|
|
import org.testcontainers.containers.MySQLContainer;
|
|
|
|
import java.sql.Connection;
|
|
import java.sql.DriverManager;
|
|
import java.sql.ResultSet;
|
|
import java.sql.Statement;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
|
|
|
/**
|
|
* V57 展示字段在**真实 MySQL** 上的迁移验证(隔离容器,容器/库名带 b01 前缀)。
|
|
*
|
|
* <p>逐项覆盖:数据库版本记录;从 V56 基线升级到 V57;旧数据保留、新列为 NULL;
|
|
* 新字段写入与重新查询;更新清空语义(NULL);重复迁移无待执行项且 V57 只有一条历史;
|
|
* 空库(独立容器)从零完整迁移到 V57。</p>
|
|
*
|
|
* <p>连接统一追加 {@code characterEncoding=UTF-8},避免 MySQL 5.7 服务器默认字符集导致的种子中文写入失败。
|
|
* Docker 不可用时环境性跳过(不宣称通过)。</p>
|
|
*/
|
|
class JobCompanyDisplayFieldsMysqlMigrationTest {
|
|
|
|
private static final String MIG = "classpath:db/migration";
|
|
|
|
@Test
|
|
void mysql57StagedUpgradeAndEmptyDatabase() throws Exception {
|
|
verifyMigration(System.getProperty("b01.mysql.image57", "mysql:5.7.36"), "57");
|
|
}
|
|
|
|
@Test
|
|
void mysql8StagedUpgradeAndEmptyDatabase() throws Exception {
|
|
verifyMigration(System.getProperty("b01.mysql.image8", "mysql:8.4"), "8");
|
|
}
|
|
|
|
private void verifyMigration(String image, String tag) throws Exception {
|
|
assumeTrue(dockerAvailable(), "Docker 不可用,跳过真实 MySQL 迁移验证");
|
|
|
|
String version;
|
|
// ── 阶段一:V56 基线 → 旧数据 → 升级 V57 ──────────────────
|
|
try (MySQLContainer<?> mysql = new MySQLContainer<>(image)
|
|
.withDatabaseName("mydreamjob_b01_" + tag)
|
|
.withUsername("b01")
|
|
.withPassword("b01-test-only")) {
|
|
mysql.start();
|
|
String url = charset(mysql.getJdbcUrl());
|
|
String user = mysql.getUsername();
|
|
String pass = mysql.getPassword();
|
|
// MySQL 5.7 服务器默认字符集为 latin1;与真实部署一致,先把库默认字符集设为 utf8mb4,
|
|
// 使未显式指定字符集的迁移建表(如 V37)可写入中文种子数据。
|
|
ensureUtf8mb4(url, user, pass, mysql.getDatabaseName());
|
|
|
|
version = scalar(url, user, pass, "SELECT VERSION()");
|
|
System.out.println("[B01][" + image + "] SELECT VERSION() = " + version);
|
|
assertNotNull(version);
|
|
|
|
// 1) 只迁移到 V56 基线
|
|
MigrateResult to56 = flyway(url, user, pass, "56").migrate();
|
|
assertTrue(to56.success, "迁移到 V56 应成功");
|
|
assertEquals(0, count(url, user, pass,
|
|
"SELECT count(*) FROM flyway_schema_history WHERE version = '57'"), "V56 基线不应含 V57");
|
|
|
|
// 2) 旧企业与旧岗位数据
|
|
try (Connection c = DriverManager.getConnection(url, user, pass); Statement st = c.createStatement()) {
|
|
st.executeUpdate("INSERT INTO users(phone, role, is_active, is_verified) "
|
|
+ "VALUES('19900009001','COMPANY',1,1)");
|
|
st.executeUpdate("INSERT INTO companies(owner_user_id, name, description, industry, verified) "
|
|
+ "SELECT id,'B01旧企业','旧简介','旧行业',1 FROM users WHERE phone='19900009001'");
|
|
st.executeUpdate("INSERT INTO jobs(company_id, title, description, requirements, city, "
|
|
+ "employment_type, salary_min, salary_max, status) SELECT c.id,'B01旧岗位','旧描述','旧要求',"
|
|
+ "'北京','FULL_TIME',10000,20000,'PUBLISHED' FROM companies c WHERE c.name='B01旧企业'");
|
|
}
|
|
|
|
// 3) 升级到 V57
|
|
MigrateResult latest = flyway(url, user, pass, "57").migrate();
|
|
assertTrue(latest.success, "升级到 V57 应成功");
|
|
assertEquals(1, latest.migrationsExecuted, "从 V56 升级应只执行一条迁移(V57)");
|
|
|
|
// 4) 旧数据保留、新列为 NULL
|
|
assertEquals(1, count(url, user, pass, "SELECT count(*) FROM companies WHERE name='B01旧企业'"));
|
|
assertEquals(1, count(url, user, pass, "SELECT count(*) FROM jobs WHERE title='B01旧岗位'"));
|
|
assertNull(scalar(url, user, pass, "SELECT company_size FROM companies WHERE name='B01旧企业'"));
|
|
assertNull(scalar(url, user, pass, "SELECT experience FROM jobs WHERE title='B01旧岗位'"));
|
|
assertNull(scalar(url, user, pass, "SELECT benefits_json FROM jobs WHERE title='B01旧岗位'"));
|
|
assertNull(scalar(url, user, pass, "SELECT salary_months FROM jobs WHERE title='B01旧岗位'"));
|
|
assertNull(scalar(url, user, pass, "SELECT work_location FROM jobs WHERE title='B01旧岗位'"));
|
|
|
|
// 5) 新增字段写入 + 重新查询 + 更新清空语义(NULL)
|
|
try (Connection c = DriverManager.getConnection(url, user, pass); Statement st = c.createStatement()) {
|
|
st.executeUpdate("UPDATE companies SET company_size='100-499人' WHERE name='B01旧企业'");
|
|
st.executeUpdate("UPDATE jobs SET experience='3-5年', education='本科', "
|
|
+ "benefits_json='[\"五险一金\",\"弹性工作\"]', salary_months=14, work_location='上海市浦东新区' "
|
|
+ "WHERE title='B01旧岗位'");
|
|
}
|
|
assertEquals("100-499人", scalar(url, user, pass,
|
|
"SELECT company_size FROM companies WHERE name='B01旧企业'"));
|
|
assertEquals("[\"五险一金\",\"弹性工作\"]", scalar(url, user, pass,
|
|
"SELECT benefits_json FROM jobs WHERE title='B01旧岗位'"));
|
|
assertEquals("14", scalar(url, user, pass,
|
|
"SELECT salary_months FROM jobs WHERE title='B01旧岗位'"));
|
|
|
|
try (Connection c = DriverManager.getConnection(url, user, pass); Statement st = c.createStatement()) {
|
|
st.executeUpdate("UPDATE companies SET company_size=NULL WHERE name='B01旧企业'");
|
|
st.executeUpdate("UPDATE jobs SET benefits_json=NULL, salary_months=NULL WHERE title='B01旧岗位'");
|
|
}
|
|
assertNull(scalar(url, user, pass, "SELECT company_size FROM companies WHERE name='B01旧企业'"));
|
|
assertNull(scalar(url, user, pass, "SELECT benefits_json FROM jobs WHERE title='B01旧岗位'"));
|
|
assertNull(scalar(url, user, pass, "SELECT salary_months FROM jobs WHERE title='B01旧岗位'"));
|
|
|
|
// 6) 重复迁移无待执行项;V57 只有一条历史且成功;新增列不重复
|
|
MigrateResult again = flyway(url, user, pass, "57").migrate();
|
|
assertEquals(0, again.migrationsExecuted, "再次迁移应无待执行项");
|
|
assertEquals(1, count(url, user, pass,
|
|
"SELECT count(*) FROM flyway_schema_history WHERE version='57' AND success=1"),
|
|
"flyway_schema_history 中 V57 应只有一条");
|
|
assertEquals(6, count(url, user, pass, newColumnCountSql()), "新增 6 列应恰好各一列,不重复增加");
|
|
|
|
System.out.println("[B01][" + image + "] staged upgrade verified: version=" + version
|
|
+ ", to56=" + to56.migrationsExecuted + ", upgrade=" + latest.migrationsExecuted
|
|
+ ", rerun=" + again.migrationsExecuted);
|
|
}
|
|
|
|
// ── 阶段二:空库(独立容器)从零完整迁移到 V57 ────────────
|
|
try (MySQLContainer<?> empty = new MySQLContainer<>(image)
|
|
.withDatabaseName("mydreamjob_b01_empty_" + tag)
|
|
.withUsername("b01")
|
|
.withPassword("b01-test-only")) {
|
|
empty.start();
|
|
String url = charset(empty.getJdbcUrl());
|
|
String user = empty.getUsername();
|
|
String pass = empty.getPassword();
|
|
ensureUtf8mb4(url, user, pass, empty.getDatabaseName());
|
|
|
|
MigrateResult fromScratch = flyway(url, user, pass, "57").migrate();
|
|
assertTrue(fromScratch.success, "空库应能完整迁移到 V57");
|
|
assertEquals("57", scalar(url, user, pass,
|
|
"SELECT version FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 1"),
|
|
"空库迁移后的最高版本应为 57");
|
|
assertEquals(6, count(url, user, pass, newColumnCountSql()), "空库迁移后新增 6 列存在");
|
|
System.out.println("[B01][" + image + "] empty-db migration verified: executed="
|
|
+ fromScratch.migrationsExecuted);
|
|
}
|
|
}
|
|
|
|
private static String newColumnCountSql() {
|
|
return "SELECT count(*) FROM information_schema.columns "
|
|
+ "WHERE table_schema = DATABASE() AND ((table_name='companies' AND column_name='company_size') "
|
|
+ "OR (table_name='jobs' AND column_name IN ('experience','education','benefits_json',"
|
|
+ "'salary_months','work_location')))";
|
|
}
|
|
|
|
private static String charset(String url) {
|
|
return url + (url.contains("?") ? "&" : "?") + "useUnicode=true&characterEncoding=UTF-8";
|
|
}
|
|
|
|
private static void ensureUtf8mb4(String url, String user, String pass, String database) throws Exception {
|
|
try (Connection c = DriverManager.getConnection(url, user, pass); Statement st = c.createStatement()) {
|
|
st.executeUpdate("ALTER DATABASE `" + database + "` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
|
}
|
|
}
|
|
|
|
private Flyway flyway(String url, String user, String pass, String target) {
|
|
var cfg = Flyway.configure().dataSource(url, user, pass).locations(MIG);
|
|
// 与生产启动路径一致:应用 MySQL 5.7 兼容钩子(仅替换 V49;MySQL 8/H2 下为空操作)。
|
|
com.mydreamjob.config.MySql57MigrationConfiguration.configure(cfg);
|
|
if (target != null) {
|
|
cfg = cfg.target(MigrationVersion.fromVersion(target));
|
|
}
|
|
return cfg.load();
|
|
}
|
|
|
|
private String scalar(String url, String user, String pass, String sql) throws Exception {
|
|
try (Connection c = DriverManager.getConnection(url, user, pass);
|
|
Statement st = c.createStatement();
|
|
ResultSet rs = st.executeQuery(sql)) {
|
|
return rs.next() ? rs.getString(1) : null;
|
|
}
|
|
}
|
|
|
|
private int count(String url, String user, String pass, String sql) throws Exception {
|
|
return Integer.parseInt(scalar(url, user, pass, sql));
|
|
}
|
|
|
|
private boolean dockerAvailable() {
|
|
try {
|
|
return DockerClientFactory.instance().isDockerAvailable();
|
|
} catch (Throwable ignored) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|