doc: add JavaDoc and remaining tests
parent
9ff5748964
commit
3baa041745
|
@ -17,6 +17,19 @@ 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<
|
||||
|
@ -24,6 +37,10 @@ public class BatchBeanCleanerService extends AbstractExecutionThreadService {
|
|||
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;
|
||||
|
@ -40,6 +57,7 @@ public class BatchBeanCleanerService extends AbstractExecutionThreadService {
|
|||
|
||||
@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
|
||||
|
@ -78,19 +96,39 @@ public class BatchBeanCleanerService extends AbstractExecutionThreadService {
|
|||
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<?, ?>> row) {
|
||||
String id, PString<? extends TQRootBean<?, ?>> column) {
|
||||
final CompletableFuture<Integer> jobExecution = new CompletableFuture<>();
|
||||
entriesToRemove.offer(Tuple.of(id, row, jobExecution));
|
||||
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) {
|
||||
|
|
|
@ -73,7 +73,7 @@ class ShowHelp implements Processor {
|
|||
return CompletableFuture.supplyAsync(
|
||||
() ->
|
||||
chatUtil
|
||||
.extractChat(callbackQueryContext, update) // FIXME callbackquerycontext removal?
|
||||
.extractChat(callbackQueryContext, update)
|
||||
.map(
|
||||
chat -> {
|
||||
final String message = modelHelp.getMessage(chat, tgCommandProcessors);
|
||||
|
|
|
@ -4,9 +4,21 @@ 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 {
|
||||
|
||||
// FIXME tests, doc
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
|
|
@ -87,7 +87,6 @@ class Start implements Processor {
|
|||
"template/telegram/start.vm");
|
||||
log.trace("Start command - message to send back: " + message);
|
||||
|
||||
// FIXME bug!! Show help button set to true but its fake news
|
||||
chatUtil.updateChatContext(chat, TRIGGERING_STAGING_NAME, 0, Collections.emptyMap());
|
||||
|
||||
// To get the messageId we should send the message first, then save it in the database!
|
||||
|
|
|
@ -13,11 +13,17 @@ import java.util.stream.Collectors;
|
|||
import javax.inject.Inject;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
// FIXME tests!
|
||||
/**
|
||||
* 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 static final String TRIGGERING_STAGING_NAME = "/help";
|
||||
|
||||
private final TemplateContentGenerator templateContentGenerator;
|
||||
private final UUIDGenerator uuidGenerator;
|
||||
|
||||
|
@ -27,6 +33,15 @@ public class Help {
|
|||
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 -> {
|
||||
|
@ -45,6 +60,17 @@ public class Help {
|
|||
"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) {
|
||||
|
|
|
@ -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,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"));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue