177 lines
9.5 KiB
Java
177 lines
9.5 KiB
Java
package com.example.service.unit_testing;
|
|
|
|
import com.example.dto.dialogflow.base.DetectIntentResponseDTO;
|
|
import com.example.dto.dialogflow.conversation.*;
|
|
import com.example.dto.quickreplies.QuestionDTO;
|
|
import com.example.dto.quickreplies.QuickReplyDTO;
|
|
import com.example.mapper.conversation.ConversationEntryMapper;
|
|
import com.example.service.conversation.ConversationManagerService;
|
|
import com.example.service.conversation.FirestoreConversationService;
|
|
import com.example.service.conversation.MemoryStoreConversationService;
|
|
import com.example.service.quickreplies.QuickRepliesManagerService;
|
|
import com.example.service.quickreplies.QuickReplyContentService;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.DisplayName;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.extension.ExtendWith;
|
|
import org.mockito.Mock;
|
|
import org.mockito.junit.jupiter.MockitoExtension;
|
|
import reactor.core.publisher.Flux;
|
|
import reactor.core.publisher.Mono;
|
|
import reactor.test.StepVerifier;
|
|
|
|
import java.time.Instant;
|
|
import java.util.List;
|
|
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
import static org.mockito.ArgumentMatchers.anyString;
|
|
import static org.mockito.ArgumentMatchers.eq;
|
|
import static org.mockito.Mockito.*;
|
|
|
|
@ExtendWith(MockitoExtension.class)
|
|
class QuickRepliesManagerServiceTest {
|
|
|
|
@Mock private MemoryStoreConversationService memoryStoreConversationService;
|
|
@Mock private FirestoreConversationService firestoreConversationService;
|
|
@Mock private QuickReplyContentService quickReplyContentService;
|
|
@Mock private ConversationManagerService conversationManagerService;
|
|
@Mock private ConversationEntryMapper conversationEntryMapper;
|
|
|
|
private QuickRepliesManagerService quickRepliesManagerService;
|
|
|
|
// Test Data
|
|
private final String PHONE = "5555555555";
|
|
private final String SESSION_ID = "session-123";
|
|
private final String USER_ID = "user_by_phone_5555555555";
|
|
private final String CONTEXTO = "pagos";
|
|
|
|
@BeforeEach
|
|
void setUp() {
|
|
quickRepliesManagerService = new QuickRepliesManagerService(
|
|
conversationManagerService,
|
|
memoryStoreConversationService,
|
|
firestoreConversationService,
|
|
quickReplyContentService,
|
|
conversationEntryMapper
|
|
);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("manageConversation - Count 0 - NO MATCH: Should clear context and delegate to Dialogflow")
|
|
void manageConversation_Count0_NoMatch_ShouldDelegate() {
|
|
// 1. SETUP: User typed "Hola", but context "pagos" is active.
|
|
ExternalConvRequestDTO request = new ExternalConvRequestDTO("Hola", new UsuarioDTO(PHONE, "Nick"), "whatsapp", ConversationEntryType.CONVERSACION, null);
|
|
ConversationSessionDTO session = new ConversationSessionDTO(SESSION_ID, USER_ID, PHONE, Instant.now(), Instant.now(), "last", CONTEXTO);
|
|
|
|
// Mock Session Retrieval
|
|
when(memoryStoreConversationService.getSessionByTelefono(PHONE)).thenReturn(Mono.just(session));
|
|
|
|
// Mock History: Only the SYSTEM message (The Menu) exists. Count = 0.
|
|
ConversationMessageDTO sysMsg = new ConversationMessageDTO(MessageType.SYSTEM, Instant.now(), "Menu...", null, "whatsapp");
|
|
when(memoryStoreConversationService.getMessages(SESSION_ID)).thenReturn(Flux.just(sysMsg));
|
|
|
|
// Mock QR Content: The menu has options, but "Hola" is NOT one of them.
|
|
QuestionDTO q1 = new QuestionDTO("Ver Saldo", "desc", "Tu saldo is 10");
|
|
QuickReplyDTO qrDto = new QuickReplyDTO("Header", "Body", "Btn", "Section", List.of(q1));
|
|
when(quickReplyContentService.getQuickReplies(CONTEXTO)).thenReturn(Mono.just(qrDto));
|
|
|
|
// Mock Orchestrator Delegation (The expected outcome)
|
|
DetectIntentResponseDTO delegatedResponse = new DetectIntentResponseDTO("df-response", new QueryResultDTO("Hola soy Beto", null));
|
|
when(memoryStoreConversationService.updateSession(any())).thenReturn(Mono.empty()); // Clearing context
|
|
when(conversationManagerService.manageConversation(request)).thenReturn(Mono.just(delegatedResponse));
|
|
|
|
// 2. EXECUTE
|
|
StepVerifier.create(quickRepliesManagerService.manageConversation(request))
|
|
.expectNext(delegatedResponse)
|
|
.verifyComplete();
|
|
|
|
// 3. VERIFY
|
|
// Ensure we cleared the context
|
|
verify(memoryStoreConversationService).updateSession(argThat(s -> s.pantallaContexto() == null));
|
|
// Ensure we called the normal conversation manager
|
|
verify(conversationManagerService).manageConversation(request);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("manageConversation - Count 0 - MATCH: Should return QR Answer")
|
|
void manageConversation_Count0_Match_ShouldAnswer() {
|
|
// 1. SETUP: User typed "Ver Saldo" (Valid Option)
|
|
ExternalConvRequestDTO request = new ExternalConvRequestDTO("Ver Saldo", new UsuarioDTO(PHONE, "Nick"), "whatsapp", ConversationEntryType.CONVERSACION, null);
|
|
ConversationSessionDTO session = new ConversationSessionDTO(SESSION_ID, USER_ID, PHONE, Instant.now(), Instant.now(), "last", CONTEXTO);
|
|
|
|
when(memoryStoreConversationService.getSessionByTelefono(PHONE)).thenReturn(Mono.just(session));
|
|
|
|
// Count 0 (Last message was System)
|
|
ConversationMessageDTO sysMsg = new ConversationMessageDTO(MessageType.SYSTEM, Instant.now(), "Menu...", null, "whatsapp");
|
|
when(memoryStoreConversationService.getMessages(SESSION_ID)).thenReturn(Flux.just(sysMsg));
|
|
|
|
// Valid Option exists
|
|
QuestionDTO q1 = new QuestionDTO("Ver Saldo", "desc", "Tu saldo es 100 pesos");
|
|
QuickReplyDTO qrDto = new QuickReplyDTO("Header", "Body", "Btn", "Section", List.of(q1));
|
|
when(quickReplyContentService.getQuickReplies(CONTEXTO)).thenReturn(Mono.just(qrDto));
|
|
|
|
// Mocks for saving the conversation turn
|
|
mockPersistence();
|
|
when(memoryStoreConversationService.updateSession(any())).thenReturn(Mono.empty());
|
|
|
|
// 2. EXECUTE
|
|
StepVerifier.create(quickRepliesManagerService.manageConversation(request))
|
|
.assertNext(response -> {
|
|
// Expect the pre-defined answer from the JSON
|
|
assert response.queryResult().responseText().equals("Tu saldo es 100 pesos");
|
|
})
|
|
.verifyComplete();
|
|
|
|
// Verify we did NOT delegate
|
|
verify(conversationManagerService, never()).manageConversation(any());
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("manageConversation - Count 1 - NO MATCH: Should save User Msg, Clear, and Delegate")
|
|
void manageConversation_Count1_NoMatch_ShouldDelegate() {
|
|
// 1. SETUP: User typed "Gracias" (Not an option)
|
|
ExternalConvRequestDTO request = new ExternalConvRequestDTO("Gracias", new UsuarioDTO(PHONE, "Nick"), "whatsapp", ConversationEntryType.CONVERSACION, null);
|
|
ConversationSessionDTO session = new ConversationSessionDTO(SESSION_ID, USER_ID, PHONE, Instant.now(), Instant.now(), "last", CONTEXTO);
|
|
|
|
when(memoryStoreConversationService.getSessionByTelefono(PHONE)).thenReturn(Mono.just(session));
|
|
|
|
// Mock History: System msg -> User msg (Invalid) -> Now this is the 2nd user msg (Count 1 logic)
|
|
// Wait, logic says count is messages AFTER last init.
|
|
// Sys (Init) -> User("Hola" - ignored previously) -> Current Request("Gracias")
|
|
ConversationMessageDTO sysMsg = new ConversationMessageDTO(MessageType.SYSTEM, Instant.now(), "Menu...", null, "whatsapp");
|
|
ConversationMessageDTO userMsg1 = new ConversationMessageDTO(MessageType.USER, Instant.now(), "Hola", null, "whatsapp");
|
|
when(memoryStoreConversationService.getMessages(SESSION_ID)).thenReturn(Flux.just(sysMsg, userMsg1));
|
|
|
|
QuestionDTO q1 = new QuestionDTO("Ver Saldo", "desc", "Tu saldo es 10");
|
|
QuickReplyDTO qrDto = new QuickReplyDTO("Header", "Body", "Btn", "Section", List.of(q1));
|
|
when(quickReplyContentService.getQuickReplies(CONTEXTO)).thenReturn(Mono.just(qrDto));
|
|
|
|
// Mock persistence for the user message (Manual save in the else block)
|
|
mockPersistence();
|
|
|
|
// Mock Delegation
|
|
DetectIntentResponseDTO delegatedResponse = new DetectIntentResponseDTO("df-response", new QueryResultDTO("De nada", null));
|
|
when(memoryStoreConversationService.updateSession(any())).thenReturn(Mono.empty());
|
|
when(conversationManagerService.manageConversation(request)).thenReturn(Mono.just(delegatedResponse));
|
|
|
|
// 2. EXECUTE
|
|
StepVerifier.create(quickRepliesManagerService.manageConversation(request))
|
|
.expectNext(delegatedResponse)
|
|
.verifyComplete();
|
|
|
|
// 3. VERIFY
|
|
// Ensure manual save was called (verifying flow of `persistConversationTurn`)
|
|
verify(memoryStoreConversationService, times(1)).saveMessage(eq(SESSION_ID), any());
|
|
verify(memoryStoreConversationService).updateSession(argThat(s -> s.pantallaContexto() == null));
|
|
verify(conversationManagerService).manageConversation(request);
|
|
}
|
|
|
|
// Helper to mock the complex save chain
|
|
private void mockPersistence() {
|
|
when(conversationEntryMapper.toConversationMessageDTO(any())).thenReturn(new ConversationMessageDTO(MessageType.USER, Instant.now(), "text", null, "wa"));
|
|
when(memoryStoreConversationService.saveSession(any())).thenReturn(Mono.empty());
|
|
when(memoryStoreConversationService.saveMessage(anyString(), any())).thenReturn(Mono.empty());
|
|
when(firestoreConversationService.saveSession(any())).thenReturn(Mono.empty());
|
|
when(firestoreConversationService.saveMessage(anyString(), any())).thenReturn(Mono.empty());
|
|
}
|
|
} |