轻松聘AI开发
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

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;
}
}
}