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);
- }
-}