Compare commits

..

4 Commits

Author SHA1 Message Date
Davide Polonio 39b673bdfd feat: add translations, update db migration
continuous-integration/drone/push Build is passing Details
2023-05-11 12:33:54 +02:00
Davide Polonio 0169d28ffd test: add test for createCampaign command. Still wip
continuous-integration/drone/push Build is passing Details
2023-05-11 12:13:34 +02:00
Davide Polonio 81b25404b8 chore: add TODO 2023-05-10 23:59:43 +02:00
Davide Polonio d93fe8d3b9 feat: campaign draft, wip
continuous-integration/drone/push Build is passing Details
2023-05-10 22:50:29 +02:00
22 changed files with 1171 additions and 17 deletions

View File

@ -0,0 +1,63 @@
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 name;
@Length(4096)
@Nullable
@Null
private String description;
@ManyToMany(fetch = FetchType.LAZY)
private List<User> users;
public Campaign(String id, String name, @Nullable String description) {
this.id = id;
this.name = name;
this.description = description;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Nullable
public String getDescription() {
return description;
}
public void setDescription(@Nullable String description) {
this.description = description;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}

View File

@ -4,6 +4,7 @@ import io.ebean.annotation.ConstraintMode;
import io.ebean.annotation.DbForeignKey; import io.ebean.annotation.DbForeignKey;
import io.ebean.annotation.Length; import io.ebean.annotation.Length;
import io.ebean.annotation.NotNull; import io.ebean.annotation.NotNull;
import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.persistence.*; import javax.persistence.*;
@ -22,17 +23,20 @@ public class User extends Base {
@OneToOne(fetch = FetchType.LAZY, optional = true) @OneToOne(fetch = FetchType.LAZY, optional = true)
@DbForeignKey(onDelete = ConstraintMode.CASCADE) @DbForeignKey(onDelete = ConstraintMode.CASCADE)
@JoinColumn(name = "telegram_id", referencedColumnName = "id") @JoinColumn(name = "telegram_id", referencedColumnName = "id")
private TgChat telegramId; private TgChat telegramChat;
@ManyToMany(fetch = FetchType.LAZY)
private List<Campaign> campaigns;
@Length(256) @Length(256)
@Nullable @Nullable
private String emailAddress; 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.id = id;
this.emailAddress = emailAddress; this.emailAddress = emailAddress;
this.isActive = isActive; this.isActive = isActive;
this.telegramId = telegramId; this.telegramChat = telegramChat;
} }
public String getId() { public String getId() {
@ -47,19 +51,28 @@ public class User extends Base {
isActive = active; isActive = active;
} }
public TgChat getTelegramId() { public TgChat getTelegramChat() {
return telegramId; return telegramChat;
} }
public void setTelegramId(TgChat telegramId) { public void setTelegramChat(TgChat telegramChat) {
this.telegramId = telegramId; this.telegramChat = telegramChat;
} }
@Nullable
public String getEmailAddress() { public String getEmailAddress() {
return emailAddress; return emailAddress;
} }
public void setEmailAddress(String emailAddress) { public void setEmailAddress(@Nullable String emailAddress) {
this.emailAddress = emailAddress; this.emailAddress = emailAddress;
} }
public List<Campaign> getCampaigns() {
return campaigns;
}
public void setCampaigns(List<Campaign> campaigns) {
this.campaigns = campaigns;
}
} }

View File

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

View File

@ -34,19 +34,20 @@ public class ChatUtil {
* then saved in the database * then saved in the database
* *
* @param chat the chat that will be updated with the new {@link ChatContext} values * @param chat the chat that will be updated with the new {@link ChatContext} values
* @param stepName the step name to set * @param stageName the stage name to set
* @param stageNumber the stage number to set * @param stepNumber the step number to set
* @param additionalFields if there are, additional custom fields that will be added to {@link * @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 * 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 * available for custom entries. Use {@link Collections#emptyMap()} if you don't wish to add
* any additional field * any additional field
*/ */
public void updateChatContext( public void updateChatContext(
TgChat chat, String stepName, int stageNumber, Map<String, Object> additionalFields) { TgChat chat, String stageName, int stepNumber, Map<String, Object> additionalFields) {
final ChatContext chatContext = chat.getChatContext(); final ChatContext chatContext = chat.getChatContext();
chatContext.setStage(stepName); chatContext.setStage(stageName);
chatContext.setStep(stageNumber); chatContext.setStep(stepNumber);
chatContext.setPreviousMessageUnixTimestampInSeconds(clock.now()); chatContext.setPreviousMessageUnixTimestampInSeconds(clock.now());
chatContext.getAdditionalProperties().clear();
additionalFields.forEach(chatContext::setAdditionalProperty); additionalFields.forEach(chatContext::setAdditionalProperty);
chat.setChatContext(chatContext); chat.setChatContext(chatContext);

View File

@ -10,7 +10,7 @@ import java.util.Optional;
* @author Davide Polonio * @author Davide Polonio
* @since 1.0 * @since 1.0
*/ */
public class Util { class Util {
/** /**
* Extract the message id of the given {@link Update} * 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, * @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. * otherwise a {@link Optional#empty()} if it is not found.
*/ */
public static Optional<Integer> extractMessageId(Update update) { static Optional<Integer> extractMessageId(Update update) {
return Optional.ofNullable(update.callbackQuery().message()).map(Message::messageId); return Optional.ofNullable(update.callbackQuery().message()).map(Message::messageId);
} }
} }

View File

@ -37,10 +37,12 @@ public class CommandDI extends AbstractModule {
@Provides @Provides
@Singleton @Singleton
@Named("commandProcessor") @Named("commandProcessor")
public Map<String, Processor> getCommandProcessor(Start start, Help help) { public Map<String, Processor> getCommandProcessor(
Start start, Help help, CreateCampaign createCampaign) {
final HashMap<String, Processor> commandMap = new HashMap<>(); final HashMap<String, Processor> commandMap = new HashMap<>();
commandMap.putAll(mapForProcessor(start)); commandMap.putAll(mapForProcessor(start));
commandMap.putAll(mapForProcessor(help)); commandMap.putAll(mapForProcessor(help));
commandMap.putAll(mapForProcessor(createCampaign));
return commandMap; return commandMap;
} }

View File

@ -0,0 +1,179 @@
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<String> getTriggerKeywords() {
return Set.of(TRIGGERING_KEYWORD, "/newCampaign");
}
@Override
public String getLocaleDescriptionKeyword() {
return "createCampaign.cmdDescription";
}
@Override
public CompletableFuture<Optional<BaseRequest<?, ?>>> 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<Optional<BaseRequest<?, ?>>> 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<Optional<BaseRequest<?, ?>>>
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.1.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<Optional<BaseRequest<?, ?>>> 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.2.vm");
})
.orElseGet(
() ->
templateContentGenerator.mergeTemplate(
ctx -> {},
chat.getLocale(),
"template/telegram/createCampaign.descriptionNotValid.vm")))
.thenApply(
message ->
Optional.of(new SendMessage(chat.getId(), message).parseMode(ParseMode.Markdown)));
}
}

View File

@ -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<Long> extractSenderId(Update update) {
return Optional.ofNullable(update).map(Update::message).map(Message::from).map(User::id);
}
static Optional<String> extractText(Update update) {
return Optional.ofNullable(update).map(Update::message).map(Message::text);
}
}

View File

@ -8,6 +8,21 @@ create table callback_query_context (
constraint pk_callback_query_context primary key (id) constraint pk_callback_query_context primary key (id)
); );
create table campaign (
id varchar(64) not null,
name varchar(256) not null,
description varchar(4096),
entry_created timestamptz not null,
entry_modified timestamptz not null,
constraint pk_campaign primary key (id)
);
create table campaign_registered_user (
campaign_id varchar(64) not null,
registered_user_id varchar(64) not null,
constraint pk_campaign_registered_user primary key (campaign_id,registered_user_id)
);
create table telegram_chat ( create table telegram_chat (
id bigint generated by default as identity not null, id bigint generated by default as identity not null,
chat_context jsonb not null default '{}'::jsonb not null, chat_context jsonb not null default '{}'::jsonb not null,
@ -29,5 +44,23 @@ create table registered_user (
constraint pk_registered_user primary key (id) constraint pk_registered_user primary key (id)
); );
create table registered_user_campaign (
registered_user_id varchar(64) not null,
campaign_id varchar(64) not null,
constraint pk_registered_user_campaign primary key (registered_user_id,campaign_id)
);
-- foreign keys and indices -- foreign keys and indices
create index ix_campaign_registered_user_campaign on campaign_registered_user (campaign_id);
alter table campaign_registered_user add constraint fk_campaign_registered_user_campaign foreign key (campaign_id) references campaign (id) on delete restrict on update restrict;
create index ix_campaign_registered_user_registered_user on campaign_registered_user (registered_user_id);
alter table campaign_registered_user add constraint fk_campaign_registered_user_registered_user foreign key (registered_user_id) references registered_user (id) on delete restrict on update restrict;
alter table registered_user add constraint fk_registered_user_telegram_id foreign key (telegram_id) references telegram_chat (id) on delete cascade on update restrict; alter table registered_user add constraint fk_registered_user_telegram_id foreign key (telegram_id) references telegram_chat (id) on delete cascade on update restrict;
create index ix_registered_user_campaign_registered_user on registered_user_campaign (registered_user_id);
alter table registered_user_campaign add constraint fk_registered_user_campaign_registered_user foreign key (registered_user_id) references registered_user (id) on delete restrict on update restrict;
create index ix_registered_user_campaign_campaign on registered_user_campaign (campaign_id);
alter table registered_user_campaign add constraint fk_registered_user_campaign_campaign foreign key (campaign_id) references campaign (id) on delete restrict on update restrict;

View File

@ -21,3 +21,12 @@ help.buttonsToo=You can do the same operations you''d do with the commands afore
help.cmdDescription=Print the help message 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.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! notfound.howToHelp=Let me show you what I can do by typing /help in the chat!
createCampaign.letsStartName=Very well, give me a second that I go grab some scroll, ink and quill...
createCampaign.pickUpAction=*Searches frenetically in the desk between piles of books and written scrolls for a free piece of scroll*
createCampaign.readyPromptName=Here we are! Please, tell me, how would you like to call your new campaign?
createCampaign.optionalDescription=Uhmm yes, {0}... Very well, great name choice by the way! Now, would you like to give me a short description of your campaign? You can skip this step if you don''t want, otherwise just reply with the description
createCampaign.creationDone=All done! I have successfully added your campaign to my archives! Let me store it properly...
createCampaign.finalCreationAction=*Finishes to write down on the scroll the last details*
createCampaign.lookToPileAction=*Moves the scroll into a pile of other, disorganized, scrolls*
createCampaign.shareCampaign=Now, if you want, you can share this campaign to other players so that they can join! To do this, just click on the button below and then select the user or the group you want to share this campaign with! They will be able to add their characters and see the other characters in this campaign
createCampaign.cmdDescription=Create a new DnD campaign

View File

@ -21,3 +21,12 @@ help.buttonsToo=You can do the same operations you''d do with the commands afore
help.cmdDescription=Print the help message 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.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! notfound.howToHelp=Let me show you what I can do by typing /help in the chat!
createCampaign.letsStartName=Very well, give me a second that I go grab some scroll, ink and quill...
createCampaign.pickUpAction=*Searches frenetically in the desk between piles of books and written scrolls for a free piece of scroll*
createCampaign.readyPromptName=Here we are! Please, tell me, how would you like to call your new campaign?
createCampaign.optionalDescription=Uhmm yes, {0}... Very well, great name choice by the way! Now, would you like to give me a short description of your campaign? You can skip this step if you don''t want, otherwise just reply with the description
createCampaign.creationDone=All done! I have successfully added your campaign to my archives! Let me store it properly...
createCampaign.finalCreationAction=*Finishes to write down on the scroll the last details*
createCampaign.lookToPileAction=*Moves the scroll into a pile of other, disorganized, scrolls*
createCampaign.shareCampaign=Now, if you want, you can share this campaign to other players so that they can join! To do this, just click on the button below and then select the user or the group you want to share this campaign with! They will be able to add their characters and see the other characters in this campaign
createCampaign.cmdDescription=Create a new DnD campaign

View File

@ -21,3 +21,4 @@ help.buttonsToo=Puoi fare le stesse operazioni che faresti con i comandi elencat
help.cmdDescription=Stampa il messaggio d''aiuto 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.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! notfound.howToHelp=Lascia che ti mostri cosa posso fare, scrivi /help nella chat!
createCampaign.cmdDescription=Crea una nuova campagna di DnD

View File

@ -21,3 +21,4 @@ help.buttonsToo=Puoi fare le stesse operazioni che faresti con i comandi elencat
help.cmdDescription=Stampa il messaggio d''aiuto 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.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! notfound.howToHelp=Lascia che ti mostri cosa posso fare, scrivi /help nella chat!
createCampaign.cmdDescription=Crea una nuova campagna di DnD

View File

@ -0,0 +1,5 @@
${i18n.createCampaign.letsStartName}
_${i18n.createCampaign.pickUpAction}_
${i18n.createCampaign.readyPromptName}

View File

@ -0,0 +1 @@
${i18n.createCampaign.optionalDescription.insert(${campaignName})}

View File

@ -0,0 +1,7 @@
_${i18n.createCampaign.finalCreationAction}_
${i18n.createCampaign.creationDone}
_${i18n.createCampaign.lookToPileAction}_
${i18n.createCampaign.shareCampaign}

View File

@ -0,0 +1 @@
${i18n.createCampaign.descriptionNotValid}

View File

@ -0,0 +1 @@
${i18n.createCampaign.nameNotValid}

View File

@ -69,6 +69,6 @@ public class UserIntegrationTest {
final User id123 = new QUser().id.eq("id123").findOne(); final User id123 = new QUser().id.eq("id123").findOne();
assertNotNull(id123); assertNotNull(id123);
assertTrue(id123.isActive()); assertTrue(id123.isActive());
assertNull(id123.getTelegramId()); assertNull(id123.getTelegramChat());
} }
} }

