JavaRush /Java blogi /Random-UZ /Biz guruhdagi maqolalarga obunani olib tashlaymiz - "A da...

Biz guruhdagi maqolalarga obunani olib tashlaymiz - "A dan Zgacha Java loyihasi"

Guruhda nashr etilgan
Hammaga salom, aziz do'stlarim, bo'lajak Katta dasturiy ta'minot muhandislari. Biz telegram botini ishlab chiqishda davom etamiz. Loyihamizning ushbu bosqichida biz dastur qiymatidan ko'ra ko'rinadigan qiymatga ega bo'lgan uchta vazifani ko'rib chiqamiz. Muayyan guruhdan yangi maqolalarga obunani qanday olib tashlashni o'rganishimiz kerak: botni o'chirish uchun /stop buyrug'idan foydalaning va uni faollashtirish uchun /start buyrug'idan foydalaning. Bundan tashqari, barcha so'rovlar va yangilanishlar faqat botning faol foydalanuvchilariga tegishli. Odatdagidek, barcha o'zgarishlarni olish va yangisini yaratish uchun asosiy filialni yangilaymiz : STEP_7_JRTB-7. Ushbu qismda biz obunani o'chirish haqida gaplashamiz va voqealar uchun 5 ta variantni ko'rib chiqamiz - bu qiziqarli bo'ladi.

JRTB-7: guruhdan yangi maqolalarga obunani olib tashlash

Barcha foydalanuvchilar yangi maqolalar haqida bildirishnoma olmaslik uchun obunani o'chirishni xohlashlari aniq. Uning mantig'i obuna qo'shish mantig'iga juda o'xshash bo'ladi. Agar biz faqat bitta buyruq yuboradigan bo'lsak, javoban biz foydalanuvchi allaqachon obuna bo'lgan guruhlar ro'yxatini va ularning identifikatorlarini olamiz, shunda biz aniq nimani o'chirish kerakligini tushunishimiz mumkin. Va agar foydalanuvchi guruh identifikatorini jamoa bilan birga yuborsa, biz obunani o'chirib tashlaymiz. Shuning uchun keling, ushbu buyruqni telegram bot tomonidan ishlab chiqaylik.
  1. Keling, yangi buyruq nomini qo'shamiz - /deleteGroupSub , va CommandName -da - qator:

    DELETE_GROUP_SUB("/deleteGroupSub")

  2. Keyin DeleteGroupSubCommand buyrug'ini yaratamiz :

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

Buning uchun biz GroupSub ob'ekti bilan ishlashning yana ikkita usulini qo'shishimiz kerak edi - ma'lumotlar bazasidan ID orqali olish va ob'ektning o'zini saqlash. Bu usullarning barchasi shunchaki tayyor ombor usullarini chaqiradi. Men sizga obunani o'chirish haqida alohida aytib beraman. Ma'lumotlar bazasi sxemasida bu ko'p-ko'p jarayoni uchun javobgar bo'lgan jadval va bu munosabatni o'chirish uchun undagi yozuvni o'chirishingiz kerak. Bu, agar biz ma'lumotlar bazasining umumiy tushunchasidan foydalansak. Lekin biz Spring Data-dan foydalanamiz va sukut bo'yicha Hibernate mavjud, bu buni boshqacha qilishi mumkin. Biz GroupSub ob'ektini olamiz, u bilan bog'langan barcha foydalanuvchilar unga jalb qilinadi. Ushbu foydalanuvchilar to'plamidan biz keraklisini olib tashlaymiz va groupSub-ni ma'lumotlar bazasiga saqlaymiz, lekin bu foydalanuvchisiz. Shunday qilib, Spring Data biz xohlagan narsani tushunadi va yozuvni o'chiradi. "A dan Zgacha Java loyihasi": Guruhdan maqolalarga obunani olib tashlash - 1"A dan Zgacha Java loyihasi": Guruhdan maqolalarga obunani olib tashlash - 2Foydalanuvchini tezda oʻchirish uchun men TelegramUser uchun EqualsAndHashCode izohini qoʻshdim, hech qanday muammo boʻlmasligi uchun GroupSub roʻyxatidan tashqari. Va bizga kerak bo'lgan foydalanuvchi bilan foydalanuvchilarni yig'ish bo'yicha olib tashlash usulini chaqirdi. Bu TelegramUser uchun shunday ko'rinadi:
@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;
}
Natijada hamma narsa biz xohlagandek o'tdi. Jamoada bir nechta mumkin bo'lgan hodisalar mavjud, shuning uchun ularning har biri uchun yaxshi test yozish ajoyib g'oya. Sinovlar haqida gapiradigan bo'lsam: men ularni yozayotganimda, men mantiqda nuqson topdim va uni ishlab chiqarishga chiqarilishidan oldin tuzatdim. Agar sinov bo'lmaganida, u qanchalik tez aniqlangan bo'lishi noma'lum. 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);
   }
}
Bu erda har bir test alohida stsenariyni tekshiradi va sizga eslatib o'taman, ulardan faqat beshtasi bor:
  • oddiygina /deleteGroupSub buyrug'ini topshirganingizda va guruh obunalari yo'q;
  • oddiygina /deleteGroupSub buyrug'ini topshirganingizda va guruhlarga obuna bo'lganingizda;
  • noto'g'ri guruh identifikatori o'tkazilganda, masalan, /deleteGroupSub abc ;
  • kutilganidek, hamma narsa to'g'ri o'chirilgan stsenariy;
  • guruh identifikatori haqiqiy bo'lgan stsenariy, lekin bunday guruh ma'lumotlar bazasida yo'q.
