diff --git a/pom.xml b/pom.xml index 4795c70..529f51d 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ 1.16.3 1.1.1 2.13.3 + 5.9.1 @@ -68,6 +69,13 @@ test + + org.junit.jupiter + junit-jupiter-params + ${junit-jupiter-params.version} + test + + io.jooby jooby-test diff --git a/schema/json/CallbackQueryMetadata.json b/schema/json/CallbackQueryMetadata.json index 3e4d651..ad290b5 100644 --- a/schema/json/CallbackQueryMetadata.json +++ b/schema/json/CallbackQueryMetadata.json @@ -5,7 +5,6 @@ "default": {}, "required": [ "event", - "messageId", "telegramChatId" ], "additionalProperties": true, diff --git a/src/main/java/com/github/polpetta/mezzotre/App.java b/src/main/java/com/github/polpetta/mezzotre/App.java index c11e464..9261d48 100644 --- a/src/main/java/com/github/polpetta/mezzotre/App.java +++ b/src/main/java/com/github/polpetta/mezzotre/App.java @@ -3,6 +3,7 @@ package com.github.polpetta.mezzotre; import com.github.polpetta.mezzotre.orm.di.Db; import com.github.polpetta.mezzotre.route.Telegram; import com.github.polpetta.mezzotre.route.di.Route; +import com.github.polpetta.mezzotre.telegram.callbackquery.di.CallbackQuery; import com.github.polpetta.mezzotre.telegram.command.di.Command; import com.github.polpetta.mezzotre.util.di.ThreadPool; import com.google.inject.*; @@ -26,6 +27,7 @@ public class App extends Jooby { modules.add(new ThreadPool()); modules.add(new Route()); modules.add(new Command()); + modules.add(new CallbackQuery()); return modules; }; diff --git a/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java b/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java index 86a070a..b905c18 100644 --- a/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java +++ b/src/main/java/com/github/polpetta/mezzotre/i18n/LocalizedMessageFactory.java @@ -22,8 +22,6 @@ public class LocalizedMessageFactory { public ToolManager create(Locale locale) { - // properties.setProperty("file.resource.loader.class", FileResourceLoader.class.getName());. - final ToolManager toolManager = new ToolManager(); toolManager.setVelocityEngine(velocityEngine); final FactoryConfiguration factoryConfiguration = new FactoryConfiguration(); diff --git a/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java b/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java index 9ede71a..3c29f91 100644 --- a/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java +++ b/src/main/java/com/github/polpetta/mezzotre/orm/model/CallbackQueryContext.java @@ -2,6 +2,7 @@ package com.github.polpetta.mezzotre.orm.model; import com.github.polpetta.types.json.CallbackQueryMetadata; import io.ebean.annotation.DbJson; +import io.ebean.annotation.Length; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @@ -22,12 +23,19 @@ import javax.validation.constraints.NotNull; @Table(name = "callback_query_context") public class CallbackQueryContext extends Base { - @Id private final String id; + @Id + @Length(36) + private final String id; + + @NotNull + @Length(36) + private final String entryGroup; @DbJson @NotNull private final CallbackQueryMetadata fields; - public CallbackQueryContext(String id, CallbackQueryMetadata fields) { + public CallbackQueryContext(String id, String entryGroup, CallbackQueryMetadata fields) { this.id = id; + this.entryGroup = entryGroup; this.fields = fields; } @@ -35,6 +43,10 @@ public class CallbackQueryContext extends Base { return id; } + public String getEntryGroup() { + return entryGroup; + } + public CallbackQueryMetadata getFields() { return fields; } diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java index 8f599fe..c0a0aaf 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguage.java @@ -1,27 +1,157 @@ package com.github.polpetta.mezzotre.telegram.callbackquery; +import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; +import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext; +import com.github.polpetta.mezzotre.orm.model.query.QTgChat; import com.pengrad.telegrambot.model.Update; +import com.pengrad.telegrambot.model.request.ParseMode; import com.pengrad.telegrambot.request.BaseRequest; +import com.pengrad.telegrambot.request.SendMessage; +import io.vavr.control.Try; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import javax.inject.Inject; +import javax.inject.Named; import javax.inject.Singleton; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.tools.ToolManager; +import org.apache.velocity.util.StringBuilderWriter; +import org.slf4j.Logger; @Singleton public class ChangeLanguage implements Processor { + public static final String EVENT_NAME = "changeLanguage"; + private final Executor threadPool; + private final LocalizedMessageFactory localizedMessageFactory; + private final Logger log; + + public enum Field { + NewLanguage("newLanguage"); + + private final String name; + + Field(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + public enum Language { + English("en-US"), + Italian("it-IT"); + + private final String locale; + + Language(String locale) { + this.locale = locale; + } + + public String getLocale() { + return locale; + } + } + @Inject - public ChangeLanguage() {} + public ChangeLanguage( + @Named("eventThreadPool") Executor threadPool, + LocalizedMessageFactory localizedMessageFactory, + Logger log) { + this.threadPool = threadPool; + this.localizedMessageFactory = localizedMessageFactory; + this.log = log; + } @Override public String getEventName() { - return "changeLanguage"; + return EVENT_NAME; } @Override public CompletableFuture>> process( CallbackQueryContext callbackQueryContext, Update update) { - return null; + return CompletableFuture.supplyAsync( + () -> + Optional.of(callbackQueryContext.getFields().getTelegramChatId()) + .filter( + chatId -> + chatId.longValue() != 0L + && !chatId.isNaN() + && !chatId.isInfinite() + && chatId != Double.MIN_VALUE) + .flatMap(chatId -> new QTgChat().id.eq(chatId.longValue()).findOneOrEmpty()) + .map( + tgChat -> { + tgChat.setLocale( + (String) + callbackQueryContext + .getFields() + .getAdditionalProperties() + .getOrDefault( + Field.NewLanguage.getName(), + Language.English.getLocale())); + tgChat.save(); + return tgChat; + }) + .orElseThrow( + () -> + new NoSuchElementException( + "Unable to find telegram chat " + + Double.valueOf( + callbackQueryContext.getFields().getTelegramChatId()) + .longValue() + + " in the database")), + threadPool) + .thenApplyAsync( + // If we are here then we're sure there is at least a chat associated with this callback + tgChat -> { + final String message = + Try.of( + () -> { + final Locale locale = Locale.forLanguageTag(tgChat.getLocale()); + final ToolManager toolManager = localizedMessageFactory.create(locale); + final VelocityContext velocityContext = + new VelocityContext(toolManager.createContext()); + + final StringBuilder content = new StringBuilder(); + final StringBuilderWriter stringBuilderWriter = + new StringBuilderWriter(content); + + toolManager + .getVelocityEngine() + .mergeTemplate( + "/template/callbackQuery/changeLanguage.vm", + StandardCharsets.UTF_8.name(), + velocityContext, + stringBuilderWriter); + + stringBuilderWriter.close(); + return content.toString(); + }) + .get(); + + log.trace("ChangeLanguage event - message to send back: " + message); + + final String callBackGroupToDelete = callbackQueryContext.getEntryGroup(); + final int delete = + new QCallbackQueryContext().entryGroup.eq(callBackGroupToDelete).delete(); + log.trace( + "Deleted " + + delete + + " entries regarding callback group " + + callBackGroupToDelete); + + return Optional.of( + new SendMessage(tgChat.getId(), message).parseMode(ParseMode.Markdown)); + }, + threadPool); } } diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java index a98a650..8c5aae5 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Start.java @@ -1,10 +1,17 @@ package com.github.polpetta.mezzotre.telegram.command; import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; +import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; import com.github.polpetta.mezzotre.orm.model.TgChat; +import com.github.polpetta.mezzotre.telegram.callbackquery.ChangeLanguage; +import com.github.polpetta.mezzotre.util.Clock; +import com.github.polpetta.mezzotre.util.UUIDGenerator; +import com.github.polpetta.types.json.CallbackQueryMetadata; import com.github.polpetta.types.json.ChatContext; import com.google.inject.Singleton; import com.pengrad.telegrambot.model.Update; +import com.pengrad.telegrambot.model.request.InlineKeyboardButton; +import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup; import com.pengrad.telegrambot.model.request.ParseMode; import com.pengrad.telegrambot.request.BaseRequest; import com.pengrad.telegrambot.request.SendMessage; @@ -13,6 +20,7 @@ import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Named; import org.apache.velocity.VelocityContext; @@ -29,18 +37,24 @@ import org.slf4j.Logger; @Singleton public class Start implements Processor { - private final java.util.concurrent.Executor threadPool; + private final Executor threadPool; private final Logger log; + private final UUIDGenerator uuidGenerator; + private final Clock clock; private final LocalizedMessageFactory localizedMessageFactory; @Inject public Start( LocalizedMessageFactory localizedMessageFactory, - @Named("eventThreadPool") java.util.concurrent.Executor threadPool, - Logger log) { + @Named("eventThreadPool") Executor threadPool, + Logger log, + UUIDGenerator uuidGenerator, + Clock clock) { this.localizedMessageFactory = localizedMessageFactory; this.threadPool = threadPool; this.log = log; + this.uuidGenerator = uuidGenerator; + this.clock = clock; } @Override @@ -62,17 +76,17 @@ public class Start implements Processor { Try.of( () -> { final Locale locale = Locale.forLanguageTag(chat.getLocale()); - final ToolManager toolContext = localizedMessageFactory.create(locale); + final ToolManager toolManager = localizedMessageFactory.create(locale); final VelocityContext context = - new VelocityContext(toolContext.createContext()); + new VelocityContext(toolManager.createContext()); context.put("firstName", update.message().chat().firstName()); - context.put("programName", "Mezzotre"); + context.put("programName", "_Mezzotre_"); final StringBuilder content = new StringBuilder(); final StringBuilderWriter stringBuilderWriter = new StringBuilderWriter(content); - toolContext + toolManager .getVelocityEngine() .mergeTemplate( "template/command/start.0.vm", @@ -87,14 +101,51 @@ public class Start implements Processor { log.trace("Start command - message to send back: " + message); final ChatContext chatContext = chat.getChatContext(); - chatContext.setLastMessageSentId(update.message().messageId()); chatContext.setStage(getTriggerKeyword()); chatContext.setStep(0); - chatContext.setPreviousMessageUnixTimestampInSeconds(update.message().date()); + chatContext.setPreviousMessageUnixTimestampInSeconds(clock.now()); chat.setChatContext(chatContext); chat.save(); - return Optional.of(new SendMessage(chat.getId(), message).parseMode(ParseMode.Markdown)); + // To get the messageId we should send the message first, then save it in the database! + final String groupId = uuidGenerator.generateAsString(); + final CallbackQueryContext switchToEnglish = + new CallbackQueryContext( + uuidGenerator.generateAsString(), + groupId, + new CallbackQueryMetadata.CallbackQueryMetadataBuilder() + .withEvent(ChangeLanguage.EVENT_NAME) + .withTelegramChatId(update.message().chat().id()) + .withAdditionalProperty( + ChangeLanguage.Field.NewLanguage.getName(), + ChangeLanguage.Language.English) + .build()); + + final CallbackQueryContext switchToItalian = + new CallbackQueryContext( + uuidGenerator.generateAsString(), + groupId, + new CallbackQueryMetadata.CallbackQueryMetadataBuilder() + .withEvent(ChangeLanguage.EVENT_NAME) + .withTelegramChatId(update.message().chat().id()) + .withAdditionalProperty( + ChangeLanguage.Field.NewLanguage.getName(), + ChangeLanguage.Language.Italian) + .build()); + + final SendMessage messageToSend = + new SendMessage(chat.getId(), message) + .parseMode(ParseMode.Markdown) + .replyMarkup( + new InlineKeyboardMarkup( + new InlineKeyboardButton("English").callbackData(switchToEnglish.getId()), + new InlineKeyboardButton("Italian") + .callbackData(switchToItalian.getId()))); + + switchToEnglish.save(); + switchToItalian.save(); + + return Optional.of(messageToSend); }, threadPool); } diff --git a/src/main/resources/i18n/message.properties b/src/main/resources/i18n/message.properties index e18c881..1a07ce4 100644 --- a/src/main/resources/i18n/message.properties +++ b/src/main/resources/i18n/message.properties @@ -1,3 +1,6 @@ -start.hello=Hello -start.thisIs=This is -start.description=a simple bot focused on DnD content management! Please start by choosing a language down below. +start.helloFirstName=Hello {0}! \ud83d\udc4b +start.description=This is {0}, a simple bot focused on DnD content management! Please start by choosing a language down below \ud83d\udc47 +changeLanguage.drinkAction=*Proceeds to drink a potion with a strange, multicolor liquid* +changeLanguage.setLanguage=Thanks! Now that I drank this modified potion of {0} that I''ve found at the "Crystal Fermentary" magic potion shop yesterday I can speak with you in the language you prefer! +changeLanguage.instructions=You can always change your language settings by typing /changeLanguage in the chat. +spell.speakWithAnimals=Speak with animals diff --git a/src/main/resources/i18n/message_en_US.properties b/src/main/resources/i18n/message_en_US.properties index e18c881..1a07ce4 100644 --- a/src/main/resources/i18n/message_en_US.properties +++ b/src/main/resources/i18n/message_en_US.properties @@ -1,3 +1,6 @@ -start.hello=Hello -start.thisIs=This is -start.description=a simple bot focused on DnD content management! Please start by choosing a language down below. +start.helloFirstName=Hello {0}! \ud83d\udc4b +start.description=This is {0}, a simple bot focused on DnD content management! Please start by choosing a language down below \ud83d\udc47 +changeLanguage.drinkAction=*Proceeds to drink a potion with a strange, multicolor liquid* +changeLanguage.setLanguage=Thanks! Now that I drank this modified potion of {0} that I''ve found at the "Crystal Fermentary" magic potion shop yesterday I can speak with you in the language you prefer! +changeLanguage.instructions=You can always change your language settings by typing /changeLanguage in the chat. +spell.speakWithAnimals=Speak with animals diff --git a/src/main/resources/i18n/message_it.properties b/src/main/resources/i18n/message_it.properties index 0f7424c..739ea44 100644 --- a/src/main/resources/i18n/message_it.properties +++ b/src/main/resources/i18n/message_it.properties @@ -1,3 +1,6 @@ -start.hello=Ciao -start.thisIs=Questo è -start.description=un semplice bot che ci concenta sulla gestione di contenuto per DnD! Per favore comincia selezionando la lingua qui sotto +start.helloFirstName=Ciao {0}! \ud83d\udc4b +start.description=Questo è {0}, un semplice bot che ci concenta sulla gestione di contenuto per DnD! Per favore comincia selezionando la lingua qui sotto \ud83d\udc47 +changeLanguage.drinkAction=*Procede a bere una pozione al cui suo interno si trova uno strano liquido multicolore* +changeLanguage.setLanguage=Grazie! Ora che ho bevuto quest posizione modificata di {0} che ho trovato ieri al negozio di pozioni magiche la "Cristalleria Fermentatrice" posso parlare con te nel linguaggio che preferisci! +changeLanguage.instructions=Puoi sempre cambiare le preferenze della tua lingua scrivendo /changeLanguage nella chat. +spell.speakWithAnimals=Parlare con animali diff --git a/src/main/resources/i18n/message_it_IT.properties b/src/main/resources/i18n/message_it_IT.properties index 0f7424c..739ea44 100644 --- a/src/main/resources/i18n/message_it_IT.properties +++ b/src/main/resources/i18n/message_it_IT.properties @@ -1,3 +1,6 @@ -start.hello=Ciao -start.thisIs=Questo è -start.description=un semplice bot che ci concenta sulla gestione di contenuto per DnD! Per favore comincia selezionando la lingua qui sotto +start.helloFirstName=Ciao {0}! \ud83d\udc4b +start.description=Questo è {0}, un semplice bot che ci concenta sulla gestione di contenuto per DnD! Per favore comincia selezionando la lingua qui sotto \ud83d\udc47 +changeLanguage.drinkAction=*Procede a bere una pozione al cui suo interno si trova uno strano liquido multicolore* +changeLanguage.setLanguage=Grazie! Ora che ho bevuto quest posizione modificata di {0} che ho trovato ieri al negozio di pozioni magiche la "Cristalleria Fermentatrice" posso parlare con te nel linguaggio che preferisci! +changeLanguage.instructions=Puoi sempre cambiare le preferenze della tua lingua scrivendo /changeLanguage nella chat. +spell.speakWithAnimals=Parlare con animali diff --git a/src/main/resources/template/callbackQuery/changeLanguage.vm b/src/main/resources/template/callbackQuery/changeLanguage.vm new file mode 100644 index 0000000..57fa7c0 --- /dev/null +++ b/src/main/resources/template/callbackQuery/changeLanguage.vm @@ -0,0 +1,5 @@ +_${i18n.changeLanguage.drinkAction}_ + +${i18n.changeLanguage.setLanguage.insert(${i18n.spell.speakWithAnimals})} + +${i18n.changeLanguage.instructions} \ No newline at end of file diff --git a/src/main/resources/template/command/start.0.vm b/src/main/resources/template/command/start.0.vm index 4e6a73d..7889c0b 100644 --- a/src/main/resources/template/command/start.0.vm +++ b/src/main/resources/template/command/start.0.vm @@ -1,4 +1,3 @@ -## https://velocity.apache.org/tools/2.0/apidocs/org/apache/velocity/tools/generic/ResourceTool.html -**$i18n.start.hello $firstName! 👋** +**${i18n.start.helloFirstName.insert(${firstName})}** -$i18n.start.thisIs _${programName}_, $i18n.start.description 👇 \ No newline at end of file +${i18n.start.description.insert(${programName})} \ No newline at end of file diff --git a/src/test/java/com/github/polpetta/mezzotre/helper/Loader.java b/src/test/java/com/github/polpetta/mezzotre/helper/Loader.java index 4f6d18e..90bc7b9 100644 --- a/src/test/java/com/github/polpetta/mezzotre/helper/Loader.java +++ b/src/test/java/com/github/polpetta/mezzotre/helper/Loader.java @@ -11,6 +11,9 @@ import java.io.InputStream; import java.net.URL; import java.util.Properties; import org.apache.commons.lang3.tuple.Pair; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; import org.testcontainers.containers.PostgreSQLContainer; public class Loader { @@ -49,4 +52,13 @@ public class Loader { public static Database connectToDatabase(Pair connectionProperties) { return connectToDatabase(connectionProperties.getLeft(), connectionProperties.getRight()); } + + public static VelocityEngine defaultVelocityEngine() { + final VelocityEngine velocityEngine = new VelocityEngine(); + velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADERS, "classpath"); + velocityEngine.setProperty( + "resource.loader.classpath.class", ClasspathResourceLoader.class.getName()); + velocityEngine.init(); + return velocityEngine; + } } diff --git a/src/test/java/com/github/polpetta/mezzotre/route/TelegramIntegrationTest.java b/src/test/java/com/github/polpetta/mezzotre/route/TelegramIntegrationTest.java index 267c191..7479666 100644 --- a/src/test/java/com/github/polpetta/mezzotre/route/TelegramIntegrationTest.java +++ b/src/test/java/com/github/polpetta/mezzotre/route/TelegramIntegrationTest.java @@ -120,6 +120,7 @@ class TelegramIntegrationTest { final CallbackQueryContext callbackQueryContext = new CallbackQueryContext( "41427473-0d81-40a8-af60-9517163615a4", + "2ee7f5c6-93f0-4859-b902-af9476cf74ad", new CallbackQueryMetadata.CallbackQueryMetadataBuilder() .withTelegramChatId(666L) .withMessageId(42L) diff --git a/src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguageIntegrationTest.java b/src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguageIntegrationTest.java new file mode 100644 index 0000000..26056a1 --- /dev/null +++ b/src/test/java/com/github/polpetta/mezzotre/telegram/callbackquery/ChangeLanguageIntegrationTest.java @@ -0,0 +1,148 @@ +package com.github.polpetta.mezzotre.telegram.callbackquery; + +import static org.junit.jupiter.api.Assertions.*; + +import com.github.polpetta.mezzotre.helper.Loader; +import com.github.polpetta.mezzotre.helper.TestConfig; +import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; +import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext; +import com.github.polpetta.mezzotre.orm.model.TgChat; +import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext; +import com.github.polpetta.mezzotre.orm.model.query.QTgChat; +import com.github.polpetta.types.json.CallbackQueryMetadata; +import com.github.polpetta.types.json.ChatContext; +import com.google.gson.Gson; +import com.pengrad.telegrambot.model.Update; +import com.pengrad.telegrambot.request.BaseRequest; +import com.pengrad.telegrambot.request.SendMessage; +import io.ebean.Database; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@Tag("slow") +@Tag("database") +@Tag("velocity") +@Testcontainers +class ChangeLanguageIntegrationTest { + + private static Gson gson; + + @Container + private final PostgreSQLContainer postgresServer = + new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE); + + private Database database; + private ChangeLanguage changeLanguage; + + @BeforeAll + static void beforeAll() { + gson = new Gson(); + } + + @BeforeEach + void setUp() throws Exception { + database = + Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer)); + + changeLanguage = + new ChangeLanguage( + Executors.newSingleThreadExecutor(), + new LocalizedMessageFactory(Loader.defaultVelocityEngine()), + LoggerFactory.getLogger(ChangeLanguage.class)); + } + + private static Stream getTestLocales() { + return Stream.of( + Arguments.of( + ChangeLanguage.Language.Italian, + "_*Procede a bere una pozione al cui suo interno si trova uno strano liquido" + + " multicolore*_\n" + + "\n" + + "Grazie! Ora che ho bevuto quest posizione modificata di Parlare con animali che" + + " ho trovato ieri al negozio di pozioni magiche la \"Cristalleria Fermentatrice\"" + + " posso parlare con te nel linguaggio che preferisci!\n" + + "\n" + + "Puoi sempre cambiare le preferenze della tua lingua scrivendo /changeLanguage" + + " nella chat.", + "en-US"), + Arguments.of( + ChangeLanguage.Language.English, + "_*Proceeds to drink a potion with a strange, multicolor liquid*_\n" + + "\n" + + "Thanks! Now that I drank this modified potion of Speak with animals that I've" + + " found at the \"Crystal Fermentary\" magic potion shop yesterday I can speak" + + " with you in the language you prefer!\n" + + "\n" + + "You can always change your language settings by typing /changeLanguage in the" + + " chat.", + "it-IT")); + } + + @ParameterizedTest + @Timeout(value = 1, unit = TimeUnit.MINUTES) + @MethodSource("getTestLocales") + void shouldProcessChangeLanguageToDesiredOne( + ChangeLanguage.Language language, String expectedResult, String startingLocale) + throws Exception { + + final Update update = + gson.fromJson( + "{\n" + + "\"update_id\":10000,\n" + + "\"callback_query\":{\n" + + " \"id\": \"4382bfdwdsb323b2d9\",\n" + + " \"from\":{\n" + + " \"last_name\":\"Test Lastname\",\n" + + " \"type\": \"private\",\n" + + " \"id\":1111111,\n" + + " \"first_name\":\"Test Firstname\",\n" + + " \"username\":\"Testusername\"\n" + + " },\n" + + " \"data\": \"Data from button callback\",\n" + + " \"inline_message_id\": \"1234csdbsk4839\"\n" + + "}\n" + + "}", + Update.class); + + final long tgChatId = 1111111L; + final ChatContext chatContext = new ChatContext(); + final TgChat tgChat = new TgChat(tgChatId, chatContext, startingLocale); + tgChat.save(); + + final CallbackQueryMetadata callbackQueryMetadata = + new CallbackQueryMetadata.CallbackQueryMetadataBuilder() + .withEvent("changeLanguage") + .withTelegramChatId(tgChatId) + .withAdditionalProperty( + ChangeLanguage.Field.NewLanguage.getName(), language.getLocale()) + .build(); + final String entryGroupId = "2e67774a-e4e4-4369-a414-a7f8bfe74b80"; + final CallbackQueryContext changeLanguageCallbackQueryContext = + new CallbackQueryContext( + "c018108f-6612-4848-8fca-cf301460d4eb", entryGroupId, callbackQueryMetadata); + changeLanguageCallbackQueryContext.save(); + + final CompletableFuture>> processFuture = + changeLanguage.process(changeLanguageCallbackQueryContext, update); + final Optional> gotResponseOpt = processFuture.get(); + final SendMessage gotMessage = (SendMessage) gotResponseOpt.get(); + assertEquals(expectedResult, gotMessage.getParameters().get("text")); + + final TgChat retrievedTgChat = new QTgChat().id.eq(tgChatId).findOne(); + assertNotNull(retrievedTgChat); + assertEquals(language.getLocale(), retrievedTgChat.getLocale()); + + assertEquals(0, new QCallbackQueryContext().entryGroup.eq(entryGroupId).findCount()); + } +} diff --git a/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartIntegrationTest.java b/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartIntegrationTest.java index 4d12134..08999a8 100644 --- a/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartIntegrationTest.java +++ b/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartIntegrationTest.java @@ -1,12 +1,15 @@ package com.github.polpetta.mezzotre.telegram.command; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; import com.github.polpetta.mezzotre.helper.Loader; import com.github.polpetta.mezzotre.helper.TestConfig; import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; import com.github.polpetta.mezzotre.orm.model.TgChat; import com.github.polpetta.mezzotre.orm.model.query.QTgChat; +import com.github.polpetta.mezzotre.util.Clock; +import com.github.polpetta.mezzotre.util.UUIDGenerator; import com.github.polpetta.types.json.ChatContext; import com.google.gson.Gson; import com.pengrad.telegrambot.model.Update; @@ -15,6 +18,7 @@ import com.pengrad.telegrambot.request.SendMessage; import io.ebean.Database; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.runtime.RuntimeConstants; @@ -45,6 +49,8 @@ class StartIntegrationTest { private LocalizedMessageFactory localizedMessageFactory; private Start start; private Database database; + private Clock fakeClock; + private UUIDGenerator fakeUUIDGenerator; @BeforeAll static void beforeAll() { @@ -64,11 +70,25 @@ class StartIntegrationTest { final Logger log = LoggerFactory.getLogger(Start.class); - start = new Start(localizedMessageFactory, Executors.newSingleThreadExecutor(), log); + fakeClock = mock(Clock.class); + fakeUUIDGenerator = mock(UUIDGenerator.class); + + start = + new Start( + localizedMessageFactory, + Executors.newSingleThreadExecutor(), + log, + fakeUUIDGenerator, + fakeClock); } @Test void shouldUpdateContextInTheDatabase() throws Exception { + when(fakeClock.now()).thenReturn(42L); + when(fakeUUIDGenerator.generateAsString()) + .thenReturn("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5") + .thenReturn("16507fbd-9f28-48a8-9de1-3ea1c943af67") + .thenReturn("0b0ac18e-f621-484e-aa8d-9b176be5b930"); final TgChat tgChat = new TgChat(1111111L, new ChatContext()); tgChat.setLocale("en-US"); tgChat.save(); @@ -108,16 +128,104 @@ class StartIntegrationTest { assertEquals( "**Hello Test Firstname! \uD83D\uDC4B**\n\n" + "This is _Mezzotre_, a simple bot focused on DnD content management! Please start by" - + " choosing a language down below. \uD83D\uDC47", + + " choosing a language down below \uD83D\uDC47", message); assertEquals(1111111L, (Long) gotMessage.getParameters().get("chat_id")); final TgChat retrievedTgChat = new QTgChat().id.eq(1111111L).findOne(); assertNotNull(retrievedTgChat); final ChatContext gotChatContext = retrievedTgChat.getChatContext(); - assertEquals(1441645532, gotChatContext.getPreviousMessageUnixTimestampInSeconds()); - assertEquals(1365, gotChatContext.getLastMessageSentId()); + assertEquals(42, gotChatContext.getPreviousMessageUnixTimestampInSeconds()); + assertEquals(0, gotChatContext.getLastMessageSentId()); assertEquals("/start", gotChatContext.getStage()); assertEquals(0, gotChatContext.getStep()); } + + @Test + void shouldReceiveHelloIntroduction() throws Exception { + when(fakeClock.now()).thenReturn(42L); + when(fakeUUIDGenerator.generateAsString()) + .thenReturn("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5") + .thenReturn("16507fbd-9f28-48a8-9de1-3ea1c943af67") + .thenReturn("0b0ac18e-f621-484e-aa8d-9b176be5b930"); + + final TgChat tgChat = new TgChat(1111111L, new ChatContext(), "en-US"); + + final Update update = + gson.fromJson( + "{\n" + + "\"update_id\":10000,\n" + + "\"message\":{\n" + + " \"date\":1441645532,\n" + + " \"chat\":{\n" + + " \"last_name\":\"Test Lastname\",\n" + + " \"id\":1111111,\n" + + " \"type\": \"private\",\n" + + " \"first_name\":\"Test Firstname\",\n" + + " \"username\":\"Testusername\"\n" + + " },\n" + + " \"message_id\":1365,\n" + + " \"from\":{\n" + + " \"last_name\":\"Test Lastname\",\n" + + " \"id\":1111111,\n" + + " \"first_name\":\"Test Firstname\",\n" + + " \"username\":\"Testusername\"\n" + + " },\n" + + " \"text\":\"/start\"\n" + + "}\n" + + "}", + Update.class); + + final CompletableFuture>> gotFuture = start.process(tgChat, update); + assertDoesNotThrow(() -> gotFuture.get()); + final Optional> gotMessageOptional = gotFuture.get(); + assertDoesNotThrow(gotMessageOptional::get); + final BaseRequest gotMessage = gotMessageOptional.get(); + assertInstanceOf(SendMessage.class, gotMessage); + final String message = (String) gotMessage.getParameters().get("text"); + assertEquals( + "**Hello Test Firstname! \uD83D\uDC4B**\n\n" + + "This is _Mezzotre_, a simple bot focused on DnD content management! Please start by" + + " choosing a language down below \uD83D\uDC47", + message); + assertEquals(1111111L, (Long) gotMessage.getParameters().get("chat_id")); + } + + @Test + void shouldThrowErrorIfLocaleNonExists() { + when(fakeClock.now()).thenReturn(42L); + when(fakeUUIDGenerator.generateAsString()) + .thenReturn("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5") + .thenReturn("16507fbd-9f28-48a8-9de1-3ea1c943af67") + .thenReturn("0b0ac18e-f621-484e-aa8d-9b176be5b930"); + final TgChat tgChat = new TgChat(1111111L, null); + + final Update update = + gson.fromJson( + "{\n" + + "\"update_id\":10000,\n" + + "\"message\":{\n" + + " \"date\":1441645532,\n" + + " \"chat\":{\n" + + " \"last_name\":\"Test Lastname\",\n" + + " \"id\":1111111,\n" + + " \"type\": \"private\",\n" + + " \"first_name\":\"Test Firstname\",\n" + + " \"username\":\"Testusername\"\n" + + " },\n" + + " \"message_id\":1365,\n" + + " \"from\":{\n" + + " \"last_name\":\"Test Lastname\",\n" + + " \"id\":1111111,\n" + + " \"first_name\":\"Test Firstname\",\n" + + " \"username\":\"Testusername\"\n" + + " },\n" + + " \"text\":\"/start\"\n" + + "}\n" + + "}", + Update.class); + + final CompletableFuture>> gotFuture = start.process(tgChat, update); + assertThrows(ExecutionException.class, gotFuture::get); + } } diff --git a/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartTest.java b/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartTest.java deleted file mode 100644 index 31e6cf4..0000000 --- a/src/test/java/com/github/polpetta/mezzotre/telegram/command/StartTest.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.github.polpetta.mezzotre.telegram.command; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory; -import com.github.polpetta.mezzotre.orm.model.TgChat; -import com.github.polpetta.types.json.ChatContext; -import com.google.gson.Gson; -import com.pengrad.telegrambot.model.Update; -import com.pengrad.telegrambot.request.BaseRequest; -import com.pengrad.telegrambot.request.SendMessage; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import org.apache.velocity.app.VelocityEngine; -import org.apache.velocity.runtime.RuntimeConstants; -import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.slf4j.Logger; - -@Tag("velocity") -@Execution(ExecutionMode.CONCURRENT) -class StartTest { - - private VelocityEngine velocityEngine; - private LocalizedMessageFactory localizedMessageFactory; - private Start start; - private static Gson gson; - - @BeforeAll - static void beforeAll() { - gson = new Gson(); - } - - @BeforeEach - void setUp() { - velocityEngine = new VelocityEngine(); - velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADERS, "classpath"); - velocityEngine.setProperty( - "resource.loader.classpath.class", ClasspathResourceLoader.class.getName()); - velocityEngine.init(); - localizedMessageFactory = new LocalizedMessageFactory(velocityEngine); - - final Logger fakeLog = mock(Logger.class); - - start = new Start(localizedMessageFactory, Executors.newSingleThreadExecutor(), fakeLog); - } - - @Test - void shouldReceiveHelloIntroduction() throws Exception { - final TgChat fakeChat = mock(TgChat.class); - when(fakeChat.getLocale()).thenReturn("en-US"); - when(fakeChat.getChatContext()).thenReturn(new ChatContext()); - when(fakeChat.getId()).thenReturn(1111111L); - - final Update update = - gson.fromJson( - "{\n" - + "\"update_id\":10000,\n" - + "\"message\":{\n" - + " \"date\":1441645532,\n" - + " \"chat\":{\n" - + " \"last_name\":\"Test Lastname\",\n" - + " \"id\":1111111,\n" - + " \"type\": \"private\",\n" - + " \"first_name\":\"Test Firstname\",\n" - + " \"username\":\"Testusername\"\n" - + " },\n" - + " \"message_id\":1365,\n" - + " \"from\":{\n" - + " \"last_name\":\"Test Lastname\",\n" - + " \"id\":1111111,\n" - + " \"first_name\":\"Test Firstname\",\n" - + " \"username\":\"Testusername\"\n" - + " },\n" - + " \"text\":\"/start\"\n" - + "}\n" - + "}", - Update.class); - - final CompletableFuture>> gotFuture = - start.process(fakeChat, update); - assertDoesNotThrow(() -> gotFuture.get()); - final Optional> gotMessageOptional = gotFuture.get(); - assertDoesNotThrow(gotMessageOptional::get); - final BaseRequest gotMessage = gotMessageOptional.get(); - assertInstanceOf(SendMessage.class, gotMessage); - final String message = (String) gotMessage.getParameters().get("text"); - assertEquals( - "**Hello Test Firstname! \uD83D\uDC4B**\n\n" - + "This is _Mezzotre_, a simple bot focused on DnD content management! Please start by" - + " choosing a language down below. \uD83D\uDC47", - message); - assertEquals(1111111L, (Long) gotMessage.getParameters().get("chat_id")); - verify(fakeChat, times(1)).save(); - } - - @Test - void shouldThrowErrorIfLocaleNonExists() { - final TgChat fakeChat = mock(TgChat.class); - // Do not set Locale on purpose - when(fakeChat.getId()).thenReturn(1111111L); - - final Update update = - gson.fromJson( - "{\n" - + "\"update_id\":10000,\n" - + "\"message\":{\n" - + " \"date\":1441645532,\n" - + " \"chat\":{\n" - + " \"last_name\":\"Test Lastname\",\n" - + " \"id\":1111111,\n" - + " \"type\": \"private\",\n" - + " \"first_name\":\"Test Firstname\",\n" - + " \"username\":\"Testusername\"\n" - + " },\n" - + " \"message_id\":1365,\n" - + " \"from\":{\n" - + " \"last_name\":\"Test Lastname\",\n" - + " \"id\":1111111,\n" - + " \"first_name\":\"Test Firstname\",\n" - + " \"username\":\"Testusername\"\n" - + " },\n" - + " \"text\":\"/start\"\n" - + "}\n" - + "}", - Update.class); - - final CompletableFuture>> gotFuture = - start.process(fakeChat, update); - assertThrows(ExecutionException.class, gotFuture::get); - } -}