JavaRush /Java Blog /Random EN /Remove a subscription to articles from the group - "Java ...

Remove a subscription to articles from the group - "Java project from A to Z"

Published in the Random EN group
Hello everyone, my dear friends, future Senior Software Engineers. We continue to develop the telegram bot. At this step of our project, we will consider three tasks that have more visible value than programmatic. We need to learn how to remove a subscription to new articles from a specific group: use the /stop command to deactivate the bot, and use the /start command to activate it. And so that all requests and updates concern only active users of the bot. As usual, update the main branch to get all the changes and create a new one: STEP_7_JRTB-7. In this part, we will talk about deleting a subscription and consider 5 options for events - it will be interesting.

JRTB-7: Removing a new article subscription from a group

It is clear that all users will want to be able to unsubscribe so that they do not receive notifications of new articles. Its logic will be very similar to the logic of adding a subscription. If we send only one command, in response we will receive a list of groups and their IDs to which the user is already subscribed, so that we can understand what exactly needs to be deleted. And if the user passes the group ID along with the team, we will remove the subscription. Therefore, let's go to develop this command from the side of the telegram bot.
  1. Add the name of the new command - /deleteGroupSub , and in the CommandName - the line:

    DELETE_GROUP_SUB("/deleteGroupSub")

  2. Next, let's create the DeleteGroupSubCommand command :

    package com.github.codegymcommunity.jrtb.command;
    
    import com.github.codegymcommunity.jrtb.repository.entity.GroupSub;
    import com.github.codegymcommunity.jrtb.repository.entity.TelegramUser;
    import com.github.codegymcommunity.jrtb.service.GroupSubService;
    import com.github.codegymcommunity.jrtb.service.SendBotMessageService;
    import com.github.codegymcommunity.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.codegymcommunity.jrtb.command.CommandName.DELETE_GROUP_SUB;
    import static com.github.codegymcommunity.jrtb.command.CommandUtils.getChatId;
    import static com.github.codegymcommunity.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));
       }
    }

To do this, we had to add two more methods for working with the GroupSub entity - getting from the database by ID and saving the entity itself. All these methods simply call ready-made methods of the repository. Separately, I will tell you about deleting a subscription. In the database schema, this is the table that is responsible for the many-to-many process, and to remove this relationship, you need to delete the entry in it. It if to use the general understanding from a DB. But we use Spring Data and there is Hibernate by default, which can do it differently. We get the GroupSub entity, to which all users associated with it will be pulled. From this collection of users, we will delete the one we need and save the groupSub back in the database, but without this user. This way Spring Data will understand what we wanted and remove the entry."Java project from A to Z": Remove a subscription to articles from a group - 1"Java project from A to Z": Remove a subscription to articles from a group - 2To quickly delete a user, I added an EqualsAndHashCode annotation for TelegramUser, excluding the GroupSub list so that there are no problems. And called the remove method on the user collection with the user we need. Here's what it looks like for 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;
}
As a result, everything took off, as we wanted. There are several event options in the team, so writing a good test for each of them is a great idea. Speaking of tests: while writing them, I found a defect in the logic and corrected it even before the release. There would be no test - it is not clear how quickly he would have discovered it. DeleteGroupSubCommandTest:
package com.github.codegymcommunity.jrtb.command;

import com.github.codegymcommunity.jrtb.repository.entity.GroupSub;
import com.github.codegymcommunity.jrtb.repository.entity.TelegramUser;
import com.github.codegymcommunity.jrtb.service.GroupSubService;
import com.github.codegymcommunity.jrtb.service.SendBotMessageService;
import com.github.codegymcommunity.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.codegymcommunity.jrtb.command.AbstractCommandTest.prepareUpdate;
import static com.github.codegymcommunity.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);
   }
}
Here, each test checks a separate scenario, and, let me remind you, there are only five of them:
  • when the /deleteGroupSub command was simply passed and there are no subscriptions to groups;
  • when they simply passed the /deleteGroupSub command and there are subscriptions to groups;
  • when an invalid group ID is passed, such as /deleteGroupSub abc ;
  • a scenario in which everything is correctly deleted, as expected;
  • a scenario where the group ID is valid, but there is no such group in the database.
As you can see, all these scenarios need to be covered with tests. While I was writing, I realized that for better writing tests, it is worth taking some tester courses. I think this will help to correctly look for different options. That's right, thoughts for the future. Next, you need to add to the /help command a description that you can now delete a subscription. Let's put it in the section for working with subscriptions. "Java project from A to Z": Remove a subscription to articles from a group - 3Of course, for this command to work, you need to add its initialization to the CommandContainer :
.put(DELETE_GROUP_SUB.getCommandName(),
       new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
Now you can test the functionality already on the test bot. We start our database using docker-compose-test.yml: docker-compose -f docker-compose-test.yml up And through IDEA we start SpringBoot. I will completely clear the correspondence with the bot and start again. I will run through all the options that may arise when working with this team. "Java project from A to Z": Remove a subscription to articles from a group - 4As you can see from the screenshot, all options went through and were successful.
Friends! Do you want to know immediately when a new code for the project is released? When is the new article coming out? Join my Telegram channel . There I gather my articles, thoughts and open source development together.
Update the version of our project to 0.6.0-SNAPSHOT Update RELEASE_NOTES.md, adding a description of what has been done in the new version:
## 0.6.0-SNAPSHOT * JRTB-7: added the ability to delete group subscription.
The code works, tests are written for it: it's time to push the task to the repository and create a pull request."Java project from A to Z": Remove a subscription to articles from a group - 5

Instead of ending

We haven't looked at our project board for a long time, but big changes have taken place there: "Java project from A to Z": Remove a subscription to articles from a group - 6There are only 5 tasks left. That is, we are already at the very end of the road. Left a little. Especially to see that this series of articles has been going on since mid-September, that is, for 7 months!!! When I came up with this idea, I did not expect that it would be sooo long. However, I am more than satisfied with the result! Friends, if it is not clear what is happening in the article, ask questions in the comments. This way I will know that something needs to be better described, and something needs to be further explained. Well, as usual, like - subscribe - bell, put a star to our project , write comments and rate the article! Thanks to all. Until the next part. We'll soon talk about how to add deactivation and activation of the bot through /stop & /start commands and how to use them better. See you later!"Java project from A to Z": Remove a subscription to articles from a group - 7

List of all materials in the series at the beginning of this article.

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