| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- package com.etotem.num.service;
- import com.etotem.num.common.BizException;
- import com.etotem.num.entity.ChatMessage;
- import com.etotem.num.entity.Relation;
- import com.etotem.num.entity.RelationAnalysisRecord;
- import com.etotem.num.entity.User;
- import com.etotem.num.repository.ChatMessageRepository;
- import com.etotem.num.repository.RelationAnalysisRecordRepository;
- import com.etotem.num.repository.RelationRepository;
- import com.google.gson.Gson;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
- import org.mockito.ArgumentCaptor;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.boot.test.mock.mockito.MockBean;
- import java.time.LocalDate;
- import java.time.LocalDateTime;
- import java.util.*;
- import static org.junit.jupiter.api.Assertions.*;
- import static org.mockito.Mockito.*;
- @SpringBootTest
- class RelationAnalysisServiceTest {
- @Autowired
- private RelationAnalysisService relationAnalysisService;
- @MockBean
- private RelationAnalysisRecordRepository recordRepository;
- @MockBean
- private RelationRepository relationRepository;
- @MockBean
- private ChatMessageRepository chatMessageRepository;
- @MockBean
- private UserService userService;
- @MockBean
- private DifyService difyService;
- @MockBean
- private CalculatorService calculatorService;
- // Gson is final and cannot be mocked by Mockito; use the real bean
- @Autowired
- private Gson gson;
- private final Long userId = 1L;
- private final Long recordId = 100L;
- private User mockUser;
- private Relation mockRelation;
- private RelationAnalysisRecord mockRecord;
- @BeforeEach
- void setUp() {
- mockUser = new User();
- mockUser.setId(userId);
- mockUser.setNickname("测试用户");
- mockUser.setBirthYear(1990);
- mockUser.setBirthMonth(6);
- mockUser.setBirthDay(15);
- mockUser.setGender(1);
- mockRelation = new Relation();
- mockRelation.setId(10L);
- mockRelation.setUserId(userId);
- mockRelation.setName("测试关系人");
- mockRelation.setRelationType("spouse");
- mockRelation.setRelationTypeLabel("配偶");
- mockRelation.setBirthDate(LocalDate.of(1992, 3, 20));
- mockRelation.setGender(0);
- mockRecord = new RelationAnalysisRecord();
- mockRecord.setId(recordId);
- mockRecord.setUserId(userId);
- mockRecord.setTitle("测试用户 & 测试关系人(配偶)");
- mockRecord.setMemberSnapshot("[{\"name\":\"测试用户\"}]");
- mockRecord.setStatus("active");
- mockRecord.setChatSessionId("chat_ses_abc");
- mockRecord.setCreateTime(LocalDateTime.now());
- mockRecord.setUpdateTime(LocalDateTime.now());
- java.util.Map<String, Object> positions = new java.util.HashMap<>();
- positions.put("O", 6);
- java.util.Map<String, Object> triangleResult = new java.util.HashMap<>();
- triangleResult.put("mainCharacter", 6);
- triangleResult.put("positions", positions);
- when(calculatorService.calculateFullTriangle(anyInt(), anyInt(), anyInt()))
- .thenReturn(triangleResult);
- }
- // ─── listRecords ────────────────────────────────────────────────
- @Test
- void testListRecords() {
- when(recordRepository.findByUserIdOrderByCreateTimeDesc(userId))
- .thenReturn(Collections.singletonList(mockRecord));
- List<RelationAnalysisRecord> records = relationAnalysisService.listRecords(userId);
- assertEquals(1, records.size());
- assertEquals(recordId, records.get(0).getId());
- verify(recordRepository).findByUserIdOrderByCreateTimeDesc(userId);
- }
- @Test
- void testListRecords_empty() {
- when(recordRepository.findByUserIdOrderByCreateTimeDesc(userId))
- .thenReturn(Collections.emptyList());
- List<RelationAnalysisRecord> records = relationAnalysisService.listRecords(userId);
- assertTrue(records.isEmpty());
- }
- // ─── getRecordDetail ────────────────────────────────────────────
- @Test
- void testGetRecordDetail_success() {
- when(recordRepository.findByIdAndUserId(recordId, userId))
- .thenReturn(Optional.of(mockRecord));
- RelationAnalysisRecord result = relationAnalysisService.getRecordDetail(userId, recordId);
- assertNotNull(result);
- assertEquals(recordId, result.getId());
- }
- @Test
- void testGetRecordDetail_notFound() {
- when(recordRepository.findByIdAndUserId(recordId, userId))
- .thenReturn(Optional.empty());
- assertThrows(BizException.class,
- () -> relationAnalysisService.getRecordDetail(userId, recordId));
- }
- // ─── deleteRecord ───────────────────────────────────────────────
- @Test
- void testDeleteRecord_success() {
- when(recordRepository.findByIdAndUserId(recordId, userId))
- .thenReturn(Optional.of(mockRecord));
- relationAnalysisService.deleteRecord(userId, recordId);
- verify(recordRepository).delete(mockRecord);
- }
- @Test
- void testDeleteRecord_notFound() {
- when(recordRepository.findByIdAndUserId(recordId, userId))
- .thenReturn(Optional.empty());
- assertThrows(BizException.class,
- () -> relationAnalysisService.deleteRecord(userId, recordId));
- verify(recordRepository, never()).delete(any());
- }
- // ─── getChatHistory ─────────────────────────────────────────────
- @Test
- void testGetChatHistory_success() {
- List<ChatMessage> messages = Arrays.asList(
- createChatMessage(1L, "user", "你好"),
- createChatMessage(2L, "ai", "你好!有什么可以帮助你的?")
- );
- when(recordRepository.findByIdAndUserId(recordId, userId))
- .thenReturn(Optional.of(mockRecord));
- when(chatMessageRepository.findByRelationRecordIdOrderByCreatedAtAsc(recordId))
- .thenReturn(messages);
- List<ChatMessage> result = relationAnalysisService.getChatHistory(userId, recordId);
- assertEquals(2, result.size());
- assertEquals("你好", result.get(0).getContent());
- }
- @Test
- void testGetChatHistory_recordNotFound() {
- when(recordRepository.findByIdAndUserId(recordId, userId))
- .thenReturn(Optional.empty());
- assertThrows(BizException.class,
- () -> relationAnalysisService.getChatHistory(userId, recordId));
- }
- // ─── sendChatMessage ────────────────────────────────────────────
- @Test
- void testSendChatMessage_success() {
- String query = "我们的关系如何?";
- String aiResponse = "你们的关系非常和谐。";
- when(recordRepository.findByIdAndUserId(recordId, userId))
- .thenReturn(Optional.of(mockRecord));
- when(difyService.invokeRelationChatflow(mockRecord.getMemberSnapshot(), query, userId.toString()))
- .thenReturn(aiResponse);
- String result = relationAnalysisService.sendChatMessage(userId, recordId, query);
- assertEquals(aiResponse, result);
- // Verify user message saved
- ArgumentCaptor<ChatMessage> userMsgCaptor = ArgumentCaptor.forClass(ChatMessage.class);
- verify(chatMessageRepository, times(2)).save(userMsgCaptor.capture());
- List<ChatMessage> savedMessages = userMsgCaptor.getAllValues();
- assertEquals("user", savedMessages.get(0).getRole());
- assertEquals(query, savedMessages.get(0).getContent());
- assertEquals("ai", savedMessages.get(1).getRole());
- assertEquals(aiResponse, savedMessages.get(1).getContent());
- // Verify record timestamp updated
- verify(recordRepository).save(mockRecord);
- }
- @Test
- void testSendChatMessage_difyFailure() {
- when(recordRepository.findByIdAndUserId(recordId, userId))
- .thenReturn(Optional.of(mockRecord));
- when(difyService.invokeRelationChatflow(anyString(), anyString(), anyString()))
- .thenThrow(new RuntimeException("Dify unavailable"));
- String result = relationAnalysisService.sendChatMessage(userId, recordId, "提问");
- assertEquals("AI 解读服务暂时不可用,请稍后再试。", result);
- }
- @Test
- void testSendChatMessage_recordNotFound() {
- when(recordRepository.findByIdAndUserId(recordId, userId))
- .thenReturn(Optional.empty());
- assertThrows(BizException.class,
- () -> relationAnalysisService.sendChatMessage(userId, recordId, "提问"));
- }
- // ─── startAnalysis ──────────────────────────────────────────────
- @Test
- void testStartAnalysis_success() {
- String question = "我们之间的能量关系如何?";
- List<Long> relationIds = Collections.singletonList(10L);
- when(userService.getById(userId)).thenReturn(mockUser);
- when(relationRepository.findAllById(relationIds)).thenReturn(Collections.singletonList(mockRelation));
- when(recordRepository.save(any(RelationAnalysisRecord.class))).thenAnswer(invocation -> {
- RelationAnalysisRecord saved = invocation.getArgument(0);
- saved.setId(recordId);
- return saved;
- });
- when(difyService.invokeRelationChatflow(anyString(), eq(question), eq(userId.toString())))
- .thenReturn("分析结果文本");
- RelationAnalysisRecord result = relationAnalysisService.startAnalysis(userId, relationIds, question);
- assertNotNull(result);
- assertEquals(userId, result.getUserId());
- assertEquals("active", result.getStatus());
- assertTrue(result.getTitle().contains("测试用户"));
- assertTrue(result.getTitle().contains("测试关系人"));
- assertTrue(result.getMemberSnapshot().contains("测试用户"));
- // Verify chat messages saved
- verify(chatMessageRepository, times(2)).save(any(ChatMessage.class));
- }
- @Test
- void testStartAnalysis_noRelations() {
- String question = "我的个人能量如何?";
- List<Long> relationIds = Collections.emptyList();
- when(userService.getById(userId)).thenReturn(mockUser);
- when(relationRepository.findAllById(relationIds)).thenReturn(Collections.emptyList());
- when(recordRepository.save(any(RelationAnalysisRecord.class))).thenAnswer(invocation -> {
- RelationAnalysisRecord saved = invocation.getArgument(0);
- saved.setId(recordId);
- return saved;
- });
- when(difyService.invokeRelationChatflow(anyString(), eq(question), eq(userId.toString())))
- .thenReturn("个人能量分析结果");
- RelationAnalysisRecord result = relationAnalysisService.startAnalysis(userId, relationIds, question);
- assertNotNull(result);
- assertEquals("个人能量分析", result.getTitle());
- }
- @Test
- void testStartAnalysis_difyFailure() {
- List<Long> relationIds = Collections.singletonList(10L);
- when(userService.getById(userId)).thenReturn(mockUser);
- when(relationRepository.findAllById(relationIds)).thenReturn(Collections.singletonList(mockRelation));
- when(recordRepository.save(any(RelationAnalysisRecord.class))).thenAnswer(invocation -> {
- RelationAnalysisRecord saved = invocation.getArgument(0);
- saved.setId(recordId);
- return saved;
- });
- when(difyService.invokeRelationChatflow(anyString(), anyString(), anyString()))
- .thenThrow(new RuntimeException("Dify error"));
- // Should still return the record (not throw)
- RelationAnalysisRecord result = relationAnalysisService.startAnalysis(userId, relationIds, "提问");
- assertNotNull(result);
- assertEquals(recordId, result.getId());
- // No chat messages should be saved when Dify fails
- verify(chatMessageRepository, never()).save(any(ChatMessage.class));
- }
- @Test
- void testStartAnalysis_userNotFound() {
- when(userService.getById(userId)).thenReturn(null);
- assertThrows(BizException.class,
- () -> relationAnalysisService.startAnalysis(userId, Collections.singletonList(10L), "提问"));
- }
- @Test
- void testStartAnalysis_skipRelationNotOwnedByUser() {
- Relation otherUserRelation = new Relation();
- otherUserRelation.setId(99L);
- otherUserRelation.setUserId(999L); // different user
- otherUserRelation.setName("别人的关系人");
- when(userService.getById(userId)).thenReturn(mockUser);
- when(relationRepository.findAllById(Collections.singletonList(99L))).thenReturn(Collections.singletonList(otherUserRelation));
- when(recordRepository.save(any(RelationAnalysisRecord.class))).thenAnswer(invocation -> {
- RelationAnalysisRecord saved = invocation.getArgument(0);
- saved.setId(recordId);
- return saved;
- });
- when(difyService.invokeRelationChatflow(anyString(), anyString(), anyString()))
- .thenReturn("ok");
- RelationAnalysisRecord result = relationAnalysisService.startAnalysis(userId, Collections.singletonList(99L), "提问");
- assertNotNull(result);
- // Title uses the raw relations list (before ownership filtering), so includes "别人的关系人"
- assertTrue(result.getTitle().contains("别人的关系人"));
- }
- // ─── Helpers ────────────────────────────────────────────────────
- private ChatMessage createChatMessage(Long id, String role, String content) {
- ChatMessage msg = new ChatMessage();
- msg.setId(id);
- msg.setRelationRecordId(recordId);
- msg.setUserId(userId);
- msg.setRole(role);
- msg.setContent(content);
- msg.setCreatedAt(LocalDateTime.now());
- return msg;
- }
- }
|