Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions docs/mcp.json → .mcp.json
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
{
"mcpServers": {
"context7": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@upstash/context7-mcp@latest"
]
},
"Sequential-thinking": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-sequential-thinking"
]
},
"broswertools": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@browsermcp/mcp@latest"
]
},
"Playwright": {
"command": "npx",
"args": [
"-y",
"@playwright/mcp@latest"
]
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
val libs = the<org.gradle.accessors.dm.LibrariesForLibs>()
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 文件
Expand Down
16 changes: 16 additions & 0 deletions docs/mcp.md
Original file line number Diff line number Diff line change
@@ -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"]}
}
}
```
33 changes: 17 additions & 16 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down Expand Up @@ -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" }
Expand Down Expand Up @@ -339,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",
]
4 changes: 4 additions & 0 deletions testtoolkit/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,6 +31,9 @@ data class TestcontainersProperties(
/** PostgreSQL 配置 */
val postgres: PostgresConfig = PostgresConfig(),

/** MySQL 配置 */
val mysql: MysqlConfig = MysqlConfig(),

/** Redis 配置 */
val redis: RedisConfig = RedisConfig(),

Expand All @@ -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",
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Nothing>(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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -23,7 +24,7 @@ class TestcontainersVerificationTest {
fun `启动 Alpine 容器 输出日志并校验运行状态`() {
log.info("开始测试 Testcontainers")

GenericContainer("alpine:latest")
GenericContainer("alpine:3.21")
.apply {
// 增加容器运行时间,确保有足够时间进行日志检查
// 使用 flush 确保输出立即写入,避免缓冲延迟
Expand Down Expand Up @@ -153,6 +154,37 @@ class TestcontainersVerificationTest {
}
}

@Test
fun `启动 MySQL 容器 获取连接信息并校验运行状态`() {
log.info("开始测试 MySQL 容器")

MySQLContainer<Nothing>("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 网络配置") {
Expand All @@ -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") // 增加容器运行时间
}
Expand Down
Loading