JavaRush /Java Blog /Random-IT /Rimuoviamo l'abbonamento agli articoli dal gruppo - "Prog...
Roman Beekeeper
Livello 35

Rimuoviamo l'abbonamento agli articoli dal gruppo - "Progetto Java dalla A alla Z"

Pubblicato nel gruppo Random-IT
Ciao a tutti, miei cari amici, futuri ingegneri software senior. Continuiamo a sviluppare un bot di Telegram. In questa fase del nostro progetto, prenderemo in considerazione tre attività che hanno un valore più visibile rispetto al valore del programma. Dobbiamo imparare come rimuovere un abbonamento a nuovi articoli da un gruppo specifico: utilizzare il comando /stop per disattivare il bot e utilizzare il comando /start per attivarlo. Inoltre, tutte le richieste e gli aggiornamenti riguardano solo gli utenti attivi del bot. Come al solito, aggiorneremo il ramo principale per ottenere tutte le modifiche e crearne uno nuovo: STEP_7_JRTB-7. In questa parte parleremo dell'eliminazione di un abbonamento e considereremo 5 opzioni per gli eventi: sarà interessante.

JRTB-7: rimozione di un abbonamento a nuovi articoli da un gruppo

È chiaro che tutti gli utenti vorranno poter cancellare la propria iscrizione per non ricevere notifiche sui nuovi articoli. La sua logica sarà molto simile alla logica di aggiunta di un abbonamento. Se inviamo un solo comando, in risposta riceveremo l'elenco dei gruppi e dei relativi ID a cui l'utente è già iscritto, in modo da poter capire cosa esattamente occorre eliminare. E se l'utente invia l'ID del gruppo insieme al team, elimineremo l'abbonamento. Andiamo quindi a sviluppare questo comando dal lato bot di Telegram.
  1. Aggiungiamo il nome del nuovo comando - /deleteGroupSub e in CommandName - la riga:

    DELETE_GROUP_SUB("/deleteGroupSub")

  2. Successivamente, creiamo il 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));
       }
    }

Per fare ciò, abbiamo dovuto aggiungere altri due metodi per lavorare con l'entità GroupSub: recuperare dal database tramite ID e salvare l'entità stessa. Tutti questi metodi chiamano semplicemente metodi di repository già pronti. Ti parlerò separatamente dell'eliminazione di un abbonamento. Nello schema del database, questa è la tabella responsabile del processo molti-a-molti e per eliminare questa relazione è necessario eliminare il record in essa contenuto. Questo se usiamo la comprensione generale da parte del database. Ma usiamo Spring Data e c'è Hibernate per impostazione predefinita, che può farlo in modo diverso. Otteniamo l'entità GroupSub, a cui verranno attratti tutti gli utenti ad essa associati. Da questa raccolta di utenti rimuoveremo quello di cui abbiamo bisogno e salveremo groupSub nel database, ma senza questo utente. In questo modo Spring Data capirà cosa volevamo ed eliminerà il record. "Progetto Java dalla A alla Z": Rimuovere l'iscrizione agli articoli dal gruppo - 1"Progetto Java dalla A alla Z": Rimuovere l'iscrizione agli articoli dal gruppo - 2Per rimuovere velocemente un utente, ho aggiunto l'annotazione EqualsAndHashCode per TelegramUser, escludendo la lista GroupSub in modo che non ci fossero problemi. E abbiamo chiamato il metodo di rimozione sulla raccolta di utenti con l'utente di cui abbiamo bisogno. Questo è quello che appare per 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;
}
Di conseguenza, tutto è andato come volevamo. Ci sono diversi eventi possibili in una squadra, quindi scrivere un buon test per ciascuno di essi è un'ottima idea. A proposito di test: mentre li scrivevo ho riscontrato un difetto nella logica e l'ho corretto prima che venisse messo in produzione. Se non ci fosse stato il test, non è chiaro quanto velocemente sarebbe stato rilevato. EliminaGruppoSubCommandTest:
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);
   }
}
Qui, ogni test controlla uno scenario separato e, lascia che te lo ricordi, ce ne sono solo cinque:
  • quando hai semplicemente passato il comando /deleteGroupSub e non ci sono iscrizioni ai gruppi;
  • quando hai semplicemente passato il comando /deleteGroupSub e ci sono iscrizioni ai gruppi;
  • quando è stato passato un ID di gruppo non valido, ad esempio /deleteGroupSub abc ;
  • uno scenario in cui tutto viene cancellato correttamente, come previsto;
  • uno scenario in cui l'ID del gruppo è valido, ma tale gruppo non è presente nel database.
Come puoi vedere, tutti questi scenari devono essere coperti da test. Mentre scrivevo mi sono reso conto che per scrivere test migliori vale la pena seguire alcuni corsi di testing. Penso che questo aiuterà a cercare correttamente diverse opzioni. Esatto, pensieri per il futuro. Successivamente, devi aggiungere una descrizione al comando /help per poter eliminare l'abbonamento. Inseriamolo nella sezione per lavorare con gli abbonamenti. "Progetto Java dalla A alla Z": Rimuovere l'iscrizione agli articoli dal gruppo - 3Naturalmente, affinché questo comando funzioni, è necessario aggiungere la sua inizializzazione al CommandContainer :
.put(DELETE_GROUP_SUB.getCommandName(),
       new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
Ora puoi testare la funzionalità su un bot di prova. Lanciamo il nostro database utilizzando docker-compose-test.yml: docker-compose -f docker-compose-test.yml up E lanciamo SpringBoot tramite IDEA. Cancellerò completamente la corrispondenza con il bot e ricomincerò. Esaminerò tutte le opzioni che potrebbero sorgere quando si lavora con questo team. "Progetto Java dalla A alla Z": Rimuovere l'iscrizione agli articoli dal gruppo - 4Come puoi vedere dallo screenshot, tutte le opzioni sono state eseguite e hanno avuto successo.
Amici! Vuoi sapere subito quando viene rilasciato il nuovo codice per un progetto? Quando esce un nuovo articolo? Unisciti al mio canale Telegram . Lì raccolgo insieme i miei articoli, pensieri e sviluppo open source.
Aggiorniamo la versione del nostro progetto a 0.6.0-SNAPSHOT Aggiorniamo RELEASE_NOTES.md, aggiungendo una descrizione di quanto fatto nella nuova versione:
## 0.6.0-SNAPSHOT * JRTB-7: aggiunta la possibilità di eliminare l'iscrizione al gruppo.
Il codice funziona, sono stati scritti dei test per questo: è ora di inserire l'attività nel repository e creare una richiesta pull."Progetto Java dalla A alla Z": Rimuovere l'iscrizione agli articoli dal gruppo - 5

Invece di finire

Non guardiamo la nostra bacheca di progetto da molto tempo, ma ci sono stati grandi cambiamenti: "Progetto Java dalla A alla Z": rimozione di un'iscrizione agli articoli dal gruppo - 6sono rimaste solo 5 attività. Cioè, tu ed io siamo già alla fine della strada. Lasciato un po'. È particolarmente interessante notare che questa serie di articoli è in corso da metà settembre, cioè da 7 mesi!!! Quando mi è venuta questa idea, non mi aspettavo che ci volesse così tanto tempo. Allo stesso tempo, sono più che soddisfatto del risultato! Amici, se non è chiaro cosa sta succedendo nell'articolo, fate domande nei commenti. In questo modo saprò che qualcosa deve essere descritto meglio e qualcosa necessita di ulteriori spiegazioni. Bene, come al solito, metti mi piace - iscriviti - suona il campanello, dai una stella al nostro progetto , scrivi commenti e valuta l'articolo! Grazie a tutti. Fino alla parte successiva. Parleremo presto di come aggiungere la disattivazione e l'attivazione dei bot tramite i comandi /stop e /start e di come utilizzarli al meglio. Arrivederci!

Un elenco di tutti i materiali della serie si trova all'inizio di questo articolo.

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