From 289b9678f2fc9fd983d9f914569d3f475fa1109e Mon Sep 17 00:00:00 2001 From: LamGC Date: Mon, 20 Jun 2022 20:55:04 +0800 Subject: [PATCH 01/22] =?UTF-8?q?refactor(config):=20=E5=B0=86=E4=B8=8E?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=9B=B8=E5=85=B3=E7=9A=84=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E5=88=B0=20scalabot-meta=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 通过将配置迁移到单独的模块, 可以方便使用其他程序扩展 ScaleBot, 而不仅仅是让 ScaleBot 成为扩展的平台. BREAKING CHANGE: 与配置有关的 Class 移动到了 scalabot-meta 模块. 目前仅所有配置类(以 `Config` 结尾的 Class)和相应的序列化类(以 `Serializer` 结尾的)都迁移到了 meta 模块, 但其工具方法则作为扩展函数保留在 app 模块中. 这么做的好处是为了方便其他应用(例如 ScalaBot 外部管理程序)根据需要生成配置文件. scalabot-meta 将会作为依赖项发布, 可根据需要获取 ScalaBot-meta 生成 ScalaBot 的配置. 此次改动普通用户无需迁移. --- scalabot-app/build.gradle.kts | 1 + scalabot-app/src/main/kotlin/AppConfigs.kt | 189 +++++------------- scalabot-app/src/main/kotlin/AppMain.kt | 14 +- scalabot-app/src/main/kotlin/BotDBMaker.kt | 1 + scalabot-app/src/main/kotlin/ScalaBot.kt | 1 + .../main/kotlin/util/UsernameAuthenticator.kt | 65 ------ scalabot-app/src/test/kotlin/AppConfigTest.kt | 34 ---- scalabot-meta/build.gradle.kts | 30 +++ scalabot-meta/src/main/kotlin/Configs.kt | 108 ++++++++++ .../src/main/kotlin/UsernameAuthenticator.kt | 26 +++ .../src/main/kotlin/serializer/Serializer.kt | 63 ++++-- .../src/test/kotlin/BotAccountTest.kt | 38 ++++ .../serializer}/ArtifactSerializerTest.kt | 2 +- .../kotlin/serializer}/SerializersKtTest.kt | 17 +- .../serializer}/UsernameAuthenticatorTest.kt | 3 +- settings.gradle.kts | 1 + 16 files changed, 325 insertions(+), 268 deletions(-) delete mode 100644 scalabot-app/src/main/kotlin/util/UsernameAuthenticator.kt create mode 100644 scalabot-meta/build.gradle.kts create mode 100644 scalabot-meta/src/main/kotlin/Configs.kt create mode 100644 scalabot-meta/src/main/kotlin/UsernameAuthenticator.kt rename scalabot-app/src/main/kotlin/util/Serializers.kt => scalabot-meta/src/main/kotlin/serializer/Serializer.kt (69%) create mode 100644 scalabot-meta/src/test/kotlin/BotAccountTest.kt rename {scalabot-app/src/test/kotlin/util => scalabot-meta/src/test/kotlin/serializer}/ArtifactSerializerTest.kt (97%) rename {scalabot-app/src/test/kotlin/util => scalabot-meta/src/test/kotlin/serializer}/SerializersKtTest.kt (95%) rename {scalabot-app/src/test/kotlin/util => scalabot-meta/src/test/kotlin/serializer}/UsernameAuthenticatorTest.kt (97%) diff --git a/scalabot-app/build.gradle.kts b/scalabot-app/build.gradle.kts index f954c43..922ca96 100644 --- a/scalabot-app/build.gradle.kts +++ b/scalabot-app/build.gradle.kts @@ -8,6 +8,7 @@ plugins { } dependencies { + implementation(project(":scalabot-meta")) implementation(project(":scalabot-extension")) implementation("org.slf4j:slf4j-api:1.7.36") diff --git a/scalabot-app/src/main/kotlin/AppConfigs.kt b/scalabot-app/src/main/kotlin/AppConfigs.kt index 09b38c6..27700cb 100644 --- a/scalabot-app/src/main/kotlin/AppConfigs.kt +++ b/scalabot-app/src/main/kotlin/AppConfigs.kt @@ -5,14 +5,14 @@ import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.reflect.TypeToken import mu.KotlinLogging -import net.lamgc.scalabot.util.* +import net.lamgc.scalabot.config.* +import net.lamgc.scalabot.config.serializer.* import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.Proxy import org.eclipse.aether.repository.RemoteRepository import org.eclipse.aether.repository.RepositoryPolicy import org.telegram.telegrambots.bots.DefaultBotOptions -import org.telegram.telegrambots.meta.ApiConstants import java.io.File import java.net.URL import java.nio.charset.StandardCharsets @@ -22,154 +22,63 @@ import kotlin.system.exitProcess private val log = KotlinLogging.logger { } -/** - * 机器人帐号信息. - * @property name 机器人名称, 建议与实际设定的名称相同. - * @property token 机器人 API Token. - * @property creatorId 机器人创建者, 管理机器人需要使用该信息. - */ -internal data class BotAccount( - val name: String, - val token: String, - val creatorId: Long = -1 -) { - - val id - // 不要想着每次获取都要从 token 里取出有性能损耗. - // 由于 Gson 解析方式, 如果不这么做, 会出现 token 设置前 id 初始化完成, 就只有"0"了, - // 虽然能过单元测试, 但实际使用过程是不能正常用的. - get() = token.substringBefore(":").toLong() +internal fun ProxyType.toTelegramBotsType(): DefaultBotOptions.ProxyType { + return when (this) { + ProxyType.NO_PROXY -> DefaultBotOptions.ProxyType.NO_PROXY + ProxyType.HTTP -> DefaultBotOptions.ProxyType.HTTP + ProxyType.SOCKS4 -> DefaultBotOptions.ProxyType.SOCKS4 + ProxyType.SOCKS5 -> DefaultBotOptions.ProxyType.SOCKS5 + } } -/** - * 机器人配置. - * @property account 机器人帐号信息, 用于访问 API. - * @property disableBuiltInAbility 是否禁用 AbilityBot 自带命令. - * @property extensions 该机器人启用的扩展. - * @property proxy 为该机器人单独设置的代理配置, 如无设置, 则使用 AppConfig 中的代理配置. - */ -internal data class BotConfig( - val enabled: Boolean = true, - val account: BotAccount, - val disableBuiltInAbility: Boolean = false, - val autoUpdateCommandList: Boolean = false, - /* - * 使用构件坐标来选择机器人所使用的扩展包. - * 这么做的原因是我暂时没找到一个合适的方法来让开发者方便地设定自己的扩展 Id, - * 而构件坐标(POM Reference 或者叫 GAV 坐标)是开发者创建 Maven/Gradle 项目时一定会设置的, - * 所以就直接用了. :P - */ - val extensions: Set, - val proxy: ProxyConfig? = ProxyConfig(), - val baseApiUrl: String? = ApiConstants.BASE_URL -) +internal fun ProxyConfig.toAetherProxy(): Proxy? { + return if (type == ProxyType.HTTP) { + Proxy(Proxy.TYPE_HTTP, host, port) + } else { + null + } +} -/** - * 代理配置. - * @property type 代理类型. - * @property host 代理服务端地址. - * @property port 代理服务端端口. - */ -internal data class ProxyConfig( - val type: DefaultBotOptions.ProxyType = DefaultBotOptions.ProxyType.NO_PROXY, - val host: String = "127.0.0.1", - val port: Int = 1080 -) { - - fun toAetherProxy(): Proxy? { - return if (type == DefaultBotOptions.ProxyType.HTTP) { - Proxy(Proxy.TYPE_HTTP, host, port) - } else { - null - } +internal fun MavenRepositoryConfig.toRemoteRepository(proxyConfig: ProxyConfig): RemoteRepository { + val builder = + RemoteRepository.Builder(id ?: createDefaultRepositoryId(), checkRepositoryLayout(layout), url.toString()) + if (proxy != null) { + builder.setProxy(proxy) + } else if (proxyConfig.type == ProxyType.HTTP) { + builder.setProxy(proxyConfig.toAetherProxy()) } -} - -internal data class MetricsConfig( - val enable: Boolean = false, - val port: Int = 9386, - val bindAddress: String? = "0.0.0.0", - val authenticator: UsernameAuthenticator? = null -) - -/** - * Maven 远端仓库配置. - * @property url 仓库地址. - * @property proxy 访问仓库所使用的代理, 仅支持 http/https 代理. - * @property layout 仓库布局版本, Maven 2 及以上使用 `default`, Maven 1 使用 `legacy`. - */ -internal data class MavenRepositoryConfig( - val id: String? = null, - val url: URL, - val proxy: Proxy? = null, - val layout: String = "default", - val enableReleases: Boolean = true, - val enableSnapshots: Boolean = true, - // 可能要设计个 type 来判断解析成什么类型的 Authentication. - val authentication: Authentication? = null -) { - - fun toRemoteRepository(proxyConfig: ProxyConfig): RemoteRepository { - val builder = - RemoteRepository.Builder(id ?: createDefaultRepositoryId(), checkRepositoryLayout(layout), url.toString()) - if (proxy != null) { - builder.setProxy(proxy) - } else if (proxyConfig.type == DefaultBotOptions.ProxyType.HTTP) { - builder.setProxy(proxyConfig.toAetherProxy()) - } - - builder.setReleasePolicy( - RepositoryPolicy( - enableReleases, - RepositoryPolicy.UPDATE_POLICY_NEVER, - RepositoryPolicy.CHECKSUM_POLICY_FAIL - ) + builder.setReleasePolicy( + RepositoryPolicy( + enableReleases, + RepositoryPolicy.UPDATE_POLICY_NEVER, + RepositoryPolicy.CHECKSUM_POLICY_FAIL ) - builder.setSnapshotPolicy( - RepositoryPolicy( - enableSnapshots, - RepositoryPolicy.UPDATE_POLICY_ALWAYS, - RepositoryPolicy.CHECKSUM_POLICY_WARN - ) + ) + builder.setSnapshotPolicy( + RepositoryPolicy( + enableSnapshots, + RepositoryPolicy.UPDATE_POLICY_ALWAYS, + RepositoryPolicy.CHECKSUM_POLICY_WARN ) + ) - return builder.build() - } - - private companion object { - fun checkRepositoryLayout(layoutType: String): String { - val type = layoutType.trim().lowercase() - if (type != "default" && type != "legacy") { - throw IllegalArgumentException("Invalid layout type (expecting 'default' or 'legacy')") - } - return type - } - - private val repoNumber = AtomicInteger(1) - - fun createDefaultRepositoryId(): String { - return "Repository-${repoNumber.getAndIncrement()}" - } - - } + return builder.build() } -/** - * ScalaBot App 配置. - * - * App 配置信息与 BotConfig 分开, 分别存储在各自单独的文件中. - * @property proxy Telegram API 代理配置. - * @property metrics 运行指标数据配置. 可通过时序数据库记录运行数据. - * @property mavenRepositories Maven 远端仓库配置. - * @property mavenLocalRepository Maven 本地仓库路径. 相对于运行目录 (而不是 DATA_ROOT 目录) - */ -internal data class AppConfig( - val proxy: ProxyConfig = ProxyConfig(), - val metrics: MetricsConfig = MetricsConfig(), - val mavenRepositories: List = emptyList(), - val mavenLocalRepository: String? = null -) +internal fun checkRepositoryLayout(layoutType: String): String { + val type = layoutType.trim().lowercase() + if (type != "default" && type != "legacy") { + throw IllegalArgumentException("Invalid layout type (expecting 'default' or 'legacy')") + } + return type +} + +private val repoNumberGenerator = AtomicInteger(1) + +internal fun createDefaultRepositoryId(): String { + return "Repository-${repoNumberGenerator.getAndIncrement()}" +} /** * 需要用到的路径. diff --git a/scalabot-app/src/main/kotlin/AppMain.kt b/scalabot-app/src/main/kotlin/AppMain.kt index 6b1b623..e5ba519 100644 --- a/scalabot-app/src/main/kotlin/AppMain.kt +++ b/scalabot-app/src/main/kotlin/AppMain.kt @@ -3,6 +3,10 @@ package net.lamgc.scalabot import io.prometheus.client.exporter.HTTPServer import kotlinx.coroutines.runBlocking import mu.KotlinLogging +import net.lamgc.scalabot.config.AppConfig +import net.lamgc.scalabot.config.BotConfig +import net.lamgc.scalabot.config.MetricsConfig +import net.lamgc.scalabot.config.ProxyType import net.lamgc.scalabot.util.registerShutdownHook import org.eclipse.aether.repository.LocalRepository import org.telegram.telegrambots.bots.DefaultBotOptions @@ -71,9 +75,9 @@ internal class Launcher(private val config: AppConfig = Const.config) : AutoClos private fun getMavenLocalRepository(): LocalRepository { val localPath = - if (config.mavenLocalRepository != null && config.mavenLocalRepository.isNotEmpty()) { + if (config.mavenLocalRepository != null && config.mavenLocalRepository!!.isNotEmpty()) { val repoPath = AppPaths.DATA_ROOT.file.toPath() - .resolve(config.mavenLocalRepository) + .resolve(config.mavenLocalRepository!!) .apply { if (!exists()) { if (!parent.isWritable() || !parent.isReadable()) { @@ -136,15 +140,15 @@ internal class Launcher(private val config: AppConfig = Const.config) : AutoClos log.info { "正在启动机器人 `${botConfig.account.name}`..." } val botOption = DefaultBotOptions().apply { val proxyConfig = - if (botConfig.proxy != null && botConfig.proxy.type != DefaultBotOptions.ProxyType.NO_PROXY) { + if (botConfig.proxy != null && botConfig.proxy!!.type != ProxyType.NO_PROXY) { botConfig.proxy - } else if (config.proxy.type != DefaultBotOptions.ProxyType.NO_PROXY) { + } else if (config.proxy.type != ProxyType.NO_PROXY) { config.proxy } else { null } if (proxyConfig != null) { - proxyType = proxyConfig.type + proxyType = proxyConfig.type.toTelegramBotsType() proxyHost = config.proxy.host proxyPort = config.proxy.port log.debug { "机器人 `${botConfig.account.name}` 已启用代理配置: $proxyConfig" } diff --git a/scalabot-app/src/main/kotlin/BotDBMaker.kt b/scalabot-app/src/main/kotlin/BotDBMaker.kt index bdea555..24737e9 100644 --- a/scalabot-app/src/main/kotlin/BotDBMaker.kt +++ b/scalabot-app/src/main/kotlin/BotDBMaker.kt @@ -2,6 +2,7 @@ package net.lamgc.scalabot import com.google.common.io.Files import mu.KotlinLogging +import net.lamgc.scalabot.config.BotAccount import net.lamgc.scalabot.util.toHexString import org.mapdb.DB import org.mapdb.DBException diff --git a/scalabot-app/src/main/kotlin/ScalaBot.kt b/scalabot-app/src/main/kotlin/ScalaBot.kt index 06fbd4f..6723698 100644 --- a/scalabot-app/src/main/kotlin/ScalaBot.kt +++ b/scalabot-app/src/main/kotlin/ScalaBot.kt @@ -4,6 +4,7 @@ import io.prometheus.client.Counter import io.prometheus.client.Gauge import io.prometheus.client.Summary import mu.KotlinLogging +import net.lamgc.scalabot.config.BotConfig import org.eclipse.aether.artifact.Artifact import org.telegram.abilitybots.api.bot.AbilityBot import org.telegram.abilitybots.api.db.DBContext diff --git a/scalabot-app/src/main/kotlin/util/UsernameAuthenticator.kt b/scalabot-app/src/main/kotlin/util/UsernameAuthenticator.kt deleted file mode 100644 index b5b8770..0000000 --- a/scalabot-app/src/main/kotlin/util/UsernameAuthenticator.kt +++ /dev/null @@ -1,65 +0,0 @@ -package net.lamgc.scalabot.util - -import com.google.gson.* -import com.sun.net.httpserver.BasicAuthenticator -import java.lang.reflect.Type - -class UsernameAuthenticator(private val username: String, private val password: String) : - BasicAuthenticator("metrics") { - override fun checkCredentials(username: String?, password: String?): Boolean = - this.username == username && this.password == password - - fun toJsonObject(): JsonObject = JsonObject().apply { - addProperty("username", username) - addProperty("password", password) - } - - override fun equals(other: Any?): Boolean { - return other is UsernameAuthenticator && this.username == other.username && this.password == other.password - } - - override fun hashCode(): Int { - var result = username.hashCode() - result = 31 * result + password.hashCode() - return result - } - -} - -object UsernameAuthenticatorSerializer : JsonSerializer, - JsonDeserializer { - - override fun serialize( - src: UsernameAuthenticator, - typeOfSrc: Type?, - context: JsonSerializationContext? - ): JsonElement { - return src.toJsonObject() - } - - override fun deserialize( - json: JsonElement, - typeOfT: Type?, - context: JsonDeserializationContext? - ): UsernameAuthenticator? { - if (json.isJsonNull) { - return null - } else if (!json.isJsonObject) { - throw JsonParseException("Invalid attribute value type.") - } - - val jsonObj = json.asJsonObject - - if (jsonObj["username"]?.isJsonPrimitive != true) { - throw JsonParseException("Invalid attribute value: username") - } else if (jsonObj["password"]?.isJsonPrimitive != true) { - throw JsonParseException("Invalid attribute value: password") - } - - if (jsonObj["username"].asString.isEmpty() || jsonObj["password"].asString.isEmpty()) { - throw JsonParseException("`username` or `password` is empty.") - } - return UsernameAuthenticator(jsonObj["username"].asString, jsonObj["password"].asString) - } - -} diff --git a/scalabot-app/src/test/kotlin/AppConfigTest.kt b/scalabot-app/src/test/kotlin/AppConfigTest.kt index ad39f47..a3a6f92 100644 --- a/scalabot-app/src/test/kotlin/AppConfigTest.kt +++ b/scalabot-app/src/test/kotlin/AppConfigTest.kt @@ -1,50 +1,16 @@ package net.lamgc.scalabot import com.github.stefanbirkner.systemlambda.SystemLambda -import com.google.gson.Gson -import com.google.gson.JsonObject import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.junit.jupiter.api.io.TempDir import java.io.File -import java.util.* -import kotlin.math.abs import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -internal class BotAccountTest { - - @Test - fun `id getter`() { - val accountId = abs(Random().nextInt()).toLong() - assertEquals(accountId, BotAccount("Test", "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", 0).id) - } - - @Test - fun deserializerTest() { - val accountId = abs(Random().nextInt()).toLong() - val creatorId = abs(Random().nextInt()).toLong() - val botAccountJsonObject = Gson().fromJson( - """ - { - "name": "TestBot", - "token": "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", - "creatorId": $creatorId - } - """.trimIndent(), JsonObject::class.java - ) - val botAccount = Gson().fromJson(botAccountJsonObject, BotAccount::class.java) - assertEquals(botAccountJsonObject["name"].asString, botAccount.name) - assertEquals(botAccountJsonObject["token"].asString, botAccount.token) - assertEquals(accountId, botAccount.id, "BotAccount ID does not match expectations.") - assertEquals(creatorId, botAccount.creatorId) - } - -} - internal class AppPathsTest { @Test diff --git a/scalabot-meta/build.gradle.kts b/scalabot-meta/build.gradle.kts new file mode 100644 index 0000000..f0ef77e --- /dev/null +++ b/scalabot-meta/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + kotlin("jvm") version "1.6.10" + id("org.jetbrains.kotlinx.kover") version "0.5.1" +} + +group = "net.lamgc" +version = "0.3.1" + +repositories { + mavenCentral() +} + +dependencies { + val aetherVersion = "1.1.0" + implementation("org.eclipse.aether:aether-api:$aetherVersion") + implementation("org.eclipse.aether:aether-util:$aetherVersion") + + implementation("org.telegram:telegrambots-meta:6.0.1") + + implementation("com.google.code.gson:gson:2.9.0") + + testImplementation(kotlin("test")) + testImplementation("io.mockk:mockk:1.12.4") + testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + +tasks.getByName("test") { + useJUnitPlatform() +} \ No newline at end of file diff --git a/scalabot-meta/src/main/kotlin/Configs.kt b/scalabot-meta/src/main/kotlin/Configs.kt new file mode 100644 index 0000000..f8299f0 --- /dev/null +++ b/scalabot-meta/src/main/kotlin/Configs.kt @@ -0,0 +1,108 @@ +package net.lamgc.scalabot.config + +import org.eclipse.aether.artifact.Artifact +import org.eclipse.aether.repository.Authentication +import org.eclipse.aether.repository.Proxy +import org.telegram.telegrambots.meta.ApiConstants +import java.net.URL + +/** + * 机器人帐号信息. + * @property name 机器人名称, 建议与实际设定的名称相同. + * @property token 机器人 API Token. + * @property creatorId 机器人创建者, 管理机器人需要使用该信息. + */ +data class BotAccount( + val name: String, + val token: String, + val creatorId: Long = -1 +) { + + val id + // 不要想着每次获取都要从 token 里取出有性能损耗. + // 由于 Gson 解析方式, 如果不这么做, 会出现 token 设置前 id 初始化完成, 就只有"0"了, + // 虽然能过单元测试, 但实际使用过程是不能正常用的. + get() = token.substringBefore(":").toLong() +} + +/** + * 机器人配置. + * @property account 机器人帐号信息, 用于访问 API. + * @property disableBuiltInAbility 是否禁用 AbilityBot 自带命令. + * @property extensions 该机器人启用的扩展. + * @property proxy 为该机器人单独设置的代理配置, 如无设置, 则使用 AppConfig 中的代理配置. + */ +data class BotConfig( + val enabled: Boolean = true, + val account: BotAccount, + val disableBuiltInAbility: Boolean = false, + val autoUpdateCommandList: Boolean = false, + /* + * 使用构件坐标来选择机器人所使用的扩展包. + * 这么做的原因是我暂时没找到一个合适的方法来让开发者方便地设定自己的扩展 Id, + * 而构件坐标(POM Reference 或者叫 GAV 坐标)是开发者创建 Maven/Gradle 项目时一定会设置的, + * 所以就直接用了. :P + */ + val extensions: Set, + val proxy: ProxyConfig? = ProxyConfig(), + val baseApiUrl: String? = ApiConstants.BASE_URL +) + +enum class ProxyType { + NO_PROXY, + HTTP, + SOCKS4, + SOCKS5 +} + +/** + * 代理配置. + * @property type 代理类型. + * @property host 代理服务端地址. + * @property port 代理服务端端口. + */ +data class ProxyConfig( + val type: ProxyType = ProxyType.NO_PROXY, + val host: String = "127.0.0.1", + val port: Int = 1080 +) + +data class MetricsConfig( + val enable: Boolean = false, + val port: Int = 9386, + val bindAddress: String? = "0.0.0.0", + val authenticator: UsernameAuthenticator? = null +) + +/** + * Maven 远端仓库配置. + * @property url 仓库地址. + * @property proxy 访问仓库所使用的代理, 仅支持 http/https 代理. + * @property layout 仓库布局版本, Maven 2 及以上使用 `default`, Maven 1 使用 `legacy`. + */ +data class MavenRepositoryConfig( + val id: String? = null, + val url: URL, + val proxy: Proxy? = null, + val layout: String = "default", + val enableReleases: Boolean = true, + val enableSnapshots: Boolean = true, + // 可能要设计个 type 来判断解析成什么类型的 Authentication. + val authentication: Authentication? = null +) + +/** + * ScalaBot App 配置. + * + * App 配置信息与 BotConfig 分开, 分别存储在各自单独的文件中. + * @property proxy Telegram API 代理配置. + * @property metrics 运行指标数据配置. 可通过时序数据库记录运行数据. + * @property mavenRepositories Maven 远端仓库配置. + * @property mavenLocalRepository Maven 本地仓库路径. 相对于运行目录 (而不是 DATA_ROOT 目录) + */ +data class AppConfig( + val proxy: ProxyConfig = ProxyConfig(), + val metrics: MetricsConfig = MetricsConfig(), + val mavenRepositories: List = emptyList(), + val mavenLocalRepository: String? = null +) diff --git a/scalabot-meta/src/main/kotlin/UsernameAuthenticator.kt b/scalabot-meta/src/main/kotlin/UsernameAuthenticator.kt new file mode 100644 index 0000000..f81a6f4 --- /dev/null +++ b/scalabot-meta/src/main/kotlin/UsernameAuthenticator.kt @@ -0,0 +1,26 @@ +package net.lamgc.scalabot.config + +import com.google.gson.JsonObject +import com.sun.net.httpserver.BasicAuthenticator + +class UsernameAuthenticator(private val username: String, private val password: String) : + BasicAuthenticator("metrics") { + override fun checkCredentials(username: String?, password: String?): Boolean = + this.username == username && this.password == password + + fun toJsonObject(): JsonObject = JsonObject().apply { + addProperty("username", username) + addProperty("password", password) + } + + override fun equals(other: Any?): Boolean { + return other is UsernameAuthenticator && this.username == other.username && this.password == other.password + } + + override fun hashCode(): Int { + var result = username.hashCode() + result = 31 * result + password.hashCode() + return result + } + +} diff --git a/scalabot-app/src/main/kotlin/util/Serializers.kt b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt similarity index 69% rename from scalabot-app/src/main/kotlin/util/Serializers.kt rename to scalabot-meta/src/main/kotlin/serializer/Serializer.kt index b574154..ab6de86 100644 --- a/scalabot-app/src/main/kotlin/util/Serializers.kt +++ b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt @@ -1,40 +1,41 @@ -package net.lamgc.scalabot.util +package net.lamgc.scalabot.config.serializer import com.google.gson.* -import net.lamgc.scalabot.MavenRepositoryConfig +import net.lamgc.scalabot.config.MavenRepositoryConfig +import net.lamgc.scalabot.config.ProxyType +import net.lamgc.scalabot.config.UsernameAuthenticator import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.DefaultArtifact import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.Proxy import org.eclipse.aether.util.repository.AuthenticationBuilder -import org.telegram.telegrambots.bots.DefaultBotOptions import java.lang.reflect.Type import java.net.URL -internal object ProxyTypeSerializer : JsonDeserializer, - JsonSerializer { +object ProxyTypeSerializer : JsonDeserializer, + JsonSerializer { override fun deserialize( json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext? - ): DefaultBotOptions.ProxyType { + ): ProxyType { if (json.isJsonNull) { - return DefaultBotOptions.ProxyType.NO_PROXY + return ProxyType.NO_PROXY } if (!json.isJsonPrimitive) { throw JsonParseException("Wrong configuration value type.") } val value = json.asString.trim() try { - return DefaultBotOptions.ProxyType.valueOf(value.uppercase()) + return ProxyType.valueOf(value.uppercase()) } catch (e: IllegalArgumentException) { throw JsonParseException("Invalid value: $value") } } override fun serialize( - src: DefaultBotOptions.ProxyType, + src: ProxyType, typeOfSrc: Type?, context: JsonSerializationContext? ): JsonElement { @@ -42,7 +43,7 @@ internal object ProxyTypeSerializer : JsonDeserializer, JsonDeserializer { +object ArtifactSerializer : JsonSerializer, JsonDeserializer { override fun serialize(src: Artifact, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { val gavBuilder = StringBuilder("${src.groupId}:${src.artifactId}") if (!src.extension.equals("jar")) { @@ -63,7 +64,7 @@ internal object ArtifactSerializer : JsonSerializer, JsonDeserializer< } -internal object AuthenticationSerializer : JsonDeserializer { +object AuthenticationSerializer : JsonDeserializer { override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Authentication { if (json !is JsonObject) { @@ -90,7 +91,7 @@ private object SerializerUtils { } } -internal object MavenRepositoryConfigSerializer +object MavenRepositoryConfigSerializer : JsonDeserializer { override fun deserialize( @@ -125,3 +126,41 @@ internal object MavenRepositoryConfigSerializer } } } + +object UsernameAuthenticatorSerializer : JsonSerializer, + JsonDeserializer { + + override fun serialize( + src: UsernameAuthenticator, + typeOfSrc: Type?, + context: JsonSerializationContext? + ): JsonElement { + return src.toJsonObject() + } + + override fun deserialize( + json: JsonElement, + typeOfT: Type?, + context: JsonDeserializationContext? + ): UsernameAuthenticator? { + if (json.isJsonNull) { + return null + } else if (!json.isJsonObject) { + throw JsonParseException("Invalid attribute value type.") + } + + val jsonObj = json.asJsonObject + + if (jsonObj["username"]?.isJsonPrimitive != true) { + throw JsonParseException("Invalid attribute value: username") + } else if (jsonObj["password"]?.isJsonPrimitive != true) { + throw JsonParseException("Invalid attribute value: password") + } + + if (jsonObj["username"].asString.isEmpty() || jsonObj["password"].asString.isEmpty()) { + throw JsonParseException("`username` or `password` is empty.") + } + return UsernameAuthenticator(jsonObj["username"].asString, jsonObj["password"].asString) + } + +} diff --git a/scalabot-meta/src/test/kotlin/BotAccountTest.kt b/scalabot-meta/src/test/kotlin/BotAccountTest.kt new file mode 100644 index 0000000..db11830 --- /dev/null +++ b/scalabot-meta/src/test/kotlin/BotAccountTest.kt @@ -0,0 +1,38 @@ +package net.lamgc.scalabot.config + +import com.google.gson.Gson +import com.google.gson.JsonObject +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.* +import kotlin.math.abs + +internal class BotAccountTest { + + @Test + fun `id getter`() { + val accountId = abs(Random().nextInt()).toLong() + assertEquals(accountId, BotAccount("Test", "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", 0).id) + } + + @Test + fun deserializerTest() { + val accountId = abs(Random().nextInt()).toLong() + val creatorId = abs(Random().nextInt()).toLong() + val botAccountJsonObject = Gson().fromJson( + """ + { + "name": "TestBot", + "token": "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", + "creatorId": $creatorId + } + """.trimIndent(), JsonObject::class.java + ) + val botAccount = Gson().fromJson(botAccountJsonObject, BotAccount::class.java) + assertEquals(botAccountJsonObject["name"].asString, botAccount.name) + assertEquals(botAccountJsonObject["token"].asString, botAccount.token) + assertEquals(accountId, botAccount.id, "BotAccount ID does not match expectations.") + assertEquals(creatorId, botAccount.creatorId) + } + +} \ No newline at end of file diff --git a/scalabot-app/src/test/kotlin/util/ArtifactSerializerTest.kt b/scalabot-meta/src/test/kotlin/serializer/ArtifactSerializerTest.kt similarity index 97% rename from scalabot-app/src/test/kotlin/util/ArtifactSerializerTest.kt rename to scalabot-meta/src/test/kotlin/serializer/ArtifactSerializerTest.kt index 100c5b4..6c12ea4 100644 --- a/scalabot-app/src/test/kotlin/util/ArtifactSerializerTest.kt +++ b/scalabot-meta/src/test/kotlin/serializer/ArtifactSerializerTest.kt @@ -1,6 +1,6 @@ @file:Suppress("PackageDirectoryMismatch") -package net.lamgc.scalabot.util +package net.lamgc.scalabot.config.serializer import com.google.gson.JsonObject import com.google.gson.JsonParseException diff --git a/scalabot-app/src/test/kotlin/util/SerializersKtTest.kt b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt similarity index 95% rename from scalabot-app/src/test/kotlin/util/SerializersKtTest.kt rename to scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt index 591c8e8..0a116ed 100644 --- a/scalabot-app/src/test/kotlin/util/SerializersKtTest.kt +++ b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt @@ -1,19 +1,16 @@ -package util +package net.lamgc.scalabot.config.serializer import com.google.gson.* import io.mockk.every import io.mockk.mockk import io.mockk.verify -import net.lamgc.scalabot.MavenRepositoryConfig -import net.lamgc.scalabot.util.AuthenticationSerializer -import net.lamgc.scalabot.util.MavenRepositoryConfigSerializer -import net.lamgc.scalabot.util.ProxyTypeSerializer +import net.lamgc.scalabot.config.MavenRepositoryConfig +import net.lamgc.scalabot.config.ProxyType import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.AuthenticationContext import org.eclipse.aether.repository.Proxy import org.intellij.lang.annotations.Language import org.junit.jupiter.api.Assertions.assertThrows -import org.telegram.telegrambots.bots.DefaultBotOptions import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method import java.lang.reflect.Type @@ -26,7 +23,7 @@ internal class SerializersKtTest { private val method: Method init { - val clazz = Class.forName("net.lamgc.scalabot.util.SerializerUtils") + val clazz = Class.forName("net.lamgc.scalabot.config.serializer.SerializerUtils") method = clazz.getDeclaredMethod("checkJsonKey", JsonObject::class.java, String::class.java) method.isAccessible = true instance = clazz.getDeclaredField("INSTANCE").apply { @@ -69,7 +66,7 @@ internal class ProxyTypeSerializerTest { @Test fun `serialize test`() { - for (type in DefaultBotOptions.ProxyType.values()) { + for (type in ProxyType.values()) { assertEquals( JsonPrimitive(type.name), ProxyTypeSerializer.serialize(type, null, null), "ProxyType 序列化结果与预期不符." @@ -91,11 +88,11 @@ internal class ProxyTypeSerializerTest { } assertEquals( - DefaultBotOptions.ProxyType.NO_PROXY, + ProxyType.NO_PROXY, ProxyTypeSerializer.deserialize(JsonNull.INSTANCE, null, null) ) - for (type in DefaultBotOptions.ProxyType.values()) { + for (type in ProxyType.values()) { assertEquals( type, ProxyTypeSerializer.deserialize(JsonPrimitive(type.name), null, null), "ProxyType 反序列化结果与预期不符." diff --git a/scalabot-app/src/test/kotlin/util/UsernameAuthenticatorTest.kt b/scalabot-meta/src/test/kotlin/serializer/UsernameAuthenticatorTest.kt similarity index 97% rename from scalabot-app/src/test/kotlin/util/UsernameAuthenticatorTest.kt rename to scalabot-meta/src/test/kotlin/serializer/UsernameAuthenticatorTest.kt index 92629db..8f71a37 100644 --- a/scalabot-app/src/test/kotlin/util/UsernameAuthenticatorTest.kt +++ b/scalabot-meta/src/test/kotlin/serializer/UsernameAuthenticatorTest.kt @@ -1,6 +1,7 @@ -package net.lamgc.scalabot.util +package net.lamgc.scalabot.config.serializer import com.google.gson.* +import net.lamgc.scalabot.config.UsernameAuthenticator import org.junit.jupiter.api.assertThrows import kotlin.test.* diff --git a/settings.gradle.kts b/settings.gradle.kts index 7db5f57..3b0db76 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,3 +4,4 @@ rootProject.name = "scalabot" include(":scalabot-app") include(":scalabot-extension") include("scalabot-ext-example") +include("scalabot-meta") From 8a33448b19de9b1f5fdee8a99042056986c87dde Mon Sep 17 00:00:00 2001 From: LamGC Date: Thu, 23 Jun 2022 03:52:04 +0800 Subject: [PATCH 02/22] =?UTF-8?q?refactor(config):=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E8=AE=BF=E9=97=AE=E6=9D=83.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 迁移前, createDefaultRepositoryId 方法和 checkRepositoryLayout 方法已经是 Private 了, 迁移中出现差错导致变更为 internal, 现已修复. Pull Request #8 --- scalabot-app/src/main/kotlin/AppConfigs.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scalabot-app/src/main/kotlin/AppConfigs.kt b/scalabot-app/src/main/kotlin/AppConfigs.kt index 27700cb..326beb5 100644 --- a/scalabot-app/src/main/kotlin/AppConfigs.kt +++ b/scalabot-app/src/main/kotlin/AppConfigs.kt @@ -66,7 +66,7 @@ internal fun MavenRepositoryConfig.toRemoteRepository(proxyConfig: ProxyConfig): return builder.build() } -internal fun checkRepositoryLayout(layoutType: String): String { +private fun checkRepositoryLayout(layoutType: String): String { val type = layoutType.trim().lowercase() if (type != "default" && type != "legacy") { throw IllegalArgumentException("Invalid layout type (expecting 'default' or 'legacy')") @@ -76,7 +76,7 @@ internal fun checkRepositoryLayout(layoutType: String): String { private val repoNumberGenerator = AtomicInteger(1) -internal fun createDefaultRepositoryId(): String { +private fun createDefaultRepositoryId(): String { return "Repository-${repoNumberGenerator.getAndIncrement()}" } From d5e66156b97c5d7086f11a4e7557d3b0e0a39cc3 Mon Sep 17 00:00:00 2001 From: LamGC Date: Thu, 23 Jun 2022 11:37:37 +0800 Subject: [PATCH 03/22] =?UTF-8?q?perf(config):=20=E4=BC=98=E5=8C=96=20Arti?= =?UTF-8?q?fact=20=E7=9A=84=E5=BA=8F=E5=88=97=E5=8C=96=E8=BF=87=E7=A8=8B.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AbstractArtifact 已经有官方的 toString 实现了, 故不再多此一举. 同时, 如果有不基于 AbstractArtifact 的 Artifact 实现, 将会转换成 DefaultArtifact 并直接使用 toString. --- .../src/main/kotlin/serializer/Serializer.kt | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/scalabot-meta/src/main/kotlin/serializer/Serializer.kt b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt index ab6de86..37571af 100644 --- a/scalabot-meta/src/main/kotlin/serializer/Serializer.kt +++ b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt @@ -4,6 +4,7 @@ import com.google.gson.* import net.lamgc.scalabot.config.MavenRepositoryConfig import net.lamgc.scalabot.config.ProxyType import net.lamgc.scalabot.config.UsernameAuthenticator +import org.eclipse.aether.artifact.AbstractArtifact import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.DefaultArtifact import org.eclipse.aether.repository.Authentication @@ -45,14 +46,19 @@ object ProxyTypeSerializer : JsonDeserializer, object ArtifactSerializer : JsonSerializer, JsonDeserializer { override fun serialize(src: Artifact, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { - val gavBuilder = StringBuilder("${src.groupId}:${src.artifactId}") - if (!src.extension.equals("jar")) { - gavBuilder.append(':').append(src.extension) + return if (src is AbstractArtifact) { + JsonPrimitive(src.toString()) + } else { + JsonPrimitive( + DefaultArtifact( + src.groupId, + src.artifactId, + src.classifier, + src.extension, + src.version + ).toString() + ) } - if (src.classifier.isNotEmpty()) { - gavBuilder.append(':').append(src.classifier) - } - return JsonPrimitive(gavBuilder.append(':').append(src.version).toString()) } override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): Artifact { From a2667438f236b2be05e4ace2559e187e48db0780 Mon Sep 17 00:00:00 2001 From: LamGC Date: Thu, 23 Jun 2022 12:25:14 +0800 Subject: [PATCH 04/22] =?UTF-8?q?refactor(config):=20=E5=8C=85=E8=A3=85=20?= =?UTF-8?q?Serializer=20=E5=8F=AF=E8=83=BD=E6=8A=9B=E5=87=BA=E7=9A=84?= =?UTF-8?q?=E5=BC=82=E5=B8=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加对 Serializer 中可能抛出的异常(例如 MalformedURLException, IllegalArgumentException)包装成 JsonParseException, 以避免异常类型混乱的问题. --- .../src/main/kotlin/serializer/Serializer.kt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/scalabot-meta/src/main/kotlin/serializer/Serializer.kt b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt index 37571af..d28e42e 100644 --- a/scalabot-meta/src/main/kotlin/serializer/Serializer.kt +++ b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt @@ -11,6 +11,7 @@ import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.Proxy import org.eclipse.aether.util.repository.AuthenticationBuilder import java.lang.reflect.Type +import java.net.MalformedURLException import java.net.URL object ProxyTypeSerializer : JsonDeserializer, @@ -65,7 +66,12 @@ object ArtifactSerializer : JsonSerializer, JsonDeserializer if (!json.isJsonPrimitive) { throw JsonParseException("Wrong configuration value type.") } - return DefaultArtifact(json.asString.trim()) + val artifactStr = json.asString.trim() + try { + return DefaultArtifact(artifactStr) + } catch (e: IllegalArgumentException) { + throw JsonParseException("Invalid artifact format: `${artifactStr}`.") + } } } @@ -124,10 +130,14 @@ object MavenRepositoryConfigSerializer ) } is JsonPrimitive -> { - MavenRepositoryConfig(url = URL(json.asString)) + try { + return MavenRepositoryConfig(url = URL(json.asString)) + } catch (e: MalformedURLException) { + throw JsonParseException("Invalid URL: ${json.asString}", e) + } } else -> { - throw JsonParseException("Unsupported Maven warehouse configuration type.") + throw JsonParseException("Unsupported Maven repository configuration type. (Only support JSON object or url string)") } } } From b12758bd189ce7b5c561aab8a90208fb664c036a Mon Sep 17 00:00:00 2001 From: LamGC Date: Fri, 24 Jun 2022 02:00:11 +0800 Subject: [PATCH 05/22] =?UTF-8?q?refactor(config):=20=E6=9B=B4=E6=94=B9?= =?UTF-8?q?=E9=83=A8=E5=88=86=E9=85=8D=E7=BD=AE=E7=B1=BB=E7=9A=84=E5=B1=9E?= =?UTF-8?q?=E6=80=A7=E9=BB=98=E8=AE=A4=E5=80=BC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为了保证扩展中命令的权限判断有效性, 故移除 BotAccount 中 creatorId 字段的默认值, 此改动将要求用户提供准确的 Bot 创建者 Id. 这个改动拖得越久, 影响的范围就越大. 另外, 为 BotConfig 中的 extensions 属性和 proxy 属性增加默认值, 以减少意义重复的情况(例如当用户没设置 proxy 属性时提供一个 type 为 NO_PROXY 的 ProxyConfig, 无需判断是否为 null). --- scalabot-meta/src/main/kotlin/Configs.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scalabot-meta/src/main/kotlin/Configs.kt b/scalabot-meta/src/main/kotlin/Configs.kt index f8299f0..28d02d4 100644 --- a/scalabot-meta/src/main/kotlin/Configs.kt +++ b/scalabot-meta/src/main/kotlin/Configs.kt @@ -15,7 +15,7 @@ import java.net.URL data class BotAccount( val name: String, val token: String, - val creatorId: Long = -1 + val creatorId: Long ) { val id @@ -43,8 +43,8 @@ data class BotConfig( * 而构件坐标(POM Reference 或者叫 GAV 坐标)是开发者创建 Maven/Gradle 项目时一定会设置的, * 所以就直接用了. :P */ - val extensions: Set, - val proxy: ProxyConfig? = ProxyConfig(), + val extensions: Set = emptySet(), + val proxy: ProxyConfig = ProxyConfig(type = ProxyType.NO_PROXY), val baseApiUrl: String? = ApiConstants.BASE_URL ) From a1790a07163c4c04ab7df4f21975267e6e8f86f5 Mon Sep 17 00:00:00 2001 From: LamGC Date: Fri, 24 Jun 2022 19:08:41 +0800 Subject: [PATCH 06/22] =?UTF-8?q?perf(config):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BD=BF=E7=94=A8=E8=BF=87=E7=A8=8B=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E5=88=A4=E6=96=AD.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 通过调整部分属性的 null-safety 特性, 移除了部分 non-null 判断, 略微(真的很略微)提高了性能(虽然仅限于启动). --- scalabot-app/src/main/kotlin/AppMain.kt | 6 ++---- scalabot-meta/src/main/kotlin/Configs.kt | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/scalabot-app/src/main/kotlin/AppMain.kt b/scalabot-app/src/main/kotlin/AppMain.kt index e5ba519..7146390 100644 --- a/scalabot-app/src/main/kotlin/AppMain.kt +++ b/scalabot-app/src/main/kotlin/AppMain.kt @@ -140,7 +140,7 @@ internal class Launcher(private val config: AppConfig = Const.config) : AutoClos log.info { "正在启动机器人 `${botConfig.account.name}`..." } val botOption = DefaultBotOptions().apply { val proxyConfig = - if (botConfig.proxy != null && botConfig.proxy!!.type != ProxyType.NO_PROXY) { + if (botConfig.proxy.type != ProxyType.NO_PROXY) { botConfig.proxy } else if (config.proxy.type != ProxyType.NO_PROXY) { config.proxy @@ -154,9 +154,7 @@ internal class Launcher(private val config: AppConfig = Const.config) : AutoClos log.debug { "机器人 `${botConfig.account.name}` 已启用代理配置: $proxyConfig" } } - if (botConfig.baseApiUrl != null) { - baseUrl = botConfig.baseApiUrl - } + baseUrl = botConfig.baseApiUrl } val account = botConfig.account diff --git a/scalabot-meta/src/main/kotlin/Configs.kt b/scalabot-meta/src/main/kotlin/Configs.kt index 28d02d4..60a335d 100644 --- a/scalabot-meta/src/main/kotlin/Configs.kt +++ b/scalabot-meta/src/main/kotlin/Configs.kt @@ -45,7 +45,7 @@ data class BotConfig( */ val extensions: Set = emptySet(), val proxy: ProxyConfig = ProxyConfig(type = ProxyType.NO_PROXY), - val baseApiUrl: String? = ApiConstants.BASE_URL + val baseApiUrl: String = ApiConstants.BASE_URL ) enum class ProxyType { From 4a160ad42b537dc75d733f22b8253929440212a9 Mon Sep 17 00:00:00 2001 From: LamGC Date: Fri, 24 Jun 2022 19:10:39 +0800 Subject: [PATCH 07/22] =?UTF-8?q?refactor(config):=20=E6=9B=B4=E6=94=B9=20?= =?UTF-8?q?BotConfig.enabled=20=E7=9A=84=E9=BB=98=E8=AE=A4=E5=80=BC?= =?UTF-8?q?=E4=B8=BA=20false.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更改 enabled 的默认值, 以防止意外启动 Bot. 同时让 bot.json 在初始化时设为 true, 方便用户改完就能启动. --- scalabot-app/src/main/kotlin/AppConfigs.kt | 7 ++++--- scalabot-meta/src/main/kotlin/Configs.kt | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/scalabot-app/src/main/kotlin/AppConfigs.kt b/scalabot-app/src/main/kotlin/AppConfigs.kt index 326beb5..293136e 100644 --- a/scalabot-app/src/main/kotlin/AppConfigs.kt +++ b/scalabot-app/src/main/kotlin/AppConfigs.kt @@ -131,7 +131,7 @@ internal enum class AppPaths( GsonConst.botConfigGson.toJson( setOf( BotConfig( - enabled = false, + enabled = true, proxy = ProxyConfig(), account = BotAccount( "Bot Username", @@ -226,14 +226,15 @@ private object GsonConst { .create() val appConfigGson: Gson = baseGson.newBuilder() - .registerTypeAdapter(DefaultBotOptions.ProxyType::class.java, ProxyTypeSerializer) + .registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer) .registerTypeAdapter(MavenRepositoryConfig::class.java, MavenRepositoryConfigSerializer) .registerTypeAdapter(Authentication::class.java, AuthenticationSerializer) .registerTypeAdapter(UsernameAuthenticator::class.java, UsernameAuthenticatorSerializer) .create() val botConfigGson: Gson = baseGson.newBuilder() - .registerTypeAdapter(DefaultBotOptions.ProxyType::class.java, ProxyTypeSerializer) + .registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer) + .registerTypeAdapter(BotConfig::class.java, BotConfigSerializer) .registerTypeAdapter(Artifact::class.java, ArtifactSerializer) .create() } diff --git a/scalabot-meta/src/main/kotlin/Configs.kt b/scalabot-meta/src/main/kotlin/Configs.kt index 60a335d..c1039d9 100644 --- a/scalabot-meta/src/main/kotlin/Configs.kt +++ b/scalabot-meta/src/main/kotlin/Configs.kt @@ -33,7 +33,7 @@ data class BotAccount( * @property proxy 为该机器人单独设置的代理配置, 如无设置, 则使用 AppConfig 中的代理配置. */ data class BotConfig( - val enabled: Boolean = true, + val enabled: Boolean = false, val account: BotAccount, val disableBuiltInAbility: Boolean = false, val autoUpdateCommandList: Boolean = false, From 084280564af58d1af22db5b57c67577d93bd820e Mon Sep 17 00:00:00 2001 From: LamGC Date: Fri, 24 Jun 2022 19:36:39 +0800 Subject: [PATCH 08/22] =?UTF-8?q?fix(config):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=B8=A4=E4=B8=AA=E5=BA=8F=E5=88=97=E5=8C=96=E5=99=A8=E6=9D=A5?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=9B=A0=20Gson=20=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E7=9A=84=E8=A7=A3=E6=9E=90=E9=94=99=E8=AF=AF.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 由于 Gson 的解析方式不能正确处理 Kotlin 的 null-safety 属性, 因此添加两个 Serializer, 手动解析 Json 以避开这个问题. Close #9 --- .../src/main/kotlin/serializer/Serializer.kt | 85 ++++++++++++++++++- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/scalabot-meta/src/main/kotlin/serializer/Serializer.kt b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt index d28e42e..1341272 100644 --- a/scalabot-meta/src/main/kotlin/serializer/Serializer.kt +++ b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt @@ -1,15 +1,15 @@ package net.lamgc.scalabot.config.serializer import com.google.gson.* -import net.lamgc.scalabot.config.MavenRepositoryConfig -import net.lamgc.scalabot.config.ProxyType -import net.lamgc.scalabot.config.UsernameAuthenticator +import com.google.gson.reflect.TypeToken +import net.lamgc.scalabot.config.* import org.eclipse.aether.artifact.AbstractArtifact import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.DefaultArtifact import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.Proxy import org.eclipse.aether.util.repository.AuthenticationBuilder +import org.telegram.telegrambots.meta.ApiConstants import java.lang.reflect.Type import java.net.MalformedURLException import java.net.URL @@ -180,3 +180,82 @@ object UsernameAuthenticatorSerializer : JsonSerializer, } } + +object ProxyConfigSerializer : JsonSerializer, JsonDeserializer { + override fun serialize(src: ProxyConfig?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { + if (src == null) { + return JsonNull.INSTANCE + } + return JsonObject().apply { + addProperty("type", src.type.name) + addProperty("host", src.host) + addProperty("port", src.port) + } + } + + override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): ProxyConfig { + if (json == null || json.isJsonNull) { + return ProxyConfig() + } else if (json !is JsonObject) { + throw JsonParseException("Invalid json type.") + } + + val typeStr = json["type"]?.asString ?: return ProxyConfig() + val type = try { + ProxyType.valueOf(typeStr) + } catch (e: IllegalArgumentException) { + throw JsonParseException("Invalid proxy type: `$typeStr`") + } + + if (!json.has("host") || !json.has("port")) { + throw JsonParseException("Missing `host` field or `port` field.") + } + + return ProxyConfig( + type = type, + host = json["host"].asString, + port = json["port"].asInt + ) + } + +} + +object BotConfigSerializer : JsonSerializer, JsonDeserializer { + + override fun serialize(src: BotConfig, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + return JsonObject().apply { + addProperty("enabled", src.enabled) + add("account", context.serialize(src.account)) + addProperty("disableBuiltInAbility", src.disableBuiltInAbility) + addProperty("autoUpdateCommandList", src.autoUpdateCommandList) + add("extensions", context.serialize(src.extensions)) + add("proxy", ProxyConfigSerializer.serialize(src.proxy, ProxyConfig::class.java, context)) + addProperty("baseApiUrl", src.baseApiUrl) + } + } + + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): BotConfig { + if (json !is JsonObject) { + throw JsonParseException("Unsupported JSON type.") + } + + if (!json.has("account")) { + throw JsonParseException("Missing `account` field.") + } else if (!json.get("account").isJsonObject) { + throw JsonParseException("Invalid `account` field type.") + } + + // 从 json 反序列化 BotConfig(使用构造函数) + return BotConfig( + enabled = json.get("enabled")?.asBoolean ?: true, + account = context.deserialize(json.get("account"), BotAccount::class.java)!!, + disableBuiltInAbility = json.get("disableBuiltInAbility")?.asBoolean ?: false, + autoUpdateCommandList = json.get("autoUpdateCommandList")?.asBoolean ?: false, + extensions = context.deserialize(json.get("extensions"), object : TypeToken>() {}.type) + ?: emptySet(), + proxy = context.deserialize(json.get("proxy"), ProxyConfig::class.java) ?: ProxyConfig(), + baseApiUrl = json.get("baseApiUrl")?.asString ?: ApiConstants.BASE_URL + ) + } +} + From 8f8d7635662ef3050a55f051f6cf0016f8cd96cf Mon Sep 17 00:00:00 2001 From: LamGC Date: Fri, 24 Jun 2022 19:38:47 +0800 Subject: [PATCH 09/22] =?UTF-8?q?test(config):=20=E5=90=88=E5=B9=B6=20BotA?= =?UTF-8?q?ccountTest=20=E5=B9=B6=E8=A1=A5=E5=85=85=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=B1=BB=E7=9A=84=E8=A7=A3=E6=9E=90=E6=B5=8B?= =?UTF-8?q?=E8=AF=95.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 BotAccountTest 合并入 ConfigsTest, 方便归类测试, 并补充其他配置类的 JSON 解析; 此部分独立于 Serializer 以防止后续更改出现潜在的解析错误. --- .../src/test/kotlin/BotAccountTest.kt | 38 -- scalabot-meta/src/test/kotlin/ConfigsTest.kt | 475 ++++++++++++++++++ 2 files changed, 475 insertions(+), 38 deletions(-) delete mode 100644 scalabot-meta/src/test/kotlin/BotAccountTest.kt create mode 100644 scalabot-meta/src/test/kotlin/ConfigsTest.kt diff --git a/scalabot-meta/src/test/kotlin/BotAccountTest.kt b/scalabot-meta/src/test/kotlin/BotAccountTest.kt deleted file mode 100644 index db11830..0000000 --- a/scalabot-meta/src/test/kotlin/BotAccountTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -package net.lamgc.scalabot.config - -import com.google.gson.Gson -import com.google.gson.JsonObject -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import java.util.* -import kotlin.math.abs - -internal class BotAccountTest { - - @Test - fun `id getter`() { - val accountId = abs(Random().nextInt()).toLong() - assertEquals(accountId, BotAccount("Test", "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", 0).id) - } - - @Test - fun deserializerTest() { - val accountId = abs(Random().nextInt()).toLong() - val creatorId = abs(Random().nextInt()).toLong() - val botAccountJsonObject = Gson().fromJson( - """ - { - "name": "TestBot", - "token": "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", - "creatorId": $creatorId - } - """.trimIndent(), JsonObject::class.java - ) - val botAccount = Gson().fromJson(botAccountJsonObject, BotAccount::class.java) - assertEquals(botAccountJsonObject["name"].asString, botAccount.name) - assertEquals(botAccountJsonObject["token"].asString, botAccount.token) - assertEquals(accountId, botAccount.id, "BotAccount ID does not match expectations.") - assertEquals(creatorId, botAccount.creatorId) - } - -} \ No newline at end of file diff --git a/scalabot-meta/src/test/kotlin/ConfigsTest.kt b/scalabot-meta/src/test/kotlin/ConfigsTest.kt new file mode 100644 index 0000000..b3fb0a2 --- /dev/null +++ b/scalabot-meta/src/test/kotlin/ConfigsTest.kt @@ -0,0 +1,475 @@ +package net.lamgc.scalabot.config + +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonObject +import net.lamgc.scalabot.config.serializer.* +import org.eclipse.aether.artifact.Artifact +import org.eclipse.aether.artifact.DefaultArtifact +import org.eclipse.aether.repository.Authentication +import org.eclipse.aether.repository.Proxy +import org.intellij.lang.annotations.Language +import org.junit.jupiter.api.Assertions +import java.net.URL +import java.util.* +import kotlin.math.abs +import kotlin.test.* + +internal class BotAccountTest { + + @org.junit.jupiter.api.Test + fun `id getter`() { + val accountId = abs(Random().nextInt()).toLong() + Assertions.assertEquals(accountId, BotAccount("Test", "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", 0).id) + } + + private val gson = GsonBuilder() + .create() + + @org.junit.jupiter.api.Test + fun deserializerTest() { + val accountId = abs(Random().nextInt()).toLong() + val creatorId = abs(Random().nextInt()).toLong() + val botAccountJsonObject = gson.fromJson( + """ + { + "name": "TestBot", + "token": "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", + "creatorId": $creatorId + } + """.trimIndent(), JsonObject::class.java + ) + val botAccount = Gson().fromJson(botAccountJsonObject, BotAccount::class.java) + assertEquals(accountId, botAccount.id) + assertEquals("TestBot", botAccount.name) + assertEquals(creatorId, botAccount.creatorId) + assertEquals("${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", botAccount.token) + } + + @org.junit.jupiter.api.Test + fun serializerTest() { + val accountId = abs(Random().nextInt()).toLong() + val creatorId = abs(Random().nextInt()).toLong() + val botAccount = BotAccount("TestBot", "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", creatorId) + val botAccountJsonObject = gson.toJsonTree(botAccount) + assertTrue(botAccountJsonObject is JsonObject) + assertEquals(botAccount.name, botAccountJsonObject["name"].asString) + assertEquals(botAccount.token, botAccountJsonObject["token"].asString) + assertNull(botAccountJsonObject["id"]) + Assertions.assertEquals(creatorId, botAccountJsonObject["creatorId"].asLong) + } + +} + +internal class BotConfigTest { + + private val gson = GsonBuilder() + .registerTypeAdapter(BotConfig::class.java, BotConfigSerializer) + .registerTypeAdapter(Artifact::class.java, ArtifactSerializer) + .registerTypeAdapter(ProxyConfig::class.java, ProxyConfigSerializer) + .create() + + @Test + fun `json serialize`() { + val minimumExpectConfig = BotConfig( + account = BotAccount( + name = "TestBot", + token = "123456789:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", + creatorId = 123456789L + ), + ) + + val json = gson.toJsonTree(minimumExpectConfig) + assertTrue(json is JsonObject) + assertEquals(minimumExpectConfig.enabled, json.get("enabled").asBoolean) + + assertEquals(minimumExpectConfig.account.name, json.get("account").asJsonObject.get("name").asString) + assertEquals(minimumExpectConfig.account.token, json.get("account").asJsonObject.get("token").asString) + assertEquals(minimumExpectConfig.account.creatorId, json.get("account").asJsonObject.get("creatorId").asLong) + assertNull(json.get("account").asJsonObject.get("id")) + + assertEquals(minimumExpectConfig.proxy.host, json.get("proxy").asJsonObject.get("host").asString) + assertEquals(minimumExpectConfig.proxy.port, json.get("proxy").asJsonObject.get("port").asInt) + assertEquals(minimumExpectConfig.proxy.type.name, json.get("proxy").asJsonObject.get("type").asString) + + assertEquals(minimumExpectConfig.disableBuiltInAbility, json.get("disableBuiltInAbility").asBoolean) + assertEquals(minimumExpectConfig.autoUpdateCommandList, json.get("autoUpdateCommandList").asBoolean) + + assertNotNull(json.get("extensions")) + assertTrue(json.get("extensions").isJsonArray) + assertTrue(json.get("extensions").asJsonArray.isEmpty) + + assertEquals(minimumExpectConfig.baseApiUrl, json.get("baseApiUrl").asString) + } + + // 测试 BotConfig 的 json 反序列化 + @Test + fun `json deserialize`() { + val expectExtensionArtifact = DefaultArtifact("org.example.test:test-extension:1.0.0") + @Language("JSON5") val looksGoodJson = """ + { + "enabled": false, + "account": { + "name": "TestBot", + "token": "123456789:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", + "creatorId": 123456789 + }, + "proxy": { + "host": "localhost", + "port": 8080, + "type": "HTTP" + }, + "disableBuiltInAbility": false, + "autoUpdateCommandList": true, + "extensions": [ + "$expectExtensionArtifact" + ], + "baseApiUrl": "http://localhost:8080" + } + """.trimIndent() + + val actualConfig = gson.fromJson(looksGoodJson, BotConfig::class.java) + + assertEquals(false, actualConfig.enabled) + + assertEquals("TestBot", actualConfig.account.name) + assertEquals("123456789:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", actualConfig.account.token) + assertEquals(123456789L, actualConfig.account.creatorId) + + assertEquals("localhost", actualConfig.proxy.host) + assertEquals(8080, actualConfig.proxy.port) + assertEquals(ProxyType.HTTP, actualConfig.proxy.type) + + assertEquals(false, actualConfig.disableBuiltInAbility) + assertEquals(true, actualConfig.autoUpdateCommandList) + + assertEquals(1, actualConfig.extensions.size) + assertEquals(expectExtensionArtifact, actualConfig.extensions.first()) + + assertEquals("http://localhost:8080", actualConfig.baseApiUrl) + } + +} + +internal class ProxyConfigTest { + + private val gson = GsonBuilder() + .registerTypeAdapter(ProxyConfig::class.java, ProxyConfigSerializer) + .registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer) + .create() + + @Test + fun `json serialize`() { + val proxyConfig = ProxyConfig( + host = "localhost", + port = 8080, + type = ProxyType.HTTP + ) + val json = gson.toJsonTree(proxyConfig) + assertTrue(json is JsonObject) + assertEquals(proxyConfig.host, json.get("host").asString) + assertEquals(proxyConfig.port, json.get("port").asInt) + assertEquals(proxyConfig.type.name, json.get("type").asString) + } + + @Test + fun `json deserialize`() { + @Language("JSON5") val looksGoodJson = """ + { + "host": "localhost", + "port": 8080, + "type": "HTTP" + } + """.trimIndent() + + val actualConfig = gson.fromJson(looksGoodJson, ProxyConfig::class.java) + + assertEquals("localhost", actualConfig.host) + assertEquals(8080, actualConfig.port) + assertEquals(ProxyType.HTTP, actualConfig.type) + } +} + +internal class MetricsConfigTest { + + private val gson = GsonBuilder() + .registerTypeAdapter(UsernameAuthenticator::class.java, UsernameAuthenticatorSerializer) + .create() + + @Test + fun `json serializer`() { + val config = MetricsConfig( + enable = true, + port = 8800, + bindAddress = "127.0.0.1", + authenticator = UsernameAuthenticator( + username = "username", + password = "password" + ) + ) + + val json = gson.toJsonTree(config).asJsonObject + + assertEquals(config.enable, json.get("enable").asBoolean) + assertEquals(config.port, json.get("port").asInt) + assertEquals(config.bindAddress, json.get("bindAddress").asString) + assertNotNull(config.authenticator) + assertTrue( + config.authenticator!!.checkCredentials( + json.get("authenticator").asJsonObject.get("username").asString, + json.get("authenticator").asJsonObject.get("password").asString + ) + ) + + val expectDefaultValueConfig = MetricsConfig() + val defaultValueJson = gson.toJsonTree(expectDefaultValueConfig).asJsonObject + assertEquals(expectDefaultValueConfig.enable, defaultValueJson.get("enable").asBoolean) + assertEquals(expectDefaultValueConfig.port, defaultValueJson.get("port").asInt) + assertEquals(expectDefaultValueConfig.bindAddress, defaultValueJson.get("bindAddress").asString) + assertNull(defaultValueJson.get("authenticator")) + } + + @Test + fun `json deserializer`() { + val json = """ + { + "enable": true, + "port": 8800, + "bindAddress": "127.0.0.1", + "authenticator": { + "username": "username", + "password": "password" + } + } + """.trimIndent() + val config = gson.fromJson(json, MetricsConfig::class.java) + assertEquals(true, config.enable) + assertEquals(8800, config.port) + assertEquals("127.0.0.1", config.bindAddress) + assertNotNull(config.authenticator) + assertTrue(config.authenticator!!.checkCredentials("username", "password")) + + val defaultValueConfig = MetricsConfig() + val defaultValueJson = gson.toJsonTree(defaultValueConfig).asJsonObject + assertEquals(defaultValueConfig.enable, defaultValueJson.get("enable").asBoolean) + assertEquals(defaultValueConfig.port, defaultValueJson.get("port").asInt) + assertEquals(defaultValueConfig.bindAddress, defaultValueJson.get("bindAddress").asString) + assertNull(defaultValueJson.get("authenticator")) + } + + @Test + fun `json deserializer - default value`() { + val actualConfig = gson.fromJson("{}", MetricsConfig::class.java) + val expectConfig = MetricsConfig() + + assertEquals(expectConfig, actualConfig) + } + +} + +internal class MavenRepositoryConfigTest { + + private val gson = GsonBuilder() + .registerTypeAdapter(MavenRepositoryConfig::class.java, MavenRepositoryConfigSerializer) + .registerTypeAdapter(Authentication::class.java, AuthenticationSerializer) + .create() + + @Test + fun `json serializer`() { + val config = MavenRepositoryConfig( + id = "test", + url = URL("http://localhost:8080/repository"), + proxy = Proxy( + "http", + "localhost", + 8080, + ), + layout = "legacy", + enableReleases = false, + enableSnapshots = true, + authentication = null + ) + val json = gson.toJsonTree(config).asJsonObject + assertEquals(config.id, json.get("id").asString) + assertEquals(config.url.toString(), json.get("url").asString) + + assertEquals(config.proxy!!.host, json.get("proxy").asJsonObject.get("host").asString) + assertEquals(config.proxy!!.port, json.get("proxy").asJsonObject.get("port").asInt) + assertEquals(config.proxy!!.type, json.get("proxy").asJsonObject.get("type").asString) + + assertEquals(config.layout, json.get("layout").asString) + assertEquals(config.enableReleases, json.get("enableReleases").asBoolean) + assertEquals(config.enableSnapshots, json.get("enableSnapshots").asBoolean) + assertNull(json.get("authentication")) + } + + + @Test + fun `json deserializer`() { + @Language("JSON5") + val json = """ + { + "id": "test", + "url": "http://localhost:8080/repository", + "proxy": { + "host": "localhost", + "port": 8080, + "type": "HTTP" + }, + "layout": "legacy", + "enableReleases": false, + "enableSnapshots": true, + "authentication": { + "username": "username", + "password": "password" + } + } + """.trimIndent() + + val config = gson.fromJson(json, MavenRepositoryConfig::class.java) + + assertEquals("test", config.id) + assertEquals(URL("http://localhost:8080/repository"), config.url) + assertEquals( + Proxy( + "HTTP", + "localhost", + 8080 + ), config.proxy + ) + assertEquals("legacy", config.layout) + assertEquals(false, config.enableReleases) + assertEquals(true, config.enableSnapshots) + assertNotNull(config.authentication) + } + +} + +internal class AppConfigTest { + + private val gson = GsonBuilder() + .registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer) + .registerTypeAdapter(ProxyConfig::class.java, ProxyConfigSerializer) + .registerTypeAdapter(UsernameAuthenticator::class.java, UsernameAuthenticatorSerializer) + .registerTypeAdapter(MavenRepositoryConfig::class.java, MavenRepositoryConfigSerializer) + .create() + + @Test + fun `json serializer - default value`() { + val config = AppConfig() + val json = gson.toJsonTree(config).asJsonObject + assertEquals(config.proxy.type.name, json.get("proxy").asJsonObject.get("type").asString) + assertEquals(config.proxy.host, json.get("proxy").asJsonObject.get("host").asString) + assertEquals(config.proxy.port, json.get("proxy").asJsonObject.get("port").asInt) + assertEquals(config.metrics.enable, json.get("metrics").asJsonObject.get("enable").asBoolean) + assertEquals(config.metrics.port, json.get("metrics").asJsonObject.get("port").asInt) + assertEquals(config.metrics.bindAddress, json.get("metrics").asJsonObject.get("bindAddress").asString) + assertNull(json["metrics"].asJsonObject.get("authenticator")) + assertTrue(json["mavenRepositories"].asJsonArray.isEmpty) + assertNull(json["mavenLocalRepository"]) + } + + @Test + fun `json serializer - Provide values`() { + val config = AppConfig( + proxy = ProxyConfig( + type = ProxyType.HTTP, + host = "localhost", + port = 8080 + ), + metrics = MetricsConfig( + enable = true, + port = 8800, + bindAddress = "127.0.0.1", + authenticator = UsernameAuthenticator( + username = "username", + password = "password" + ) + ), + mavenRepositories = listOf( + MavenRepositoryConfig( + url = URL("https://repository.maven.apache.org/maven2/") + ) + ), + mavenLocalRepository = "file:///tmp/maven-local-repository" + ) + + val json = gson.toJsonTree(config).asJsonObject + + assertEquals(config.proxy.type.name, json.get("proxy").asJsonObject.get("type").asString) + assertEquals(config.proxy.host, json.get("proxy").asJsonObject.get("host").asString) + assertEquals(config.proxy.port, json.get("proxy").asJsonObject.get("port").asInt) + + assertEquals(config.metrics.enable, json.get("metrics").asJsonObject.get("enable").asBoolean) + assertEquals(config.metrics.port, json.get("metrics").asJsonObject.get("port").asInt) + assertEquals(config.metrics.bindAddress, json.get("metrics").asJsonObject.get("bindAddress").asString) + assertNotNull(config.metrics.authenticator) + assertTrue( + config.metrics.authenticator!!.checkCredentials( + json.get("metrics").asJsonObject.get("authenticator").asJsonObject.get("username").asString, + json.get("metrics").asJsonObject.get("authenticator").asJsonObject.get("password").asString + ) + ) + + assertEquals(1, json["mavenRepositories"].asJsonArray.size()) + assertEquals( + config.mavenRepositories[0].url.toString(), + json["mavenRepositories"].asJsonArray[0].asJsonObject.get("url").asString + ) + + assertEquals(config.mavenLocalRepository, json["mavenLocalRepository"].asString) + } + + @Test + fun `json deserializer - complete`() { + val json = """ + { + "proxy": { + "type": "HTTP", + "host": "localhost", + "port": 8080 + }, + "metrics": { + "enable": true, + "port": 8800, + "bindAddress": "127.0.0.1", + "authenticator": { + "username": "username", + "password": "password" + } + }, + "mavenRepositories": [ + { + "url": "https://repository.maven.apache.org/maven2/" + } + ], + "mavenLocalRepository": "file:///tmp/maven-local-repository" + } + """.trimIndent() + val config = gson.fromJson(json, AppConfig::class.java) + + assertEquals(ProxyType.HTTP, config.proxy.type) + assertEquals("localhost", config.proxy.host) + assertEquals(8080, config.proxy.port) + + assertEquals(true, config.metrics.enable) + assertEquals(8800, config.metrics.port) + assertEquals("127.0.0.1", config.metrics.bindAddress) + assertNotNull(config.metrics.authenticator) + assertTrue(config.metrics.authenticator!!.checkCredentials("username", "password")) + + assertEquals(1, config.mavenRepositories.size) + assertEquals(URL("https://repository.maven.apache.org/maven2/"), config.mavenRepositories[0].url) + + assertEquals("file:///tmp/maven-local-repository", config.mavenLocalRepository) + } + + @Test + fun `json deserializer - default value`() { + val actualConfig = gson.fromJson("{}", AppConfig::class.java) + val expectConfig = AppConfig() + + assertEquals(expectConfig, actualConfig) + } + +} From 85e59f4a64b9f7c4189360355582e80dcb735f06 Mon Sep 17 00:00:00 2001 From: LamGC Date: Fri, 24 Jun 2022 19:43:06 +0800 Subject: [PATCH 10/22] =?UTF-8?q?test(config):=20=E5=B0=86=20ArtifactSeria?= =?UTF-8?q?lizerTest=20=E5=90=88=E5=B9=B6=E5=88=B0=20SerializersKtTest,=20?= =?UTF-8?q?=E5=B9=B6=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=9A=84=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为了归类单元测试, 所以将 ArtifactSerializerTest 合并到 SerializersKtTest; 添加 ProxyConfig 和 BotConfig 的单元测试类. --- .../serializer/ArtifactSerializerTest.kt | 43 --- .../kotlin/serializer/SerializersKtTest.kt | 349 +++++++++++++++++- 2 files changed, 329 insertions(+), 63 deletions(-) delete mode 100644 scalabot-meta/src/test/kotlin/serializer/ArtifactSerializerTest.kt diff --git a/scalabot-meta/src/test/kotlin/serializer/ArtifactSerializerTest.kt b/scalabot-meta/src/test/kotlin/serializer/ArtifactSerializerTest.kt deleted file mode 100644 index 6c12ea4..0000000 --- a/scalabot-meta/src/test/kotlin/serializer/ArtifactSerializerTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -@file:Suppress("PackageDirectoryMismatch") - -package net.lamgc.scalabot.config.serializer - -import com.google.gson.JsonObject -import com.google.gson.JsonParseException -import com.google.gson.JsonPrimitive -import org.eclipse.aether.artifact.DefaultArtifact -import org.junit.jupiter.api.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -internal class ArtifactSerializerTest { - - @Test - fun badJsonType() { - assertFailsWith { ArtifactSerializer.deserialize(JsonObject(), null, null) } - } - - @Test - fun `Basic format serialization`() { - val gav = "org.example.software:test:1.0.0-SNAPSHOT" - val expectArtifact = DefaultArtifact(gav) - val actualArtifact = DefaultArtifact(ArtifactSerializer.serialize(expectArtifact, null, null).asString) - assertEquals(expectArtifact, actualArtifact) - } - - @Test - fun `Full format serialization`() { - val gav = "org.example.software:test:war:javadoc:1.0.0-SNAPSHOT" - val expectArtifact = DefaultArtifact(gav) - val actualArtifact = DefaultArtifact(ArtifactSerializer.serialize(expectArtifact, null, null).asString) - assertEquals(expectArtifact, actualArtifact) - } - - @Test - fun deserialize() { - val gav = "org.example.software:test:1.0.0-SNAPSHOT" - val expectArtifact = DefaultArtifact(gav) - val actualArtifact = ArtifactSerializer.deserialize(JsonPrimitive(gav), null, null) - assertEquals(expectArtifact, actualArtifact) - } -} \ No newline at end of file diff --git a/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt index 0a116ed..5b7f4ff 100644 --- a/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt +++ b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt @@ -4,8 +4,9 @@ import com.google.gson.* import io.mockk.every import io.mockk.mockk import io.mockk.verify -import net.lamgc.scalabot.config.MavenRepositoryConfig -import net.lamgc.scalabot.config.ProxyType +import net.lamgc.scalabot.config.* +import org.eclipse.aether.artifact.Artifact +import org.eclipse.aether.artifact.DefaultArtifact import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.AuthenticationContext import org.eclipse.aether.repository.Proxy @@ -116,14 +117,14 @@ internal class MavenRepositoryConfigSerializerTest { MavenRepositoryConfigSerializer.deserialize( JsonArray(), MavenRepositoryConfig::class.java, - TestJsonDeserializationContext + TestJsonSerializationContext.default() ) } assertThrows(JsonParseException::class.java) { MavenRepositoryConfigSerializer.deserialize( JsonNull.INSTANCE, MavenRepositoryConfig::class.java, - TestJsonDeserializationContext + TestJsonSerializationContext.default() ) } } @@ -134,9 +135,17 @@ internal class MavenRepositoryConfigSerializerTest { val config = MavenRepositoryConfigSerializer.deserialize( JsonPrimitive(expectRepoUrl), MavenRepositoryConfig::class.java, - TestJsonDeserializationContext + TestJsonSerializationContext.default() ) + assertThrows(JsonParseException::class.java) { + MavenRepositoryConfigSerializer.deserialize( + JsonPrimitive("NOT A URL."), + MavenRepositoryConfig::class.java, + TestJsonSerializationContext.default() + ) + } + assertNull(config.id) assertEquals(URL(expectRepoUrl), config.url) assertNull(config.proxy, "Proxy 默认值不为 null.") @@ -154,7 +163,7 @@ internal class MavenRepositoryConfigSerializerTest { val config = MavenRepositoryConfigSerializer.deserialize( jsonObject, MavenRepositoryConfig::class.java, - TestJsonDeserializationContext + TestJsonSerializationContext.default() ) assertNull(config.id) @@ -188,7 +197,7 @@ internal class MavenRepositoryConfigSerializerTest { var config = MavenRepositoryConfigSerializer.deserialize( jsonObject, MavenRepositoryConfig::class.java, - TestJsonDeserializationContext + TestJsonSerializationContext.default() ) assertEquals(jsonObject["id"].asString, config.id) @@ -206,7 +215,7 @@ internal class MavenRepositoryConfigSerializerTest { config = MavenRepositoryConfigSerializer.deserialize( jsonObject, MavenRepositoryConfig::class.java, - TestJsonDeserializationContext + TestJsonSerializationContext.default() ) assertEquals(jsonObject["id"].asString, config.id) @@ -226,7 +235,7 @@ internal class MavenRepositoryConfigSerializerTest { config = MavenRepositoryConfigSerializer.deserialize( jsonObject, MavenRepositoryConfig::class.java, - TestJsonDeserializationContext + TestJsonSerializationContext.default() ) assertEquals(jsonObject["id"].asString, config.id) @@ -247,7 +256,7 @@ internal class MavenRepositoryConfigSerializerTest { config = MavenRepositoryConfigSerializer.deserialize( jsonObject, MavenRepositoryConfig::class.java, - TestJsonDeserializationContext + TestJsonSerializationContext.default() ) assertEquals(jsonObject["id"].asString, config.id) @@ -261,15 +270,37 @@ internal class MavenRepositoryConfigSerializerTest { } -private object TestJsonDeserializationContext : JsonDeserializationContext { +private class TestJsonSerializationContext(private val gson: Gson) : JsonDeserializationContext, + JsonSerializationContext { - private val gson = GsonBuilder() - .registerTypeAdapter(Authentication::class.java, AuthenticationSerializer) - .create() - - override fun deserialize(json: JsonElement, typeOfT: Type): T { + override fun deserialize(json: JsonElement?, typeOfT: Type): T { return gson.fromJson(json, typeOfT) } + + companion object { + fun default(): TestJsonSerializationContext { + return TestJsonSerializationContext( + GsonBuilder() + .registerTypeAdapter(MavenRepositoryConfig::class.java, MavenRepositoryConfigSerializer) + .registerTypeAdapter(BotConfig::class.java, BotConfigSerializer) + .registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer) + .registerTypeAdapter(Artifact::class.java, ArtifactSerializer) + .registerTypeAdapter(Authentication::class.java, AuthenticationSerializer) + .registerTypeAdapter(UsernameAuthenticator::class.java, UsernameAuthenticatorSerializer) + .registerTypeAdapter(ProxyConfig::class.java, ProxyConfigSerializer) + .create() + ) + } + } + + override fun serialize(src: Any?): JsonElement { + return gson.toJsonTree(src) + } + + override fun serialize(src: Any?, typeOfSrc: Type?): JsonElement { + return gson.toJsonTree(src, typeOfSrc) + } + } internal class AuthenticationSerializerTest { @@ -279,19 +310,19 @@ internal class AuthenticationSerializerTest { assertThrows(JsonParseException::class.java) { AuthenticationSerializer.deserialize( JsonNull.INSTANCE, - Authentication::class.java, TestJsonDeserializationContext + Authentication::class.java, TestJsonSerializationContext.default() ) } assertThrows(JsonParseException::class.java) { AuthenticationSerializer.deserialize( JsonArray(), - Authentication::class.java, TestJsonDeserializationContext + Authentication::class.java, TestJsonSerializationContext.default() ) } assertThrows(JsonParseException::class.java) { AuthenticationSerializer.deserialize( JsonPrimitive("A STRING"), - Authentication::class.java, TestJsonDeserializationContext + Authentication::class.java, TestJsonSerializationContext.default() ) } @@ -306,7 +337,7 @@ internal class AuthenticationSerializerTest { val result = AuthenticationSerializer.deserialize( expectJsonObject, - Authentication::class.java, TestJsonDeserializationContext + Authentication::class.java, TestJsonSerializationContext.default() ) assertNotNull(result) @@ -320,3 +351,281 @@ internal class AuthenticationSerializerTest { } } + +internal class BotConfigSerializerTest { + + private val gson = GsonBuilder() + .registerTypeAdapter(BotConfig::class.java, BotConfigSerializer) + .registerTypeAdapter(Artifact::class.java, ArtifactSerializer) + .registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer) + .registerTypeAdapter(ProxyConfig::class.java, ProxyConfigSerializer) + .create() + + @Test + fun `serializer test`() { + // 检查 BotConfig 的序列化 + val botConfig = BotConfig( + account = BotAccount( + name = "test-bot", + token = "test-token", + creatorId = 10000 + ) + ) + + // 使用 gson 序列化 botConfig, 并检查序列化结果 + val jsonObject = gson.toJsonTree(botConfig) as JsonObject + assertEquals("test-bot", jsonObject["account"].asJsonObject["name"].asString) + assertEquals("test-token", jsonObject["account"].asJsonObject["token"].asString) + assertEquals(10000, jsonObject["account"].asJsonObject["creatorId"].asInt) + + assertEquals(botConfig.enabled, jsonObject["enabled"].asBoolean) + assertEquals(botConfig.proxy.host, jsonObject["proxy"].asJsonObject["host"].asString) + assertEquals(botConfig.proxy.port, jsonObject["proxy"].asJsonObject["port"].asInt) + assertEquals(botConfig.proxy.type.name, jsonObject["proxy"].asJsonObject["type"].asString) + assertEquals(botConfig.disableBuiltInAbility, jsonObject["disableBuiltInAbility"].asBoolean) + assertEquals(botConfig.autoUpdateCommandList, jsonObject["autoUpdateCommandList"].asBoolean) + assertEquals(botConfig.extensions.isEmpty(), jsonObject["extensions"].asJsonArray.isEmpty) + assertEquals(botConfig.baseApiUrl, jsonObject["baseApiUrl"].asString) + } + + @Test + fun `deserialize test`() { + assertThrows(JsonParseException::class.java) { + BotConfigSerializer.deserialize( + JsonNull.INSTANCE, + BotConfig::class.java, TestJsonSerializationContext(gson) + ) + } + assertThrows(JsonParseException::class.java) { + BotConfigSerializer.deserialize( + JsonArray(), + BotConfig::class.java, TestJsonSerializationContext(gson) + ) + } + assertThrows(JsonParseException::class.java) { + BotConfigSerializer.deserialize( + JsonPrimitive("A STRING"), + BotConfig::class.java, TestJsonSerializationContext(gson) + ) + } + + // 检查 BotConfig 的反序列化中是否能正确判断 account 的类型 + assertThrows(JsonParseException::class.java) { + BotConfigSerializer.deserialize( + JsonObject().apply { + addProperty("account", "A STRING") + }, + BotConfig::class.java, TestJsonSerializationContext(gson) + ) + } + assertThrows(JsonParseException::class.java) { + BotConfigSerializer.deserialize( + JsonObject().apply { + add("account", JsonNull.INSTANCE) + }, + BotConfig::class.java, TestJsonSerializationContext(gson) + ) + } + assertThrows(JsonParseException::class.java) { + BotConfigSerializer.deserialize( + JsonObject().apply { + add("account", JsonArray()) + }, + BotConfig::class.java, TestJsonSerializationContext(gson) + ) + } + assertThrows(JsonParseException::class.java) { + BotConfigSerializer.deserialize( + JsonObject(), + BotConfig::class.java, TestJsonSerializationContext(gson) + ) + } + + + val expectBotAccount = BotAccount( + name = "test-bot", + token = "test-token", + creatorId = 10000 + ) + val expectDefaultBotConfig = BotConfig(account = expectBotAccount) + val minimumJsonObject = JsonObject().apply { + add("account", gson.toJsonTree(expectBotAccount)) + } + val actualMinimumBotConfig = BotConfigSerializer.deserialize( + minimumJsonObject, BotConfig::class.java, TestJsonSerializationContext(gson) + ) + assertNotNull(actualMinimumBotConfig) + assertEquals(expectDefaultBotConfig, actualMinimumBotConfig) + + val expectDefaultProxy = ProxyConfig( + type = ProxyType.HTTP, + host = "https://example.com", + port = 443 + ) + + // ------------------------------------------------- + + val jsonObject = JsonObject().apply { + add( + "account", gson.toJsonTree( + BotAccount( + name = "test-bot", + token = "test-token", + creatorId = 10000 + ) + ) + ) + addProperty("enabled", true) + add("proxy", gson.toJsonTree(expectDefaultProxy)) + addProperty("disableBuiltInAbility", true) + addProperty("autoUpdateCommandList", true) + addProperty("baseApiUrl", "https://test.com") + add("extensions", JsonArray()) + } + + val botConfig = BotConfigSerializer.deserialize( + jsonObject, + BotConfig::class.java, TestJsonSerializationContext(gson) + ) + + assertEquals("test-bot", botConfig.account.name) + assertEquals("test-token", botConfig.account.token) + assertEquals(10000, botConfig.account.creatorId) + assertEquals(true, botConfig.enabled) + assertEquals(expectDefaultProxy, botConfig.proxy) + assertEquals(true, botConfig.disableBuiltInAbility) + assertEquals(true, botConfig.autoUpdateCommandList) + assertEquals("https://test.com", botConfig.baseApiUrl) + assertEquals(true, botConfig.extensions.isEmpty()) + } +} + +internal class ProxyConfigSerializerTest { + + // 测试 ProxyConfig 的 Json 序列化 + @Test + fun `serialize test`() { + assertEquals(JsonNull.INSTANCE, ProxyConfigSerializer.serialize(null, null, null)) + + val expectDefaultConfig = ProxyConfig() + val actualDefaultJson = ProxyConfigSerializer.serialize(expectDefaultConfig, null, null) + assertTrue(actualDefaultJson is JsonObject) + assertEquals(expectDefaultConfig.type.name, actualDefaultJson["type"].asString) + assertEquals(expectDefaultConfig.host, actualDefaultJson["host"].asString) + assertEquals(expectDefaultConfig.port, actualDefaultJson["port"].asInt) + } + + @Test + fun `Bad type deserialize test`() { + val defaultConfig = ProxyConfig() + assertEquals(defaultConfig, ProxyConfigSerializer.deserialize(null, null, null)) + assertEquals(defaultConfig, ProxyConfigSerializer.deserialize(JsonNull.INSTANCE, null, null)) + } + + @Test + fun `deserialize test - object`() { + val defaultConfig = ProxyConfig() + + assertThrows(JsonParseException::class.java) { + ProxyConfigSerializer.deserialize(JsonArray(), null, null) + } + + val jsonWithoutType = JsonObject().apply { + addProperty("host", "example.com") + addProperty("port", 8080) + } + assertEquals(defaultConfig, ProxyConfigSerializer.deserialize(jsonWithoutType, null, null)) + + val looksGoodJson = JsonObject().apply { + addProperty("type", "HTTP") + addProperty("host", "example.com") + addProperty("port", 8080) + } + assertEquals( + ProxyConfig( + type = ProxyType.HTTP, + host = "example.com", + port = 8080 + ), ProxyConfigSerializer.deserialize(looksGoodJson, null, null) + ) + + assertThrows(JsonParseException::class.java) { + ProxyConfigSerializer.deserialize(JsonObject().apply { + addProperty("type", "UNKNOWN") + addProperty("host", "example.com") + addProperty("port", 8080) + }, null, null) + } + + assertThrows(JsonParseException::class.java) { + ProxyConfigSerializer.deserialize(JsonObject().apply { + addProperty("type", "HTTP") + addProperty("host", "example.com") + }, null, null) + } + assertThrows(JsonParseException::class.java) { + ProxyConfigSerializer.deserialize(JsonObject().apply { + addProperty("type", "HTTP") + addProperty("port", 8080) + }, null, null) + } + } +} + +internal class ArtifactSerializerTest { + + @org.junit.jupiter.api.Test + fun badJsonType() { + assertFailsWith { ArtifactSerializer.deserialize(JsonObject(), null, null) } + assertFailsWith { ArtifactSerializer.deserialize(JsonArray(), null, null) } + assertFailsWith { ArtifactSerializer.deserialize(JsonPrimitive("A STRING"), null, null) } + } + + @org.junit.jupiter.api.Test + fun `Basic format serialization`() { + val gav = "org.example.software:test:1.0.0-SNAPSHOT" + val expectArtifact = DefaultArtifact(gav) + val actualArtifact = DefaultArtifact(ArtifactSerializer.serialize(expectArtifact, null, null).asString) + assertEquals(expectArtifact, actualArtifact) + } + + @org.junit.jupiter.api.Test + fun `Full format serialization`() { + val gav = "org.example.software:test:war:javadoc:1.0.0-SNAPSHOT" + val expectArtifact = DefaultArtifact(gav) + val actualArtifact = DefaultArtifact(ArtifactSerializer.serialize(expectArtifact, null, null).asString) + assertEquals(expectArtifact, actualArtifact) + } + + @org.junit.jupiter.api.Test + fun `Bad format serialization`() { + assertFailsWith { + ArtifactSerializer.deserialize(JsonPrimitive("org.example~test"), null, null) + } + } + + @org.junit.jupiter.api.Test + fun `Other artifact implementation serialization`() { + val gav = "org.example.software:test:war:javadoc:1.0.0-SNAPSHOT" + val expectArtifact = DefaultArtifact(gav) + val otherArtifactImpl = mockk { + every { groupId } returns expectArtifact.groupId + every { artifactId } returns expectArtifact.artifactId + every { version } returns expectArtifact.version + every { classifier } returns expectArtifact.classifier + every { extension } returns expectArtifact.extension + } + + val json = ArtifactSerializer.serialize(otherArtifactImpl, null, null) + assertTrue(json is JsonPrimitive) + assertEquals(expectArtifact.toString(), json.asString) + } + + @org.junit.jupiter.api.Test + fun deserialize() { + val gav = "org.example.software:test:war:javadoc:1.0.0-SNAPSHOT" + val expectArtifact = DefaultArtifact(gav) + val actualArtifact = ArtifactSerializer.deserialize(JsonPrimitive(gav), null, null) + assertEquals(expectArtifact, actualArtifact) + } +} From 128e33e5453046dd439e0e482caedbc13f95e5e1 Mon Sep 17 00:00:00 2001 From: LamGC Date: Fri, 24 Jun 2022 19:44:31 +0800 Subject: [PATCH 11/22] =?UTF-8?q?test(config):=20=E7=A7=BB=E5=8A=A8=20User?= =?UTF-8?q?nameAuthenticatorTest=20=E5=88=B0=E6=96=B0=E7=9A=84=E5=8C=85?= =?UTF-8?q?=E8=B7=AF=E5=BE=84.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UsernameAuthenticatorTest 所测试的 UsernameAuthenticator 是属于 config 包的, 所以修正了一下这个问题. --- .../test/kotlin/{serializer => }/UsernameAuthenticatorTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename scalabot-meta/src/test/kotlin/{serializer => }/UsernameAuthenticatorTest.kt (97%) diff --git a/scalabot-meta/src/test/kotlin/serializer/UsernameAuthenticatorTest.kt b/scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt similarity index 97% rename from scalabot-meta/src/test/kotlin/serializer/UsernameAuthenticatorTest.kt rename to scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt index 8f71a37..eebc19e 100644 --- a/scalabot-meta/src/test/kotlin/serializer/UsernameAuthenticatorTest.kt +++ b/scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt @@ -1,7 +1,7 @@ -package net.lamgc.scalabot.config.serializer +package net.lamgc.scalabot.config import com.google.gson.* -import net.lamgc.scalabot.config.UsernameAuthenticator +import net.lamgc.scalabot.config.serializer.UsernameAuthenticatorSerializer import org.junit.jupiter.api.assertThrows import kotlin.test.* From b8a99a449119ba5cedd8ef9ec46ca8063b9e369d Mon Sep 17 00:00:00 2001 From: LamGC Date: Fri, 24 Jun 2022 19:51:27 +0800 Subject: [PATCH 12/22] =?UTF-8?q?fix(config):=20=E4=BF=AE=E6=AD=A3=20BotCo?= =?UTF-8?q?nfigSerializer=20=E4=B8=AD=E4=BD=BF=E7=94=A8=E7=9A=84=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E9=BB=98=E8=AE=A4=E5=80=BC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 由于默认值未及时变更, 导致出现默认值与预期不符的情况; 目前已调整了新的默认值获取方式, 以便于后续调整默认值. --- .../src/main/kotlin/serializer/Serializer.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/scalabot-meta/src/main/kotlin/serializer/Serializer.kt b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt index 1341272..2ff6a90 100644 --- a/scalabot-meta/src/main/kotlin/serializer/Serializer.kt +++ b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt @@ -9,7 +9,6 @@ import org.eclipse.aether.artifact.DefaultArtifact import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.Proxy import org.eclipse.aether.util.repository.AuthenticationBuilder -import org.telegram.telegrambots.meta.ApiConstants import java.lang.reflect.Type import java.net.MalformedURLException import java.net.URL @@ -222,6 +221,8 @@ object ProxyConfigSerializer : JsonSerializer, JsonDeserializer, JsonDeserializer { + private val defaultConfig = BotConfig(account = BotAccount("__Default__", "__Default__", 0)) + override fun serialize(src: BotConfig, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { return JsonObject().apply { addProperty("enabled", src.enabled) @@ -247,14 +248,14 @@ object BotConfigSerializer : JsonSerializer, JsonDeserializer>() {}.type) - ?: emptySet(), - proxy = context.deserialize(json.get("proxy"), ProxyConfig::class.java) ?: ProxyConfig(), - baseApiUrl = json.get("baseApiUrl")?.asString ?: ApiConstants.BASE_URL + ?: defaultConfig.extensions, + proxy = context.deserialize(json.get("proxy"), ProxyConfig::class.java) ?: defaultConfig.proxy, + baseApiUrl = json.get("baseApiUrl")?.asString ?: defaultConfig.baseApiUrl ) } } From df484d6bd7e156177cdc3c10d9fb4ea6b759604b Mon Sep 17 00:00:00 2001 From: LamGC Date: Sat, 25 Jun 2022 19:19:55 +0800 Subject: [PATCH 13/22] =?UTF-8?q?test(config):=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=95=B0=E6=8D=AE,=20=E4=BB=A5=E7=AC=A6?= =?UTF-8?q?=E5=90=88=E8=AF=A5=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B=E7=9A=84?= =?UTF-8?q?=E6=83=85=E5=86=B5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为 BotConfig 的完整序列化测试添加 Artifact 值, 覆盖解析 BotConfig 所涉及的 Artifact 序列化. --- .../src/test/kotlin/serializer/SerializersKtTest.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt index 5b7f4ff..acc7895 100644 --- a/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt +++ b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt @@ -480,7 +480,9 @@ internal class BotConfigSerializerTest { addProperty("disableBuiltInAbility", true) addProperty("autoUpdateCommandList", true) addProperty("baseApiUrl", "https://test.com") - add("extensions", JsonArray()) + add("extensions", JsonArray().apply { + add("org.example:test:1.0.0-SNAPSHOT") + }) } val botConfig = BotConfigSerializer.deserialize( @@ -496,7 +498,8 @@ internal class BotConfigSerializerTest { assertEquals(true, botConfig.disableBuiltInAbility) assertEquals(true, botConfig.autoUpdateCommandList) assertEquals("https://test.com", botConfig.baseApiUrl) - assertEquals(true, botConfig.extensions.isEmpty()) + assertEquals(false, botConfig.extensions.isEmpty()) + assertEquals(1, botConfig.extensions.size) } } From 581eeba20b0aedf714713b83487fad21cb1c7a84 Mon Sep 17 00:00:00 2001 From: LamGC Date: Sat, 25 Jun 2022 23:00:51 +0800 Subject: [PATCH 14/22] =?UTF-8?q?feat(config):=20=E6=96=B0=E5=A2=9E=20HTTP?= =?UTF-8?q?S=20=E4=BB=A3=E7=90=86=E7=B1=BB=E5=9E=8B,=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=20Maven=20=E5=AF=B9=20HTTPS=20=E4=BB=A3=E7=90=86=E7=9A=84?= =?UTF-8?q?=E6=94=AF=E6=8C=81.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为 ProxyType 增加 HTTPS 类型, 同时为 Aether 增加 Https 代理支持, 方便用户使用现有的公开代理下载依赖包. --- scalabot-app/src/main/kotlin/AppConfigs.kt | 10 ++++++---- scalabot-meta/src/main/kotlin/Configs.kt | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/scalabot-app/src/main/kotlin/AppConfigs.kt b/scalabot-app/src/main/kotlin/AppConfigs.kt index 293136e..af2ef67 100644 --- a/scalabot-app/src/main/kotlin/AppConfigs.kt +++ b/scalabot-app/src/main/kotlin/AppConfigs.kt @@ -26,17 +26,19 @@ internal fun ProxyType.toTelegramBotsType(): DefaultBotOptions.ProxyType { return when (this) { ProxyType.NO_PROXY -> DefaultBotOptions.ProxyType.NO_PROXY ProxyType.HTTP -> DefaultBotOptions.ProxyType.HTTP + ProxyType.HTTPS -> DefaultBotOptions.ProxyType.HTTP ProxyType.SOCKS4 -> DefaultBotOptions.ProxyType.SOCKS4 ProxyType.SOCKS5 -> DefaultBotOptions.ProxyType.SOCKS5 } } internal fun ProxyConfig.toAetherProxy(): Proxy? { - return if (type == ProxyType.HTTP) { - Proxy(Proxy.TYPE_HTTP, host, port) - } else { - null + val typeStr = when (type) { + ProxyType.HTTP -> Proxy.TYPE_HTTP + ProxyType.HTTPS -> Proxy.TYPE_HTTPS + else -> return null } + return Proxy(typeStr, host, port) } internal fun MavenRepositoryConfig.toRemoteRepository(proxyConfig: ProxyConfig): RemoteRepository { diff --git a/scalabot-meta/src/main/kotlin/Configs.kt b/scalabot-meta/src/main/kotlin/Configs.kt index c1039d9..0b46dd2 100644 --- a/scalabot-meta/src/main/kotlin/Configs.kt +++ b/scalabot-meta/src/main/kotlin/Configs.kt @@ -51,6 +51,7 @@ data class BotConfig( enum class ProxyType { NO_PROXY, HTTP, + HTTPS, SOCKS4, SOCKS5 } From d6b25c4560f8820b42d837072ec63cbe9dffce56 Mon Sep 17 00:00:00 2001 From: LamGC Date: Sat, 25 Jun 2022 23:02:11 +0800 Subject: [PATCH 15/22] =?UTF-8?q?test(config):=20=E5=B0=86=20UsernameAuthe?= =?UTF-8?q?nticatorSerializerTest=20=E8=BF=81=E7=A7=BB=E5=88=B0=20Serializ?= =?UTF-8?q?ersKtTest.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将测试类移动到对应的文件中. --- .../test/kotlin/UsernameAuthenticatorTest.kt | 80 +------------------ .../kotlin/serializer/SerializersKtTest.kt | 75 +++++++++++++++++ 2 files changed, 76 insertions(+), 79 deletions(-) diff --git a/scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt b/scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt index eebc19e..8270187 100644 --- a/scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt +++ b/scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt @@ -1,11 +1,8 @@ package net.lamgc.scalabot.config -import com.google.gson.* -import net.lamgc.scalabot.config.serializer.UsernameAuthenticatorSerializer -import org.junit.jupiter.api.assertThrows import kotlin.test.* -class UsernameAuthenticatorTest { +internal class UsernameAuthenticatorTest { @Test fun checkCredentialsTest() { @@ -38,78 +35,3 @@ class UsernameAuthenticatorTest { } } - -class UsernameAuthenticatorSerializerTest { - - @Test - fun serializeTest() { - val authenticator = UsernameAuthenticator("testUser", "testPassword") - val jsonElement = UsernameAuthenticatorSerializer.serialize(authenticator, null, null) - assertTrue(jsonElement.isJsonObject) - val jsonObject = jsonElement.asJsonObject - assertEquals("testUser", jsonObject["username"]?.asString) - assertEquals("testPassword", jsonObject["password"]?.asString) - } - - @Test - fun deserializeTest() { - assertThrows { - UsernameAuthenticatorSerializer.deserialize(JsonArray(), null, null) - } - assertThrows { - UsernameAuthenticatorSerializer.deserialize(JsonPrimitive(""), null, null) - } - assertNull(UsernameAuthenticatorSerializer.deserialize(JsonNull.INSTANCE, null, null)) - assertThrows { - UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { - addProperty("username", "testUser") - }, null, null) - } - assertThrows { - UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { - addProperty("username", "testUser") - add("password", JsonArray()) - }, null, null) - } - assertThrows { - UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { - addProperty("password", "testPassword") - }, null, null) - } - assertThrows { - UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { - add("username", JsonArray()) - addProperty("password", "testPassword") - }, null, null) - } - assertThrows { - UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { - addProperty("username", "") - addProperty("password", "") - }, null, null) - } - assertThrows { - UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { - addProperty("username", "testUser") - addProperty("password", "") - }, null, null) - } - assertThrows { - UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { - addProperty("username", "") - addProperty("password", "testPassword") - }, null, null) - } - - val authenticator = UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { - addProperty("username", "testUser") - addProperty("password", "testPassword") - }, null, null) - assertNotNull(authenticator) - - assertTrue(authenticator.checkCredentials("testUser", "testPassword")) - assertFalse(authenticator.checkCredentials("falseUser", "testPassword")) - assertFalse(authenticator.checkCredentials("testUser", "falsePassword")) - } - -} diff --git a/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt index acc7895..10defc4 100644 --- a/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt +++ b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt @@ -632,3 +632,78 @@ internal class ArtifactSerializerTest { assertEquals(expectArtifact, actualArtifact) } } + +internal class UsernameAuthenticatorSerializerTest { + + @Test + fun serializeTest() { + val authenticator = UsernameAuthenticator("testUser", "testPassword") + val jsonElement = UsernameAuthenticatorSerializer.serialize(authenticator, null, null) + assertTrue(jsonElement.isJsonObject) + val jsonObject = jsonElement.asJsonObject + assertEquals("testUser", jsonObject["username"]?.asString) + assertEquals("testPassword", jsonObject["password"]?.asString) + } + + @Test + fun deserializeTest() { + org.junit.jupiter.api.assertThrows { + UsernameAuthenticatorSerializer.deserialize(JsonArray(), null, null) + } + org.junit.jupiter.api.assertThrows { + UsernameAuthenticatorSerializer.deserialize(JsonPrimitive(""), null, null) + } + assertNull(UsernameAuthenticatorSerializer.deserialize(JsonNull.INSTANCE, null, null)) + org.junit.jupiter.api.assertThrows { + UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { + addProperty("username", "testUser") + }, null, null) + } + org.junit.jupiter.api.assertThrows { + UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { + addProperty("username", "testUser") + add("password", JsonArray()) + }, null, null) + } + org.junit.jupiter.api.assertThrows { + UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { + addProperty("password", "testPassword") + }, null, null) + } + org.junit.jupiter.api.assertThrows { + UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { + add("username", JsonArray()) + addProperty("password", "testPassword") + }, null, null) + } + org.junit.jupiter.api.assertThrows { + UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { + addProperty("username", "") + addProperty("password", "") + }, null, null) + } + org.junit.jupiter.api.assertThrows { + UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { + addProperty("username", "testUser") + addProperty("password", "") + }, null, null) + } + org.junit.jupiter.api.assertThrows { + UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { + addProperty("username", "") + addProperty("password", "testPassword") + }, null, null) + } + + val authenticator = UsernameAuthenticatorSerializer.deserialize(JsonObject().apply { + addProperty("username", "testUser") + addProperty("password", "testPassword") + }, null, null) + assertNotNull(authenticator) + + assertTrue(authenticator.checkCredentials("testUser", "testPassword")) + assertFalse(authenticator.checkCredentials("falseUser", "testPassword")) + assertFalse(authenticator.checkCredentials("testUser", "falsePassword")) + } + +} From 045b3e5d54c57e358042b4674b0ac0a493c9a8b0 Mon Sep 17 00:00:00 2001 From: LamGC Date: Sun, 26 Jun 2022 02:32:53 +0800 Subject: [PATCH 16/22] =?UTF-8?q?test(config):=20=E7=BB=9F=E4=B8=80=20Test?= =?UTF-8?q?=20=E6=B3=A8=E8=A7=A3=E7=9A=84=E4=BD=BF=E7=94=A8,=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81=E7=9A=84=E9=A1=BA?= =?UTF-8?q?=E5=BA=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test 注解将统一使用 kotlin.test.Test, 这么做可以保持兼容性; 将 MavenRepositoryConfigSerializerTest.`json primitive deserialize test` 中的两段代码顺序调整一下, 以避免出现歧义. --- scalabot-meta/src/test/kotlin/ConfigsTest.kt | 6 ++--- .../kotlin/serializer/SerializersKtTest.kt | 26 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/scalabot-meta/src/test/kotlin/ConfigsTest.kt b/scalabot-meta/src/test/kotlin/ConfigsTest.kt index b3fb0a2..37a0244 100644 --- a/scalabot-meta/src/test/kotlin/ConfigsTest.kt +++ b/scalabot-meta/src/test/kotlin/ConfigsTest.kt @@ -17,7 +17,7 @@ import kotlin.test.* internal class BotAccountTest { - @org.junit.jupiter.api.Test + @Test fun `id getter`() { val accountId = abs(Random().nextInt()).toLong() Assertions.assertEquals(accountId, BotAccount("Test", "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", 0).id) @@ -26,7 +26,7 @@ internal class BotAccountTest { private val gson = GsonBuilder() .create() - @org.junit.jupiter.api.Test + @Test fun deserializerTest() { val accountId = abs(Random().nextInt()).toLong() val creatorId = abs(Random().nextInt()).toLong() @@ -46,7 +46,7 @@ internal class BotAccountTest { assertEquals("${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", botAccount.token) } - @org.junit.jupiter.api.Test + @Test fun serializerTest() { val accountId = abs(Random().nextInt()).toLong() val creatorId = abs(Random().nextInt()).toLong() diff --git a/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt index 10defc4..6cefc4a 100644 --- a/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt +++ b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt @@ -131,13 +131,6 @@ internal class MavenRepositoryConfigSerializerTest { @Test fun `json primitive deserialize test`() { - val expectRepoUrl = "https://repo.example.org/maven" - val config = MavenRepositoryConfigSerializer.deserialize( - JsonPrimitive(expectRepoUrl), - MavenRepositoryConfig::class.java, - TestJsonSerializationContext.default() - ) - assertThrows(JsonParseException::class.java) { MavenRepositoryConfigSerializer.deserialize( JsonPrimitive("NOT A URL."), @@ -146,6 +139,13 @@ internal class MavenRepositoryConfigSerializerTest { ) } + val expectRepoUrl = "https://repo.example.org/maven" + val config = MavenRepositoryConfigSerializer.deserialize( + JsonPrimitive(expectRepoUrl), + MavenRepositoryConfig::class.java, + TestJsonSerializationContext.default() + ) + assertNull(config.id) assertEquals(URL(expectRepoUrl), config.url) assertNull(config.proxy, "Proxy 默认值不为 null.") @@ -577,14 +577,14 @@ internal class ProxyConfigSerializerTest { internal class ArtifactSerializerTest { - @org.junit.jupiter.api.Test + @Test fun badJsonType() { assertFailsWith { ArtifactSerializer.deserialize(JsonObject(), null, null) } assertFailsWith { ArtifactSerializer.deserialize(JsonArray(), null, null) } assertFailsWith { ArtifactSerializer.deserialize(JsonPrimitive("A STRING"), null, null) } } - @org.junit.jupiter.api.Test + @Test fun `Basic format serialization`() { val gav = "org.example.software:test:1.0.0-SNAPSHOT" val expectArtifact = DefaultArtifact(gav) @@ -592,7 +592,7 @@ internal class ArtifactSerializerTest { assertEquals(expectArtifact, actualArtifact) } - @org.junit.jupiter.api.Test + @Test fun `Full format serialization`() { val gav = "org.example.software:test:war:javadoc:1.0.0-SNAPSHOT" val expectArtifact = DefaultArtifact(gav) @@ -600,14 +600,14 @@ internal class ArtifactSerializerTest { assertEquals(expectArtifact, actualArtifact) } - @org.junit.jupiter.api.Test + @Test fun `Bad format serialization`() { assertFailsWith { ArtifactSerializer.deserialize(JsonPrimitive("org.example~test"), null, null) } } - @org.junit.jupiter.api.Test + @Test fun `Other artifact implementation serialization`() { val gav = "org.example.software:test:war:javadoc:1.0.0-SNAPSHOT" val expectArtifact = DefaultArtifact(gav) @@ -624,7 +624,7 @@ internal class ArtifactSerializerTest { assertEquals(expectArtifact.toString(), json.asString) } - @org.junit.jupiter.api.Test + @Test fun deserialize() { val gav = "org.example.software:test:war:javadoc:1.0.0-SNAPSHOT" val expectArtifact = DefaultArtifact(gav) From c7c24fa45410a8b69979641ef795802c900c2ce7 Mon Sep 17 00:00:00 2001 From: LamGC Date: Sun, 26 Jun 2022 02:42:35 +0800 Subject: [PATCH 17/22] =?UTF-8?q?fix(config):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=BD=9C=E5=9C=A8=E7=9A=84=E6=97=A0=E7=8A=B6=E5=86=B5=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E9=97=AE=E9=A2=98.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 由于在 MavenRepositoryConfigSerializer 反序列化中过滤了 Json 的类型, 导致用户在配置中使用了错误的 Json 数据类型将不会有任何错误信息. 该改动已解决该问题. --- scalabot-meta/src/main/kotlin/serializer/Serializer.kt | 8 ++++---- .../src/test/kotlin/serializer/SerializersKtTest.kt | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/scalabot-meta/src/main/kotlin/serializer/Serializer.kt b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt index 2ff6a90..9899b96 100644 --- a/scalabot-meta/src/main/kotlin/serializer/Serializer.kt +++ b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt @@ -115,16 +115,16 @@ object MavenRepositoryConfigSerializer MavenRepositoryConfig( id = json.get("id")?.asString, url = URL(SerializerUtils.checkJsonKey(json, "url")), - proxy = if (json.has("proxy") && json.get("proxy").isJsonObject) + proxy = if (json.has("proxy")) context.deserialize( - json.getAsJsonObject("proxy"), Proxy::class.java + json.get("proxy"), Proxy::class.java ) else null, layout = json.get("layout")?.asString ?: "default", enableReleases = json.get("enableReleases")?.asBoolean ?: true, enableSnapshots = json.get("enableSnapshots")?.asBoolean ?: true, - authentication = if (json.has("authentication") && json.get("authentication").isJsonObject) + authentication = if (json.has("authentication")) context.deserialize( - json.getAsJsonObject("authentication"), Authentication::class.java + json.get("authentication"), Authentication::class.java ) else null ) } diff --git a/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt index 6cefc4a..c5cd5dd 100644 --- a/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt +++ b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt @@ -227,7 +227,6 @@ internal class MavenRepositoryConfigSerializerTest { // ------------------------------------ - jsonObject.add("authentication", JsonArray()) jsonObject.add("layout", mockk { every { asString }.returns(null) }) From 61b611b22e47586c9134fd2826b6b25bd2ad52a9 Mon Sep 17 00:00:00 2001 From: LamGC Date: Sun, 26 Jun 2022 02:51:33 +0800 Subject: [PATCH 18/22] =?UTF-8?q?test(config):=20=E4=B8=BA=20BotConfig=20?= =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=9C=80=E5=B0=91=E5=8F=82=E6=95=B0=E7=9A=84?= =?UTF-8?q?=E5=8F=8D=E5=BA=8F=E5=88=97=E5=8C=96=E6=B5=8B=E8=AF=95=E9=A1=B9?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充最少参数的反序列化测试项, 以确保在 Json 属性缺失的情况下依然能正确反序列化出正确的对象. --- scalabot-meta/src/test/kotlin/ConfigsTest.kt | 29 +++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/scalabot-meta/src/test/kotlin/ConfigsTest.kt b/scalabot-meta/src/test/kotlin/ConfigsTest.kt index 37a0244..6c2ad49 100644 --- a/scalabot-meta/src/test/kotlin/ConfigsTest.kt +++ b/scalabot-meta/src/test/kotlin/ConfigsTest.kt @@ -102,7 +102,6 @@ internal class BotConfigTest { assertEquals(minimumExpectConfig.baseApiUrl, json.get("baseApiUrl").asString) } - // 测试 BotConfig 的 json 反序列化 @Test fun `json deserialize`() { val expectExtensionArtifact = DefaultArtifact("org.example.test:test-extension:1.0.0") @@ -149,6 +148,34 @@ internal class BotConfigTest { assertEquals("http://localhost:8080", actualConfig.baseApiUrl) } + @Test + fun `json deserialize - minimum parameters`() { + @Language("JSON5") val minimumLooksGoodJson = """ + { + "account": { + "name": "TestBot", + "token": "123456789:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", + "creatorId": 123456789 + } + } + """.trimIndent() + val expectDefaultConfig = BotConfig(account = BotAccount("Test", "Test", 0)) + val actualMinimumConfig = gson.fromJson(minimumLooksGoodJson, BotConfig::class.java) + assertNotNull(actualMinimumConfig) + assertEquals("TestBot", actualMinimumConfig.account.name) + assertEquals("123456789:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", actualMinimumConfig.account.token) + assertEquals(123456789, actualMinimumConfig.account.creatorId) + + assertEquals(expectDefaultConfig.enabled, actualMinimumConfig.enabled) + assertEquals(expectDefaultConfig.disableBuiltInAbility, actualMinimumConfig.disableBuiltInAbility) + assertEquals(expectDefaultConfig.autoUpdateCommandList, actualMinimumConfig.autoUpdateCommandList) + assertEquals(expectDefaultConfig.proxy, actualMinimumConfig.proxy) + assertEquals(expectDefaultConfig.baseApiUrl, actualMinimumConfig.baseApiUrl) + + assertTrue(expectDefaultConfig.extensions.containsAll(actualMinimumConfig.extensions)) + assertTrue(actualMinimumConfig.extensions.containsAll(expectDefaultConfig.extensions)) + } + } internal class ProxyConfigTest { From db010d6c86ac895e453420a857070d1a3dd383f5 Mon Sep 17 00:00:00 2001 From: LamGC Date: Sun, 26 Jun 2022 02:52:11 +0800 Subject: [PATCH 19/22] =?UTF-8?q?test(config):=20=E4=B8=BA=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E8=A1=A5=E5=85=85=E4=B8=80=E4=B8=AA=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E6=96=AD=E8=A8=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充新的断言状况, 以保证功能正常使用. --- scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt b/scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt index 8270187..6dde4f1 100644 --- a/scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt +++ b/scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt @@ -10,6 +10,7 @@ internal class UsernameAuthenticatorTest { assertTrue(authenticator.checkCredentials("testUser", "testPassword")) assertFalse(authenticator.checkCredentials("falseUser", "testPassword")) assertFalse(authenticator.checkCredentials("testUser", "falsePassword")) + assertFalse(authenticator.checkCredentials("falseUser", "falsePassword")) } @Test From 06acc78180babfe4162897b3cec9e9d796b568e1 Mon Sep 17 00:00:00 2001 From: LamGC Date: Sun, 26 Jun 2022 03:06:14 +0800 Subject: [PATCH 20/22] =?UTF-8?q?test(config):=20=E8=A1=A5=E5=85=A8?= =?UTF-8?q?=E5=8D=95=E5=85=83=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 由于 `MavenRepositoryConfig.authentication` 有关联的序列化器, 因此不可忽略对该属性进行检查; 现已补充单元测试用例以确保反序列化结果正确. --- scalabot-meta/src/test/kotlin/ConfigsTest.kt | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/scalabot-meta/src/test/kotlin/ConfigsTest.kt b/scalabot-meta/src/test/kotlin/ConfigsTest.kt index 6c2ad49..eaf4931 100644 --- a/scalabot-meta/src/test/kotlin/ConfigsTest.kt +++ b/scalabot-meta/src/test/kotlin/ConfigsTest.kt @@ -3,10 +3,14 @@ package net.lamgc.scalabot.config import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonObject +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify import net.lamgc.scalabot.config.serializer.* import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.DefaultArtifact import org.eclipse.aether.repository.Authentication +import org.eclipse.aether.repository.AuthenticationContext import org.eclipse.aether.repository.Proxy import org.intellij.lang.annotations.Language import org.junit.jupiter.api.Assertions @@ -347,8 +351,8 @@ internal class MavenRepositoryConfigTest { "enableReleases": false, "enableSnapshots": true, "authentication": { - "username": "username", - "password": "password" + "username": "testUser", + "password": "testPassword" } } """.trimIndent() @@ -368,6 +372,16 @@ internal class MavenRepositoryConfigTest { assertEquals(false, config.enableReleases) assertEquals(true, config.enableSnapshots) assertNotNull(config.authentication) + + val authContext = mockk { + every { put(ofType(String::class), any()) } answers { } + } + config.authentication!!.fill(authContext, null, emptyMap()) + + verify { + authContext.put(any(), "testUser") + authContext.put(any(), "testPassword".toCharArray()) + } } } From 5843e3719688bac97ed3204f4d9e593c8c44cdb3 Mon Sep 17 00:00:00 2001 From: LamGC Date: Sun, 26 Jun 2022 13:25:44 +0800 Subject: [PATCH 21/22] =?UTF-8?q?docs(config):=20=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=B1=BB=E7=9A=84=E6=96=87=E6=A1=A3.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 由于先前的更新忘记补充文档了, 所以补充一下. --- scalabot-meta/src/main/kotlin/Configs.kt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/scalabot-meta/src/main/kotlin/Configs.kt b/scalabot-meta/src/main/kotlin/Configs.kt index 0b46dd2..1061680 100644 --- a/scalabot-meta/src/main/kotlin/Configs.kt +++ b/scalabot-meta/src/main/kotlin/Configs.kt @@ -11,6 +11,7 @@ import java.net.URL * @property name 机器人名称, 建议与实际设定的名称相同. * @property token 机器人 API Token. * @property creatorId 机器人创建者, 管理机器人需要使用该信息. + * @property id 机器人账号 ID. */ data class BotAccount( val name: String, @@ -27,10 +28,13 @@ data class BotAccount( /** * 机器人配置. + * @property enabled 是否启用机器人. * @property account 机器人帐号信息, 用于访问 API. * @property disableBuiltInAbility 是否禁用 AbilityBot 自带命令. + * @property autoUpdateCommandList 是否自动更新机器人在 Telegram 的命令列表. * @property extensions 该机器人启用的扩展. * @property proxy 为该机器人单独设置的代理配置, 如无设置, 则使用 AppConfig 中的代理配置. + * @property baseApiUrl 机器人所使用的 API 地址, 适用于自建 Telegram Bot API 端点. */ data class BotConfig( val enabled: Boolean = false, @@ -48,6 +52,9 @@ data class BotConfig( val baseApiUrl: String = ApiConstants.BASE_URL ) +/** + * 代理类型. + */ enum class ProxyType { NO_PROXY, HTTP, @@ -68,6 +75,17 @@ data class ProxyConfig( val port: Int = 1080 ) +/** + * ScalaBot 的运行指标公开配置. + * + * ScalaBot 内置了用于公开运行指标的服务端, + * 该指标遵循 Prometheus 的标准, 可以通过 Prometheus 的工具来查看. + * + * @property enable 是否启用运行指标服务端. + * @property port 运行指标服务端的端口. + * @property bindAddress 运行指标服务端的绑定地址, 绑定后只有该地址可以访问. + * @property authenticator 运行指标服务端的 HTTP 认证配置. + */ data class MetricsConfig( val enable: Boolean = false, val port: Int = 9386, @@ -77,9 +95,13 @@ data class MetricsConfig( /** * Maven 远端仓库配置. + * @property id 远端仓库 ID, 如果该属性未配置 (null), 那么运行时将会自动分配一个 Id. * @property url 仓库地址. * @property proxy 访问仓库所使用的代理, 仅支持 http/https 代理. * @property layout 仓库布局版本, Maven 2 及以上使用 `default`, Maven 1 使用 `legacy`. + * @property enableReleases 是否在该远端仓库获取发布版本. + * @property enableSnapshots 是否在该远端仓库获取快照版本. + * @property authentication 访问该远端仓库所使用的认证配置. */ data class MavenRepositoryConfig( val id: String? = null, From aa31bcd3a8ca1412d52b8e3b3adb9d3dc3fa03b5 Mon Sep 17 00:00:00 2001 From: LamGC Date: Mon, 27 Jun 2022 18:49:40 +0800 Subject: [PATCH 22/22] =?UTF-8?q?build(meta):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E9=85=8D=E7=BD=AE.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为 meta 模块增加 Maven 构件发布配置, 但仍需本地发布(因为要 GPG 签名). --- scalabot-meta/build.gradle.kts | 90 ++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/scalabot-meta/build.gradle.kts b/scalabot-meta/build.gradle.kts index f0ef77e..88cd8f7 100644 --- a/scalabot-meta/build.gradle.kts +++ b/scalabot-meta/build.gradle.kts @@ -1,11 +1,11 @@ plugins { kotlin("jvm") version "1.6.10" id("org.jetbrains.kotlinx.kover") version "0.5.1" + id("org.jetbrains.dokka") version "1.7.0" + `maven-publish` + signing } -group = "net.lamgc" -version = "0.3.1" - repositories { mavenCentral() } @@ -23,8 +23,90 @@ dependencies { testImplementation("io.mockk:mockk:1.12.4") testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") + + dokkaHtmlPlugin("org.jetbrains.dokka:javadoc-plugin:1.7.0") +} + +tasks.withType().configureEach { + kotlinOptions { + jvmTarget = "11" + } +} + +java { + withJavadocJar() + withSourcesJar() + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } tasks.getByName("test") { useJUnitPlatform() -} \ No newline at end of file +} + +val javadocJar = tasks.named("javadocJar") { + from(tasks.named("dokkaJavadoc")) +} + +publishing { + repositories { + if (project.version.toString().endsWith("-SNAPSHOT", ignoreCase = true)) { + maven("https://nexus.kuku.me/repository/maven-snapshots/") { + credentials { + username = project.properties["repo.credentials.private.username"].toString() + password = project.properties["repo.credentials.private.password"].toString() + } + } + } else { + maven("https://nexus.kuku.me/repository/maven-releases/") { + credentials { + username = project.properties["repo.credentials.private.username"].toString() + password = project.properties["repo.credentials.private.password"].toString() + } + } + } + } + + publications { + create("maven") { + from(components["kotlin"]) + artifact(javadocJar) + artifact(tasks.named("sourcesJar")) + pom { + name.set("ScalaBot-meta") + description.set( + "Shared components used by scalabot (such as configuration classes)" + ) + url.set("https://github.com/LamGC/ScalaBot") + licenses { + license { + name.set("The MIT License") + url.set("https://www.opensource.org/licenses/mit-license.php") + } + } + developers { + developer { + id.set("LamGC") + name.set("LamGC") + email.set("lam827@lamgc.net") + url.set("https://github.com/LamGC") + } + } + scm { + connection.set("scm:git:https://github.com/LamGC/ScalaBot.git") + developerConnection.set("scm:git:https://github.com/LamGC/ScalaBot.git") + url.set("https://github.com/LamGC/ScalaBot") + } + issueManagement { + url.set("https://github.com/LamGC/ScalaBot/issues") + system.set("Github Issues") + } + } + } + } +} + +signing { + useGpgCmd() + sign(publishing.publications["maven"]) +}