Witam wszystkich, drodzy przyjaciele, przyszli Starsi Inżynierowie Oprogramowania. Kontynuujemy rozwój bota telegramowego. Na tym etapie naszego projektu przyjrzymy się trzem zadaniom, które mają bardziej widoczną wartość niż wartość oprogramowania. Musimy dowiedzieć się, jak usunąć subskrypcję nowych artykułów z określonej grupy: użyj polecenia /stop , aby dezaktywować bota, a poleceniem /start, aby go aktywować. I tak, aby wszystkie żądania i aktualizacje dotyczyły tylko aktywnych użytkowników bota. Jak zwykle zaktualizujemy główną gałąź, aby pobrać wszystkie zmiany i utworzymy nową: STEP_7_JRTB-7. W tej części porozmawiamy o usunięciu subskrypcji i rozważymy 5 opcji wydarzeń - będzie ciekawie.
Aktualizujemy wersję naszego projektu do 0.6.0-SNAPSHOT Aktualizujemy RELEASE_NOTES.md, dodając opis co zostało zrobione w nowej wersji:
lajkuj - subskrybuj - zadzwoń, daj gwiazdkę naszemu projektowi , napisz komentarz i oceń artykuł! Dziękuje za wszystko. Do następnej części. Wkrótce porozmawiamy o tym, jak dodać dezaktywację i aktywację bota za pomocą poleceń /stop i /start oraz jak najlepiej z nich korzystać. Do zobaczenia później!
JRTB-7: usuwanie subskrypcji nowych artykułów z grupy
Oczywiste jest, że wszyscy użytkownicy będą chcieli mieć możliwość usunięcia swojej subskrypcji, aby nie otrzymywać powiadomień o nowych artykułach. Jego logika będzie bardzo podobna do logiki dodawania abonamentu. Jeśli wyślemy tylko jedno polecenie, w odpowiedzi otrzymamy listę grup i ich identyfikatory, do których użytkownik jest już zapisany, abyśmy mogli zrozumieć, co dokładnie należy usunąć. A jeśli użytkownik wyśle ID grupy wraz z zespołem, usuniemy subskrypcję. Dlatego rozwińmy to polecenie od strony bota telegramu.-
Dodajmy nazwę nowego polecenia - /deleteGroupSub , a w CommandName - linię:
DELETE_GROUP_SUB("/deleteGroupSub")
-
Następnie utwórzmy polecenie 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 = "Пока нет подписок на группы. Coбы добавить подписку напиши /addGroupSub"; } else { message = "Coбы удалить подписку на группу - передай комадну вместе с ID группы. \n" + "Например: /deleteGroupSub 16 \n\n" + "я подготовил список всех групп, на которые ты подписан) \n\n" + "Nazwa группы - 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)); } }
@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;
}
Dzięki temu wszystko poszło tak jak chcieliśmy. Możliwych zdarzeń w zespole jest kilka, dlatego napisanie dobrego testu dla każdego z nich jest świetnym pomysłem. A skoro mowa o testach: podczas ich pisania znalazłem błąd w logice i poprawiłem go zanim trafił do produkcji. Gdyby nie przeprowadzono testu, nie jest jasne, jak szybko zostałby wykryty. Usuń grupęSubCommandTest:
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 = "Пока нет подписок на группы. Coбы добавить подписку напиши /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 = "Coбы удалить подписку на группу - передай комадну вместе с ID группы. \n" +
"Например: /deleteGroupSub 16 \n\n" +
"я подготовил список всех групп, на которые ты подписан) \n\n" +
"Nazwa группы - 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);
}
}
Tutaj każdy test sprawdza osobny scenariusz, a przypomnę, że jest ich tylko pięć:
- kiedy po prostu przekazałeś polecenie /deleteGroupSub i nie ma żadnych subskrypcji grupowych;
- kiedy po prostu przekazałeś polecenie /deleteGroupSub i istnieją subskrypcje grup;
- gdy przekazany został nieprawidłowy identyfikator grupy, na przykład /deleteGroupSub abc ;
- scenariusz, w którym wszystko zostanie poprawnie usunięte, zgodnie z oczekiwaniami;
- scenariusz, gdy identyfikator grupy jest ważny, ale takiej grupy nie ma w bazie.
.put(DELETE_GROUP_SUB.getCommandName(),
new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
Teraz możesz przetestować funkcjonalność na bocie testowym. Uruchamiamy naszą bazę danych za pomocą docker-compose-test.yml: docker-compose -f docker-compose-test.yml up I uruchamiamy SpringBoot poprzez IDEA. Całkowicie wyczyszczę korespondencję z botem i zacznę od nowa. Przeanalizuję wszystkie opcje, które mogą się pojawić podczas pracy z tym zespołem. Jak widać na zrzucie ekranu, wszystkie opcje zostały pomyślnie zrealizowane.
Przyjaciele! Chcesz wiedzieć od razu, kiedy zostanie wydany nowy kod projektu? Kiedy pojawi się nowy artykuł? Dołącz do mojego kanału na Telegramie . Gromadzę tam swoje artykuły, przemyślenia i rozwój open source. |
## 0.6.0-SNAPSHOT * JRTB-7: dodano możliwość usuwania subskrypcji grupowej.
Kod działa, napisano dla niego testy: czas wypchnąć zadanie do repozytorium i utworzyć pull request.
GO TO FULL VERSION