View File

@ -67,4 +67,27 @@ class ChatUtilTest {
assertEquals(obj1, gotAdditionalProperties.get("field1")); assertEquals(obj1, gotAdditionalProperties.get("field1"));
assertEquals(obj2, gotAdditionalProperties.get("field2")); assertEquals(obj2, gotAdditionalProperties.get("field2"));
} }
@Test
void shouldEmptyAdditionalElementsIfEmptyMapIsPassed() {
final TgChat fakeTgChat = mock(TgChat.class);
final ChatContext chatContext = new ChatContext();
chatContext.setAdditionalProperty("en example", "a value");
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());
final ChatContext capturedChatContextValue = chatContextArgumentCaptor.getValue();
final Map<String, Object> gotAdditionalProperties =
capturedChatContextValue.getAdditionalProperties();
assertEquals(
0,
gotAdditionalProperties.size(),
"The map should be empty in this case, but some elements where found");
}
} }

View File

@ -0,0 +1,273 @@
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.Campaign;
import com.github.polpetta.mezzotre.orm.model.TgChat;
import com.github.polpetta.mezzotre.orm.model.query.QCampaign;
import com.github.polpetta.mezzotre.orm.model.query.QTgChat;
import com.github.polpetta.mezzotre.orm.telegram.CampaignUtil;
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.request.BaseRequest;
import com.pengrad.telegrambot.request.SendMessage;
import io.ebean.Database;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@Tag("slow")
@Tag("database")
@Tag("velocity")
@Testcontainers
class CreateCampaignIntegrationTest {
private static Gson gson;
@Container
private final PostgreSQLContainer<?> postgresServer =
new PostgreSQLContainer<>(TestConfig.POSTGRES_DOCKER_IMAGE);
private VelocityEngine velocityEngine;
private Database database;
private UUIDGenerator fakeUUIDGenerator;
private Clock fakeClock;
private ChatUtil chatUtil;
private CampaignUtil campaignUtil;
private CreateCampaign createCampaign;
@BeforeAll
static void beforeAll() {
gson = new Gson();
}
@BeforeEach
void setUp() throws Exception {
database =
Loader.connectToDatabase(Loader.loadDefaultEbeanConfigWithPostgresSettings(postgresServer));
velocityEngine = Loader.defaultVelocityEngine();
fakeClock = mock(Clock.class);
fakeUUIDGenerator = mock(UUIDGenerator.class);
chatUtil = new ChatUtil(fakeClock);
campaignUtil = new CampaignUtil(fakeUUIDGenerator);
createCampaign =
new CreateCampaign(
new TemplateContentGenerator(new LocalizedMessageFactory(velocityEngine)),
Executors.newSingleThreadExecutor(),
fakeUUIDGenerator,
chatUtil,
campaignUtil);
}
static Stream<Arguments> getValidCampaignEntries() {
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("getValidCampaignEntries")
@Timeout(value = 1, unit = TimeUnit.MINUTES)
void shouldCreateANewCampaign(String name, String description) throws Exception {
when(fakeUUIDGenerator.generateAsString()).thenReturn("64b483b4-7de8-4632-9117-927756d26210");
final TgChat chat =
new TgChat(
42L, new ChatContext(), "en-US", true // doesn't really matter
);
chat.save();
{
final Update firstMessage =
gson.fromJson(
"{\n"
+ "\"update_id\":10000,\n"
+ "\"message\":{\n"
+ " \"date\":1441645532,\n"
+ " \"chat\":{\n"
+ " \"last_name\":\"Test Lastname\",\n"
+ " \"id\":42,\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<Optional<BaseRequest<?, ?>>> got =
createCampaign.process(chat, firstMessage);
final Optional<BaseRequest<?, ?>> baseRequestOptional = got.get();
final BaseRequest<?, ?> gotResponse = baseRequestOptional.get();
assertInstanceOf(SendMessage.class, gotResponse);
assertEquals(
"Very well, give me a second that I go grab some scroll, ink and quill...\n"
+ "\n"
+ "_*Searches frenetically in the desk between piles of books and written scrolls for"
+ " a free piece of scroll*_\n"
+ "\n"
+ "Here we are! Please, tell me, how would you like to call your new campaign?",
gotResponse.getParameters().get("text"));
final ChatContext retrievedChatContext =
Objects.requireNonNull(new QTgChat().id.eq(42L).findOne()).getChatContext();
assertEquals(1, retrievedChatContext.getStep());
assertEquals("/createCampaign", retrievedChatContext.getStage());
}
{
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<Optional<BaseRequest<?, ?>>> got =
createCampaign.process(chat, secondMessageName);
final Optional<BaseRequest<?, ?>> baseRequestOptional = got.get();
final BaseRequest<?, ?> gotResponse = baseRequestOptional.get();
assertInstanceOf(SendMessage.class, gotResponse);
assertEquals(
"Uhmm yes, "
+ name
+ "... Very well, great name choice by the way! Now, would you like to give me a"
+ " short description of your campaign? You can skip this step if you don't want,"
+ " otherwise just reply with the description",
gotResponse.getParameters().get("text"));
final ChatContext retrievedChatContext =
Objects.requireNonNull(new QTgChat().id.eq(42L).findOne()).getChatContext();
assertEquals(2, retrievedChatContext.getStep());
assertEquals("/createCampaign", retrievedChatContext.getStage());
assertEquals(name, retrievedChatContext.getAdditionalProperties().get("campaign_name"));
}
{
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<Optional<BaseRequest<?, ?>>> got =
createCampaign.process(chat, thirdMessageDescription);
final Optional<BaseRequest<?, ?>> baseRequestOptional = got.get();
final BaseRequest<?, ?> gotResponse = baseRequestOptional.get();
assertInstanceOf(SendMessage.class, gotResponse);
assertEquals(
"_*Finishes to write down on the scroll the last details*_\n"
+ "\n"
+ "All done! I have successfully added your campaign to my archives! Let me store it"
+ " properly...\n"
+ "\n"
+ "_*Moves the scroll into a pile of other, disorganized, scrolls*_\n"
+ "\n"
+ "Now, if you want, you can share this campaign to other players so that they can"
+ " join! To do this, just click on the button below and then select the user or the"
+ " group you want to share this campaign with! They will be able to add their"
+ " characters and see the other characters in this campaign",
gotResponse.getParameters().get("text"));
final ChatContext retrievedChatContext =
Objects.requireNonNull(new QTgChat().id.eq(42L).findOne()).getChatContext();
assertEquals(3, retrievedChatContext.getStep());
assertEquals("/createCampaign", retrievedChatContext.getStage());
assertEquals(
0,
retrievedChatContext.getAdditionalProperties().size(),
() ->
"Additional Properties should be empty, instead got: \n"
+ retrievedChatContext.getAdditionalProperties().entrySet().stream()
.map(e -> "key: " + e.getKey() + " -> value: " + e.getValue())
.collect(Collectors.joining("\n")));
}
final Campaign gotCampaign =
new QCampaign().name.eq(name).id.eq("64b483b4-7de8-4632-9117-927756d26210").findOne();
assertNotNull(gotCampaign);
// First two assertions kinda pointless btw
assertEquals("64b483b4-7de8-4632-9117-927756d26210", gotCampaign.getId());
assertEquals(name, gotCampaign.getName());
assertEquals(description, gotCampaign.getDescription());
}
// TODO add invalid name tests, add InlineButtonTests too
}

View File

@ -0,0 +1,493 @@
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 com.pengrad.telegrambot.request.SendMessage;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.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);
}
static Stream<Arguments> getValidCampaignEntries() {
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("getValidCampaignEntries")
@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<Optional<BaseRequest<?, ?>>> got =
createCampaign.process(fakeTgChat, firstMessage);
final Optional<BaseRequest<?, ?>> baseRequestOptional = got.get();
final BaseRequest<?, ?> gotResponse = baseRequestOptional.get();
assertInstanceOf(SendMessage.class, gotResponse);
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.1.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<Optional<BaseRequest<?, ?>>> got =
createCampaign.process(fakeTgChat, secondMessageName);
final Optional<BaseRequest<?, ?>> baseRequestOptional = got.get();
final BaseRequest<?, ?> gotResponse = baseRequestOptional.get();
assertInstanceOf(SendMessage.class, gotResponse);
assertEquals("a second string", gotResponse.getParameters().get("text"));
final ArgumentCaptor<Map<String, Object>> capturedChatCtx =
ArgumentCaptor.forClass(Map.class);
verify(fakeChatUtil, times(1))
.updateChatContext(
eq(fakeTgChat), eq("/createCampaign"), eq(2), capturedChatCtx.capture());
final Map<String, Object> chatCtxValue = capturedChatCtx.getValue();
assertEquals(name, chatCtxValue.get("campaign_name"));
}
{
when(fakeTemplateContentGenerator.mergeTemplate(
any(), eq("en-US"), eq("template/telegram/createCampaign.2.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<Optional<BaseRequest<?, ?>>> got =
createCampaign.process(fakeTgChat, thirdMessageDescription);
final Optional<BaseRequest<?, ?>> baseRequestOptional = got.get();
final BaseRequest<?, ?> gotResponse = baseRequestOptional.get();
assertInstanceOf(SendMessage.class, gotResponse);
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());
}
}
// TODO test failures, check InlineButtons too
static Stream<Arguments> getInvalidCampaignNameEntries() {
// In the future we can add more invalid test cases
return Stream.of(
Arguments.of(
"jDjonwP0iRSDGwRfUbdwOoF9iq478mRvMjaFCB9mtwKHyymOp0neEj3XU5aOafffyFeKyJS0sFuleeYVHDavMeoZf6ozM86MfB82K0ONtBztEiWy35gWmxefTfLf6xyhXdI8Monev9F6J68lqgedHYGuzORvMbfMiAu673v7tv5ZNkaoBko3q4aiI4zDL1IWZx1drucRc4puZq9XUeNEyw9Fahpn33nCt15CzC9YTVqNDGu6vR3P52GUZKlv8hRv33nCt15CzC9YTVqNDGu6vR3P52GUZKlv8hRv"));
}
@ParameterizedTest
@Timeout(value = 1, unit = TimeUnit.MINUTES)
@MethodSource("getInvalidCampaignNameEntries")
void shouldNotPassNameValidation(String name) 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<Optional<BaseRequest<?, ?>>> got =
createCampaign.process(fakeTgChat, firstMessage);
final Optional<BaseRequest<?, ?>> baseRequestOptional = got.get();
final BaseRequest<?, ?> gotResponse = baseRequestOptional.get();
assertInstanceOf(SendMessage.class, gotResponse);
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.nameNotValid.vm")))
.thenReturn("an error message");
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<Optional<BaseRequest<?, ?>>> got =
createCampaign.process(fakeTgChat, secondMessageName);
final Optional<BaseRequest<?, ?>> baseRequestOptional = got.get();
final BaseRequest<?, ?> gotResponse = baseRequestOptional.get();
assertInstanceOf(SendMessage.class, gotResponse);
assertEquals("an error message", gotResponse.getParameters().get("text"));
verify(fakeChatUtil, times(0))
.updateChatContext(eq(fakeTgChat), eq("/createCampaign"), eq(2), any());
}
}
static Stream<Arguments> getInvalidCampaignDescriptionEntries() {
// In the future we can add more invalid test cases
return Stream.of(
Arguments.of(
"a campaign name",
"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"));
}
@ParameterizedTest
@Timeout(value = 1, unit = TimeUnit.MINUTES)
@MethodSource("getInvalidCampaignDescriptionEntries")
void shouldNotPassDescriptionValidation(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<Optional<BaseRequest<?, ?>>> got =
createCampaign.process(fakeTgChat, firstMessage);
final Optional<BaseRequest<?, ?>> baseRequestOptional = got.get();
final BaseRequest<?, ?> gotResponse = baseRequestOptional.get();
assertInstanceOf(SendMessage.class, gotResponse);
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.1.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<Optional<BaseRequest<?, ?>>> got =
createCampaign.process(fakeTgChat, secondMessageName);
final Optional<BaseRequest<?, ?>> baseRequestOptional = got.get();
final BaseRequest<?, ?> gotResponse = baseRequestOptional.get();
assertInstanceOf(SendMessage.class, gotResponse);
assertEquals("a second string", gotResponse.getParameters().get("text"));
final ArgumentCaptor<Map<String, Object>> capturedChatCtx =
ArgumentCaptor.forClass(Map.class);
verify(fakeChatUtil, times(1))
.updateChatContext(
eq(fakeTgChat), eq("/createCampaign"), eq(2), capturedChatCtx.capture());
final Map<String, Object> chatCtxValue = capturedChatCtx.getValue();
assertEquals(name, chatCtxValue.get("campaign_name"));
}
{
when(fakeTemplateContentGenerator.mergeTemplate(
any(), eq("en-US"), eq("template/telegram/createCampaign.descriptionNotValid.vm")))
.thenReturn("an error message");
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<Optional<BaseRequest<?, ?>>> got =
createCampaign.process(fakeTgChat, thirdMessageDescription);
final Optional<BaseRequest<?, ?>> baseRequestOptional = got.get();
final BaseRequest<?, ?> gotResponse = baseRequestOptional.get();
assertInstanceOf(SendMessage.class, gotResponse);
assertEquals("an error message", gotResponse.getParameters().get("text"));
verify(fakeCampaignUtil, times(1)).insertNewCampaign(name, description);
verify(fakeChatUtil, times(1))
.updateChatContext(fakeTgChat, "/createCampaign", 3, Collections.emptyMap());
}
}
}