JavaRush /Blog Java /Random-FR /Nous supprimons l'abonnement aux articles du groupe - "Pr...
Roman Beekeeper
Niveau 35

Nous supprimons l'abonnement aux articles du groupe - "Projet Java de A à Z"

Publié dans le groupe Random-FR
Bonjour à tous, mes chers amis, futurs ingénieurs logiciels seniors. Nous continuons à développer un robot télégramme. Dans cette étape de notre projet, nous considérerons trois tâches qui ont plus de valeur visible que la valeur du programme. Nous devons apprendre à supprimer un abonnement aux nouveaux articles d'un groupe spécifique : utilisez la commande /stop pour désactiver le bot et utilisez la commande /start pour l'activer. De plus, toutes les demandes et mises à jour concernent uniquement les utilisateurs actifs du bot. Comme d'habitude, nous mettrons à jour la branche principale pour récupérer toutes les modifications et en créer une nouvelle : STEP_7_JRTB-7. Dans cette partie, nous parlerons de la suppression d'un abonnement et considérerons 5 options pour les événements - ce sera intéressant.

JRTB-7 : supprimer un abonnement aux nouveaux articles d'un groupe

Il est clair que tous les utilisateurs voudront pouvoir supprimer leur abonnement pour ne pas recevoir de notifications sur les nouveaux articles. Sa logique sera très similaire à la logique d’ajout d’un abonnement. Si nous envoyons une seule commande, nous recevrons en réponse une liste des groupes et leurs identifiants auxquels l'utilisateur est déjà abonné, afin que nous puissions comprendre exactement ce qui doit être supprimé. Et si l'utilisateur envoie l'identifiant du groupe avec l'équipe, nous supprimerons l'abonnement. Par conséquent, allons développer cette commande du côté du bot Telegram.
  1. Ajoutons le nom de la nouvelle commande - /deleteGroupSub , et dans CommandName - la ligne :

    DELETE_GROUP_SUB("/deleteGroupSub")

  2. Créons ensuite la commande 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));
       }
    }

Pour ce faire, nous avons dû ajouter deux méthodes supplémentaires pour travailler avec l'entité GroupSub : récupérer de la base de données par ID et enregistrer l'entité elle-même. Toutes ces méthodes appellent simplement des méthodes de référentiel prêtes à l'emploi. Je vais vous parler séparément de la suppression d'un abonnement. Dans le schéma de la base de données, il s'agit de la table qui est responsable du processus plusieurs-à-plusieurs, et pour supprimer cette relation, vous devez supprimer l'enregistrement qu'elle contient. C'est si nous utilisons la compréhension générale de la base de données. Mais nous utilisons Spring Data et il existe Hibernate par défaut, qui peut faire cela différemment. Nous obtenons l'entité GroupSub, vers laquelle tous les utilisateurs qui lui sont associés seront attirés. De cette collection d'utilisateurs, nous supprimerons celui dont nous avons besoin et sauvegarderons groupSub dans la base de données, mais sans cet utilisateur. De cette façon, Spring Data comprendra ce que nous voulions et supprimera l'enregistrement. "Projet Java de A à Z" : Suppression de l'abonnement aux articles du groupe - 1"Projet Java de A à Z" : Suppression de l'abonnement aux articles du groupe - 2Pour supprimer rapidement un utilisateur, j'ai ajouté l'annotation EqualsAndHashCode pour TelegramUser, en excluant la liste GroupSub afin qu'il n'y ait aucun problème. Et j'ai appelé la méthode Remove pour rassembler les utilisateurs avec l'utilisateur dont nous avons besoin. Voici à quoi cela ressemble pour TelegramUser :
@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;
}
En conséquence, tout s’est déroulé comme nous le souhaitions. Il y a plusieurs événements possibles dans une équipe, donc rédiger un bon test pour chacun d'eux est une excellente idée. En parlant de tests : pendant que je les écrivais, j'ai trouvé un défaut dans la logique et je l'ai corrigé avant sa mise en production. S’il n’y avait pas eu de test, on ne sait pas avec quelle rapidité il aurait été détecté. SupprimerGroupSubCommandTest :
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);
   }
}
Ici, chaque test vérifie un scénario distinct, et permettez-moi de vous rappeler qu'il n'y en a que cinq :
  • lorsque vous avez simplement passé la commande /deleteGroupSub et qu'il n'y a aucun abonnement de groupe ;
  • lorsque vous avez simplement passé la commande /deleteGroupSub et qu'il existe des abonnements à des groupes ;
  • lorsqu'un ID de groupe non valide a été transmis, par exemple /deleteGroupSub abc ;
  • un scénario dans lequel tout est supprimé correctement, comme prévu ;
  • un scénario dans lequel l'ID de groupe est valide, mais un tel groupe n'est pas dans la base de données.
