JavaRush /בלוג Java /Random-HE /אנו מסירים את המנוי לכתבות מהקבוצה - "פרויקט ג'אווה מא' ע...
Roman Beekeeper
רָמָה

אנו מסירים את המנוי לכתבות מהקבוצה - "פרויקט ג'אווה מא' עד ת'"

פורסם בקבוצה
שלום לכולם, חברים יקרים שלי, מהנדסי תוכנה בכירים לעתיד. אנו ממשיכים לפתח בוט טלגרם. בשלב זה של הפרויקט שלנו, נשקול שלוש משימות בעלות ערך גלוי יותר מערך התוכנית. עלינו ללמוד כיצד להסיר מנוי למאמרים חדשים מקבוצה מסוימת: השתמש בפקודה /stop כדי לבטל את הבוט, והשתמש בפקודה /start כדי להפעיל אותו. יתרה מכך, כל הבקשות והעדכונים נוגעים רק למשתמשים הפעילים של הבוט. כרגיל, נעדכן את הסניף הראשי כדי לקבל את כל השינויים וליצור אחד חדש: STEP_7_JRTB-7. בחלק זה, נדבר על מחיקת מנוי ונשקול 5 אפשרויות לאירועים - זה יהיה מעניין.

JRTB-7: הסרת מנוי למאמרים חדשים מקבוצה

ברור שכל המשתמשים ירצו להיות מסוגלים למחוק את המנוי שלהם כדי לא לקבל הודעות על מאמרים חדשים. ההיגיון שלו יהיה דומה מאוד להיגיון של הוספת מנוי. אם נשלח רק פקודה אחת, בתגובה נקבל רשימה של קבוצות ותעודות הזהות שלהן שהמשתמש כבר מנוי אליהן, כדי שנוכל להבין מה בדיוק צריך למחוק. ואם המשתמש ישלח את מזהה הקבוצה יחד עם הצוות, אנו נמחק את המנוי. לכן, בוא נלך לפתח את הפקודה הזו מהצד של בוט הטלגרם.
  1. בואו נוסיף את שם הפקודה החדשה - /deleteGroupSub , וב- CommandName - השורה:

    DELETE_GROUP_SUB("/deleteGroupSub")

  2. לאחר מכן, בואו ניצור את הפקודה 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));
       }
    }

לשם כך, היינו צריכים להוסיף עוד שתי שיטות לעבודה עם הישות GroupSub - שליפה מהמסד לפי ID ושמירת הישות עצמה. כל השיטות הללו פשוט קוראות לשיטות מאגר מוכנות. אני אספר לך בנפרד על מחיקת מנוי. בסכימת מסד הנתונים זו הטבלה שאחראית על תהליך הרב-לרבים, וכדי למחוק את הקשר הזה צריך למחוק את הרשומה בה. זאת אם נשתמש בהבנה הכללית של בסיס הנתונים. אבל אנחנו משתמשים ב-Spring Data ויש Hibernate כברירת מחדל, שיכול לעשות זאת אחרת. אנו מקבלים את הישות GroupSub, אליה יימשכו כל המשתמשים המשויכים אליה. מאוסף המשתמשים הזה נסיר את זה שאנחנו צריכים ונשמור את groupSub בחזרה למסד הנתונים, אבל בלי משתמש זה. כך Spring Data יבין מה רצינו וימחק את הרשומה. "פרויקט ג'אווה מא' עד ת'": הסרת המנוי למאמרים מהקבוצה - 1"פרויקט ג'אווה מא' עד ת'": הסרת המנוי לכתבות מהקבוצה - 2כדי להסיר במהירות משתמש, הוספתי את הערת EqualsAndHashCode עבור TelegramUser, לא כולל רשימת GroupSub כדי שלא יהיו בעיות. ונקרא שיטת הסר על אוסף המשתמשים עם המשתמש שאנחנו צריכים. כך זה נראה עבור 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;
}
כתוצאה מכך הכל המריא בדיוק כמו שרצינו. ישנם מספר אירועים אפשריים בצוות, כך שכתיבת מבחן טוב עבור כל אחד מהם הוא רעיון מצוין. אם כבר מדברים על מבחנים: בזמן שכתבתי אותם, מצאתי פגם בלוגיקה ותיקנתי אותו לפני שיצא לייצור. אם לא הייתה בדיקה, לא ברור באיזו מהירות היא הייתה מתגלה. 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);
   }
}
הנה, כל בדיקה בודקת תרחיש נפרד, ותן לי להזכיר לך, יש רק חמישה מהם:
  • כאשר פשוט עברת את הפקודה /deleteGroupSub ואין מנויים לקבוצה;
  • כאשר פשוט עברת את הפקודה /deleteGroupSub ויש מנויים לקבוצות;
  • כאשר מזהה קבוצה לא חוקי הועבר, למשל, /deleteGroupSub abc ;
  • תרחיש שבו הכל נמחק כראוי, כצפוי;
  • תרחיש כאשר מזהה הקבוצה חוקי, אך קבוצה כזו אינה במסד הנתונים.
כפי שאתה יכול לראות, כל התרחישים האלה צריכים להיות מכוסים בבדיקות. בזמן הכתיבה הבנתי שכדי לכתוב מבחנים טובים יותר, כדאי לקחת כמה קורסי מבחן. אני חושב שזה יעזור לחפש כראוי אפשרויות שונות. נכון, מחשבות לעתיד. לאחר מכן, עליך להוסיף תיאור לפקודה /help שכעת תוכל למחוק את המנוי. בואו נמקם אותו בחלק לעבודה עם מנויים. "פרויקט ג'אווה מא' עד ת'": הסרת המנוי לכתבות מהקבוצה - 3כמובן, כדי שהפקודה הזו תעבוד, עליך להוסיף את האתחול שלה ל- CommandContainer :
.put(DELETE_GROUP_SUB.getCommandName(),
       new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
עכשיו אתה יכול לבדוק את הפונקציונליות על בוט בדיקה. אנו משיקים את מסד הנתונים שלנו באמצעות docker-compose-test.yml: docker-compose -f docker-compose-test.yml למעלה ומשיקים את SpringBoot דרך IDEA. אני אנקה לחלוטין את ההתכתבות עם הבוט ואתחיל מחדש. אני אעבור על כל האפשרויות שעלולות לצוץ בעבודה עם הצוות הזה. "פרויקט ג'אווה מא' עד ת'": הסרת המנוי לכתבות מהקבוצה - 4כפי שניתן לראות מצילום המסך, כל האפשרויות עברו והצליחו.
חברים! האם אתה רוצה לדעת מיד מתי יוצא קוד חדש לפרויקט? מתי יוצא כתבה חדשה? הצטרף לערוץ הטלגרם שלי . שם אני אוסף את המאמרים, המחשבות ופיתוח הקוד הפתוח שלי ביחד.
אנו מעדכנים את הגרסה של הפרויקט שלנו ל-0.6.0-SNAPSHOT אנו מעדכנים את RELEASE_NOTES.md, ומוסיפים תיאור של מה שנעשה בגרסה החדשה:
## 0.6.0-SNAPSHOT * JRTB-7: הוספה את היכולת למחוק מנוי קבוצתי.
הקוד עובד, נכתבו עבורו בדיקות: הגיע הזמן לדחוף את המשימה למאגר וליצור בקשת משיכה."פרויקט ג'אווה מא' עד ת'": הסרת המנוי לכתבות מהקבוצה - 5

במקום לסיים

הרבה זמן לא הסתכלנו על לוח הפרויקטים שלנו, אבל חלו שינויים גדולים: "פרויקט ג'אווה מא' עד ת'": הסרת מנוי לכתבות מהקבוצה - 6נותרו רק 5 משימות. כלומר, אתה ואני כבר ממש בסוף הדרך. השאירו קצת. מעניין במיוחד לציין שסדרת הכתבות הזו פועלת מאמצע ספטמבר, כלומר כבר 7 חודשים!!! כשהגעתי לרעיון הזה, לא ציפיתי שזה ייקח כל כך הרבה זמן. יחד עם זאת, אני יותר ממרוצה מהתוצאה! חברים, אם לא ברור מה קורה בכתבה, שאלו שאלות בתגובות. כך אדע שצריך לתאר משהו טוב יותר, ומשהו צריך הסבר נוסף. ובכן, כרגיל, לייק - הירשם - צלצל בפעמון, תן לפרויקט שלנו כוכב , כתבו תגובות ודרג את המאמר! תודה לכל. עד החלק הבא. בקרוב נדבר על איך להוסיף ביטול והפעלה של בוט באמצעות פקודות /stop & /start וכיצד להשתמש בהן בצורה הטובה ביותר. נתראה אחר כך!

רשימה של כל החומרים בסדרה נמצאת בתחילת מאמר זה.

הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION