Skip to content

Commit fd896c6

Browse files
committed
[testtoolkit] 添加MySQL容器测试用例
- 新增IDatabaseMysqlContainerTest测试类 - 验证MySQL容器功能和配置
1 parent 513c4ab commit fd896c6

1 file changed

Lines changed: 181 additions & 0 deletions

File tree

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package io.github.truenine.composeserver.testtoolkit.testcontainers
2+
3+
import jakarta.annotation.Resource
4+
import java.sql.DriverManager
5+
import java.sql.SQLException
6+
import kotlin.test.assertEquals
7+
import kotlin.test.assertFailsWith
8+
import kotlin.test.assertNotNull
9+
import kotlin.test.assertTrue
10+
import org.junit.jupiter.api.Test
11+
import org.springframework.boot.test.context.SpringBootTest
12+
import org.springframework.core.env.Environment
13+
import org.springframework.jdbc.core.JdbcTemplate
14+
15+
@SpringBootTest
16+
class IDatabaseMysqlContainerTest : IDatabaseMysqlContainer {
17+
lateinit var environment: Environment
18+
@Resource set
19+
20+
lateinit var jdbcTemplate: JdbcTemplate
21+
@Resource set
22+
23+
@Test
24+
fun `验证 MySQL 容器成功启动`() {
25+
assertNotNull(mysqlContainer, "MySQL 容器应该存在")
26+
assertTrue(mysqlContainer?.isRunning == true, "MySQL 容器应该处于运行状态")
27+
28+
// 通过执行简单查询来验证容器是否正常工作
29+
val version = jdbcTemplate.queryForObject("SELECT VERSION()", String::class.java)
30+
assertNotNull(version, "应该能够获取 MySQL 版本信息")
31+
assertTrue(version.contains("8.0"), "数据库应该是 MySQL 8.0")
32+
}
33+
34+
@Test
35+
fun `验证 Spring 环境中包含数据源配置`() {
36+
// 验证必要的数据源配置属性是否存在
37+
assertNotNull(environment.getProperty("spring.datasource.url"), "数据源 URL 应该存在")
38+
assertNotNull(environment.getProperty("spring.datasource.username"), "数据源用户名应该存在")
39+
assertNotNull(environment.getProperty("spring.datasource.password"), "数据源密码应该存在")
40+
assertNotNull(environment.getProperty("spring.datasource.driver-class-name"), "数据源驱动类名应该存在")
41+
42+
// 验证 URL 是否指向 TestContainers 的 MySQL
43+
val jdbcUrl = environment.getProperty("spring.datasource.url")
44+
assertTrue(jdbcUrl?.contains("jdbc:mysql") == true, "JDBC URL 应该是 MySQL 连接")
45+
}
46+
47+
@Test
48+
fun `验证数据库连接可以成功建立`() {
49+
val connection = jdbcTemplate.dataSource?.connection
50+
assertNotNull(connection, "应该能够获取数据库连接")
51+
52+
connection.use { conn ->
53+
assertTrue(conn.isValid(5), "数据库连接应该有效")
54+
assertEquals("MySQL", conn.metaData.databaseProductName, "数据库类型应该是 MySQL")
55+
56+
// 验证数据库连接状态
57+
val stmt = conn.createStatement()
58+
val rs = stmt.executeQuery("SELECT CONNECTION_ID() as id")
59+
assertTrue(rs.next(), "应该能够查询到当前连接的ID")
60+
val connectionId = rs.getLong("id")
61+
assertTrue(connectionId > 0, "连接ID应该大于0")
62+
}
63+
}
64+
65+
@Test
66+
fun `验证数据库基本操作正常`() {
67+
// 验证可以执行基本的 SQL 操作
68+
val result = jdbcTemplate.queryForObject("SELECT 1", Int::class.java)
69+
assertEquals(1, result, "应该能够执行基本的 SQL 查询")
70+
71+
// 验证可以创建和删除临时表
72+
jdbcTemplate.execute("CREATE TEMPORARY TABLE test_table (id int)")
73+
74+
// 验证表结构 - 对于临时表,直接通过 DESCRIBE 命令验证
75+
val columnInfo = jdbcTemplate.queryForList("DESCRIBE test_table")
76+
assertTrue(columnInfo.isNotEmpty(), "临时表应该有列定义")
77+
assertTrue(columnInfo.any { it["Field"] == "id" }, "临时表应该包含 id 列")
78+
79+
// 验证表是否可以正常操作
80+
jdbcTemplate.execute("INSERT INTO test_table (id) VALUES (999)")
81+
val tableOperationTest = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM test_table WHERE id = 999", Int::class.java)
82+
assertEquals(1, tableOperationTest, "应该能够向临时表插入数据并查询")
83+
84+
// 验证表的可操作性 - 添加另一条记录并验证总数
85+
jdbcTemplate.execute("INSERT INTO test_table (id) VALUES (1)")
86+
val insertedCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM test_table", Int::class.java)
87+
assertEquals(2, insertedCount, "临时表应该包含所有插入的记录")
88+
89+
// 验证可以删除数据
90+
jdbcTemplate.execute("DELETE FROM test_table WHERE id = 1")
91+
val remainingCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM test_table", Int::class.java)
92+
assertEquals(1, remainingCount, "删除后应该只剩下一条记录")
93+
}
94+
95+
@Test
96+
fun `验证容器端口映射正确`() {
97+
val mappedPort = mysqlContainer?.getMappedPort(3306)
98+
assertNotNull(mappedPort, "MySQL 端口应该被正确映射")
99+
assertTrue(mappedPort > 0, "映射端口应该是有效的端口号")
100+
101+
// 验证端口可访问性
102+
val databaseName = mysqlContainer?.databaseName
103+
assertNotNull(databaseName, "数据库名称不应为空")
104+
105+
val jdbcUrl = "jdbc:mysql://localhost:$mappedPort/$databaseName"
106+
val username = mysqlContainer?.username
107+
val password = mysqlContainer?.password
108+
109+
assertNotNull(username, "数据库用户名不应为空")
110+
assertNotNull(password, "数据库密码不应为空")
111+
112+
DriverManager.getConnection(jdbcUrl, username, password).use { conn ->
113+
assertTrue(conn.isValid(5), "应该能够通过映射端口建立连接")
114+
115+
// 验证连接的数据库名称
116+
assertEquals(databaseName, conn.catalog, "连接的数据库名称应该正确")
117+
118+
// 验证数据库名称格式
119+
assertTrue(databaseName.matches(Regex("^[a-zA-Z_][a-zA-Z0-9_]*$")), "数据库名称应符合标准格式")
120+
121+
// 验证连接属性
122+
assertTrue(conn.metaData.supportsTransactions(), "应支持事务")
123+
assertTrue(conn.metaData.supportsStoredProcedures(), "应支持存储过程")
124+
}
125+
}
126+
127+
@Test
128+
fun `验证无效连接时抛出异常`() {
129+
val invalidJdbcUrl = "jdbc:mysql://localhost:1234/nonexistent"
130+
assertFailsWith<SQLException>("使用无效连接应该抛出异常") { DriverManager.getConnection(invalidJdbcUrl) }
131+
}
132+
133+
@Test
134+
fun `验证数据库字符集配置`() {
135+
val charset = jdbcTemplate.queryForObject("SELECT @@character_set_database", String::class.java)
136+
assertNotNull(charset, "数据库字符集应该存在")
137+
138+
// MySQL 8.0 默认字符集是 utf8mb4
139+
assertTrue(charset.contains("utf8") || charset == "utf8mb4", "数据库字符集应该是 UTF8 相关 (actual: $charset)")
140+
141+
// 验证客户端连接编码
142+
val connectionCharset = jdbcTemplate.queryForObject("SELECT @@character_set_connection", String::class.java)
143+
assertNotNull(connectionCharset, "连接字符集应该存在")
144+
}
145+
146+
@Test
147+
fun `验证数据库时区配置`() {
148+
val timezone = jdbcTemplate.queryForObject("SELECT @@system_time_zone", String::class.java)
149+
assertNotNull(timezone, "数据库时区设置应该存在")
150+
151+
// 验证时区设置不为空
152+
assertTrue(timezone.isNotEmpty(), "时区设置不应为空")
153+
154+
// 验证可以获取当前时间
155+
val currentTime = jdbcTemplate.queryForObject("SELECT NOW()", java.sql.Timestamp::class.java)
156+
assertNotNull(currentTime, "应该能获取当前时间")
157+
158+
// 验证时间的合理性 - 考虑时区差异,允许更大的时间差
159+
val now = System.currentTimeMillis()
160+
val timeDiff = kotlin.math.abs(currentTime.time - now)
161+
// 允许最大24小时的时区差异加上1分钟的执行时间差
162+
val maxAllowedDiff = 24 * 60 * 60 * 1000 + 60000 // 24小时 + 1分钟
163+
assertTrue(timeDiff < maxAllowedDiff, "数据库时间应在合理范围内 (差值: ${timeDiff}ms, 约${timeDiff / 3600000}小时)")
164+
}
165+
166+
@Test
167+
fun `验证 MySQL 特性支持`() {
168+
// 验证 MySQL 版本特性
169+
val version = jdbcTemplate.queryForObject("SELECT VERSION()", String::class.java)
170+
assertNotNull(version, "MySQL 版本信息不应为空")
171+
assertTrue(version!!.startsWith("8.0"), "应该是 MySQL 8.0 版本")
172+
173+
// 验证存储引擎支持
174+
val engines = jdbcTemplate.queryForList("SHOW ENGINES")
175+
assertTrue(engines.any { (it["Engine"] as String).contains("InnoDB") }, "应该支持 InnoDB 存储引擎")
176+
177+
// 验证 SQL 模式
178+
val sqlMode = jdbcTemplate.queryForObject("SELECT @@sql_mode", String::class.java)
179+
assertNotNull(sqlMode, "SQL 模式应该存在")
180+
}
181+
}

0 commit comments

Comments
 (0)