사랑하는 친구 여러분, 미래의 수석 소프트웨어 엔지니어 여러분, 안녕하세요. 우리는 계속해서 텔레그램 봇을 개발하고 있습니다. 이번 프로젝트 단계에서는 소프트웨어 가치보다 가시적인 가치가 더 높은 세 가지 작업을 살펴보겠습니다. 특정 그룹에서 새 기사에 대한 구독을 제거하는 방법을 배워야 합니다. /stop 명령을 사용하여 봇을 비활성화하고 /start 명령을 사용하여 활성화합니다. 모든 요청과 업데이트는 봇의 활성 사용자에게만 적용됩니다. 평소와 같이 메인 브랜치를 업데이트하여 모든 변경 사항을 적용하고 새 브랜치인 STEP_7_JRTB-7을 생성합니다. 이 부분에서는 구독 삭제에 대해 이야기하고 이벤트에 대한 5가지 옵션을 고려하겠습니다. 흥미로울 것입니다.
프로젝트 버전을 0.6.0-SNAPSHOT으로 업데이트합니다. RELEASE_NOTES.md를 업데이트하고 새 버전에서 수행된 작업에 대한 설명을 추가합니다.
- 구독 - 벨을 울리고, 프로젝트에 별점을 주고 , 댓글을 쓰고, 기사를 평가해 주세요! 모두에게 감사드립니다. 다음 부분까지. /stop 및 /start 명령을 통해 봇 비활성화 및 활성화를 추가하는 방법 과 이를 가장 잘 사용하는 방법에 대해 곧 설명하겠습니다 . 나중에 봐요!
JRTB-7: 그룹에서 새 기사 구독 제거
모든 사용자는 새 기사에 대한 알림을 받지 않기 위해 구독을 삭제할 수 있기를 원할 것입니다. 해당 논리는 구독을 추가하는 논리와 매우 유사합니다. 하나의 명령만 보내면 이에 대한 응답으로 사용자가 이미 구독하고 있는 그룹 및 해당 ID 목록을 수신하므로 삭제해야 할 것이 정확히 무엇인지 이해할 수 있습니다. 그리고 사용자가 팀과 함께 그룹 ID를 보내면 구독이 삭제됩니다. 따라서 텔레그램 봇 측에서 이 명령을 개발해 보겠습니다.-
새 명령의 이름( /deleteGroupSub ) 을 추가하고 CommandName 에 다음 줄을 추가해 보겠습니다.
DELETE_GROUP_SUB("/deleteGroupSub")
-
다음으로, 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)); } }
@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;
}
결과적으로 모든 것이 우리가 원하는 대로 이루어졌습니다. 팀에는 여러 가지 가능한 이벤트가 있으므로 각 이벤트에 대해 좋은 테스트를 작성하는 것이 좋습니다. 테스트에 대해 말하자면, 테스트를 작성하는 동안 로직의 결함을 발견하고 프로덕션에 출시되기 전에 수정했습니다. 테스트가 없었다면 얼마나 빨리 발견됐을지는 불분명합니다. 삭제그룹하위명령테스트:
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);
}
}
여기서 각 테스트는 별도의 시나리오를 확인하며 그 중 5개만 있음을 상기시켜 드립니다.
- 단순히 /deleteGroupSub 명령을 전달했고 그룹 구독이 없는 경우
- 단순히 /deleteGroupSub 명령을 전달 하고 그룹에 대한 구독이 있는 경우
- 잘못된 그룹 ID가 전달된 경우(예: /deleteGroupSub abc )
- 예상대로 모든 것이 올바르게 삭제되는 시나리오;
- 그룹 ID는 유효하지만 해당 그룹이 데이터베이스에 없는 경우의 시나리오입니다.
.put(DELETE_GROUP_SUB.getCommandName(),
new DeleteGroupSubCommand(sendBotMessageService, groupSubService, telegramUserService))
이제 테스트 봇에서 기능을 테스트할 수 있습니다. docker-compose-test.yml을 사용하여 데이터베이스를 시작합니다. docker-compose -f docker-compose-test.yml up 그리고 IDEA를 통해 SpringBoot를 시작합니다. 봇과의 서신을 완전히 삭제하고 다시 시작하겠습니다. 이 팀과 협력할 때 발생할 수 있는 모든 옵션을 살펴보겠습니다. 스크린샷에서 볼 수 있듯이 모든 옵션이 통과되어 성공했습니다.
친구! 프로젝트의 새 코드가 출시되면 즉시 알고 싶으십니까? 새 기사는 언제 나오나요? 내 텔레그램 채널에 가입하세요 . 그곳에서 나는 내 기사, 생각, 오픈 소스 개발을 함께 수집합니다. |
## 0.6.0-SNAPSHOT * JRTB-7: 그룹 구독 삭제 기능이 추가되었습니다.
코드가 작동하고 이에 대한 테스트가 작성되었습니다. 이제 작업을 저장소에 푸시하고 풀 요청을 생성할 시간입니다.
GO TO FULL VERSION