diff --git a/CHANGELOG.md b/CHANGELOG.md index 719ef6d064..208179e5fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,18 @@ * `Game` now is not serializable and have no additional trash, related to serialization * `TelegramFile` was removed +### 0.18.1 Libraries update + +* Update libraries: + * `kotlin`: 1.3.41 -> 1.3.61 + * `kotlin coroutines`: 1.2.2 -> 1.3.2 + * `kotlin serialization`: 0.11.1 -> 0.14.0 + * `joda time`: 2.10.3 -> 2.10.5 + * `ktor`: 1.2.3 -> 1.2.6 +* `BotAction` now will be deserialized in a little bit other way + * `BotActionSerializer` now is internal +* Most part of serializers now are objects (instead of classes as was previously) + ## 0.17.0 July 29, 2019 API Update Libraries updates: diff --git a/build.gradle b/build.gradle index 6204b9b1f1..01f8be4cd9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -project.version = "0.18.0" +project.version = "0.18.1" project.group = "com.github.insanusmokrassar" buildscript { @@ -39,6 +39,8 @@ dependencies { api "io.ktor:ktor-server:$ktor_version" api "io.ktor:ktor-server-host-common:$ktor_version" + + testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" } compileKotlin { diff --git a/gradle.properties b/gradle.properties index eee4a31201..069d652777 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ kotlin.code.style=official -kotlin_version=1.3.41 -kotlin_coroutines_version=1.2.2 -kotlin_serialisation_runtime_version=0.11.1 -joda_time_version=2.10.3 -ktor_version=1.2.3 +kotlin_version=1.3.61 +kotlin_coroutines_version=1.3.2 +kotlin_serialisation_runtime_version=0.14.0 +joda_time_version=2.10.5 +ktor_version=1.2.6 gradle_bintray_plugin_version=1.8.4 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 59f3ee8c4c..0879550bd9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Feb 21 12:05:40 HKT 2019 +#Fri Nov 29 12:11:00 HKT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip diff --git a/settings.gradle b/settings.gradle index c0b8bff845..ec52a7d9d2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,18 +1 @@ -/* - * This settings file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * In a single project build this file can be empty or even removed. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user guide at https://docs.gradle.org/3.4.1/userguide/multi_project_builds.html - */ - -/* -// To declare projects as part of a multi-project build use the 'include' method -include 'shared' -include 'api' -include 'services:webservice' -*/ - rootProject.name = 'TelegramBotAPI' diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ChatIdentifier.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ChatIdentifier.kt index c1d5928304..cc6d97f46e 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ChatIdentifier.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ChatIdentifier.kt @@ -37,7 +37,7 @@ data class Username( fun String.toUsername(): Username = Username(this) @Serializer(ChatIdentifier::class) -internal class ChatIdentifierSerializer: KSerializer { +internal object ChatIdentifierSerializer : KSerializer { override fun deserialize(decoder: Decoder): ChatIdentifier { val id = decoder.decodeString() return id.toLongOrNull() ?.let { @@ -51,7 +51,7 @@ internal class ChatIdentifierSerializer: KSerializer { override fun serialize(encoder: Encoder, obj: ChatIdentifier) { when (obj) { - is ChatId -> encoder.encodeString(obj.chatId.toString()) + is ChatId -> encoder.encodeLong(obj.chatId) is Username -> encoder.encodeString(obj.username) } } diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ParseMode/ParseMode.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ParseMode/ParseMode.kt index 98a1d1b1b4..7a94983458 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ParseMode/ParseMode.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ParseMode/ParseMode.kt @@ -27,10 +27,9 @@ typealias Markdown = MarkdownParseMode typealias HTML = HTMLParseMode @Serializer(ParseMode::class) -internal class ParseModeSerializerObject: KSerializer { +internal object ParseModeSerializerObject : KSerializer { override fun deserialize(decoder: Decoder): ParseMode { - val mode = decoder.decodeString() - return when (mode) { + return when (decoder.decodeString()) { MarkdownParseMode.parseModeName -> MarkdownParseMode HTMLParseMode.parseModeName -> HTMLParseMode else -> throw IllegalArgumentException("Unknown parse mode") diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/TelegramDate.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/TelegramDate.kt index b83ec0ace6..ff71a73f73 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/TelegramDate.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/TelegramDate.kt @@ -5,7 +5,10 @@ import org.joda.time.DateTime import java.util.concurrent.TimeUnit @Serializable(TelegramDateSerializer::class) -class TelegramDate( +data class TelegramDate( + /** + * Contains UNIX time (seconds) + */ private val date: Long ) { constructor(dateTime: DateTime) : this( @@ -21,7 +24,7 @@ class TelegramDate( fun DateTime.toTelegramDate(): TelegramDate = TelegramDate(this) @Serializer(TelegramDate::class) -internal class TelegramDateSerializer: KSerializer { +internal object TelegramDateSerializer : KSerializer { override fun serialize(encoder: Encoder, obj: TelegramDate) { encoder.encodeLong( TimeUnit.MILLISECONDS.toSeconds(obj.asDate.millis) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/actions/BotAction.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/actions/BotAction.kt index 4dde4ccf52..8563a874c7 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/actions/BotAction.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/actions/BotAction.kt @@ -2,31 +2,29 @@ package com.github.insanusmokrassar.TelegramBotAPI.types.actions import kotlinx.serialization.* -private val actions = listOf( - TypingAction, - UploadPhotoAction, - RecordVideoAction, - UploadVideoAction, - RecordAudioAction, - UploadAudioAction, - UploadDocumentAction, - FindLocationAction -) - @Serializable(BotActionSerializer::class) sealed class BotAction { abstract val actionName: String } @Serializer(BotAction::class) -class BotActionSerializer: KSerializer { +internal object BotActionSerializer: KSerializer { override fun serialize(encoder: Encoder, obj: BotAction) { encoder.encodeString(obj.actionName) } override fun deserialize(decoder: Decoder): BotAction { - val actionName = decoder.decodeString() - return actions.firstOrNull { it.actionName == actionName } ?: throw IllegalStateException("Unknown action type: $actionName") + return when (val actionName = decoder.decodeString()) { + TypingAction.actionName -> TypingAction + UploadPhotoAction.actionName -> UploadPhotoAction + RecordVideoAction.actionName -> RecordVideoAction + UploadVideoAction.actionName -> UploadVideoAction + RecordAudioAction.actionName -> RecordAudioAction + UploadAudioAction.actionName -> UploadAudioAction + UploadDocumentAction.actionName -> UploadDocumentAction + FindLocationAction.actionName -> FindLocationAction + else -> throw IllegalStateException("Unknown action type: $actionName") + } } } diff --git a/src/test/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/BotActionTests.kt b/src/test/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/BotActionTests.kt new file mode 100644 index 0000000000..c208576169 --- /dev/null +++ b/src/test/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/BotActionTests.kt @@ -0,0 +1,60 @@ +package com.github.insanusmokrassar.TelegramBotAPI.types + +import com.github.insanusmokrassar.TelegramBotAPI.types.actions.* +import kotlinx.serialization.ImplicitReflectionSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlin.test.Test +import kotlin.test.assertEquals + +@ImplicitReflectionSerializer +class BotActionTests { + @Serializable + data class Example( + val botAction: BotAction + ) + + private fun checkBotAction(example: Example, sourceAction: BotAction) { + assertEquals( + sourceAction.actionName, + when (example.botAction) { + TypingAction -> example.botAction.actionName + UploadPhotoAction -> example.botAction.actionName + RecordVideoAction -> example.botAction.actionName + UploadVideoAction -> example.botAction.actionName + RecordAudioAction -> example.botAction.actionName + UploadAudioAction -> example.botAction.actionName + UploadDocumentAction -> example.botAction.actionName + FindLocationAction -> example.botAction.actionName + } + ) + } + + private fun checkBotActionSerializeDeserialize(example: Example) { + val stringified = Json.plain.stringify(Example.serializer(), example) + assertEquals("{\"botAction\":\"${example.botAction.actionName}\"}", stringified) + + val deserialized = Json.plain.parse(Example.serializer(), stringified) + + assertEquals(example, deserialized) + + checkBotAction(deserialized, example.botAction) + } + + @Test + fun `BotAction correctly serialized and deserialized`() { + fun BotAction.example() = Example(this) + listOf( + TypingAction.example(), + UploadPhotoAction.example(), + RecordVideoAction.example(), + UploadVideoAction.example(), + RecordAudioAction.example(), + UploadAudioAction.example(), + UploadDocumentAction.example(), + FindLocationAction.example() + ).forEach { + checkBotActionSerializeDeserialize(it) + } + } +} diff --git a/src/test/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ChatIdentifierTests.kt b/src/test/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ChatIdentifierTests.kt new file mode 100644 index 0000000000..bcf6f87870 --- /dev/null +++ b/src/test/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ChatIdentifierTests.kt @@ -0,0 +1,79 @@ +package com.github.insanusmokrassar.TelegramBotAPI.types + +import kotlinx.serialization.ImplicitReflectionSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlin.test.* + +@ImplicitReflectionSerializer +private const val chatIdentifierChatId: Identifier = 123L +@ImplicitReflectionSerializer +private const val chatIdentifierLink = "tg://user?id=$chatIdentifierChatId" +@ImplicitReflectionSerializer +private const val testUsername = "@Example" + +@ImplicitReflectionSerializer +class ChatIdentifierTests { + @Test + fun `Cast from Int to ChatId is working correctly`() { + val chatId = chatIdentifierChatId.toInt().toChatId() + assertEquals(chatIdentifierChatId, chatId.chatId) + } + @Test + fun `Cast from Byte to ChatId is working correctly`() { + val chatId = chatIdentifierChatId.toByte().toChatId() + assertEquals(chatIdentifierChatId, chatId.chatId) + } + @Test + fun `Cast from Identifier to ChatId is working correctly`() { + val chatId = chatIdentifierChatId.toChatId() + assertEquals(chatIdentifierChatId, chatId.chatId) + } + + @Test + fun `Creating link from ChatId is correct`() { + val chatId = chatIdentifierChatId.toChatId() + assertEquals(chatIdentifierLink, chatId.link) + } + + @Test + fun `Cast from String to Username is working correctly`() { + assertEquals(testUsername, testUsername.toUsername().username) + + assertFails("Username creating must fail when trying to create from string which is not starting from @ symbol") { + testUsername.replace("@", "").toUsername().username + } + } + + + @Test + fun `Deserializing from String must work correctly`() { + @Serializable + data class Example( + val identifier: ChatIdentifier + ) + + Example(chatIdentifierChatId.toChatId()).let { withChatId -> + val stringified = Json.plain.stringify(Example.serializer(), withChatId) + assertEquals(stringified, "{\"identifier\":$chatIdentifierChatId}") + val deserialized = Json.plain.parse(Example.serializer(), stringified) + assertEquals(withChatId, deserialized) + } + + Example(testUsername.toUsername()).let { withUsername -> + val stringified = Json.plain.stringify(Example.serializer(), withUsername) + assertEquals(stringified, "{\"identifier\":\"$testUsername\"}") + val deserialized = Json.plain.parse(Example.serializer(), stringified) + assertEquals(withUsername, deserialized) + } + + // Replace @ by empty string, because from time to time we can retrieve from Telegram system + // username without starting @ symbol + Example(testUsername.toUsername()).let { withUsername -> + val stringified = Json.plain.stringify(Example.serializer(), withUsername).replace("@", "") + assertEquals("{\"identifier\":\"${testUsername.replace("@", "")}\"}", stringified) + val deserialized = Json.plain.parse(Example.serializer(), stringified) + assertEquals(withUsername, deserialized) + } + } +} diff --git a/src/test/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ParseModeTests.kt b/src/test/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ParseModeTests.kt new file mode 100644 index 0000000000..42aa361716 --- /dev/null +++ b/src/test/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/ParseModeTests.kt @@ -0,0 +1,38 @@ +package com.github.insanusmokrassar.TelegramBotAPI.types + +import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.* +import kotlinx.serialization.ImplicitReflectionSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlin.test.Test +import kotlin.test.assertEquals + +@ImplicitReflectionSerializer +class ParseModeTests { + @Serializable + data class Example( + val mode: ParseMode + ) + + @Test + fun `Markdown parse mode correctly serializing and deserializing`() { + val example = Example(Markdown) + + val stringified = Json.plain.stringify(Example.serializer(), example) + assertEquals("{\"mode\":\"Markdown\"}", stringified) + + val deserialized = Json.plain.parse(Example.serializer(), stringified) + assertEquals(example, deserialized) + } + + @Test + fun `HTML parse mode correctly serializing and deserializing`() { + val example = Example(HTML) + + val stringified = Json.plain.stringify(Example.serializer(), example) + assertEquals("{\"mode\":\"HTML\"}", stringified) + + val deserialized = Json.plain.parse(Example.serializer(), stringified) + assertEquals(example, deserialized) + } +} diff --git a/src/test/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/TelegramDateTests.kt b/src/test/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/TelegramDateTests.kt new file mode 100644 index 0000000000..ee9fd95ead --- /dev/null +++ b/src/test/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/TelegramDateTests.kt @@ -0,0 +1,33 @@ +package com.github.insanusmokrassar.TelegramBotAPI.types + +import kotlinx.serialization.ImplicitReflectionSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import org.joda.time.DateTime +import java.util.concurrent.TimeUnit +import kotlin.test.Test +import kotlin.test.assertEquals + +private val dateTimeMillis = System.currentTimeMillis() +private val dateTimeUnix = TimeUnit.MILLISECONDS.toSeconds(dateTimeMillis) +private val dateTime = DateTime(TimeUnit.SECONDS.toMillis(dateTimeUnix)) + +@ImplicitReflectionSerializer +class TelegramDateTests { + @Serializable + data class Example( + val dateTime: TelegramDate + ) + @Test + fun `Serializtion of TelegramDate is working correctly`() { + val example = Example(TelegramDate(dateTimeUnix)) + + val stringified = Json.plain.stringify(Example.serializer(), example) + assertEquals("{\"dateTime\":$dateTimeUnix}", stringified) + + val deserialized = Json.plain.parse(Example.serializer(), stringified) + assertEquals(example, deserialized) + + assertEquals(dateTime, deserialized.dateTime.asDate) + } +}