feat: add help message #3
|
@ -0,0 +1,10 @@
|
|||
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
|
||||
--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
|
||||
--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
|
|
@ -36,22 +36,22 @@ curl -F "url=https://example.com/api/tg" \
|
|||
Build is achieved through Maven. To build a `jar` run:
|
||||
|
||||
```shell
|
||||
mvn package -DskipTests=true -Dmaven.test.skip=true -Dmaven.site.skip=true -Dmaven.javadoc.skip=true
|
||||
./mvnw package -DskipTests=true -Dmaven.test.skip=true -Dmaven.site.skip=true -Dmaven.javadoc.skip=true
|
||||
```
|
||||
|
||||
In the `target/` folder wou will find an uber-jar and optionally the possibility to run it via a script and to setup
|
||||
In the `target/` folder you will find an uber-jar and optionally the possibility to run it via a script and to setup
|
||||
auto-startup via systemd or openrc units.
|
||||
|
||||
## Developing
|
||||
|
||||
### Automatic testing
|
||||
|
||||
You can simply run tests with `mvn test`. This will run `UT` and `IT` tests together.
|
||||
You can simply run tests with `./mvnw test`. This will run `UT` and `IT` tests together.
|
||||
|
||||
### Manual testing
|
||||
|
||||
For a manual approach, just open a terminal and type `mvn jooby:run`. Assuming you have a database locally available
|
||||
(check out [application.conf](conf/application.conf)) and a valid Telegram token set (maybe as enviroment variable) you
|
||||
(check out [application.conf](conf/application.conf)) and a valid Telegram token set (maybe as environment variable) you
|
||||
can develop and see live changes of your Mezzotre on the fly. Finally, by using Postman, you can simulate incoming
|
||||
Telegram events.
|
||||
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
db.url = "jdbc:postgresql://localhost:5433/example"
|
||||
db.user = example
|
||||
db.password = example
|
||||
|
||||
hikari.autoCommit = false
|
||||
hikari.maximumPoolSize = 4
|
||||
|
||||
telegram.key = akey
|
||||
|
||||
application.lang = en en-US it it-IT
|
||||
|
|
23
pom.xml
23
pom.xml
|
@ -41,6 +41,8 @@
|
|||
<jsonschema2pojo.version>1.1.1</jsonschema2pojo.version>
|
||||
<jackson-databind.version>2.13.3</jackson-databind.version>
|
||||
<junit-jupiter-params.version>5.9.1</junit-jupiter-params.version>
|
||||
<google-guice.version>5.1.0</google-guice.version>
|
||||
<error-prone.version>2.18.0</error-prone.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@ -86,6 +88,12 @@
|
|||
<artifactId>jooby-guice</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.inject.extensions</groupId>
|
||||
<artifactId>guice-assistedinject</artifactId>
|
||||
<version>${google-guice.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
|
@ -233,7 +241,11 @@
|
|||
<version>31.1-jre</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.errorprone</groupId>
|
||||
<artifactId>error_prone_annotations</artifactId>
|
||||
<version>${error-prone.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -272,12 +284,19 @@
|
|||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.6.2</version>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<compilerArgs>
|
||||
<arg>-parameters</arg>
|
||||
<arg>-XDcompilePolicy=simple</arg>
|
||||
<arg>-Xplugin:ErrorProne</arg>
|
||||
</compilerArgs>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>com.google.errorprone</groupId>
|
||||
<artifactId>error_prone_core</artifactId>
|
||||
<version>${error-prone.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>io.jooby</groupId>
|
||||
<artifactId>jooby-apt</artifactId>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package com.github.polpetta.mezzotre;
|
||||
|
||||
import com.github.polpetta.mezzotre.orm.di.Db;
|
||||
import com.github.polpetta.mezzotre.orm.OrmDI;
|
||||
import com.github.polpetta.mezzotre.route.RouteDI;
|
||||
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.github.polpetta.mezzotre.telegram.callbackquery.CallbackQueryDI;
|
||||
import com.github.polpetta.mezzotre.telegram.command.CommandDI;
|
||||
import com.github.polpetta.mezzotre.util.ServiceModule;
|
||||
import com.github.polpetta.mezzotre.util.UtilDI;
|
||||
import com.google.inject.*;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.name.Names;
|
||||
|
@ -23,11 +24,11 @@ public class App extends Jooby {
|
|||
public static final Function<Jooby, Collection<Module>> DEFAULT_DI_MODULES =
|
||||
(jooby) -> {
|
||||
final HashSet<Module> modules = new HashSet<>();
|
||||
modules.add(new Db());
|
||||
modules.add(new ThreadPool());
|
||||
modules.add(new Route());
|
||||
modules.add(new Command());
|
||||
modules.add(new CallbackQuery());
|
||||
modules.add(new OrmDI());
|
||||
modules.add(new UtilDI());
|
||||
modules.add(new RouteDI());
|
||||
modules.add(new CommandDI());
|
||||
modules.add(new CallbackQueryDI());
|
||||
return modules;
|
||||
};
|
||||
|
||||
|
@ -42,7 +43,7 @@ public class App extends Jooby {
|
|||
if (modules == null || modules.size() == 0) {
|
||||
toInject = DEFAULT_DI_MODULES.apply(this);
|
||||
}
|
||||
toInject.add(new InjectionModule(this));
|
||||
toInject.add(new AppDI(this));
|
||||
toInject.add(new JoobyModule(this));
|
||||
|
||||
final Injector injector = Guice.createInjector(runningEnv, toInject);
|
||||
|
@ -55,6 +56,7 @@ public class App extends Jooby {
|
|||
install(
|
||||
injector.getInstance(Key.get(Extension.class, Names.named("flyWayMigrationExtension"))));
|
||||
install(injector.getInstance(Key.get(Extension.class, Names.named("ebeanExtension"))));
|
||||
install(injector.getInstance(Key.get(ServiceModule.class, Names.named("serviceModule"))));
|
||||
|
||||
decorator(new AccessLogHandler());
|
||||
decorator(new TransactionalRequest());
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package com.github.polpetta.mezzotre;
|
||||
|
||||
import com.github.polpetta.mezzotre.orm.BatchBeanCleanerService;
|
||||
import com.google.common.util.concurrent.Service;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import io.jooby.Jooby;
|
||||
import java.util.List;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class AppDI extends AbstractModule {
|
||||
|
||||
// In the future we can get this name from mvn, by now it is good as it is
|
||||
public static final String APPLICATION_NAME = "Mezzotre";
|
||||
private final Jooby jooby;
|
||||
|
||||
public AppDI(Jooby jooby) {
|
||||
this.jooby = jooby;
|
||||
}
|
||||
|
||||
@Provides
|
||||
public Logger getLogger() {
|
||||
return jooby.getLog();
|
||||
}
|
||||
|
||||
@Named("applicationName")
|
||||
@Singleton
|
||||
@Provides
|
||||
public String getAppName() {
|
||||
return APPLICATION_NAME;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("services")
|
||||
public List<Service> getApplicationServices(BatchBeanCleanerService batchBeanCleanerService) {
|
||||
return List.of(batchBeanCleanerService);
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package com.github.polpetta.mezzotre;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import io.jooby.Jooby;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class InjectionModule extends AbstractModule {
|
||||
private final Jooby jooby;
|
||||
|
||||
public InjectionModule(Jooby jooby) {
|
||||
this.jooby = jooby;
|
||||
}
|
||||
|
||||
@Provides
|
||||
public Logger getLogger() {
|
||||
return jooby.getLog();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package com.github.polpetta.mezzotre.i18n;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
import javax.inject.Inject;
|
||||
import org.apache.velocity.VelocityContext;
|
||||
import org.apache.velocity.tools.ToolManager;
|
||||
import org.apache.velocity.util.StringBuilderWriter;
|
||||
|
||||
/**
|
||||
* This class aims to generate localized messages or strings either merging Velocity templates or
|
||||
* taking directly the keys from {@link java.util.ResourceBundle}
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
*/
|
||||
public class TemplateContentGenerator {
|
||||
|
||||
private final LocalizedMessageFactory localizedMessageFactory;
|
||||
|
||||
@Inject
|
||||
public TemplateContentGenerator(LocalizedMessageFactory localizedMessageFactory) {
|
||||
this.localizedMessageFactory = localizedMessageFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a Velocity template located in the local Jar resource path with the given {@link Locale},
|
||||
* provided as a {@link String}. A {@link Consumer} is provided in order to let the user customize
|
||||
* the {@link VelocityContext} with additional variables. Localization strings will be directly
|
||||
* taken via the use of {@link LocalizedTool} class.
|
||||
*
|
||||
* @param velocityContextConsumer a lambda function that allows for the customization of {@link
|
||||
* VelocityContext} instance
|
||||
* @param localeAsString a {@link Locale} formatted as a {@link String}. Note that {@link
|
||||
* Locale#forLanguageTag(String)} will be used for the conversion
|
||||
* @param templateName the path to the local Jar resource
|
||||
* @return a {@link String} containing the localized message
|
||||
*/
|
||||
public String mergeTemplate(
|
||||
Consumer<VelocityContext> velocityContextConsumer,
|
||||
String localeAsString,
|
||||
String templateName) {
|
||||
final Locale locale = Locale.forLanguageTag(localeAsString);
|
||||
final ToolManager toolManager = localizedMessageFactory.createVelocityToolManager(locale);
|
||||
final VelocityContext velocityContext = new VelocityContext(toolManager.createContext());
|
||||
|
||||
velocityContextConsumer.accept(velocityContext);
|
||||
|
||||
final StringBuilder content = new StringBuilder();
|
||||
final StringBuilderWriter stringBuilderWriter = new StringBuilderWriter(content);
|
||||
|
||||
toolManager
|
||||
.getVelocityEngine()
|
||||
.mergeTemplate(
|
||||
templateName, StandardCharsets.UTF_8.name(), velocityContext, stringBuilderWriter);
|
||||
|
||||
stringBuilderWriter.close();
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a localized {@link String}
|
||||
*
|
||||
* @param locale a provided {@link Locale}
|
||||
* @param key the key that will be retrieved from the localized messages
|
||||
* @return a localized {@link String}
|
||||
* @see #getString(String, String)
|
||||
*/
|
||||
public String getString(Locale locale, String key) {
|
||||
return localizedMessageFactory.createResourceBundle(locale).getString(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a localized {@link String}
|
||||
*
|
||||
* @param localeAsString a provided {@link Locale} as a {@link String}. Will be converted using
|
||||
* {@link Locale#forLanguageTag(String)}
|
||||
* @param key the key that will be retrieved from the localized messages
|
||||
* @return a localized {@link String}
|
||||
* @see #getString(Locale, String)
|
||||
*/
|
||||
public String getString(String localeAsString, String key) {
|
||||
return getString(Locale.forLanguageTag(localeAsString), key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
package com.github.polpetta.mezzotre.orm;
|
||||
|
||||
import com.google.common.util.concurrent.AbstractExecutionThreadService;
|
||||
import io.ebean.typequery.PString;
|
||||
import io.ebean.typequery.TQRootBean;
|
||||
import io.vavr.Tuple;
|
||||
import io.vavr.Tuple3;
|
||||
import io.vavr.control.Try;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* This class works as a "batch" persistence entries remover - basically, it removes all the entries
|
||||
* that are in the database in an async way. This allows better performance on other pieces of
|
||||
* codebase, where we don't really care about checking that the removal has been successful, and
|
||||
* instead we just want to issue a deletion and instantly continue with our codebase flow.
|
||||
*
|
||||
* <p>This service provides the ability to listen to the operation in two ways: via old-fashioned
|
||||
* listeners and via {@link CompletableFuture}. If the caller wants to be sure that the removal has
|
||||
* effectively happened, it can sync with the given {@link CompletableFuture}.
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
*/
|
||||
public class BatchBeanCleanerService extends AbstractExecutionThreadService {
|
||||
|
||||
private final LinkedBlockingDeque<
|
||||
Tuple3<String, PString<? extends TQRootBean<?, ?>>, CompletableFuture<Integer>>>
|
||||
entriesToRemove;
|
||||
private final Logger log;
|
||||
private final Pair<Integer, TimeUnit> serviceRunningCheckTime;
|
||||
|
||||
// Accessing adding listeners while we iterate on the list inside the service thread _can_ be
|
||||
// considered a race condition - but given the frequency of adding and removing listeners, the
|
||||
// computational cost of putting Locks in place and check for them everytime is way greater
|
||||
private final LinkedList<
|
||||
Consumer<Tuple3<String, PString<? extends TQRootBean<?, ?>>, CompletableFuture<Integer>>>>
|
||||
deletionListener;
|
||||
|
||||
@Inject
|
||||
public BatchBeanCleanerService(
|
||||
Logger logger,
|
||||
@Named("serviceRunningCheckTime") Pair<Integer, TimeUnit> serviceRunningCheckTime) {
|
||||
this.log = logger;
|
||||
this.serviceRunningCheckTime = serviceRunningCheckTime;
|
||||
this.entriesToRemove = new LinkedBlockingDeque<>();
|
||||
this.deletionListener = new LinkedList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
log.trace("BatchBeanCleanerService run() method invoked");
|
||||
while (isRunning()) {
|
||||
// This statement is blocking for 1 sec if the queue is empty, and then it goes on - this way
|
||||
// we check if the service is still supposed to be up or what
|
||||
Optional.ofNullable(
|
||||
entriesToRemove.poll(
|
||||
serviceRunningCheckTime.getLeft(), serviceRunningCheckTime.getRight()))
|
||||
.ifPresent(
|
||||
entryToRemove -> {
|
||||
Try.of(
|
||||
() -> {
|
||||
final int deleted = entryToRemove._2().eq(entryToRemove._1()).delete();
|
||||
if (deleted > 0) {
|
||||
log.trace(
|
||||
"Bean with id " + entryToRemove._1() + " removed successfully ");
|
||||
} else {
|
||||
log.warn(
|
||||
"Bean(s) with id "
|
||||
+ entryToRemove._1()
|
||||
+ " for query "
|
||||
+ entryToRemove._2()
|
||||
+ " was not removed from the database because it was not"
|
||||
+ " found");
|
||||
}
|
||||
entryToRemove._3().complete(deleted);
|
||||
deletionListener.parallelStream().forEach(l -> l.accept(entryToRemove));
|
||||
return null;
|
||||
})
|
||||
.onFailure(ex -> entryToRemove._3().completeExceptionally(ex));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown() throws Exception {
|
||||
super.shutDown();
|
||||
entriesToRemove.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entry to be removed
|
||||
*
|
||||
* @param id the id of the entry to remove
|
||||
* @param column the column that will be used to perform the delete statement
|
||||
* @return a {@link CompletableFuture} containing an {@link Integer} indicating the number of rows
|
||||
* affected by the operation
|
||||
*/
|
||||
public CompletableFuture<Integer> removeAsync(
|
||||
String id, PString<? extends TQRootBean<?, ?>> column) {
|
||||
final CompletableFuture<Integer> jobExecution = new CompletableFuture<>();
|
||||
entriesToRemove.offer(Tuple.of(id, column, jobExecution));
|
||||
return jobExecution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener that will be invoked once the entry has been removed. This listener is invoked
|
||||
* after the {@link CompletableFuture} completion. Note that all the listeners are called in a
|
||||
* parallel fashion, so call order can change any time.
|
||||
*
|
||||
* @param listener the listener to add.
|
||||
*/
|
||||
public void addListener(
|
||||
Consumer<Tuple3<String, PString<? extends TQRootBean<?, ?>>, CompletableFuture<Integer>>>
|
||||
listener) {
|
||||
deletionListener.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a listener
|
||||
*
|
||||
* @param listener the listener to be removed
|
||||
*/
|
||||
public void removeListener(
|
||||
Consumer<Tuple3<String, PString<? extends TQRootBean<?, ?>>, CompletableFuture<Integer>>>
|
||||
listener) {
|
||||
deletionListener.remove(listener);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package com.github.polpetta.mezzotre.orm;
|
||||
|
||||
import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||
import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import io.ebean.typequery.PString;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* This class allows the system to lazily remove {@link CallbackQueryContextCleaner} entries that
|
||||
* are no more needed, using the {@link BatchBeanCleanerService} service under the hood.
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @see BatchBeanCleanerService
|
||||
* @since 1.0
|
||||
*/
|
||||
@Singleton
|
||||
public class CallbackQueryContextCleaner {
|
||||
|
||||
private static final Supplier<PString<QCallbackQueryContext>> ENTRY_GROUP =
|
||||
() -> new QCallbackQueryContext().entryGroup;
|
||||
private static final Supplier<PString<QCallbackQueryContext>> SINGLE_ENTRY =
|
||||
() -> new QCallbackQueryContext().id;
|
||||
|
||||
private final BatchBeanCleanerService batchBeanCleanerService;
|
||||
private final Logger log;
|
||||
|
||||
@Inject
|
||||
public CallbackQueryContextCleaner(BatchBeanCleanerService batchBeanCleanerService, Logger log) {
|
||||
this.batchBeanCleanerService = batchBeanCleanerService;
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a group of {@link com.github.polpetta.mezzotre.orm.model.CallbackQueryContext} to be
|
||||
* removed
|
||||
*
|
||||
* @param id the group id of the {@link
|
||||
* com.github.polpetta.mezzotre.orm.model.CallbackQueryContext}
|
||||
* @return a {@link CompletableFuture} with an {@link Integer} indicating how many entries have
|
||||
* been deleted from the persistence layer
|
||||
* @see CallbackQueryContext#getEntryGroup()
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public CompletableFuture<Integer> removeGroupAsync(String id) {
|
||||
log.trace("CallbackQueryContext entry group " + id + " queued for removal");
|
||||
return batchBeanCleanerService.removeAsync(id, ENTRY_GROUP.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single {@link com.github.polpetta.mezzotre.orm.model.CallbackQueryContext} to be removed
|
||||
*
|
||||
* @param id the id of the {@link CallbackQueryContext} to remove
|
||||
* @return a {@link CompletableFuture} with an {@link Integer} indicating how many entries have
|
||||
* been deleted from the persistence layer. Can be 0 or 1.
|
||||
* @see CallbackQueryContext#getId()
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public CompletableFuture<Integer> removeIdAsync(String id) {
|
||||
log.trace("CallbackQueryContext single entity " + id + " queued for removal");
|
||||
return batchBeanCleanerService.removeAsync(id, SINGLE_ENTRY.get());
|
||||
}
|
||||
}
|
|
@ -1,16 +1,16 @@
|
|||
package com.github.polpetta.mezzotre.orm.di;
|
||||
package com.github.polpetta.mezzotre.orm;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.Singleton;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import io.jooby.Extension;
|
||||
import io.jooby.ebean.EbeanModule;
|
||||
import io.jooby.flyway.FlywayModule;
|
||||
import io.jooby.hikari.HikariModule;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
public class Db extends AbstractModule {
|
||||
public class OrmDI extends AbstractModule {
|
||||
/**
|
||||
* Returns null. This allows to fetch the configuration from file rather than fetch from other
|
||||
* environment
|
|
@ -0,0 +1,83 @@
|
|||
package com.github.polpetta.mezzotre.orm.telegram;
|
||||
|
||||
import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||
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.types.json.ChatContext;
|
||||
import com.pengrad.telegrambot.model.Message;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* ChatUtil provides utilities for interacting in an easier way when manipulating chat related data.
|
||||
* In particular, it provides an easy and DRY way to modify a set of frequently-accessed fields, or
|
||||
* simply to extract data performing all the necessary steps
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
*/
|
||||
public class ChatUtil {
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
@Inject
|
||||
public ChatUtil(Clock clock) {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the {@link ChatContext} with the values passed to the method. The updated values are
|
||||
* then saved in the database
|
||||
*
|
||||
* @param chat the chat that will be updated with the new {@link ChatContext} values
|
||||
* @param stepName the step name to set
|
||||
* @param stageNumber the stage number to set
|
||||
* @param additionalFields if there are, additional custom fields that will be added to {@link
|
||||
* ChatContext}. Note that these values will have to be manually retrieved since no method is
|
||||
* available for custom entries. Use {@link Collections#emptyMap()} if you don't wish to add
|
||||
* any additional field
|
||||
*/
|
||||
public void updateChatContext(
|
||||
TgChat chat, String stepName, int stageNumber, Map<String, Object> additionalFields) {
|
||||
final ChatContext chatContext = chat.getChatContext();
|
||||
chatContext.setStage(stepName);
|
||||
chatContext.setStep(stageNumber);
|
||||
chatContext.setPreviousMessageUnixTimestampInSeconds(clock.now());
|
||||
additionalFields.forEach(chatContext::setAdditionalProperty);
|
||||
|
||||
chat.setChatContext(chatContext);
|
||||
chat.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a possible {@link TgChat} from the given {@link CallbackQueryContext} or the {@link
|
||||
* Update}. Note that first the {@link CallbackQueryContext} is checked, then {@link Update} is
|
||||
* used as fallback. Once the Telegram chat id is retrieved from one of these two, the persistence
|
||||
* layer is queried and if there is any {@link TgChat} which the corresponding ID it is returned
|
||||
* to the caller.
|
||||
*
|
||||
* @param callbackQueryContext the {@link CallbackQueryContext} coming from a Telegram callback
|
||||
* query
|
||||
* @param update the whole {@link Update} object that is sent from Telegram servers
|
||||
* @return an {@link Optional} that may contain a {@link TgChat} if the ID is found in the
|
||||
* persistence layer, otherwise {@link Optional#empty()} is given back either if the id is not
|
||||
* present or if the chat is not present in the persistence layer.
|
||||
*/
|
||||
public Optional<TgChat> extractChat(CallbackQueryContext callbackQueryContext, Update update) {
|
||||
return Optional.of(callbackQueryContext.getFields().getTelegramChatId())
|
||||
.map(Double::longValue)
|
||||
// If we're desperate, search in the message for the chat id
|
||||
.filter(chatId -> chatId != 0L && chatId != Long.MIN_VALUE)
|
||||
.or(
|
||||
() ->
|
||||
Optional.ofNullable(update.callbackQuery().message())
|
||||
.map(Message::messageId)
|
||||
.map(Long::valueOf))
|
||||
.filter(chatId -> chatId != 0L && chatId != Long.MIN_VALUE)
|
||||
.flatMap(chatId -> new QTgChat().id.eq(chatId).findOneOrEmpty());
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.github.polpetta.mezzotre.route.di;
|
||||
package com.github.polpetta.mezzotre.route;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
|
@ -7,7 +7,7 @@ import io.jooby.Jooby;
|
|||
import java.util.Optional;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
public class Route extends AbstractModule {
|
||||
public class RouteDI extends AbstractModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
public TelegramBot getTelegramBot(Jooby jooby) {
|
|
@ -0,0 +1,33 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.assistedinject.FactoryModuleBuilder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
public class CallbackQueryDI extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
super.configure();
|
||||
|
||||
install(
|
||||
new FactoryModuleBuilder()
|
||||
.implement(Processor.class, NotFound.class)
|
||||
.build(NotFoundFactory.class));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("eventProcessors")
|
||||
public Map<String, Processor> getEventProcessor(
|
||||
SelectLanguageTutorial selectLanguageTutorial, ShowHelp showHelp) {
|
||||
final HashMap<String, Processor> commandMap = new HashMap<>();
|
||||
commandMap.put(selectLanguageTutorial.getEventName(), selectLanguageTutorial);
|
||||
commandMap.put(showHelp.getEventName(), showHelp);
|
||||
return commandMap;
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@ package com.github.polpetta.mezzotre.telegram.callbackquery;
|
|||
import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.inject.Inject;
|
||||
|
@ -22,15 +22,18 @@ import javax.inject.Singleton;
|
|||
@Singleton
|
||||
public class Dispatcher {
|
||||
|
||||
private final Set<Processor> tgEventProcessors;
|
||||
private final Map<String, Processor> tgEventProcessors;
|
||||
private final Executor threadPool;
|
||||
private final NotFoundFactory notFoundFactory;
|
||||
|
||||
@Inject
|
||||
public Dispatcher(
|
||||
@Named("eventProcessors") Set<Processor> tgEventProcessors,
|
||||
@Named("eventThreadPool") Executor threadPool) {
|
||||
@Named("eventProcessors") Map<String, Processor> tgEventProcessors,
|
||||
@Named("eventThreadPool") Executor threadPool,
|
||||
NotFoundFactory notFoundFactory) {
|
||||
this.tgEventProcessors = tgEventProcessors;
|
||||
this.threadPool = threadPool;
|
||||
this.notFoundFactory = notFoundFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -50,14 +53,11 @@ public class Dispatcher {
|
|||
.thenComposeAsync(
|
||||
ignored ->
|
||||
Optional.of(callbackQueryContext.getFields().getEvent())
|
||||
.flatMap(
|
||||
.map(
|
||||
eventName ->
|
||||
tgEventProcessors.stream()
|
||||
// FIXME this is fucking stupid, why iterate over, just use a map!
|
||||
// Make mapping at startup then we're gucci for the rest of the run
|
||||
.filter(processor -> processor.getEventName().equals(eventName))
|
||||
.findAny())
|
||||
.map(processor -> processor.process(callbackQueryContext, update))
|
||||
tgEventProcessors
|
||||
.getOrDefault(eventName, notFoundFactory.create(eventName))
|
||||
.process(callbackQueryContext, update))
|
||||
.orElse(CompletableFuture.failedFuture(new EventProcessorNotFoundException())),
|
||||
threadPool);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
||||
|
||||
/**
|
||||
* This interface is a placeholder to keep all fields in one place
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
*/
|
||||
public interface Field {
|
||||
/**
|
||||
* Additional fields that are related to {@code changeLanguage} event
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
*/
|
||||
enum SelectLanguageTutorial {
|
||||
NewLanguage("newLanguage");
|
||||
|
||||
private final String name;
|
||||
|
||||
SelectLanguageTutorial(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerator with custom fields for {@link
|
||||
* com.github.polpetta.mezzotre.telegram.callbackquery.ShowHelp} callback query
|
||||
*/
|
||||
enum ShowHelp {
|
||||
InvokedFromHelpMessage("invokedFromHelpMessage");
|
||||
|
||||
private final String name;
|
||||
|
||||
ShowHelp(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
||||
|
||||
import com.github.polpetta.mezzotre.orm.CallbackQueryContextCleaner;
|
||||
import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import javax.inject.Inject;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* Callback query event that is triggered only when a corresponding event is not found in the system
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
*/
|
||||
public class NotFound implements Processor {
|
||||
|
||||
private final Logger log;
|
||||
private final CallbackQueryContextCleaner callbackQueryContextCleaner;
|
||||
private final String eventName;
|
||||
|
||||
@Inject
|
||||
public NotFound(
|
||||
Logger logger,
|
||||
CallbackQueryContextCleaner callbackQueryContextCleaner,
|
||||
@Assisted String eventName) {
|
||||
this.log = logger;
|
||||
this.callbackQueryContextCleaner = callbackQueryContextCleaner;
|
||||
this.eventName = eventName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return "eventNotFound";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
CallbackQueryContext callbackQueryContext, Update update) {
|
||||
log.warn(
|
||||
"A stray event was detected for callback "
|
||||
+ callbackQueryContext.getId()
|
||||
+ " event name "
|
||||
+ eventName);
|
||||
callbackQueryContextCleaner.removeGroupAsync(callbackQueryContext.getEntryGroup());
|
||||
return CompletableFuture.completedFuture(Optional.empty());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
||||
|
||||
public interface NotFoundFactory {
|
||||
NotFound create(String eventName);
|
||||
}
|
|
@ -17,12 +17,31 @@ import java.util.concurrent.CompletableFuture;
|
|||
public interface Processor {
|
||||
|
||||
/**
|
||||
* The even name this processor is able to process
|
||||
* The event name this processor is able to process
|
||||
*
|
||||
* @return a {@link String} containig the name of the event supported
|
||||
* @return a {@link String} containing the name of the event supported
|
||||
*/
|
||||
String getEventName();
|
||||
|
||||
/**
|
||||
* Determines if the Callback Query can directly be invoked by the user, such as starting an
|
||||
* interaction from this point or invoking it from an inline query button
|
||||
*
|
||||
* @return true if it is, false otherwise
|
||||
*/
|
||||
default boolean canBeDirectlyInvokedByTheUser() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives the ability to print a localized version of the event, in a pretty way
|
||||
*
|
||||
* @return the key for the corresponding localization string
|
||||
*/
|
||||
default Optional<String> getPrettyPrintLocaleKeyName() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the current event
|
||||
*
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
||||
|
||||
import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory;
|
||||
import com.github.polpetta.mezzotre.i18n.TemplateContentGenerator;
|
||||
import com.github.polpetta.mezzotre.orm.CallbackQueryContextCleaner;
|
||||
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.github.polpetta.mezzotre.orm.telegram.ChatUtil;
|
||||
import com.github.polpetta.mezzotre.util.UUIDGenerator;
|
||||
import com.github.polpetta.types.json.CallbackQueryMetadata;
|
||||
import com.pengrad.telegrambot.model.Message;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardButton;
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup;
|
||||
|
@ -14,9 +13,6 @@ import com.pengrad.telegrambot.model.request.ParseMode;
|
|||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import com.pengrad.telegrambot.request.EditMessageText;
|
||||
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;
|
||||
|
@ -24,9 +20,6 @@ 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;
|
||||
|
||||
/**
|
||||
|
@ -41,61 +34,26 @@ public class SelectLanguageTutorial implements Processor {
|
|||
|
||||
public static final String EVENT_NAME = "selectLanguageTutorial";
|
||||
private final Executor threadPool;
|
||||
private final LocalizedMessageFactory localizedMessageFactory;
|
||||
private final TemplateContentGenerator templateContentGenerator;
|
||||
private final Logger log;
|
||||
private final UUIDGenerator uuidGenerator;
|
||||
|
||||
/**
|
||||
* Additional fields that are related to {@code changeLanguage} event
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
*/
|
||||
public enum Field {
|
||||
NewLanguage("newLanguage");
|
||||
|
||||
private final String name;
|
||||
|
||||
Field(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible values for the additional {@link Field} of {@code changeLanguage} event
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
*/
|
||||
public enum Language {
|
||||
English("en-US"),
|
||||
Italian("it-IT");
|
||||
|
||||
private final String locale;
|
||||
|
||||
Language(String locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public String getLocale() {
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
private final ChatUtil chatUtil;
|
||||
private final CallbackQueryContextCleaner callbackQueryContextCleaner;
|
||||
|
||||
@Inject
|
||||
public SelectLanguageTutorial(
|
||||
@Named("eventThreadPool") Executor threadPool,
|
||||
LocalizedMessageFactory localizedMessageFactory,
|
||||
TemplateContentGenerator templateContentGenerator,
|
||||
Logger log,
|
||||
UUIDGenerator uuidGenerator) {
|
||||
UUIDGenerator uuidGenerator,
|
||||
ChatUtil chatUtil,
|
||||
CallbackQueryContextCleaner callbackQueryContextCleaner) {
|
||||
this.threadPool = threadPool;
|
||||
this.localizedMessageFactory = localizedMessageFactory;
|
||||
this.templateContentGenerator = templateContentGenerator;
|
||||
this.log = log;
|
||||
this.uuidGenerator = uuidGenerator;
|
||||
this.chatUtil = chatUtil;
|
||||
this.callbackQueryContextCleaner = callbackQueryContextCleaner;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -108,16 +66,8 @@ public class SelectLanguageTutorial implements Processor {
|
|||
CallbackQueryContext callbackQueryContext, Update update) {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() ->
|
||||
Optional.of(callbackQueryContext.getFields().getTelegramChatId())
|
||||
.map(Double::longValue)
|
||||
// If we're desperate, search in the message for the chat id
|
||||
.or(
|
||||
() ->
|
||||
Optional.ofNullable(update.callbackQuery().message())
|
||||
.map(Message::messageId)
|
||||
.map(Long::valueOf))
|
||||
.filter(chatId -> chatId != 0L && chatId != Long.MIN_VALUE)
|
||||
.flatMap(chatId -> new QTgChat().id.eq(chatId).findOneOrEmpty())
|
||||
chatUtil
|
||||
.extractChat(callbackQueryContext, update)
|
||||
.map(
|
||||
tgChat -> {
|
||||
tgChat.setLocale(
|
||||
|
@ -126,7 +76,8 @@ public class SelectLanguageTutorial implements Processor {
|
|||
.getFields()
|
||||
.getAdditionalProperties()
|
||||
.getOrDefault(
|
||||
Field.NewLanguage.getName(), tgChat.getLocale()));
|
||||
Field.SelectLanguageTutorial.NewLanguage.getName(),
|
||||
tgChat.getLocale()));
|
||||
tgChat.save();
|
||||
log.trace(
|
||||
"Locale for chat "
|
||||
|
@ -147,55 +98,34 @@ public class SelectLanguageTutorial implements Processor {
|
|||
.thenApplyAsync(
|
||||
// If we are here then we're sure there is at least a chat associated with this callback
|
||||
tgChat -> {
|
||||
if (tgChat.getHasHelpBeenShown()) {
|
||||
log.trace(
|
||||
"Help message has already been shown for this user - no help button will be"
|
||||
+ " present");
|
||||
} else {
|
||||
log.trace("No help message shown yet - the help button will be added");
|
||||
}
|
||||
|
||||
final String message =
|
||||
Try.of(
|
||||
() -> {
|
||||
final Locale locale = Locale.forLanguageTag(tgChat.getLocale());
|
||||
final ToolManager toolManager =
|
||||
localizedMessageFactory.createVelocityToolManager(locale);
|
||||
final VelocityContext velocityContext =
|
||||
new VelocityContext(toolManager.createContext());
|
||||
|
||||
velocityContext.put("hasHelpBeenShown", tgChat.getHasHelpBeenShown());
|
||||
|
||||
final StringBuilder content = new StringBuilder();
|
||||
final StringBuilderWriter stringBuilderWriter =
|
||||
new StringBuilderWriter(content);
|
||||
|
||||
toolManager
|
||||
.getVelocityEngine()
|
||||
.mergeTemplate(
|
||||
"/template/callbackQuery/selectLanguageTutorial.vm",
|
||||
StandardCharsets.UTF_8.name(),
|
||||
velocityContext,
|
||||
stringBuilderWriter);
|
||||
|
||||
stringBuilderWriter.close();
|
||||
return content.toString();
|
||||
})
|
||||
.get();
|
||||
templateContentGenerator.mergeTemplate(
|
||||
velocityContext ->
|
||||
velocityContext.put("hasHelpBeenShown", tgChat.getHasHelpBeenShown()),
|
||||
tgChat.getLocale(),
|
||||
"/template/telegram/selectLanguageTutorial.vm");
|
||||
|
||||
log.trace("SelectLanguageTutorial 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);
|
||||
callbackQueryContextCleaner.removeGroupAsync(callbackQueryContext.getEntryGroup());
|
||||
|
||||
final Optional<Integer> messageId =
|
||||
Optional.ofNullable(update.callbackQuery().message()).map(Message::messageId);
|
||||
final Optional<Integer> messageId = Util.extractMessageId(update);
|
||||
BaseRequest<?, ?> baseRequest;
|
||||
Optional<InlineKeyboardMarkup> helpButton = Optional.empty();
|
||||
if (!tgChat.getHasHelpBeenShown()) {
|
||||
// Add a button to show all the possible commands
|
||||
final String showMeTutorialString =
|
||||
localizedMessageFactory
|
||||
.createResourceBundle(Locale.forLanguageTag(tgChat.getLocale()))
|
||||
.getString("button.showMeTutorial");
|
||||
templateContentGenerator.getString(
|
||||
tgChat.getLocale(),
|
||||
"selectLanguageTutorial.showMeTutorialInlineKeyboardButtonName");
|
||||
|
||||
final CallbackQueryMetadata callbackQueryMetadata =
|
||||
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
||||
|
|
|
@ -1,15 +1,66 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
||||
|
||||
import com.github.polpetta.mezzotre.orm.CallbackQueryContextCleaner;
|
||||
import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||
import com.github.polpetta.mezzotre.orm.telegram.ChatUtil;
|
||||
import com.github.polpetta.mezzotre.telegram.model.Help;
|
||||
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.EditMessageText;
|
||||
import com.pengrad.telegrambot.request.SendMessage;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
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;
|
||||
|
||||
public class ShowHelp implements Processor {
|
||||
/**
|
||||
* ShowHelp callback query event edits a previous message (if available), otherwise it sends a new
|
||||
* message with a list of available commands (the one implemented by {@link
|
||||
* com.github.polpetta.mezzotre.telegram.command.Processor}), and a list of buttons that are
|
||||
* callback queries (the ones implemented by {@link Processor}). The class is a Singleton since it
|
||||
* is stateless and having multiple instances would only be a waste of memory.
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
* @see com.github.polpetta.mezzotre.telegram.command.Help same functionality but as {@link
|
||||
* com.github.polpetta.mezzotre.telegram.command.Processor} command
|
||||
*/
|
||||
@Singleton
|
||||
class ShowHelp implements Processor {
|
||||
|
||||
public static String EVENT_NAME = "showHelp";
|
||||
private final Executor threadPool;
|
||||
private final Help modelHelp;
|
||||
private final Map<String, com.github.polpetta.mezzotre.telegram.command.Processor>
|
||||
tgCommandProcessors;
|
||||
private final Map<String, Processor> eventProcessor;
|
||||
private final ChatUtil chatUtil;
|
||||
private final CallbackQueryContextCleaner callbackQueryContextCleaner;
|
||||
|
||||
@Inject
|
||||
public ShowHelp(
|
||||
@Named("eventThreadPool") Executor threadPool,
|
||||
com.github.polpetta.mezzotre.telegram.model.Help modelHelp,
|
||||
@Named("commandProcessor")
|
||||
Map<String, com.github.polpetta.mezzotre.telegram.command.Processor> tgCommandProcessors,
|
||||
@Named("eventProcessors")
|
||||
Map<String, com.github.polpetta.mezzotre.telegram.callbackquery.Processor> eventProcessor,
|
||||
ChatUtil chatUtil,
|
||||
CallbackQueryContextCleaner callbackQueryContextCleaner) {
|
||||
this.threadPool = threadPool;
|
||||
this.modelHelp = modelHelp;
|
||||
this.tgCommandProcessors = tgCommandProcessors;
|
||||
this.eventProcessor = eventProcessor;
|
||||
this.chatUtil = chatUtil;
|
||||
this.callbackQueryContextCleaner = callbackQueryContextCleaner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
|
@ -19,8 +70,44 @@ public class ShowHelp implements Processor {
|
|||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
CallbackQueryContext callbackQueryContext, Update update) {
|
||||
// TODO implement this method and put `hasHelpBeenShown` in tgChat to false
|
||||
return CompletableFuture.completedFuture(
|
||||
Optional.of(new SendMessage(callbackQueryContext.getFields().getTelegramChatId(), "TODO")));
|
||||
return CompletableFuture.supplyAsync(
|
||||
() ->
|
||||
chatUtil
|
||||
.extractChat(callbackQueryContext, update)
|
||||
.map(
|
||||
chat -> {
|
||||
final String message = modelHelp.getMessage(chat, tgCommandProcessors);
|
||||
chatUtil.updateChatContext(chat, EVENT_NAME, 0, Collections.emptyMap());
|
||||
chat.setHasHelpBeenShown(true);
|
||||
chat.save();
|
||||
final Optional<Integer> messageId = Util.extractMessageId(update);
|
||||
final InlineKeyboardButton[] buttons =
|
||||
modelHelp.generateInlineKeyBoardButton(chat, eventProcessor);
|
||||
BaseRequest<?, ?> request;
|
||||
if (messageId.isPresent()) {
|
||||
final EditMessageText editMessageText =
|
||||
new EditMessageText(chat.getId(), messageId.get(), message)
|
||||
.parseMode(ParseMode.Markdown);
|
||||
if (buttons.length > 0) {
|
||||
editMessageText.replyMarkup(new InlineKeyboardMarkup(buttons));
|
||||
}
|
||||
request = editMessageText;
|
||||
} else {
|
||||
final SendMessage sendMessage =
|
||||
new SendMessage(chat.getId(), message).parseMode(ParseMode.Markdown);
|
||||
if (buttons.length > 0) {
|
||||
sendMessage.replyMarkup(new InlineKeyboardMarkup(buttons));
|
||||
}
|
||||
request = sendMessage;
|
||||
}
|
||||
|
||||
// We don't check if the element is removed or what - we just schedule its
|
||||
// removal, then it is someone else problem
|
||||
callbackQueryContextCleaner.removeGroupAsync(
|
||||
callbackQueryContext.getEntryGroup());
|
||||
|
||||
return request;
|
||||
}),
|
||||
threadPool);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
||||
|
||||
import com.pengrad.telegrambot.model.Message;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A class with misc utilities
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
*/
|
||||
public class Util {
|
||||
|
||||
/**
|
||||
* Extract the message id of the given {@link Update}
|
||||
*
|
||||
* @param update the {@link Update} to check and search the message id for
|
||||
* @return an {@link Optional} containing a {@link Integer} with the message id if it is present,
|
||||
* otherwise a {@link Optional#empty()} if it is not found.
|
||||
*/
|
||||
public static Optional<Integer> extractMessageId(Update update) {
|
||||
return Optional.ofNullable(update.callbackQuery().message()).map(Message::messageId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
||||
|
||||
public interface Value {
|
||||
/**
|
||||
* Possible values for the additional {@link Field.SelectLanguageTutorial} of {@code
|
||||
* changeLanguage} event
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
*/
|
||||
enum SelectLanguageTutorial {
|
||||
English("en-US"),
|
||||
Italian("it-IT");
|
||||
|
||||
private final String locale;
|
||||
|
||||
SelectLanguageTutorial(String locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public String getLocale() {
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery.di;
|
||||
|
||||
import com.github.polpetta.mezzotre.telegram.callbackquery.Processor;
|
||||
import com.github.polpetta.mezzotre.telegram.callbackquery.SelectLanguageTutorial;
|
||||
import com.github.polpetta.mezzotre.telegram.callbackquery.ShowHelp;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import java.util.Set;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
public class CallbackQuery extends AbstractModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("eventProcessors")
|
||||
public Set<Processor> getEventProcessor(
|
||||
SelectLanguageTutorial selectLanguageTutorial, ShowHelp showHelp) {
|
||||
return Set.of(selectLanguageTutorial, showHelp);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.assistedinject.FactoryModuleBuilder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.velocity.app.VelocityEngine;
|
||||
import org.apache.velocity.runtime.RuntimeConstants;
|
||||
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
|
||||
|
||||
public class CommandDI extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
super.configure();
|
||||
|
||||
install(
|
||||
new FactoryModuleBuilder()
|
||||
.implement(Processor.class, NotFound.class)
|
||||
.build(NotFoundFactory.class));
|
||||
}
|
||||
|
||||
private static Map<String, Processor> mapForProcessor(Processor processor) {
|
||||
final HashMap<String, Processor> commandMap = new HashMap<>();
|
||||
processor
|
||||
.getTriggerKeywords()
|
||||
.forEach(
|
||||
keyword -> {
|
||||
commandMap.put(keyword, processor);
|
||||
});
|
||||
return commandMap;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("commandProcessor")
|
||||
public Map<String, Processor> getCommandProcessor(Start start, Help help) {
|
||||
final HashMap<String, Processor> commandMap = new HashMap<>();
|
||||
commandMap.putAll(mapForProcessor(start));
|
||||
commandMap.putAll(mapForProcessor(help));
|
||||
|
||||
return commandMap;
|
||||
}
|
||||
|
||||
@Provides
|
||||
public VelocityEngine getVelocityEngine() {
|
||||
final VelocityEngine velocityEngine = new VelocityEngine();
|
||||
velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
|
||||
velocityEngine.setProperty(
|
||||
"classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
|
||||
velocityEngine.init();
|
||||
return velocityEngine;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command;
|
||||
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.github.polpetta.mezzotre.orm.telegram.ChatUtil;
|
||||
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;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* The Help command class allows the user to be informed in any moment of commands that are
|
||||
* available in the system. If possible, it also provides a {@code reply_markup} keyboard ({@link
|
||||
* InlineKeyboardMarkup} for easier bot interactions
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
* @see com.github.polpetta.mezzotre.telegram.callbackquery.ShowHelp for event-based help
|
||||
*/
|
||||
@Singleton
|
||||
class Help implements Processor {
|
||||
|
||||
private static final String TRIGGERING_STAGING_NAME = "/help";
|
||||
|
||||
private final Executor threadPool;
|
||||
private final Map<String, Processor> tgCommandProcessors;
|
||||
private final Map<String, com.github.polpetta.mezzotre.telegram.callbackquery.Processor>
|
||||
eventProcessor;
|
||||
private final com.github.polpetta.mezzotre.telegram.model.Help modelHelp;
|
||||
private final ChatUtil chatUtil;
|
||||
|
||||
@Inject
|
||||
public Help(
|
||||
@Named("eventThreadPool") Executor threadPool,
|
||||
@Named("commandProcessor") Map<String, Processor> tgCommandProcessors,
|
||||
@Named("eventProcessors")
|
||||
Map<String, com.github.polpetta.mezzotre.telegram.callbackquery.Processor> eventProcessor,
|
||||
com.github.polpetta.mezzotre.telegram.model.Help modelHelp,
|
||||
ChatUtil chatUtil) {
|
||||
this.threadPool = threadPool;
|
||||
this.tgCommandProcessors = tgCommandProcessors;
|
||||
this.eventProcessor = eventProcessor;
|
||||
this.modelHelp = modelHelp;
|
||||
this.chatUtil = chatUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return Set.of(TRIGGERING_STAGING_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(TgChat chat, Update update) {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
final String message = modelHelp.getMessage(chat, tgCommandProcessors);
|
||||
chatUtil.updateChatContext(chat, TRIGGERING_STAGING_NAME, 0, Collections.emptyMap());
|
||||
chat.setHasHelpBeenShown(true);
|
||||
chat.save();
|
||||
|
||||
final SendMessage sendMessage =
|
||||
new SendMessage(chat.getId(), message).parseMode(ParseMode.Markdown);
|
||||
|
||||
final InlineKeyboardButton[] buttons =
|
||||
modelHelp.generateInlineKeyBoardButton(chat, eventProcessor);
|
||||
|
||||
if (buttons.length > 0) {
|
||||
sendMessage.replyMarkup(new InlineKeyboardMarkup(buttons));
|
||||
}
|
||||
|
||||
return Optional.of(sendMessage);
|
||||
},
|
||||
threadPool);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command;
|
||||
|
||||
import com.github.polpetta.mezzotre.i18n.TemplateContentGenerator;
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import com.pengrad.telegrambot.request.SendMessage;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
/**
|
||||
* Generates a "Command not found" message
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
* @see com.github.polpetta.mezzotre.telegram.callbackquery.NotFound for the event-based version
|
||||
*/
|
||||
class NotFound implements Processor {
|
||||
private static final String CMD_CTX_NAME = "cmdName";
|
||||
|
||||
private final String commandName;
|
||||
private final TemplateContentGenerator templateContentGenerator;
|
||||
private final Executor threadPool;
|
||||
|
||||
@Inject
|
||||
public NotFound(
|
||||
@Assisted String commandName,
|
||||
TemplateContentGenerator templateContentGenerator,
|
||||
@Named("eventThreadPool") Executor threadPool) {
|
||||
this.commandName = commandName;
|
||||
this.templateContentGenerator = templateContentGenerator;
|
||||
this.threadPool = threadPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(TgChat chat, Update update) {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() ->
|
||||
templateContentGenerator.mergeTemplate(
|
||||
ctx -> ctx.put(CMD_CTX_NAME, commandName),
|
||||
chat.getLocale(),
|
||||
"template/telegram/notFound.vm"),
|
||||
threadPool)
|
||||
.thenApply(text -> Optional.of(new SendMessage(chat.getId(), text)));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command;
|
||||
|
||||
interface NotFoundFactory {
|
||||
NotFound create(String commandName);
|
||||
}
|
|
@ -4,6 +4,7 @@ import com.github.polpetta.mezzotre.orm.model.TgChat;
|
|||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
|
@ -16,12 +17,12 @@ import java.util.concurrent.CompletableFuture;
|
|||
public interface Processor {
|
||||
|
||||
/**
|
||||
* Provides the keyword to trigger this executor. Note that it must start with "/" at the
|
||||
* Provides the keywords to trigger this executor. Note that it must start with "/" at the
|
||||
* beginning, e.g. {@code /start}.
|
||||
*
|
||||
* @return a {@link String} providing the keyword to trigger the current {@link Processor}
|
||||
* @return a {@link Set} providing the keywords to trigger the current {@link Processor}
|
||||
*/
|
||||
String getTriggerKeyword();
|
||||
Set<String> getTriggerKeywords();
|
||||
|
||||
/**
|
||||
* Process the current update
|
||||
|
@ -31,4 +32,17 @@ public interface Processor {
|
|||
* @return a {@link CompletableFuture} with the result of the computation
|
||||
*/
|
||||
CompletableFuture<Optional<BaseRequest<?, ?>>> process(TgChat chat, Update update);
|
||||
|
||||
/**
|
||||
* Provide the key to retrieve the current processor descriptor. This is useful for help messages
|
||||
* or to provide a user with a description of what this command does. Whilst a default
|
||||
* implementation is provided, we suggest to rename it accordingly since the name generation is
|
||||
* based on reflection and can be fragile and subject to code refactor changes.
|
||||
*
|
||||
* @return a {@link String} with the name of the localization key containing the command
|
||||
* description
|
||||
*/
|
||||
default String getLocaleDescriptionKeyword() {
|
||||
return this.getClass().getSimpleName().toLowerCase() + ".cmdDescription";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import com.google.inject.Inject;
|
|||
import com.google.inject.Singleton;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.inject.Named;
|
||||
|
@ -21,15 +21,18 @@ import javax.inject.Named;
|
|||
@Singleton
|
||||
public class Router {
|
||||
|
||||
private final Set<Processor> tgCommandProcessors;
|
||||
private final Map<String, Processor> tgCommandProcessors;
|
||||
private final Executor threadPool;
|
||||
private final NotFoundFactory notFoundFactory;
|
||||
|
||||
@Inject
|
||||
public Router(
|
||||
@Named("commandProcessor") Set<Processor> tgCommandProcessors,
|
||||
@Named("eventThreadPool") Executor threadPool) {
|
||||
@Named("commandProcessor") Map<String, Processor> tgCommandProcessors,
|
||||
@Named("eventThreadPool") Executor threadPool,
|
||||
NotFoundFactory notFoundFactory) {
|
||||
this.tgCommandProcessors = tgCommandProcessors;
|
||||
this.threadPool = threadPool;
|
||||
this.notFoundFactory = notFoundFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,15 +61,13 @@ public class Router {
|
|||
.map(list -> list[0])
|
||||
.filter(wannabeCommand -> wannabeCommand.startsWith("/"))
|
||||
.or(() -> Optional.ofNullable(chat.getChatContext().getStage()))
|
||||
.flatMap(
|
||||
.map(
|
||||
command ->
|
||||
tgCommandProcessors.stream()
|
||||
// FIXME this is fucking stupid, why iterate over, just use a map!
|
||||
// Make mapping at startup then we're gucci for the rest of the run
|
||||
.filter(ex -> ex.getTriggerKeyword().equals(command))
|
||||
.findAny())
|
||||
.map(executor -> executor.process(chat, update))
|
||||
.orElse(CompletableFuture.failedFuture(new CommandNotFoundException())),
|
||||
tgCommandProcessors
|
||||
.getOrDefault(command, notFoundFactory.create(command))
|
||||
.process(chat, update))
|
||||
// This should never happen
|
||||
.orElse(CompletableFuture.failedFuture(new IllegalStateException())),
|
||||
threadPool);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command;
|
||||
|
||||
import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory;
|
||||
import com.github.polpetta.mezzotre.i18n.TemplateContentGenerator;
|
||||
import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.github.polpetta.mezzotre.telegram.callbackquery.SelectLanguageTutorial;
|
||||
import com.github.polpetta.mezzotre.util.Clock;
|
||||
import com.github.polpetta.mezzotre.orm.telegram.ChatUtil;
|
||||
import com.github.polpetta.mezzotre.telegram.callbackquery.Field;
|
||||
import com.github.polpetta.mezzotre.telegram.callbackquery.Value;
|
||||
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;
|
||||
|
@ -15,17 +15,14 @@ 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;
|
||||
import io.vavr.control.Try;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.apache.velocity.VelocityContext;
|
||||
import org.apache.velocity.tools.ToolManager;
|
||||
import org.apache.velocity.util.StringBuilderWriter;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
|
@ -35,31 +32,37 @@ import org.slf4j.Logger;
|
|||
* @since 1.0
|
||||
*/
|
||||
@Singleton
|
||||
public class Start implements Processor {
|
||||
class Start implements Processor {
|
||||
|
||||
private static final String TRIGGERING_STAGING_NAME = "/start";
|
||||
|
||||
private final Executor threadPool;
|
||||
private final Logger log;
|
||||
private final UUIDGenerator uuidGenerator;
|
||||
private final Clock clock;
|
||||
private final LocalizedMessageFactory localizedMessageFactory;
|
||||
private final String applicationName;
|
||||
private final ChatUtil chatUtil;
|
||||
|
||||
private final TemplateContentGenerator templateContentGenerator;
|
||||
|
||||
@Inject
|
||||
public Start(
|
||||
LocalizedMessageFactory localizedMessageFactory,
|
||||
TemplateContentGenerator templateContentGenerator,
|
||||
@Named("eventThreadPool") Executor threadPool,
|
||||
Logger log,
|
||||
UUIDGenerator uuidGenerator,
|
||||
Clock clock) {
|
||||
this.localizedMessageFactory = localizedMessageFactory;
|
||||
@Named("applicationName") String applicationName,
|
||||
ChatUtil chatUtil) {
|
||||
this.templateContentGenerator = templateContentGenerator;
|
||||
this.threadPool = threadPool;
|
||||
this.log = log;
|
||||
this.uuidGenerator = uuidGenerator;
|
||||
this.clock = clock;
|
||||
this.applicationName = applicationName;
|
||||
this.chatUtil = chatUtil;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTriggerKeyword() {
|
||||
return "/start";
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return Set.of(TRIGGERING_STAGING_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -73,40 +76,18 @@ public class Start implements Processor {
|
|||
3 - Reply to Telegram
|
||||
*/
|
||||
final String message =
|
||||
Try.of(
|
||||
() -> {
|
||||
final Locale locale = Locale.forLanguageTag(chat.getLocale());
|
||||
final ToolManager toolManager =
|
||||
localizedMessageFactory.createVelocityToolManager(locale);
|
||||
final VelocityContext context =
|
||||
new VelocityContext(toolManager.createContext());
|
||||
context.put("firstName", update.message().chat().firstName());
|
||||
context.put("programName", "_Mezzotre_");
|
||||
|
||||
final StringBuilder content = new StringBuilder();
|
||||
final StringBuilderWriter stringBuilderWriter =
|
||||
new StringBuilderWriter(content);
|
||||
|
||||
toolManager
|
||||
.getVelocityEngine()
|
||||
.mergeTemplate(
|
||||
"template/command/start.0.vm",
|
||||
StandardCharsets.UTF_8.name(),
|
||||
context,
|
||||
stringBuilderWriter);
|
||||
|
||||
stringBuilderWriter.close();
|
||||
return content.toString();
|
||||
})
|
||||
.get();
|
||||
templateContentGenerator.mergeTemplate(
|
||||
velocityContext -> {
|
||||
velocityContext.put("firstName", update.message().chat().firstName());
|
||||
// FIXME add some very cool markdown formatter instead of concatenating stuff
|
||||
// this way
|
||||
velocityContext.put("programName", "_" + applicationName + "_");
|
||||
},
|
||||
chat.getLocale(),
|
||||
"template/telegram/start.vm");
|
||||
log.trace("Start command - message to send back: " + message);
|
||||
|
||||
final ChatContext chatContext = chat.getChatContext();
|
||||
chatContext.setStage(getTriggerKeyword());
|
||||
chatContext.setStep(0);
|
||||
chatContext.setPreviousMessageUnixTimestampInSeconds(clock.now());
|
||||
chat.setChatContext(chatContext);
|
||||
chat.save();
|
||||
chatUtil.updateChatContext(chat, TRIGGERING_STAGING_NAME, 0, Collections.emptyMap());
|
||||
|
||||
// To get the messageId we should send the message first, then save it in the database!
|
||||
final String groupId = uuidGenerator.generateAsString();
|
||||
|
@ -115,11 +96,13 @@ public class Start implements Processor {
|
|||
uuidGenerator.generateAsString(),
|
||||
groupId,
|
||||
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
||||
.withEvent(SelectLanguageTutorial.EVENT_NAME)
|
||||
.withEvent(
|
||||
com.github.polpetta.mezzotre.telegram.callbackquery.SelectLanguageTutorial
|
||||
.EVENT_NAME)
|
||||
.withTelegramChatId(update.message().chat().id())
|
||||
.withAdditionalProperty(
|
||||
SelectLanguageTutorial.Field.NewLanguage.getName(),
|
||||
SelectLanguageTutorial.Language.English.getLocale())
|
||||
Field.SelectLanguageTutorial.NewLanguage.getName(),
|
||||
Value.SelectLanguageTutorial.English.getLocale())
|
||||
.build());
|
||||
|
||||
final CallbackQueryContext switchToItalian =
|
||||
|
@ -127,21 +110,19 @@ public class Start implements Processor {
|
|||
uuidGenerator.generateAsString(),
|
||||
groupId,
|
||||
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
||||
.withEvent(SelectLanguageTutorial.EVENT_NAME)
|
||||
.withEvent(
|
||||
com.github.polpetta.mezzotre.telegram.callbackquery.SelectLanguageTutorial
|
||||
.EVENT_NAME)
|
||||
.withTelegramChatId(update.message().chat().id())
|
||||
.withAdditionalProperty(
|
||||
SelectLanguageTutorial.Field.NewLanguage.getName(),
|
||||
SelectLanguageTutorial.Language.Italian.getLocale())
|
||||
Field.SelectLanguageTutorial.NewLanguage.getName(),
|
||||
Value.SelectLanguageTutorial.Italian.getLocale())
|
||||
.build());
|
||||
|
||||
final String englishButton =
|
||||
localizedMessageFactory
|
||||
.createResourceBundle(Locale.US)
|
||||
.getString("changeLanguage.english");
|
||||
templateContentGenerator.getString(Locale.US, "changeLanguage.english");
|
||||
final String italianButton =
|
||||
localizedMessageFactory
|
||||
.createResourceBundle(Locale.ITALY)
|
||||
.getString("changeLanguage.italian");
|
||||
templateContentGenerator.getString(Locale.ITALY, "changeLanguage.italian");
|
||||
|
||||
final SendMessage messageToSend =
|
||||
new SendMessage(chat.getId(), message)
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command.di;
|
||||
|
||||
import com.github.polpetta.mezzotre.telegram.command.Processor;
|
||||
import com.github.polpetta.mezzotre.telegram.command.Start;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import java.util.Set;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.velocity.app.VelocityEngine;
|
||||
import org.apache.velocity.runtime.RuntimeConstants;
|
||||
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
|
||||
|
||||
public class Command extends AbstractModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("commandProcessor")
|
||||
public Set<Processor> getCommandProcessor(Start start) {
|
||||
return Set.of(start);
|
||||
}
|
||||
|
||||
@Provides
|
||||
public VelocityEngine getVelocityEngine() {
|
||||
final VelocityEngine velocityEngine = new VelocityEngine();
|
||||
velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
|
||||
velocityEngine.setProperty(
|
||||
"classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
|
||||
velocityEngine.init();
|
||||
return velocityEngine;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package com.github.polpetta.mezzotre.telegram.model;
|
||||
|
||||
import com.github.polpetta.mezzotre.i18n.TemplateContentGenerator;
|
||||
import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.github.polpetta.mezzotre.telegram.callbackquery.Field;
|
||||
import com.github.polpetta.mezzotre.telegram.command.Processor;
|
||||
import com.github.polpetta.mezzotre.util.UUIDGenerator;
|
||||
import com.github.polpetta.types.json.CallbackQueryMetadata;
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardButton;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
/**
|
||||
* This class provides the model for the Help message output. It is the business logic behind the
|
||||
* message, and a way to keep the codebase DRY.
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
* @see com.github.polpetta.mezzotre.telegram.command.Help for the command help
|
||||
* @see com.github.polpetta.mezzotre.telegram.callbackquery.ShowHelp for the event help
|
||||
*/
|
||||
public class Help {
|
||||
|
||||
private final TemplateContentGenerator templateContentGenerator;
|
||||
private final UUIDGenerator uuidGenerator;
|
||||
|
||||
@Inject
|
||||
public Help(TemplateContentGenerator templateContentGenerator, UUIDGenerator uuidGenerator) {
|
||||
this.templateContentGenerator = templateContentGenerator;
|
||||
this.uuidGenerator = uuidGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the message that will be sent back to the user. This takes the given {@link
|
||||
* Processor} and formats them accordingly to the chat locale
|
||||
*
|
||||
* @param chat the {@link TgChat} conversation
|
||||
* @param tgCommandProcessors a {@link Map} of all the {@link Processor} that will be printed in
|
||||
* the help message
|
||||
* @return a {@link String} localized ready to be sent to the user
|
||||
*/
|
||||
public String getMessage(TgChat chat, Map<String, Processor> tgCommandProcessors) {
|
||||
return templateContentGenerator.mergeTemplate(
|
||||
velocityContext -> {
|
||||
velocityContext.put(
|
||||
"commands",
|
||||
tgCommandProcessors.values().stream()
|
||||
.distinct()
|
||||
.map(
|
||||
p ->
|
||||
Pair.of(
|
||||
p.getTriggerKeywords().stream().sorted().collect(Collectors.toList()),
|
||||
p.getLocaleDescriptionKeyword()))
|
||||
.collect(Collectors.toList()));
|
||||
},
|
||||
chat.getLocale(),
|
||||
"template/telegram/help.vm");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates {@link InlineKeyboardButton} to be returned to the user to give them the possibility
|
||||
* to interact with them via events rather than commands.
|
||||
*
|
||||
* @param chat the current {@link TgChat} conversation
|
||||
* @param eventProcessors a {@link Map} of {@link
|
||||
* com.github.polpetta.mezzotre.telegram.callbackquery.Processor} that are currently used to
|
||||
* process all events. Note that only the one the user can interact with will be added as
|
||||
* buttons
|
||||
* @return an array of {@link InlineKeyboardButton}
|
||||
*/
|
||||
public InlineKeyboardButton[] generateInlineKeyBoardButton(
|
||||
TgChat chat,
|
||||
Map<String, com.github.polpetta.mezzotre.telegram.callbackquery.Processor> eventProcessors) {
|
||||
final String callBackGroupId = uuidGenerator.generateAsString();
|
||||
return eventProcessors.values().stream()
|
||||
.filter(
|
||||
com.github.polpetta.mezzotre.telegram.callbackquery.Processor
|
||||
::canBeDirectlyInvokedByTheUser)
|
||||
.filter(e -> e.getPrettyPrintLocaleKeyName().isPresent())
|
||||
.map(
|
||||
eventProcessor -> {
|
||||
final CallbackQueryContext callbackQueryContext =
|
||||
new CallbackQueryContext(
|
||||
uuidGenerator.generateAsString(),
|
||||
callBackGroupId,
|
||||
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
||||
.withEvent(eventProcessor.getEventName())
|
||||
.withTelegramChatId(chat.getId())
|
||||
.withAdditionalProperty(
|
||||
Field.ShowHelp.InvokedFromHelpMessage.getName(), true)
|
||||
.build());
|
||||
callbackQueryContext.save();
|
||||
|
||||
return new InlineKeyboardButton(
|
||||
templateContentGenerator.getString(
|
||||
chat.getLocale(), eventProcessor.getPrettyPrintLocaleKeyName().get()))
|
||||
.callbackData(callbackQueryContext.getId());
|
||||
})
|
||||
.toArray(InlineKeyboardButton[]::new);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package com.github.polpetta.mezzotre.util;
|
||||
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.common.util.concurrent.Service;
|
||||
import com.google.common.util.concurrent.ServiceManager;
|
||||
import io.jooby.Extension;
|
||||
import io.jooby.Jooby;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* This Jooby {@link Extension} allows to start and execute services that implement {@link Service}
|
||||
* class. This allows implementation of this interface to be automatically started. Services need to
|
||||
* me listed by the named key {@code "services"}, which is a {@link List} of services passed by the
|
||||
* {@link com.google.inject.Injector}.
|
||||
*
|
||||
* <p>The module handles the services start and stop when the whole JVM is stopped, allowing a
|
||||
* graceful service shutdown.
|
||||
*
|
||||
* @author Davide Polonio
|
||||
* @since 1.0
|
||||
* @see <a href="https://github.com/google/guava/wiki/ServiceExplained">Guava Service Wiki
|
||||
* explained</a>
|
||||
* @see ServiceManager
|
||||
*/
|
||||
public class ServiceModule implements Extension {
|
||||
|
||||
private final ServiceManager serviceManager;
|
||||
|
||||
@Inject
|
||||
public ServiceModule(@Named("services") List<Service> services) {
|
||||
serviceManager = new ServiceManager(services);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lateinit() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void install(@NotNull Jooby application) throws Exception {
|
||||
final CompletableFuture<Void> initialization = new CompletableFuture<>();
|
||||
serviceManager.addListener(
|
||||
new ServiceManager.Listener() {
|
||||
@Override
|
||||
public void healthy() {
|
||||
super.healthy();
|
||||
application.getLog().info("All internal application services are up and running");
|
||||
initialization.complete(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failure(@NotNull Service service) {
|
||||
super.failure(service);
|
||||
initialization.completeExceptionally(service.failureCause());
|
||||
}
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
|
||||
Runtime.getRuntime()
|
||||
.addShutdownHook(
|
||||
new Thread(
|
||||
() -> {
|
||||
try {
|
||||
serviceManager.stopAsync().awaitStopped(Duration.ofSeconds(15));
|
||||
} catch (TimeoutException ex) {
|
||||
application
|
||||
.getLog()
|
||||
.warn(
|
||||
"Unable to correctly stop all the services, got the following error"
|
||||
+ " while stopping: "
|
||||
+ ex.getMessage());
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}));
|
||||
|
||||
// Blocking call to wait for all the services to be up and running
|
||||
serviceManager.startAsync();
|
||||
initialization.get();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package com.github.polpetta.mezzotre.util;
|
||||
|
||||
import com.google.common.util.concurrent.Service;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
public class UtilDI extends AbstractModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("eventThreadPool")
|
||||
public Executor getEventExecutor() {
|
||||
return ForkJoinPool.commonPool();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("longJobThreadPool")
|
||||
public Executor getLongJobExecutor() {
|
||||
return Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("serviceModule")
|
||||
public ServiceModule getServiceModule(@Named("services") List<Service> moduleList) {
|
||||
return new ServiceModule(moduleList);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("serviceRunningCheckTime")
|
||||
public Pair<Integer, TimeUnit> getTime() {
|
||||
return Pair.of(5, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package com.github.polpetta.mezzotre.util.di;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
public class ThreadPool extends AbstractModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("eventThreadPool")
|
||||
public Executor getEventExecutor() {
|
||||
return ForkJoinPool.commonPool();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("longJobThreadPool")
|
||||
public Executor getLongJobExecutor() {
|
||||
return Executors.newCachedThreadPool();
|
||||
}
|
||||
}
|
|
@ -1,11 +1,23 @@
|
|||
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
|
||||
start.cmdDescription=Trigger this very bot
|
||||
start.inlineKeyboardButtonName=Let''s begin!
|
||||
selectLanguageTutorial.inlineKeyboardButtonName=Select language
|
||||
selectLanguageTutorial.drinkAction=*Proceeds to drink a potion with a strange, multicolor liquid*
|
||||
selectLanguageTutorial.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 that you prefer!
|
||||
selectLanguageTutorial.instructions=You can always change your language settings by typing /selectLanguageTutorial in the chat.
|
||||
selectLanguageTutorial.instructions=You can always change your language settings by typing /changeLanguage in the chat.
|
||||
changeLanguage.english=English
|
||||
changeLanguage.italian=Italian
|
||||
changeLanguage.cmdDescription=Select the new language I will use to speak to you
|
||||
changeLanguage.inlineKeyboardButtonName=Change language
|
||||
selectLanguageTutorial.english=English
|
||||
selectLanguageTutorial.italian=Italian
|
||||
spell.speakWithAnimals=Speak with animals
|
||||
button.showMeTutorial=Show me what you can do!
|
||||
selectLanguageTutorial.showMeTutorialInlineKeyboardButtonName=Show me what you can do!
|
||||
help.notShownYet=It seems you haven''t checked out what I can do yet! To have a complete list of my abilities, type /help in chat at any time!
|
||||
help.buttonBelow=Alternatively, you can click the button down below.
|
||||
help.description=Here is a list of what I can do
|
||||
help.buttonsToo=You can do the same operations you''d do with the commands aforementioned by selecting the corresponding button below \ud83d\udc47
|
||||
help.cmdDescription=Print the help message
|
||||
notfound.description=Mmm I''m not able to find command {0}, are you sure to have it typed correctly?
|
||||
notfound.howToHelp=Let me show you what I can do by typing /help in the chat!
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
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
|
||||
start.cmdDescription=Trigger this very bot
|
||||
start.inlineKeyboardButtonName=Let''s begin!
|
||||
selectLanguageTutorial.inlineKeyboardButtonName=Select language
|
||||
selectLanguageTutorial.drinkAction=*Proceeds to drink a potion with a strange, multicolor liquid*
|
||||
selectLanguageTutorial.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 that you prefer!
|
||||
selectLanguageTutorial.instructions=You can always change your language settings by typing /selectLanguageTutorial in the chat.
|
||||
selectLanguageTutorial.instructions=You can always change your language settings by typing /changeLanguage in the chat.
|
||||
changeLanguage.english=English
|
||||
changeLanguage.italian=Italian
|
||||
changeLanguage.cmdDescription=Select the new language I will use to speak to you
|
||||
changeLanguage.inlineKeyboardButtonName=Change language
|
||||
selectLanguageTutorial.english=English
|
||||
selectLanguageTutorial.italian=Italian
|
||||
spell.speakWithAnimals=Speak with animals
|
||||
button.showMeTutorial=Show me what you can do!
|
||||
selectLanguageTutorial.showMeTutorialInlineKeyboardButtonName=Show me what you can do!
|
||||
help.notShownYet=It seems you haven''t checked out what I can do yet! To have a complete list of my abilities, type /help in chat at any time!
|
||||
help.buttonBelow=Alternatively, you can click the button down below.
|
||||
help.description=Here is a list of what I can do
|
||||
help.buttonsToo=You can do the same operations you''d do with the commands aforementioned by selecting the corresponding button below \ud83d\udc47
|
||||
help.cmdDescription=Print the help message
|
||||
notfound.description=Mmm I''m not able to find command {0}, are you sure to have it typed correctly?
|
||||
notfound.howToHelp=Let me show you what I can do by typing /help in the chat!
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
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
|
||||
start.cmdDescription=Comincia a chattare con questo bot
|
||||
start.inlineKeyboardButtonName=Cominciamo!
|
||||
selectLanguageTutorial.inlineKeyboardButtonName=Seleziona linguaggio
|
||||
selectLanguageTutorial.drinkAction=*Procede a bere una pozione al cui suo interno si trova uno strano liquido multicolore*
|
||||
selectLanguageTutorial.setLanguage=Grazie! Ora che ho bevuto questa posizione modificata di {0} che ho trovato ieri al negozio di pozioni magiche la "Cristalleria Fermentatrice" posso parlare con te nel linguaggio che preferisci!
|
||||
selectLanguageTutorial.instructions=Puoi sempre cambiare le preferenze della tua lingua scrivendo /selectLanguageTutorial nella chat.
|
||||
selectLanguageTutorial.instructions=Puoi sempre cambiare le preferenze della tua lingua scrivendo /changeLanguage nella chat.
|
||||
changeLanguage.english=Inglese
|
||||
changeLanguage.italian=Italiano
|
||||
changeLanguage.cmdDescription=Seleziona il nuovo linguaggio che userò per parlare con te
|
||||
changeLanguage.inlineKeyboardButtonName=Cambia lingua
|
||||
selectLanguageTutorial.english=Inglese
|
||||
selectLanguageTutorial.italian=Italiano
|
||||
spell.speakWithAnimals=Parlare con animali
|
||||
button.showMeTutorial=Mostrami cosa puoi fare!
|
||||
selectLanguageTutorial.showMeTutorialInlineKeyboardButtonName=Mostrami cosa puoi fare!
|
||||
help.notShownYet=Sembra tu non abbia ancora visto cosa posso fare! Per avere una lista completa delle mie abilità, scrivi /help nella chat in qualsiasi momento!
|
||||
help.buttonBelow=Alternativamente, puoi premere il bottone qui sotto.
|
||||
help.description=Ecco una lista di quello che sono in grado di fare
|
||||
help.buttonsToo=Puoi fare le stesse operazioni che faresti con i comandi elencati precedentemente cliccando il bottone corrispondente qui di sotto \ud83d\udc47
|
||||
help.cmdDescription=Stampa il messaggio d''aiuto
|
||||
notfound.description=Mmm non sono in grado di trovare il comando {0}, sei sicuro di averlo scritto correttamente?
|
||||
notfound.howToHelp=Lascia che ti mostri cosa posso fare, scrivi /help nella chat!
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
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
|
||||
start.cmdDescription=Comincia a chattare con questo bot
|
||||
start.inlineKeyboardButtonName=Cominciamo!
|
||||
selectLanguageTutorial.inlineKeyboardButtonName=Seleziona linguaggio
|
||||
selectLanguageTutorial.drinkAction=*Procede a bere una pozione al cui suo interno si trova uno strano liquido multicolore*
|
||||
selectLanguageTutorial.setLanguage=Grazie! Ora che ho bevuto questa posizione modificata di {0} che ho trovato ieri al negozio di pozioni magiche la "Cristalleria Fermentatrice" posso parlare con te nel linguaggio che preferisci!
|
||||
selectLanguageTutorial.instructions=Puoi sempre cambiare le preferenze della tua lingua scrivendo /selectLanguageTutorial nella chat.
|
||||
selectLanguageTutorial.instructions=Puoi sempre cambiare le preferenze della tua lingua scrivendo /changeLanguage nella chat.
|
||||
changeLanguage.english=Inglese
|
||||
changeLanguage.italian=Italiano
|
||||
changeLanguage.cmdDescription=Seleziona il nuovo linguaggio che userò per parlare con te
|
||||
changeLanguage.inlineKeyboardButtonName=Cambia lingua
|
||||
selectLanguageTutorial.english=Inglese
|
||||
selectLanguageTutorial.italian=Italiano
|
||||
spell.speakWithAnimals=Parlare con animali
|
||||
button.showMeTutorial=Mostrami cosa puoi fare!
|
||||
selectLanguageTutorial.showMeTutorialInlineKeyboardButtonName=Mostrami cosa puoi fare!
|
||||
help.notShownYet=Sembra tu non abbia ancora visto cosa posso fare! Per avere una lista completa delle mie abilità, scrivi /help nella chat in qualsiasi momento!
|
||||
help.buttonBelow=Alternativamente, puoi premere il bottone qui sotto.
|
||||
help.description=Ecco una lista di quello che sono in grado di fare
|
||||
help.buttonsToo=Puoi fare le stesse operazioni che faresti con i comandi elencati precedentemente cliccando il bottone corrispondente qui di sotto \ud83d\udc47
|
||||
help.cmdDescription=Stampa il messaggio d''aiuto
|
||||
notfound.description=Mmm non sono in grado di trovare il comando {0}, sei sicuro di averlo scritto correttamente?
|
||||
notfound.howToHelp=Lascia che ti mostri cosa posso fare, scrivi /help nella chat!
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
**${i18n.start.helloFirstName.insert(${firstName})}**
|
||||
|
||||
${i18n.start.description.insert(${programName})}
|
|
@ -0,0 +1,7 @@
|
|||
${i18n.help.description}:
|
||||
|
||||
#foreach(${command} in ${commands})
|
||||
-#foreach(${key} in ${command.left}) ${key}#end: ${i18n.get(${command.right})}
|
||||
#end
|
||||
|
||||
${i18n.help.buttonsToo}
|
|
@ -0,0 +1,3 @@
|
|||
${i18n.notfound.description.insert(${cmdName})}
|
||||
|
||||
${i18n.notfound.howToHelp}
|
|
@ -0,0 +1,3 @@
|
|||
*${i18n.start.helloFirstName.insert(${firstName})}*
|
||||
|
||||
${i18n.start.description.insert(${programName})}
|
|
@ -3,15 +3,21 @@ package com.github.polpetta.mezzotre;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import com.google.inject.*;
|
||||
import com.github.polpetta.mezzotre.util.ServiceModule;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.Stage;
|
||||
import io.jooby.*;
|
||||
import io.jooby.ebean.EbeanModule;
|
||||
import io.jooby.flyway.FlywayModule;
|
||||
import io.jooby.hikari.HikariModule;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class UnitTest {
|
||||
|
@ -37,6 +43,20 @@ public class UnitTest {
|
|||
public Extension getEbeanExtension() {
|
||||
return mock(EbeanModule.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("serviceModule")
|
||||
public ServiceModule getServiceModule() {
|
||||
return mock(ServiceModule.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("serviceRunningCheckTime")
|
||||
public Pair<Integer, TimeUnit> getTime() {
|
||||
return Pair.of(0, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
package com.github.polpetta.mezzotre.helper;
|
||||
|
||||
import com.github.polpetta.mezzotre.App;
|
||||
import com.github.polpetta.mezzotre.route.di.Route;
|
||||
import com.github.polpetta.mezzotre.route.RouteDI;
|
||||
import com.github.polpetta.mezzotre.util.ServiceModule;
|
||||
import com.google.common.util.concurrent.Service;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.Stage;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import io.jooby.Extension;
|
||||
import io.jooby.ebean.EbeanModule;
|
||||
import io.jooby.flyway.FlywayModule;
|
||||
import io.jooby.hikari.HikariModule;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
|
||||
|
@ -50,13 +54,27 @@ public class IntegrationAppFactory {
|
|||
public Extension getEbeanExtension() {
|
||||
return new EbeanModule();
|
||||
}
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
@Named("serviceModule")
|
||||
public ServiceModule getServiceModule(@Named("services") List<Service> moduleList) {
|
||||
return new ServiceModule(moduleList);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("serviceRunningCheckTime")
|
||||
public Pair<Integer, TimeUnit> getTime() {
|
||||
return Pair.of(0, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
public static App loadCustomDbApplication() {
|
||||
final PostgreSQLContainer<?> container =
|
||||
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||
container.start();
|
||||
final Route routeModule = new Route();
|
||||
final RouteDI routeModule = new RouteDI();
|
||||
final DatabaseDI databaseDI = new DatabaseDI(container);
|
||||
return new App(Stage.DEVELOPMENT, Set.of(databaseDI, routeModule));
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ public class Loader {
|
|||
hikariConnectionProperties.put("username", container.getUsername());
|
||||
hikariConnectionProperties.put("password", container.getPassword());
|
||||
hikariConnectionProperties.put("jdbcUrl", container.getJdbcUrl());
|
||||
hikariConnectionProperties.put("autoCommit", "false");
|
||||
|
||||
ebeanConnectionProperties.load(ebeanInputStream);
|
||||
ebeanConnectionProperties.put("datasource_db_username", container.getUsername());
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
package com.github.polpetta.mezzotre.i18n;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
import org.apache.velocity.VelocityContext;
|
||||
import org.apache.velocity.app.VelocityEngine;
|
||||
import org.apache.velocity.tools.ToolContext;
|
||||
import org.apache.velocity.tools.ToolManager;
|
||||
import org.apache.velocity.util.StringBuilderWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.parallel.Execution;
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
class TemplateContentGeneratorTest {
|
||||
|
||||
private LocalizedMessageFactory fakeLocalizedMessageFactory;
|
||||
private TemplateContentGenerator templateContentGenerator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
fakeLocalizedMessageFactory = mock(LocalizedMessageFactory.class);
|
||||
templateContentGenerator = new TemplateContentGenerator(fakeLocalizedMessageFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallMergeTemplateWithRightArguments() {
|
||||
final VelocityEngine fakeVelocityEngine = mock(VelocityEngine.class);
|
||||
final ToolManager fakeToolManager = mock(ToolManager.class);
|
||||
when(fakeToolManager.getVelocityEngine()).thenReturn(fakeVelocityEngine);
|
||||
final ToolContext fakeToolContext = mock(ToolContext.class);
|
||||
when(fakeToolManager.createContext()).thenReturn(fakeToolContext);
|
||||
when(fakeLocalizedMessageFactory.createVelocityToolManager(Locale.forLanguageTag("en-US")))
|
||||
.thenReturn(fakeToolManager);
|
||||
|
||||
templateContentGenerator.mergeTemplate(
|
||||
ctx -> {
|
||||
ctx.put("testtesttest", "value");
|
||||
},
|
||||
"en-US",
|
||||
"a/fake/path.vm");
|
||||
|
||||
final ArgumentCaptor<VelocityContext> velocityContextArgumentCaptor =
|
||||
ArgumentCaptor.forClass(VelocityContext.class);
|
||||
verify(fakeVelocityEngine, times(1))
|
||||
.mergeTemplate(
|
||||
eq("a/fake/path.vm"),
|
||||
eq(StandardCharsets.UTF_8.name()),
|
||||
velocityContextArgumentCaptor.capture(),
|
||||
any(StringBuilderWriter.class));
|
||||
|
||||
final VelocityContext gotVelocityContext = velocityContextArgumentCaptor.getValue();
|
||||
assertEquals("value", gotVelocityContext.get("testtesttest"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallLocaleFactoryWhenRetrievingKey() {
|
||||
final Locale enUS = Locale.forLanguageTag("en-US");
|
||||
|
||||
class StubRB extends ResourceBundle {
|
||||
|
||||
@Override
|
||||
protected Object handleGetObject(@NotNull String s) {
|
||||
return "testtest123";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Enumeration<String> getKeys() {
|
||||
return new Enumeration<String>() {
|
||||
|
||||
private boolean next = true;
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
final boolean toRet = next;
|
||||
next = !next;
|
||||
return toRet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nextElement() {
|
||||
return "doens't matter";
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
final StubRB stubRB = new StubRB();
|
||||
when(fakeLocalizedMessageFactory.createResourceBundle(any())).thenReturn(stubRB);
|
||||
|
||||
final String got = templateContentGenerator.getString(enUS, "a.string");
|
||||
assertEquals("testtest123", got);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package com.github.polpetta.mezzotre.orm;
|
||||
|
||||
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.orm.model.CallbackQueryContext;
|
||||
import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext;
|
||||
import com.github.polpetta.types.json.CallbackQueryMetadata;
|
||||
import io.ebean.Database;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.junit.jupiter.api.*;
|
||||
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 BatchBeanCleanerServiceIntegrationTest {
|
||||
|
||||
@Container
|
||||
private final PostgreSQLContainer<?> postgresServer =
|
||||
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||
|
||||
private Database database;
|
||||
private BatchBeanCleanerService batchBeanCleanerService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
database =
|
||||
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||
batchBeanCleanerService =
|
||||
new BatchBeanCleanerService(
|
||||
LoggerFactory.getLogger(BatchBeanCleanerService.class),
|
||||
Pair.of(0, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
if (batchBeanCleanerService != null) {
|
||||
batchBeanCleanerService.stopAsync().awaitTerminated(Duration.ofSeconds(10));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Timeout(value = 1, unit = TimeUnit.MINUTES)
|
||||
void shouldRemoveEntryFromDatabase() throws Exception {
|
||||
final CallbackQueryContext callbackQueryContext =
|
||||
new CallbackQueryContext("1234", "4567", new CallbackQueryMetadata());
|
||||
callbackQueryContext.save();
|
||||
|
||||
batchBeanCleanerService.startAsync();
|
||||
batchBeanCleanerService.awaitRunning();
|
||||
|
||||
final CompletableFuture<Integer> integerCompletableFuture =
|
||||
batchBeanCleanerService.removeAsync("1234", new QCallbackQueryContext().id);
|
||||
|
||||
final Integer gotDeletion = integerCompletableFuture.get();
|
||||
|
||||
assertEquals(1, gotDeletion);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Timeout(value = 1, unit = TimeUnit.MINUTES)
|
||||
void shouldRemoveMultipleEntriesFromDatabase() throws Exception {
|
||||
final CallbackQueryContext callbackQueryContext =
|
||||
new CallbackQueryContext("1234", "4567", new CallbackQueryMetadata());
|
||||
callbackQueryContext.save();
|
||||
final CallbackQueryContext callbackQueryContext2 =
|
||||
new CallbackQueryContext("4321", "4567", new CallbackQueryMetadata());
|
||||
callbackQueryContext2.save();
|
||||
|
||||
batchBeanCleanerService.startAsync();
|
||||
batchBeanCleanerService.awaitRunning();
|
||||
|
||||
final CompletableFuture<Integer> integerCompletableFuture =
|
||||
batchBeanCleanerService.removeAsync("4567", new QCallbackQueryContext().entryGroup);
|
||||
final CompletableFuture<Integer> integerCompletableFuture2 =
|
||||
batchBeanCleanerService.removeAsync("4567", new QCallbackQueryContext().entryGroup);
|
||||
|
||||
final Integer gotDeletion1 = integerCompletableFuture.get();
|
||||
final Integer gotDeletion2 = integerCompletableFuture2.get();
|
||||
|
||||
assertEquals(2, gotDeletion1);
|
||||
assertEquals(0, gotDeletion2);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package com.github.polpetta.mezzotre.orm;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.github.polpetta.mezzotre.helper.Loader;
|
||||
import com.github.polpetta.mezzotre.helper.TestConfig;
|
||||
import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||
import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext;
|
||||
import com.github.polpetta.types.json.CallbackQueryMetadata;
|
||||
import io.ebean.Database;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.junit.jupiter.api.*;
|
||||
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 CallbackQueryContextCleanerIntegrationTest {
|
||||
|
||||
@Container
|
||||
private final PostgreSQLContainer<?> postgresServer =
|
||||
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||
|
||||
private Database database;
|
||||
|
||||
private BatchBeanCleanerService batchBeanCleanerService;
|
||||
private CallbackQueryContextCleaner callbackQueryContextCleaner;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
database =
|
||||
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||
|
||||
batchBeanCleanerService =
|
||||
new BatchBeanCleanerService(
|
||||
LoggerFactory.getLogger(BatchBeanCleanerService.class),
|
||||
Pair.of(0, TimeUnit.MILLISECONDS));
|
||||
batchBeanCleanerService.startAsync().awaitRunning(Duration.ofSeconds(10));
|
||||
|
||||
callbackQueryContextCleaner =
|
||||
new CallbackQueryContextCleaner(
|
||||
batchBeanCleanerService, LoggerFactory.getLogger(CallbackQueryContextCleaner.class));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
batchBeanCleanerService.stopAsync().awaitTerminated(Duration.ofSeconds(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Timeout(value = 1, unit = TimeUnit.MINUTES)
|
||||
void shouldDeleteByGroupId() throws Exception {
|
||||
new CallbackQueryContext("doesn't matter", "an id", new CallbackQueryMetadata()).save();
|
||||
|
||||
final Integer got = callbackQueryContextCleaner.removeGroupAsync("an id").get();
|
||||
|
||||
assertEquals(1, got);
|
||||
assertEquals(0, new QCallbackQueryContext().findCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Timeout(value = 1, unit = TimeUnit.MINUTES)
|
||||
void shouldDeleteById() throws Exception {
|
||||
new CallbackQueryContext("an id", "doesn't matter", new CallbackQueryMetadata()).save();
|
||||
|
||||
final Integer got = callbackQueryContextCleaner.removeIdAsync("an id").get();
|
||||
|
||||
assertEquals(1, got);
|
||||
assertEquals(0, new QCallbackQueryContext().findCount());
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ import org.testcontainers.junit.jupiter.Testcontainers;
|
|||
@Tag("slow")
|
||||
@Tag("database")
|
||||
@Testcontainers
|
||||
class CallbackQueryContextIntegrationTest {
|
||||
class CallbackQueryDIContextIntegrationTest {
|
||||
|
||||
private static ObjectMapper objectMapper;
|
||||
private static UUIDGenerator uuidGenerator;
|
|
@ -0,0 +1,176 @@
|
|||
package com.github.polpetta.mezzotre.orm.telegram;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import com.github.polpetta.mezzotre.helper.Loader;
|
||||
import com.github.polpetta.mezzotre.helper.TestConfig;
|
||||
import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.github.polpetta.mezzotre.util.Clock;
|
||||
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 io.ebean.Database;
|
||||
import java.util.Optional;
|
||||
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.testcontainers.containers.PostgreSQLContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
@Tag("slow")
|
||||
@Tag("database")
|
||||
@Testcontainers
|
||||
class ChatUtilIntegrationTest {
|
||||
|
||||
private static Gson gson;
|
||||
|
||||
@Container
|
||||
private final PostgreSQLContainer<?> postgresServer =
|
||||
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||
|
||||
private Database database;
|
||||
private Clock fakeClock;
|
||||
private ChatUtil chatUtil;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
gson = new Gson();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
database =
|
||||
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||
|
||||
fakeClock = mock(Clock.class);
|
||||
chatUtil = new ChatUtil(fakeClock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExtractChatFromCallBackQueryContext() {
|
||||
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\":\"/help\"\n"
|
||||
+ "}\n"
|
||||
+ "}",
|
||||
Update.class);
|
||||
final CallbackQueryContext callbackQueryContext =
|
||||
new CallbackQueryContext(
|
||||
"123",
|
||||
"456",
|
||||
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
||||
.withTelegramChatId(69420L)
|
||||
.build());
|
||||
|
||||
final TgChat expectedChat = new TgChat(69420L, new ChatContext());
|
||||
expectedChat.save();
|
||||
|
||||
final Optional<TgChat> tgChatOptional =
|
||||
chatUtil.extractChat(
|
||||
callbackQueryContext, null); // null just for test purposes, not usually expected
|
||||
|
||||
assertEquals(expectedChat, tgChatOptional.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExtractChatFromUpdateIfCallbackQueryContextIsEmpty() {
|
||||
final Update update =
|
||||
gson.fromJson(
|
||||
"{\n"
|
||||
+ " \"update_id\": 158712614,\n"
|
||||
+ " \"callback_query\": {\n"
|
||||
+ " \"id\": \"20496049451114620\",\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"is_bot\": false,\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"language_code\": \"en\"\n"
|
||||
+ " },\n"
|
||||
+ " \"message\": {\n"
|
||||
+ " \"message_id\": 69420,\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 244745330,\n"
|
||||
+ " \"is_bot\": true,\n"
|
||||
+ " \"first_name\": \"Dev - DavideBot\",\n"
|
||||
+ " \"username\": \"devdavidebot\"\n"
|
||||
+ " },\n"
|
||||
+ " \"date\": 1681218838,\n"
|
||||
+ " \"chat\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"type\": \"private\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\"\n"
|
||||
+ " },\n"
|
||||
+ " \"text\": \"Hello xxxxxx! \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\",\n"
|
||||
+ " \"entities\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"type\": \"bold\",\n"
|
||||
+ " \"offset\": 0,\n"
|
||||
+ " \"length\": 16\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"type\": \"italic\",\n"
|
||||
+ " \"offset\": 26,\n"
|
||||
+ " \"length\": 8\n"
|
||||
+ " }\n"
|
||||
+ " ],\n"
|
||||
+ " \"reply_markup\": {\n"
|
||||
+ " \"inline_keyboard\": [\n"
|
||||
+ " [\n"
|
||||
+ " {\n"
|
||||
+ " \"text\": \"English\",\n"
|
||||
+ " \"callback_data\": \"9a64be11-d086-4bd9-859f-720c43dedcb5\"\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"text\": \"Italian\",\n"
|
||||
+ " \"callback_data\": \"8768d660-f05f-4f4b-bda5-3451ab573d56\"\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "}",
|
||||
Update.class);
|
||||
final CallbackQueryContext callbackQueryContext =
|
||||
new CallbackQueryContext("123", "456", new CallbackQueryMetadata());
|
||||
|
||||
final TgChat expectedChat = new TgChat(69420L, new ChatContext());
|
||||
expectedChat.save();
|
||||
|
||||
final Optional<TgChat> tgChatOptional =
|
||||
chatUtil.extractChat(
|
||||
callbackQueryContext, update); // null just for test purposes, not usually expected
|
||||
|
||||
assertEquals(expectedChat, tgChatOptional.get());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package com.github.polpetta.mezzotre.orm.telegram;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.github.polpetta.mezzotre.util.Clock;
|
||||
import com.github.polpetta.types.json.ChatContext;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.parallel.Execution;
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
class ChatUtilTest {
|
||||
private Clock fakeClock;
|
||||
private ChatUtil chatUtil;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
fakeClock = mock(Clock.class);
|
||||
chatUtil = new ChatUtil(fakeClock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateChatContext() {
|
||||
final TgChat fakeTgChat = mock(TgChat.class);
|
||||
final ChatContext chatContext = new ChatContext();
|
||||
when(fakeTgChat.getChatContext()).thenReturn(chatContext);
|
||||
when(fakeClock.now()).thenReturn(69420L);
|
||||
chatUtil.updateChatContext(fakeTgChat, "/test1", 42, Collections.emptyMap());
|
||||
|
||||
verify(fakeTgChat, times(1)).getChatContext();
|
||||
final ArgumentCaptor<ChatContext> chatContextArgumentCaptor =
|
||||
ArgumentCaptor.forClass(ChatContext.class);
|
||||
verify(fakeTgChat, times(1)).setChatContext(chatContextArgumentCaptor.capture());
|
||||
verify(fakeTgChat, times(0)).setHasHelpBeenShown(eq(true));
|
||||
final ChatContext capturedChatContextValue = chatContextArgumentCaptor.getValue();
|
||||
assertEquals("/test1", capturedChatContextValue.getStage());
|
||||
assertEquals(42, capturedChatContextValue.getStep());
|
||||
assertEquals(69420L, capturedChatContextValue.getPreviousMessageUnixTimestampInSeconds());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAddMapElementsToChatContextToo() {
|
||||
final TgChat fakeTgChat = mock(TgChat.class);
|
||||
final ChatContext chatContext = new ChatContext();
|
||||
when(fakeTgChat.getChatContext()).thenReturn(chatContext);
|
||||
when(fakeClock.now()).thenReturn(69420L);
|
||||
|
||||
final Object obj1 = new Object();
|
||||
final Object obj2 = new Object();
|
||||
|
||||
chatUtil.updateChatContext(fakeTgChat, "/test1", 42, Map.of("field1", obj1, "field2", obj2));
|
||||
|
||||
verify(fakeTgChat, times(1)).getChatContext();
|
||||
final ArgumentCaptor<ChatContext> chatContextArgumentCaptor =
|
||||
ArgumentCaptor.forClass(ChatContext.class);
|
||||
verify(fakeTgChat, times(1)).setChatContext(chatContextArgumentCaptor.capture());
|
||||
final ChatContext capturedChatContextValue = chatContextArgumentCaptor.getValue();
|
||||
final Map<String, Object> gotAdditionalProperties =
|
||||
capturedChatContextValue.getAdditionalProperties();
|
||||
assertEquals(2, gotAdditionalProperties.size());
|
||||
assertEquals(obj1, gotAdditionalProperties.get("field1"));
|
||||
assertEquals(obj2, gotAdditionalProperties.get("field2"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.github.polpetta.mezzotre.orm.CallbackQueryContextCleaner;
|
||||
import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.parallel.Execution;
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
class NotFoundTest {
|
||||
|
||||
private CallbackQueryContextCleaner fakeCallbackQueryCleaner;
|
||||
private NotFound notFound;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
fakeCallbackQueryCleaner = mock(CallbackQueryContextCleaner.class);
|
||||
|
||||
notFound =
|
||||
new NotFound(LoggerFactory.getLogger(NotFound.class), fakeCallbackQueryCleaner, "anEvent");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallCallbackQueryContextCleaner() throws Exception {
|
||||
final CallbackQueryContext fakeCallbackQuery = mock(CallbackQueryContext.class);
|
||||
when(fakeCallbackQuery.getId()).thenReturn("anId");
|
||||
when(fakeCallbackQuery.getEntryGroup()).thenReturn("123");
|
||||
final Update fakeUpdate = mock(Update.class);
|
||||
|
||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotResult =
|
||||
notFound.process(fakeCallbackQuery, fakeUpdate);
|
||||
|
||||
verify(fakeCallbackQueryCleaner, times(1)).removeGroupAsync(eq("123"));
|
||||
assertEquals(Optional.empty(), gotResult.get());
|
||||
}
|
||||
}
|
|
@ -6,10 +6,15 @@ 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.i18n.TemplateContentGenerator;
|
||||
import com.github.polpetta.mezzotre.orm.BatchBeanCleanerService;
|
||||
import com.github.polpetta.mezzotre.orm.CallbackQueryContextCleaner;
|
||||
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.mezzotre.orm.telegram.ChatUtil;
|
||||
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;
|
||||
|
@ -21,12 +26,17 @@ import com.pengrad.telegrambot.request.BaseRequest;
|
|||
import com.pengrad.telegrambot.request.EditMessageText;
|
||||
import com.pengrad.telegrambot.request.SendMessage;
|
||||
import io.ebean.Database;
|
||||
import io.ebean.typequery.PString;
|
||||
import io.ebean.typequery.TQRootBean;
|
||||
import io.vavr.Tuple3;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
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.apache.commons.lang3.tuple.Pair;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
|
@ -51,6 +61,8 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
private Database database;
|
||||
private SelectLanguageTutorial selectLanguageTutorial;
|
||||
private UUIDGenerator fakeUUIDGenerator;
|
||||
private ChatUtil chatUtil;
|
||||
private BatchBeanCleanerService batchBeanCleanerService;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
|
@ -63,19 +75,35 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||
|
||||
fakeUUIDGenerator = mock(UUIDGenerator.class);
|
||||
chatUtil = new ChatUtil(new Clock());
|
||||
batchBeanCleanerService =
|
||||
new BatchBeanCleanerService(
|
||||
LoggerFactory.getLogger(BatchBeanCleanerService.class),
|
||||
Pair.of(0, TimeUnit.MILLISECONDS));
|
||||
batchBeanCleanerService.startAsync().awaitRunning(Duration.ofSeconds(10));
|
||||
|
||||
selectLanguageTutorial =
|
||||
new SelectLanguageTutorial(
|
||||
Executors.newSingleThreadExecutor(),
|
||||
new LocalizedMessageFactory(Loader.defaultVelocityEngine()),
|
||||
new TemplateContentGenerator(
|
||||
new LocalizedMessageFactory(Loader.defaultVelocityEngine())),
|
||||
LoggerFactory.getLogger(SelectLanguageTutorial.class),
|
||||
fakeUUIDGenerator);
|
||||
fakeUUIDGenerator,
|
||||
chatUtil,
|
||||
new CallbackQueryContextCleaner(
|
||||
batchBeanCleanerService,
|
||||
LoggerFactory.getLogger(CallbackQueryContextCleaner.class)));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
batchBeanCleanerService.stopAsync().awaitTerminated(Duration.ofSeconds(10));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> getTestLocales() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
SelectLanguageTutorial.Language.Italian,
|
||||
Value.SelectLanguageTutorial.Italian,
|
||||
"_*Procede a bere una pozione al cui suo interno si trova uno strano liquido"
|
||||
+ " multicolore*_\n"
|
||||
+ "\n"
|
||||
|
@ -84,7 +112,7 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
+ " posso parlare con te nel linguaggio che preferisci!\n"
|
||||
+ "\n"
|
||||
+ "Puoi sempre cambiare le preferenze della tua lingua scrivendo"
|
||||
+ " /selectLanguageTutorial nella chat.\n"
|
||||
+ " /changeLanguage nella chat.\n"
|
||||
+ "\n"
|
||||
+ "Sembra tu non abbia ancora visto cosa posso fare! Per avere una lista completa"
|
||||
+ " delle mie abilità, scrivi /help nella chat in qualsiasi momento!"
|
||||
|
@ -93,14 +121,14 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
"Mostrami cosa puoi fare!",
|
||||
false),
|
||||
Arguments.of(
|
||||
SelectLanguageTutorial.Language.English,
|
||||
Value.SelectLanguageTutorial.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 that you prefer!\n"
|
||||
+ "\n"
|
||||
+ "You can always change your language settings by typing /selectLanguageTutorial"
|
||||
+ "You can always change your language settings by typing /changeLanguage"
|
||||
+ " in the chat.\n"
|
||||
+ "\n"
|
||||
+ "It seems you haven't checked out what I can do yet! To have a complete list of"
|
||||
|
@ -110,7 +138,7 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
"Show me what you can do!",
|
||||
false),
|
||||
Arguments.of(
|
||||
SelectLanguageTutorial.Language.Italian,
|
||||
Value.SelectLanguageTutorial.Italian,
|
||||
"_*Procede a bere una pozione al cui suo interno si trova uno strano liquido"
|
||||
+ " multicolore*_\n"
|
||||
+ "\n"
|
||||
|
@ -119,19 +147,19 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
+ " posso parlare con te nel linguaggio che preferisci!\n"
|
||||
+ "\n"
|
||||
+ "Puoi sempre cambiare le preferenze della tua lingua scrivendo"
|
||||
+ " /selectLanguageTutorial nella chat.\n\n",
|
||||
+ " /changeLanguage nella chat.\n\n",
|
||||
"en-US",
|
||||
"Mostrami cosa puoi fare!",
|
||||
true),
|
||||
Arguments.of(
|
||||
SelectLanguageTutorial.Language.English,
|
||||
Value.SelectLanguageTutorial.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 that you prefer!\n"
|
||||
+ "\n"
|
||||
+ "You can always change your language settings by typing /selectLanguageTutorial"
|
||||
+ "You can always change your language settings by typing /changeLanguage"
|
||||
+ " in the chat.\n\n",
|
||||
"it-IT",
|
||||
"Show me what you can do!",
|
||||
|
@ -142,7 +170,7 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
@Timeout(value = 1, unit = TimeUnit.MINUTES)
|
||||
@MethodSource("getTestLocales")
|
||||
void shouldProcessChangeLanguageToDesiredOneSendMessage(
|
||||
SelectLanguageTutorial.Language language,
|
||||
Value.SelectLanguageTutorial selectLanguageTutorial,
|
||||
String expectedResult,
|
||||
String startingLocale,
|
||||
String buttonLocale,
|
||||
|
@ -182,7 +210,8 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
.withEvent("selectLanguageTutorial")
|
||||
.withTelegramChatId(tgChatId)
|
||||
.withAdditionalProperty(
|
||||
SelectLanguageTutorial.Field.NewLanguage.getName(), language.getLocale())
|
||||
Field.SelectLanguageTutorial.NewLanguage.getName(),
|
||||
selectLanguageTutorial.getLocale())
|
||||
.build();
|
||||
final String entryGroupId = "2e67774a-e4e4-4369-a414-a7f8bfe74b80";
|
||||
final CallbackQueryContext changeLanguageCallbackQueryContext =
|
||||
|
@ -190,8 +219,13 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
"c018108f-6612-4848-8fca-cf301460d4eb", entryGroupId, callbackQueryMetadata);
|
||||
changeLanguageCallbackQueryContext.save();
|
||||
|
||||
final CompletableFuture<
|
||||
Tuple3<String, PString<? extends TQRootBean<?, ?>>, CompletableFuture<Integer>>>
|
||||
callBackFuture = new CompletableFuture<>();
|
||||
batchBeanCleanerService.addListener(callBackFuture::complete);
|
||||
|
||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> processFuture =
|
||||
selectLanguageTutorial.process(changeLanguageCallbackQueryContext, update);
|
||||
this.selectLanguageTutorial.process(changeLanguageCallbackQueryContext, update);
|
||||
final Optional<BaseRequest<?, ?>> gotResponseOpt = processFuture.get();
|
||||
final SendMessage gotMessage = (SendMessage) gotResponseOpt.get();
|
||||
assertEquals(expectedResult, gotMessage.getParameters().get("text"));
|
||||
|
@ -210,14 +244,20 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
assertEquals(
|
||||
1, new QCallbackQueryContext().id.eq("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5").findCount());
|
||||
} else {
|
||||
callBackFuture.get(); // Await that callback are cleaned out first
|
||||
assertEquals(0, keyboardButtons.size());
|
||||
assertEquals(0, new QCallbackQueryContext().findCount());
|
||||
}
|
||||
|
||||
final TgChat retrievedTgChat = new QTgChat().id.eq(tgChatId).findOne();
|
||||
assertNotNull(retrievedTgChat);
|
||||
assertEquals(language.getLocale(), retrievedTgChat.getLocale());
|
||||
assertEquals(selectLanguageTutorial.getLocale(), retrievedTgChat.getLocale());
|
||||
|
||||
final Tuple3<String, PString<? extends TQRootBean<?, ?>>, CompletableFuture<Integer>>
|
||||
deletionCallback = callBackFuture.get();
|
||||
|
||||
assertEquals(entryGroupId, deletionCallback._1());
|
||||
assertEquals(1, deletionCallback._3().get());
|
||||
assertEquals(0, new QCallbackQueryContext().entryGroup.eq(entryGroupId).findCount());
|
||||
}
|
||||
|
||||
|
@ -225,7 +265,7 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
@Timeout(value = 1, unit = TimeUnit.MINUTES)
|
||||
@MethodSource("getTestLocales")
|
||||
void shouldProcessChangeLanguageToDesiredOneEditMessage(
|
||||
SelectLanguageTutorial.Language language,
|
||||
Value.SelectLanguageTutorial selectLanguageTutorial,
|
||||
String expectedResult,
|
||||
String startingLocale,
|
||||
String buttonLocale,
|
||||
|
@ -297,16 +337,21 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
.withEvent("selectLanguageTutorial")
|
||||
.withTelegramChatId(tgChatId)
|
||||
.withAdditionalProperty(
|
||||
SelectLanguageTutorial.Field.NewLanguage.getName(), language.getLocale())
|
||||
Field.SelectLanguageTutorial.NewLanguage.getName(),
|
||||
selectLanguageTutorial.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<
|
||||
Tuple3<String, PString<? extends TQRootBean<?, ?>>, CompletableFuture<Integer>>>
|
||||
callBackFuture = new CompletableFuture<>();
|
||||
batchBeanCleanerService.addListener(callBackFuture::complete);
|
||||
|
||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> processFuture =
|
||||
selectLanguageTutorial.process(changeLanguageCallbackQueryContext, update);
|
||||
this.selectLanguageTutorial.process(changeLanguageCallbackQueryContext, update);
|
||||
final Optional<BaseRequest<?, ?>> gotResponseOpt = processFuture.get();
|
||||
final EditMessageText gotMessage = (EditMessageText) gotResponseOpt.get();
|
||||
assertEquals(expectedResult, gotMessage.getParameters().get("text"));
|
||||
|
@ -325,14 +370,20 @@ class SelectLanguageTutorialIntegrationTest {
|
|||
assertEquals(
|
||||
1, new QCallbackQueryContext().id.eq("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5").findCount());
|
||||
} else {
|
||||
callBackFuture.get(); // Await that callback are cleaned out first
|
||||
assertEquals(0, keyboardButtons.size());
|
||||
assertEquals(0, new QCallbackQueryContext().findCount());
|
||||
}
|
||||
|
||||
final TgChat retrievedTgChat = new QTgChat().id.eq(tgChatId).findOne();
|
||||
assertNotNull(retrievedTgChat);
|
||||
assertEquals(language.getLocale(), retrievedTgChat.getLocale());
|
||||
assertEquals(selectLanguageTutorial.getLocale(), retrievedTgChat.getLocale());
|
||||
|
||||
final Tuple3<String, PString<? extends TQRootBean<?, ?>>, CompletableFuture<Integer>>
|
||||
deletionCallback = callBackFuture.get();
|
||||
|
||||
assertEquals(entryGroupId, deletionCallback._1());
|
||||
assertEquals(1, deletionCallback._3().get());
|
||||
assertEquals(0, new QCallbackQueryContext().entryGroup.eq(entryGroupId).findCount());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,457 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
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.i18n.TemplateContentGenerator;
|
||||
import com.github.polpetta.mezzotre.orm.BatchBeanCleanerService;
|
||||
import com.github.polpetta.mezzotre.orm.CallbackQueryContextCleaner;
|
||||
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.mezzotre.orm.telegram.ChatUtil;
|
||||
import com.github.polpetta.mezzotre.telegram.model.Help;
|
||||
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.gson.Gson;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardButton;
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import com.pengrad.telegrambot.request.EditMessageText;
|
||||
import com.pengrad.telegrambot.request.SendMessage;
|
||||
import io.ebean.Database;
|
||||
import io.ebean.typequery.PString;
|
||||
import io.ebean.typequery.TQRootBean;
|
||||
import io.vavr.Tuple3;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.velocity.app.VelocityEngine;
|
||||
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.Logger;
|
||||
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 ShowHelpIntegrationTest {
|
||||
|
||||
private static Gson gson;
|
||||
private static Logger testLog;
|
||||
|
||||
@Container
|
||||
private final PostgreSQLContainer<?> postgresServer =
|
||||
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||
|
||||
private Database database;
|
||||
private VelocityEngine velocityEngine;
|
||||
private UUIDGenerator fakeUUIDGenerator;
|
||||
private Clock fakeClock;
|
||||
private BatchBeanCleanerService batchBeanCleanerService;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
gson = new Gson();
|
||||
testLog = LoggerFactory.getLogger(ShowHelpIntegrationTest.class);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
database =
|
||||
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||
velocityEngine = Loader.defaultVelocityEngine();
|
||||
|
||||
final Logger log = LoggerFactory.getLogger(ShowHelp.class);
|
||||
fakeUUIDGenerator = mock(UUIDGenerator.class);
|
||||
fakeClock = mock(Clock.class);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
if (batchBeanCleanerService != null) {
|
||||
batchBeanCleanerService.stopAsync().awaitTerminated(Duration.ofSeconds(10));
|
||||
}
|
||||
}
|
||||
|
||||
private ShowHelp generateShowHelpWithCustomCommands(
|
||||
Map<String, Processor> eventProcessors,
|
||||
Map<String, com.github.polpetta.mezzotre.telegram.command.Processor> commandProcessors)
|
||||
throws TimeoutException {
|
||||
|
||||
batchBeanCleanerService =
|
||||
new BatchBeanCleanerService(
|
||||
LoggerFactory.getLogger(CallbackQueryContextCleaner.class),
|
||||
Pair.of(0, TimeUnit.MILLISECONDS));
|
||||
batchBeanCleanerService.startAsync().awaitRunning(Duration.ofSeconds(10));
|
||||
|
||||
return new ShowHelp(
|
||||
Executors.newSingleThreadExecutor(),
|
||||
new Help(
|
||||
new TemplateContentGenerator(new LocalizedMessageFactory(velocityEngine)),
|
||||
fakeUUIDGenerator),
|
||||
commandProcessors,
|
||||
eventProcessors,
|
||||
// We need to implement a service that is sync - no thread creation!
|
||||
new ChatUtil(fakeClock),
|
||||
new CallbackQueryContextCleaner(
|
||||
batchBeanCleanerService, LoggerFactory.getLogger(CallbackQueryContextCleaner.class)));
|
||||
}
|
||||
|
||||
public static Stream<Arguments> getMessageIdOrNot() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
gson.fromJson(
|
||||
"{\n"
|
||||
+ " \"update_id\": 158712614,\n"
|
||||
+ " \"callback_query\": {\n"
|
||||
+ " \"id\": \"20496049451114620\",\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"is_bot\": false,\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"language_code\": \"en\"\n"
|
||||
+ " },\n"
|
||||
+ " \"message\": {\n"
|
||||
+ " \"message_id\": 2723,\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 244745330,\n"
|
||||
+ " \"is_bot\": true,\n"
|
||||
+ " \"first_name\": \"Dev - DavideBot\",\n"
|
||||
+ " \"username\": \"devdavidebot\"\n"
|
||||
+ " },\n"
|
||||
+ " \"date\": 1681218838,\n"
|
||||
+ " \"chat\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"type\": \"private\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\"\n"
|
||||
+ " },\n"
|
||||
+ " \"text\": \"a message\",\n"
|
||||
+ " \"reply_markup\": {\n"
|
||||
+ " \"inline_keyboard\": [\n"
|
||||
+ " [\n"
|
||||
+ " {\n"
|
||||
+ " \"text\": \"English\",\n"
|
||||
+ " \"callback_data\": \"9a64be11-d086-4bd9-859f-720c43dedcb5\"\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"text\": \"Italian\",\n"
|
||||
+ " \"callback_data\": \"8768d660-f05f-4f4b-bda5-3451ab573d56\"\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "}",
|
||||
Update.class),
|
||||
EditMessageText.class),
|
||||
Arguments.of(
|
||||
gson.fromJson(
|
||||
"{\n"
|
||||
+ " \"update_id\": 158712614,\n"
|
||||
+ " \"callback_query\": {\n"
|
||||
+ " \"id\": \"20496049451114620\",\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"is_bot\": false,\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"language_code\": \"en\"\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "}",
|
||||
Update.class),
|
||||
SendMessage.class));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@Timeout(value = 1, unit = TimeUnit.MINUTES)
|
||||
@MethodSource("getMessageIdOrNot")
|
||||
void shouldShowHelpMessageWithButtons(Update update, Class<?> typeOfMessage) throws Exception {
|
||||
// we have 2 events + group uuid
|
||||
when(fakeUUIDGenerator.generateAsString())
|
||||
.thenReturn("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5")
|
||||
.thenReturn("16507fbd-9f28-48a8-9de1-3ea1c943af67")
|
||||
.thenReturn("0b0ac18e-f621-484e-aa8d-9b176be5b930");
|
||||
when(fakeClock.now()).thenReturn(42L);
|
||||
|
||||
final HashMap<String, com.github.polpetta.mezzotre.telegram.command.Processor> commands =
|
||||
new HashMap<>();
|
||||
final com.github.polpetta.mezzotre.telegram.command.Processor dummy1 =
|
||||
new com.github.polpetta.mezzotre.telegram.command.Processor() {
|
||||
@Override
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return Set.of("/a", "/b");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
TgChat chat, Update update) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocaleDescriptionKeyword() {
|
||||
return "start.cmdDescription";
|
||||
}
|
||||
};
|
||||
final com.github.polpetta.mezzotre.telegram.command.Processor dummy2 =
|
||||
new com.github.polpetta.mezzotre.telegram.command.Processor() {
|
||||
@Override
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return Set.of("/different");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
TgChat chat, Update update) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocaleDescriptionKeyword() {
|
||||
return "help.cmdDescription";
|
||||
}
|
||||
};
|
||||
commands.put("/a", dummy1);
|
||||
commands.put("/b", dummy1);
|
||||
commands.put("/different", dummy2);
|
||||
|
||||
final Map<String, Processor> events = new HashMap<>();
|
||||
|
||||
final Processor dummyEvent1 =
|
||||
new Processor() {
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return "exampleEvent";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeDirectlyInvokedByTheUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getPrettyPrintLocaleKeyName() {
|
||||
return Optional.of("changeLanguage.inlineKeyboardButtonName");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
CallbackQueryContext callbackQueryContext, Update update) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
final Processor dummyEvent2 =
|
||||
new com.github.polpetta.mezzotre.telegram.callbackquery.Processor() {
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return "secondExampleEvent";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeDirectlyInvokedByTheUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getPrettyPrintLocaleKeyName() {
|
||||
return Optional.of("selectLanguageTutorial.inlineKeyboardButtonName");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
CallbackQueryContext callbackQueryContext, Update update) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
events.put(dummyEvent1.getEventName(), dummyEvent1);
|
||||
events.put(dummyEvent2.getEventName(), dummyEvent2);
|
||||
|
||||
final ShowHelp showHelp = generateShowHelpWithCustomCommands(events, commands);
|
||||
final TgChat tgChat = new TgChat(1111111L, new ChatContext());
|
||||
tgChat.save();
|
||||
final CallbackQueryContext callbackQueryContext =
|
||||
new CallbackQueryContext(
|
||||
"1234",
|
||||
"5678",
|
||||
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
||||
.withTelegramChatId(1111111L)
|
||||
.build());
|
||||
callbackQueryContext.save();
|
||||
|
||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotResultFuture =
|
||||
showHelp.process(callbackQueryContext, update);
|
||||
|
||||
final Optional<BaseRequest<?, ?>> baseRequestOptional = gotResultFuture.get();
|
||||
final BaseRequest<?, ?> gotResponse = baseRequestOptional.get();
|
||||
assertInstanceOf(typeOfMessage, gotResponse);
|
||||
final String message = (String) gotResponse.getParameters().get("text");
|
||||
assertEquals(
|
||||
"Here is a list of what I can do:\n"
|
||||
+ "\n"
|
||||
+ "- /a /b: Trigger this very bot\n"
|
||||
+ "- /different: Print the help message\n"
|
||||
+ "\n"
|
||||
+ "You can do the same operations you'd do with the commands aforementioned by"
|
||||
+ " selecting the corresponding button below \uD83D\uDC47",
|
||||
message);
|
||||
final InlineKeyboardButton[][] keyboard =
|
||||
((InlineKeyboardMarkup)
|
||||
gotResponse
|
||||
.getParameters()
|
||||
.getOrDefault("reply_markup", new InlineKeyboardMarkup()))
|
||||
.inlineKeyboard();
|
||||
|
||||
final List<InlineKeyboardButton> keyboardButtons =
|
||||
Stream.of(keyboard).flatMap(Stream::of).toList();
|
||||
|
||||
assertEquals(2, keyboardButtons.size());
|
||||
|
||||
assertFalse(keyboardButtons.get(0).callbackData().isBlank());
|
||||
assertFalse(keyboardButtons.get(1).callbackData().isBlank());
|
||||
|
||||
assertTrue(
|
||||
keyboardButtons.stream()
|
||||
.map(InlineKeyboardButton::text)
|
||||
.anyMatch("Select language"::equals));
|
||||
|
||||
assertTrue(
|
||||
keyboardButtons.stream()
|
||||
.map(InlineKeyboardButton::text)
|
||||
.anyMatch("Change language"::equals));
|
||||
|
||||
final TgChat gotChat = new QTgChat().id.eq(1111111L).findOne();
|
||||
assertNotNull(gotChat);
|
||||
assertTrue(gotChat.getHasHelpBeenShown());
|
||||
final ChatContext gotChatChatContext = gotChat.getChatContext();
|
||||
assertEquals(0, gotChatChatContext.getStep());
|
||||
assertEquals("showHelp", gotChatChatContext.getStage());
|
||||
assertEquals(42, gotChatChatContext.getPreviousMessageUnixTimestampInSeconds());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Timeout(value = 1, unit = TimeUnit.MINUTES)
|
||||
void shouldCleanUpCallbackQueryContextAfterRun() throws Exception {
|
||||
when(fakeClock.now()).thenReturn(42L);
|
||||
|
||||
final ShowHelp showHelp =
|
||||
generateShowHelpWithCustomCommands(Collections.emptyMap(), Collections.emptyMap());
|
||||
final TgChat tgChat = new TgChat(1111111L, new ChatContext());
|
||||
tgChat.save();
|
||||
final Update update =
|
||||
gson.fromJson(
|
||||
"{\n"
|
||||
+ " \"update_id\": 158712614,\n"
|
||||
+ " \"callback_query\": {\n"
|
||||
+ " \"id\": \"20496049451114620\",\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"is_bot\": false,\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"language_code\": \"en\"\n"
|
||||
+ " },\n"
|
||||
+ " \"message\": {\n"
|
||||
+ " \"message_id\": 2723,\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 244745330,\n"
|
||||
+ " \"is_bot\": true,\n"
|
||||
+ " \"first_name\": \"Dev - DavideBot\",\n"
|
||||
+ " \"username\": \"devdavidebot\"\n"
|
||||
+ " },\n"
|
||||
+ " \"date\": 1681218838,\n"
|
||||
+ " \"chat\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"type\": \"private\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\"\n"
|
||||
+ " },\n"
|
||||
+ " \"text\": \"Hello xxxxxx! \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\",\n"
|
||||
+ " \"entities\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"type\": \"bold\",\n"
|
||||
+ " \"offset\": 0,\n"
|
||||
+ " \"length\": 16\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"type\": \"italic\",\n"
|
||||
+ " \"offset\": 26,\n"
|
||||
+ " \"length\": 8\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "}",
|
||||
Update.class);
|
||||
final CallbackQueryContext callbackQueryContext =
|
||||
new CallbackQueryContext(
|
||||
"1234",
|
||||
"5678",
|
||||
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
||||
.withTelegramChatId(1111111L)
|
||||
.build());
|
||||
callbackQueryContext.save();
|
||||
// Create another CallbackQuery context of the same group
|
||||
final CallbackQueryContext callbackQueryContext2 =
|
||||
new CallbackQueryContext(
|
||||
"7891",
|
||||
"5678",
|
||||
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
||||
.withTelegramChatId(1111111L)
|
||||
.build());
|
||||
callbackQueryContext2.save();
|
||||
|
||||
// I know I know, a future containing a future...but it is for testing purposes, otherwise we'd
|
||||
// have to do some while/sleep thread hack which I find horrible to do
|
||||
final CompletableFuture<
|
||||
Tuple3<String, PString<? extends TQRootBean<?, ?>>, CompletableFuture<Integer>>>
|
||||
callBackFuture = new CompletableFuture<>();
|
||||
batchBeanCleanerService.addListener(callBackFuture::complete);
|
||||
|
||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotResultFuture =
|
||||
showHelp.process(callbackQueryContext, update);
|
||||
|
||||
final Optional<BaseRequest<?, ?>> baseRequestOptional = gotResultFuture.get();
|
||||
assertDoesNotThrow(baseRequestOptional::get);
|
||||
|
||||
final Tuple3<String, PString<? extends TQRootBean<?, ?>>, CompletableFuture<Integer>>
|
||||
deletionCallback = callBackFuture.get();
|
||||
|
||||
assertEquals("5678", deletionCallback._1());
|
||||
assertEquals(2, deletionCallback._3().get());
|
||||
assertEquals(
|
||||
0,
|
||||
new QCallbackQueryContext().findCount(),
|
||||
"The CallbackQuery context group has not been cleaned properly!");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.github.polpetta.mezzotre.orm.CallbackQueryContextCleaner;
|
||||
import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.github.polpetta.mezzotre.orm.telegram.ChatUtil;
|
||||
import com.github.polpetta.mezzotre.telegram.model.Help;
|
||||
import com.github.polpetta.types.json.ChatContext;
|
||||
import com.google.gson.Gson;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardButton;
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.parallel.Execution;
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode;
|
||||
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
class ShowHelpTest {
|
||||
|
||||
private static Gson gson;
|
||||
private ShowHelp showHelp;
|
||||
private Help fakeModelHelp;
|
||||
private ChatUtil fakeChatUtil;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
gson = new Gson();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
fakeModelHelp = mock(Help.class);
|
||||
fakeChatUtil = mock(ChatUtil.class);
|
||||
final CallbackQueryContextCleaner fakeCallbackQueryContextCleaner =
|
||||
mock(CallbackQueryContextCleaner.class);
|
||||
|
||||
showHelp =
|
||||
new ShowHelp(
|
||||
Executors.newSingleThreadExecutor(),
|
||||
fakeModelHelp,
|
||||
Collections.emptyMap(),
|
||||
Collections.emptyMap(),
|
||||
fakeChatUtil,
|
||||
fakeCallbackQueryContextCleaner);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateChatContext() throws Exception {
|
||||
final CallbackQueryContext fakeCallbackQueryContext = mock(CallbackQueryContext.class);
|
||||
final Update update =
|
||||
gson.fromJson(
|
||||
"{\n"
|
||||
+ " \"update_id\": 158712614,\n"
|
||||
+ " \"callback_query\": {\n"
|
||||
+ " \"id\": \"20496049451114620\",\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"is_bot\": false,\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"language_code\": \"en\"\n"
|
||||
+ " },\n"
|
||||
+ " \"message\": {\n"
|
||||
+ " \"message_id\": 2723,\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 244745330,\n"
|
||||
+ " \"is_bot\": true,\n"
|
||||
+ " \"first_name\": \"Dev - DavideBot\",\n"
|
||||
+ " \"username\": \"devdavidebot\"\n"
|
||||
+ " },\n"
|
||||
+ " \"date\": 1681218838,\n"
|
||||
+ " \"chat\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"type\": \"private\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\"\n"
|
||||
+ " },\n"
|
||||
+ " \"text\": \"Hello xxxxxx! \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\",\n"
|
||||
+ " \"entities\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"type\": \"bold\",\n"
|
||||
+ " \"offset\": 0,\n"
|
||||
+ " \"length\": 16\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"type\": \"italic\",\n"
|
||||
+ " \"offset\": 26,\n"
|
||||
+ " \"length\": 8\n"
|
||||
+ " }\n"
|
||||
+ " ],\n"
|
||||
+ " \"reply_markup\": {\n"
|
||||
+ " \"inline_keyboard\": [\n"
|
||||
+ " [\n"
|
||||
+ " {\n"
|
||||
+ " \"text\": \"English\",\n"
|
||||
+ " \"callback_data\": \"9a64be11-d086-4bd9-859f-720c43dedcb5\"\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"text\": \"Italian\",\n"
|
||||
+ " \"callback_data\": \"8768d660-f05f-4f4b-bda5-3451ab573d56\"\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "}",
|
||||
Update.class);
|
||||
final TgChat fakeTgChat = mock(TgChat.class);
|
||||
when(fakeTgChat.getId()).thenReturn(1111111L);
|
||||
when(fakeTgChat.getChatContext()).thenReturn(new ChatContext());
|
||||
when(fakeModelHelp.getMessage(eq(fakeTgChat), any())).thenReturn("doesn't matter");
|
||||
when(fakeModelHelp.generateInlineKeyBoardButton(eq(fakeTgChat), eq(Collections.emptyMap())))
|
||||
.thenReturn(new InlineKeyboardButton[] {});
|
||||
when(fakeChatUtil.extractChat(eq(fakeCallbackQueryContext), eq(update)))
|
||||
.thenReturn(Optional.of(fakeTgChat));
|
||||
|
||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotResponseFuture =
|
||||
showHelp.process(fakeCallbackQueryContext, update);
|
||||
final Optional<BaseRequest<?, ?>> gotResponseOpt = gotResponseFuture.get();
|
||||
|
||||
verify(fakeChatUtil, times(1))
|
||||
.updateChatContext(eq(fakeTgChat), eq("showHelp"), eq(0), eq(Collections.emptyMap()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAddInlineKeyboardIfButtonsAreReturned() throws Exception {
|
||||
final CallbackQueryContext fakeCallbackQueryContext = mock(CallbackQueryContext.class);
|
||||
final Update update =
|
||||
gson.fromJson(
|
||||
"{\n"
|
||||
+ " \"update_id\": 158712614,\n"
|
||||
+ " \"callback_query\": {\n"
|
||||
+ " \"id\": \"20496049451114620\",\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"is_bot\": false,\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"language_code\": \"en\"\n"
|
||||
+ " },\n"
|
||||
+ " \"message\": {\n"
|
||||
+ " \"message_id\": 2723,\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 244745330,\n"
|
||||
+ " \"is_bot\": true,\n"
|
||||
+ " \"first_name\": \"Dev - DavideBot\",\n"
|
||||
+ " \"username\": \"devdavidebot\"\n"
|
||||
+ " },\n"
|
||||
+ " \"date\": 1681218838,\n"
|
||||
+ " \"chat\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"type\": \"private\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\"\n"
|
||||
+ " },\n"
|
||||
+ " \"text\": \"Hello xxxxxx! \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\",\n"
|
||||
+ " \"entities\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"type\": \"bold\",\n"
|
||||
+ " \"offset\": 0,\n"
|
||||
+ " \"length\": 16\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"type\": \"italic\",\n"
|
||||
+ " \"offset\": 26,\n"
|
||||
+ " \"length\": 8\n"
|
||||
+ " }\n"
|
||||
+ " ],\n"
|
||||
+ " \"reply_markup\": {\n"
|
||||
+ " \"inline_keyboard\": [\n"
|
||||
+ " [\n"
|
||||
+ " {\n"
|
||||
+ " \"text\": \"English\",\n"
|
||||
+ " \"callback_data\": \"9a64be11-d086-4bd9-859f-720c43dedcb5\"\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"text\": \"Italian\",\n"
|
||||
+ " \"callback_data\": \"8768d660-f05f-4f4b-bda5-3451ab573d56\"\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "}",
|
||||
Update.class);
|
||||
final TgChat fakeTgChat = mock(TgChat.class);
|
||||
when(fakeTgChat.getId()).thenReturn(1111111L);
|
||||
when(fakeTgChat.getChatContext()).thenReturn(new ChatContext());
|
||||
when(fakeModelHelp.getMessage(eq(fakeTgChat), any())).thenReturn("doesn't matter");
|
||||
when(fakeModelHelp.generateInlineKeyBoardButton(eq(fakeTgChat), eq(Collections.emptyMap())))
|
||||
.thenReturn(new InlineKeyboardButton[] {new InlineKeyboardButton("example1")});
|
||||
when(fakeChatUtil.extractChat(eq(fakeCallbackQueryContext), eq(update)))
|
||||
.thenReturn(Optional.of(fakeTgChat));
|
||||
|
||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotResponseFuture =
|
||||
showHelp.process(fakeCallbackQueryContext, update);
|
||||
final Optional<BaseRequest<?, ?>> gotResponseOpt = gotResponseFuture.get();
|
||||
final BaseRequest<?, ?> gotResponse = gotResponseOpt.get();
|
||||
|
||||
final InlineKeyboardMarkup replyMarkup =
|
||||
(InlineKeyboardMarkup) gotResponse.getParameters().get("reply_markup");
|
||||
assertEquals(1, replyMarkup.inlineKeyboard()[0].length);
|
||||
assertEquals("example1", replyMarkup.inlineKeyboard()[0][0].text());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAddAnyKeyboardIfThereAreNoButtons() throws Exception {
|
||||
final CallbackQueryContext fakeCallbackQueryContext = mock(CallbackQueryContext.class);
|
||||
final Update update =
|
||||
gson.fromJson(
|
||||
"{\n"
|
||||
+ " \"update_id\": 158712614,\n"
|
||||
+ " \"callback_query\": {\n"
|
||||
+ " \"id\": \"20496049451114620\",\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"is_bot\": false,\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"language_code\": \"en\"\n"
|
||||
+ " },\n"
|
||||
+ " \"message\": {\n"
|
||||
+ " \"message_id\": 2723,\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 244745330,\n"
|
||||
+ " \"is_bot\": true,\n"
|
||||
+ " \"first_name\": \"Dev - DavideBot\",\n"
|
||||
+ " \"username\": \"devdavidebot\"\n"
|
||||
+ " },\n"
|
||||
+ " \"date\": 1681218838,\n"
|
||||
+ " \"chat\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"type\": \"private\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\"\n"
|
||||
+ " },\n"
|
||||
+ " \"text\": \"Hello xxxxxx! \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\",\n"
|
||||
+ " \"entities\": [\n"
|
||||
+ " {\n"
|
||||
+ " \"type\": \"bold\",\n"
|
||||
+ " \"offset\": 0,\n"
|
||||
+ " \"length\": 16\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"type\": \"italic\",\n"
|
||||
+ " \"offset\": 26,\n"
|
||||
+ " \"length\": 8\n"
|
||||
+ " }\n"
|
||||
+ " ],\n"
|
||||
+ " \"reply_markup\": {\n"
|
||||
+ " \"inline_keyboard\": [\n"
|
||||
+ " [\n"
|
||||
+ " {\n"
|
||||
+ " \"text\": \"English\",\n"
|
||||
+ " \"callback_data\": \"9a64be11-d086-4bd9-859f-720c43dedcb5\"\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"text\": \"Italian\",\n"
|
||||
+ " \"callback_data\": \"8768d660-f05f-4f4b-bda5-3451ab573d56\"\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "}",
|
||||
Update.class);
|
||||
final TgChat fakeTgChat = mock(TgChat.class);
|
||||
when(fakeTgChat.getId()).thenReturn(1111111L);
|
||||
when(fakeTgChat.getChatContext()).thenReturn(new ChatContext());
|
||||
when(fakeModelHelp.getMessage(eq(fakeTgChat), any())).thenReturn("doesn't matter");
|
||||
when(fakeModelHelp.generateInlineKeyBoardButton(eq(fakeTgChat), eq(Collections.emptyMap())))
|
||||
.thenReturn(new InlineKeyboardButton[] {});
|
||||
when(fakeChatUtil.extractChat(eq(fakeCallbackQueryContext), eq(update)))
|
||||
.thenReturn(Optional.of(fakeTgChat));
|
||||
|
||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotResponseFuture =
|
||||
showHelp.process(fakeCallbackQueryContext, update);
|
||||
final Optional<BaseRequest<?, ?>> gotResponseOpt = gotResponseFuture.get();
|
||||
final BaseRequest<?, ?> gotResponse = gotResponseOpt.get();
|
||||
final InlineKeyboardMarkup replyMarkup =
|
||||
(InlineKeyboardMarkup) gotResponse.getParameters().get("reply_markup");
|
||||
assertNull(replyMarkup);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.parallel.Execution;
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode;
|
||||
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
class UtilTest {
|
||||
|
||||
private static Gson gson;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
gson = new Gson();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProvideAMessageIdGivenTheUpdate() {
|
||||
final Update update =
|
||||
gson.fromJson(
|
||||
"{\n"
|
||||
+ " \"update_id\": 158712614,\n"
|
||||
+ " \"callback_query\": {\n"
|
||||
+ " \"id\": \"20496049451114620\",\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"is_bot\": false,\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"language_code\": \"en\"\n"
|
||||
+ " },\n"
|
||||
+ " \"message\": {\n"
|
||||
+ " \"message_id\": 2723,\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 244745330,\n"
|
||||
+ " \"is_bot\": true,\n"
|
||||
+ " \"first_name\": \"Dev - DavideBot\",\n"
|
||||
+ " \"username\": \"devdavidebot\"\n"
|
||||
+ " },\n"
|
||||
+ " \"date\": 1681218838,\n"
|
||||
+ " \"chat\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"type\": \"private\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\"\n"
|
||||
+ " },\n"
|
||||
+ " \"text\": \"a message\",\n"
|
||||
+ " \"reply_markup\": {\n"
|
||||
+ " \"inline_keyboard\": [\n"
|
||||
+ " [\n"
|
||||
+ " {\n"
|
||||
+ " \"text\": \"English\",\n"
|
||||
+ " \"callback_data\": \"9a64be11-d086-4bd9-859f-720c43dedcb5\"\n"
|
||||
+ " },\n"
|
||||
+ " {\n"
|
||||
+ " \"text\": \"Italian\",\n"
|
||||
+ " \"callback_data\": \"8768d660-f05f-4f4b-bda5-3451ab573d56\"\n"
|
||||
+ " }\n"
|
||||
+ " ]\n"
|
||||
+ " ]\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "}",
|
||||
Update.class);
|
||||
|
||||
assertEquals(
|
||||
2723, Util.extractMessageId(update).get(), "A message id should be returned, 2723");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGiveAnEmptyOptionalWithoutMessageId() {
|
||||
final Update update =
|
||||
gson.fromJson(
|
||||
"{\n"
|
||||
+ " \"update_id\": 158712614,\n"
|
||||
+ " \"callback_query\": {\n"
|
||||
+ " \"id\": \"20496049451114620\",\n"
|
||||
+ " \"from\": {\n"
|
||||
+ " \"id\": 1111111,\n"
|
||||
+ " \"is_bot\": false,\n"
|
||||
+ " \"first_name\": \"Test Firstname\",\n"
|
||||
+ " \"last_name\": \"Test Lastname\",\n"
|
||||
+ " \"username\": \"Testusername\",\n"
|
||||
+ " \"language_code\": \"en\"\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "}",
|
||||
Update.class);
|
||||
|
||||
assertTrue(Util.extractMessageId(update).isEmpty(), "The shouldn't be any message id");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
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.i18n.TemplateContentGenerator;
|
||||
import com.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.github.polpetta.mezzotre.orm.model.query.QTgChat;
|
||||
import com.github.polpetta.mezzotre.orm.telegram.ChatUtil;
|
||||
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;
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardButton;
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import com.pengrad.telegrambot.request.SendMessage;
|
||||
import io.ebean.Database;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.velocity.app.VelocityEngine;
|
||||
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.slf4j.Logger;
|
||||
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 HelpIntegrationTest {
|
||||
private static Gson gson;
|
||||
|
||||
@Container
|
||||
private final PostgreSQLContainer<?> postgresServer =
|
||||
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||
|
||||
private Database database;
|
||||
private VelocityEngine velocityEngine;
|
||||
private Clock fakeClock;
|
||||
private Help help;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
gson = new Gson();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
database =
|
||||
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||
velocityEngine = Loader.defaultVelocityEngine();
|
||||
|
||||
final Logger log = LoggerFactory.getLogger(Start.class);
|
||||
fakeClock = mock(Clock.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProvideMessageWithButtons() throws Exception {
|
||||
when(fakeClock.now()).thenReturn(42L);
|
||||
|
||||
final TgChat tgChat = new TgChat(1111111L, new ChatContext(), "en-US", false);
|
||||
tgChat.save();
|
||||
|
||||
final HashMap<String, Processor> commands = new HashMap<>();
|
||||
final Processor dummy1 =
|
||||
new Processor() {
|
||||
@Override
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return Set.of("/a", "/b");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
TgChat chat, Update update) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocaleDescriptionKeyword() {
|
||||
return "start.cmdDescription";
|
||||
}
|
||||
};
|
||||
final Processor dummy2 =
|
||||
new Processor() {
|
||||
@Override
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return Set.of("/different");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
TgChat chat, Update update) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocaleDescriptionKeyword() {
|
||||
return "help.cmdDescription";
|
||||
}
|
||||
};
|
||||
commands.put("/a", dummy1);
|
||||
commands.put("/b", dummy1);
|
||||
commands.put("/different", dummy2);
|
||||
|
||||
final Map<String, com.github.polpetta.mezzotre.telegram.callbackquery.Processor> events =
|
||||
new HashMap<>();
|
||||
|
||||
final com.github.polpetta.mezzotre.telegram.callbackquery.Processor dummyEvent1 =
|
||||
new com.github.polpetta.mezzotre.telegram.callbackquery.Processor() {
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return "exampleEvent";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeDirectlyInvokedByTheUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getPrettyPrintLocaleKeyName() {
|
||||
return Optional.of("changeLanguage.inlineKeyboardButtonName");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
CallbackQueryContext callbackQueryContext, Update update) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
final com.github.polpetta.mezzotre.telegram.callbackquery.Processor dummyEvent2 =
|
||||
new com.github.polpetta.mezzotre.telegram.callbackquery.Processor() {
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return "secondExampleEvent";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeDirectlyInvokedByTheUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getPrettyPrintLocaleKeyName() {
|
||||
return Optional.of("selectLanguageTutorial.inlineKeyboardButtonName");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
CallbackQueryContext callbackQueryContext, Update update) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
events.put(dummyEvent1.getEventName(), dummyEvent1);
|
||||
events.put(dummyEvent2.getEventName(), dummyEvent2);
|
||||
|
||||
final com.github.polpetta.mezzotre.telegram.model.Help modelHelp =
|
||||
new com.github.polpetta.mezzotre.telegram.model.Help(
|
||||
new TemplateContentGenerator(new LocalizedMessageFactory(velocityEngine)),
|
||||
new UUIDGenerator());
|
||||
|
||||
help =
|
||||
new Help(
|
||||
Executors.newSingleThreadExecutor(),
|
||||
commands,
|
||||
events,
|
||||
modelHelp,
|
||||
new ChatUtil(fakeClock));
|
||||
|
||||
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\":\"/help\"\n"
|
||||
+ "}\n"
|
||||
+ "}",
|
||||
Update.class);
|
||||
|
||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotFuture = help.process(tgChat, update);
|
||||
final Optional<BaseRequest<?, ?>> gotResponseOpt = gotFuture.get();
|
||||
final BaseRequest<?, ?> gotResponse = gotResponseOpt.get();
|
||||
assertInstanceOf(SendMessage.class, gotResponse);
|
||||
final String message = (String) gotResponse.getParameters().get("text");
|
||||
assertEquals(
|
||||
"Here is a list of what I can do:\n"
|
||||
+ "\n"
|
||||
+ "- /a /b: Trigger this very bot\n"
|
||||
+ "- /different: Print the help message\n"
|
||||
+ "\n"
|
||||
+ "You can do the same operations you'd do with the commands aforementioned by"
|
||||
+ " selecting the corresponding button below \uD83D\uDC47",
|
||||
message);
|
||||
|
||||
final InlineKeyboardButton[][] keyboard =
|
||||
((InlineKeyboardMarkup)
|
||||
gotResponse
|
||||
.getParameters()
|
||||
.getOrDefault("reply_markup", new InlineKeyboardMarkup()))
|
||||
.inlineKeyboard();
|
||||
|
||||
final List<InlineKeyboardButton> keyboardButtons =
|
||||
Stream.of(keyboard).flatMap(Stream::of).toList();
|
||||
|
||||
assertEquals(2, keyboardButtons.size());
|
||||
|
||||
assertFalse(keyboardButtons.get(0).callbackData().isBlank());
|
||||
assertFalse(keyboardButtons.get(1).callbackData().isBlank());
|
||||
|
||||
assertTrue(
|
||||
keyboardButtons.stream()
|
||||
.map(InlineKeyboardButton::text)
|
||||
.anyMatch("Select language"::equals));
|
||||
|
||||
assertTrue(
|
||||
keyboardButtons.stream()
|
||||
.map(InlineKeyboardButton::text)
|
||||
.anyMatch("Change language"::equals));
|
||||
|
||||
final TgChat gotChat = new QTgChat().id.eq(1111111L).findOne();
|
||||
assertNotNull(gotChat);
|
||||
assertTrue(gotChat.getHasHelpBeenShown());
|
||||
final ChatContext gotChatChatContext = gotChat.getChatContext();
|
||||
assertEquals(0, gotChatChatContext.getStep());
|
||||
assertEquals("/help", gotChatChatContext.getStage());
|
||||
assertEquals(42, gotChatChatContext.getPreviousMessageUnixTimestampInSeconds());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.github.polpetta.mezzotre.orm.telegram.ChatUtil;
|
||||
import com.github.polpetta.types.json.ChatContext;
|
||||
import com.google.gson.Gson;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardButton;
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.parallel.Execution;
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode;
|
||||
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
class HelpTest {
|
||||
|
||||
private static Gson gson;
|
||||
private com.github.polpetta.mezzotre.telegram.model.Help fakeModelHelp;
|
||||
private ChatUtil fakeChatUtil;
|
||||
private Help help;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() {
|
||||
gson = new Gson();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
final Map<String, Processor> commands = Collections.emptyMap();
|
||||
final Map<String, com.github.polpetta.mezzotre.telegram.callbackquery.Processor> events =
|
||||
Collections.emptyMap();
|
||||
|
||||
fakeModelHelp = mock(com.github.polpetta.mezzotre.telegram.model.Help.class);
|
||||
fakeChatUtil = mock(ChatUtil.class);
|
||||
|
||||
help =
|
||||
new Help(
|
||||
Executors.newSingleThreadExecutor(), commands, events, fakeModelHelp, fakeChatUtil);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateChatContext() throws Exception {
|
||||
final TgChat fakeTgChat = mock(TgChat.class);
|
||||
when(fakeTgChat.getId()).thenReturn(1111111L);
|
||||
when(fakeTgChat.getLocale()).thenReturn("en-US");
|
||||
when(fakeTgChat.getChatContext()).thenReturn(new ChatContext());
|
||||
when(fakeModelHelp.getMessage(eq(fakeTgChat), eq(Collections.emptyMap())))
|
||||
.thenReturn("doesn't matter");
|
||||
when(fakeModelHelp.generateInlineKeyBoardButton(eq(fakeTgChat), eq(Collections.emptyMap())))
|
||||
.thenReturn(new InlineKeyboardButton[] {});
|
||||
|
||||
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\":\"/help\"\n"
|
||||
+ "}\n"
|
||||
+ "}",
|
||||
Update.class);
|
||||
|
||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotResponseFuture =
|
||||
help.process(fakeTgChat, update);
|
||||
assertDoesNotThrow(() -> gotResponseFuture.get());
|
||||
verify(fakeChatUtil, times(1))
|
||||
.updateChatContext(eq(fakeTgChat), eq("/help"), eq(0), eq(Collections.emptyMap()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendKeyboardIfButtonsAreReturned() throws Exception {
|
||||
final TgChat fakeTgChat = mock(TgChat.class);
|
||||
when(fakeTgChat.getId()).thenReturn(1111111L);
|
||||
when(fakeTgChat.getLocale()).thenReturn("en-US");
|
||||
when(fakeTgChat.getChatContext()).thenReturn(new ChatContext());
|
||||
when(fakeModelHelp.getMessage(eq(fakeTgChat), eq(Collections.emptyMap())))
|
||||
.thenReturn("doesn't matter");
|
||||
when(fakeModelHelp.generateInlineKeyBoardButton(eq(fakeTgChat), eq(Collections.emptyMap())))
|
||||
.thenReturn(new InlineKeyboardButton[] {new InlineKeyboardButton("example1")});
|
||||
|
||||
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\":\"/help\"\n"
|
||||
+ "}\n"
|
||||
+ "}",
|
||||
Update.class);
|
||||
|
||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotResponseFuture =
|
||||
help.process(fakeTgChat, update);
|
||||
final Optional<BaseRequest<?, ?>> gotResponseOpt = gotResponseFuture.get();
|
||||
final BaseRequest<?, ?> gotResponse = gotResponseOpt.get();
|
||||
final InlineKeyboardMarkup replyMarkup =
|
||||
(InlineKeyboardMarkup) gotResponse.getParameters().get("reply_markup");
|
||||
assertEquals(1, replyMarkup.inlineKeyboard()[0].length);
|
||||
assertEquals("example1", replyMarkup.inlineKeyboard()[0][0].text());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAppendKeyboardIfNoButtonIsPresent() throws Exception {
|
||||
final TgChat fakeTgChat = mock(TgChat.class);
|
||||
when(fakeTgChat.getId()).thenReturn(1111111L);
|
||||
when(fakeTgChat.getLocale()).thenReturn("en-US");
|
||||
when(fakeTgChat.getChatContext()).thenReturn(new ChatContext());
|
||||
when(fakeModelHelp.getMessage(eq(fakeTgChat), eq(Collections.emptyMap())))
|
||||
.thenReturn("doesn't matter");
|
||||
when(fakeModelHelp.generateInlineKeyBoardButton(eq(fakeTgChat), eq(Collections.emptyMap())))
|
||||
.thenReturn(new InlineKeyboardButton[] {});
|
||||
|
||||
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\":\"/help\"\n"
|
||||
+ "}\n"
|
||||
+ "}",
|
||||
Update.class);
|
||||
|
||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> gotResponseFuture =
|
||||
help.process(fakeTgChat, update);
|
||||
final Optional<BaseRequest<?, ?>> gotResponseOpt = gotResponseFuture.get();
|
||||
final BaseRequest<?, ?> gotResponse = gotResponseOpt.get();
|
||||
final InlineKeyboardMarkup replyMarkup =
|
||||
(InlineKeyboardMarkup) gotResponse.getParameters().get("reply_markup");
|
||||
assertNull(replyMarkup);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package com.github.polpetta.mezzotre.telegram.command;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.parallel.Execution;
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode;
|
||||
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
class ProcessorTest {
|
||||
|
||||
@Test
|
||||
void shouldGetSimpleNameClassIfNotDefined() {
|
||||
|
||||
class Test implements Processor {
|
||||
|
||||
@Override
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(TgChat chat, Update update) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final Test test = new Test();
|
||||
|
||||
assertEquals("test.cmdDescription", test.getLocaleDescriptionKeyword());
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ 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.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
@ -32,12 +33,12 @@ class RouterTest {
|
|||
gson = new Gson();
|
||||
|
||||
dummyEmptyExampleProcessor = mock(Processor.class);
|
||||
when(dummyEmptyExampleProcessor.getTriggerKeyword()).thenReturn("/example");
|
||||
when(dummyEmptyExampleProcessor.getTriggerKeywords()).thenReturn(Set.of("/example"));
|
||||
when(dummyEmptyExampleProcessor.process(any(), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.empty()));
|
||||
|
||||
anotherKeyWithResultProcessor = mock(Processor.class);
|
||||
when(anotherKeyWithResultProcessor.getTriggerKeyword()).thenReturn("/anotherExample");
|
||||
when(anotherKeyWithResultProcessor.getTriggerKeywords()).thenReturn(Set.of("/anotherExample"));
|
||||
when(anotherKeyWithResultProcessor.process(any(), any()))
|
||||
.thenReturn(
|
||||
CompletableFuture.completedFuture(Optional.of(new SendMessage(1234L, "hello world"))));
|
||||
|
@ -46,7 +47,10 @@ class RouterTest {
|
|||
@Test
|
||||
void shouldMessageExampleMessageAndGetEmptyOptional() throws Exception {
|
||||
final Router router =
|
||||
new Router(Set.of(dummyEmptyExampleProcessor), Executors.newSingleThreadExecutor());
|
||||
new Router(
|
||||
Map.of("/example", dummyEmptyExampleProcessor),
|
||||
Executors.newSingleThreadExecutor(),
|
||||
mock(NotFoundFactory.class));
|
||||
final TgChat fakeChat = mock(TgChat.class);
|
||||
when(fakeChat.getChatContext()).thenReturn(new ChatContext());
|
||||
final Update update =
|
||||
|
@ -85,8 +89,13 @@ class RouterTest {
|
|||
void shouldSelectRightExecutorAndReturnResult() throws Exception {
|
||||
final Router router =
|
||||
new Router(
|
||||
Set.of(dummyEmptyExampleProcessor, anotherKeyWithResultProcessor),
|
||||
Executors.newSingleThreadExecutor());
|
||||
Map.of(
|
||||
"/example",
|
||||
dummyEmptyExampleProcessor,
|
||||
"/anotherExample",
|
||||
anotherKeyWithResultProcessor),
|
||||
Executors.newSingleThreadExecutor(),
|
||||
mock(NotFoundFactory.class));
|
||||
final TgChat fakeChat = mock(TgChat.class);
|
||||
when(fakeChat.getChatContext()).thenReturn(new ChatContext());
|
||||
final Update update =
|
||||
|
|
|
@ -6,9 +6,11 @@ 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.i18n.TemplateContentGenerator;
|
||||
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.mezzotre.orm.telegram.ChatUtil;
|
||||
import com.github.polpetta.mezzotre.util.Clock;
|
||||
import com.github.polpetta.mezzotre.util.UUIDGenerator;
|
||||
import com.github.polpetta.types.json.ChatContext;
|
||||
|
@ -22,8 +24,6 @@ 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;
|
||||
|
@ -62,12 +62,7 @@ class StartIntegrationTest {
|
|||
void setUp() throws Exception {
|
||||
database =
|
||||
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||
velocityEngine = new VelocityEngine();
|
||||
velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADERS, "classpath");
|
||||
velocityEngine.setProperty(
|
||||
"resource.loader.classpath.class", ClasspathResourceLoader.class.getName());
|
||||
velocityEngine.init();
|
||||
localizedMessageFactory = new LocalizedMessageFactory(velocityEngine);
|
||||
velocityEngine = Loader.defaultVelocityEngine();
|
||||
|
||||
final Logger log = LoggerFactory.getLogger(Start.class);
|
||||
|
||||
|
@ -76,11 +71,12 @@ class StartIntegrationTest {
|
|||
|
||||
start =
|
||||
new Start(
|
||||
localizedMessageFactory,
|
||||
new TemplateContentGenerator(new LocalizedMessageFactory(velocityEngine)),
|
||||
Executors.newSingleThreadExecutor(),
|
||||
log,
|
||||
fakeUUIDGenerator,
|
||||
fakeClock);
|
||||
"Mezzotre",
|
||||
new ChatUtil(fakeClock));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -127,7 +123,7 @@ class StartIntegrationTest {
|
|||
assertInstanceOf(SendMessage.class, gotMessage);
|
||||
final String message = (String) gotMessage.getParameters().get("text");
|
||||
assertEquals(
|
||||
"**Hello Test Firstname! \uD83D\uDC4B**\n\n"
|
||||
"*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);
|
||||
|
@ -185,7 +181,7 @@ class StartIntegrationTest {
|
|||
assertInstanceOf(SendMessage.class, gotMessage);
|
||||
final String message = (String) gotMessage.getParameters().get("text");
|
||||
assertEquals(
|
||||
"**Hello Test Firstname! \uD83D\uDC4B**\n\n"
|
||||
"*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);
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
package com.github.polpetta.mezzotre.telegram.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
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.i18n.TemplateContentGenerator;
|
||||
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.telegram.callbackquery.Processor;
|
||||
import com.github.polpetta.mezzotre.util.UUIDGenerator;
|
||||
import com.github.polpetta.types.json.ChatContext;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardButton;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import io.ebean.Database;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
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 HelpIntegrationTest {
|
||||
|
||||
@Container
|
||||
private final PostgreSQLContainer<?> postgresServer =
|
||||
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||
|
||||
private TemplateContentGenerator templateContentGenerator;
|
||||
private UUIDGenerator fakeUUIDGenerator;
|
||||
private Help help;
|
||||
private Database database;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
database =
|
||||
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||
templateContentGenerator =
|
||||
new TemplateContentGenerator(new LocalizedMessageFactory(Loader.defaultVelocityEngine()));
|
||||
fakeUUIDGenerator = mock(UUIDGenerator.class);
|
||||
|
||||
help = new Help(templateContentGenerator, fakeUUIDGenerator);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGenerateAGoodHelpMessage() {
|
||||
final TgChat tgChat = new TgChat(11111L, new ChatContext());
|
||||
tgChat.save();
|
||||
|
||||
final com.github.polpetta.mezzotre.telegram.command.Processor dummyCommand1 =
|
||||
new com.github.polpetta.mezzotre.telegram.command.Processor() {
|
||||
@Override
|
||||
public Set<String> getTriggerKeywords() {
|
||||
return Set.of("/example", "/another");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
TgChat chat, Update update) {
|
||||
return CompletableFuture.completedFuture(Optional.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocaleDescriptionKeyword() {
|
||||
return "help.cmdDescription";
|
||||
}
|
||||
};
|
||||
|
||||
final String gotMessage =
|
||||
help.getMessage(tgChat, Map.of("/example", dummyCommand1, "/another", dummyCommand1));
|
||||
assertEquals(
|
||||
"Here is a list of what I can do:\n"
|
||||
+ "\n"
|
||||
+ "- /another /example: Print the help message\n"
|
||||
+ "\n"
|
||||
+ "You can do the same operations you'd do with the commands aforementioned by"
|
||||
+ " selecting the corresponding button below \uD83D\uDC47",
|
||||
gotMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGenerateButtonsCorrectly() {
|
||||
final TgChat tgChat = new TgChat(111111L, new ChatContext());
|
||||
tgChat.save();
|
||||
when(fakeUUIDGenerator.generateAsString())
|
||||
.thenReturn("53dc6dca-1042-4bc7-beb8-ce2a34df6a54")
|
||||
.thenReturn("d5d3e016-7b60-4f1a-bd79-e1a6bff32f17");
|
||||
|
||||
final Processor visibleProcessor1 =
|
||||
new Processor() {
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return "eventName";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeDirectlyInvokedByTheUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getPrettyPrintLocaleKeyName() {
|
||||
return Optional.of("changeLanguage.inlineKeyboardButtonName");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
CallbackQueryContext callbackQueryContext, Update update) {
|
||||
return CompletableFuture.completedFuture(Optional.empty());
|
||||
}
|
||||
};
|
||||
|
||||
final Processor invisibleProcessor1 =
|
||||
new Processor() {
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return "invisible";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||
CallbackQueryContext callbackQueryContext, Update update) {
|
||||
return CompletableFuture.completedFuture(Optional.empty());
|
||||
}
|
||||
};
|
||||
|
||||
final InlineKeyboardButton[] buttons =
|
||||
help.generateInlineKeyBoardButton(
|
||||
tgChat,
|
||||
Map.of(
|
||||
visibleProcessor1.getEventName(),
|
||||
visibleProcessor1,
|
||||
invisibleProcessor1.getEventName(),
|
||||
invisibleProcessor1));
|
||||
|
||||
assertEquals(1, buttons.length);
|
||||
assertEquals("Change language", buttons[0].text());
|
||||
|
||||
assertEquals(1, new QCallbackQueryContext().findCount());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package com.github.polpetta.mezzotre.telegram.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.github.polpetta.mezzotre.i18n.TemplateContentGenerator;
|
||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||
import com.github.polpetta.mezzotre.util.UUIDGenerator;
|
||||
import java.util.Collections;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.parallel.Execution;
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode;
|
||||
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
class HelpTest {
|
||||
|
||||
private TemplateContentGenerator fakeTemplateContentGenerator;
|
||||
private UUIDGenerator fakeUUIDGenerator;
|
||||
private Help help;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
fakeTemplateContentGenerator = mock(TemplateContentGenerator.class);
|
||||
fakeUUIDGenerator = mock(UUIDGenerator.class);
|
||||
|
||||
help = new Help(fakeTemplateContentGenerator, fakeUUIDGenerator);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallTemplateContentGeneratorRight() {
|
||||
|
||||
final TgChat fakeChat = mock(TgChat.class);
|
||||
when(fakeChat.getLocale()).thenReturn("en-US");
|
||||
|
||||
final String message = help.getMessage(fakeChat, Collections.emptyMap());
|
||||
|
||||
verify(fakeTemplateContentGenerator, times(1))
|
||||
.mergeTemplate(any(), eq("en-US"), eq("template/telegram/help.vm"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package com.github.polpetta.mezzotre.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import com.google.common.util.concurrent.Service;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.parallel.Execution;
|
||||
import org.junit.jupiter.api.parallel.ExecutionMode;
|
||||
|
||||
@Execution(ExecutionMode.CONCURRENT)
|
||||
class ServiceModuleTest {
|
||||
|
||||
/**
|
||||
* This test seems stupid, but it is actually important that the behavior of the extension is
|
||||
* always with a {@code lateInit} set to true, otherwise services won't be alive for the whole JVM
|
||||
* execution
|
||||
*/
|
||||
@Test
|
||||
void shouldBeLateInit() {
|
||||
// Necessary otherwise ServiceManager will throw exception in the constructor 🤦
|
||||
final Service dumbService =
|
||||
new Service() {
|
||||
@Override
|
||||
public Service startAsync() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State state() {
|
||||
return State.NEW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Service stopAsync() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void awaitRunning() {}
|
||||
|
||||
@Override
|
||||
public void awaitRunning(long timeout, TimeUnit unit) throws TimeoutException {}
|
||||
|
||||
@Override
|
||||
public void awaitTerminated() {}
|
||||
|
||||
@Override
|
||||
public void awaitTerminated(long timeout, TimeUnit unit) throws TimeoutException {}
|
||||
|
||||
@Override
|
||||
public Throwable failureCause() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Listener listener, Executor executor) {}
|
||||
};
|
||||
|
||||
final ServiceModule serviceModule = new ServiceModule(List.of(dumbService));
|
||||
|
||||
assertTrue(serviceModule.lateinit());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue