From 1e3da94e2da4abb815d8a9579551b45b1839e63c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Sun, 27 Jul 2025 16:44:43 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=8E=A8=20[docs]=20Update=20and=20simp?= =?UTF-8?q?lify=20MCP=20documentation=20and=20add=20.mcp.json=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/mcp.json => .mcp.json | 10 +++++++--- docs/mcp.md | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) rename docs/mcp.json => .mcp.json (77%) create mode 100644 docs/mcp.md diff --git a/docs/mcp.json b/.mcp.json similarity index 77% rename from docs/mcp.json rename to .mcp.json index c555cb194..8d678cc4c 100644 --- a/docs/mcp.json +++ b/.mcp.json @@ -1,7 +1,6 @@ { "mcpServers": { "context7": { - "type": "stdio", "command": "npx", "args": [ "-y", @@ -9,7 +8,6 @@ ] }, "Sequential-thinking": { - "type": "stdio", "command": "npx", "args": [ "-y", @@ -17,12 +15,18 @@ ] }, "broswertools": { - "type": "stdio", "command": "npx", "args": [ "-y", "@browsermcp/mcp@latest" ] + }, + "Playwright": { + "command": "npx", + "args": [ + "-y", + "@playwright/mcp@latest" + ] } } } diff --git a/docs/mcp.md b/docs/mcp.md new file mode 100644 index 000000000..f696d27b0 --- /dev/null +++ b/docs/mcp.md @@ -0,0 +1,16 @@ +# ALL MCP + +## integrate to claude code + +add `.mcp.json` to your project root + +```json +{ + "mcpServers": { + "context7": {"command": "npx", "args": ["-y", "@upstash/context7-mcp@latest"]}, + "Sequential-thinking": {"command": "npx", "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"]}, + "broswertools": {"command": "npx", "args": ["-y", "@browsermcp/mcp@latest"]}, + "Playwright": {"command": "npx", "args": ["-y", "@playwright/mcp@latest"]} + } +} +``` From e07f42593ac3f69349962ce915b1bd215a39f188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Tue, 29 Jul 2025 00:24:01 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=20[testtoolkit]=20=E6=B7=BB=E5=8A=A0MySQL?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=9A=84=E6=9E=84=E5=BB=BA=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加mysql testcontainers依赖到版本目录 - 更新testtoolkit构建脚本支持MySQL - 优化Kotlin Spring约定插件使用kapt --- .../kotlin/buildlogic.kotlinspring-conventions.gradle.kts | 5 +++-- gradle/libs.versions.toml | 1 + testtoolkit/build.gradle.kts | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/build-logic/src/main/kotlin/buildlogic.kotlinspring-conventions.gradle.kts b/build-logic/src/main/kotlin/buildlogic.kotlinspring-conventions.gradle.kts index 1bda9f10d..1312eabb4 100644 --- a/build-logic/src/main/kotlin/buildlogic.kotlinspring-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/buildlogic.kotlinspring-conventions.gradle.kts @@ -1,12 +1,13 @@ val libs = the() plugins { - id("buildlogic.javaspring-conventions") + id("buildlogic.javaspring-conventions") id("buildlogic.kotlin-conventions") kotlin("plugin.spring") + kotlin("kapt") } dependencies { - annotationProcessor(libs.org.springframework.boot.spring.boot.configuration.processor) + kapt(libs.org.springframework.boot.spring.boot.configuration.processor) } // 配置 jar 任务包含 LICENSE 文件 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 55cc68379..50398cedb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -305,6 +305,7 @@ org-springframework-spring-webflux = { module = "org.springframework:spring-webf org-springframework-spring-webmvc = { module = "org.springframework:spring-webmvc", version.ref = "org-springframework-framework" } org-testcontainers-junit-jupiter = { module = "org.testcontainers:junit-jupiter", version.ref = "org-testcontainers" } org-testcontainers-minio = { module = "org.testcontainers:minio", version.ref = "org-testcontainers" } +org-testcontainers-mysql = { module = "org.testcontainers:mysql", version.ref = "org-testcontainers" } org-testcontainers-postgresql = { module = "org.testcontainers:postgresql", version.ref = "org-testcontainers" } org-testcontainers-testcontainers = { module = "org.testcontainers:testcontainers", version.ref = "org-testcontainers" } org-testcontainers-testcontainers-bom = { module = "org.testcontainers:testcontainers-bom", version.ref = "org-testcontainers" } diff --git a/testtoolkit/build.gradle.kts b/testtoolkit/build.gradle.kts index f9c364ba8..a23e65ea5 100644 --- a/testtoolkit/build.gradle.kts +++ b/testtoolkit/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { api(libs.org.testcontainers.testcontainers) api(libs.org.testcontainers.postgresql) + api(libs.org.testcontainers.mysql) api(libs.org.testcontainers.junit.jupiter) api(libs.org.testcontainers.minio) @@ -61,12 +62,15 @@ dependencies { runtimeOnly(libs.org.springframework.boot.spring.boot.starter.logging) testImplementation(libs.org.testcontainers.postgresql) + testImplementation(libs.org.testcontainers.mysql) testImplementation(libs.org.springframework.boot.spring.boot.starter.jdbc) testImplementation(libs.io.minio.minio) testRuntimeOnly(libs.org.postgresql.postgresql) + testRuntimeOnly(libs.com.mysql.mysql.connector.j) testImplementation(libs.org.testcontainers.junit.jupiter) testImplementation(libs.org.testcontainers.postgresql) + testImplementation(libs.org.testcontainers.mysql) testImplementation(libs.org.springframework.boot.spring.boot.starter.data.redis) testImplementation(libs.org.testcontainers.testcontainers) testImplementation(libs.org.testcontainers.minio) From 513c4abe1748e0a21a6b3c51da78d31c669e8f57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Tue, 29 Jul 2025 00:24:26 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=20[testtoolkit]=20=E6=96=B0=E5=A2=9EMySQL?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=AE=B9=E5=99=A8=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加IDatabaseMysqlContainer接口 - 扩展TestcontainersProperties支持MySQL配置 - 完善MySQL容器使用文档和注释 --- .../properties/TestcontainersProperties.kt | 39 +++++++-- .../testcontainers/IDatabaseMysqlContainer.kt | 81 +++++++++++++++++++ 2 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 testtoolkit/src/main/kotlin/io/github/truenine/composeserver/testtoolkit/testcontainers/IDatabaseMysqlContainer.kt diff --git a/testtoolkit/src/main/kotlin/io/github/truenine/composeserver/testtoolkit/properties/TestcontainersProperties.kt b/testtoolkit/src/main/kotlin/io/github/truenine/composeserver/testtoolkit/properties/TestcontainersProperties.kt index e09822afa..36a767e0b 100644 --- a/testtoolkit/src/main/kotlin/io/github/truenine/composeserver/testtoolkit/properties/TestcontainersProperties.kt +++ b/testtoolkit/src/main/kotlin/io/github/truenine/composeserver/testtoolkit/properties/TestcontainersProperties.kt @@ -14,11 +14,13 @@ import org.springframework.boot.context.properties.ConfigurationProperties * testtoolkit: * testcontainers: * postgres: - * image: "postgres:17.4-alpine" + * image: "postgres:17-alpine" + * mysql: + * image: "mysql:8.0" * redis: - * image: "redis:7.4.2-alpine3.21" + * image: "redis:7-alpine" * minio: - * image: "minio/minio:RELEASE.2025-04-22T22-12-26Z" + * image: "minio/minio:RELEASE.2025-07-23T15-54-02Z" * ``` * * @author TrueNine @@ -29,6 +31,9 @@ data class TestcontainersProperties( /** PostgreSQL 配置 */ val postgres: PostgresConfig = PostgresConfig(), + /** MySQL 配置 */ + val mysql: MysqlConfig = MysqlConfig(), + /** Redis 配置 */ val redis: RedisConfig = RedisConfig(), @@ -38,8 +43,8 @@ data class TestcontainersProperties( /** # PostgreSQL 容器配置 */ data class PostgresConfig( - /** PostgreSQL Docker 镜像 默认使用 postgres:17.4-alpine(当前 LTS 版本) */ - val image: String = "postgres:17.4-alpine", + /** PostgreSQL Docker 镜像 默认使用 postgres:17-alpine(当前 LTS 版本) */ + val image: String = "postgres:17-alpine", /** 默认数据库名称 */ val databaseName: String = "testdb", @@ -51,16 +56,34 @@ data class PostgresConfig( val password: String = "test", ) +/** # MySQL 容器配置 */ +data class MysqlConfig( + /** MySQL Docker 镜像 默认使用 mysql:8.0(当前 LTS 版本) */ + val image: String = "mysql:8.0", + + /** 默认数据库名称 */ + val databaseName: String = "testdb", + + /** 默认用户名 */ + val username: String = "test", + + /** 默认密码 */ + val password: String = "test", + + /** 默认根密码 */ + val rootPassword: String = "roottest", +) + /** # Redis 容器配置 */ data class RedisConfig( - /** Redis Docker 镜像 默认使用 redis:7.4.2-alpine3.21(当前稳定版本) */ - val image: String = "redis:7.4.2-alpine3.21" + /** Redis Docker 镜像 默认使用 redis:7-alpine(当前稳定版本) */ + val image: String = "redis:7-alpine" ) /** # MinIO 容器配置 */ data class MinioConfig( /** MinIO Docker 镜像 默认使用较新的稳定版本 */ - val image: String = "minio/minio:RELEASE.2025-04-22T22-12-26Z", + val image: String = "minio/minio:RELEASE.2025-07-23T15-54-02Z", /** 默认访问密钥 */ val accessKey: String = "minioadmin", diff --git a/testtoolkit/src/main/kotlin/io/github/truenine/composeserver/testtoolkit/testcontainers/IDatabaseMysqlContainer.kt b/testtoolkit/src/main/kotlin/io/github/truenine/composeserver/testtoolkit/testcontainers/IDatabaseMysqlContainer.kt new file mode 100644 index 000000000..e5afd3ac8 --- /dev/null +++ b/testtoolkit/src/main/kotlin/io/github/truenine/composeserver/testtoolkit/testcontainers/IDatabaseMysqlContainer.kt @@ -0,0 +1,81 @@ +package io.github.truenine.composeserver.testtoolkit.testcontainers + +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource +import org.testcontainers.containers.MySQLContainer +import org.testcontainers.junit.jupiter.Testcontainers + +/** + * # MySQL 数据库测试容器接口 + * + * 该接口提供了MySQL测试容器的标准配置,用于集成测试环境。 通过实现此接口,测试类可以自动获得配置好的MySQL测试数据库实例。 + * + * ## 特性 + * - 自动配置MySQL测试容器 + * - 提供标准的数据库连接配置 + * - 支持Spring Test的动态属性注入 + * + * ## 使用方式 + * + * ```kotlin + * @SpringBootTest + * class YourTestClass : IDatabaseMysqlContainer { + * // 你的测试代码 + * } + * ``` + * + * @see org.testcontainers.junit.jupiter.Testcontainers + * @see org.testcontainers.containers.MySQLContainer + * @author TrueNine + * @since 2025-07-28 + */ +@Testcontainers +interface IDatabaseMysqlContainer { + companion object { + /** + * MySQL 测试容器实例 + * + * 预配置的 MySQL 容器,设置可通过配置自定义: + * - 数据库名称: 可配置,默认 testdb + * - 用户名: 可配置,默认 test + * - 密码: 可配置,默认 test + * - 根密码: 可配置,默认 roottest + * - 版本: 可配置,默认 mysql:8.0 + */ + @JvmStatic + val container by lazy { + val config = TestcontainersConfigurationHolder.getTestcontainersProperties() + MySQLContainer(config.mysql.image).apply { + withDatabaseName(config.mysql.databaseName) + withUsername(config.mysql.username) + withPassword(config.mysql.password) + withEnv("MYSQL_ROOT_PASSWORD", config.mysql.rootPassword) + addExposedPorts(3306) + start() + } + } + + /** + * Spring测试环境动态属性配置 + * + * 自动注入数据库连接相关的配置属性到Spring测试环境中: + * - JDBC URL + * - 用户名 + * - 密码 + * - 数据库驱动类名 + * + * @param registry Spring动态属性注册器 + */ + @JvmStatic + @DynamicPropertySource + fun properties(registry: DynamicPropertyRegistry) { + registry.add("spring.datasource.url", container::getJdbcUrl) + registry.add("spring.datasource.username", container::getUsername) + registry.add("spring.datasource.password", container::getPassword) + registry.add("spring.datasource.driver-class-name") { "com.mysql.cj.jdbc.Driver" } + } + } + + val mysqlContainer: MySQLContainer<*>? + get() = container +} From fd896c6b4f3fef44b86336012c7f9cf8b3ede790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Tue, 29 Jul 2025 00:27:15 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=20[testtoolkit]=20=E6=B7=BB=E5=8A=A0MySQL?= =?UTF-8?q?=E5=AE=B9=E5=99=A8=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增IDatabaseMysqlContainerTest测试类 - 验证MySQL容器功能和配置 --- .../IDatabaseMysqlContainerTest.kt | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/testcontainers/IDatabaseMysqlContainerTest.kt diff --git a/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/testcontainers/IDatabaseMysqlContainerTest.kt b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/testcontainers/IDatabaseMysqlContainerTest.kt new file mode 100644 index 000000000..be24c556a --- /dev/null +++ b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/testcontainers/IDatabaseMysqlContainerTest.kt @@ -0,0 +1,181 @@ +package io.github.truenine.composeserver.testtoolkit.testcontainers + +import jakarta.annotation.Resource +import java.sql.DriverManager +import java.sql.SQLException +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull +import kotlin.test.assertTrue +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.core.env.Environment +import org.springframework.jdbc.core.JdbcTemplate + +@SpringBootTest +class IDatabaseMysqlContainerTest : IDatabaseMysqlContainer { + lateinit var environment: Environment + @Resource set + + lateinit var jdbcTemplate: JdbcTemplate + @Resource set + + @Test + fun `验证 MySQL 容器成功启动`() { + assertNotNull(mysqlContainer, "MySQL 容器应该存在") + assertTrue(mysqlContainer?.isRunning == true, "MySQL 容器应该处于运行状态") + + // 通过执行简单查询来验证容器是否正常工作 + val version = jdbcTemplate.queryForObject("SELECT VERSION()", String::class.java) + assertNotNull(version, "应该能够获取 MySQL 版本信息") + assertTrue(version.contains("8.0"), "数据库应该是 MySQL 8.0") + } + + @Test + fun `验证 Spring 环境中包含数据源配置`() { + // 验证必要的数据源配置属性是否存在 + assertNotNull(environment.getProperty("spring.datasource.url"), "数据源 URL 应该存在") + assertNotNull(environment.getProperty("spring.datasource.username"), "数据源用户名应该存在") + assertNotNull(environment.getProperty("spring.datasource.password"), "数据源密码应该存在") + assertNotNull(environment.getProperty("spring.datasource.driver-class-name"), "数据源驱动类名应该存在") + + // 验证 URL 是否指向 TestContainers 的 MySQL + val jdbcUrl = environment.getProperty("spring.datasource.url") + assertTrue(jdbcUrl?.contains("jdbc:mysql") == true, "JDBC URL 应该是 MySQL 连接") + } + + @Test + fun `验证数据库连接可以成功建立`() { + val connection = jdbcTemplate.dataSource?.connection + assertNotNull(connection, "应该能够获取数据库连接") + + connection.use { conn -> + assertTrue(conn.isValid(5), "数据库连接应该有效") + assertEquals("MySQL", conn.metaData.databaseProductName, "数据库类型应该是 MySQL") + + // 验证数据库连接状态 + val stmt = conn.createStatement() + val rs = stmt.executeQuery("SELECT CONNECTION_ID() as id") + assertTrue(rs.next(), "应该能够查询到当前连接的ID") + val connectionId = rs.getLong("id") + assertTrue(connectionId > 0, "连接ID应该大于0") + } + } + + @Test + fun `验证数据库基本操作正常`() { + // 验证可以执行基本的 SQL 操作 + val result = jdbcTemplate.queryForObject("SELECT 1", Int::class.java) + assertEquals(1, result, "应该能够执行基本的 SQL 查询") + + // 验证可以创建和删除临时表 + jdbcTemplate.execute("CREATE TEMPORARY TABLE test_table (id int)") + + // 验证表结构 - 对于临时表,直接通过 DESCRIBE 命令验证 + val columnInfo = jdbcTemplate.queryForList("DESCRIBE test_table") + assertTrue(columnInfo.isNotEmpty(), "临时表应该有列定义") + assertTrue(columnInfo.any { it["Field"] == "id" }, "临时表应该包含 id 列") + + // 验证表是否可以正常操作 + jdbcTemplate.execute("INSERT INTO test_table (id) VALUES (999)") + val tableOperationTest = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM test_table WHERE id = 999", Int::class.java) + assertEquals(1, tableOperationTest, "应该能够向临时表插入数据并查询") + + // 验证表的可操作性 - 添加另一条记录并验证总数 + jdbcTemplate.execute("INSERT INTO test_table (id) VALUES (1)") + val insertedCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM test_table", Int::class.java) + assertEquals(2, insertedCount, "临时表应该包含所有插入的记录") + + // 验证可以删除数据 + jdbcTemplate.execute("DELETE FROM test_table WHERE id = 1") + val remainingCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM test_table", Int::class.java) + assertEquals(1, remainingCount, "删除后应该只剩下一条记录") + } + + @Test + fun `验证容器端口映射正确`() { + val mappedPort = mysqlContainer?.getMappedPort(3306) + assertNotNull(mappedPort, "MySQL 端口应该被正确映射") + assertTrue(mappedPort > 0, "映射端口应该是有效的端口号") + + // 验证端口可访问性 + val databaseName = mysqlContainer?.databaseName + assertNotNull(databaseName, "数据库名称不应为空") + + val jdbcUrl = "jdbc:mysql://localhost:$mappedPort/$databaseName" + val username = mysqlContainer?.username + val password = mysqlContainer?.password + + assertNotNull(username, "数据库用户名不应为空") + assertNotNull(password, "数据库密码不应为空") + + DriverManager.getConnection(jdbcUrl, username, password).use { conn -> + assertTrue(conn.isValid(5), "应该能够通过映射端口建立连接") + + // 验证连接的数据库名称 + assertEquals(databaseName, conn.catalog, "连接的数据库名称应该正确") + + // 验证数据库名称格式 + assertTrue(databaseName.matches(Regex("^[a-zA-Z_][a-zA-Z0-9_]*$")), "数据库名称应符合标准格式") + + // 验证连接属性 + assertTrue(conn.metaData.supportsTransactions(), "应支持事务") + assertTrue(conn.metaData.supportsStoredProcedures(), "应支持存储过程") + } + } + + @Test + fun `验证无效连接时抛出异常`() { + val invalidJdbcUrl = "jdbc:mysql://localhost:1234/nonexistent" + assertFailsWith("使用无效连接应该抛出异常") { DriverManager.getConnection(invalidJdbcUrl) } + } + + @Test + fun `验证数据库字符集配置`() { + val charset = jdbcTemplate.queryForObject("SELECT @@character_set_database", String::class.java) + assertNotNull(charset, "数据库字符集应该存在") + + // MySQL 8.0 默认字符集是 utf8mb4 + assertTrue(charset.contains("utf8") || charset == "utf8mb4", "数据库字符集应该是 UTF8 相关 (actual: $charset)") + + // 验证客户端连接编码 + val connectionCharset = jdbcTemplate.queryForObject("SELECT @@character_set_connection", String::class.java) + assertNotNull(connectionCharset, "连接字符集应该存在") + } + + @Test + fun `验证数据库时区配置`() { + val timezone = jdbcTemplate.queryForObject("SELECT @@system_time_zone", String::class.java) + assertNotNull(timezone, "数据库时区设置应该存在") + + // 验证时区设置不为空 + assertTrue(timezone.isNotEmpty(), "时区设置不应为空") + + // 验证可以获取当前时间 + val currentTime = jdbcTemplate.queryForObject("SELECT NOW()", java.sql.Timestamp::class.java) + assertNotNull(currentTime, "应该能获取当前时间") + + // 验证时间的合理性 - 考虑时区差异,允许更大的时间差 + val now = System.currentTimeMillis() + val timeDiff = kotlin.math.abs(currentTime.time - now) + // 允许最大24小时的时区差异加上1分钟的执行时间差 + val maxAllowedDiff = 24 * 60 * 60 * 1000 + 60000 // 24小时 + 1分钟 + assertTrue(timeDiff < maxAllowedDiff, "数据库时间应在合理范围内 (差值: ${timeDiff}ms, 约${timeDiff / 3600000}小时)") + } + + @Test + fun `验证 MySQL 特性支持`() { + // 验证 MySQL 版本特性 + val version = jdbcTemplate.queryForObject("SELECT VERSION()", String::class.java) + assertNotNull(version, "MySQL 版本信息不应为空") + assertTrue(version!!.startsWith("8.0"), "应该是 MySQL 8.0 版本") + + // 验证存储引擎支持 + val engines = jdbcTemplate.queryForList("SHOW ENGINES") + assertTrue(engines.any { (it["Engine"] as String).contains("InnoDB") }, "应该支持 InnoDB 存储引擎") + + // 验证 SQL 模式 + val sqlMode = jdbcTemplate.queryForObject("SELECT @@sql_mode", String::class.java) + assertNotNull(sqlMode, "SQL 模式应该存在") + } +} From 1f662a1f11a732a37e02fcb2185cbba69aa0a3db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Tue, 29 Jul 2025 00:27:35 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=20[testtoolkit]=20=E4=BC=98=E5=8C=96Testco?= =?UTF-8?q?ntainers=E7=9B=B8=E5=85=B3=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增强TestcontainersVerificationTest测试覆盖 - 完善TestcontainersPropertiesIntegrationTest集成测试 - 改进TestcontainersPropertiesTest单元测试 --- .../TestcontainersVerificationTest.kt | 36 +++++++++++++++++-- ...TestcontainersPropertiesIntegrationTest.kt | 16 +++++++-- .../TestcontainersPropertiesTest.kt | 31 ++++++++++++++-- 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/TestcontainersVerificationTest.kt b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/TestcontainersVerificationTest.kt index d8cde6d95..e059cc588 100644 --- a/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/TestcontainersVerificationTest.kt +++ b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/TestcontainersVerificationTest.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.selects.select import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertTimeout import org.testcontainers.containers.GenericContainer +import org.testcontainers.containers.MySQLContainer import org.testcontainers.containers.PostgreSQLContainer import org.testcontainers.containers.output.Slf4jLogConsumer import org.testcontainers.utility.MountableFile @@ -23,7 +24,7 @@ class TestcontainersVerificationTest { fun `启动 Alpine 容器 输出日志并校验运行状态`() { log.info("开始测试 Testcontainers") - GenericContainer("alpine:latest") + GenericContainer("alpine:3.21") .apply { // 增加容器运行时间,确保有足够时间进行日志检查 // 使用 flush 确保输出立即写入,避免缓冲延迟 @@ -153,6 +154,37 @@ class TestcontainersVerificationTest { } } + @Test + fun `启动 MySQL 容器 获取连接信息并校验运行状态`() { + log.info("开始测试 MySQL 容器") + + MySQLContainer("mysql:8.0") + .apply { + withDatabaseName("testdb") + withUsername("testuser") + withPassword("testpass") + withEnv("MYSQL_ROOT_PASSWORD", "rootpass") + withExposedPorts(3306) + withLogConsumer(Slf4jLogConsumer(log)) + withStartupTimeout(Duration.ofSeconds(120)) // 增加启动超时时间 + } + .use { mysql -> + log.info("正在启动 MySQL 容器...") + mysql.start() + + // 使用重试机制等待容器完全就绪 + TestRetryUtils.waitUntil(timeout = Duration.ofSeconds(30), pollInterval = Duration.ofSeconds(1)) { mysql.isRunning && mysql.jdbcUrl.isNotEmpty() } + + assertTrue(mysql.isRunning, "MySQL 容器应该处于运行状态") + log.info("MySQL 连接 URL: ${mysql.jdbcUrl}") + log.info("MySQL 用户名: ${mysql.username}") + log.info("MySQL 密码: ${mysql.password}") + + val expectedJdbcUrlPrefix = "jdbc:mysql://" + assertTrue(mysql.jdbcUrl.startsWith(expectedJdbcUrlPrefix), "JDBC URL 应该以 $expectedJdbcUrlPrefix 开头") + } + } + @Test fun `并发测试多个网址 任一完成即结束`() { assertTimeout(Duration.ofSeconds(20), "可能由于docker 网络原因导致测试失败,考虑检查 docker 网络配置") { @@ -161,7 +193,7 @@ class TestcontainersVerificationTest { val urls = listOf("https://www.aliyun.com", "https://www.tencent.com", "https://www.baidu.com", "https://www.qq.com") - GenericContainer("alpine/curl:latest") + GenericContainer("curlimages/curl:8.1.0") .apply { withCommand("sleep", "30") // 增加容器运行时间 } diff --git a/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/autoconfig/TestcontainersPropertiesIntegrationTest.kt b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/autoconfig/TestcontainersPropertiesIntegrationTest.kt index 9e19a49b5..1516d7180 100644 --- a/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/autoconfig/TestcontainersPropertiesIntegrationTest.kt +++ b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/autoconfig/TestcontainersPropertiesIntegrationTest.kt @@ -36,9 +36,10 @@ class TestcontainersPropertiesIntegrationTest { @Test fun should_inject_properties_with_defaults() { assertNotNull(testcontainersProperties) - assertEquals("postgres:17.4-alpine", testcontainersProperties.postgres.image) - assertEquals("redis:7.4.2-alpine3.21", testcontainersProperties.redis.image) - assertEquals("minio/minio:RELEASE.2025-04-22T22-12-26Z", testcontainersProperties.minio.image) + assertEquals("postgres:17-alpine", testcontainersProperties.postgres.image) + assertEquals("mysql:8.0", testcontainersProperties.mysql.image) + assertEquals("redis:7-alpine", testcontainersProperties.redis.image) + assertEquals("minio/minio:RELEASE.2025-07-23T15-54-02Z", testcontainersProperties.minio.image) } } @@ -56,6 +57,8 @@ class TestcontainersPropertiesIntegrationTest { [ "compose.testtoolkit.testcontainers.postgres.image=postgres:16-alpine", "compose.testtoolkit.testcontainers.postgres.database-name=customdb", + "compose.testtoolkit.testcontainers.mysql.image=mysql:8.1", + "compose.testtoolkit.testcontainers.mysql.database-name=mysqlcustomdb", "compose.testtoolkit.testcontainers.redis.image=redis:7.2-alpine", "compose.testtoolkit.testcontainers.minio.image=minio/minio:RELEASE.2024-01-01T00-00-00Z", "compose.testtoolkit.testcontainers.minio.access-key=customkey", @@ -74,6 +77,13 @@ class TestcontainersPropertiesIntegrationTest { assertEquals("customdb", testcontainersProperties.postgres.databaseName) assertEquals("test", testcontainersProperties.postgres.username) // 保持默认值 + // 验证自定义 MySQL 配置 + assertEquals("mysql:8.1", testcontainersProperties.mysql.image) + assertEquals("mysqlcustomdb", testcontainersProperties.mysql.databaseName) + assertEquals("test", testcontainersProperties.mysql.username) // 保持默认值 + assertEquals("test", testcontainersProperties.mysql.password) // 保持默认值 + assertEquals("roottest", testcontainersProperties.mysql.rootPassword) // 保持默认值 + // 验证自定义 Redis 配置 assertEquals("redis:7.2-alpine", testcontainersProperties.redis.image) diff --git a/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/autoconfig/TestcontainersPropertiesTest.kt b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/autoconfig/TestcontainersPropertiesTest.kt index b2d07b20e..f426f952a 100644 --- a/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/autoconfig/TestcontainersPropertiesTest.kt +++ b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/autoconfig/TestcontainersPropertiesTest.kt @@ -1,6 +1,7 @@ package io.github.truenine.composeserver.testtoolkit.autoconfig import io.github.truenine.composeserver.testtoolkit.properties.MinioConfig +import io.github.truenine.composeserver.testtoolkit.properties.MysqlConfig import io.github.truenine.composeserver.testtoolkit.properties.PostgresConfig import io.github.truenine.composeserver.testtoolkit.properties.RedisConfig import io.github.truenine.composeserver.testtoolkit.properties.TestcontainersProperties @@ -18,24 +19,35 @@ class TestcontainersPropertiesTest { fun should_have_correct_default_postgres_config() { val properties = TestcontainersProperties() - assertEquals("postgres:17.4-alpine", properties.postgres.image) + assertEquals("postgres:17-alpine", properties.postgres.image) assertEquals("testdb", properties.postgres.databaseName) assertEquals("test", properties.postgres.username) assertEquals("test", properties.postgres.password) } + @Test + fun should_have_correct_default_mysql_config() { + val properties = TestcontainersProperties() + + assertEquals("mysql:8.0", properties.mysql.image) + assertEquals("testdb", properties.mysql.databaseName) + assertEquals("test", properties.mysql.username) + assertEquals("test", properties.mysql.password) + assertEquals("roottest", properties.mysql.rootPassword) + } + @Test fun should_have_correct_default_redis_config() { val properties = TestcontainersProperties() - assertEquals("redis:7.4.2-alpine3.21", properties.redis.image) + assertEquals("redis:7-alpine", properties.redis.image) } @Test fun should_have_correct_default_minio_config() { val properties = TestcontainersProperties() - assertEquals("minio/minio:RELEASE.2025-04-22T22-12-26Z", properties.minio.image) + assertEquals("minio/minio:RELEASE.2025-07-23T15-54-02Z", properties.minio.image) assertEquals("minioadmin", properties.minio.accessKey) assertEquals("minioadmin", properties.minio.secretKey) } @@ -55,6 +67,19 @@ class TestcontainersPropertiesTest { assertEquals("custompass", properties.postgres.password) } + @Test + fun should_accept_custom_mysql_config() { + val customMysql = + MysqlConfig(image = "mysql:8.1", databaseName = "customdb", username = "customuser", password = "custompass", rootPassword = "customroot") + val properties = TestcontainersProperties(mysql = customMysql) + + assertEquals("mysql:8.1", properties.mysql.image) + assertEquals("customdb", properties.mysql.databaseName) + assertEquals("customuser", properties.mysql.username) + assertEquals("custompass", properties.mysql.password) + assertEquals("customroot", properties.mysql.rootPassword) + } + @Test fun should_accept_custom_redis_config() { val customRedis = RedisConfig(image = "redis:7.2-alpine") From 98235d8876737993763d3cf0048d4adf38f255ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Tue, 29 Jul 2025 00:28:02 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=20[testtoolkit]=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E7=B1=BB=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化TestcontainersConfigurationHolderTest测试结构 - 改进ContainerCommandExecutorTest测试用例 - 增强ContainerExtensionsTest测试覆盖率 --- .../TestcontainersConfigurationHolderTest.kt | 8 ++--- .../utils/ContainerCommandExecutorTest.kt | 28 ++++++++-------- .../utils/ContainerExtensionsTest.kt | 32 +++++++++---------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/testcontainers/TestcontainersConfigurationHolderTest.kt b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/testcontainers/TestcontainersConfigurationHolderTest.kt index 27821ff4a..4b156834a 100644 --- a/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/testcontainers/TestcontainersConfigurationHolderTest.kt +++ b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/testcontainers/TestcontainersConfigurationHolderTest.kt @@ -29,9 +29,9 @@ class TestcontainersConfigurationHolderTest { assertNotNull(properties.minio, "MinIO 配置不应为 null") // 验证默认配置值 - assertEquals("postgres:17.4-alpine", properties.postgres.image, "PostgreSQL 默认镜像应该正确") - assertEquals("redis:7.4.2-alpine3.21", properties.redis.image, "Redis 默认镜像应该正确") - assertEquals("minio/minio:RELEASE.2025-04-22T22-12-26Z", properties.minio.image, "MinIO 默认镜像应该正确") + assertEquals("postgres:17-alpine", properties.postgres.image, "PostgreSQL 默认镜像应该正确") + assertEquals("redis:7-alpine", properties.redis.image, "Redis 默认镜像应该正确") + assertEquals("minio/minio:RELEASE.2025-07-23T15-54-02Z", properties.minio.image, "MinIO 默认镜像应该正确") // 验证配置的完整性 assertTrue(properties.postgres.image.isNotEmpty(), "PostgreSQL 镜像名不应为空") @@ -56,7 +56,7 @@ class TestcontainersConfigurationHolderTest { // 验证在Bean不存在时返回默认配置 assertNotNull(properties, "属性对象不应为 null") - assertEquals("postgres:17.4-alpine", properties.postgres.image, "PostgreSQL 默认镜像应该正确") + assertEquals("postgres:17-alpine", properties.postgres.image, "PostgreSQL 默认镜像应该正确") // 验证所有默认配置都可用 assertNotNull(properties.postgres, "PostgreSQL 配置不应为 null") diff --git a/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/utils/ContainerCommandExecutorTest.kt b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/utils/ContainerCommandExecutorTest.kt index a59820f56..7a7fc835a 100644 --- a/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/utils/ContainerCommandExecutorTest.kt +++ b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/utils/ContainerCommandExecutorTest.kt @@ -20,7 +20,7 @@ class ContainerCommandExecutorTest { fun `测试 ContainerCommandExecutor 构造函数和常量`() { log.info("开始测试 ContainerCommandExecutor 构造函数和常量") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) @@ -38,7 +38,7 @@ class ContainerCommandExecutorTest { fun `测试 executeCommand 方法的成功执行`() { log.info("开始测试 executeCommand 方法的成功执行") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) @@ -55,7 +55,7 @@ class ContainerCommandExecutorTest { fun `测试 executeCommand 方法的空命令验证`() { log.info("开始测试 executeCommand 方法的空命令验证") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) @@ -70,7 +70,7 @@ class ContainerCommandExecutorTest { fun `测试 executeCommand 方法的自定义参数`() { log.info("开始测试 executeCommand 方法的自定义参数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) @@ -87,7 +87,7 @@ class ContainerCommandExecutorTest { fun `测试 executeCommandWithExpectedExitCode 方法的成功情况`() { log.info("开始测试 executeCommandWithExpectedExitCode 方法的成功情况") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) @@ -104,7 +104,7 @@ class ContainerCommandExecutorTest { fun `测试 executeCommandWithExpectedExitCode 方法的失败情况`() { log.info("开始测试 executeCommandWithExpectedExitCode 方法的失败情况") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) @@ -119,7 +119,7 @@ class ContainerCommandExecutorTest { fun `测试 executeCommandAndGetOutput 方法`() { log.info("开始测试 executeCommandAndGetOutput 方法") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) @@ -135,7 +135,7 @@ class ContainerCommandExecutorTest { fun `测试 executeCommandAndCheckOutput 方法的成功情况`() { log.info("开始测试 executeCommandAndCheckOutput 方法的成功情况") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) @@ -152,7 +152,7 @@ class ContainerCommandExecutorTest { fun `测试 executeCommandAndCheckOutput 方法的失败情况`() { log.info("开始测试 executeCommandAndCheckOutput 方法的失败情况") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) @@ -167,7 +167,7 @@ class ContainerCommandExecutorTest { fun `测试 waitForContainerReady 方法`() { log.info("开始测试 waitForContainerReady 方法") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) @@ -185,7 +185,7 @@ class ContainerCommandExecutorTest { fun `测试 fileExists 方法的存在文件`() { log.info("开始测试 fileExists 方法的存在文件") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) @@ -201,7 +201,7 @@ class ContainerCommandExecutorTest { fun `测试 fileExists 方法的不存在文件`() { log.info("开始测试 fileExists 方法的不存在文件") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) @@ -217,7 +217,7 @@ class ContainerCommandExecutorTest { fun `测试 waitForFile 方法`() { log.info("开始测试 waitForFile 方法") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) @@ -238,7 +238,7 @@ class ContainerCommandExecutorTest { fun `测试 readFileContent 方法`() { log.info("开始测试 readFileContent 方法") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = ContainerCommandExecutor(container) diff --git a/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/utils/ContainerExtensionsTest.kt b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/utils/ContainerExtensionsTest.kt index 794ce4d0e..abaf781a3 100644 --- a/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/utils/ContainerExtensionsTest.kt +++ b/testtoolkit/src/test/kotlin/io/github/truenine/composeserver/testtoolkit/utils/ContainerExtensionsTest.kt @@ -23,7 +23,7 @@ class ContainerExtensionsTest { fun `测试 commandExecutor 扩展函数`() { log.info("开始测试 commandExecutor 扩展函数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val executor = container.commandExecutor() @@ -38,7 +38,7 @@ class ContainerExtensionsTest { fun `测试 safeExecInContainer 扩展函数`() { log.info("开始测试 safeExecInContainer 扩展函数") - GenericContainer(DockerImageName.parse("alpine:latest")) + GenericContainer(DockerImageName.parse("alpine:3.21")) .withCommand("sleep", "300") // 让容器运行5分钟,足够测试使用 .use { container -> container.start() @@ -59,7 +59,7 @@ class ContainerExtensionsTest { fun `测试 safeExecInContainer 扩展函数带自定义参数`() { log.info("开始测试 safeExecInContainer 扩展函数带自定义参数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val result = container.safeExecInContainer(timeout = Duration.ofSeconds(10), maxRetries = 2, "echo", "Custom Test") @@ -75,7 +75,7 @@ class ContainerExtensionsTest { fun `测试 execWithExpectedExitCode 扩展函数`() { log.info("开始测试 execWithExpectedExitCode 扩展函数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() // 测试成功的命令 @@ -90,7 +90,7 @@ class ContainerExtensionsTest { fun `测试 execAndGetOutput 扩展函数`() { log.info("开始测试 execAndGetOutput 扩展函数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val output = container.execAndGetOutput(commands = arrayOf("echo", "Test Output")) @@ -105,7 +105,7 @@ class ContainerExtensionsTest { fun `测试 execAndCheckOutput 扩展函数`() { log.info("开始测试 execAndCheckOutput 扩展函数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() val result = container.execAndCheckOutput("Hello", commands = arrayOf("echo", "Hello World")) @@ -121,7 +121,7 @@ class ContainerExtensionsTest { fun `测试 waitForReady 扩展函数`() { log.info("开始测试 waitForReady 扩展函数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() // 容器已经启动,等待就绪应该立即完成 @@ -137,7 +137,7 @@ class ContainerExtensionsTest { fun `测试 fileExists 扩展函数`() { log.info("开始测试 fileExists 扩展函数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() // 测试存在的文件 @@ -156,7 +156,7 @@ class ContainerExtensionsTest { fun `测试 readFile 扩展函数`() { log.info("开始测试 readFile 扩展函数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() // 创建一个测试文件 @@ -174,7 +174,7 @@ class ContainerExtensionsTest { fun `测试 waitForFile 扩展函数`() { log.info("开始测试 waitForFile 扩展函数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() // 创建文件 @@ -195,7 +195,7 @@ class ContainerExtensionsTest { log.info("开始测试 withStableWaitStrategy 扩展函数") // 测试扩展函数的存在性,通过调用其他扩展函数来间接验证 - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() // 验证容器正常启动 @@ -213,7 +213,7 @@ class ContainerExtensionsTest { fun `测试 withStableWaitStrategy 扩展函数带自定义参数`() { log.info("开始测试 withStableWaitStrategy 扩展函数带自定义参数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() assertTrue(container.isRunning, "使用自定义参数的稳定等待策略应该正常工作") @@ -226,7 +226,7 @@ class ContainerExtensionsTest { fun `测试 withHealthCheck 扩展函数`() { log.info("开始测试 withHealthCheck 扩展函数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() assertTrue(container.isRunning, "使用健康检查的容器应该正常启动") @@ -239,7 +239,7 @@ class ContainerExtensionsTest { fun `测试 withHealthCheck 扩展函数带自定义参数`() { log.info("开始测试 withHealthCheck 扩展函数带自定义参数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.start() assertTrue(container.isRunning, "使用自定义健康检查参数的容器应该正常启动") @@ -252,7 +252,7 @@ class ContainerExtensionsTest { fun `测试 startAndWaitForReady 扩展函数`() { log.info("开始测试 startAndWaitForReady 扩展函数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.startAndWaitForReady() assertTrue(container.isRunning, "启动并等待就绪的容器应该处于运行状态") @@ -265,7 +265,7 @@ class ContainerExtensionsTest { fun `测试 startAndWaitForReady 扩展函数带自定义参数`() { log.info("开始测试 startAndWaitForReady 扩展函数带自定义参数") - GenericContainer(DockerImageName.parse("alpine:latest")).withCommand("sleep", "infinity").use { container -> + GenericContainer(DockerImageName.parse("alpine:3.21")).withCommand("sleep", "infinity").use { container -> container.startAndWaitForReady(readyTimeout = Duration.ofSeconds(20), readyPollInterval = Duration.ofMillis(200)) assertTrue(container.isRunning, "使用自定义参数启动并等待就绪的容器应该处于运行状态") From d70f672b0eed3779e13dd6034d6a830aaa83ac23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E6=97=A5=E5=A4=A9?= Date: Tue, 29 Jul 2025 00:31:54 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=9A=80=20[release]=200.0.13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 50398cedb..4a4f5f019 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,7 +50,7 @@ org-springframework-modulith = "2.0.0-M1" org-springframework-security = "6.5.1" org-testcontainers = "1.21.3" org-testng = "7.11.0" -project = "0.0.12" +project = "0.0.13" [libraries] cn-dev33-sa-token-redis-jackson = { module = "cn.dev33:sa-token-redis-jackson", version.ref = "cn-dev33-sa-token" } @@ -340,29 +340,29 @@ tech-argonariod-gradle-plugin-jimmer = "tech.argonariod.gradle-plugin-jimmer:1.2 [bundles] junit5 = [ - "org-junit-jupiter-junit-jupiter-api", - "org-junit-jupiter-junit-jupiter-engine", - "org-junit-platform-junit-platform-launcher", + "org-junit-jupiter-junit-jupiter-api", + "org-junit-jupiter-junit-jupiter-engine", + "org-junit-platform-junit-platform-launcher", ] kotlin-reactor = [ - "io-projectreactor-kotlin-reactor-kotlin-extensions", - "org-jetbrains-kotlinx-kotlinx-coroutines-reactor", + "io-projectreactor-kotlin-reactor-kotlin-extensions", + "org-jetbrains-kotlinx-kotlinx-coroutines-reactor", ] kotlin-test-junit5 = [ - "io-mockk-mockk", - "io-projectreactor-reactor-test", - "org-jetbrains-kotlin-kotlin-test-junit5", - "org-jetbrains-kotlinx-kotlinx-coroutines-test", + "io-mockk-mockk", + "io-projectreactor-reactor-test", + "org-jetbrains-kotlin-kotlin-test-junit5", + "org-jetbrains-kotlinx-kotlinx-coroutines-test", ] p6spy = [ - "com-github-gavlyukovskiy-p6spy-spring-boot-starter", - "p6spy-p6spy", + "com-github-gavlyukovskiy-p6spy-spring-boot-starter", + "p6spy-p6spy", ] redis = [ - "org-apache-commons-commons-pool2", - "org-springframework-boot-spring-boot-starter-data-redis", + "org-apache-commons-commons-pool2", + "org-springframework-boot-spring-boot-starter-data-redis", ] selenium = [ - "io-github-bonigarcia-webdrivermanager", - "org-seleniumhq-selenium-selenium-java", + "io-github-bonigarcia-webdrivermanager", + "org-seleniumhq-selenium-selenium-java", ]