bugfix: sync message reactions via websocket

This commit is contained in:
tim
2026-02-11 20:33:41 +08:00
parent 8d20d8ef67
commit c9c890d034
5 changed files with 256 additions and 15 deletions

View File

@@ -14,6 +14,7 @@ import com.openisle.model.Reaction;
import com.openisle.model.ReactionType;
import com.openisle.model.User;
import com.openisle.service.LevelService;
import com.openisle.service.PointService;
import com.openisle.service.ReactionService;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@@ -39,6 +40,9 @@ class ReactionControllerTest {
@MockBean
private LevelService levelService;
@MockBean
private PointService pointService;
@Test
void reactToPost() throws Exception {
User user = new User();

View File

@@ -1,22 +1,32 @@
package com.openisle.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.*;
import com.openisle.dto.MessageNotificationPayload;
import com.openisle.dto.ReactionDto;
import com.openisle.mapper.ReactionMapper;
import com.openisle.model.*;
import com.openisle.repository.*;
import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
class ReactionServiceTest {
@Test
void reactToPostSendsEmailEveryFive() {
void reactToPostCreatesNotificationForAuthor() {
ReactionRepository reactionRepo = mock(ReactionRepository.class);
UserRepository userRepo = mock(UserRepository.class);
PostRepository postRepo = mock(PostRepository.class);
CommentRepository commentRepo = mock(CommentRepository.class);
MessageRepository messageRepo = mock(MessageRepository.class);
NotificationService notif = mock(NotificationService.class);
NotificationProducer notificationProducer = mock(NotificationProducer.class);
ReactionMapper reactionMapper = new ReactionMapper();
EmailSender email = mock(EmailSender.class);
ReactionService service = new ReactionService(
reactionRepo,
@@ -25,14 +35,10 @@ class ReactionServiceTest {
commentRepo,
messageRepo,
notif,
notificationProducer,
reactionMapper,
email
);
org.springframework.test.util.ReflectionTestUtils.setField(
service,
"websiteUrl",
"https://ex.com"
);
User user = new User();
user.setId(1L);
user.setUsername("bob");
@@ -49,11 +55,162 @@ class ReactionServiceTest {
Optional.empty()
);
when(reactionRepo.save(any(Reaction.class))).thenAnswer(i -> i.getArgument(0));
when(reactionRepo.countReceived(author.getUsername())).thenReturn(5L);
service.reactToPost("bob", 3L, ReactionType.LIKE);
verify(email).sendEmail("a@a.com", "你有新的互动", "https://ex.com/messages");
verify(notif).sendCustomPush(author, "你有新的互动", "https://ex.com/messages");
verify(notif).createNotification(
eq(author),
eq(NotificationType.REACTION),
eq(post),
isNull(),
isNull(),
eq(user),
eq(ReactionType.LIKE),
isNull()
);
verifyNoInteractions(email);
}
@Test
void reactToMessageBroadcastsAddedEvent() {
ReactionRepository reactionRepo = mock(ReactionRepository.class);
UserRepository userRepo = mock(UserRepository.class);
PostRepository postRepo = mock(PostRepository.class);
CommentRepository commentRepo = mock(CommentRepository.class);
MessageRepository messageRepo = mock(MessageRepository.class);
NotificationService notif = mock(NotificationService.class);
NotificationProducer notificationProducer = mock(NotificationProducer.class);
ReactionMapper reactionMapper = new ReactionMapper();
EmailSender email = mock(EmailSender.class);
ReactionService service = new ReactionService(
reactionRepo,
userRepo,
postRepo,
commentRepo,
messageRepo,
notif,
notificationProducer,
reactionMapper,
email
);
User user = new User();
user.setId(10L);
user.setUsername("alice");
MessageConversation conversation = new MessageConversation();
conversation.setId(20L);
Message message = new Message();
message.setId(30L);
message.setConversation(conversation);
when(userRepo.findByUsername("alice")).thenReturn(Optional.of(user));
when(messageRepo.findById(30L)).thenReturn(Optional.of(message));
when(reactionRepo.findByUserAndMessageAndType(user, message, ReactionType.LIKE)).thenReturn(
Optional.empty()
);
when(reactionRepo.save(any(Reaction.class))).thenAnswer(invocation -> {
Reaction saved = invocation.getArgument(0);
saved.setId(40L);
return saved;
});
Reaction result = service.reactToMessage("alice", 30L, ReactionType.LIKE);
assertEquals(40L, result.getId());
ArgumentCaptor<MessageNotificationPayload> payloadCaptor = ArgumentCaptor.forClass(
MessageNotificationPayload.class
);
verify(notificationProducer).sendNotification(payloadCaptor.capture());
MessageNotificationPayload outbound = payloadCaptor.getValue();
assertEquals("alice", outbound.getTargetUsername());
Object payloadObject = outbound.getPayload();
assertInstanceOf(Map.class, payloadObject);
Map<?, ?> payload = (Map<?, ?>) payloadObject;
assertEquals("MESSAGE_REACTION", payload.get("eventType"));
assertEquals(20L, payload.get("conversationId"));
assertEquals(30L, payload.get("messageId"));
assertEquals("ADDED", payload.get("action"));
Object reactionObject = payload.get("reaction");
assertInstanceOf(ReactionDto.class, reactionObject);
ReactionDto reactionDto = (ReactionDto) reactionObject;
assertEquals(40L, reactionDto.getId());
assertEquals("alice", reactionDto.getUser());
assertEquals(30L, reactionDto.getMessageId());
assertEquals(ReactionType.LIKE, reactionDto.getType());
}
@Test
void reactToMessageBroadcastsRemovedEvent() {
ReactionRepository reactionRepo = mock(ReactionRepository.class);
UserRepository userRepo = mock(UserRepository.class);
PostRepository postRepo = mock(PostRepository.class);
CommentRepository commentRepo = mock(CommentRepository.class);
MessageRepository messageRepo = mock(MessageRepository.class);
NotificationService notif = mock(NotificationService.class);
NotificationProducer notificationProducer = mock(NotificationProducer.class);
ReactionMapper reactionMapper = new ReactionMapper();
EmailSender email = mock(EmailSender.class);
ReactionService service = new ReactionService(
reactionRepo,
userRepo,
postRepo,
commentRepo,
messageRepo,
notif,
notificationProducer,
reactionMapper,
email
);
User user = new User();
user.setId(10L);
user.setUsername("alice");
MessageConversation conversation = new MessageConversation();
conversation.setId(20L);
Message message = new Message();
message.setId(30L);
message.setConversation(conversation);
Reaction existing = new Reaction();
existing.setId(50L);
existing.setUser(user);
existing.setMessage(message);
existing.setType(ReactionType.LIKE);
when(userRepo.findByUsername("alice")).thenReturn(Optional.of(user));
when(messageRepo.findById(30L)).thenReturn(Optional.of(message));
when(reactionRepo.findByUserAndMessageAndType(user, message, ReactionType.LIKE)).thenReturn(
Optional.of(existing)
);
Reaction result = service.reactToMessage("alice", 30L, ReactionType.LIKE);
assertNull(result);
verify(reactionRepo).delete(existing);
ArgumentCaptor<MessageNotificationPayload> payloadCaptor = ArgumentCaptor.forClass(
MessageNotificationPayload.class
);
verify(notificationProducer).sendNotification(payloadCaptor.capture());
MessageNotificationPayload outbound = payloadCaptor.getValue();
assertEquals("alice", outbound.getTargetUsername());
Object payloadObject = outbound.getPayload();
assertInstanceOf(Map.class, payloadObject);
Map<?, ?> payload = (Map<?, ?>) payloadObject;
assertEquals("MESSAGE_REACTION", payload.get("eventType"));
assertEquals(20L, payload.get("conversationId"));
assertEquals(30L, payload.get("messageId"));
assertEquals("REMOVED", payload.get("action"));
Object reactionObject = payload.get("reaction");
assertInstanceOf(ReactionDto.class, reactionObject);
ReactionDto reactionDto = (ReactionDto) reactionObject;
assertEquals(50L, reactionDto.getId());
assertEquals("alice", reactionDto.getUser());
assertEquals(30L, reactionDto.getMessageId());
assertEquals(ReactionType.LIKE, reactionDto.getType());
}
}