Skip to content

Commit 3873460

Browse files
authored
Merge pull request #129 from AET-DevOps25/add-server-tests
Add tests
2 parents 050da18 + 3b8ae74 commit 3873460

File tree

3 files changed

+649
-3
lines changed

3 files changed

+649
-3
lines changed

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ The project includes Helm charts for Kubernetes deployment in the `recipai-chart
397397
## CI/CD Pipeline
398398

399399
The project includes a GitHub Actions workflow `ci-cd.yml` for:
400+
- **Testing Server**: For each push, server tests on server microservices are run.
400401
- **Building Docker Images**: Automatically builds and pushes Docker images to GitHub Container Registry.
401402
- **Deploying Docker Images**: Automatically deploys the application to a production environment by using deployment manifests in helm for K8s cluster.
402403

@@ -409,9 +410,6 @@ The project includes a GitHub Actions workflow `ansible-manual.yml` for:
409410
The project includes a GitHub Actions workflow `genai-tests.yml` for:
410411
- **Running GenAI Tests**: Automatically runs the tests defined in the `genai/tests` directory on every code push in genai module.
411412

412-
The project includes a GitHub Actions workflow `server-tests.yml` for:
413-
- **Running Server Tests**: TODO
414-
415413
## API Documentation
416414

417415
### User Service
Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
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

Comments
 (0)