From e35c7734a7a437b9c2bc087471fa65c1228f1a61 Mon Sep 17 00:00:00 2001 From: malkoas <41993717+malkoas@users.noreply.github.com> Date: Tue, 13 Jun 2023 16:00:46 +0300 Subject: [PATCH] BUS-24: Initial commit (#1) --- .github/settings.yml | 2 + .github/workflows/basic-linters.yml | 10 + .github/workflows/build.yml | 10 + .github/workflows/deploy.yml | 13 + .gitignore | 78 +++++ LICENSE | 176 ++++++++++ pom.xml | 321 ++++++++++++++++++ renovate.json | 4 + .../alert/tg/bot/AlertTgBotApplication.java | 15 + .../alert/tg/bot/config/MayDayConfig.java | 21 ++ .../config/properties/AlertBotProperties.java | 24 ++ .../config/properties/MayDayProperties.java | 23 ++ .../alert/tg/bot/constants/MainMenu.java | 28 ++ .../alert/tg/bot/constants/TextConstants.java | 23 ++ .../alert/tg/bot/constants/UserStatuses.java | 24 ++ .../vality/alert/tg/bot/dao/AbstractDao.java | 12 + .../alert/tg/bot/dao/ParametersDao.java | 11 + .../vality/alert/tg/bot/dao/StateDataDao.java | 13 + .../tg/bot/dao/impl/ParametersDaoImpl.java | 55 +++ .../tg/bot/dao/impl/StateDataDaoImpl.java | 64 ++++ .../tg/bot/exeptions/AlertTgBotException.java | 12 + .../exeptions/UnknownHandlerException.java | 8 + .../alert/tg/bot/handler/CallbackHandler.java | 41 +++ .../alert/tg/bot/handler/CommonHandler.java | 13 + .../alert/tg/bot/handler/MainMenuHandler.java | 34 ++ .../alert/tg/bot/handler/MessageHandler.java | 43 +++ .../alert/tg/bot/handler/ReplyHandler.java | 69 ++++ .../alert/tg/bot/mapper/JsonMapper.java | 28 ++ .../tg/bot/mapper/MenuCallbackMapper.java | 94 +++++ .../bot/mapper/ParametersCallbackMapper.java | 71 ++++ .../tg/bot/mapper/ReplyMessagesMapper.java | 82 +++++ .../vality/alert/tg/bot/service/AlertBot.java | 109 ++++++ .../alert/tg/bot/service/MayDayService.java | 39 +++ .../alert/tg/bot/service/NotifierService.java | 44 +++ .../tg/bot/servlet/AlertTgBotServlet.java | 29 ++ .../alert/tg/bot/utils/MainMenuBuilder.java | 39 +++ src/main/resources/application.yml | 50 +++ src/main/resources/db/migration/V1__init.sql | 26 ++ .../alert/tg/bot/TestObjectFactory.java | 141 ++++++++ .../ExcludeDataSourceConfiguration.java | 19 ++ .../tg/bot/config/PostgresqlJooqTest.java | 16 + .../tg/bot/dao/ParametersDaoImplTest.java | 57 ++++ .../alert/tg/bot/dao/StateDaoImplTest.java | 73 ++++ .../tg/bot/handler/CallbackHandlerTest.java | 105 ++++++ .../tg/bot/handler/MainMenuHandlerTest.java | 41 +++ .../tg/bot/handler/MessageHandlerTest.java | 44 +++ .../tg/bot/handler/ReplyHandlerTest.java | 66 ++++ .../tg/bot/mapper/MenuCallbackMapperTest.java | 90 +++++ .../mapper/ParametersCallbackMapperTest.java | 59 ++++ .../tg/bot/mapper/ReplyMessageMapperTest.java | 78 +++++ src/test/resources/logback-test.xml | 10 + 51 files changed, 2557 insertions(+) create mode 100644 .github/settings.yml create mode 100644 .github/workflows/basic-linters.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 pom.xml create mode 100644 renovate.json create mode 100644 src/main/java/dev/vality/alert/tg/bot/AlertTgBotApplication.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/config/MayDayConfig.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/config/properties/AlertBotProperties.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/config/properties/MayDayProperties.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/constants/MainMenu.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/constants/TextConstants.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/constants/UserStatuses.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/dao/AbstractDao.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/dao/ParametersDao.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/dao/StateDataDao.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/dao/impl/ParametersDaoImpl.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/dao/impl/StateDataDaoImpl.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/exeptions/AlertTgBotException.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/exeptions/UnknownHandlerException.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/handler/CallbackHandler.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/handler/CommonHandler.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/handler/MainMenuHandler.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/handler/MessageHandler.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/handler/ReplyHandler.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/mapper/JsonMapper.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapper.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapper.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/mapper/ReplyMessagesMapper.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/service/AlertBot.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/service/MayDayService.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/service/NotifierService.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/servlet/AlertTgBotServlet.java create mode 100644 src/main/java/dev/vality/alert/tg/bot/utils/MainMenuBuilder.java create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/db/migration/V1__init.sql create mode 100644 src/test/java/dev/vality/alert/tg/bot/TestObjectFactory.java create mode 100644 src/test/java/dev/vality/alert/tg/bot/config/ExcludeDataSourceConfiguration.java create mode 100644 src/test/java/dev/vality/alert/tg/bot/config/PostgresqlJooqTest.java create mode 100644 src/test/java/dev/vality/alert/tg/bot/dao/ParametersDaoImplTest.java create mode 100644 src/test/java/dev/vality/alert/tg/bot/dao/StateDaoImplTest.java create mode 100644 src/test/java/dev/vality/alert/tg/bot/handler/CallbackHandlerTest.java create mode 100644 src/test/java/dev/vality/alert/tg/bot/handler/MainMenuHandlerTest.java create mode 100644 src/test/java/dev/vality/alert/tg/bot/handler/MessageHandlerTest.java create mode 100644 src/test/java/dev/vality/alert/tg/bot/handler/ReplyHandlerTest.java create mode 100644 src/test/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapperTest.java create mode 100644 src/test/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapperTest.java create mode 100644 src/test/java/dev/vality/alert/tg/bot/mapper/ReplyMessageMapperTest.java create mode 100644 src/test/resources/logback-test.xml diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 0000000..9267e7d --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,2 @@ +# These settings are synced to GitHub by https://probot.github.io/apps/settings/ +_extends: .github diff --git a/.github/workflows/basic-linters.yml b/.github/workflows/basic-linters.yml new file mode 100644 index 0000000..6114f14 --- /dev/null +++ b/.github/workflows/basic-linters.yml @@ -0,0 +1,10 @@ +name: Vality basic linters + +on: + pull_request: + branches: + - "*" + +jobs: + lint: + uses: valitydev/base-workflows/.github/workflows/basic-linters.yml@v1 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..424e109 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,10 @@ +name: Build Maven Artifact + +on: + pull_request: + branches: + - '*' + +jobs: + build: + uses: valitydev/java-workflow/.github/workflows/maven-service-build.yml@v2 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..a4192d7 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,13 @@ +name: Maven Deploy Artifact + +on: + push: + branches: + - 'master' + +jobs: + build-and-deploy: + uses: valitydev/java-workflow/.github/workflows/maven-service-deploy.yml@v2 + secrets: + github-token: ${{ secrets.GITHUB_TOKEN }} + mm-webhook-url: ${{ secrets.MATTERMOST_WEBHOOK_URL }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..721f9b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,78 @@ +# Created by .ignore support plugin (hsz.mobi) +.eunit +deps +*.o +*.beam +*.plt +erl_crash.dump +ebin/*.beam +rel/example_project +.concrete/DEV_MODE +.rebar +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/ +.idea/workspace.xml +.idea/tasks.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +*.iws +*.ipr +*.iml + + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +env.list diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d9a10c0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..03dfd73 --- /dev/null +++ b/pom.xml @@ -0,0 +1,321 @@ + + + 4.0.0 + + + dev.vality + service-parent-pom + 2.1.4 + + + alert-tg-bot + 1.0-SNAPSHOT + + + UTF-8 + UTF-8 + 17 + 8022 + 8023 + ${server.port} ${management.port} + alert_tg_bot + 5432 + jdbc:postgresql://localhost:${db.port}/${db.name} + postgres + postgres + alert_tg_bot + 1.4.3 + + + + + + dev.vality + mayday-proto + 1.5-5015827 + + + dev.vality + alert-tg-bot-proto + 1.4-adf83ed + + + dev.vality + db-common-lib + + + dev.vality.geck + serializer + + + dev.vality.geck + common + + + dev.vality.geck + filter + + + dev.vality.geck + migrator + + + dev.vality + adapter-common-lib + 1.2.8 + + + dev.vality + damsel + 1.597-bfedcb9 + + + + + org.springframework.vault + spring-vault-core + 2.3.2 + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + org.hibernate + hibernate-validator + + + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-jooq + + + + + javax.servlet + javax.servlet-api + + + org.projectlombok + lombok + provided + + + io.micrometer + micrometer-core + + + io.micrometer + micrometer-registry-prometheus + + + org.telegram + telegrambots + 6.5.0 + + + org.telegram + telegrambots-spring-boot-starter + 6.5.0 + + + org.flywaydb + flyway-core + + + com.zaxxer + HikariCP + + + org.postgresql + postgresql + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + dev.vality + testcontainers-annotations + ${testcontainers.annotations.version} + test + + + + + + + ${project.build.directory}/maven-shared-archive-resources + ${project.build.directory} + + Dockerfile + + true + + + ${project.build.directory}/maven-shared-archive-resources + true + + Dockerfile + + + + src/main/resources + true + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-remote-resources-plugin + 1.6.0 + + + org.apache.maven.shared + maven-filtering + 1.3 + + + + + dev.vality:shared-resources:${shared-resources.version} + + false + false + + + + + process + + + + + + org.flywaydb + flyway-maven-plugin + + ${db.url} + ${db.user} + ${db.password} + + ${db.schema} + + + filesystem:${project.basedir}/src/main/resources/db/migration + + + + + migrate + generate-sources + + migrate + + + + + + org.jooq + jooq-codegen-maven + + + org.postgresql.Driver + ${db.url} + ${db.user} + ${db.password} + + + + true + true + true + true + + + org.jooq.meta.postgres.PostgresDatabase + .* + schema_version|flyway_schema_history + ${db.schema} + + + dev.vality.alert.tg.bot.domain + target/generated-sources/jooq + + + + + + gen-src + generate-sources + + generate + + + + + + dev.vality.maven.plugins + pg-embedded-plugin + 1.0.1 + + ${db.port} + ${db.name} + + ${db.schema} + + + + + PG_server_start + initialize + + start + + + + PG_server_stop + compile + + stop + + + + + + + diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..a20bfd6 --- /dev/null +++ b/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["local>valitydev/.github:renovate-config"] +} diff --git a/src/main/java/dev/vality/alert/tg/bot/AlertTgBotApplication.java b/src/main/java/dev/vality/alert/tg/bot/AlertTgBotApplication.java new file mode 100644 index 0000000..e8961b2 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/AlertTgBotApplication.java @@ -0,0 +1,15 @@ +package dev.vality.alert.tg.bot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.ServletComponentScan; + +@ServletComponentScan +@SpringBootApplication +public class AlertTgBotApplication extends SpringApplication { + + public static void main(String[] args) { + SpringApplication.run(AlertTgBotApplication.class, args); + } + +} diff --git a/src/main/java/dev/vality/alert/tg/bot/config/MayDayConfig.java b/src/main/java/dev/vality/alert/tg/bot/config/MayDayConfig.java new file mode 100644 index 0000000..b14f620 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/config/MayDayConfig.java @@ -0,0 +1,21 @@ +package dev.vality.alert.tg.bot.config; + +import dev.vality.alert.tg.bot.config.properties.MayDayProperties; +import dev.vality.alerting.mayday.AlertingServiceSrv; +import dev.vality.woody.thrift.impl.http.THSpawnClientBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.IOException; + +@Configuration +public class MayDayConfig { + + @Bean + public AlertingServiceSrv.Iface mayDayClient(MayDayProperties properties) throws IOException { + return new THSpawnClientBuilder() + .withAddress(properties.getUrl().getURI()) + .withNetworkTimeout(properties.getNetworkTimeout()) + .build(AlertingServiceSrv.Iface.class); + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/config/properties/AlertBotProperties.java b/src/main/java/dev/vality/alert/tg/bot/config/properties/AlertBotProperties.java new file mode 100644 index 0000000..cfbe1d9 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/config/properties/AlertBotProperties.java @@ -0,0 +1,24 @@ +package dev.vality.alert.tg.bot.config.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotNull; + +@Configuration +@ConfigurationProperties(prefix = "bot") +@Validated +@Getter +@Setter +public class AlertBotProperties { + + @NotNull + private String token; + @NotNull + private String name; + @NotNull + private String chatId; +} diff --git a/src/main/java/dev/vality/alert/tg/bot/config/properties/MayDayProperties.java b/src/main/java/dev/vality/alert/tg/bot/config/properties/MayDayProperties.java new file mode 100644 index 0000000..9ba04a1 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/config/properties/MayDayProperties.java @@ -0,0 +1,23 @@ +package dev.vality.alert.tg.bot.config.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotNull; + +@Configuration +@ConfigurationProperties(prefix = "mayday") +@Validated +@Getter +@Setter +public class MayDayProperties { + + @NotNull + private Resource url; + @NotNull + private int networkTimeout = 5000; +} diff --git a/src/main/java/dev/vality/alert/tg/bot/constants/MainMenu.java b/src/main/java/dev/vality/alert/tg/bot/constants/MainMenu.java new file mode 100644 index 0000000..fd95eab --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/constants/MainMenu.java @@ -0,0 +1,28 @@ +package dev.vality.alert.tg.bot.constants; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +@Getter +@RequiredArgsConstructor +public enum MainMenu { + + CREATE_ALERT("Создать новый алерт", "createAlert"), + GET_ALL_ALERTS("Получить все созданные алерты", "getAllAlerts"), + DELETE_ALERT("Удалить алерт", "deleteAlert"), + RETURN_TO_MENU("Возврат в меню", "return"), + DELETE_ALL_ALERTS("Удалить все алерты", "deleteAllAlerts"), + CONFIGURE_PARAM("CONFIGURE_PARAM", "CONFIGURE_PARAM"); + + private final String text; + + private final String callbackData; + + public static MainMenu valueOfCallbackData(String callbackData) { + return Arrays.stream(MainMenu.values()).filter(mainMenu -> mainMenu.getCallbackData().equals(callbackData)) + .findFirst().orElse(CONFIGURE_PARAM); + } + +} diff --git a/src/main/java/dev/vality/alert/tg/bot/constants/TextConstants.java b/src/main/java/dev/vality/alert/tg/bot/constants/TextConstants.java new file mode 100644 index 0000000..3266e35 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/constants/TextConstants.java @@ -0,0 +1,23 @@ +package dev.vality.alert.tg.bot.constants; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum TextConstants { + + SELECT_ACTION("Выберите действие"), + REPLY_OR_ACTION("Ответьте на предыдущее сообщение или выберите действие"), + ENTER_PARAMETER("Введите параметр %s в формате %s"), + ALERT_CREATED("Алерт создан"), + ENTER_ALERT_ID_FOR_REMOVED("Введите id алерта для удаления"), + DELETE_ALERT("Удалить алерт"), + SELECT_ALERT("Выберите алерт"), + ALERT_REMOVED("Алерт удален"), + ALERTS_REMOVED("Алерты удалены"), + DELETE_ALL_ALERTS("Удалить все алерты"); + + private final String text; + +} diff --git a/src/main/java/dev/vality/alert/tg/bot/constants/UserStatuses.java b/src/main/java/dev/vality/alert/tg/bot/constants/UserStatuses.java new file mode 100644 index 0000000..a9eb034 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/constants/UserStatuses.java @@ -0,0 +1,24 @@ +package dev.vality.alert.tg.bot.constants; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Set; + +@Getter +@RequiredArgsConstructor +public enum UserStatuses { + + CREATOR("creator"), + ADMINISTRATOR("administrator"), + MEMBER("member"); + + private final String value; + + public static final Set ALLOWED_USER_STATUSES = + Set.of( + CREATOR.getValue(), + ADMINISTRATOR.getValue(), + MEMBER.getValue() + ); +} diff --git a/src/main/java/dev/vality/alert/tg/bot/dao/AbstractDao.java b/src/main/java/dev/vality/alert/tg/bot/dao/AbstractDao.java new file mode 100644 index 0000000..81351cd --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/dao/AbstractDao.java @@ -0,0 +1,12 @@ +package dev.vality.alert.tg.bot.dao; + +import dev.vality.dao.impl.AbstractGenericDao; + +import javax.sql.DataSource; + +public class AbstractDao extends AbstractGenericDao { + + public AbstractDao(DataSource dataSource) { + super(dataSource); + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/dao/ParametersDao.java b/src/main/java/dev/vality/alert/tg/bot/dao/ParametersDao.java new file mode 100644 index 0000000..98b117a --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/dao/ParametersDao.java @@ -0,0 +1,11 @@ +package dev.vality.alert.tg.bot.dao; + +import dev.vality.alert.tg.bot.domain.tables.pojos.ParametersData; + +public interface ParametersDao { + + ParametersData save(ParametersData parametersData); + + ParametersData getByAlertIdAndParamName(String alertId, String paramName); + +} diff --git a/src/main/java/dev/vality/alert/tg/bot/dao/StateDataDao.java b/src/main/java/dev/vality/alert/tg/bot/dao/StateDataDao.java new file mode 100644 index 0000000..1f1ef7f --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/dao/StateDataDao.java @@ -0,0 +1,13 @@ +package dev.vality.alert.tg.bot.dao; + +import dev.vality.alert.tg.bot.domain.tables.pojos.StateData; + +public interface StateDataDao { + + StateData save(StateData data); + + void updateParams(Long userId, String params); + + StateData getByUserId(Long userId); + +} diff --git a/src/main/java/dev/vality/alert/tg/bot/dao/impl/ParametersDaoImpl.java b/src/main/java/dev/vality/alert/tg/bot/dao/impl/ParametersDaoImpl.java new file mode 100644 index 0000000..0dbfc53 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/dao/impl/ParametersDaoImpl.java @@ -0,0 +1,55 @@ +package dev.vality.alert.tg.bot.dao.impl; + + +import dev.vality.alert.tg.bot.dao.AbstractDao; +import dev.vality.alert.tg.bot.dao.ParametersDao; +import dev.vality.alert.tg.bot.domain.tables.pojos.ParametersData; +import dev.vality.mapper.RecordRowMapper; +import org.jooq.Query; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; +import java.util.Optional; + +import static dev.vality.alert.tg.bot.domain.Tables.PARAMETERS_DATA; + + +@Component +public class ParametersDaoImpl extends AbstractDao implements ParametersDao { + + private final RecordRowMapper parameterTypeRecordRowMapper; + + public ParametersDaoImpl(DataSource dataSource) { + super(dataSource); + parameterTypeRecordRowMapper = new RecordRowMapper<>(PARAMETERS_DATA, ParametersData.class); + } + + @Override + public ParametersData save(ParametersData parametersData) { + Query query = getDslContext() + .insertInto(PARAMETERS_DATA) + .set(getDslContext().newRecord(PARAMETERS_DATA, parametersData)) + .onConflict(PARAMETERS_DATA.PARAM_ID) + .doUpdate() + .set(getDslContext().newRecord(PARAMETERS_DATA, parametersData)) + .returning(PARAMETERS_DATA.ID); + GeneratedKeyHolder keyHolder = new GeneratedKeyHolder(); + execute(query, keyHolder); + Long id = Optional.ofNullable(keyHolder.getKey()) + .map(Number::longValue) + .orElseThrow(() -> new IllegalStateException("Can't get data id")); + parametersData.setId(id); + return parametersData; + } + + + @Override + public ParametersData getByAlertIdAndParamName(String alertId, String paramName) { + var get = getDslContext() + .selectFrom(PARAMETERS_DATA) + .where(PARAMETERS_DATA.ALERT_ID.eq(alertId)) + .and(PARAMETERS_DATA.PARAM_NAME.eq(paramName)); + return fetchOne(get, parameterTypeRecordRowMapper); + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/dao/impl/StateDataDaoImpl.java b/src/main/java/dev/vality/alert/tg/bot/dao/impl/StateDataDaoImpl.java new file mode 100644 index 0000000..d6e4ba5 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/dao/impl/StateDataDaoImpl.java @@ -0,0 +1,64 @@ +package dev.vality.alert.tg.bot.dao.impl; + + +import dev.vality.alert.tg.bot.dao.AbstractDao; +import dev.vality.alert.tg.bot.dao.StateDataDao; +import dev.vality.alert.tg.bot.domain.tables.pojos.StateData; +import dev.vality.alert.tg.bot.domain.tables.records.StateDataRecord; +import dev.vality.mapper.RecordRowMapper; +import org.jooq.Query; +import org.jooq.UpdateConditionStep; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; +import java.util.Optional; + +import static dev.vality.alert.tg.bot.domain.Tables.STATE_DATA; + + +@Component +public class StateDataDaoImpl extends AbstractDao implements StateDataDao { + + private final RecordRowMapper stateDataRecordRowMapper; + + public StateDataDaoImpl(DataSource dataSource) { + super(dataSource); + stateDataRecordRowMapper = new RecordRowMapper<>(STATE_DATA, StateData.class); + } + + @Override + public StateData save(StateData data) { + Query query = getDslContext() + .insertInto(STATE_DATA) + .set(getDslContext().newRecord(STATE_DATA, data)) + .onConflict(STATE_DATA.USER_ID) + .doUpdate() + .set(getDslContext().newRecord(STATE_DATA, data)) + .returning(STATE_DATA.ID); + GeneratedKeyHolder keyHolder = new GeneratedKeyHolder(); + execute(query, keyHolder); + Long id = Optional.ofNullable(keyHolder.getKey()) + .map(Number::longValue) + .orElseThrow(() -> new IllegalStateException("Can't get data id")); + data.setId(id); + return data; + } + + @Override + public void updateParams(Long userId, String params) { + UpdateConditionStep update = getDslContext() + .update(STATE_DATA) + .set(STATE_DATA.MAP_PARAMS, params) + .where(STATE_DATA.USER_ID.eq(userId)); + execute(update); + } + + @Override + public StateData getByUserId(Long userId) { + var get = getDslContext() + .selectFrom(STATE_DATA) + .where(STATE_DATA.USER_ID.eq(userId)); + return fetchOne(get, stateDataRecordRowMapper); + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/exeptions/AlertTgBotException.java b/src/main/java/dev/vality/alert/tg/bot/exeptions/AlertTgBotException.java new file mode 100644 index 0000000..860ad4b --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/exeptions/AlertTgBotException.java @@ -0,0 +1,12 @@ +package dev.vality.alert.tg.bot.exeptions; + +public class AlertTgBotException extends RuntimeException { + + public AlertTgBotException(String message) { + super(message); + } + + public AlertTgBotException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/exeptions/UnknownHandlerException.java b/src/main/java/dev/vality/alert/tg/bot/exeptions/UnknownHandlerException.java new file mode 100644 index 0000000..a518b2a --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/exeptions/UnknownHandlerException.java @@ -0,0 +1,8 @@ +package dev.vality.alert.tg.bot.exeptions; + +public class UnknownHandlerException extends RuntimeException { + + public UnknownHandlerException() { + super("Unknown handler"); + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/handler/CallbackHandler.java b/src/main/java/dev/vality/alert/tg/bot/handler/CallbackHandler.java new file mode 100644 index 0000000..778f136 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/handler/CallbackHandler.java @@ -0,0 +1,41 @@ +package dev.vality.alert.tg.bot.handler; + +import dev.vality.alert.tg.bot.constants.MainMenu; +import dev.vality.alert.tg.bot.exeptions.AlertTgBotException; +import dev.vality.alert.tg.bot.mapper.MenuCallbackMapper; +import dev.vality.alert.tg.bot.mapper.ParametersCallbackMapper; +import lombok.RequiredArgsConstructor; +import org.apache.thrift.TException; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.Update; + +@Component +@RequiredArgsConstructor +@Order(Ordered.HIGHEST_PRECEDENCE) +public class CallbackHandler implements CommonHandler { + + private final MenuCallbackMapper menuCallbackMapper; + private final ParametersCallbackMapper parametersCallbackMapper; + + @Override + public boolean filter(Update update) { + return update.hasCallbackQuery(); + } + + @Override + public SendMessage handle(Update update, long userId) throws TException { + String callbackData = update.getCallbackQuery().getData(); + return switch (MainMenu.valueOfCallbackData(callbackData)) { + case CREATE_ALERT -> menuCallbackMapper.createAlertCallback(userId); + case GET_ALL_ALERTS -> menuCallbackMapper.getAllAlertsCallback(userId); + case DELETE_ALERT -> menuCallbackMapper.deleteAlertCallback(userId); + case DELETE_ALL_ALERTS -> menuCallbackMapper.deleteAllAlertsCallback(userId); + case RETURN_TO_MENU -> menuCallbackMapper.returnCallback(); + case CONFIGURE_PARAM -> parametersCallbackMapper.mapParametersCallback(callbackData, userId); + }; + } + +} diff --git a/src/main/java/dev/vality/alert/tg/bot/handler/CommonHandler.java b/src/main/java/dev/vality/alert/tg/bot/handler/CommonHandler.java new file mode 100644 index 0000000..d01eb2a --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/handler/CommonHandler.java @@ -0,0 +1,13 @@ +package dev.vality.alert.tg.bot.handler; + +import org.apache.thrift.TException; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.Update; + +public interface CommonHandler { + + boolean filter(final Update update); + + SendMessage handle(Update update, long userId) throws TException; + +} diff --git a/src/main/java/dev/vality/alert/tg/bot/handler/MainMenuHandler.java b/src/main/java/dev/vality/alert/tg/bot/handler/MainMenuHandler.java new file mode 100644 index 0000000..36537c5 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/handler/MainMenuHandler.java @@ -0,0 +1,34 @@ +package dev.vality.alert.tg.bot.handler; + +import dev.vality.alert.tg.bot.constants.TextConstants; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.Update; + +import java.util.Objects; + +import static dev.vality.alert.tg.bot.utils.MainMenuBuilder.buildMainInlineKeyboardMarkup; + +@Component +@Order(Ordered.LOWEST_PRECEDENCE) +public class MainMenuHandler implements CommonHandler { + + @Override + public boolean filter(Update update) { + return true; + } + + @Override + public SendMessage handle(Update update, long userId) { + if (Objects.equals(update.getMessage().getChatId(), update.getMessage().getFrom().getId())) { + SendMessage message = new SendMessage(); + message.setChatId(userId); + message.setText(TextConstants.REPLY_OR_ACTION.getText()); + message.setReplyMarkup(buildMainInlineKeyboardMarkup()); + return message; + } + return null; + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/handler/MessageHandler.java b/src/main/java/dev/vality/alert/tg/bot/handler/MessageHandler.java new file mode 100644 index 0000000..6bfceee --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/handler/MessageHandler.java @@ -0,0 +1,43 @@ +package dev.vality.alert.tg.bot.handler; + +import dev.vality.alert.tg.bot.constants.TextConstants; +import dev.vality.alert.tg.bot.dao.StateDataDao; +import dev.vality.alert.tg.bot.domain.tables.pojos.StateData; +import lombok.RequiredArgsConstructor; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.Update; + +import java.util.Objects; + +import static dev.vality.alert.tg.bot.utils.MainMenuBuilder.buildMainInlineKeyboardMarkup; + +@Component +@RequiredArgsConstructor +@Order(Ordered.HIGHEST_PRECEDENCE) +public class MessageHandler implements CommonHandler { + + private final StateDataDao stateDataDao; + + @Override + public boolean filter(Update update) { + return update.hasMessage() + && update.getMessage().hasText() + && update.getMessage().getReplyToMessage() == null + && Objects.equals(update.getMessage().getChatId(), update.getMessage().getFrom().getId()); + } + + @Override + public SendMessage handle(Update update, long userId) { + SendMessage message = new SendMessage(); + message.setText(TextConstants.SELECT_ACTION.getText()); + StateData stateData = new StateData(); + stateData.setUserId(userId); + stateDataDao.save(stateData); + message.setReplyMarkup(buildMainInlineKeyboardMarkup()); + return message; + } + +} diff --git a/src/main/java/dev/vality/alert/tg/bot/handler/ReplyHandler.java b/src/main/java/dev/vality/alert/tg/bot/handler/ReplyHandler.java new file mode 100644 index 0000000..9df85ca --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/handler/ReplyHandler.java @@ -0,0 +1,69 @@ +package dev.vality.alert.tg.bot.handler; + +import dev.vality.alert.tg.bot.constants.TextConstants; +import dev.vality.alert.tg.bot.dao.StateDataDao; +import dev.vality.alert.tg.bot.domain.tables.pojos.StateData; +import dev.vality.alert.tg.bot.mapper.JsonMapper; +import dev.vality.alert.tg.bot.mapper.ReplyMessagesMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.thrift.TException; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.Update; + +import java.util.Map; +import java.util.Objects; + +@Slf4j +@Component +@RequiredArgsConstructor +@Order(Ordered.HIGHEST_PRECEDENCE) +public class ReplyHandler implements CommonHandler { + + private final StateDataDao stateDataDao; + private final ReplyMessagesMapper replyMessagesMapper; + private final JsonMapper jsonMapper; + + @Override + public boolean filter(Update update) { + return update.hasMessage() + && update.getMessage().getReplyToMessage() != null + && Objects.equals(update.getMessage().getChatId(), update.getMessage().getFrom().getId()); + } + + @Override + public SendMessage handle(Update update, long userId) throws TException { + if (isReplyMessageDeleteAlert(update)) { + log.info("Delete alert {} for user {}", update.getMessage().getText(), userId); + return replyMessagesMapper.deleteAlert(update.getMessage().getText()); + } else { + StateData stateData = stateDataDao.getByUserId(userId); + Map paramMap = jsonMapper.toMap(stateData.getMapParams()); + paramMap.put(update.getMessage().getReplyToMessage().getText(), update.getMessage().getText()); + stateData.setMapParams(jsonMapper.toJson(paramMap)); + stateDataDao.updateParams(userId, stateData.getMapParams()); + String nextKey = getNextKeyForFill(paramMap); + if (nextKey != null) { + return replyMessagesMapper.createNextParameterRequest(nextKey); + } else { + return replyMessagesMapper.createAlertRequest(userId); + } + } + } + + private boolean isReplyMessageDeleteAlert(Update update) { + return update.getMessage().getReplyToMessage().getText() + .equals(TextConstants.ENTER_ALERT_ID_FOR_REMOVED.getText()); + } + + private static String getNextKeyForFill(Map map) { + return map.entrySet().stream() + .filter(entry -> null == entry.getValue()) + .findFirst().map(Map.Entry::getKey) + .orElse(null); + } +} + diff --git a/src/main/java/dev/vality/alert/tg/bot/mapper/JsonMapper.java b/src/main/java/dev/vality/alert/tg/bot/mapper/JsonMapper.java new file mode 100644 index 0000000..e8c43e3 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/mapper/JsonMapper.java @@ -0,0 +1,28 @@ +package dev.vality.alert.tg.bot.mapper; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class JsonMapper { + + private final ObjectMapper objectMapper; + + @SneakyThrows(JsonProcessingException.class) + public String toJson(Object data) { + return objectMapper.writeValueAsString(data); + } + + @SneakyThrows(JsonProcessingException.class) + public Map toMap(String json) { + return objectMapper.readValue(json, new TypeReference<>() { + }); + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapper.java b/src/main/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapper.java new file mode 100644 index 0000000..0a1ad49 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapper.java @@ -0,0 +1,94 @@ +package dev.vality.alert.tg.bot.mapper; + +import dev.vality.alert.tg.bot.constants.MainMenu; +import dev.vality.alert.tg.bot.dao.StateDataDao; +import dev.vality.alert.tg.bot.domain.tables.pojos.StateData; +import dev.vality.alert.tg.bot.service.MayDayService; +import dev.vality.alerting.mayday.Alert; +import dev.vality.alerting.mayday.UserAlert; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.thrift.TException; +import org.springframework.stereotype.Component; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.ForceReplyKeyboard; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static dev.vality.alert.tg.bot.constants.TextConstants.*; +import static dev.vality.alert.tg.bot.utils.MainMenuBuilder.buildMainInlineKeyboardMarkup; + +@Slf4j +@Component +@RequiredArgsConstructor +public class MenuCallbackMapper { + + private final MayDayService mayDayService; + private final StateDataDao stateDataDao; + + public SendMessage createAlertCallback(long userId) throws TException { + StateData stateData = new StateData(); + stateData.setUserId(userId); + stateDataDao.save(stateData); + SendMessage message = new SendMessage(); + message.setText(SELECT_ALERT.getText()); + List alerts = mayDayService.getSupportedAlerts(); + List> rowsInline = new ArrayList<>(); + alerts.forEach(alert -> rowsInline.add(Collections.singletonList(InlineKeyboardButton.builder() + .text(alert.getName()) + .callbackData(alert.getAlert()) + .build()))); + rowsInline.add(Collections.singletonList(InlineKeyboardButton.builder() + .text(MainMenu.RETURN_TO_MENU.getText()) + .callbackData(MainMenu.RETURN_TO_MENU.getCallbackData()) + .build())); + List rowInline = new ArrayList<>(); + rowsInline.add(rowInline); + InlineKeyboardMarkup markupInline = new InlineKeyboardMarkup(); + markupInline.setKeyboard(rowsInline); + message.setReplyMarkup(markupInline); + return message; + } + + public SendMessage getAllAlertsCallback(long userId) throws TException { + SendMessage message = new SendMessage(); + List userAlerts = mayDayService.getUserAlerts(String.valueOf(userId)); + StringBuilder text = new StringBuilder("Ваши алерты:\n"); + userAlerts.forEach(userAlert -> { + text.append("id: ").append(userAlert.getId()) + .append(" Название: ").append(userAlert.getName()) + .append("\n"); + }); + message.setText(text.toString()); + message.setReplyMarkup(buildMainInlineKeyboardMarkup()); + return message; + } + + public SendMessage deleteAlertCallback(long userId) throws TException { + SendMessage message = new SendMessage(); + mayDayService.deleteAlert(String.valueOf(userId)); + message.setText(ENTER_ALERT_ID_FOR_REMOVED.getText()); + message.setReplyMarkup(new ForceReplyKeyboard()); + return message; + } + + public SendMessage deleteAllAlertsCallback(long userId) throws TException { + log.info("User {} delete all alerts", userId); + SendMessage message = new SendMessage(); + mayDayService.deleteAllAlerts(String.valueOf(userId)); + message.setText(ALERTS_REMOVED.getText()); + message.setReplyMarkup(buildMainInlineKeyboardMarkup()); + return message; + } + + public SendMessage returnCallback() { + SendMessage message = new SendMessage(); + message.setText(SELECT_ACTION.getText()); + message.setReplyMarkup(buildMainInlineKeyboardMarkup()); + return message; + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapper.java b/src/main/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapper.java new file mode 100644 index 0000000..dc0c5af --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapper.java @@ -0,0 +1,71 @@ +package dev.vality.alert.tg.bot.mapper; + +import dev.vality.alert.tg.bot.dao.ParametersDao; +import dev.vality.alert.tg.bot.dao.StateDataDao; +import dev.vality.alert.tg.bot.domain.enums.ParameterType; +import dev.vality.alert.tg.bot.domain.tables.pojos.ParametersData; +import dev.vality.alert.tg.bot.domain.tables.pojos.StateData; +import dev.vality.alert.tg.bot.service.MayDayService; +import dev.vality.alerting.mayday.AlertConfiguration; +import dev.vality.alerting.mayday.ParameterConfiguration; +import lombok.RequiredArgsConstructor; +import org.apache.thrift.TException; +import org.springframework.stereotype.Component; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.ForceReplyKeyboard; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class ParametersCallbackMapper { + + private final MayDayService mayDayService; + private final StateDataDao stateDataDao; + private final ParametersDao parametersDao; + private final JsonMapper jsonMapper; + + public SendMessage mapParametersCallback(String callData, long userId) throws TException { + SendMessage message = new SendMessage(); + AlertConfiguration alertConfiguration = mayDayService.getAlertConfiguration(callData); + String alertId = alertConfiguration.getAlertId(); + List parameterConfigurations = alertConfiguration.getParameters(); + fillStateDataAndSave(userId, alertId, parameterConfigurations); + parameterConfigurations.forEach(param -> convertParameterConfigurationsAndSave(alertId, param)); + message.setText(parameterConfigurations.get(0).getName()); + message.setReplyMarkup(new ForceReplyKeyboard()); + return message; + } + + private void fillStateDataAndSave(long userId, String alertId, + List parameterConfigurations) { + StateData stateData = stateDataDao.getByUserId(userId); + stateData.setAlertId(alertId); + Map mapParams = new HashMap<>(); + parameterConfigurations.forEach(param -> { + mapParams.put(param.getName(), null); + }); + stateData.setMapParams(jsonMapper.toJson(mapParams)); + stateDataDao.save(stateData); + } + + private void convertParameterConfigurationsAndSave(String alertId, ParameterConfiguration param) { + ParametersData parametersData = new ParametersData(); + parametersData.setAlertId(alertId); + parametersData.setParamId(param.getId()); + parametersData.setParamName(param.getName()); + parametersData.setParamType(getParamType(param.getType())); + parametersDao.save(parametersData); + } + + private ParameterType getParamType(dev.vality.alerting.mayday.ParameterType parameterType) { + return switch (parameterType) { + case integer -> ParameterType.integer; + case str -> ParameterType.str; + case bl -> ParameterType.bl; + case fl -> ParameterType.fl; + }; + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/mapper/ReplyMessagesMapper.java b/src/main/java/dev/vality/alert/tg/bot/mapper/ReplyMessagesMapper.java new file mode 100644 index 0000000..f665325 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/mapper/ReplyMessagesMapper.java @@ -0,0 +1,82 @@ +package dev.vality.alert.tg.bot.mapper; + +import dev.vality.alert.tg.bot.constants.TextConstants; +import dev.vality.alert.tg.bot.dao.ParametersDao; +import dev.vality.alert.tg.bot.dao.StateDataDao; +import dev.vality.alert.tg.bot.domain.tables.pojos.ParametersData; +import dev.vality.alert.tg.bot.domain.tables.pojos.StateData; +import dev.vality.alert.tg.bot.service.MayDayService; +import dev.vality.alerting.mayday.CreateAlertRequest; +import dev.vality.alerting.mayday.ParameterInfo; +import dev.vality.alerting.mayday.ParameterValue; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.thrift.TException; +import org.springframework.stereotype.Component; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.ForceReplyKeyboard; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static dev.vality.alert.tg.bot.utils.MainMenuBuilder.buildMainInlineKeyboardMarkup; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ReplyMessagesMapper { + + private final MayDayService mayDayService; + private final ParametersDao parametersDao; + private final StateDataDao stateDataDao; + private final JsonMapper jsonMapper; + + public SendMessage createAlertRequest(long userId) throws TException { + StateData stateData = stateDataDao.getByUserId(userId); + log.info("Start create alert with stateData {}", stateData); + Map paramMap = jsonMapper.toMap(stateData.getMapParams()); + CreateAlertRequest createAlertRequest = new CreateAlertRequest(); + createAlertRequest.setAlertId(stateData.getAlertId()); + createAlertRequest.setUserId(String.valueOf(userId)); + List parameterInfos = new ArrayList<>(); + for (String key : paramMap.keySet()) { + ParametersData parametersData = parametersDao.getByAlertIdAndParamName(stateData.getAlertId(), key); + ParameterInfo parameterInfo = new ParameterInfo(); + parameterInfo.setParameterId(parametersData.getParamId()); + parameterInfo.setType(mapParameterValue(parametersData, paramMap, key)); + parameterInfos.add(parameterInfo); + } + createAlertRequest.setParameters(parameterInfos); + mayDayService.createAlert(createAlertRequest); + SendMessage message = new SendMessage(); + message.setText(TextConstants.ALERT_CREATED.getText()); + message.setReplyMarkup(buildMainInlineKeyboardMarkup()); + log.info("Alert {} was created", createAlertRequest); + return message; + } + + public SendMessage createNextParameterRequest(String text) { + SendMessage message = new SendMessage(); + message.setReplyMarkup(new ForceReplyKeyboard()); + message.setText(text); + return message; + } + + public SendMessage deleteAlert(String text) throws TException { + SendMessage message = new SendMessage(); + mayDayService.deleteAlert(text); + message.setText(TextConstants.ALERT_REMOVED.getText()); + message.setReplyMarkup(buildMainInlineKeyboardMarkup()); + return message; + } + + private ParameterValue mapParameterValue(ParametersData parametersData, Map paramMap, String key) { + return switch (parametersData.getParamType()) { + case str -> ParameterValue.str(paramMap.get(key)); + case integer -> ParameterValue.integer(Long.parseLong(paramMap.get(key))); + case fl -> ParameterValue.fl(Double.parseDouble(paramMap.get(key))); + case bl -> ParameterValue.bl(Boolean.parseBoolean(paramMap.get(key))); + }; + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/service/AlertBot.java b/src/main/java/dev/vality/alert/tg/bot/service/AlertBot.java new file mode 100644 index 0000000..b6d997a --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/service/AlertBot.java @@ -0,0 +1,109 @@ +package dev.vality.alert.tg.bot.service; + +import dev.vality.alert.tg.bot.config.properties.AlertBotProperties; +import dev.vality.alert.tg.bot.exeptions.AlertTgBotException; +import dev.vality.alert.tg.bot.exeptions.UnknownHandlerException; +import dev.vality.alert.tg.bot.handler.CommonHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.thrift.TException; +import org.springframework.stereotype.Service; +import org.telegram.telegrambots.bots.TelegramLongPollingBot; +import org.telegram.telegrambots.meta.api.methods.groupadministration.GetChatMember; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.Update; +import org.telegram.telegrambots.meta.api.objects.chatmember.ChatMember; +import org.telegram.telegrambots.meta.exceptions.TelegramApiException; + +import java.util.List; + +import static dev.vality.alert.tg.bot.constants.UserStatuses.ALLOWED_USER_STATUSES; + +@Slf4j +@Service +public class AlertBot extends TelegramLongPollingBot { + + private final List handlers; + private final AlertBotProperties alertBotProperties; + private final MayDayService mayDayService; + + public AlertBot(AlertBotProperties alertBotProperties, + List handlers, + MayDayService mayDayService) { + super(alertBotProperties.getToken()); + this.handlers = handlers; + this.alertBotProperties = alertBotProperties; + this.mayDayService = mayDayService; + } + + @Override + public String getBotUsername() { + return alertBotProperties.getName(); + } + + @Override + public void onUpdateReceived(Update update) { + try { + if (isUserPermission(update)) { + long userId = update.hasMessage() + ? update.getMessage().getFrom().getId() + : update.getCallbackQuery().getFrom().getId(); + SendMessage message = handlers.stream() + .filter(handler -> handler.filter(update)) + .findFirst() + .orElseThrow(UnknownHandlerException::new) + .handle(update, userId); + if (message != null) { + message.setChatId(userId); + execute(message); + } + } + } catch (TelegramApiException | TException ex) { + throw new AlertTgBotException(String.format("Received an exception while handle update %s", update), ex); + } + } + + public boolean isUserPermission(Update update) throws TException { + Long userId = update.hasMessage() + ? update.getMessage().getFrom().getId() + : update.getCallbackQuery().getMessage().getFrom().getId(); + String userName = update.hasMessage() + ? update.getMessage().getFrom().getUserName() + : update.getCallbackQuery().getMessage().getFrom().getUserName(); + try { + return isChatMemberPermission(userId); + } catch (TelegramApiException e) { + checkExceptionIsUserInChat(e, userName, userId); + throw new AlertTgBotException(String.format("Error while checking user %s permissions", userName), e); + } + } + + public boolean isChatMemberPermission(Long userId) throws TelegramApiException, TException { + ChatMember chatMember = execute(new GetChatMember(alertBotProperties.getChatId(), userId)); + if (ALLOWED_USER_STATUSES.contains(chatMember.getStatus())) { + return true; + } else { + mayDayService.deleteAllAlerts(String.valueOf(userId)); + SendMessage message = new SendMessage(); + message.setChatId(userId); + message.setText("У вас не прав на создание алертов, все ранее созданные алерты удалены"); + execute(message); + return false; + } + } + + private void checkExceptionIsUserInChat(TelegramApiException e, String userName, Long userId) { + if (e.getMessage().contains("[400] Bad Request: user not found")) { + log.info("User {} not found in chat", userName); + SendMessage message = new SendMessage(); + message.setChatId(userId); + message.setText("У вас не прав на создание алертов"); + try { + execute(message); + throw new AlertTgBotException(String.format("User %s don't have permissions", userName), e); + } catch (TelegramApiException ex) { + throw new AlertTgBotException( + String.format("Error while checking user %s permissions", userName), e); + } + } + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/service/MayDayService.java b/src/main/java/dev/vality/alert/tg/bot/service/MayDayService.java new file mode 100644 index 0000000..1505849 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/service/MayDayService.java @@ -0,0 +1,39 @@ +package dev.vality.alert.tg.bot.service; + +import dev.vality.alerting.mayday.*; +import lombok.RequiredArgsConstructor; +import org.apache.thrift.TException; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class MayDayService { + + private final AlertingServiceSrv.Iface mayDayClient; + + public void deleteAllAlerts(String userId) throws TException { + mayDayClient.deleteAllAlerts(userId); + } + + public void deleteAlert(String userAlertId) throws TException { + mayDayClient.deleteAlert(userAlertId); + } + + public List getUserAlerts(String userId) throws TException { + return mayDayClient.getUserAlerts(userId); + } + + public List getSupportedAlerts() throws TException { + return mayDayClient.getSupportedAlerts(); + } + + public AlertConfiguration getAlertConfiguration(String alertId) throws TException { + return mayDayClient.getAlertConfiguration(alertId); + } + + public void createAlert(CreateAlertRequest createAlertRequest) throws TException { + mayDayClient.createAlert(createAlertRequest); + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/service/NotifierService.java b/src/main/java/dev/vality/alert/tg/bot/service/NotifierService.java new file mode 100644 index 0000000..74a9da4 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/service/NotifierService.java @@ -0,0 +1,44 @@ +package dev.vality.alert.tg.bot.service; + + +import dev.vality.alert.tg.bot.exeptions.AlertTgBotException; +import dev.vality.alerting.tg_bot.Notification; +import dev.vality.alerting.tg_bot.NotifierServiceSrv; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.thrift.TException; +import org.springframework.stereotype.Service; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.exceptions.TelegramApiException; + +@Slf4j +@Service +@RequiredArgsConstructor +public class NotifierService implements NotifierServiceSrv.Iface { + + private final AlertBot alertBot; + + @Override + public void notify(Notification notification) { + log.info("Try to send notification {} to user {}", notification.getMessage(), notification.getReceiverId()); + try { + if (alertBot.isChatMemberPermission(Long.valueOf(notification.getReceiverId()))) { + alertBot.execute(SendMessage.builder() + .chatId(Long.valueOf(notification.getReceiverId())) + .text(notification.getMessage()).build()); + + log.info("Notification {} to user {} was send", + notification.getMessage(), + notification.getReceiverId()); + } + } catch (TelegramApiException | TException ex) { + throw new AlertTgBotException( + String.format( + "Received an exception while notify receiver %s with message %s", + notification.getReceiverId(), + notification.getMessage()), + ex); + } + } + +} diff --git a/src/main/java/dev/vality/alert/tg/bot/servlet/AlertTgBotServlet.java b/src/main/java/dev/vality/alert/tg/bot/servlet/AlertTgBotServlet.java new file mode 100644 index 0000000..a047683 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/servlet/AlertTgBotServlet.java @@ -0,0 +1,29 @@ +package dev.vality.alert.tg.bot.servlet; + +import dev.vality.alerting.tg_bot.NotifierServiceSrv; +import dev.vality.woody.thrift.impl.http.THServiceBuilder; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.servlet.*; +import javax.servlet.annotation.WebServlet; +import java.io.IOException; + +@WebServlet("/alert/tg/bot") +public class AlertTgBotServlet extends GenericServlet { + + @Autowired + private NotifierServiceSrv.Iface serverHandler; + + private Servlet servlet; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + servlet = new THServiceBuilder().build(NotifierServiceSrv.Iface.class, serverHandler); + } + + @Override + public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { + servlet.service(request, response); + } +} diff --git a/src/main/java/dev/vality/alert/tg/bot/utils/MainMenuBuilder.java b/src/main/java/dev/vality/alert/tg/bot/utils/MainMenuBuilder.java new file mode 100644 index 0000000..e033fd0 --- /dev/null +++ b/src/main/java/dev/vality/alert/tg/bot/utils/MainMenuBuilder.java @@ -0,0 +1,39 @@ +package dev.vality.alert.tg.bot.utils; + +import dev.vality.alert.tg.bot.constants.MainMenu; +import lombok.RequiredArgsConstructor; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup; +import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@RequiredArgsConstructor +public class MainMenuBuilder { + + public static InlineKeyboardMarkup buildMainInlineKeyboardMarkup() { + List> rowsInline = new ArrayList<>(); + List rowInline = new ArrayList<>(); + rowsInline.add(Collections.singletonList(InlineKeyboardButton.builder() + .text(MainMenu.CREATE_ALERT.getText()) + .callbackData(MainMenu.CREATE_ALERT.getCallbackData()) + .build())); + rowsInline.add(Collections.singletonList(InlineKeyboardButton.builder() + .text(MainMenu.GET_ALL_ALERTS.getText()) + .callbackData(MainMenu.GET_ALL_ALERTS.getCallbackData()) + .build())); + rowsInline.add(Collections.singletonList(InlineKeyboardButton.builder() + .text(MainMenu.DELETE_ALERT.getText()) + .callbackData(MainMenu.DELETE_ALERT.getCallbackData()) + .build())); + rowsInline.add(Collections.singletonList(InlineKeyboardButton.builder() + .text(MainMenu.DELETE_ALL_ALERTS.getText()) + .callbackData(MainMenu.DELETE_ALL_ALERTS.getCallbackData()) + .build())); + rowsInline.add(rowInline); + InlineKeyboardMarkup markupInline = new InlineKeyboardMarkup(); + markupInline.setKeyboard(rowsInline); + return markupInline; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..eab9f4e --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,50 @@ +server: + port: ${server.port} +management: + security: + flag: false + server: + port: ${management.port} + metrics: + tags: + application: ${project.name} + export: + prometheus: + enabled: true + endpoint: + health: + show-details: always + metrics: + enabled: true + prometheus: + enabled: true + endpoints: + web: + exposure: + include: health,info,prometheus + +spring: + application: + name: '@project.name@' + output: + ansi: + enabled: always + main: + allow-bean-definition-overriding: true + datasource: + url: jdbc:postgresql://localhost:5432/alert_tg_bot + username: postgres + password: postgres + +info: + version: '@project.version@' + stage: dev + +mayday: + url: http://localhost:8022/mayday + networkTimeout: 5000 + +bot: + token: test + name: AlertBot + chatId: test diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql new file mode 100644 index 0000000..cc9ad6e --- /dev/null +++ b/src/main/resources/db/migration/V1__init.sql @@ -0,0 +1,26 @@ +CREATE SCHEMA IF NOT EXISTS alert_tg_bot; + +CREATE TABLE alert_tg_bot.state_data +( + id BIGSERIAL NOT NULL, + user_id BIGINT NOT NULL, + alert_id CHARACTER VARYING, + map_params CHARACTER VARYING, + + CONSTRAINT state_data_pkey PRIMARY KEY (id), + UNIQUE (user_id) +); + +CREATE TYPE alert_tg_bot.parameter_type AS ENUM ('bl', 'fl', 'integer', 'str'); + +CREATE TABLE alert_tg_bot.parameters_data +( + id BIGSERIAL NOT NULL, + alert_id CHARACTER VARYING NOT NULL, + param_id CHARACTER VARYING NOT NULL, + param_name CHARACTER VARYING NOT NULL, + param_type alert_tg_bot.parameter_type NOT NULL, + + CONSTRAINT parameters_pkey PRIMARY KEY (id), + UNIQUE (param_id) +); diff --git a/src/test/java/dev/vality/alert/tg/bot/TestObjectFactory.java b/src/test/java/dev/vality/alert/tg/bot/TestObjectFactory.java new file mode 100644 index 0000000..7760166 --- /dev/null +++ b/src/test/java/dev/vality/alert/tg/bot/TestObjectFactory.java @@ -0,0 +1,141 @@ +package dev.vality.alert.tg.bot; + +import dev.vality.alert.tg.bot.domain.enums.ParameterType; +import dev.vality.alert.tg.bot.domain.tables.pojos.ParametersData; +import dev.vality.alert.tg.bot.domain.tables.pojos.StateData; +import org.telegram.telegrambots.meta.api.objects.*; + + +public abstract class TestObjectFactory { + + public static StateData testStateData() { + StateData stateData = new StateData(); + stateData.setUserId(122233L); + stateData.setAlertId("45"); + stateData.setMapParams("{\"Процент\":34,\"Имя Терминала\":56}"); + return stateData; + } + + public static ParametersData testParameters() { + ParametersData params = new ParametersData(); + params.setAlertId("32"); + params.setParamName("Терминал"); + params.setParamId("14"); + params.setParamType(ParameterType.str); + return params; + } + + public static Update testUpdateMessage() { + Chat chat = new Chat(); + chat.setId(123L); + User user = new User(); + user.setId(123L); + Message message = new Message(); + message.setText("test"); + message.setChat(chat); + message.setFrom(user); + Update update = new Update(); + update.setMessage(message); + return update; + } + + public static Update testUpdateMessageWithWithDifferentId() { + Chat chat = new Chat(); + chat.setId(123L); + User user = new User(); + user.setId(567L); + Message message = new Message(); + message.setText("test"); + message.setChat(chat); + message.setFrom(user); + Update update = new Update(); + update.setMessage(message); + return update; + } + + public static Update testUpdateReply() { + User user = new User(); + user.setId(123L); + Chat chat = new Chat(); + chat.setId(123L); + Message message = new Message(); + message.setText("test"); + message.setReplyToMessage(message); + message.setChat(chat); + message.setFrom(user); + Update update = new Update(); + update.setMessage(message); + return update; + } + + public static Update testUpdateReplyDeleteAlert() { + User user = new User(); + user.setId(123L); + Chat chat = new Chat(); + chat.setId(123L); + Message message = new Message(); + message.setText("Введите id алерта для удаления"); + message.setReplyToMessage(message); + message.setChat(chat); + message.setFrom(user); + Update update = new Update(); + update.setMessage(message); + return update; + } + + public static Update testUpdateDeleteAllCallback() { + User user = new User(); + user.setId(123L); + CallbackQuery callbackQuery = new CallbackQuery(); + callbackQuery.setFrom(user); + callbackQuery.setData("deleteAllAlerts"); + Update update = new Update(); + update.setCallbackQuery(callbackQuery); + return update; + } + + public static Update testUpdateCreateAlertCallback() { + User user = new User(); + user.setId(123L); + CallbackQuery callbackQuery = new CallbackQuery(); + callbackQuery.setFrom(user); + callbackQuery.setData("createAlert"); + Update update = new Update(); + update.setCallbackQuery(callbackQuery); + return update; + } + + public static Update testUpdateGetAllAlertsCallback() { + User user = new User(); + user.setId(123L); + CallbackQuery callbackQuery = new CallbackQuery(); + callbackQuery.setFrom(user); + callbackQuery.setData("getAllAlerts"); + Update update = new Update(); + update.setCallbackQuery(callbackQuery); + return update; + } + + public static Update testUpdateReturnCallback() { + User user = new User(); + user.setId(123L); + CallbackQuery callbackQuery = new CallbackQuery(); + callbackQuery.setFrom(user); + callbackQuery.setData("return"); + Update update = new Update(); + update.setCallbackQuery(callbackQuery); + return update; + } + + public static Update testUpdateMessageCallback() { + User user = new User(); + user.setId(123L); + CallbackQuery callbackQuery = new CallbackQuery(); + callbackQuery.setFrom(user); + callbackQuery.setData("test"); + Update update = new Update(); + update.setCallbackQuery(callbackQuery); + return update; + } + +} diff --git a/src/test/java/dev/vality/alert/tg/bot/config/ExcludeDataSourceConfiguration.java b/src/test/java/dev/vality/alert/tg/bot/config/ExcludeDataSourceConfiguration.java new file mode 100644 index 0000000..ae4704e --- /dev/null +++ b/src/test/java/dev/vality/alert/tg/bot/config/ExcludeDataSourceConfiguration.java @@ -0,0 +1,19 @@ +package dev.vality.alert.tg.bot.config; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; + +import javax.sql.DataSource; + +@TestConfiguration +@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class}) +public class ExcludeDataSourceConfiguration { + + @MockBean + private DataSource dataSource; +} diff --git a/src/test/java/dev/vality/alert/tg/bot/config/PostgresqlJooqTest.java b/src/test/java/dev/vality/alert/tg/bot/config/PostgresqlJooqTest.java new file mode 100644 index 0000000..2586f21 --- /dev/null +++ b/src/test/java/dev/vality/alert/tg/bot/config/PostgresqlJooqTest.java @@ -0,0 +1,16 @@ +package dev.vality.alert.tg.bot.config; + +import dev.vality.testcontainers.annotations.postgresql.PostgresqlTestcontainerSingleton; +import org.springframework.boot.test.autoconfigure.jooq.JooqTest; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@PostgresqlTestcontainerSingleton +@JooqTest +public @interface PostgresqlJooqTest { +} diff --git a/src/test/java/dev/vality/alert/tg/bot/dao/ParametersDaoImplTest.java b/src/test/java/dev/vality/alert/tg/bot/dao/ParametersDaoImplTest.java new file mode 100644 index 0000000..f238279 --- /dev/null +++ b/src/test/java/dev/vality/alert/tg/bot/dao/ParametersDaoImplTest.java @@ -0,0 +1,57 @@ +package dev.vality.alert.tg.bot.dao; + +import dev.vality.alert.tg.bot.TestObjectFactory; +import dev.vality.alert.tg.bot.config.PostgresqlJooqTest; +import dev.vality.alert.tg.bot.dao.impl.ParametersDaoImpl; +import dev.vality.alert.tg.bot.domain.tables.pojos.ParametersData; +import org.jooq.DSLContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; + +import static dev.vality.alert.tg.bot.domain.Tables.PARAMETERS_DATA; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@PostgresqlJooqTest +@ContextConfiguration(classes = {ParametersDaoImpl.class}) +class ParametersDaoImplTest { + + @Autowired + private ParametersDao parametersDao; + + @Autowired + private DSLContext dslContext; + + @BeforeEach + void setUp() { + dslContext.deleteFrom(PARAMETERS_DATA).execute(); + } + + @Test + void save() { + ParametersData parameters = TestObjectFactory.testParameters(); + + ParametersData savedParameters = parametersDao.save(parameters); + + assertEquals(1, dslContext.fetchCount(PARAMETERS_DATA)); + assertNotNull(savedParameters); + assertNotNull(savedParameters.getId()); + assertEquals(parameters.getParamId(), savedParameters.getParamId()); + } + + @Test + void getByAlertIdAndParamName() { + ParametersData parameters = TestObjectFactory.testParameters(); + dslContext.insertInto(PARAMETERS_DATA) + .set(dslContext.newRecord(PARAMETERS_DATA, parameters)) + .execute(); + + ParametersData params = + parametersDao.getByAlertIdAndParamName(parameters.getAlertId(), parameters.getParamName()); + + assertEquals(parameters.getParamId(), params.getParamId()); + } + +} diff --git a/src/test/java/dev/vality/alert/tg/bot/dao/StateDaoImplTest.java b/src/test/java/dev/vality/alert/tg/bot/dao/StateDaoImplTest.java new file mode 100644 index 0000000..19ba789 --- /dev/null +++ b/src/test/java/dev/vality/alert/tg/bot/dao/StateDaoImplTest.java @@ -0,0 +1,73 @@ +package dev.vality.alert.tg.bot.dao; + +import dev.vality.alert.tg.bot.TestObjectFactory; +import dev.vality.alert.tg.bot.config.PostgresqlJooqTest; +import dev.vality.alert.tg.bot.dao.impl.StateDataDaoImpl; +import dev.vality.alert.tg.bot.domain.tables.pojos.StateData; +import dev.vality.alert.tg.bot.domain.tables.records.StateDataRecord; +import org.jooq.DSLContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; + +import static dev.vality.alert.tg.bot.domain.Tables.STATE_DATA; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@PostgresqlJooqTest +@ContextConfiguration(classes = {StateDataDaoImpl.class}) +class StateDaoImplTest { + + @Autowired + private StateDataDao stateDataDao; + + @Autowired + private DSLContext dslContext; + + @BeforeEach + void setUp() { + dslContext.deleteFrom(STATE_DATA).execute(); + } + + @Test + void save() { + StateData stateData = TestObjectFactory.testStateData(); + + StateData savedStateData = stateDataDao.save(stateData); + + assertEquals(1, dslContext.fetchCount(STATE_DATA)); + assertNotNull(savedStateData); + assertNotNull(savedStateData.getId()); + assertEquals(stateData.getAlertId(), savedStateData.getAlertId()); + } + + @Test + void updateParams() { + StateData stateData = TestObjectFactory.testStateData(); + stateData.setMapParams("{\"Процент\":56,\"Имя Терминала\":test}"); + dslContext.insertInto(STATE_DATA) + .set(dslContext.newRecord(STATE_DATA, stateData)) + .execute(); + + String newMapParams = "{\"Процент\":56,\"Имя Терминала\":new}"; + stateDataDao.updateParams(stateData.getUserId(), newMapParams); + + StateDataRecord stateDataRecord = dslContext.fetchAny(STATE_DATA); + + assertEquals(newMapParams, stateDataRecord.getMapParams()); + } + + @Test + void getByUserId() { + StateData stateData = TestObjectFactory.testStateData(); + dslContext.insertInto(STATE_DATA) + .set(dslContext.newRecord(STATE_DATA, stateData)) + .execute(); + + StateData data = stateDataDao.getByUserId(stateData.getUserId()); + + assertEquals(stateData.getUserId(), data.getUserId()); + } + +} diff --git a/src/test/java/dev/vality/alert/tg/bot/handler/CallbackHandlerTest.java b/src/test/java/dev/vality/alert/tg/bot/handler/CallbackHandlerTest.java new file mode 100644 index 0000000..e39c70a --- /dev/null +++ b/src/test/java/dev/vality/alert/tg/bot/handler/CallbackHandlerTest.java @@ -0,0 +1,105 @@ +package dev.vality.alert.tg.bot.handler; + +import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration; +import dev.vality.alert.tg.bot.dao.ParametersDao; +import dev.vality.alert.tg.bot.dao.StateDataDao; +import dev.vality.alert.tg.bot.mapper.JsonMapper; +import dev.vality.alert.tg.bot.mapper.MenuCallbackMapper; +import dev.vality.alert.tg.bot.mapper.ParametersCallbackMapper; +import dev.vality.alert.tg.bot.service.MayDayService; +import dev.vality.alerting.mayday.AlertConfiguration; +import dev.vality.alerting.mayday.ParameterConfiguration; +import dev.vality.alerting.mayday.ParameterType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.Update; + +import java.util.Collections; +import java.util.List; + +import static dev.vality.alert.tg.bot.TestObjectFactory.*; +import static dev.vality.alert.tg.bot.constants.TextConstants.ALERTS_REMOVED; +import static dev.vality.alert.tg.bot.constants.TextConstants.SELECT_ALERT; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(SpringExtension.class) +@Import(ExcludeDataSourceConfiguration.class) +@ContextConfiguration(classes = {CallbackHandler.class, MenuCallbackMapper.class, ParametersCallbackMapper.class, + JsonMapper.class}) +public class CallbackHandlerTest { + @MockBean + private ParametersDao parametersDao; + @MockBean + private StateDataDao stateDataDao; + @MockBean + private MayDayService mayDayService; + @Autowired + private CallbackHandler callbackHandler; + + + @Test + void testCallbackDeleteAllAlertsHandler() throws Exception { + Update update = testUpdateDeleteAllCallback(); + SendMessage sendMessage = callbackHandler.handle(update, 123L); + assertNotNull(sendMessage); + assertEquals(ALERTS_REMOVED.getText(), sendMessage.getText()); + verify(mayDayService, times(1)).deleteAllAlerts(any()); + } + + @Test + void testCallbackCreateAlertHandler() throws Exception { + Update update = testUpdateCreateAlertCallback(); + SendMessage sendMessage = callbackHandler.handle(update, 123L); + assertNotNull(sendMessage); + assertEquals(SELECT_ALERT.getText(), sendMessage.getText()); + verify(stateDataDao, times(1)).save(any()); + + } + + @Test + void testCallbackGetAllAlertsHandler() throws Exception { + Update update = testUpdateGetAllAlertsCallback(); + SendMessage sendMessage = callbackHandler.handle(update, 123L); + assertNotNull(sendMessage); + assertNotNull(sendMessage.getReplyMarkup()); + verify(mayDayService, times(1)).getUserAlerts(any()); + + } + + @Test + void testCallbackReturnHandler() throws Exception { + Update update = testUpdateReturnCallback(); + SendMessage sendMessage = callbackHandler.handle(update, 123L); + assertNotNull(sendMessage); + assertNotNull(sendMessage.getReplyMarkup()); + } + + @Test + void testCallbackMessageHandler() throws Exception { + List parameterConfigurations = + Collections.singletonList(new ParameterConfiguration() + .setId("2").setName("test").setType(ParameterType.str)); + when(mayDayService.getAlertConfiguration(any())) + .thenReturn(new AlertConfiguration().setAlertId("test").setParameters(parameterConfigurations)); + when(stateDataDao.getByUserId(any())).thenReturn(testStateData()); + Update update = testUpdateMessageCallback(); + SendMessage sendMessage = callbackHandler.handle(update, 123L); + assertNotNull(sendMessage); + assertNotNull(sendMessage.getReplyMarkup()); + verify(mayDayService, times(1)).getAlertConfiguration(any()); + verify(stateDataDao, times(1)).getByUserId(any()); + verify(stateDataDao, times(1)).save(any()); + verify(parametersDao, times(1)).save(any()); + + } + +} diff --git a/src/test/java/dev/vality/alert/tg/bot/handler/MainMenuHandlerTest.java b/src/test/java/dev/vality/alert/tg/bot/handler/MainMenuHandlerTest.java new file mode 100644 index 0000000..04f5f45 --- /dev/null +++ b/src/test/java/dev/vality/alert/tg/bot/handler/MainMenuHandlerTest.java @@ -0,0 +1,41 @@ +package dev.vality.alert.tg.bot.handler; + +import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.Update; + +import static dev.vality.alert.tg.bot.TestObjectFactory.testUpdateMessage; +import static dev.vality.alert.tg.bot.TestObjectFactory.testUpdateMessageWithWithDifferentId; +import static dev.vality.alert.tg.bot.constants.TextConstants.REPLY_OR_ACTION; +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(SpringExtension.class) +@Import(ExcludeDataSourceConfiguration.class) +@ContextConfiguration(classes = {MainMenuHandler.class}) +public class MainMenuHandlerTest { + + @Autowired + private MainMenuHandler mainMenuHandler; + + + @Test + void testMainMenuWithWithDifferentIdHandle() { + Update update = testUpdateMessageWithWithDifferentId(); + SendMessage sendMessage = mainMenuHandler.handle(update, 123L); + assertNull(sendMessage); + } + + @Test + void testMainMenuHandle() { + Update update = testUpdateMessage(); + SendMessage sendMessage = mainMenuHandler.handle(update, 123L); + assertNotNull(sendMessage); + assertEquals(REPLY_OR_ACTION.getText(), sendMessage.getText()); + } +} diff --git a/src/test/java/dev/vality/alert/tg/bot/handler/MessageHandlerTest.java b/src/test/java/dev/vality/alert/tg/bot/handler/MessageHandlerTest.java new file mode 100644 index 0000000..dd67fc8 --- /dev/null +++ b/src/test/java/dev/vality/alert/tg/bot/handler/MessageHandlerTest.java @@ -0,0 +1,44 @@ +package dev.vality.alert.tg.bot.handler; + +import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration; +import dev.vality.alert.tg.bot.dao.StateDataDao; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.Update; + +import static dev.vality.alert.tg.bot.TestObjectFactory.testUpdateMessage; +import static dev.vality.alert.tg.bot.constants.TextConstants.SELECT_ACTION; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(SpringExtension.class) +@Import(ExcludeDataSourceConfiguration.class) +@ContextConfiguration(classes = {MessageHandler.class}) +public class MessageHandlerTest { + + @MockBean + private StateDataDao stateDataDao; + @Autowired + private MessageHandler messageHandler; + + + @Test + void testMessageHandle() { + Update update = testUpdateMessage(); + SendMessage sendMessage = messageHandler.handle(update, 123L); + assertNotNull(sendMessage); + assertNotNull(sendMessage.getReplyMarkup()); + assertEquals(SELECT_ACTION.getText(), sendMessage.getText()); + verify(stateDataDao, times(1)).save(any()); + } + +} diff --git a/src/test/java/dev/vality/alert/tg/bot/handler/ReplyHandlerTest.java b/src/test/java/dev/vality/alert/tg/bot/handler/ReplyHandlerTest.java new file mode 100644 index 0000000..c683a6b --- /dev/null +++ b/src/test/java/dev/vality/alert/tg/bot/handler/ReplyHandlerTest.java @@ -0,0 +1,66 @@ +package dev.vality.alert.tg.bot.handler; + +import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration; +import dev.vality.alert.tg.bot.dao.ParametersDao; +import dev.vality.alert.tg.bot.dao.StateDataDao; +import dev.vality.alert.tg.bot.mapper.JsonMapper; +import dev.vality.alert.tg.bot.mapper.ReplyMessagesMapper; +import dev.vality.alert.tg.bot.service.MayDayService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; +import org.telegram.telegrambots.meta.api.objects.Update; + +import static dev.vality.alert.tg.bot.TestObjectFactory.*; +import static dev.vality.alert.tg.bot.constants.TextConstants.ALERT_CREATED; +import static dev.vality.alert.tg.bot.constants.TextConstants.ALERT_REMOVED; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(SpringExtension.class) +@Import(ExcludeDataSourceConfiguration.class) +@ContextConfiguration(classes = {ReplyHandler.class, ReplyMessagesMapper.class, JsonMapper.class}) +public class ReplyHandlerTest { + + @MockBean + private ParametersDao parametersDao; + @MockBean + private StateDataDao stateDataDao; + @MockBean + private MayDayService mayDayService; + @Autowired + private ReplyHandler replyHandler; + + + @Test + void testReplyHandle() throws Exception { + when(stateDataDao.getByUserId(anyLong())).thenReturn(testStateData()); + when(parametersDao.getByAlertIdAndParamName(anyString(), anyString())).thenReturn(testParameters()); + Update update = testUpdateReply(); + SendMessage sendMessage = replyHandler.handle(update, 123L); + assertNotNull(sendMessage); + assertEquals(ALERT_CREATED.getText(), sendMessage.getText()); + verify(mayDayService, times(1)).createAlert(any()); + verify(stateDataDao, times(2)).getByUserId(any()); + verify(stateDataDao, times(1)).updateParams(any(), any()); + } + + @Test + void testReplyDeleteAlertHandle() throws Exception { + when(stateDataDao.getByUserId(anyLong())).thenReturn(testStateData()); + when(parametersDao.getByAlertIdAndParamName(anyString(), anyString())).thenReturn(testParameters()); + Update update = testUpdateReplyDeleteAlert(); + SendMessage sendMessage = replyHandler.handle(update, 123L); + assertNotNull(sendMessage); + assertEquals(ALERT_REMOVED.getText(), sendMessage.getText()); + verify(mayDayService, times(1)).deleteAlert(any()); + + } +} diff --git a/src/test/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapperTest.java b/src/test/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapperTest.java new file mode 100644 index 0000000..4bf03aa --- /dev/null +++ b/src/test/java/dev/vality/alert/tg/bot/mapper/MenuCallbackMapperTest.java @@ -0,0 +1,90 @@ +package dev.vality.alert.tg.bot.mapper; + +import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration; +import dev.vality.alert.tg.bot.dao.StateDataDao; +import dev.vality.alert.tg.bot.service.MayDayService; +import dev.vality.alerting.mayday.UserAlert; +import org.apache.thrift.TException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; + +import java.util.Collections; +import java.util.List; + +import static dev.vality.alert.tg.bot.constants.TextConstants.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(SpringExtension.class) +@Import(ExcludeDataSourceConfiguration.class) +@ContextConfiguration(classes = {MenuCallbackMapper.class}) +public class MenuCallbackMapperTest { + + @MockBean + private StateDataDao stateDataDao; + @MockBean + private MayDayService mayDayService; + @Autowired + private MenuCallbackMapper menuCallbackMapper; + + @Test + void testGetAllAlertsCallback() throws Exception { + List userAlertsList = Collections.singletonList(new UserAlert().setId("2").setName("test")); + when(mayDayService.getUserAlerts(any())) + .thenReturn(userAlertsList); + SendMessage sendMessage = menuCallbackMapper.getAllAlertsCallback(123L); + assertNotNull(sendMessage); + assertNotNull(sendMessage.getReplyMarkup()); + StringBuilder text = new StringBuilder("Ваши алерты:\n"); + userAlertsList.forEach(userAlert -> { + text.append("id: ").append(userAlert.getId()) + .append(" Название: ").append(userAlert.getName()) + .append("\n"); + }); + assertEquals(text.toString(), sendMessage.getText()); + } + + @Test + void testDeleteAlertCallback() throws Exception { + SendMessage sendMessage = menuCallbackMapper.deleteAlertCallback(123L); + assertNotNull(sendMessage); + assertNotNull(sendMessage.getReplyMarkup()); + assertEquals(ENTER_ALERT_ID_FOR_REMOVED.getText(), sendMessage.getText()); + } + + @Test + void testDeleteAllAlertsCallback() throws Exception { + SendMessage sendMessage = menuCallbackMapper.deleteAllAlertsCallback(123L); + assertNotNull(sendMessage); + assertNotNull(sendMessage.getReplyMarkup()); + assertEquals(ALERTS_REMOVED.getText(), sendMessage.getText()); + } + + @Test + void testReturnCallback() { + SendMessage sendMessage = menuCallbackMapper.returnCallback(); + assertNotNull(sendMessage); + assertNotNull(sendMessage.getReplyMarkup()); + assertEquals(SELECT_ACTION.getText(), sendMessage.getText()); + } + + @Test + void testCreateAlertCallback() throws TException { + SendMessage sendMessage = menuCallbackMapper.createAlertCallback(123L); + assertNotNull(sendMessage); + assertNotNull(sendMessage.getReplyMarkup()); + assertEquals(SELECT_ALERT.getText(), sendMessage.getText()); + verify(stateDataDao, times(1)).save(any()); + verify(mayDayService, times(1)).getSupportedAlerts(); + } + + +} diff --git a/src/test/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapperTest.java b/src/test/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapperTest.java new file mode 100644 index 0000000..6797b61 --- /dev/null +++ b/src/test/java/dev/vality/alert/tg/bot/mapper/ParametersCallbackMapperTest.java @@ -0,0 +1,59 @@ +package dev.vality.alert.tg.bot.mapper; + +import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration; +import dev.vality.alert.tg.bot.dao.ParametersDao; +import dev.vality.alert.tg.bot.dao.StateDataDao; +import dev.vality.alert.tg.bot.service.MayDayService; +import dev.vality.alerting.mayday.AlertConfiguration; +import dev.vality.alerting.mayday.ParameterConfiguration; +import dev.vality.alerting.mayday.ParameterType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; + +import java.util.Collections; +import java.util.List; + +import static dev.vality.alert.tg.bot.TestObjectFactory.testStateData; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(SpringExtension.class) +@Import(ExcludeDataSourceConfiguration.class) +@ContextConfiguration(classes = {ParametersCallbackMapper.class, JsonMapper.class}) +public class ParametersCallbackMapperTest { + + @MockBean + private StateDataDao stateDataDao; + @MockBean + private ParametersDao parametersDao; + @MockBean + private MayDayService mayDayService; + @Autowired + private ParametersCallbackMapper parametersCallbackMapper; + + @Test + void testMapParametersCallback() throws Exception { + List parameterConfigurations = + Collections.singletonList(new ParameterConfiguration() + .setId("2").setName("test").setType(ParameterType.str)); + when(mayDayService.getAlertConfiguration(any())) + .thenReturn(new AlertConfiguration().setAlertId("test").setParameters(parameterConfigurations)); + when(stateDataDao.getByUserId(any())).thenReturn(testStateData()); + SendMessage sendMessage = parametersCallbackMapper.mapParametersCallback("test", 123L); + assertNotNull(sendMessage); + assertNotNull(sendMessage.getReplyMarkup()); + assertEquals("test", sendMessage.getText()); + verify(mayDayService, times(1)).getAlertConfiguration(any()); + verify(stateDataDao, times(1)).getByUserId(any()); + verify(stateDataDao, times(1)).save(any()); + verify(parametersDao, times(1)).save(any()); + } +} diff --git a/src/test/java/dev/vality/alert/tg/bot/mapper/ReplyMessageMapperTest.java b/src/test/java/dev/vality/alert/tg/bot/mapper/ReplyMessageMapperTest.java new file mode 100644 index 0000000..7b3b111 --- /dev/null +++ b/src/test/java/dev/vality/alert/tg/bot/mapper/ReplyMessageMapperTest.java @@ -0,0 +1,78 @@ +package dev.vality.alert.tg.bot.mapper; + +import dev.vality.alert.tg.bot.config.ExcludeDataSourceConfiguration; +import dev.vality.alert.tg.bot.dao.ParametersDao; +import dev.vality.alert.tg.bot.dao.StateDataDao; +import dev.vality.alert.tg.bot.service.MayDayService; +import dev.vality.alerting.mayday.AlertConfiguration; +import dev.vality.alerting.mayday.ParameterConfiguration; +import dev.vality.alerting.mayday.ParameterType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.telegram.telegrambots.meta.api.methods.send.SendMessage; + +import java.util.Collections; +import java.util.List; + +import static dev.vality.alert.tg.bot.TestObjectFactory.testParameters; +import static dev.vality.alert.tg.bot.TestObjectFactory.testStateData; +import static dev.vality.alert.tg.bot.constants.TextConstants.ALERT_CREATED; +import static dev.vality.alert.tg.bot.constants.TextConstants.ALERT_REMOVED; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(SpringExtension.class) +@Import(ExcludeDataSourceConfiguration.class) +@ContextConfiguration(classes = {ReplyMessagesMapper.class, JsonMapper.class}) +public class ReplyMessageMapperTest { + + @MockBean + private StateDataDao stateDataDao; + @MockBean + private ParametersDao parametersDao; + @MockBean + private MayDayService mayDayService; + @Autowired + private ReplyMessagesMapper replyMessagesMapper; + + @Test + void testCreateAlertRequest() throws Exception { + List parameterConfigurations = + Collections.singletonList(new ParameterConfiguration() + .setId("2").setName("test").setType(ParameterType.str)); + when(mayDayService.getAlertConfiguration(any())) + .thenReturn(new AlertConfiguration().setAlertId("test").setParameters(parameterConfigurations)); + when(stateDataDao.getByUserId(any())).thenReturn(testStateData()); + when(parametersDao.getByAlertIdAndParamName(any(), any())).thenReturn(testParameters()); + SendMessage sendMessage = replyMessagesMapper.createAlertRequest(123L); + assertNotNull(sendMessage); + assertNotNull(sendMessage.getReplyMarkup()); + assertEquals(ALERT_CREATED.getText(), sendMessage.getText()); + verify(mayDayService, times(1)).createAlert(any()); + verify(stateDataDao, times(1)).getByUserId(any()); + } + + @Test + void testCreateNextParameterRequest() { + SendMessage sendMessage = replyMessagesMapper.createNextParameterRequest("test"); + assertNotNull(sendMessage); + assertNotNull(sendMessage.getReplyMarkup()); + assertEquals("test", sendMessage.getText()); + } + + @Test + void testDeleteAlert() throws Exception { + SendMessage sendMessage = replyMessagesMapper.deleteAlert("test"); + assertNotNull(sendMessage); + assertNotNull(sendMessage.getReplyMarkup()); + assertEquals(ALERT_REMOVED.getText(), sendMessage.getText()); + verify(mayDayService, times(1)).deleteAlert(any()); + } +} diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..4bb5766 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,10 @@ + + + + + + + + + +