* create a dynamic help message * add documentation and add way more tests * add batch entity cleaning service and add service structure Co-authored-by: Davide Polonio <poloniodavide@gmail.com> Reviewed-on: #3devel
parent
00bac97e59
commit
5788d59516
|
@ -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:
|
Build is achieved through Maven. To build a `jar` run:
|
||||||
|
|
||||||
```shell
|
```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.
|
auto-startup via systemd or openrc units.
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
### Automatic testing
|
### 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
|
### Manual testing
|
||||||
|
|
||||||
For a manual approach, just open a terminal and type `mvn jooby:run`. Assuming you have a database locally available
|
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
|
can develop and see live changes of your Mezzotre on the fly. Finally, by using Postman, you can simulate incoming
|
||||||
Telegram events.
|
Telegram events.
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
db.url = "jdbc:postgresql://localhost:5433/example"
|
db.url = "jdbc:postgresql://localhost:5433/example"
|
||||||
db.user = example
|
db.user = example
|
||||||
db.password = example
|
db.password = example
|
||||||
|
|
||||||
|
hikari.autoCommit = false
|
||||||
|
hikari.maximumPoolSize = 4
|
||||||
|
|
||||||
telegram.key = akey
|
telegram.key = akey
|
||||||
|
|
||||||
application.lang = en en-US it it-IT
|
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>
|
<jsonschema2pojo.version>1.1.1</jsonschema2pojo.version>
|
||||||
<jackson-databind.version>2.13.3</jackson-databind.version>
|
<jackson-databind.version>2.13.3</jackson-databind.version>
|
||||||
<junit-jupiter-params.version>5.9.1</junit-jupiter-params.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>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -86,6 +88,12 @@
|
||||||
<artifactId>jooby-guice</artifactId>
|
<artifactId>jooby-guice</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.inject.extensions</groupId>
|
||||||
|
<artifactId>guice-assistedinject</artifactId>
|
||||||
|
<version>${google-guice.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.swagger.core.v3</groupId>
|
<groupId>io.swagger.core.v3</groupId>
|
||||||
<artifactId>swagger-annotations</artifactId>
|
<artifactId>swagger-annotations</artifactId>
|
||||||
|
@ -233,7 +241,11 @@
|
||||||
<version>31.1-jre</version>
|
<version>31.1-jre</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_annotations</artifactId>
|
||||||
|
<version>${error-prone.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -272,12 +284,19 @@
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.6.2</version>
|
<version>3.8.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<compilerArgs>
|
<compilerArgs>
|
||||||
<arg>-parameters</arg>
|
<arg>-parameters</arg>
|
||||||
|
<arg>-XDcompilePolicy=simple</arg>
|
||||||
|
<arg>-Xplugin:ErrorProne</arg>
|
||||||
</compilerArgs>
|
</compilerArgs>
|
||||||
<annotationProcessorPaths>
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>com.google.errorprone</groupId>
|
||||||
|
<artifactId>error_prone_core</artifactId>
|
||||||
|
<version>${error-prone.version}</version>
|
||||||
|
</path>
|
||||||
<path>
|
<path>
|
||||||
<groupId>io.jooby</groupId>
|
<groupId>io.jooby</groupId>
|
||||||
<artifactId>jooby-apt</artifactId>
|
<artifactId>jooby-apt</artifactId>
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
package com.github.polpetta.mezzotre;
|
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.Telegram;
|
||||||
import com.github.polpetta.mezzotre.route.di.Route;
|
import com.github.polpetta.mezzotre.telegram.callbackquery.CallbackQueryDI;
|
||||||
import com.github.polpetta.mezzotre.telegram.callbackquery.di.CallbackQuery;
|
import com.github.polpetta.mezzotre.telegram.command.CommandDI;
|
||||||
import com.github.polpetta.mezzotre.telegram.command.di.Command;
|
import com.github.polpetta.mezzotre.util.ServiceModule;
|
||||||
import com.github.polpetta.mezzotre.util.di.ThreadPool;
|
import com.github.polpetta.mezzotre.util.UtilDI;
|
||||||
import com.google.inject.*;
|
import com.google.inject.*;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
import com.google.inject.name.Names;
|
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 =
|
public static final Function<Jooby, Collection<Module>> DEFAULT_DI_MODULES =
|
||||||
(jooby) -> {
|
(jooby) -> {
|
||||||
final HashSet<Module> modules = new HashSet<>();
|
final HashSet<Module> modules = new HashSet<>();
|
||||||
modules.add(new Db());
|
modules.add(new OrmDI());
|
||||||
modules.add(new ThreadPool());
|
modules.add(new UtilDI());
|
||||||
modules.add(new Route());
|
modules.add(new RouteDI());
|
||||||
modules.add(new Command());
|
modules.add(new CommandDI());
|
||||||
modules.add(new CallbackQuery());
|
modules.add(new CallbackQueryDI());
|
||||||
return modules;
|
return modules;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ public class App extends Jooby {
|
||||||
if (modules == null || modules.size() == 0) {
|
if (modules == null || modules.size() == 0) {
|
||||||
toInject = DEFAULT_DI_MODULES.apply(this);
|
toInject = DEFAULT_DI_MODULES.apply(this);
|
||||||
}
|
}
|
||||||
toInject.add(new InjectionModule(this));
|
toInject.add(new AppDI(this));
|
||||||
toInject.add(new JoobyModule(this));
|
toInject.add(new JoobyModule(this));
|
||||||
|
|
||||||
final Injector injector = Guice.createInjector(runningEnv, toInject);
|
final Injector injector = Guice.createInjector(runningEnv, toInject);
|
||||||
|
@ -55,6 +56,7 @@ public class App extends Jooby {
|
||||||
install(
|
install(
|
||||||
injector.getInstance(Key.get(Extension.class, Names.named("flyWayMigrationExtension"))));
|
injector.getInstance(Key.get(Extension.class, Names.named("flyWayMigrationExtension"))));
|
||||||
install(injector.getInstance(Key.get(Extension.class, Names.named("ebeanExtension"))));
|
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 AccessLogHandler());
|
||||||
decorator(new TransactionalRequest());
|
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.AbstractModule;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import com.zaxxer.hikari.HikariConfig;
|
import com.zaxxer.hikari.HikariConfig;
|
||||||
import io.jooby.Extension;
|
import io.jooby.Extension;
|
||||||
import io.jooby.ebean.EbeanModule;
|
import io.jooby.ebean.EbeanModule;
|
||||||
import io.jooby.flyway.FlywayModule;
|
import io.jooby.flyway.FlywayModule;
|
||||||
import io.jooby.hikari.HikariModule;
|
import io.jooby.hikari.HikariModule;
|
||||||
import javax.inject.Named;
|
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
|
* Returns null. This allows to fetch the configuration from file rather than fetch from other
|
||||||
* environment
|
* 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.AbstractModule;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
|
@ -7,7 +7,7 @@ import io.jooby.Jooby;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
public class Route extends AbstractModule {
|
public class RouteDI extends AbstractModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public TelegramBot getTelegramBot(Jooby jooby) {
|
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.github.polpetta.mezzotre.orm.model.CallbackQueryContext;
|
||||||
import com.pengrad.telegrambot.model.Update;
|
import com.pengrad.telegrambot.model.Update;
|
||||||
import com.pengrad.telegrambot.request.BaseRequest;
|
import com.pengrad.telegrambot.request.BaseRequest;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
@ -22,15 +22,18 @@ import javax.inject.Singleton;
|
||||||
@Singleton
|
@Singleton
|
||||||
public class Dispatcher {
|
public class Dispatcher {
|
||||||
|
|
||||||
private final Set<Processor> tgEventProcessors;
|
private final Map<String, Processor> tgEventProcessors;
|
||||||
private final Executor threadPool;
|
private final Executor threadPool;
|
||||||
|
private final NotFoundFactory notFoundFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Dispatcher(
|
public Dispatcher(
|
||||||
@Named("eventProcessors") Set<Processor> tgEventProcessors,
|
@Named("eventProcessors") Map<String, Processor> tgEventProcessors,
|
||||||
@Named("eventThreadPool") Executor threadPool) {
|
@Named("eventThreadPool") Executor threadPool,
|
||||||
|
NotFoundFactory notFoundFactory) {
|
||||||
this.tgEventProcessors = tgEventProcessors;
|
this.tgEventProcessors = tgEventProcessors;
|
||||||
this.threadPool = threadPool;
|
this.threadPool = threadPool;
|
||||||
|
this.notFoundFactory = notFoundFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,14 +53,11 @@ public class Dispatcher {
|
||||||
.thenComposeAsync(
|
.thenComposeAsync(
|
||||||
ignored ->
|
ignored ->
|
||||||
Optional.of(callbackQueryContext.getFields().getEvent())
|
Optional.of(callbackQueryContext.getFields().getEvent())
|
||||||
.flatMap(
|
.map(
|
||||||
eventName ->
|
eventName ->
|
||||||
tgEventProcessors.stream()
|
tgEventProcessors
|
||||||
// FIXME this is fucking stupid, why iterate over, just use a map!
|
.getOrDefault(eventName, notFoundFactory.create(eventName))
|
||||||
// Make mapping at startup then we're gucci for the rest of the run
|
.process(callbackQueryContext, update))
|
||||||
.filter(processor -> processor.getEventName().equals(eventName))
|
|
||||||
.findAny())
|
|
||||||
.map(processor -> processor.process(callbackQueryContext, update))
|
|
||||||
.orElse(CompletableFuture.failedFuture(new EventProcessorNotFoundException())),
|
.orElse(CompletableFuture.failedFuture(new EventProcessorNotFoundException())),
|
||||||
threadPool);
|
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 {
|
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();
|
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
|
* Process the current event
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
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.CallbackQueryContext;
|
||||||
import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext;
|
import com.github.polpetta.mezzotre.orm.telegram.ChatUtil;
|
||||||
import com.github.polpetta.mezzotre.orm.model.query.QTgChat;
|
|
||||||
import com.github.polpetta.mezzotre.util.UUIDGenerator;
|
import com.github.polpetta.mezzotre.util.UUIDGenerator;
|
||||||
import com.github.polpetta.types.json.CallbackQueryMetadata;
|
import com.github.polpetta.types.json.CallbackQueryMetadata;
|
||||||
import com.pengrad.telegrambot.model.Message;
|
|
||||||
import com.pengrad.telegrambot.model.Update;
|
import com.pengrad.telegrambot.model.Update;
|
||||||
import com.pengrad.telegrambot.model.request.InlineKeyboardButton;
|
import com.pengrad.telegrambot.model.request.InlineKeyboardButton;
|
||||||
import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup;
|
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.BaseRequest;
|
||||||
import com.pengrad.telegrambot.request.EditMessageText;
|
import com.pengrad.telegrambot.request.EditMessageText;
|
||||||
import com.pengrad.telegrambot.request.SendMessage;
|
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.NoSuchElementException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
@ -24,9 +20,6 @@ import java.util.concurrent.Executor;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
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;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,61 +34,26 @@ public class SelectLanguageTutorial implements Processor {
|
||||||
|
|
||||||
public static final String EVENT_NAME = "selectLanguageTutorial";
|
public static final String EVENT_NAME = "selectLanguageTutorial";
|
||||||
private final Executor threadPool;
|
private final Executor threadPool;
|
||||||
private final LocalizedMessageFactory localizedMessageFactory;
|
private final TemplateContentGenerator templateContentGenerator;
|
||||||
private final Logger log;
|
private final Logger log;
|
||||||
private final UUIDGenerator uuidGenerator;
|
private final UUIDGenerator uuidGenerator;
|
||||||
|
private final ChatUtil chatUtil;
|
||||||
/**
|
private final CallbackQueryContextCleaner callbackQueryContextCleaner;
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SelectLanguageTutorial(
|
public SelectLanguageTutorial(
|
||||||
@Named("eventThreadPool") Executor threadPool,
|
@Named("eventThreadPool") Executor threadPool,
|
||||||
LocalizedMessageFactory localizedMessageFactory,
|
TemplateContentGenerator templateContentGenerator,
|
||||||
Logger log,
|
Logger log,
|
||||||
UUIDGenerator uuidGenerator) {
|
UUIDGenerator uuidGenerator,
|
||||||
|
ChatUtil chatUtil,
|
||||||
|
CallbackQueryContextCleaner callbackQueryContextCleaner) {
|
||||||
this.threadPool = threadPool;
|
this.threadPool = threadPool;
|
||||||
this.localizedMessageFactory = localizedMessageFactory;
|
this.templateContentGenerator = templateContentGenerator;
|
||||||
this.log = log;
|
this.log = log;
|
||||||
this.uuidGenerator = uuidGenerator;
|
this.uuidGenerator = uuidGenerator;
|
||||||
|
this.chatUtil = chatUtil;
|
||||||
|
this.callbackQueryContextCleaner = callbackQueryContextCleaner;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -108,16 +66,8 @@ public class SelectLanguageTutorial implements Processor {
|
||||||
CallbackQueryContext callbackQueryContext, Update update) {
|
CallbackQueryContext callbackQueryContext, Update update) {
|
||||||
return CompletableFuture.supplyAsync(
|
return CompletableFuture.supplyAsync(
|
||||||
() ->
|
() ->
|
||||||
Optional.of(callbackQueryContext.getFields().getTelegramChatId())
|
chatUtil
|
||||||
.map(Double::longValue)
|
.extractChat(callbackQueryContext, update)
|
||||||
// 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())
|
|
||||||
.map(
|
.map(
|
||||||
tgChat -> {
|
tgChat -> {
|
||||||
tgChat.setLocale(
|
tgChat.setLocale(
|
||||||
|
@ -126,7 +76,8 @@ public class SelectLanguageTutorial implements Processor {
|
||||||
.getFields()
|
.getFields()
|
||||||
.getAdditionalProperties()
|
.getAdditionalProperties()
|
||||||
.getOrDefault(
|
.getOrDefault(
|
||||||
Field.NewLanguage.getName(), tgChat.getLocale()));
|
Field.SelectLanguageTutorial.NewLanguage.getName(),
|
||||||
|
tgChat.getLocale()));
|
||||||
tgChat.save();
|
tgChat.save();
|
||||||
log.trace(
|
log.trace(
|
||||||
"Locale for chat "
|
"Locale for chat "
|
||||||
|
@ -147,55 +98,34 @@ public class SelectLanguageTutorial implements Processor {
|
||||||
.thenApplyAsync(
|
.thenApplyAsync(
|
||||||
// If we are here then we're sure there is at least a chat associated with this callback
|
// If we are here then we're sure there is at least a chat associated with this callback
|
||||||
tgChat -> {
|
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 =
|
final String message =
|
||||||
Try.of(
|
templateContentGenerator.mergeTemplate(
|
||||||
() -> {
|
velocityContext ->
|
||||||
final Locale locale = Locale.forLanguageTag(tgChat.getLocale());
|
velocityContext.put("hasHelpBeenShown", tgChat.getHasHelpBeenShown()),
|
||||||
final ToolManager toolManager =
|
tgChat.getLocale(),
|
||||||
localizedMessageFactory.createVelocityToolManager(locale);
|
"/template/telegram/selectLanguageTutorial.vm");
|
||||||
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();
|
|
||||||
|
|
||||||
log.trace("SelectLanguageTutorial event - message to send back: " + message);
|
log.trace("SelectLanguageTutorial event - message to send back: " + message);
|
||||||
|
|
||||||
final String callBackGroupToDelete = callbackQueryContext.getEntryGroup();
|
callbackQueryContextCleaner.removeGroupAsync(callbackQueryContext.getEntryGroup());
|
||||||
final int delete =
|
|
||||||
new QCallbackQueryContext().entryGroup.eq(callBackGroupToDelete).delete();
|
|
||||||
log.trace(
|
|
||||||
"Deleted "
|
|
||||||
+ delete
|
|
||||||
+ " entries regarding callback group "
|
|
||||||
+ callBackGroupToDelete);
|
|
||||||
|
|
||||||
final Optional<Integer> messageId =
|
final Optional<Integer> messageId = Util.extractMessageId(update);
|
||||||
Optional.ofNullable(update.callbackQuery().message()).map(Message::messageId);
|
|
||||||
BaseRequest<?, ?> baseRequest;
|
BaseRequest<?, ?> baseRequest;
|
||||||
Optional<InlineKeyboardMarkup> helpButton = Optional.empty();
|
Optional<InlineKeyboardMarkup> helpButton = Optional.empty();
|
||||||
if (!tgChat.getHasHelpBeenShown()) {
|
if (!tgChat.getHasHelpBeenShown()) {
|
||||||
// Add a button to show all the possible commands
|
// Add a button to show all the possible commands
|
||||||
final String showMeTutorialString =
|
final String showMeTutorialString =
|
||||||
localizedMessageFactory
|
templateContentGenerator.getString(
|
||||||
.createResourceBundle(Locale.forLanguageTag(tgChat.getLocale()))
|
tgChat.getLocale(),
|
||||||
.getString("button.showMeTutorial");
|
"selectLanguageTutorial.showMeTutorialInlineKeyboardButtonName");
|
||||||
|
|
||||||
final CallbackQueryMetadata callbackQueryMetadata =
|
final CallbackQueryMetadata callbackQueryMetadata =
|
||||||
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
||||||
|
|
|
@ -1,15 +1,66 @@
|
||||||
package com.github.polpetta.mezzotre.telegram.callbackquery;
|
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.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.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.BaseRequest;
|
||||||
|
import com.pengrad.telegrambot.request.EditMessageText;
|
||||||
import com.pengrad.telegrambot.request.SendMessage;
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
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";
|
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
|
@Override
|
||||||
public String getEventName() {
|
public String getEventName() {
|
||||||
|
@ -19,8 +70,44 @@ public class ShowHelp implements Processor {
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
public CompletableFuture<Optional<BaseRequest<?, ?>>> process(
|
||||||
CallbackQueryContext callbackQueryContext, Update update) {
|
CallbackQueryContext callbackQueryContext, Update update) {
|
||||||
// TODO implement this method and put `hasHelpBeenShown` in tgChat to false
|
return CompletableFuture.supplyAsync(
|
||||||
return CompletableFuture.completedFuture(
|
() ->
|
||||||
Optional.of(new SendMessage(callbackQueryContext.getFields().getTelegramChatId(), "TODO")));
|
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.model.Update;
|
||||||
import com.pengrad.telegrambot.request.BaseRequest;
|
import com.pengrad.telegrambot.request.BaseRequest;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,12 +17,12 @@ import java.util.concurrent.CompletableFuture;
|
||||||
public interface Processor {
|
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}.
|
* 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
|
* Process the current update
|
||||||
|
@ -31,4 +32,17 @@ public interface Processor {
|
||||||
* @return a {@link CompletableFuture} with the result of the computation
|
* @return a {@link CompletableFuture} with the result of the computation
|
||||||
*/
|
*/
|
||||||
CompletableFuture<Optional<BaseRequest<?, ?>>> process(TgChat chat, Update update);
|
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.google.inject.Singleton;
|
||||||
import com.pengrad.telegrambot.model.Update;
|
import com.pengrad.telegrambot.model.Update;
|
||||||
import com.pengrad.telegrambot.request.BaseRequest;
|
import com.pengrad.telegrambot.request.BaseRequest;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
@ -21,15 +21,18 @@ import javax.inject.Named;
|
||||||
@Singleton
|
@Singleton
|
||||||
public class Router {
|
public class Router {
|
||||||
|
|
||||||
private final Set<Processor> tgCommandProcessors;
|
private final Map<String, Processor> tgCommandProcessors;
|
||||||
private final Executor threadPool;
|
private final Executor threadPool;
|
||||||
|
private final NotFoundFactory notFoundFactory;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Router(
|
public Router(
|
||||||
@Named("commandProcessor") Set<Processor> tgCommandProcessors,
|
@Named("commandProcessor") Map<String, Processor> tgCommandProcessors,
|
||||||
@Named("eventThreadPool") Executor threadPool) {
|
@Named("eventThreadPool") Executor threadPool,
|
||||||
|
NotFoundFactory notFoundFactory) {
|
||||||
this.tgCommandProcessors = tgCommandProcessors;
|
this.tgCommandProcessors = tgCommandProcessors;
|
||||||
this.threadPool = threadPool;
|
this.threadPool = threadPool;
|
||||||
|
this.notFoundFactory = notFoundFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,15 +61,13 @@ public class Router {
|
||||||
.map(list -> list[0])
|
.map(list -> list[0])
|
||||||
.filter(wannabeCommand -> wannabeCommand.startsWith("/"))
|
.filter(wannabeCommand -> wannabeCommand.startsWith("/"))
|
||||||
.or(() -> Optional.ofNullable(chat.getChatContext().getStage()))
|
.or(() -> Optional.ofNullable(chat.getChatContext().getStage()))
|
||||||
.flatMap(
|
.map(
|
||||||
command ->
|
command ->
|
||||||
tgCommandProcessors.stream()
|
tgCommandProcessors
|
||||||
// FIXME this is fucking stupid, why iterate over, just use a map!
|
.getOrDefault(command, notFoundFactory.create(command))
|
||||||
// Make mapping at startup then we're gucci for the rest of the run
|
.process(chat, update))
|
||||||
.filter(ex -> ex.getTriggerKeyword().equals(command))
|
// This should never happen
|
||||||
.findAny())
|
.orElse(CompletableFuture.failedFuture(new IllegalStateException())),
|
||||||
.map(executor -> executor.process(chat, update))
|
|
||||||
.orElse(CompletableFuture.failedFuture(new CommandNotFoundException())),
|
|
||||||
threadPool);
|
threadPool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package com.github.polpetta.mezzotre.telegram.command;
|
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.CallbackQueryContext;
|
||||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
||||||
import com.github.polpetta.mezzotre.telegram.callbackquery.SelectLanguageTutorial;
|
import com.github.polpetta.mezzotre.orm.telegram.ChatUtil;
|
||||||
import com.github.polpetta.mezzotre.util.Clock;
|
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.mezzotre.util.UUIDGenerator;
|
||||||
import com.github.polpetta.types.json.CallbackQueryMetadata;
|
import com.github.polpetta.types.json.CallbackQueryMetadata;
|
||||||
import com.github.polpetta.types.json.ChatContext;
|
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import com.pengrad.telegrambot.model.Update;
|
import com.pengrad.telegrambot.model.Update;
|
||||||
import com.pengrad.telegrambot.model.request.InlineKeyboardButton;
|
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.model.request.ParseMode;
|
||||||
import com.pengrad.telegrambot.request.BaseRequest;
|
import com.pengrad.telegrambot.request.BaseRequest;
|
||||||
import com.pengrad.telegrambot.request.SendMessage;
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
import io.vavr.control.Try;
|
import java.util.Collections;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
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;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,31 +32,37 @@ import org.slf4j.Logger;
|
||||||
* @since 1.0
|
* @since 1.0
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class Start implements Processor {
|
class Start implements Processor {
|
||||||
|
|
||||||
|
private static final String TRIGGERING_STAGING_NAME = "/start";
|
||||||
|
|
||||||
private final Executor threadPool;
|
private final Executor threadPool;
|
||||||
private final Logger log;
|
private final Logger log;
|
||||||
private final UUIDGenerator uuidGenerator;
|
private final UUIDGenerator uuidGenerator;
|
||||||
private final Clock clock;
|
private final String applicationName;
|
||||||
private final LocalizedMessageFactory localizedMessageFactory;
|
private final ChatUtil chatUtil;
|
||||||
|
|
||||||
|
private final TemplateContentGenerator templateContentGenerator;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Start(
|
public Start(
|
||||||
LocalizedMessageFactory localizedMessageFactory,
|
TemplateContentGenerator templateContentGenerator,
|
||||||
@Named("eventThreadPool") Executor threadPool,
|
@Named("eventThreadPool") Executor threadPool,
|
||||||
Logger log,
|
Logger log,
|
||||||
UUIDGenerator uuidGenerator,
|
UUIDGenerator uuidGenerator,
|
||||||
Clock clock) {
|
@Named("applicationName") String applicationName,
|
||||||
this.localizedMessageFactory = localizedMessageFactory;
|
ChatUtil chatUtil) {
|
||||||
|
this.templateContentGenerator = templateContentGenerator;
|
||||||
this.threadPool = threadPool;
|
this.threadPool = threadPool;
|
||||||
this.log = log;
|
this.log = log;
|
||||||
this.uuidGenerator = uuidGenerator;
|
this.uuidGenerator = uuidGenerator;
|
||||||
this.clock = clock;
|
this.applicationName = applicationName;
|
||||||
|
this.chatUtil = chatUtil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getTriggerKeyword() {
|
public Set<String> getTriggerKeywords() {
|
||||||
return "/start";
|
return Set.of(TRIGGERING_STAGING_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -73,40 +76,18 @@ public class Start implements Processor {
|
||||||
3 - Reply to Telegram
|
3 - Reply to Telegram
|
||||||
*/
|
*/
|
||||||
final String message =
|
final String message =
|
||||||
Try.of(
|
templateContentGenerator.mergeTemplate(
|
||||||
() -> {
|
velocityContext -> {
|
||||||
final Locale locale = Locale.forLanguageTag(chat.getLocale());
|
velocityContext.put("firstName", update.message().chat().firstName());
|
||||||
final ToolManager toolManager =
|
// FIXME add some very cool markdown formatter instead of concatenating stuff
|
||||||
localizedMessageFactory.createVelocityToolManager(locale);
|
// this way
|
||||||
final VelocityContext context =
|
velocityContext.put("programName", "_" + applicationName + "_");
|
||||||
new VelocityContext(toolManager.createContext());
|
},
|
||||||
context.put("firstName", update.message().chat().firstName());
|
chat.getLocale(),
|
||||||
context.put("programName", "_Mezzotre_");
|
"template/telegram/start.vm");
|
||||||
|
|
||||||
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();
|
|
||||||
log.trace("Start command - message to send back: " + message);
|
log.trace("Start command - message to send back: " + message);
|
||||||
|
|
||||||
final ChatContext chatContext = chat.getChatContext();
|
chatUtil.updateChatContext(chat, TRIGGERING_STAGING_NAME, 0, Collections.emptyMap());
|
||||||
chatContext.setStage(getTriggerKeyword());
|
|
||||||
chatContext.setStep(0);
|
|
||||||
chatContext.setPreviousMessageUnixTimestampInSeconds(clock.now());
|
|
||||||
chat.setChatContext(chatContext);
|
|
||||||
chat.save();
|
|
||||||
|
|
||||||
// To get the messageId we should send the message first, then save it in the database!
|
// To get the messageId we should send the message first, then save it in the database!
|
||||||
final String groupId = uuidGenerator.generateAsString();
|
final String groupId = uuidGenerator.generateAsString();
|
||||||
|
@ -115,11 +96,13 @@ public class Start implements Processor {
|
||||||
uuidGenerator.generateAsString(),
|
uuidGenerator.generateAsString(),
|
||||||
groupId,
|
groupId,
|
||||||
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
||||||
.withEvent(SelectLanguageTutorial.EVENT_NAME)
|
.withEvent(
|
||||||
|
com.github.polpetta.mezzotre.telegram.callbackquery.SelectLanguageTutorial
|
||||||
|
.EVENT_NAME)
|
||||||
.withTelegramChatId(update.message().chat().id())
|
.withTelegramChatId(update.message().chat().id())
|
||||||
.withAdditionalProperty(
|
.withAdditionalProperty(
|
||||||
SelectLanguageTutorial.Field.NewLanguage.getName(),
|
Field.SelectLanguageTutorial.NewLanguage.getName(),
|
||||||
SelectLanguageTutorial.Language.English.getLocale())
|
Value.SelectLanguageTutorial.English.getLocale())
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
final CallbackQueryContext switchToItalian =
|
final CallbackQueryContext switchToItalian =
|
||||||
|
@ -127,21 +110,19 @@ public class Start implements Processor {
|
||||||
uuidGenerator.generateAsString(),
|
uuidGenerator.generateAsString(),
|
||||||
groupId,
|
groupId,
|
||||||
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
new CallbackQueryMetadata.CallbackQueryMetadataBuilder()
|
||||||
.withEvent(SelectLanguageTutorial.EVENT_NAME)
|
.withEvent(
|
||||||
|
com.github.polpetta.mezzotre.telegram.callbackquery.SelectLanguageTutorial
|
||||||
|
.EVENT_NAME)
|
||||||
.withTelegramChatId(update.message().chat().id())
|
.withTelegramChatId(update.message().chat().id())
|
||||||
.withAdditionalProperty(
|
.withAdditionalProperty(
|
||||||
SelectLanguageTutorial.Field.NewLanguage.getName(),
|
Field.SelectLanguageTutorial.NewLanguage.getName(),
|
||||||
SelectLanguageTutorial.Language.Italian.getLocale())
|
Value.SelectLanguageTutorial.Italian.getLocale())
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
final String englishButton =
|
final String englishButton =
|
||||||
localizedMessageFactory
|
templateContentGenerator.getString(Locale.US, "changeLanguage.english");
|
||||||
.createResourceBundle(Locale.US)
|
|
||||||
.getString("changeLanguage.english");
|
|
||||||
final String italianButton =
|
final String italianButton =
|
||||||
localizedMessageFactory
|
templateContentGenerator.getString(Locale.ITALY, "changeLanguage.italian");
|
||||||
.createResourceBundle(Locale.ITALY)
|
|
||||||
.getString("changeLanguage.italian");
|
|
||||||
|
|
||||||
final SendMessage messageToSend =
|
final SendMessage messageToSend =
|
||||||
new SendMessage(chat.getId(), message)
|
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.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.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.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.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.english=English
|
||||||
changeLanguage.italian=Italian
|
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
|
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.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.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.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.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.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.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.english=English
|
||||||
selectLanguageTutorial.italian=Italian
|
selectLanguageTutorial.italian=Italian
|
||||||
spell.speakWithAnimals=Speak with animals
|
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.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.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.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.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.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.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.english=Inglese
|
||||||
selectLanguageTutorial.italian=Italiano
|
selectLanguageTutorial.italian=Italiano
|
||||||
spell.speakWithAnimals=Parlare con animali
|
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.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.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.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.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.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.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.english=Inglese
|
||||||
selectLanguageTutorial.italian=Italiano
|
selectLanguageTutorial.italian=Italiano
|
||||||
spell.speakWithAnimals=Parlare con animali
|
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.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.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.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.mockito.Mockito.mock;
|
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.Module;
|
||||||
|
import com.google.inject.Provides;
|
||||||
|
import com.google.inject.Stage;
|
||||||
import io.jooby.*;
|
import io.jooby.*;
|
||||||
import io.jooby.ebean.EbeanModule;
|
import io.jooby.ebean.EbeanModule;
|
||||||
import io.jooby.flyway.FlywayModule;
|
import io.jooby.flyway.FlywayModule;
|
||||||
import io.jooby.hikari.HikariModule;
|
import io.jooby.hikari.HikariModule;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
public class UnitTest {
|
public class UnitTest {
|
||||||
|
@ -37,6 +43,20 @@ public class UnitTest {
|
||||||
public Extension getEbeanExtension() {
|
public Extension getEbeanExtension() {
|
||||||
return mock(EbeanModule.class);
|
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
|
@Test
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
package com.github.polpetta.mezzotre.helper;
|
package com.github.polpetta.mezzotre.helper;
|
||||||
|
|
||||||
import com.github.polpetta.mezzotre.App;
|
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.AbstractModule;
|
||||||
import com.google.inject.Provides;
|
import com.google.inject.Provides;
|
||||||
import com.google.inject.Singleton;
|
|
||||||
import com.google.inject.Stage;
|
import com.google.inject.Stage;
|
||||||
import com.zaxxer.hikari.HikariConfig;
|
import com.zaxxer.hikari.HikariConfig;
|
||||||
import io.jooby.Extension;
|
import io.jooby.Extension;
|
||||||
import io.jooby.ebean.EbeanModule;
|
import io.jooby.ebean.EbeanModule;
|
||||||
import io.jooby.flyway.FlywayModule;
|
import io.jooby.flyway.FlywayModule;
|
||||||
import io.jooby.hikari.HikariModule;
|
import io.jooby.hikari.HikariModule;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.testcontainers.containers.PostgreSQLContainer;
|
import org.testcontainers.containers.PostgreSQLContainer;
|
||||||
|
|
||||||
|
@ -50,13 +54,27 @@ public class IntegrationAppFactory {
|
||||||
public Extension getEbeanExtension() {
|
public Extension getEbeanExtension() {
|
||||||
return new EbeanModule();
|
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() {
|
public static App loadCustomDbApplication() {
|
||||||
final PostgreSQLContainer<?> container =
|
final PostgreSQLContainer<?> container =
|
||||||
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
|
||||||
container.start();
|
container.start();
|
||||||
final Route routeModule = new Route();
|
final RouteDI routeModule = new RouteDI();
|
||||||
final DatabaseDI databaseDI = new DatabaseDI(container);
|
final DatabaseDI databaseDI = new DatabaseDI(container);
|
||||||
return new App(Stage.DEVELOPMENT, Set.of(databaseDI, routeModule));
|
return new App(Stage.DEVELOPMENT, Set.of(databaseDI, routeModule));
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ public class Loader {
|
||||||
hikariConnectionProperties.put("username", container.getUsername());
|
hikariConnectionProperties.put("username", container.getUsername());
|
||||||
hikariConnectionProperties.put("password", container.getPassword());
|
hikariConnectionProperties.put("password", container.getPassword());
|
||||||
hikariConnectionProperties.put("jdbcUrl", container.getJdbcUrl());
|
hikariConnectionProperties.put("jdbcUrl", container.getJdbcUrl());
|
||||||
|
hikariConnectionProperties.put("autoCommit", "false");
|
||||||
|
|
||||||
ebeanConnectionProperties.load(ebeanInputStream);
|
ebeanConnectionProperties.load(ebeanInputStream);
|
||||||
ebeanConnectionProperties.put("datasource_db_username", container.getUsername());
|
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("slow")
|
||||||
@Tag("database")
|
@Tag("database")
|
||||||
@Testcontainers
|
@Testcontainers
|
||||||
class CallbackQueryContextIntegrationTest {
|
class CallbackQueryDIContextIntegrationTest {
|
||||||
|
|
||||||
private static ObjectMapper objectMapper;
|
private static ObjectMapper objectMapper;
|
||||||
private static UUIDGenerator uuidGenerator;
|
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.Loader;
|
||||||
import com.github.polpetta.mezzotre.helper.TestConfig;
|
import com.github.polpetta.mezzotre.helper.TestConfig;
|
||||||
import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory;
|
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.CallbackQueryContext;
|
||||||
import com.github.polpetta.mezzotre.orm.model.TgChat;
|
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.QCallbackQueryContext;
|
||||||
import com.github.polpetta.mezzotre.orm.model.query.QTgChat;
|
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.mezzotre.util.UUIDGenerator;
|
||||||
import com.github.polpetta.types.json.CallbackQueryMetadata;
|
import com.github.polpetta.types.json.CallbackQueryMetadata;
|
||||||
import com.github.polpetta.types.json.ChatContext;
|
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.EditMessageText;
|
||||||
import com.pengrad.telegrambot.request.SendMessage;
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
import io.ebean.Database;
|
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.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.junit.jupiter.api.*;
|
import org.junit.jupiter.api.*;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
@ -51,6 +61,8 @@ class SelectLanguageTutorialIntegrationTest {
|
||||||
private Database database;
|
private Database database;
|
||||||
private SelectLanguageTutorial selectLanguageTutorial;
|
private SelectLanguageTutorial selectLanguageTutorial;
|
||||||
private UUIDGenerator fakeUUIDGenerator;
|
private UUIDGenerator fakeUUIDGenerator;
|
||||||
|
private ChatUtil chatUtil;
|
||||||
|
private BatchBeanCleanerService batchBeanCleanerService;
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
static void beforeAll() {
|
static void beforeAll() {
|
||||||
|
@ -63,19 +75,35 @@ class SelectLanguageTutorialIntegrationTest {
|
||||||
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||||
|
|
||||||
fakeUUIDGenerator = mock(UUIDGenerator.class);
|
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 =
|
selectLanguageTutorial =
|
||||||
new SelectLanguageTutorial(
|
new SelectLanguageTutorial(
|
||||||
Executors.newSingleThreadExecutor(),
|
Executors.newSingleThreadExecutor(),
|
||||||
new LocalizedMessageFactory(Loader.defaultVelocityEngine()),
|
new TemplateContentGenerator(
|
||||||
|
new LocalizedMessageFactory(Loader.defaultVelocityEngine())),
|
||||||
LoggerFactory.getLogger(SelectLanguageTutorial.class),
|
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() {
|
private static Stream<Arguments> getTestLocales() {
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
Arguments.of(
|
Arguments.of(
|
||||||
SelectLanguageTutorial.Language.Italian,
|
Value.SelectLanguageTutorial.Italian,
|
||||||
"_*Procede a bere una pozione al cui suo interno si trova uno strano liquido"
|
"_*Procede a bere una pozione al cui suo interno si trova uno strano liquido"
|
||||||
+ " multicolore*_\n"
|
+ " multicolore*_\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
|
@ -84,7 +112,7 @@ class SelectLanguageTutorialIntegrationTest {
|
||||||
+ " posso parlare con te nel linguaggio che preferisci!\n"
|
+ " posso parlare con te nel linguaggio che preferisci!\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Puoi sempre cambiare le preferenze della tua lingua scrivendo"
|
+ "Puoi sempre cambiare le preferenze della tua lingua scrivendo"
|
||||||
+ " /selectLanguageTutorial nella chat.\n"
|
+ " /changeLanguage nella chat.\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Sembra tu non abbia ancora visto cosa posso fare! Per avere una lista completa"
|
+ "Sembra tu non abbia ancora visto cosa posso fare! Per avere una lista completa"
|
||||||
+ " delle mie abilità, scrivi /help nella chat in qualsiasi momento!"
|
+ " delle mie abilità, scrivi /help nella chat in qualsiasi momento!"
|
||||||
|
@ -93,14 +121,14 @@ class SelectLanguageTutorialIntegrationTest {
|
||||||
"Mostrami cosa puoi fare!",
|
"Mostrami cosa puoi fare!",
|
||||||
false),
|
false),
|
||||||
Arguments.of(
|
Arguments.of(
|
||||||
SelectLanguageTutorial.Language.English,
|
Value.SelectLanguageTutorial.English,
|
||||||
"_*Proceeds to drink a potion with a strange, multicolor liquid*_\n"
|
"_*Proceeds to drink a potion with a strange, multicolor liquid*_\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Thanks! Now that I drank this modified potion of Speak with animals that I've"
|
+ "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"
|
+ " found at the \"Crystal Fermentary\" magic potion shop yesterday I can speak"
|
||||||
+ " with you in the language that you prefer!\n"
|
+ " with you in the language that you prefer!\n"
|
||||||
+ "\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"
|
+ " in the chat.\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "It seems you haven't checked out what I can do yet! To have a complete list of"
|
+ "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!",
|
"Show me what you can do!",
|
||||||
false),
|
false),
|
||||||
Arguments.of(
|
Arguments.of(
|
||||||
SelectLanguageTutorial.Language.Italian,
|
Value.SelectLanguageTutorial.Italian,
|
||||||
"_*Procede a bere una pozione al cui suo interno si trova uno strano liquido"
|
"_*Procede a bere una pozione al cui suo interno si trova uno strano liquido"
|
||||||
+ " multicolore*_\n"
|
+ " multicolore*_\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
|
@ -119,19 +147,19 @@ class SelectLanguageTutorialIntegrationTest {
|
||||||
+ " posso parlare con te nel linguaggio che preferisci!\n"
|
+ " posso parlare con te nel linguaggio che preferisci!\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Puoi sempre cambiare le preferenze della tua lingua scrivendo"
|
+ "Puoi sempre cambiare le preferenze della tua lingua scrivendo"
|
||||||
+ " /selectLanguageTutorial nella chat.\n\n",
|
+ " /changeLanguage nella chat.\n\n",
|
||||||
"en-US",
|
"en-US",
|
||||||
"Mostrami cosa puoi fare!",
|
"Mostrami cosa puoi fare!",
|
||||||
true),
|
true),
|
||||||
Arguments.of(
|
Arguments.of(
|
||||||
SelectLanguageTutorial.Language.English,
|
Value.SelectLanguageTutorial.English,
|
||||||
"_*Proceeds to drink a potion with a strange, multicolor liquid*_\n"
|
"_*Proceeds to drink a potion with a strange, multicolor liquid*_\n"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ "Thanks! Now that I drank this modified potion of Speak with animals that I've"
|
+ "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"
|
+ " found at the \"Crystal Fermentary\" magic potion shop yesterday I can speak"
|
||||||
+ " with you in the language that you prefer!\n"
|
+ " with you in the language that you prefer!\n"
|
||||||
+ "\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",
|
+ " in the chat.\n\n",
|
||||||
"it-IT",
|
"it-IT",
|
||||||
"Show me what you can do!",
|
"Show me what you can do!",
|
||||||
|
@ -142,7 +170,7 @@ class SelectLanguageTutorialIntegrationTest {
|
||||||
@Timeout(value = 1, unit = TimeUnit.MINUTES)
|
@Timeout(value = 1, unit = TimeUnit.MINUTES)
|
||||||
@MethodSource("getTestLocales")
|
@MethodSource("getTestLocales")
|
||||||
void shouldProcessChangeLanguageToDesiredOneSendMessage(
|
void shouldProcessChangeLanguageToDesiredOneSendMessage(
|
||||||
SelectLanguageTutorial.Language language,
|
Value.SelectLanguageTutorial selectLanguageTutorial,
|
||||||
String expectedResult,
|
String expectedResult,
|
||||||
String startingLocale,
|
String startingLocale,
|
||||||
String buttonLocale,
|
String buttonLocale,
|
||||||
|
@ -182,7 +210,8 @@ class SelectLanguageTutorialIntegrationTest {
|
||||||
.withEvent("selectLanguageTutorial")
|
.withEvent("selectLanguageTutorial")
|
||||||
.withTelegramChatId(tgChatId)
|
.withTelegramChatId(tgChatId)
|
||||||
.withAdditionalProperty(
|
.withAdditionalProperty(
|
||||||
SelectLanguageTutorial.Field.NewLanguage.getName(), language.getLocale())
|
Field.SelectLanguageTutorial.NewLanguage.getName(),
|
||||||
|
selectLanguageTutorial.getLocale())
|
||||||
.build();
|
.build();
|
||||||
final String entryGroupId = "2e67774a-e4e4-4369-a414-a7f8bfe74b80";
|
final String entryGroupId = "2e67774a-e4e4-4369-a414-a7f8bfe74b80";
|
||||||
final CallbackQueryContext changeLanguageCallbackQueryContext =
|
final CallbackQueryContext changeLanguageCallbackQueryContext =
|
||||||
|
@ -190,8 +219,13 @@ class SelectLanguageTutorialIntegrationTest {
|
||||||
"c018108f-6612-4848-8fca-cf301460d4eb", entryGroupId, callbackQueryMetadata);
|
"c018108f-6612-4848-8fca-cf301460d4eb", entryGroupId, callbackQueryMetadata);
|
||||||
changeLanguageCallbackQueryContext.save();
|
changeLanguageCallbackQueryContext.save();
|
||||||
|
|
||||||
|
final CompletableFuture<
|
||||||
|
Tuple3<String, PString<? extends TQRootBean<?, ?>>, CompletableFuture<Integer>>>
|
||||||
|
callBackFuture = new CompletableFuture<>();
|
||||||
|
batchBeanCleanerService.addListener(callBackFuture::complete);
|
||||||
|
|
||||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> processFuture =
|
final CompletableFuture<Optional<BaseRequest<?, ?>>> processFuture =
|
||||||
selectLanguageTutorial.process(changeLanguageCallbackQueryContext, update);
|
this.selectLanguageTutorial.process(changeLanguageCallbackQueryContext, update);
|
||||||
final Optional<BaseRequest<?, ?>> gotResponseOpt = processFuture.get();
|
final Optional<BaseRequest<?, ?>> gotResponseOpt = processFuture.get();
|
||||||
final SendMessage gotMessage = (SendMessage) gotResponseOpt.get();
|
final SendMessage gotMessage = (SendMessage) gotResponseOpt.get();
|
||||||
assertEquals(expectedResult, gotMessage.getParameters().get("text"));
|
assertEquals(expectedResult, gotMessage.getParameters().get("text"));
|
||||||
|
@ -210,14 +244,20 @@ class SelectLanguageTutorialIntegrationTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
1, new QCallbackQueryContext().id.eq("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5").findCount());
|
1, new QCallbackQueryContext().id.eq("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5").findCount());
|
||||||
} else {
|
} else {
|
||||||
|
callBackFuture.get(); // Await that callback are cleaned out first
|
||||||
assertEquals(0, keyboardButtons.size());
|
assertEquals(0, keyboardButtons.size());
|
||||||
assertEquals(0, new QCallbackQueryContext().findCount());
|
assertEquals(0, new QCallbackQueryContext().findCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
final TgChat retrievedTgChat = new QTgChat().id.eq(tgChatId).findOne();
|
final TgChat retrievedTgChat = new QTgChat().id.eq(tgChatId).findOne();
|
||||||
assertNotNull(retrievedTgChat);
|
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());
|
assertEquals(0, new QCallbackQueryContext().entryGroup.eq(entryGroupId).findCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +265,7 @@ class SelectLanguageTutorialIntegrationTest {
|
||||||
@Timeout(value = 1, unit = TimeUnit.MINUTES)
|
@Timeout(value = 1, unit = TimeUnit.MINUTES)
|
||||||
@MethodSource("getTestLocales")
|
@MethodSource("getTestLocales")
|
||||||
void shouldProcessChangeLanguageToDesiredOneEditMessage(
|
void shouldProcessChangeLanguageToDesiredOneEditMessage(
|
||||||
SelectLanguageTutorial.Language language,
|
Value.SelectLanguageTutorial selectLanguageTutorial,
|
||||||
String expectedResult,
|
String expectedResult,
|
||||||
String startingLocale,
|
String startingLocale,
|
||||||
String buttonLocale,
|
String buttonLocale,
|
||||||
|
@ -297,16 +337,21 @@ class SelectLanguageTutorialIntegrationTest {
|
||||||
.withEvent("selectLanguageTutorial")
|
.withEvent("selectLanguageTutorial")
|
||||||
.withTelegramChatId(tgChatId)
|
.withTelegramChatId(tgChatId)
|
||||||
.withAdditionalProperty(
|
.withAdditionalProperty(
|
||||||
SelectLanguageTutorial.Field.NewLanguage.getName(), language.getLocale())
|
Field.SelectLanguageTutorial.NewLanguage.getName(),
|
||||||
|
selectLanguageTutorial.getLocale())
|
||||||
.build();
|
.build();
|
||||||
final String entryGroupId = "2e67774a-e4e4-4369-a414-a7f8bfe74b80";
|
final String entryGroupId = "2e67774a-e4e4-4369-a414-a7f8bfe74b80";
|
||||||
final CallbackQueryContext changeLanguageCallbackQueryContext =
|
final CallbackQueryContext changeLanguageCallbackQueryContext =
|
||||||
new CallbackQueryContext(
|
new CallbackQueryContext(
|
||||||
"c018108f-6612-4848-8fca-cf301460d4eb", entryGroupId, callbackQueryMetadata);
|
"c018108f-6612-4848-8fca-cf301460d4eb", entryGroupId, callbackQueryMetadata);
|
||||||
changeLanguageCallbackQueryContext.save();
|
changeLanguageCallbackQueryContext.save();
|
||||||
|
final CompletableFuture<
|
||||||
|
Tuple3<String, PString<? extends TQRootBean<?, ?>>, CompletableFuture<Integer>>>
|
||||||
|
callBackFuture = new CompletableFuture<>();
|
||||||
|
batchBeanCleanerService.addListener(callBackFuture::complete);
|
||||||
|
|
||||||
final CompletableFuture<Optional<BaseRequest<?, ?>>> processFuture =
|
final CompletableFuture<Optional<BaseRequest<?, ?>>> processFuture =
|
||||||
selectLanguageTutorial.process(changeLanguageCallbackQueryContext, update);
|
this.selectLanguageTutorial.process(changeLanguageCallbackQueryContext, update);
|
||||||
final Optional<BaseRequest<?, ?>> gotResponseOpt = processFuture.get();
|
final Optional<BaseRequest<?, ?>> gotResponseOpt = processFuture.get();
|
||||||
final EditMessageText gotMessage = (EditMessageText) gotResponseOpt.get();
|
final EditMessageText gotMessage = (EditMessageText) gotResponseOpt.get();
|
||||||
assertEquals(expectedResult, gotMessage.getParameters().get("text"));
|
assertEquals(expectedResult, gotMessage.getParameters().get("text"));
|
||||||
|
@ -325,14 +370,20 @@ class SelectLanguageTutorialIntegrationTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
1, new QCallbackQueryContext().id.eq("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5").findCount());
|
1, new QCallbackQueryContext().id.eq("e86e6fa1-fdd4-4120-b85d-a5482db2e8b5").findCount());
|
||||||
} else {
|
} else {
|
||||||
|
callBackFuture.get(); // Await that callback are cleaned out first
|
||||||
assertEquals(0, keyboardButtons.size());
|
assertEquals(0, keyboardButtons.size());
|
||||||
assertEquals(0, new QCallbackQueryContext().findCount());
|
assertEquals(0, new QCallbackQueryContext().findCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
final TgChat retrievedTgChat = new QTgChat().id.eq(tgChatId).findOne();
|
final TgChat retrievedTgChat = new QTgChat().id.eq(tgChatId).findOne();
|
||||||
assertNotNull(retrievedTgChat);
|
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());
|
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.model.Update;
|
||||||
import com.pengrad.telegrambot.request.BaseRequest;
|
import com.pengrad.telegrambot.request.BaseRequest;
|
||||||
import com.pengrad.telegrambot.request.SendMessage;
|
import com.pengrad.telegrambot.request.SendMessage;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
@ -32,12 +33,12 @@ class RouterTest {
|
||||||
gson = new Gson();
|
gson = new Gson();
|
||||||
|
|
||||||
dummyEmptyExampleProcessor = mock(Processor.class);
|
dummyEmptyExampleProcessor = mock(Processor.class);
|
||||||
when(dummyEmptyExampleProcessor.getTriggerKeyword()).thenReturn("/example");
|
when(dummyEmptyExampleProcessor.getTriggerKeywords()).thenReturn(Set.of("/example"));
|
||||||
when(dummyEmptyExampleProcessor.process(any(), any()))
|
when(dummyEmptyExampleProcessor.process(any(), any()))
|
||||||
.thenReturn(CompletableFuture.completedFuture(Optional.empty()));
|
.thenReturn(CompletableFuture.completedFuture(Optional.empty()));
|
||||||
|
|
||||||
anotherKeyWithResultProcessor = mock(Processor.class);
|
anotherKeyWithResultProcessor = mock(Processor.class);
|
||||||
when(anotherKeyWithResultProcessor.getTriggerKeyword()).thenReturn("/anotherExample");
|
when(anotherKeyWithResultProcessor.getTriggerKeywords()).thenReturn(Set.of("/anotherExample"));
|
||||||
when(anotherKeyWithResultProcessor.process(any(), any()))
|
when(anotherKeyWithResultProcessor.process(any(), any()))
|
||||||
.thenReturn(
|
.thenReturn(
|
||||||
CompletableFuture.completedFuture(Optional.of(new SendMessage(1234L, "hello world"))));
|
CompletableFuture.completedFuture(Optional.of(new SendMessage(1234L, "hello world"))));
|
||||||
|
@ -46,7 +47,10 @@ class RouterTest {
|
||||||
@Test
|
@Test
|
||||||
void shouldMessageExampleMessageAndGetEmptyOptional() throws Exception {
|
void shouldMessageExampleMessageAndGetEmptyOptional() throws Exception {
|
||||||
final Router router =
|
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);
|
final TgChat fakeChat = mock(TgChat.class);
|
||||||
when(fakeChat.getChatContext()).thenReturn(new ChatContext());
|
when(fakeChat.getChatContext()).thenReturn(new ChatContext());
|
||||||
final Update update =
|
final Update update =
|
||||||
|
@ -85,8 +89,13 @@ class RouterTest {
|
||||||
void shouldSelectRightExecutorAndReturnResult() throws Exception {
|
void shouldSelectRightExecutorAndReturnResult() throws Exception {
|
||||||
final Router router =
|
final Router router =
|
||||||
new Router(
|
new Router(
|
||||||
Set.of(dummyEmptyExampleProcessor, anotherKeyWithResultProcessor),
|
Map.of(
|
||||||
Executors.newSingleThreadExecutor());
|
"/example",
|
||||||
|
dummyEmptyExampleProcessor,
|
||||||
|
"/anotherExample",
|
||||||
|
anotherKeyWithResultProcessor),
|
||||||
|
Executors.newSingleThreadExecutor(),
|
||||||
|
mock(NotFoundFactory.class));
|
||||||
final TgChat fakeChat = mock(TgChat.class);
|
final TgChat fakeChat = mock(TgChat.class);
|
||||||
when(fakeChat.getChatContext()).thenReturn(new ChatContext());
|
when(fakeChat.getChatContext()).thenReturn(new ChatContext());
|
||||||
final Update update =
|
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.Loader;
|
||||||
import com.github.polpetta.mezzotre.helper.TestConfig;
|
import com.github.polpetta.mezzotre.helper.TestConfig;
|
||||||
import com.github.polpetta.mezzotre.i18n.LocalizedMessageFactory;
|
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.TgChat;
|
||||||
import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext;
|
import com.github.polpetta.mezzotre.orm.model.query.QCallbackQueryContext;
|
||||||
import com.github.polpetta.mezzotre.orm.model.query.QTgChat;
|
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.Clock;
|
||||||
import com.github.polpetta.mezzotre.util.UUIDGenerator;
|
import com.github.polpetta.mezzotre.util.UUIDGenerator;
|
||||||
import com.github.polpetta.types.json.ChatContext;
|
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.ExecutionException;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import org.apache.velocity.app.VelocityEngine;
|
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.BeforeAll;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Tag;
|
import org.junit.jupiter.api.Tag;
|
||||||
|
@ -62,12 +62,7 @@ class StartIntegrationTest {
|
||||||
void setUp() throws Exception {
|
void setUp() throws Exception {
|
||||||
database =
|
database =
|
||||||
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
|
||||||
velocityEngine = new VelocityEngine();
|
velocityEngine = Loader.defaultVelocityEngine();
|
||||||
velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADERS, "classpath");
|
|
||||||
velocityEngine.setProperty(
|
|
||||||
"resource.loader.classpath.class", ClasspathResourceLoader.class.getName());
|
|
||||||
velocityEngine.init();
|
|
||||||
localizedMessageFactory = new LocalizedMessageFactory(velocityEngine);
|
|
||||||
|
|
||||||
final Logger log = LoggerFactory.getLogger(Start.class);
|
final Logger log = LoggerFactory.getLogger(Start.class);
|
||||||
|
|
||||||
|
@ -76,11 +71,12 @@ class StartIntegrationTest {
|
||||||
|
|
||||||
start =
|
start =
|
||||||
new Start(
|
new Start(
|
||||||
localizedMessageFactory,
|
new TemplateContentGenerator(new LocalizedMessageFactory(velocityEngine)),
|
||||||
Executors.newSingleThreadExecutor(),
|
Executors.newSingleThreadExecutor(),
|
||||||
log,
|
log,
|
||||||
fakeUUIDGenerator,
|
fakeUUIDGenerator,
|
||||||
fakeClock);
|
"Mezzotre",
|
||||||
|
new ChatUtil(fakeClock));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -127,7 +123,7 @@ class StartIntegrationTest {
|
||||||
assertInstanceOf(SendMessage.class, gotMessage);
|
assertInstanceOf(SendMessage.class, gotMessage);
|
||||||
final String message = (String) gotMessage.getParameters().get("text");
|
final String message = (String) gotMessage.getParameters().get("text");
|
||||||
assertEquals(
|
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"
|
+ "This is _Mezzotre_, a simple bot focused on DnD content management! Please start by"
|
||||||
+ " choosing a language down below \uD83D\uDC47",
|
+ " choosing a language down below \uD83D\uDC47",
|
||||||
message);
|
message);
|
||||||
|
@ -185,7 +181,7 @@ class StartIntegrationTest {
|
||||||
assertInstanceOf(SendMessage.class, gotMessage);
|
assertInstanceOf(SendMessage.class, gotMessage);
|
||||||
final String message = (String) gotMessage.getParameters().get("text");
|
final String message = (String) gotMessage.getParameters().get("text");
|
||||||
assertEquals(
|
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"
|
+ "This is _Mezzotre_, a simple bot focused on DnD content management! Please start by"
|
||||||
+ " choosing a language down below \uD83D\uDC47",
|
+ " choosing a language down below \uD83D\uDC47",
|
||||||
message);
|
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