Olá a todos, meus queridos amigos, futuros engenheiros de software sênior. Continuamos a desenvolver um bot de telegrama. Nesta etapa do nosso projeto, consideraremos três tarefas que têm valor mais visível do que o valor do programa. Precisamos aprender como remover a assinatura de novos artigos de um grupo específico: use o comando /stop para desativar o bot e use o comando /start para ativá-lo. Além disso, todas as solicitações e atualizações dizem respeito apenas aos usuários ativos do bot. Como de costume, atualizaremos o branch principal para obter todas as alterações e criaremos um novo: STEP_7_JRTB-7. Nesta parte falaremos sobre a exclusão de uma assinatura e consideraremos 5 opções de eventos - será interessante.
Atualizamos a versão do nosso projeto para 0.6.0-SNAPSHOT Atualizamos RELEASE_NOTES.md, adicionando uma descrição do que foi feito na nova versão:
curta - inscreva-se - toque a campainha, dê uma estrela ao nosso projeto , escreva comentários e avalie o artigo! Obrigado a todos. Até a próxima parte. Falaremos em breve sobre como adicionar desativação e ativação de bot por meio dos comandos /stop e /start e como usá-los da melhor forma. Até mais!
JRTB-7: removendo uma assinatura para novos artigos de um grupo
É claro que todos os usuários desejarão excluir sua assinatura para não receber notificações sobre novos artigos. Sua lógica será muito semelhante à lógica de adicionar uma assinatura. Se enviarmos apenas um comando, em resposta receberemos uma lista de grupos e seus IDs nos quais o usuário já está inscrito, para que possamos entender exatamente o que precisa ser excluído. E se o usuário enviar o ID do grupo junto com a equipe, excluiremos a assinatura. Portanto, vamos desenvolver este comando do lado do bot do telegrama.-
Vamos adicionar o nome do novo comando - /deleteGroupSub , e em CommandName - a linha:
DELETE_GROUP_SUB("/deleteGroupSub")
-
A seguir, vamos criar o comando DeleteGroupSubCommand :
package com.github.javarushcommunity.jrtb.command; import com.github.javarushcommunity.jrtb.repository.entity.GroupSub; import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser; import com.github.javarushcommunity.jrtb.service.GroupSubService; import com.github.javarushcommunity.jrtb.service.SendBotMessageService; import com.github.javarushcommunity.jrtb.service.TelegramUserService; import org.springframework.util.CollectionUtils; import org.telegram.telegrambots.meta.api.objects.Update; import javax.ws.rs.NotFoundException; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import static com.github.javarushcommunity.jrtb.command.CommandName.DELETE_GROUP_SUB; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getChatId; import static com.github.javarushcommunity.jrtb.command.CommandUtils.getMessage; import static java.lang.String.format; import static org.apache.commons.lang3.StringUtils.SPACE; import static org.apache.commons.lang3.StringUtils.isNumeric; /** * Delete Group subscription {@link Command}. */ public class DeleteGroupSubCommand implements Command { private final SendBotMessageService sendBotMessageService; private final TelegramUserService telegramUserService; private final GroupSubService groupSubService; public DeleteGroupSubCommand(SendBotMessageService sendBotMessageService, GroupSubService groupSubService, TelegramUserService telegramUserService) { this.sendBotMessageService = sendBotMessageService; this.groupSubService = groupSubService; this.telegramUserService = telegramUserService; } @Override public void execute(Update update) { if (getMessage(update).equalsIgnoreCase(DELETE_GROUP_SUB.getCommandName())) { sendGroupIdList(getChatId(update)); return; } String groupId = getMessage(update).split(SPACE)[1]; String chatId = getChatId(update); if (isNumeric(groupId)) { Optional<GroupSub> optionalGroupSub = groupSubService.findById(Integer.valueOf(groupId)); if (optionalGroupSub.isPresent()) { GroupSub groupSub = optionalGroupSub.get(); TelegramUser telegramUser = telegramUserService.findByChatId(chatId).orElseThrow(NotFoundException::new); groupSub.getUsers().remove(telegramUser); groupSubService.save(groupSub); sendBotMessageService.sendMessage(chatId, format("Удалил подписку на группу: %s", groupSub.getTitle())); } else { sendBotMessageService.sendMessage(chatId, "Не нашел такой группы =/"); } } else { sendBotMessageService.sendMessage(chatId, "неправильный формат ID группы.\n " + "ID должно быть целым положительным числом"); } } private void sendGroupIdList(String chatId) { String message; List<GroupSub> groupSubs = telegramUserService.findByChatId(chatId) .orElseThrow(NotFoundException::new) .getGroupSubs(); if (CollectionUtils.isEmpty(groupSubs)) { message = "Пока нет подписок на группы. Whatбы добавить подписку напиши /addGroupSub"; } else { message = "Whatбы удалить подписку на группу - передай комадну вместе с ID группы. \n" + "Например: /deleteGroupSub 16 \n\n" + "я подготовил список всех групп, на которые ты подписан) \n\n" + "Name группы - ID группы \n\n" + "%s"; } String userGroupSubData = groupSubs.stream() .map(group -> format("%s - %s \n", group.getTitle(), group.getId())) .collect(Collectors.joining()); sendBotMessageService.sendMessage(chatId, format(message, userGroupSubData)); } }
@Data
@Entity
@Table(name = "tg_user")
@EqualsAndHashCode(exclude = "groupSubs")
public class TelegramUser {
@Id
@Column(name = "chat_id")
private String chatId;
@Column(name = "active")
private boolean active;
@ManyToMany(mappedBy = "users", fetch = FetchType.EAGER)
private List<GroupSub> groupSubs;
}
Como resultado, tudo decolou como queríamos. Existem vários eventos possíveis em uma equipe, então escrever um bom teste para cada um deles é uma ótima ideia. Falando em testes: enquanto os escrevia, encontrei um defeito na lógica e corrigi-o antes de colocá-lo em produção. Se não tivesse havido teste, não está claro com que rapidez teria sido detectado. ExcluirGroupSubCommandTest:
package com.github.javarushcommunity.jrtb.command;
import com.github.javarushcommunity.jrtb.repository.entity.GroupSub;
import com.github.javarushcommunity.jrtb.repository.entity.TelegramUser;
import com.github.javarushcommunity.jrtb.service.GroupSubService;
import com.github.javarushcommunity.jrtb.service.SendBotMessageService;
import com.github.javarushcommunity.jrtb.service.TelegramUserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.telegram.telegrambots.meta.api.objects.Update;
import java.util.ArrayList;
import java.util.Optional;
import static com.github.javarushcommunity.jrtb.command.AbstractCommandTest.prepareUpdate;
import static com.github.javarushcommunity.jrtb.command.CommandName.DELETE_GROUP_SUB;
import static java.util.Collections.singletonList;
@DisplayName("Unit-level testing for DeleteGroupSubCommand")
class DeleteGroupSubCommandTest {
private Command command;
private SendBotMessageService sendBotMessageService;
GroupSubService groupSubService;
TelegramUserService telegramUserService;
@BeforeEach
public void init() {
sendBotMessageService = Mockito.mock(SendBotMessageService.class);
groupSubService = Mockito.mock(GroupSubService.class);
telegramUserService = Mockito.mock(TelegramUserService.class);
command = new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService);
}
@Test
public void shouldProperlyReturnEmptySubscriptionList() {
//given
Long chatId = 23456L;
Update update = prepareUpdate(chatId, DELETE_GROUP_SUB.getCommandName());
Mockito.when(telegramUserService.findByChatId(String.valueOf(chatId)))
.thenReturn(Optional.of(new TelegramUser()));
String expectedMessage = "Пока нет подписок на группы. Whatбы добавить подписку напиши /addGroupSub";
//when
command.execute(update);
//then
Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
}
@Test
public void shouldProperlyReturnSubscriptionLit() {
//given
Long chatId = 23456L;
Update update = prepareUpdate(chatId, DELETE_GROUP_SUB.getCommandName());
TelegramUser telegramUser = new TelegramUser();
GroupSub gs1 = new GroupSub();
gs1.setId(123);
gs1.setTitle("GS1 Title");
telegramUser.setGroupSubs(singletonList(gs1));
Mockito.when(telegramUserService.findByChatId(String.valueOf(chatId)))
.thenReturn(Optional.of(telegramUser));
String expectedMessage = "Whatбы удалить подписку на группу - передай комадну вместе с ID группы. \n" +
"Например: /deleteGroupSub 16 \n\n" +
"я подготовил список всех групп, на которые ты подписан) \n\n" +
"Name группы - ID группы \n\n" +
"GS1 Title - 123 \n";
//when
command.execute(update);
//then
Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
}
@Test
public void shouldRejectByInvalidGroupId() {
//given
Long chatId = 23456L;
Update update = prepareUpdate(chatId, String.format("%s %s", DELETE_GROUP_SUB.getCommandName(), "groupSubId"));
TelegramUser telegramUser = new TelegramUser();
GroupSub gs1 = new GroupSub();
gs1.setId(123);
gs1.setTitle("GS1 Title");
telegramUser.setGroupSubs(singletonList(gs1));
Mockito.when(telegramUserService.findByChatId(String.valueOf(chatId)))
.thenReturn(Optional.of(telegramUser));
String expectedMessage = "неправильный формат ID группы.\n " +
"ID должно быть целым положительным числом";
//when
command.execute(update);
//then
Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
}
@Test
public void shouldProperlyDeleteByGroupId() {
//given
/// prepare update object
Long chatId = 23456L;
Integer groupId = 1234;
Update update = prepareUpdate(chatId, String.format("%s %s", DELETE_GROUP_SUB.getCommandName(), groupId));
GroupSub gs1 = new GroupSub();
gs1.setId(123);
gs1.setTitle("GS1 Title");
TelegramUser telegramUser = new TelegramUser();
telegramUser.setChatId(chatId.toString());
telegramUser.setGroupSubs(singletonList(gs1));
ArrayList<TelegramUser> users = new ArrayList<>();
users.add(telegramUser);
gs1.setUsers(users);
Mockito.when(groupSubService.findById(groupId)).thenReturn(Optional.of(gs1));
Mockito.when(telegramUserService.findByChatId(String.valueOf(chatId)))
.thenReturn(Optional.of(telegramUser));
String expectedMessage = "Удалил подписку на группу: GS1 Title";
//when
command.execute(update);
//then
users.remove(telegramUser);
Mockito.verify(groupSubService).save(gs1);
Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
}
@Test
public void shouldDoesNotExistByGroupId() {
//given
Long chatId = 23456L;
Integer groupId = 1234;
Update update = prepareUpdate(chatId, String.format("%s %s", DELETE_GROUP_SUB.getCommandName(), groupId));
Mockito.when(groupSubService.findById(groupId)).thenReturn(Optional.empty());
String expectedMessage = "Не нашел такой группы =/";
//when
command.execute(update);
//then
Mockito.verify(groupSubService).findById(groupId);
Mockito.verify(sendBotMessageService).sendMessage(chatId.toString(), expectedMessage);
}
}
Aqui, cada teste verifica um cenário separado e, deixe-me lembrá-lo, existem apenas cinco deles:
- quando você simplesmente passou o comando /deleteGroupSub e não há assinaturas de grupo;
- quando você simplesmente passou o comando /deleteGroupSub e há assinaturas em grupos;
- quando um ID de grupo inválido foi passado, por exemplo, /deleteGroupSub abc ;
- um cenário em que tudo é apagado corretamente, conforme o esperado;
- um cenário em que o ID do grupo é válido, mas esse grupo não está no banco de dados.
.put(DELETE_GROUP_SUB.getCommandName(),
new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
Agora você pode testar a funcionalidade em um bot de teste. Lançamos nosso banco de dados usando docker-compose-test.yml: docker-compose -f docker-compose-test.yml up E lançamos o SpringBoot via IDEA. Vou limpar completamente a correspondência com o bot e começar de novo. Analisarei todas as opções que podem surgir ao trabalhar com esta equipe. Como você pode ver na captura de tela, todas as opções foram executadas e bem-sucedidas.
Amigos! Você quer saber imediatamente quando um novo código para um projeto for lançado? Quando sai um novo artigo? Junte-se ao meu canal Telegram . Lá eu reúno meus artigos, pensamentos e desenvolvimento de código aberto. |
## 0.6.0-SNAPSHOT * JRTB-7: adicionada a capacidade de excluir assinatura de grupo.
O código funciona, testes foram escritos para ele: é hora de enviar a tarefa para o repositório e criar uma solicitação pull.
GO TO FULL VERSION