1+ package com .continiousdisappointment .chat ;
2+
3+ import com .continiousdisappointment .chat .domain .DietaryPreference ;
4+ import com .continiousdisappointment .chat .domain .User ;
5+ import com .continiousdisappointment .chat .domain .chat .Chat ;
6+ import com .continiousdisappointment .chat .domain .chat .GenAiMessage ;
7+ import com .continiousdisappointment .chat .domain .chat .Message ;
8+ import com .continiousdisappointment .chat .domain .chat .Role ;
9+ import com .continiousdisappointment .chat .model .ChatModel ;
10+ import com .continiousdisappointment .chat .model .MessageModel ;
11+ import com .continiousdisappointment .chat .repository .ChatRepository ;
12+ import com .continiousdisappointment .chat .service .ChatService ;
13+ import com .continiousdisappointment .chat .service .GenAiService ;
14+ import io .micrometer .core .instrument .Counter ;
15+ import io .micrometer .core .instrument .MeterRegistry ;
16+ import org .junit .jupiter .api .BeforeEach ;
17+ import org .junit .jupiter .api .Test ;
18+ import org .junit .jupiter .api .extension .ExtendWith ;
19+ import org .mockito .ArgumentCaptor ;
20+ import org .mockito .InjectMocks ;
21+ import org .mockito .Mock ;
22+ import org .mockito .junit .jupiter .MockitoExtension ;
23+
24+ import java .time .Instant ;
25+ import java .util .*;
26+
27+ import static org .assertj .core .api .Assertions .*;
28+ import static org .mockito .ArgumentMatchers .*;
29+ import static org .mockito .Mockito .*;
30+
31+ @ ExtendWith (MockitoExtension .class )
32+ class ChatServiceTest {
33+
34+ @ Mock
35+ private ChatRepository chatRepository ;
36+
37+ @ Mock
38+ private GenAiService genAiService ;
39+
40+ @ Mock
41+ private MeterRegistry meterRegistry ;
42+
43+ @ Mock
44+ private Counter counter ;
45+
46+ @ InjectMocks
47+ private ChatService chatService ;
48+
49+ private final UUID chatId = UUID .randomUUID ();
50+ private final int userId = 123 ;
51+ private final int otherUserId = 456 ;
52+ private final String chatTitle = "Test Chat" ;
53+ private final String messageContent = "Hello, world!" ;
54+ private ChatModel testChatModel ;
55+ private User testUser ;
56+
57+ @ BeforeEach
58+ void setUp () {
59+ testChatModel = new ChatModel (chatId , userId , chatTitle );
60+ testChatModel .setCreatedAt (Instant .now ());
61+ testChatModel .setUpdatedAt (Instant .now ());
62+ testChatModel .setMessages (new ArrayList <>());
63+
64+ testUser = new User (userId , "testuser" , Set .of (DietaryPreference .VEGETARIAN , DietaryPreference .GLUTEN_FREE ));
65+ }
66+
67+ @ Test
68+ void getChatsOfUser_WhenChatsExist_ReturnsListOfChats () {
69+ // Given
70+ List <ChatModel > chatModels = Arrays .asList (testChatModel );
71+ when (chatRepository .findByUserId (userId )).thenReturn (chatModels );
72+
73+ // When
74+ List <Chat > result = chatService .getChatsOfUser (userId );
75+
76+ // Then
77+ assertThat (result ).hasSize (1 );
78+ assertThat (result .get (0 ).id ()).isEqualTo (chatId );
79+ assertThat (result .get (0 ).userId ()).isEqualTo (userId );
80+ assertThat (result .get (0 ).title ()).isEqualTo (chatTitle );
81+ verify (chatRepository ).findByUserId (userId );
82+ }
83+
84+ @ Test
85+ void getChatsOfUser_WhenNoChatsExist_ReturnsEmptyList () {
86+ // Given
87+ when (chatRepository .findByUserId (userId )).thenReturn (Collections .emptyList ());
88+
89+ // When
90+ List <Chat > result = chatService .getChatsOfUser (userId );
91+
92+ // Then
93+ assertThat (result ).isEmpty ();
94+ verify (chatRepository ).findByUserId (userId );
95+ }
96+
97+ @ Test
98+ void getChatById_WhenChatExistsAndBelongsToUser_ReturnsChat () {
99+ // Given
100+ when (chatRepository .findById (chatId )).thenReturn (Optional .of (testChatModel ));
101+
102+ // When
103+ Chat result = chatService .getChatById (userId , chatId .toString ());
104+
105+ // Then
106+ assertThat (result .id ()).isEqualTo (chatId );
107+ assertThat (result .userId ()).isEqualTo (userId );
108+ assertThat (result .title ()).isEqualTo (chatTitle );
109+ verify (chatRepository ).findById (chatId );
110+ }
111+
112+ @ Test
113+ void getChatById_WhenChatNotFound_ThrowsIllegalArgumentException () {
114+ // Given
115+ when (chatRepository .findById (chatId )).thenReturn (Optional .empty ());
116+
117+ // When & Then
118+ assertThatThrownBy (() -> chatService .getChatById (userId , chatId .toString ()))
119+ .isInstanceOf (IllegalArgumentException .class )
120+ .hasMessage ("Chat not found" );
121+ verify (chatRepository ).findById (chatId );
122+ }
123+
124+ @ Test
125+ void getChatById_WhenChatDoesNotBelongToUser_ThrowsIllegalArgumentException () {
126+ // Given
127+ when (chatRepository .findById (chatId )).thenReturn (Optional .of (testChatModel ));
128+
129+ // When & Then
130+ assertThatThrownBy (() -> chatService .getChatById (otherUserId , chatId .toString ()))
131+ .isInstanceOf (IllegalArgumentException .class )
132+ .hasMessage ("Chat does not belong to the user" );
133+ verify (chatRepository ).findById (chatId );
134+ }
135+
136+ @ Test
137+ void createChat_WhenValidInput_CreatesAndReturnsChat () {
138+ // Given
139+ when (meterRegistry .counter (anyString ())).thenReturn (counter );
140+ ArgumentCaptor <ChatModel > chatModelCaptor = ArgumentCaptor .forClass (ChatModel .class );
141+
142+ // When
143+ Chat result = chatService .createChat (userId , chatTitle );
144+
145+ // Then
146+ verify (chatRepository ).save (chatModelCaptor .capture ());
147+ verify (counter ).increment ();
148+
149+ ChatModel savedChatModel = chatModelCaptor .getValue ();
150+ assertThat (savedChatModel .getUserId ()).isEqualTo (userId );
151+ assertThat (savedChatModel .getTitle ()).isEqualTo (chatTitle );
152+ assertThat (savedChatModel .getId ()).isNotNull ();
153+ assertThat (savedChatModel .getMessages ()).isEmpty ();
154+
155+ assertThat (result .userId ()).isEqualTo (userId );
156+ assertThat (result .title ()).isEqualTo (chatTitle );
157+ assertThat (result .id ()).isNotNull ();
158+ }
159+
160+ @ Test
161+ void updateChat_WhenChatExistsAndBelongsToUser_UpdatesAndReturnsChat () {
162+ // Given
163+ String newTitle = "Updated Title" ;
164+ when (chatRepository .findById (chatId )).thenReturn (Optional .of (testChatModel ));
165+
166+ // When
167+ Chat result = chatService .updateChat (userId , chatId .toString (), newTitle );
168+
169+ // Then
170+ verify (chatRepository ).save (testChatModel );
171+ assertThat (testChatModel .getTitle ()).isEqualTo (newTitle );
172+ assertThat (result .title ()).isEqualTo (newTitle );
173+ }
174+
175+ @ Test
176+ void updateChat_WhenChatNotFound_ThrowsIllegalArgumentException () {
177+ // Given
178+ when (chatRepository .findById (chatId )).thenReturn (Optional .empty ());
179+
180+ // When & Then
181+ assertThatThrownBy (() -> chatService .updateChat (userId , chatId .toString (), "New Title" ))
182+ .isInstanceOf (IllegalArgumentException .class )
183+ .hasMessage ("Chat not found" );
184+ }
185+
186+ @ Test
187+ void updateChat_WhenChatDoesNotBelongToUser_ThrowsIllegalArgumentException () {
188+ // Given
189+ when (chatRepository .findById (chatId )).thenReturn (Optional .of (testChatModel ));
190+
191+ // When & Then
192+ assertThatThrownBy (() -> chatService .updateChat (otherUserId , chatId .toString (), "New Title" ))
193+ .isInstanceOf (IllegalArgumentException .class )
194+ .hasMessage ("Chat does not belong to the user" );
195+ }
196+
197+ @ Test
198+ void deleteChat_WhenChatExistsAndBelongsToUser_DeletesChat () {
199+ // Given
200+ when (meterRegistry .counter (anyString ())).thenReturn (counter );
201+ when (chatRepository .findById (chatId )).thenReturn (Optional .of (testChatModel ));
202+
203+ // When
204+ chatService .deleteChat (userId , chatId .toString ());
205+
206+ // Then
207+ verify (chatRepository ).delete (testChatModel );
208+ verify (counter ).increment ();
209+ }
210+
211+ @ Test
212+ void deleteChat_WhenChatNotFound_ThrowsIllegalArgumentException () {
213+ // Given
214+ when (chatRepository .findById (chatId )).thenReturn (Optional .empty ());
215+
216+ // When & Then
217+ assertThatThrownBy (() -> chatService .deleteChat (userId , chatId .toString ()))
218+ .isInstanceOf (IllegalArgumentException .class )
219+ .hasMessage ("Chat not found" );
220+ }
221+
222+ @ Test
223+ void deleteChat_WhenChatDoesNotBelongToUser_ThrowsIllegalArgumentException () {
224+ // Given
225+ when (chatRepository .findById (chatId )).thenReturn (Optional .of (testChatModel ));
226+
227+ // When & Then
228+ assertThatThrownBy (() -> chatService .deleteChat (otherUserId , chatId .toString ()))
229+ .isInstanceOf (IllegalArgumentException .class )
230+ .hasMessage ("Chat does not belong to the user" );
231+ }
232+
233+ @ Test
234+ void addMessageToChat_WhenValidInputAndAiResponds_AddsUserAndAssistantMessages () {
235+ // Given
236+ when (meterRegistry .counter (anyString (), anyString (), anyString ())).thenReturn (counter );
237+ String aiResponse = "AI generated response" ;
238+ MessageModel existingMessage = new MessageModel ("Previous message" , Role .USER );
239+ testChatModel .getMessages ().add (existingMessage );
240+
241+ when (chatRepository .findById (chatId )).thenReturn (Optional .of (testChatModel ));
242+ when (genAiService .generateAssistantReply (anyString (), anyList ())).thenReturn (aiResponse );
243+
244+ // When
245+ Message result = chatService .addMessageToChat (testUser , chatId .toString (), messageContent );
246+
247+ // Then
248+ verify (chatRepository ).save (testChatModel );
249+ verify (counter , times (2 )).increment (); // Two messages added
250+
251+ assertThat (testChatModel .getMessages ()).hasSize (3 ); // Previous + user + assistant
252+ assertThat (testChatModel .getMessages ().get (1 ).getContent ()).isEqualTo (messageContent );
253+ assertThat (testChatModel .getMessages ().get (1 ).getRole ()).isEqualTo (Role .USER );
254+ assertThat (testChatModel .getMessages ().get (2 ).getContent ()).isEqualTo (aiResponse );
255+ assertThat (testChatModel .getMessages ().get (2 ).getRole ()).isEqualTo (Role .ASSISTANT );
256+
257+ assertThat (result .content ()).isEqualTo (aiResponse );
258+ assertThat (result .role ()).isEqualTo (Role .ASSISTANT );
259+
260+ // Verify AI service was called with dietary preferences appended
261+ ArgumentCaptor <String > queryCaptor = ArgumentCaptor .forClass (String .class );
262+ ArgumentCaptor <List <GenAiMessage >> messagesCaptor = ArgumentCaptor .forClass (List .class );
263+ verify (genAiService ).generateAssistantReply (queryCaptor .capture (), messagesCaptor .capture ());
264+
265+ String capturedQuery = queryCaptor .getValue ();
266+ assertThat (capturedQuery ).contains (messageContent );
267+ assertThat (capturedQuery ).contains ("My dietary preferences are:" );
268+ assertThat (capturedQuery ).contains (testUser .dietaryPreferences ().toString ());
269+
270+ List <GenAiMessage > capturedMessages = messagesCaptor .getValue ();
271+ assertThat (capturedMessages ).hasSize (1 );
272+ assertThat (capturedMessages .get (0 ).content ()).isEqualTo ("Previous message" );
273+ }
274+
275+ @ Test
276+ void addMessageToChat_WhenAiResponseIsBlank_AddsOnlyUserMessage () {
277+ // Given
278+ when (chatRepository .findById (chatId )).thenReturn (Optional .of (testChatModel ));
279+ when (genAiService .generateAssistantReply (anyString (), anyList ())).thenReturn ("" );
280+
281+ // When
282+ Message result = chatService .addMessageToChat (testUser , chatId .toString (), messageContent );
283+
284+ // Then
285+ verify (chatRepository , never ()).save (any ());
286+ assertThat (result .content ()).isEqualTo (messageContent );
287+ assertThat (result .role ()).isEqualTo (Role .USER );
288+ }
289+
290+ @ Test
291+ void addMessageToChat_WhenChatNotFound_ThrowsIllegalArgumentException () {
292+ // Given
293+ when (chatRepository .findById (chatId )).thenReturn (Optional .empty ());
294+
295+ // When & Then
296+ assertThatThrownBy (() -> chatService .addMessageToChat (testUser , chatId .toString (), messageContent ))
297+ .isInstanceOf (IllegalArgumentException .class )
298+ .hasMessage ("Chat not found" );
299+ }
300+
301+ @ Test
302+ void addMessageToChat_WhenChatDoesNotBelongToUser_ThrowsIllegalArgumentException () {
303+ // Given
304+ User otherUser = new User (otherUserId , "otheruser" , Set .of (DietaryPreference .VEGAN ));
305+ when (chatRepository .findById (chatId )).thenReturn (Optional .of (testChatModel ));
306+
307+ // When & Then
308+ assertThatThrownBy (() -> chatService .addMessageToChat (otherUser , chatId .toString (), messageContent ))
309+ .isInstanceOf (IllegalArgumentException .class )
310+ .hasMessage ("Chat does not belong to the user" );
311+ }
312+
313+ @ Test
314+ void addMessageToChat_WhenChatHasNoExistingMessages_WorksCorrectly () {
315+ // Given
316+ String aiResponse = "First AI response" ;
317+ when (meterRegistry .counter (anyString (), anyString (), anyString ())).thenReturn (counter );
318+ when (chatRepository .findById (chatId )).thenReturn (Optional .of (testChatModel ));
319+ when (genAiService .generateAssistantReply (anyString (), anyList ())).thenReturn (aiResponse );
320+
321+ // When
322+ Message result = chatService .addMessageToChat (testUser , chatId .toString (), messageContent );
323+
324+ // Then
325+ ArgumentCaptor <List <GenAiMessage >> messagesCaptor = ArgumentCaptor .forClass (List .class );
326+ verify (genAiService ).generateAssistantReply (anyString (), messagesCaptor .capture ());
327+
328+ List <GenAiMessage > capturedMessages = messagesCaptor .getValue ();
329+ assertThat (capturedMessages ).isEmpty ();
330+
331+ assertThat (testChatModel .getMessages ()).hasSize (2 );
332+ assertThat (result .content ()).isEqualTo (aiResponse );
333+ assertThat (result .role ()).isEqualTo (Role .ASSISTANT );
334+ }
335+
336+ @ Test
337+ void addMessageToChat_WhenUserHasNoDietaryPreferences_AppendsEmptySet () {
338+ // Given
339+ User userWithNoPreferences = new User (userId , "testuser" , Set .of ());
340+ String aiResponse = "AI response" ;
341+ when (meterRegistry .counter (anyString (), anyString (), anyString ())).thenReturn (counter );
342+ when (chatRepository .findById (chatId )).thenReturn (Optional .of (testChatModel ));
343+ when (genAiService .generateAssistantReply (anyString (), anyList ())).thenReturn (aiResponse );
344+
345+ // When
346+ chatService .addMessageToChat (userWithNoPreferences , chatId .toString (), messageContent );
347+
348+ // Then
349+ ArgumentCaptor <String > queryCaptor = ArgumentCaptor .forClass (String .class );
350+ verify (genAiService ).generateAssistantReply (queryCaptor .capture (), anyList ());
351+
352+ String capturedQuery = queryCaptor .getValue ();
353+ assertThat (capturedQuery ).contains ("My dietary preferences are: []" );
354+ }
355+ }
0 commit comments