Ko'rib turganingizdek, ushbu stsenariylarning barchasi sinovlar bilan qoplanishi kerak. Yozish paytida men yaxshiroq testlar yozish uchun test kurslarini o'tash kerakligini angladim. O'ylaymanki, bu turli xil variantlarni to'g'ri izlashga yordam beradi. To'g'ri, kelajak haqidagi fikrlar. Keyin, /help buyrug'iga obunani o'chirishingiz mumkin bo'lgan tavsifni qo'shishingiz kerak . Keling, uni obunalar bilan ishlash bo'limiga joylashtiramiz. "A dan Zgacha Java loyihasi": Guruhdan maqolalarga obunani olib tashlash - 3Albatta, bu buyruq ishlashi uchun siz uni ishga tushirishni CommandContainer ga qo'shishingiz kerak :
.put(DELETE_GROUP_SUB.getCommandName(),
       new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
Endi siz test botda funksionallikni sinab ko'rishingiz mumkin. Biz ma'lumotlar bazasini docker-compose-test.yml yordamida ishga tushiramiz: docker-compose -f docker-compose-test.yml up Va SpringBoot-ni IDEA orqali ishga tushiramiz. Men bot bilan yozishmalarni butunlay tozalayman va qayta boshlayman. Men ushbu jamoa bilan ishlashda yuzaga kelishi mumkin bo'lgan barcha variantlarni ko'rib chiqaman. "A dan Zgacha Java loyihasi": Guruhdan maqolalarga obunani olib tashlash - 4Skrinshotdan ko'rinib turibdiki, barcha variantlar o'tdi va muvaffaqiyatli bo'ldi.
Do'stlar! Loyiha uchun yangi kod qachon chiqarilganini darhol bilishni xohlaysizmi? Yangi maqola qachon chiqadi? Telegram kanalimga qo'shiling . U erda men maqolalarimni, fikrlarimni va ochiq manbalarni ishlab chiqishni birga to'playman.
Biz loyihamizning versiyasini 0.6.0-SNAPSHOT ga yangilaymiz Biz RELEASE_NOTES.md ni yangilab, yangi versiyada bajarilgan ishlar tavsifini qo'shamiz:
## 0.6.0-SNAPSHOT * JRTB-7: guruh obunasini o'chirish imkoniyati qo'shildi.
Kod ishlaydi, buning uchun testlar yozilgan: vazifani omborga surish va tortish so'rovini yaratish vaqti keldi."A dan Zgacha Java loyihasi": Guruhdan maqolalarga obunani olib tashlash - 5

Tugatish o'rniga

Biz loyiha kengashimizga uzoq vaqt qaramadik, lekin katta o'zgarishlar yuz berdi: "A dan Zgacha Java loyihasi": Guruhdan maqolalarga obunani olib tashlash - 6faqat 5 ta vazifa qoldi. Ya'ni, siz va men allaqachon yo'lning eng oxiridamiz. Bir oz qoldi. Shunisi qiziqki, ushbu maqolalar turkumi sentyabr oyining oʻrtalaridan boshlab, yaʼni 7 oydan beri davom etmoqda!!! Men bu fikrga kelganimda, bu juda uzoq davom etishini kutmagan edim. Shu bilan birga, men natijadan mamnunman! Do'stlar, agar maqolada nima sodir bo'layotgani aniq bo'lmasa, sharhlarda savollar bering. Shunday qilib, men nimanidir yaxshiroq tasvirlash kerakligini bilib olaman va biror narsa qo'shimcha tushuntirishga muhtoj. Xo'sh, odatdagidek, like - obuna bo'ling - qo'ng'iroqni bosing, loyihamizga yulduz bering , sharhlar yozing va maqolani baholang! Barchangizga rahmat. Keyingi qismgacha. Biz /stop & /start buyruqlari orqali botni o'chirish va faollashtirishni qanday qo'shish va ulardan qanday yaxshi foydalanish haqida tez orada gaplashamiz . Ko'rishguncha!

Seriyadagi barcha materiallar ro'yxati ushbu maqolaning boshida.

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