Comme vous pouvez le constater, tous ces scénarios doivent être couverts par des tests. Pendant que j'écrivais, j'ai réalisé que pour rédiger de meilleurs tests, cela valait la peine de suivre des cours de tests. Je pense que cela aidera à rechercher correctement différentes options. C'est vrai, des pensées pour l'avenir. Ensuite, vous devez ajouter une description à la commande /help indiquant que vous pouvez désormais supprimer l'abonnement. Plaçons-le dans la section pour travailler avec les abonnements. "Projet Java de A à Z" : Suppression de l'abonnement aux articles du groupe - 3Bien entendu, pour que cette commande fonctionne, vous devez ajouter son initialisation au CommandContainer :
.put(DELETE_GROUP_SUB.getCommandName(),
       new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
Vous pouvez désormais tester la fonctionnalité sur un robot de test. Nous lançons notre base de données en utilisant docker-compose-test.yml : docker-compose -f docker-compose-test.yml up Et lançons SpringBoot via IDEA. Je vais effacer complètement la correspondance avec le bot et recommencer. Je vais passer en revue toutes les options qui peuvent se présenter lorsque je travaille avec cette équipe. "Projet Java de A à Z" : Suppression de l'abonnement aux articles du groupe - 4Comme vous pouvez le voir sur la capture d’écran, toutes les options ont été acceptées et ont abouti.
Amis! Voulez-vous savoir immédiatement quand le nouveau code d'un projet est publié ? Quand sort un nouvel article ? Rejoignez ma chaîne Telegram . Là, je rassemble mes articles, mes réflexions et mon développement open source.
Nous mettons à jour la version de notre projet vers 0.6.0-SNAPSHOT Nous mettons à jour RELEASE_NOTES.md, en ajoutant une description de ce qui a été fait dans la nouvelle version :
## 0.6.0-SNAPSHOT * JRTB-7 : ajout de la possibilité de supprimer un abonnement de groupe.
Le code fonctionne, des tests ont été écrits pour cela : il est temps de pousser la tâche dans le référentiel et de créer une pull request."Projet Java de A à Z" : Suppression de l'abonnement aux articles du groupe - 5

Au lieu de finir

Nous n'avons pas regardé notre tableau de projet depuis longtemps, mais il y a eu de gros changements : "Projet Java de A à Z" : Supprimer un abonnement aux articles du groupe - 6il ne reste plus que 5 tâches. Autrement dit, vous et moi sommes déjà au bout du chemin. Parti un peu. Il est particulièrement intéressant de noter que cette série d’articles dure depuis la mi-septembre, soit depuis 7 mois !!! Quand j’ai eu cette idée, je ne m’attendais pas à ce que cela prenne autant de temps. En même temps, je suis plus que satisfaite du résultat ! Amis, si ce qui se passe dans l'article n'est pas clair, posez des questions dans les commentaires. De cette façon, je saurai que quelque chose doit être mieux décrit et que quelque chose nécessite des explications plus approfondies. Eh bien, comme d'habitude, aimez - abonnez-vous - sonnez la cloche, donnez une étoile à notre projet , écrivez des commentaires et notez l'article ! Merci à tous. Jusqu'à la prochaine partie. Nous parlerons bientôt de la façon d'ajouter la désactivation et l'activation du bot via les commandes /stop & /start et de la meilleure façon de les utiliser. À plus tard!

Une liste de tous les matériaux de la série se trouve au début de cet article.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION