From d93fe8d3b9e00daaaa624f2509b39266ee1540b1 Mon Sep 17 00:00:00 2001 From: Davide Polonio Date: Wed, 10 May 2023 22:50:29 +0200 Subject: [PATCH] feat: campaign draft, wip --- .../polpetta/mezzotre/orm/model/Campaign.java | 60 +++++ .../polpetta/mezzotre/orm/model/User.java | 29 ++- .../mezzotre/orm/telegram/CampaignUtil.java | 22 ++ .../mezzotre/orm/telegram/ChatUtil.java | 10 +- .../mezzotre/telegram/callbackquery/Util.java | 4 +- .../mezzotre/telegram/command/CommandDI.java | 4 +- .../telegram/command/CreateCampaign.java | 174 ++++++++++++++ .../mezzotre/telegram/command/Util.java | 17 ++ .../template/telegram/createCampaign.0.vm | 1 + .../template/telegram/createCampaign.1.vm | 1 + .../template/telegram/createCampaign.2.vm | 1 + .../createCampaign.descriptionNotValid.vm | 1 + .../telegram/createCampaign.nameNotValid.vm | 1 + .../orm/model/UserIntegrationTest.java | 2 +- .../telegram/command/CreateCampaignTest.java | 223 ++++++++++++++++++ 15 files changed, 533 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/github/polpetta/mezzotre/orm/model/Campaign.java create mode 100644 src/main/java/com/github/polpetta/mezzotre/orm/telegram/CampaignUtil.java create mode 100644 src/main/java/com/github/polpetta/mezzotre/telegram/command/CreateCampaign.java create mode 100644 src/main/java/com/github/polpetta/mezzotre/telegram/command/Util.java create mode 100644 src/main/resources/template/telegram/createCampaign.0.vm create mode 100644 src/main/resources/template/telegram/createCampaign.1.vm create mode 100644 src/main/resources/template/telegram/createCampaign.2.vm create mode 100644 src/main/resources/template/telegram/createCampaign.descriptionNotValid.vm create mode 100644 src/main/resources/template/telegram/createCampaign.nameNotValid.vm create mode 100644 src/test/java/com/github/polpetta/mezzotre/telegram/command/CreateCampaignTest.java diff --git a/src/main/java/com/github/polpetta/mezzotre/orm/model/Campaign.java b/src/main/java/com/github/polpetta/mezzotre/orm/model/Campaign.java new file mode 100644 index 0000000..c5267b7 --- /dev/null +++ b/src/main/java/com/github/polpetta/mezzotre/orm/model/Campaign.java @@ -0,0 +1,60 @@ +package com.github.polpetta.mezzotre.orm.model; + +import io.ebean.annotation.Length; +import io.ebean.annotation.NotNull; +import java.util.List; +import javax.annotation.Nullable; +import javax.persistence.*; +import javax.validation.constraints.Null; + +@Entity +public class Campaign extends Base { + + @Id + @Length(64) + private final String id; + + @Length(256) + @NotNull + private String campaignName; + + @Nullable @Null private String description; + + @ManyToMany(fetch = FetchType.LAZY) + private List users; + + public Campaign(String id, String campaignName, @Nullable String description) { + this.id = id; + this.campaignName = campaignName; + this.description = description; + } + + public String getId() { + return id; + } + + public String getCampaignName() { + return campaignName; + } + + public void setCampaignName(String campaignName) { + this.campaignName = campaignName; + } + + @Nullable + public String getDescription() { + return description; + } + + public void setDescription(@Nullable String description) { + this.description = description; + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } +} diff --git a/src/main/java/com/github/polpetta/mezzotre/orm/model/User.java b/src/main/java/com/github/polpetta/mezzotre/orm/model/User.java index 105362b..8f5cbb6 100644 --- a/src/main/java/com/github/polpetta/mezzotre/orm/model/User.java +++ b/src/main/java/com/github/polpetta/mezzotre/orm/model/User.java @@ -4,6 +4,7 @@ import io.ebean.annotation.ConstraintMode; import io.ebean.annotation.DbForeignKey; import io.ebean.annotation.Length; import io.ebean.annotation.NotNull; +import java.util.List; import javax.annotation.Nullable; import javax.persistence.*; @@ -22,17 +23,20 @@ public class User extends Base { @OneToOne(fetch = FetchType.LAZY, optional = true) @DbForeignKey(onDelete = ConstraintMode.CASCADE) @JoinColumn(name = "telegram_id", referencedColumnName = "id") - private TgChat telegramId; + private TgChat telegramChat; + + @ManyToMany(fetch = FetchType.LAZY) + private List campaigns; @Length(256) @Nullable private String emailAddress; - public User(String id, String emailAddress, Boolean isActive, TgChat telegramId) { + public User(String id, String emailAddress, Boolean isActive, TgChat telegramChat) { this.id = id; this.emailAddress = emailAddress; this.isActive = isActive; - this.telegramId = telegramId; + this.telegramChat = telegramChat; } public String getId() { @@ -47,19 +51,28 @@ public class User extends Base { isActive = active; } - public TgChat getTelegramId() { - return telegramId; + public TgChat getTelegramChat() { + return telegramChat; } - public void setTelegramId(TgChat telegramId) { - this.telegramId = telegramId; + public void setTelegramChat(TgChat telegramChat) { + this.telegramChat = telegramChat; } + @Nullable public String getEmailAddress() { return emailAddress; } - public void setEmailAddress(String emailAddress) { + public void setEmailAddress(@Nullable String emailAddress) { this.emailAddress = emailAddress; } + + public List getCampaigns() { + return campaigns; + } + + public void setCampaigns(List campaigns) { + this.campaigns = campaigns; + } } diff --git a/src/main/java/com/github/polpetta/mezzotre/orm/telegram/CampaignUtil.java b/src/main/java/com/github/polpetta/mezzotre/orm/telegram/CampaignUtil.java new file mode 100644 index 0000000..d65f485 --- /dev/null +++ b/src/main/java/com/github/polpetta/mezzotre/orm/telegram/CampaignUtil.java @@ -0,0 +1,22 @@ +package com.github.polpetta.mezzotre.orm.telegram; + +import com.github.polpetta.mezzotre.orm.model.Campaign; +import com.github.polpetta.mezzotre.util.UUIDGenerator; +import javax.inject.Inject; + +public class CampaignUtil { + + private final UUIDGenerator uuidGenerator; + + @Inject + public CampaignUtil(UUIDGenerator uuidGenerator) { + this.uuidGenerator = uuidGenerator; + } + + public Campaign insertNewCampaign(String name, String description) { + final Campaign campaign = new Campaign(uuidGenerator.generateAsString(), name, description); + + campaign.save(); + return campaign; + } +} diff --git a/src/main/java/com/github/polpetta/mezzotre/orm/telegram/ChatUtil.java b/src/main/java/com/github/polpetta/mezzotre/orm/telegram/ChatUtil.java index 4fc26fd..47dd423 100644 --- a/src/main/java/com/github/polpetta/mezzotre/orm/telegram/ChatUtil.java +++ b/src/main/java/com/github/polpetta/mezzotre/orm/telegram/ChatUtil.java @@ -34,18 +34,18 @@ public class ChatUtil { * 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 stageName the stage name to set + * @param stepNumber the step 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 additionalFields) { + TgChat chat, String stageName, int stepNumber, Map additionalFields) { final ChatContext chatContext = chat.getChatContext(); - chatContext.setStage(stepName); - chatContext.setStep(stageNumber); + chatContext.setStage(stageName); + chatContext.setStep(stepNumber); chatContext.setPreviousMessageUnixTimestampInSeconds(clock.now()); additionalFields.forEach(chatContext::setAdditionalProperty); diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Util.java b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Util.java index 3637292..7b73d01 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Util.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/callbackquery/Util.java @@ -10,7 +10,7 @@ import java.util.Optional; * @author Davide Polonio * @since 1.0 */ -public class Util { +class Util { /** * Extract the message id of the given {@link Update} @@ -19,7 +19,7 @@ public class Util { * @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 extractMessageId(Update update) { + static Optional extractMessageId(Update update) { return Optional.ofNullable(update.callbackQuery().message()).map(Message::messageId); } } diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/command/CommandDI.java b/src/main/java/com/github/polpetta/mezzotre/telegram/command/CommandDI.java index 767fdab..9213cd0 100644 --- a/src/main/java/com/github/polpetta/mezzotre/telegram/command/CommandDI.java +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/command/CommandDI.java @@ -37,10 +37,12 @@ public class CommandDI extends AbstractModule { @Provides @Singleton @Named("commandProcessor") - public Map getCommandProcessor(Start start, Help help) { + public Map getCommandProcessor( + Start start, Help help, CreateCampaign createCampaign) { final HashMap commandMap = new HashMap<>(); commandMap.putAll(mapForProcessor(start)); commandMap.putAll(mapForProcessor(help)); + commandMap.putAll(mapForProcessor(createCampaign)); return commandMap; } diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/command/CreateCampaign.java b/src/main/java/com/github/polpetta/mezzotre/telegram/command/CreateCampaign.java new file mode 100644 index 0000000..651e090 --- /dev/null +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/command/CreateCampaign.java @@ -0,0 +1,174 @@ +package com.github.polpetta.mezzotre.telegram.command; + +import com.github.polpetta.mezzotre.i18n.TemplateContentGenerator; +import com.github.polpetta.mezzotre.orm.model.TgChat; +import com.github.polpetta.mezzotre.orm.telegram.CampaignUtil; +import com.github.polpetta.mezzotre.orm.telegram.ChatUtil; +import com.github.polpetta.mezzotre.util.UUIDGenerator; +import com.pengrad.telegrambot.model.Update; +import com.pengrad.telegrambot.model.request.ParseMode; +import com.pengrad.telegrambot.request.BaseRequest; +import com.pengrad.telegrambot.request.SendMessage; +import 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; + +// FIXME tests, doc +public class CreateCampaign implements Processor { + + private static final String TRIGGERING_KEYWORD = "/createCampaign"; + // FIXME create the label + private static final String BUTTON_CREATE_IT_LOCALE_KEY = "createCampaign.createItButton"; + private static final String CAMPAIGN_NAME_CHAT_CTX_FIELD = "campaign_name"; + private static final String CAMPAIGN_NAME_TEMPLATE_CTX_FIELD = "campaignName"; + private static final int MAX_CAMPAIGN_NAME_LENGTH = 256; + private static final int MAX_CAMPAIGN_DESCRIPTION_LENGTH = 4096; + private final TemplateContentGenerator templateContentGenerator; + private final Executor threadPool; + private final UUIDGenerator uuidGenerator; + private final ChatUtil chatUtil; + private final CampaignUtil campaignUtil; + + @Inject + public CreateCampaign( + TemplateContentGenerator templateContentGenerator, + @Named("eventThreadPool") Executor threadPool, + UUIDGenerator uuidGenerator, + ChatUtil chatUtil, + CampaignUtil campaignUtil) { + + this.templateContentGenerator = templateContentGenerator; + this.threadPool = threadPool; + this.uuidGenerator = uuidGenerator; + this.chatUtil = chatUtil; + this.campaignUtil = campaignUtil; + } + + @Override + public Set getTriggerKeywords() { + return Set.of(TRIGGERING_KEYWORD, "/newCampaign"); + } + + @Override + public CompletableFuture>> process(TgChat chat, Update update) { + // There are multiple steps, we have to route the incoming campaign creation to the right one + return switch ((int) chat.getChatContext().getStep()) { + default -> promptCampaignName(chat); + case 1 -> verifyCampaignNameAndPromptCampaignDescription(chat, update); + case 2 -> verifyDescriptionAndCompleteCreation(chat, update); + }; + } + + private CompletableFuture>> promptCampaignName(TgChat chat) { + return CompletableFuture.supplyAsync( + () -> + templateContentGenerator.mergeTemplate( + ctx -> {}, chat.getLocale(), "template/telegram/createCampaign.0.vm"), + threadPool) + .thenApplyAsync( + message -> { + chatUtil.updateChatContext(chat, TRIGGERING_KEYWORD, 1, Collections.emptyMap()); + + return Optional.of( + new SendMessage(chat.getId(), message).parseMode(ParseMode.Markdown)); + }, + threadPool); + } + + private CompletableFuture>> + verifyCampaignNameAndPromptCampaignDescription(TgChat chat, Update update) { + /* + Flow: + 1 - Input validation (name length, etc...) + 2 - Build message according to validation output + 3 - Send message back + 4 - ??? + 5 - Profit + */ + return CompletableFuture.completedFuture(chat) + .thenApply( + // 1 - Perform input validation + ignored -> + Util.extractText(update) + .filter(campaignName -> campaignName.length() <= MAX_CAMPAIGN_NAME_LENGTH)) + .thenApplyAsync( + nameOpt -> { + // 2 - create appropriate message and update chat context + return nameOpt + .map( + campaignName -> { + chatUtil.updateChatContext( + chat, + TRIGGERING_KEYWORD, + 2, + Collections.singletonMap(CAMPAIGN_NAME_CHAT_CTX_FIELD, campaignName)); + return templateContentGenerator.mergeTemplate( + ctx -> ctx.put(CAMPAIGN_NAME_TEMPLATE_CTX_FIELD, campaignName), + chat.getLocale(), + "template/telegram/createCampaign.2.vm"); + }) + // We don't update the context here on purpose. If the user wants to try another + // campaign name is free to do so! + .orElseGet( + () -> + templateContentGenerator.mergeTemplate( + ctx -> {}, + chat.getLocale(), + "template/telegram/createCampaign.nameNotValid.vm")); + }) + .thenApply( + // 3 - send message back + message -> + Optional.of(new SendMessage(chat.getId(), message).parseMode(ParseMode.Markdown))); + } + + private CompletableFuture>> verifyDescriptionAndCompleteCreation( + TgChat chat, Update update) { + /* + Flow: + 1 - Input validation (description length, etc...) + 2 - Build message according to validation output + 3 - Send message back + 4 - ??? + 5 - Profit + */ + return CompletableFuture.completedFuture(chat) + .thenApply( + ignored -> + Util.extractText(update) + .filter(description -> description.length() <= MAX_CAMPAIGN_DESCRIPTION_LENGTH)) + .thenApplyAsync( + descriptionOpt -> + descriptionOpt + .map( + campaignDescription -> { + // We go full beans here because we know that the property will never be + // null at this point - if it is null, then there are bigger problems, and + // we should start questioning our life (and our programming skills + // really) + final String campaignName = + (String) + chat.getChatContext() + .getAdditionalProperties() + .get(CAMPAIGN_NAME_CHAT_CTX_FIELD); + campaignUtil.insertNewCampaign(campaignName, campaignDescription); + chatUtil.updateChatContext( + chat, TRIGGERING_KEYWORD, 3, Collections.emptyMap()); + return templateContentGenerator.mergeTemplate( + ctx -> {}, chat.getLocale(), "template/telegram/createCampaign.3.vm"); + }) + .orElseGet( + () -> + templateContentGenerator.mergeTemplate( + ctx -> {}, + chat.getLocale(), + "template/telegram/createCampaign.descriptionNotValid.vm"))) + .thenApply( + message -> + Optional.of(new SendMessage(chat.getId(), message).parseMode(ParseMode.Markdown))); + } +} diff --git a/src/main/java/com/github/polpetta/mezzotre/telegram/command/Util.java b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Util.java new file mode 100644 index 0000000..2d0715e --- /dev/null +++ b/src/main/java/com/github/polpetta/mezzotre/telegram/command/Util.java @@ -0,0 +1,17 @@ +package com.github.polpetta.mezzotre.telegram.command; + +import com.pengrad.telegrambot.model.Message; +import com.pengrad.telegrambot.model.Update; +import com.pengrad.telegrambot.model.User; +import java.util.Optional; + +class Util { + + static Optional extractSenderId(Update update) { + return Optional.ofNullable(update).map(Update::message).map(Message::from).map(User::id); + } + + static Optional extractText(Update update) { + return Optional.ofNullable(update).map(Update::message).map(Message::text); + } +} diff --git a/src/main/resources/template/telegram/createCampaign.0.vm b/src/main/resources/template/telegram/createCampaign.0.vm new file mode 100644 index 0000000..5752667 --- /dev/null +++ b/src/main/resources/template/telegram/createCampaign.0.vm @@ -0,0 +1 @@ +${i18n.createCampaign.letsStartName} \ No newline at end of file diff --git a/src/main/resources/template/telegram/createCampaign.1.vm b/src/main/resources/template/telegram/createCampaign.1.vm new file mode 100644 index 0000000..e529009 --- /dev/null +++ b/src/main/resources/template/telegram/createCampaign.1.vm @@ -0,0 +1 @@ +${i18n.createCampaign.optionalDescription} \ No newline at end of file diff --git a/src/main/resources/template/telegram/createCampaign.2.vm b/src/main/resources/template/telegram/createCampaign.2.vm new file mode 100644 index 0000000..d337feb --- /dev/null +++ b/src/main/resources/template/telegram/createCampaign.2.vm @@ -0,0 +1 @@ +${i18n.createCampaign.done} \ No newline at end of file diff --git a/src/main/resources/template/telegram/createCampaign.descriptionNotValid.vm b/src/main/resources/template/telegram/createCampaign.descriptionNotValid.vm new file mode 100644 index 0000000..17e7da8 --- /dev/null +++ b/src/main/resources/template/telegram/createCampaign.descriptionNotValid.vm @@ -0,0 +1 @@ +${i18n.createCampaign.descriptionNotValid} \ No newline at end of file diff --git a/src/main/resources/template/telegram/createCampaign.nameNotValid.vm b/src/main/resources/template/telegram/createCampaign.nameNotValid.vm new file mode 100644 index 0000000..8dcc74a --- /dev/null +++ b/src/main/resources/template/telegram/createCampaign.nameNotValid.vm @@ -0,0 +1 @@ +${i18n.createCampaign.nameNotValid} \ No newline at end of file diff --git a/src/test/java/com/github/polpetta/mezzotre/orm/model/UserIntegrationTest.java b/src/test/java/com/github/polpetta/mezzotre/orm/model/UserIntegrationTest.java index 40a6af0..d1c3825 100644 --- a/src/test/java/com/github/polpetta/mezzotre/orm/model/UserIntegrationTest.java +++ b/src/test/java/com/github/polpetta/mezzotre/orm/model/UserIntegrationTest.java @@ -69,6 +69,6 @@ public class UserIntegrationTest { final User id123 = new QUser().id.eq("id123").findOne(); assertNotNull(id123); assertTrue(id123.isActive()); - assertNull(id123.getTelegramId()); + assertNull(id123.getTelegramChat()); } } diff --git a/src/test/java/com/github/polpetta/mezzotre/telegram/command/CreateCampaignTest.java b/src/test/java/com/github/polpetta/mezzotre/telegram/command/CreateCampaignTest.java new file mode 100644 index 0000000..0a166c9 --- /dev/null +++ b/src/test/java/com/github/polpetta/mezzotre/telegram/command/CreateCampaignTest.java @@ -0,0 +1,223 @@ +package com.github.polpetta.mezzotre.telegram.command; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +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.orm.telegram.CampaignUtil; +import com.github.polpetta.mezzotre.orm.telegram.ChatUtil; +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.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 java.util.concurrent.TimeUnit; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; + +@Execution(ExecutionMode.CONCURRENT) +class CreateCampaignTest { + + private static Gson gson; + private TemplateContentGenerator fakeTemplateContentGenerator; + private UUIDGenerator fakeUUIDGenerator; + private ChatUtil fakeChatUtil; + private CampaignUtil fakeCampaignUtil; + private CreateCampaign createCampaign; + + @BeforeAll + static void beforeAll() { + gson = new Gson(); + } + + @BeforeEach + void setUp() { + + fakeTemplateContentGenerator = mock(TemplateContentGenerator.class); + fakeUUIDGenerator = mock(UUIDGenerator.class); + fakeChatUtil = mock(ChatUtil.class); + fakeCampaignUtil = mock(CampaignUtil.class); + + createCampaign = + new CreateCampaign( + fakeTemplateContentGenerator, + Executors.newSingleThreadExecutor(), + fakeUUIDGenerator, + fakeChatUtil, + fakeCampaignUtil); + } + + public static Stream getMultipleValidCampaignEntries() { + return Stream.of( + Arguments.of("a simple name", "a simple description"), + Arguments.of("a simple name", "a simple\nmultiline\n description"), + Arguments.of( + "an \uD83E\uDD21 emoji \uD83D\uDCAF name \uD83D\uDD25", + "a very \uD83E\uDDE8 campaign description \uD83E\uDD16"), + Arguments.of("一个简单的名字", "一个简单的描述")); + } + + @ParameterizedTest + @MethodSource("getMultipleValidCampaignEntries") + @Timeout(value = 1, unit = TimeUnit.MINUTES) + void shouldGenerateANewCampaign(String name, String description) throws Exception { + + final TgChat fakeTgChat = mock(TgChat.class); + final ChatContext initialChatContext = new ChatContext(); + initialChatContext.setStep(0); // implicit but better specify just for clarity + when(fakeTgChat.getChatContext()).thenReturn(initialChatContext); + when(fakeTgChat.getLocale()).thenReturn("en-US"); + + { + when(fakeTemplateContentGenerator.mergeTemplate( + any(), eq("en-US"), eq("template/telegram/createCampaign.0.vm"))) + .thenReturn("a string"); + final Update firstMessage = + 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\":\"/createCampaign\"\n" + + "}\n" + + "}", + Update.class); + + final CompletableFuture>> got = + createCampaign.process(fakeTgChat, firstMessage); + final Optional> baseRequestOptional = got.get(); + final BaseRequest gotResponse = baseRequestOptional.get(); + assertEquals("a string", gotResponse.getParameters().get("text")); + + verify(fakeChatUtil, times(1)) + .updateChatContext(fakeTgChat, "/createCampaign", 1, Collections.emptyMap()); + } + + { + when(fakeTemplateContentGenerator.mergeTemplate( + any(), eq("en-US"), eq("template/telegram/createCampaign.2.vm"))) + .thenReturn("a second string"); + final ChatContext chatContext = new ChatContext(); + chatContext.setStep(1); + when(fakeTgChat.getChatContext()).thenReturn(chatContext); + + final Update secondMessageName = + 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\":\"" + + name + + "\"\n" + + "}\n" + + "}", + Update.class); + + final CompletableFuture>> got = + createCampaign.process(fakeTgChat, secondMessageName); + final Optional> baseRequestOptional = got.get(); + final BaseRequest gotResponse = baseRequestOptional.get(); + assertEquals("a second string", gotResponse.getParameters().get("text")); + + final ArgumentCaptor> capturedChatCtx = + ArgumentCaptor.forClass(Map.class); + verify(fakeChatUtil, times(1)) + .updateChatContext( + eq(fakeTgChat), eq("/createCampaign"), eq(2), capturedChatCtx.capture()); + + final Map chatCtxValue = capturedChatCtx.getValue(); + assertEquals(name, chatCtxValue.get("campaign_name")); + } + + { + when(fakeTemplateContentGenerator.mergeTemplate( + any(), eq("en-US"), eq("template/telegram/createCampaign.3.vm"))) + .thenReturn("a third string"); + final ChatContext chatContext = new ChatContext(); + chatContext.setStep(2); + chatContext.setAdditionalProperty("campaign_name", name); + when(fakeTgChat.getChatContext()).thenReturn(chatContext); + + final Update thirdMessageDescription = + 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\":\"" + + description + + "\"\n" + + "}\n" + + "}", + Update.class); + + final CompletableFuture>> got = + createCampaign.process(fakeTgChat, thirdMessageDescription); + final Optional> baseRequestOptional = got.get(); + final BaseRequest gotResponse = baseRequestOptional.get(); + assertEquals("a third string", gotResponse.getParameters().get("text")); + + verify(fakeCampaignUtil, times(1)).insertNewCampaign(name, description); + verify(fakeChatUtil, times(1)) + .updateChatContext(fakeTgChat, "/createCampaign", 3, Collections.emptyMap()); + } + } +}