diff --git a/scalabot-app/build.gradle.kts b/scalabot-app/build.gradle.kts index 2aa9030..40f86d1 100644 --- a/scalabot-app/build.gradle.kts +++ b/scalabot-app/build.gradle.kts @@ -7,6 +7,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..af2ef67 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,65 @@ 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.HTTPS -> 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? { + val typeStr = when (type) { + ProxyType.HTTP -> Proxy.TYPE_HTTP + ProxyType.HTTPS -> Proxy.TYPE_HTTPS + else -> return null + } + return Proxy(typeStr, host, port) +} -/** - * 代理配置. - * @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 -) +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')") + } + return type +} + +private val repoNumberGenerator = AtomicInteger(1) + +private fun createDefaultRepositoryId(): String { + return "Repository-${repoNumberGenerator.getAndIncrement()}" +} /** * 需要用到的路径. @@ -222,7 +133,7 @@ internal enum class AppPaths( GsonConst.botConfigGson.toJson( setOf( BotConfig( - enabled = false, + enabled = true, proxy = ProxyConfig(), account = BotAccount( "Bot Username", @@ -317,14 +228,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-app/src/main/kotlin/AppMain.kt b/scalabot-app/src/main/kotlin/AppMain.kt index 5fc969d..ff0dd5d 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()) { @@ -133,23 +137,21 @@ 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.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" } } - if (botConfig.baseApiUrl != null) { - baseUrl = botConfig.baseApiUrl - } + baseUrl = botConfig.baseApiUrl } val account = botConfig.account diff --git a/scalabot-app/src/main/kotlin/BotDBMaker.kt b/scalabot-app/src/main/kotlin/BotDBMaker.kt index 92c08e7..e8832e4 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/Serializers.kt b/scalabot-app/src/main/kotlin/util/Serializers.kt deleted file mode 100644 index b574154..0000000 --- a/scalabot-app/src/main/kotlin/util/Serializers.kt +++ /dev/null @@ -1,127 +0,0 @@ -package net.lamgc.scalabot.util - -import com.google.gson.* -import net.lamgc.scalabot.MavenRepositoryConfig -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 { - - override fun deserialize( - json: JsonElement, - typeOfT: Type?, - context: JsonDeserializationContext? - ): DefaultBotOptions.ProxyType { - if (json.isJsonNull) { - return DefaultBotOptions.ProxyType.NO_PROXY - } - if (!json.isJsonPrimitive) { - throw JsonParseException("Wrong configuration value type.") - } - val value = json.asString.trim() - try { - return DefaultBotOptions.ProxyType.valueOf(value.uppercase()) - } catch (e: IllegalArgumentException) { - throw JsonParseException("Invalid value: $value") - } - } - - override fun serialize( - src: DefaultBotOptions.ProxyType, - typeOfSrc: Type?, - context: JsonSerializationContext? - ): JsonElement { - return JsonPrimitive(src.toString()) - } -} - -internal 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) - } - 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 { - if (!json.isJsonPrimitive) { - throw JsonParseException("Wrong configuration value type.") - } - return DefaultArtifact(json.asString.trim()) - } - -} - -internal object AuthenticationSerializer : JsonDeserializer { - - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Authentication { - if (json !is JsonObject) { - throw JsonParseException("Unsupported JSON type.") - } - val username = SerializerUtils.checkJsonKey(json, "username") - val password = SerializerUtils.checkJsonKey(json, "password") - val builder = AuthenticationBuilder() - builder.addUsername(username) - builder.addPassword(password) - return builder.build() - } - -} - -private object SerializerUtils { - fun checkJsonKey(json: JsonObject, key: String): String { - if (!json.has(key)) { - throw JsonParseException("Required field does not exist: $key") - } else if (!json.get(key).isJsonPrimitive) { - throw JsonParseException("Wrong field `$key` type: ${json.get(key)::class.java}") - } - return json.get(key).asString - } -} - -internal object MavenRepositoryConfigSerializer - : JsonDeserializer { - - override fun deserialize( - json: JsonElement, - typeOfT: Type, - context: JsonDeserializationContext - ): MavenRepositoryConfig { - return when (json) { - is JsonObject -> { - MavenRepositoryConfig( - id = json.get("id")?.asString, - url = URL(SerializerUtils.checkJsonKey(json, "url")), - proxy = if (json.has("proxy") && json.get("proxy").isJsonObject) - context.deserialize( - json.getAsJsonObject("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) - context.deserialize( - json.getAsJsonObject("authentication"), Authentication::class.java - ) else null - ) - } - is JsonPrimitive -> { - MavenRepositoryConfig(url = URL(json.asString)) - } - else -> { - throw JsonParseException("Unsupported Maven warehouse configuration type.") - } - } - } -} 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-app/src/test/kotlin/util/ArtifactSerializerTest.kt b/scalabot-app/src/test/kotlin/util/ArtifactSerializerTest.kt deleted file mode 100644 index 100c5b4..0000000 --- a/scalabot-app/src/test/kotlin/util/ArtifactSerializerTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -@file:Suppress("PackageDirectoryMismatch") - -package net.lamgc.scalabot.util - -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-app/src/test/kotlin/util/SerializersKtTest.kt b/scalabot-app/src/test/kotlin/util/SerializersKtTest.kt deleted file mode 100644 index 591c8e8..0000000 --- a/scalabot-app/src/test/kotlin/util/SerializersKtTest.kt +++ /dev/null @@ -1,325 +0,0 @@ -package util - -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 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 -import java.net.URL -import kotlin.test.* - -internal class SerializersKtTest { - - private val instance: Any - private val method: Method - - init { - val clazz = Class.forName("net.lamgc.scalabot.util.SerializerUtils") - method = clazz.getDeclaredMethod("checkJsonKey", JsonObject::class.java, String::class.java) - method.isAccessible = true - instance = clazz.getDeclaredField("INSTANCE").apply { - isAccessible = true - }.get(null) - } - - private fun invoke(json: JsonObject, key: String): String { - try { - return method.invoke(instance, json, key) as String - } catch (e: InvocationTargetException) { - throw e.targetException - } - } - - @Test - fun `Json key checker test`() { - assertThrows(JsonParseException::class.java) { - invoke(JsonObject(), "NOT_EXIST_KEY") - } - assertThrows(JsonParseException::class.java) { - invoke(JsonObject().apply { add("NULL_KEY", JsonNull.INSTANCE) }, "NULL_KEY") - } - assertThrows(JsonParseException::class.java) { - invoke(JsonObject().apply { add("ARRAY_KEY", JsonArray()) }, "ARRAY_KEY") - } - assertThrows(JsonParseException::class.java) { - invoke(JsonObject().apply { add("OBJECT_KEY", JsonObject()) }, "OBJECT_KEY") - } - - val expectKey = "TEST" - val expectString = "testString" - val json = JsonObject().apply { addProperty(expectKey, expectString) } - - assertEquals(expectString, invoke(json, expectKey)) - } -} - -internal class ProxyTypeSerializerTest { - - @Test - fun `serialize test`() { - for (type in DefaultBotOptions.ProxyType.values()) { - assertEquals( - JsonPrimitive(type.name), ProxyTypeSerializer.serialize(type, null, null), - "ProxyType 序列化结果与预期不符." - ) - } - } - - @Test - fun `deserialize test`() { - assertThrows(JsonParseException::class.java) { - ProxyTypeSerializer.deserialize(JsonObject(), null, null) - } - assertThrows(JsonParseException::class.java) { - ProxyTypeSerializer.deserialize(JsonArray(), null, null) - } - - assertThrows(JsonParseException::class.java) { - ProxyTypeSerializer.deserialize(JsonPrimitive("NOT_IN_ENUM_VALUE"), null, null) - } - - assertEquals( - DefaultBotOptions.ProxyType.NO_PROXY, - ProxyTypeSerializer.deserialize(JsonNull.INSTANCE, null, null) - ) - - for (type in DefaultBotOptions.ProxyType.values()) { - assertEquals( - type, ProxyTypeSerializer.deserialize(JsonPrimitive(type.name), null, null), - "ProxyType 反序列化结果与预期不符." - ) - - assertEquals( - type, ProxyTypeSerializer.deserialize(JsonPrimitive(" ${type.name} "), null, null), - "ProxyType 反序列化时未对 Json 字符串进行修剪(trim)." - ) - } - - } - -} - -internal class MavenRepositoryConfigSerializerTest { - - @Test - fun `unsupported json type deserialize test`() { - assertThrows(JsonParseException::class.java) { - MavenRepositoryConfigSerializer.deserialize( - JsonArray(), - MavenRepositoryConfig::class.java, - TestJsonDeserializationContext - ) - } - assertThrows(JsonParseException::class.java) { - MavenRepositoryConfigSerializer.deserialize( - JsonNull.INSTANCE, - MavenRepositoryConfig::class.java, - TestJsonDeserializationContext - ) - } - } - - @Test - fun `json primitive deserialize test`() { - val expectRepoUrl = "https://repo.example.org/maven" - val config = MavenRepositoryConfigSerializer.deserialize( - JsonPrimitive(expectRepoUrl), - MavenRepositoryConfig::class.java, - TestJsonDeserializationContext - ) - - assertNull(config.id) - assertEquals(URL(expectRepoUrl), config.url) - assertNull(config.proxy, "Proxy 默认值不为 null.") - assertEquals("default", config.layout) - assertTrue(config.enableReleases) - assertTrue(config.enableSnapshots) - assertNull(config.authentication) - } - - @Test - fun `json object default deserialize test`() { - val expectRepoUrl = "https://repo.example.org/maven" - val jsonObject = JsonObject() - jsonObject.addProperty("url", expectRepoUrl) - val config = MavenRepositoryConfigSerializer.deserialize( - jsonObject, - MavenRepositoryConfig::class.java, - TestJsonDeserializationContext - ) - - assertNull(config.id) - assertEquals(URL(expectRepoUrl), config.url) - assertNull(config.proxy, "Proxy 默认值不为 null.") - assertEquals("default", config.layout) - assertTrue(config.enableReleases) - assertTrue(config.enableSnapshots) - assertNull(config.authentication) - } - - @Test - fun `json object deserialize test`() { - @Language("JSON5") - val looksGoodJsonString = """ - { - "id": "test-repository", - "url": "https://repo.example.org/maven", - "proxy": { - "type": "http", - "host": "127.0.1.1", - "port": 10800 - }, - "layout": "default", - "enableReleases": false, - "enableSnapshots": true - } - """.trimIndent() - - val jsonObject = Gson().fromJson(looksGoodJsonString, JsonObject::class.java) - var config = MavenRepositoryConfigSerializer.deserialize( - jsonObject, - MavenRepositoryConfig::class.java, - TestJsonDeserializationContext - ) - - assertEquals(jsonObject["id"].asString, config.id) - assertEquals(URL(jsonObject["url"].asString), config.url) - assertEquals(Proxy("http", "127.0.1.1", 10800), config.proxy) - assertEquals(jsonObject["layout"].asString, config.layout) - assertEquals(jsonObject["enableReleases"].asBoolean, config.enableReleases) - assertEquals(jsonObject["enableSnapshots"].asBoolean, config.enableSnapshots) - - // ------------------------------------ - - jsonObject.add("proxy", JsonNull.INSTANCE) - jsonObject.remove("layout") - - config = MavenRepositoryConfigSerializer.deserialize( - jsonObject, - MavenRepositoryConfig::class.java, - TestJsonDeserializationContext - ) - - assertEquals(jsonObject["id"].asString, config.id) - assertEquals(URL(jsonObject["url"].asString), config.url) - assertNull(config.proxy) - assertEquals("default", config.layout) - assertEquals(jsonObject["enableReleases"].asBoolean, config.enableReleases) - assertEquals(jsonObject["enableSnapshots"].asBoolean, config.enableSnapshots) - - // ------------------------------------ - - jsonObject.add("authentication", JsonArray()) - jsonObject.add("layout", mockk { - every { asString }.returns(null) - }) - - config = MavenRepositoryConfigSerializer.deserialize( - jsonObject, - MavenRepositoryConfig::class.java, - TestJsonDeserializationContext - ) - - assertEquals(jsonObject["id"].asString, config.id) - assertEquals(URL(jsonObject["url"].asString), config.url) - assertNull(config.proxy) - assertEquals("default", config.layout) - assertEquals(jsonObject["enableReleases"].asBoolean, config.enableReleases) - assertEquals(jsonObject["enableSnapshots"].asBoolean, config.enableSnapshots) - assertNull(config.authentication) - - // ------------------------------------ - - jsonObject.add("authentication", JsonObject().apply { - addProperty("username", "testUsername") - addProperty("password", "testPassword") - }) - - config = MavenRepositoryConfigSerializer.deserialize( - jsonObject, - MavenRepositoryConfig::class.java, - TestJsonDeserializationContext - ) - - assertEquals(jsonObject["id"].asString, config.id) - assertEquals(URL(jsonObject["url"].asString), config.url) - assertNull(config.proxy) - assertEquals("default", config.layout) - assertEquals(jsonObject["enableReleases"].asBoolean, config.enableReleases) - assertEquals(jsonObject["enableSnapshots"].asBoolean, config.enableSnapshots) - assertNotNull(config.authentication) - } - -} - -private object TestJsonDeserializationContext : JsonDeserializationContext { - - private val gson = GsonBuilder() - .registerTypeAdapter(Authentication::class.java, AuthenticationSerializer) - .create() - - override fun deserialize(json: JsonElement, typeOfT: Type): T { - return gson.fromJson(json, typeOfT) - } -} - -internal class AuthenticationSerializerTest { - - @Test - fun `deserialize test`() { - assertThrows(JsonParseException::class.java) { - AuthenticationSerializer.deserialize( - JsonNull.INSTANCE, - Authentication::class.java, TestJsonDeserializationContext - ) - } - assertThrows(JsonParseException::class.java) { - AuthenticationSerializer.deserialize( - JsonArray(), - Authentication::class.java, TestJsonDeserializationContext - ) - } - assertThrows(JsonParseException::class.java) { - AuthenticationSerializer.deserialize( - JsonPrimitive("A STRING"), - Authentication::class.java, TestJsonDeserializationContext - ) - } - - val expectJsonObject = JsonObject().apply { - addProperty("username", "testUsername") - addProperty("password", "testPassword") - } - - val mockContext = mockk { - every { put(any(), any()) }.answers { } - } - - val result = AuthenticationSerializer.deserialize( - expectJsonObject, - Authentication::class.java, TestJsonDeserializationContext - ) - - assertNotNull(result) - result.fill(mockContext, "username", null) - result.fill(mockContext, "password", null) - - verify { - mockContext.put("username", "testUsername") - mockContext.put("password", "testPassword".toCharArray()) - } - } - -} diff --git a/scalabot-app/src/test/kotlin/util/UsernameAuthenticatorTest.kt b/scalabot-app/src/test/kotlin/util/UsernameAuthenticatorTest.kt deleted file mode 100644 index 92629db..0000000 --- a/scalabot-app/src/test/kotlin/util/UsernameAuthenticatorTest.kt +++ /dev/null @@ -1,114 +0,0 @@ -package net.lamgc.scalabot.util - -import com.google.gson.* -import org.junit.jupiter.api.assertThrows -import kotlin.test.* - -class UsernameAuthenticatorTest { - - @Test - fun checkCredentialsTest() { - val authenticator = UsernameAuthenticator("testUser", "testPassword") - assertTrue(authenticator.checkCredentials("testUser", "testPassword")) - assertFalse(authenticator.checkCredentials("falseUser", "testPassword")) - assertFalse(authenticator.checkCredentials("testUser", "falsePassword")) - } - - @Test - fun toJsonObjectTest() { - val authenticator = UsernameAuthenticator("testUser", "testPassword") - val jsonObject = authenticator.toJsonObject() - assertEquals("testUser", jsonObject["username"]?.asString) - assertEquals("testPassword", jsonObject["password"]?.asString) - } - - @Test - fun equalsTest() { - val authenticator = UsernameAuthenticator("testUser", "testPassword") - assertEquals(authenticator, UsernameAuthenticator("testUser", "testPassword")) - assertEquals(authenticator.hashCode(), UsernameAuthenticator("testUser", "testPassword").hashCode()) - assertNotEquals(authenticator, UsernameAuthenticator("testUser", "falsePassword")) - assertNotEquals(authenticator.hashCode(), UsernameAuthenticator("testUser", "falsePassword").hashCode()) - assertNotEquals(authenticator, UsernameAuthenticator("falseUser", "testPassword")) - assertNotEquals(authenticator.hashCode(), UsernameAuthenticator("falseUser", "testPassword").hashCode()) - assertNotEquals(authenticator, UsernameAuthenticator("falseUser", "falsePassword")) - assertNotEquals(authenticator.hashCode(), UsernameAuthenticator("falseUser", "falsePassword").hashCode()) - assertFalse(authenticator.equals(null)) - } - -} - -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/build.gradle.kts b/scalabot-meta/build.gradle.kts new file mode 100644 index 0000000..88cd8f7 --- /dev/null +++ b/scalabot-meta/build.gradle.kts @@ -0,0 +1,112 @@ +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 +} + +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") + + 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() +} + +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"]) +} diff --git a/scalabot-meta/src/main/kotlin/Configs.kt b/scalabot-meta/src/main/kotlin/Configs.kt new file mode 100644 index 0000000..1061680 --- /dev/null +++ b/scalabot-meta/src/main/kotlin/Configs.kt @@ -0,0 +1,131 @@ +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 机器人创建者, 管理机器人需要使用该信息. + * @property id 机器人账号 ID. + */ +data class BotAccount( + val name: String, + val token: String, + val creatorId: Long +) { + + val id + // 不要想着每次获取都要从 token 里取出有性能损耗. + // 由于 Gson 解析方式, 如果不这么做, 会出现 token 设置前 id 初始化完成, 就只有"0"了, + // 虽然能过单元测试, 但实际使用过程是不能正常用的. + get() = token.substringBefore(":").toLong() +} + +/** + * 机器人配置. + * @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, + val account: BotAccount, + val disableBuiltInAbility: Boolean = false, + val autoUpdateCommandList: Boolean = false, + /* + * 使用构件坐标来选择机器人所使用的扩展包. + * 这么做的原因是我暂时没找到一个合适的方法来让开发者方便地设定自己的扩展 Id, + * 而构件坐标(POM Reference 或者叫 GAV 坐标)是开发者创建 Maven/Gradle 项目时一定会设置的, + * 所以就直接用了. :P + */ + val extensions: Set = emptySet(), + val proxy: ProxyConfig = ProxyConfig(type = ProxyType.NO_PROXY), + val baseApiUrl: String = ApiConstants.BASE_URL +) + +/** + * 代理类型. + */ +enum class ProxyType { + NO_PROXY, + HTTP, + HTTPS, + 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 +) + +/** + * ScalaBot 的运行指标公开配置. + * + * ScalaBot 内置了用于公开运行指标的服务端, + * 该指标遵循 Prometheus 的标准, 可以通过 Prometheus 的工具来查看. + * + * @property enable 是否启用运行指标服务端. + * @property port 运行指标服务端的端口. + * @property bindAddress 运行指标服务端的绑定地址, 绑定后只有该地址可以访问. + * @property authenticator 运行指标服务端的 HTTP 认证配置. + */ +data class MetricsConfig( + val enable: Boolean = false, + val port: Int = 9386, + val bindAddress: String? = "0.0.0.0", + val authenticator: UsernameAuthenticator? = null +) + +/** + * 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, + 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-meta/src/main/kotlin/serializer/Serializer.kt b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt new file mode 100644 index 0000000..9899b96 --- /dev/null +++ b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt @@ -0,0 +1,262 @@ +package net.lamgc.scalabot.config.serializer + +import com.google.gson.* +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 java.lang.reflect.Type +import java.net.MalformedURLException +import java.net.URL + +object ProxyTypeSerializer : JsonDeserializer, + JsonSerializer { + + override fun deserialize( + json: JsonElement, + typeOfT: Type?, + context: JsonDeserializationContext? + ): ProxyType { + if (json.isJsonNull) { + return ProxyType.NO_PROXY + } + if (!json.isJsonPrimitive) { + throw JsonParseException("Wrong configuration value type.") + } + val value = json.asString.trim() + try { + return ProxyType.valueOf(value.uppercase()) + } catch (e: IllegalArgumentException) { + throw JsonParseException("Invalid value: $value") + } + } + + override fun serialize( + src: ProxyType, + typeOfSrc: Type?, + context: JsonSerializationContext? + ): JsonElement { + return JsonPrimitive(src.toString()) + } +} + +object ArtifactSerializer : JsonSerializer, JsonDeserializer { + override fun serialize(src: Artifact, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { + return if (src is AbstractArtifact) { + JsonPrimitive(src.toString()) + } else { + JsonPrimitive( + DefaultArtifact( + src.groupId, + src.artifactId, + src.classifier, + src.extension, + src.version + ).toString() + ) + } + } + + override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): Artifact { + if (!json.isJsonPrimitive) { + throw JsonParseException("Wrong configuration value type.") + } + val artifactStr = json.asString.trim() + try { + return DefaultArtifact(artifactStr) + } catch (e: IllegalArgumentException) { + throw JsonParseException("Invalid artifact format: `${artifactStr}`.") + } + } + +} + +object AuthenticationSerializer : JsonDeserializer { + + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Authentication { + if (json !is JsonObject) { + throw JsonParseException("Unsupported JSON type.") + } + val username = SerializerUtils.checkJsonKey(json, "username") + val password = SerializerUtils.checkJsonKey(json, "password") + val builder = AuthenticationBuilder() + builder.addUsername(username) + builder.addPassword(password) + return builder.build() + } + +} + +private object SerializerUtils { + fun checkJsonKey(json: JsonObject, key: String): String { + if (!json.has(key)) { + throw JsonParseException("Required field does not exist: $key") + } else if (!json.get(key).isJsonPrimitive) { + throw JsonParseException("Wrong field `$key` type: ${json.get(key)::class.java}") + } + return json.get(key).asString + } +} + +object MavenRepositoryConfigSerializer + : JsonDeserializer { + + override fun deserialize( + json: JsonElement, + typeOfT: Type, + context: JsonDeserializationContext + ): MavenRepositoryConfig { + return when (json) { + is JsonObject -> { + MavenRepositoryConfig( + id = json.get("id")?.asString, + url = URL(SerializerUtils.checkJsonKey(json, "url")), + proxy = if (json.has("proxy")) + context.deserialize( + 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")) + context.deserialize( + json.get("authentication"), Authentication::class.java + ) else null + ) + } + is JsonPrimitive -> { + try { + return MavenRepositoryConfig(url = URL(json.asString)) + } catch (e: MalformedURLException) { + throw JsonParseException("Invalid URL: ${json.asString}", e) + } + } + else -> { + throw JsonParseException("Unsupported Maven repository configuration type. (Only support JSON object or url string)") + } + } + } +} + +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) + } + +} + +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 { + + 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) + 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 ?: defaultConfig.enabled, + account = context.deserialize(json.get("account"), BotAccount::class.java)!!, + disableBuiltInAbility = json.get("disableBuiltInAbility")?.asBoolean ?: defaultConfig.disableBuiltInAbility, + autoUpdateCommandList = json.get("autoUpdateCommandList")?.asBoolean ?: defaultConfig.autoUpdateCommandList, + extensions = context.deserialize(json.get("extensions"), object : TypeToken>() {}.type) + ?: defaultConfig.extensions, + proxy = context.deserialize(json.get("proxy"), ProxyConfig::class.java) ?: defaultConfig.proxy, + baseApiUrl = json.get("baseApiUrl")?.asString ?: defaultConfig.baseApiUrl + ) + } +} + diff --git a/scalabot-meta/src/test/kotlin/ConfigsTest.kt b/scalabot-meta/src/test/kotlin/ConfigsTest.kt new file mode 100644 index 0000000..eaf4931 --- /dev/null +++ b/scalabot-meta/src/test/kotlin/ConfigsTest.kt @@ -0,0 +1,516 @@ +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 +import java.net.URL +import java.util.* +import kotlin.math.abs +import kotlin.test.* + +internal class BotAccountTest { + + @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() + + @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) + } + + @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) + } + + @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) + } + + @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 { + + 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": "testUser", + "password": "testPassword" + } + } + """.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) + + 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()) + } + } + +} + +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) + } + +} diff --git a/scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt b/scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt new file mode 100644 index 0000000..6dde4f1 --- /dev/null +++ b/scalabot-meta/src/test/kotlin/UsernameAuthenticatorTest.kt @@ -0,0 +1,38 @@ +package net.lamgc.scalabot.config + +import kotlin.test.* + +internal class UsernameAuthenticatorTest { + + @Test + fun checkCredentialsTest() { + val authenticator = UsernameAuthenticator("testUser", "testPassword") + assertTrue(authenticator.checkCredentials("testUser", "testPassword")) + assertFalse(authenticator.checkCredentials("falseUser", "testPassword")) + assertFalse(authenticator.checkCredentials("testUser", "falsePassword")) + assertFalse(authenticator.checkCredentials("falseUser", "falsePassword")) + } + + @Test + fun toJsonObjectTest() { + val authenticator = UsernameAuthenticator("testUser", "testPassword") + val jsonObject = authenticator.toJsonObject() + assertEquals("testUser", jsonObject["username"]?.asString) + assertEquals("testPassword", jsonObject["password"]?.asString) + } + + @Test + fun equalsTest() { + val authenticator = UsernameAuthenticator("testUser", "testPassword") + assertEquals(authenticator, UsernameAuthenticator("testUser", "testPassword")) + assertEquals(authenticator.hashCode(), UsernameAuthenticator("testUser", "testPassword").hashCode()) + assertNotEquals(authenticator, UsernameAuthenticator("testUser", "falsePassword")) + assertNotEquals(authenticator.hashCode(), UsernameAuthenticator("testUser", "falsePassword").hashCode()) + assertNotEquals(authenticator, UsernameAuthenticator("falseUser", "testPassword")) + assertNotEquals(authenticator.hashCode(), UsernameAuthenticator("falseUser", "testPassword").hashCode()) + assertNotEquals(authenticator, UsernameAuthenticator("falseUser", "falsePassword")) + assertNotEquals(authenticator.hashCode(), UsernameAuthenticator("falseUser", "falsePassword").hashCode()) + assertFalse(authenticator.equals(null)) + } + +} diff --git a/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt new file mode 100644 index 0000000..c5cd5dd --- /dev/null +++ b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt @@ -0,0 +1,708 @@ +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.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 +import org.intellij.lang.annotations.Language +import org.junit.jupiter.api.Assertions.assertThrows +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.Method +import java.lang.reflect.Type +import java.net.URL +import kotlin.test.* + +internal class SerializersKtTest { + + private val instance: Any + private val method: Method + + init { + 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 { + isAccessible = true + }.get(null) + } + + private fun invoke(json: JsonObject, key: String): String { + try { + return method.invoke(instance, json, key) as String + } catch (e: InvocationTargetException) { + throw e.targetException + } + } + + @Test + fun `Json key checker test`() { + assertThrows(JsonParseException::class.java) { + invoke(JsonObject(), "NOT_EXIST_KEY") + } + assertThrows(JsonParseException::class.java) { + invoke(JsonObject().apply { add("NULL_KEY", JsonNull.INSTANCE) }, "NULL_KEY") + } + assertThrows(JsonParseException::class.java) { + invoke(JsonObject().apply { add("ARRAY_KEY", JsonArray()) }, "ARRAY_KEY") + } + assertThrows(JsonParseException::class.java) { + invoke(JsonObject().apply { add("OBJECT_KEY", JsonObject()) }, "OBJECT_KEY") + } + + val expectKey = "TEST" + val expectString = "testString" + val json = JsonObject().apply { addProperty(expectKey, expectString) } + + assertEquals(expectString, invoke(json, expectKey)) + } +} + +internal class ProxyTypeSerializerTest { + + @Test + fun `serialize test`() { + for (type in ProxyType.values()) { + assertEquals( + JsonPrimitive(type.name), ProxyTypeSerializer.serialize(type, null, null), + "ProxyType 序列化结果与预期不符." + ) + } + } + + @Test + fun `deserialize test`() { + assertThrows(JsonParseException::class.java) { + ProxyTypeSerializer.deserialize(JsonObject(), null, null) + } + assertThrows(JsonParseException::class.java) { + ProxyTypeSerializer.deserialize(JsonArray(), null, null) + } + + assertThrows(JsonParseException::class.java) { + ProxyTypeSerializer.deserialize(JsonPrimitive("NOT_IN_ENUM_VALUE"), null, null) + } + + assertEquals( + ProxyType.NO_PROXY, + ProxyTypeSerializer.deserialize(JsonNull.INSTANCE, null, null) + ) + + for (type in ProxyType.values()) { + assertEquals( + type, ProxyTypeSerializer.deserialize(JsonPrimitive(type.name), null, null), + "ProxyType 反序列化结果与预期不符." + ) + + assertEquals( + type, ProxyTypeSerializer.deserialize(JsonPrimitive(" ${type.name} "), null, null), + "ProxyType 反序列化时未对 Json 字符串进行修剪(trim)." + ) + } + + } + +} + +internal class MavenRepositoryConfigSerializerTest { + + @Test + fun `unsupported json type deserialize test`() { + assertThrows(JsonParseException::class.java) { + MavenRepositoryConfigSerializer.deserialize( + JsonArray(), + MavenRepositoryConfig::class.java, + TestJsonSerializationContext.default() + ) + } + assertThrows(JsonParseException::class.java) { + MavenRepositoryConfigSerializer.deserialize( + JsonNull.INSTANCE, + MavenRepositoryConfig::class.java, + TestJsonSerializationContext.default() + ) + } + } + + @Test + fun `json primitive deserialize test`() { + assertThrows(JsonParseException::class.java) { + MavenRepositoryConfigSerializer.deserialize( + JsonPrimitive("NOT A URL."), + MavenRepositoryConfig::class.java, + TestJsonSerializationContext.default() + ) + } + + 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.") + assertEquals("default", config.layout) + assertTrue(config.enableReleases) + assertTrue(config.enableSnapshots) + assertNull(config.authentication) + } + + @Test + fun `json object default deserialize test`() { + val expectRepoUrl = "https://repo.example.org/maven" + val jsonObject = JsonObject() + jsonObject.addProperty("url", expectRepoUrl) + val config = MavenRepositoryConfigSerializer.deserialize( + jsonObject, + MavenRepositoryConfig::class.java, + TestJsonSerializationContext.default() + ) + + assertNull(config.id) + assertEquals(URL(expectRepoUrl), config.url) + assertNull(config.proxy, "Proxy 默认值不为 null.") + assertEquals("default", config.layout) + assertTrue(config.enableReleases) + assertTrue(config.enableSnapshots) + assertNull(config.authentication) + } + + @Test + fun `json object deserialize test`() { + @Language("JSON5") + val looksGoodJsonString = """ + { + "id": "test-repository", + "url": "https://repo.example.org/maven", + "proxy": { + "type": "http", + "host": "127.0.1.1", + "port": 10800 + }, + "layout": "default", + "enableReleases": false, + "enableSnapshots": true + } + """.trimIndent() + + val jsonObject = Gson().fromJson(looksGoodJsonString, JsonObject::class.java) + var config = MavenRepositoryConfigSerializer.deserialize( + jsonObject, + MavenRepositoryConfig::class.java, + TestJsonSerializationContext.default() + ) + + assertEquals(jsonObject["id"].asString, config.id) + assertEquals(URL(jsonObject["url"].asString), config.url) + assertEquals(Proxy("http", "127.0.1.1", 10800), config.proxy) + assertEquals(jsonObject["layout"].asString, config.layout) + assertEquals(jsonObject["enableReleases"].asBoolean, config.enableReleases) + assertEquals(jsonObject["enableSnapshots"].asBoolean, config.enableSnapshots) + + // ------------------------------------ + + jsonObject.add("proxy", JsonNull.INSTANCE) + jsonObject.remove("layout") + + config = MavenRepositoryConfigSerializer.deserialize( + jsonObject, + MavenRepositoryConfig::class.java, + TestJsonSerializationContext.default() + ) + + assertEquals(jsonObject["id"].asString, config.id) + assertEquals(URL(jsonObject["url"].asString), config.url) + assertNull(config.proxy) + assertEquals("default", config.layout) + assertEquals(jsonObject["enableReleases"].asBoolean, config.enableReleases) + assertEquals(jsonObject["enableSnapshots"].asBoolean, config.enableSnapshots) + + // ------------------------------------ + + jsonObject.add("layout", mockk { + every { asString }.returns(null) + }) + + config = MavenRepositoryConfigSerializer.deserialize( + jsonObject, + MavenRepositoryConfig::class.java, + TestJsonSerializationContext.default() + ) + + assertEquals(jsonObject["id"].asString, config.id) + assertEquals(URL(jsonObject["url"].asString), config.url) + assertNull(config.proxy) + assertEquals("default", config.layout) + assertEquals(jsonObject["enableReleases"].asBoolean, config.enableReleases) + assertEquals(jsonObject["enableSnapshots"].asBoolean, config.enableSnapshots) + assertNull(config.authentication) + + // ------------------------------------ + + jsonObject.add("authentication", JsonObject().apply { + addProperty("username", "testUsername") + addProperty("password", "testPassword") + }) + + config = MavenRepositoryConfigSerializer.deserialize( + jsonObject, + MavenRepositoryConfig::class.java, + TestJsonSerializationContext.default() + ) + + assertEquals(jsonObject["id"].asString, config.id) + assertEquals(URL(jsonObject["url"].asString), config.url) + assertNull(config.proxy) + assertEquals("default", config.layout) + assertEquals(jsonObject["enableReleases"].asBoolean, config.enableReleases) + assertEquals(jsonObject["enableSnapshots"].asBoolean, config.enableSnapshots) + assertNotNull(config.authentication) + } + +} + +private class TestJsonSerializationContext(private val gson: Gson) : JsonDeserializationContext, + JsonSerializationContext { + + 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 { + + @Test + fun `deserialize test`() { + assertThrows(JsonParseException::class.java) { + AuthenticationSerializer.deserialize( + JsonNull.INSTANCE, + Authentication::class.java, TestJsonSerializationContext.default() + ) + } + assertThrows(JsonParseException::class.java) { + AuthenticationSerializer.deserialize( + JsonArray(), + Authentication::class.java, TestJsonSerializationContext.default() + ) + } + assertThrows(JsonParseException::class.java) { + AuthenticationSerializer.deserialize( + JsonPrimitive("A STRING"), + Authentication::class.java, TestJsonSerializationContext.default() + ) + } + + val expectJsonObject = JsonObject().apply { + addProperty("username", "testUsername") + addProperty("password", "testPassword") + } + + val mockContext = mockk { + every { put(any(), any()) }.answers { } + } + + val result = AuthenticationSerializer.deserialize( + expectJsonObject, + Authentication::class.java, TestJsonSerializationContext.default() + ) + + assertNotNull(result) + result.fill(mockContext, "username", null) + result.fill(mockContext, "password", null) + + verify { + mockContext.put("username", "testUsername") + mockContext.put("password", "testPassword".toCharArray()) + } + } + +} + +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().apply { + add("org.example:test:1.0.0-SNAPSHOT") + }) + } + + 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(false, botConfig.extensions.isEmpty()) + assertEquals(1, botConfig.extensions.size) + } +} + +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 { + + @Test + fun badJsonType() { + assertFailsWith { ArtifactSerializer.deserialize(JsonObject(), null, null) } + assertFailsWith { ArtifactSerializer.deserialize(JsonArray(), null, null) } + assertFailsWith { ArtifactSerializer.deserialize(JsonPrimitive("A STRING"), 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 `Bad format serialization`() { + assertFailsWith { + ArtifactSerializer.deserialize(JsonPrimitive("org.example~test"), null, null) + } + } + + @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) + } + + @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) + } +} + +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")) + } + +} 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")