JavaRush /Java Blogu /Random-AZ /Qrupdan məqalələrə abunəni ləğv edirik - "A-dan Z-yə Java...
Roman Beekeeper
Səviyyə

Qrupdan məqalələrə abunəni ləğv edirik - "A-dan Z-yə Java layihəsi"

Qrupda dərc edilmişdir
Hər kəsə salam, əziz dostlarım, gələcəyin Baş Proqram Mühəndisləri. Biz teleqram botunu inkişaf etdirməyə davam edirik. Layihəmizin bu addımında proqram dəyərindən daha çox görünən dəyəri olan üç tapşırığı nəzərdən keçirəcəyik. Müəyyən bir qrupdan yeni məqalələrə abunəni necə silməyi öyrənməliyik: botu deaktiv etmək üçün /stop əmrindən istifadə edin və onu aktivləşdirmək üçün /start əmrindən istifadə edin. Üstəlik, bütün sorğular və yeniləmələr yalnız botun aktiv istifadəçilərinə aiddir. Həmişə olduğu kimi, bütün dəyişiklikləri əldə etmək və yenisini yaratmaq üçün əsas filialı yeniləyəcəyik : STEP_7_JRTB-7. Bu hissədə abunəni silmək haqqında danışacağıq və hadisələr üçün 5 variantı nəzərdən keçirəcəyik - maraqlı olacaq.

JRTB-7: qrupdan yeni məqalələrə abunəliyin silinməsi

Aydındır ki, bütün istifadəçilər yeni məqalələr haqqında bildiriş almamaq üçün abunəliklərini silmək istəyəcəklər. Onun məntiqi abunə əlavə etmək məntiqinə çox oxşar olacaq. Yalnız bir əmr göndərsək, cavab olaraq istifadəçinin artıq abunə olduğu qrupların siyahısını və onların identifikatorlarını alacağıq ki, dəqiq nəyin silinməli olduğunu başa düşək. İstifadəçi qrup ID-ni komanda ilə birlikdə göndərərsə, abunəni siləcəyik. Buna görə də gəlin telegram bot tərəfindən bu əmri inkişaf etdirək.
  1. Gəlin yeni əmrin adını əlavə edək - /deleteGroupSub , və CommandName -ə - sətri:

    DELETE_GROUP_SUB("/deleteGroupSub")

  2. Sonra, DeleteGroupSubCommand əmrini yaradaq :

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

Bunu etmək üçün biz GroupSub obyekti ilə işləmək üçün daha iki üsul əlavə etməli olduq - verilənlər bazasından ID ilə əldə etmək və obyektin özünü saxlamaq. Bütün bu üsullar sadəcə olaraq hazır repozitoriya metodlarını çağırır. Abunəliyin silinməsi haqqında sizə ayrıca məlumat verəcəyəm. Verilənlər bazası sxemində bu, çoxdan çoxa prosesinə cavabdeh olan cədvəldir və bu əlaqəni silmək üçün içindəki qeydi silmək lazımdır. Bu, verilənlər bazası hissəsində ümumi anlayışdan istifadə etsək. Amma biz Spring Data istifadə edirik və standart olaraq Hibernate var ki, bu da bunu fərqli şəkildə edə bilər. Biz onunla əlaqəli bütün istifadəçilərin cəlb olunacağı GroupSub obyektini əldə edirik. Bu istifadəçilər toplusundan ehtiyac duyduğumuzu siləcəyik və groupSub-u yenidən verilənlər bazasında saxlayacağıq, lakin bu istifadəçi olmadan. Bu yolla Spring Data nə istədiyimizi anlayacaq və qeydi siləcək. "A-dan Z-yə Java layihəsi": Qrupdan məqalələrə abunəliyin silinməsi - 1"A-dan Z-yə Java layihəsi": Qrupdan məqalələrə abunəliyin silinməsi - 2İstifadəçini tez silmək üçün heç bir problem olmaması üçün GroupSub siyahısı istisna olmaqla, TelegramUser üçün EqualsAndHashCode annotasiyasını əlavə etdim. Bizə lazım olan istifadəçi ilə istifadəçilərin toplanması üzrə aradan qaldırılması metodu adlanır. TelegramUser üçün belə görünür:
@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;
}
Nəticədə hər şey istədiyimiz kimi alındı. Komandada bir neçə mümkün hadisə var, ona görə də onların hər biri üçün yaxşı bir test yazmaq əla fikirdir. Testlərdən söz düşmüşkən: onları yazarkən məntiqdə bir qüsur tapdım və istehsala buraxılmamışdan əvvəl düzəltdim. Əgər test olmasaydı, onun nə qədər tez aşkar ediləcəyi bəlli deyil. DeleteGroupSubCommandTest:
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);
   }
}
Burada hər test ayrı bir ssenarini yoxlayır və sizə xatırlatmağa icazə verin, onlardan yalnız beşi var:
  • siz sadəcə /deleteGroupSub əmrini keçdiyiniz zaman və qrup abunələri olmadıqda;
  • sadəcə /deleteGroupSub əmrini keçdiyiniz zaman və qruplara abunə olduqda;
  • etibarsız qrup ID-si ötürüldükdə, məsələn, /deleteGroupSub abc ;
  • gözlənildiyi kimi hər şeyin düzgün silindiyi bir ssenari;
  • qrup identifikatorunun etibarlı olduğu, lakin belə bir qrup verilənlər bazasında olmadığı bir ssenari.
Gördüyünüz kimi, bütün bu ssenariləri testlərlə əhatə etmək lazımdır. Yazarkən başa düşdüm ki, daha yaxşı testlər yazmaq üçün bəzi test kurslarından keçməyə dəyər. Düşünürəm ki, bu, müxtəlif variantları düzgün axtarmağa kömək edəcək. Düzdü, gələcək üçün düşüncələr. Sonra, indi abunəni silə biləcəyiniz / help əmrinə təsvir əlavə etməlisiniz . Onu abunələrlə işləmək bölməsinə yerləşdirək. Əlbəttə ki, bu əmrin işləməsi üçün onun başlanğıcını CommandContainer"A-dan Z-yə Java layihəsi": Qrupdan məqalələrə abunəliyin silinməsi - 3 -ə əlavə etməlisiniz :
.put(DELETE_GROUP_SUB.getCommandName(),
       new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
İndi siz test botunda funksionallığı yoxlaya bilərsiniz. Biz verilənlər bazamızı docker-compose-test.yml istifadə edərək işə salırıq: docker-compose -f docker-compose-test.yml up Və IDEA vasitəsilə SpringBoot-u işə salın. Mən botla yazışmaları tamamilə təmizləyib yenidən başlayacağam. Bu komanda ilə işləyərkən yarana biləcək bütün variantları nəzərdən keçirəcəyəm. "A-dan Z-yə Java layihəsi": Qrupdan məqalələrə abunəliyin silinməsi - 4Ekran görüntüsündən göründüyü kimi, bütün seçimlər keçdi və uğurlu oldu.
Dostlar! Layihə üçün yeni kodun nə vaxt buraxıldığını dərhal bilmək istəyirsiniz? Yeni məqalə nə vaxt çıxır? Telegram kanalıma qoşulun . Orada məqalələrimi, düşüncələrimi və açıq mənbə inkişafımı bir yerdə toplayıram.
Layihəmizin versiyasını 0.6.0-SNAPSHOT-a yeniləyirik, yeni versiyada görülənlərin təsvirini əlavə etməklə RELEASE_NOTES.md-ni yeniləyirik:
## 0.6.0-SNAPSHOT * JRTB-7: qrup abunəliyini silmək imkanı əlavə edildi.
Kod işləyir, bunun üçün testlər yazılmışdır: tapşırığı depoya yerləşdirmək və çəkmə sorğusu yaratmaq vaxtıdır."A-dan Z-yə Java layihəsi": Qrupdan məqalələrə abunəliyin silinməsi - 5

Bitirmək əvəzinə

Çoxdandır ki, layihə lövhəmizə baxmırıq, lakin böyük dəyişikliklər oldu: "A-dan Z-yə Java layihəsi": Qrupdan məqalələrə abunəliyin silinməsi - 6Cəmi 5 tapşırıq qalıb. Yəni biz artıq yolun ən sonundayıq. Bir az qalıb. Xüsusilə maraqlıdır ki, bu məqalələr silsiləsi sentyabrın ortalarından, yəni 7 aydır fəaliyyət göstərir!!! Bu fikri ağlıma gətirəndə bu qədər uzun çəkəcəyini gözləmirdim. Eyni zamanda, nəticədən çox məmnunam! Dostlar, məqalədə nə baş verdiyi aydın deyilsə, şərhlərdə suallar verin. Bu yolla mən biləcəkəm ki, nəyisə daha yaxşı təsvir etmək lazımdır və nəyisə əlavə izah etmək lazımdır. Yaxşı, həmişəki kimi, bəyənin - abunə olun - zəngi vurun, layihəmizə ulduz verin , şərh yazın və məqaləni qiymətləndirin! Hamıya təşəkkürlər. Növbəti hissəyə qədər. Tezliklə /stop & /start əmrləri vasitəsilə botun deaktivasiyası və aktivləşdirilməsini necə əlavə etmək və onlardan ən yaxşı şəkildə necə istifadə etmək barədə danışacağıq . Sonra görüşənədək!

Serialdakı bütün materialların siyahısı bu məqalənin əvvəlindədir.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION