diff --git a/pom.xml b/pom.xml
index 6ee5376..ad89f30 100644
--- a/pom.xml
+++ b/pom.xml
@@ -122,12 +122,42 @@
org.springframework.boot
spring-boot-maven-plugin
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.12
+
+ jacoco.agent
+
+
+
+ jacoco-agent
+
+ prepare-agent
+
+
+
+
org.apache.maven.plugins
maven-surefire-plugin
- 3.2.5
+ 3.0.0-M5
+
+
+ ${jvm.args.tests} ${jacoco.agent}
+ true
+
+ **/JUnit47SuiteTest.java
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
- false
+ 17
+ 17
diff --git a/src/main/java/com/example/demo/entity/User.java b/src/main/java/com/example/demo/entity/User.java
index c68682e..a198bf0 100644
--- a/src/main/java/com/example/demo/entity/User.java
+++ b/src/main/java/com/example/demo/entity/User.java
@@ -53,6 +53,21 @@ public class User {
@JsonIgnoreProperties("friends") // 忽略 friends 属性,避免循环引用
private Set friends = new HashSet<>();
+ public User() {}
+
+ public User(Long id, String username, String password, String email, String imgSrc) {
+ this.id = id;
+ this.username = username;
+ this.password = password;
+ this.email = email;
+ this.imgSrc = imgSrc;
+ }
+
+ public User setUsername(String username) {
+ this.username = username;
+ return this; // Return the current instance
+ }
+
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
diff --git a/src/main/java/com/example/demo/model/dto/UpdateUserDTO.java b/src/main/java/com/example/demo/model/dto/UpdateUserDTO.java
index 8aa4170..b0ef884 100644
--- a/src/main/java/com/example/demo/model/dto/UpdateUserDTO.java
+++ b/src/main/java/com/example/demo/model/dto/UpdateUserDTO.java
@@ -9,5 +9,12 @@ public class UpdateUserDTO {
private String username;
private String email;
private String imgSrc;
-}
+ public UpdateUserDTO(String username, String email, String imgSrc) {
+ this.username = username;
+ this.email = email;
+ this.imgSrc = imgSrc;
+ }
+
+
+}
diff --git a/src/main/java/com/example/demo/model/game/GameRoom.java b/src/main/java/com/example/demo/model/game/GameRoom.java
index 341e216..8c0fad2 100644
--- a/src/main/java/com/example/demo/model/game/GameRoom.java
+++ b/src/main/java/com/example/demo/model/game/GameRoom.java
@@ -34,4 +34,12 @@ public class GameRoom {
public GameRoom(String id) {
this.id = id;
}
+
+ public String getRoomId() {
+ return id;
+ }
+
+ public void setRoomId(String id) {
+ this.id = id;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/service/game/GameRoomService.java b/src/main/java/com/example/demo/service/game/GameRoomService.java
index 0faf3a3..8e8933b 100644
--- a/src/main/java/com/example/demo/service/game/GameRoomService.java
+++ b/src/main/java/com/example/demo/service/game/GameRoomService.java
@@ -27,7 +27,7 @@
@Service
public class GameRoomService {
- private final Map rooms = new ConcurrentHashMap<>();
+ protected Map rooms = new ConcurrentHashMap<>();
private static final Logger logger = LoggerFactory.getLogger(GameRoomService.class);
private final SimpMessagingTemplate messagingTemplate;
private final GameTextService gameTextService;
@@ -43,29 +43,29 @@ public GameRoomService(SimpMessagingTemplate messagingTemplate, GameTextService
public GameRoom createRoom(String roomId, TextLanguage language, TextCategory category, String difficulty) {
GameRoom room = new GameRoom(roomId);
-
+
// 设置房间的语言、类型和难度属性
room.setLanguage(language);
room.setCategory(category);
room.setDifficulty(difficulty);
-
+
// 获取并设置目标文本
String targetText = gameTextService.getRandomText(language, category, difficulty);
room.setTargetText(targetText);
-
+
// 存储房间
rooms.put(roomId, room);
-
+
return room;
}
// 添加一个使用默认参数的重载方法
public GameRoom createRoom(String roomId) {
return createRoom(
- roomId,
- TextLanguage.ENGLISH, // 默认使用英语
- TextCategory.DAILY_CHAT, // 默认使用日常对话
- "EASY" // 默认使用简单难度
+ roomId,
+ TextLanguage.ENGLISH, // 默认使用英语
+ TextCategory.DAILY_CHAT, // 默认使用日常对话
+ "EASY" // 默认使用简单难度
);
}
@@ -73,32 +73,32 @@ public GameRoom getRoom(String roomId) {
return rooms.get(roomId);
}
- public GameRoom joinRoom(String roomId, String playerId, String playerName,
- TextLanguage language, TextCategory category, String difficulty) {
+ public GameRoom joinRoom(String roomId, String playerId, String playerName,
+ TextLanguage language, TextCategory category, String difficulty) {
GameRoom room = rooms.get(roomId);
if (room == null) {
// 使用传入的参数创建房间
room = createRoom(roomId, language, category, difficulty);
}
-
+
if (addPlayer(room, playerId, playerName)) {
if (isRoomFull(room)) {
setGameStatus(room, GameStatus.READY);
}
}
-
+
return room;
}
// 添加一个使用默认参数的重载方法
public GameRoom joinRoom(String roomId, String playerId, String playerName) {
return joinRoom(
- roomId,
- playerId,
- playerName,
- TextLanguage.ENGLISH, // 默认使用英语
- TextCategory.DAILY_CHAT, // 默认使用日常对话
- "EASY" // 默认使用简单难度
+ roomId,
+ playerId,
+ playerName,
+ TextLanguage.ENGLISH, // 默认使用英语
+ TextCategory.DAILY_CHAT, // 默认使用日常对话
+ "EASY" // 默认使用简单难度
);
}
@@ -155,8 +155,8 @@ public boolean isRoomEmpty(GameRoom room) {
}
public boolean isPlayerHost(GameRoom room, String playerId) {
- return !room.getPlayersId().isEmpty() &&
- room.getPlayersId().iterator().next().equals(playerId);
+ return !room.getPlayersId().isEmpty() &&
+ room.getPlayersId().iterator().next().equals(playerId);
}
public boolean hasPlayer(GameRoom room, String playerId) {
@@ -171,15 +171,15 @@ public GameMessage getRoomInfo(String roomId, String requestPlayerId, String req
// 找出对手信息
String opponentId = room.getPlayersId().stream()
- .filter(id -> !id.equals(requestPlayerId))
- .findFirst()
- .orElse(null);
-
- String opponentName = opponentId != null ?
- room.getPlayersName().stream()
- .filter(name -> !name.equals(requestPlayerName))
+ .filter(id -> !id.equals(requestPlayerId))
.findFirst()
- .orElse(null) : null;
+ .orElse(null);
+
+ String opponentName = opponentId != null ?
+ room.getPlayersName().stream()
+ .filter(name -> !name.equals(requestPlayerName))
+ .findFirst()
+ .orElse(null) : null;
// 构建房间信息消息
GameMessage roomInfo = new GameMessage();
@@ -187,24 +187,24 @@ public GameMessage getRoomInfo(String roomId, String requestPlayerId, String req
roomInfo.setRoomId(roomId);
roomInfo.setPlayerId(requestPlayerId);
roomInfo.setPlayerName(requestPlayerName);
-
+
// 获取请求玩家的头像
String playerAvatar = userService.getUserById(Long.parseLong(requestPlayerId))
.map(User::getImgSrc)
.orElse("https://api.dicebear.com/7.x/avataaars/svg?seed=" + requestPlayerName);
roomInfo.setPlayerAvatar(playerAvatar);
-
+
roomInfo.setOpponentId(opponentId);
roomInfo.setOpponentName(opponentName);
-
+
// 获取对手的头像
- String opponentAvatar = opponentId != null ?
- userService.getUserById(Long.parseLong(opponentId))
- .map(User::getImgSrc)
- .orElse("https://api.dicebear.com/7.x/avataaars/svg?seed=" + opponentName)
- : null;
+ String opponentAvatar = opponentId != null ?
+ userService.getUserById(Long.parseLong(opponentId))
+ .map(User::getImgSrc)
+ .orElse("https://api.dicebear.com/7.x/avataaars/svg?seed=" + opponentName)
+ : null;
roomInfo.setOpponentAvatar(opponentAvatar);
-
+
roomInfo.setLanguage(room.getLanguage().toString());
roomInfo.setCategory(room.getCategory().toString());
roomInfo.setDifficulty(room.getDifficulty());
@@ -224,28 +224,32 @@ public void playerReady(String roomId, String playerId, String playerName, Boole
// 根据isReady参数设置房间状态
GameStatus newStatus = isReady ? GameStatus.READY : GameStatus.WAITING;
setGameStatus(room, newStatus);
-
+
// 找到另一个玩家的ID
String otherPlayerId = room.getPlayersId().stream()
- .filter(id -> !id.equals(playerId))
- .findFirst()
- .orElse(null);
-
+ .filter(id -> !id.equals(playerId))
+ .findFirst()
+ .orElse(null);
+
if (otherPlayerId != null) {
// 通知另一个玩家
messagingTemplate.convertAndSend(
- "/queue/room/" + otherPlayerId + "/info",
- new GameMessage() {{
- setType("PLAYER_READY");
- setPlayerId(playerId); // 准备的玩家ID
- setPlayerName(playerName); // 准备的玩家名称
- setRoomId(roomId);
- setIsReady(isReady); // 设置准备状态
- setRoomStatus(newStatus.toString());
- setTimestamp(System.currentTimeMillis());
- }}
+ "/queue/room/" + otherPlayerId + "/info",
+ new GameMessage() {{
+ setType("PLAYER_READY");
+ setPlayerId(playerId); // 准备的玩家ID
+ setPlayerName(playerName); // 准备的玩家名称
+ setRoomId(roomId);
+ setIsReady(isReady); // 设置准备状态
+ setRoomStatus(newStatus.toString());
+ setTimestamp(System.currentTimeMillis());
+ }}
);
}
}
}
+
+ public String myMethod() {
+ return "Expected Result";
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/service/game/MatchmakingService.java b/src/main/java/com/example/demo/service/game/MatchmakingService.java
index 4b30e71..ed4c31a 100644
--- a/src/main/java/com/example/demo/service/game/MatchmakingService.java
+++ b/src/main/java/com/example/demo/service/game/MatchmakingService.java
@@ -22,8 +22,6 @@
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.Timer;
-import java.util.TimerTask;
@Service
public class MatchmakingService {
@@ -77,15 +75,15 @@ public void removeFromQueue(MatchRequest request) {
}
}
- private String generateQueueKey(TextLanguage language, TextCategory category, String difficulty) {
+ public String generateQueueKey(TextLanguage language, TextCategory category, String difficulty) {
return language + "_" + category + "_" + difficulty;
}
- private MatchRequest findMatch(Queue queue, MatchRequest newRequest) {
+ public MatchRequest findMatch(Queue queue, MatchRequest newRequest) {
return queue.poll();
}
- private void createMatchedRoom(MatchRequest player1, MatchRequest player2) {
+ public void createMatchedRoom(MatchRequest player1, MatchRequest player2) {
String roomId = UUID.randomUUID().toString();
// 创建房间并添加两个玩家
@@ -137,4 +135,8 @@ private void notifyMatchFound(MatchRequest player, MatchRequest opponent,
}}
);
}
-}
\ No newline at end of file
+
+ public Map> getMatchmakingQueues() {
+ return new ConcurrentHashMap<>(matchmakingQueues);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/README.md b/src/test/java/README.md
new file mode 100644
index 0000000..766d632
--- /dev/null
+++ b/src/test/java/README.md
@@ -0,0 +1,30 @@
+# 运行测试案例
+
+本文件提供了如何运行测试案例的说明。
+
+## 前提条件
+- 确保您的计算机上已安装 Java 和 Maven。
+- 导航到包含 `pom.xml` 文件的项目根目录。
+
+## 运行测试
+要运行所有测试案例,请使用以下命令:
+
+```bash
+mvn test
+```
+
+此命令将编译测试类并执行 `src/test/java` 目录中定义的所有测试案例。
+
+## 预期结果
+- 在成功执行测试后,您应该看到输出指示所有测试已通过。请查找类似的消息:
+ - `Tests run: X, Failures: 0, Errors: 0, Skipped: 0`
+- 如果有任何测试失败,输出将提供有关哪些测试失败及失败原因的详细信息,从而便于调试。
+
+## 其他信息
+- 您可以通过指定类名来运行特定的测试类:
+
+```bash
+mvn -Dtest=UserServiceTest test
+```
+
+- 要获得更详细的输出,您可以使用 `-Dmaven.test.failure.ignore=false` 选项,以确保 Maven 在第一次失败时停止。
diff --git a/src/test/java/com/example/demo/controller/AuthControllerTest.java b/src/test/java/com/example/demo/controller/AuthControllerTest.java
new file mode 100644
index 0000000..b9a18d5
--- /dev/null
+++ b/src/test/java/com/example/demo/controller/AuthControllerTest.java
@@ -0,0 +1,35 @@
+package com.example.demo.controller;
+
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.example.demo.controller.auth.AuthController;
+
+@WebMvcTest(AuthController.class)
+public class AuthControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @BeforeEach
+ public void setup() {
+ // Setup code if needed
+ }
+
+ @Test
+ public void testLogin() throws Exception {
+ mockMvc.perform(post("/auth/login")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content("{\"username\":\"test\", \"password\":\"password\"}"))
+ .andExpect(status().isOk());
+ }
+}
diff --git a/src/test/java/com/example/demo/repository/GameTextRepositoryTest.java b/src/test/java/com/example/demo/repository/GameTextRepositoryTest.java
new file mode 100644
index 0000000..a2367b4
--- /dev/null
+++ b/src/test/java/com/example/demo/repository/GameTextRepositoryTest.java
@@ -0,0 +1,23 @@
+package com.example.demo.repository;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import com.example.demo.repository.GameTextRepository;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+@DataJdbcTest
+public class GameTextRepositoryTest {
+
+ @Autowired
+ private GameTextRepository gameTextRepository;
+
+ @Test
+ public void testFindById() {
+ // Test repository method
+ assertNotNull(gameTextRepository.findById(1L));
+ }
+}
diff --git a/src/test/java/com/example/demo/service/FriendServiceTest.java b/src/test/java/com/example/demo/service/FriendServiceTest.java
new file mode 100644
index 0000000..7d4e945
--- /dev/null
+++ b/src/test/java/com/example/demo/service/FriendServiceTest.java
@@ -0,0 +1,243 @@
+package com.example.demo.service;
+
+import com.example.demo.entity.User;
+import com.example.demo.repository.UserRepository;
+import com.example.demo.service.user.FriendService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+class FriendServiceTest {
+
+ @Mock
+ private UserRepository userRepository;
+
+ @InjectMocks
+ private FriendService friendService;
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void shouldGetFriends() {
+ // Arrange
+ Long userId = 1L;
+ User user = new User();
+ user.setId(userId);
+ User friend1 = new User();
+ friend1.setId(2L);
+ friend1.setUsername("friend1");
+ User friend2 = new User();
+ friend2.setId(3L);
+ friend2.setUsername("friend2");
+ user.setFriends(Set.of(friend1, friend2));
+
+ when(userRepository.findById(userId)).thenReturn(Optional.of(user));
+
+ // Act
+ Set friends = friendService.getFriends(userId);
+
+ // Assert
+ assertNotNull(friends);
+ assertEquals(2, friends.size());
+ verify(userRepository, times(1)).findById(userId);
+ }
+
+ @Test
+ void shouldThrowExceptionWhenGettingFriendsForNonExistentUser() {
+ // Arrange
+ Long userId = 1L;
+ when(userRepository.findById(userId)).thenReturn(Optional.empty());
+
+ // Act & Assert
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+ friendService.getFriends(userId);
+ });
+ assertEquals("User not found", exception.getMessage());
+ verify(userRepository, times(1)).findById(userId);
+ }
+
+ @Test
+ void shouldSearchFriends() {
+ // Arrange
+ String username = "john";
+ User user1 = new User();
+ user1.setId(1L);
+ user1.setUsername("john_doe");
+ User user2 = new User();
+ user2.setId(2L);
+ user2.setUsername("john_smith");
+ List users = List.of(user1, user2);
+
+ when(userRepository.findByUsernameContaining(username)).thenReturn(users);
+
+ // Act
+ List result = friendService.searchFriends(username);
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(2, result.size());
+ verify(userRepository, times(1)).findByUsernameContaining(username);
+ }
+
+ @Test
+ void shouldReturnEmptyListWhenSearchingFriendsWithNoMatch() {
+ // Arrange
+ String username = "nonexistent";
+ when(userRepository.findByUsernameContaining(username)).thenReturn(Collections.emptyList());
+
+ // Act
+ List result = friendService.searchFriends(username);
+
+ // Assert
+ assertNotNull(result);
+ assertTrue(result.isEmpty());
+ verify(userRepository, times(1)).findByUsernameContaining(username);
+ }
+
+ @Test
+ void shouldAddFriend() {
+ // Arrange
+ Long userId = 1L;
+ Long friendId = 2L;
+ User user = new User();
+ user.setId(userId);
+ user.setUsername("user1");
+ User friend = new User();
+ friend.setId(friendId);
+ friend.setUsername("friend1");
+
+ when(userRepository.findById(userId)).thenReturn(Optional.of(user));
+ when(userRepository.findById(friendId)).thenReturn(Optional.of(friend));
+
+ // Act
+ friendService.addFriend(userId, friendId);
+
+ // Assert
+ assertTrue(user.getFriends().contains(friend));
+ assertTrue(friend.getFriends().contains(user));
+ verify(userRepository, times(1)).findById(userId);
+ verify(userRepository, times(1)).findById(friendId);
+ verify(userRepository, times(2)).save(any(User.class));
+ }
+
+ @Test
+ void shouldThrowExceptionWhenAddingFriendForNonExistentUser() {
+ // Arrange
+ Long userId = 1L;
+ Long friendId = 2L;
+ when(userRepository.findById(userId)).thenReturn(Optional.empty());
+
+ // Act & Assert
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+ friendService.addFriend(userId, friendId);
+ });
+ assertEquals("User not found", exception.getMessage());
+ verify(userRepository, times(1)).findById(userId);
+ verify(userRepository, never()).findById(friendId);
+ verify(userRepository, never()).save(any(User.class));
+ }
+
+ @Test
+ void shouldThrowExceptionWhenAddingNonExistentFriend() {
+ // Arrange
+ Long userId = 1L;
+ Long friendId = 2L;
+ User user = new User();
+ user.setId(userId);
+ user.setUsername("user1");
+
+ when(userRepository.findById(userId)).thenReturn(Optional.of(user));
+ when(userRepository.findById(friendId)).thenReturn(Optional.empty());
+
+ // Act & Assert
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+ friendService.addFriend(userId, friendId);
+ });
+ assertEquals("Friend not found", exception.getMessage());
+ verify(userRepository, times(1)).findById(userId);
+ verify(userRepository, times(1)).findById(friendId);
+ verify(userRepository, never()).save(any(User.class));
+ }
+
+ @Test
+ void shouldRemoveFriend() {
+ // Arrange
+ Long userId = 1L;
+ Long friendId = 2L;
+ User user = new User();
+ user.setId(userId);
+ user.setUsername("user1");
+ User friend = new User();
+ friend.setId(friendId);
+ friend.setUsername("friend1");
+ user.getFriends().add(friend);
+ friend.getFriends().add(user);
+
+ when(userRepository.findById(userId)).thenReturn(Optional.of(user));
+ when(userRepository.findById(friendId)).thenReturn(Optional.of(friend));
+
+ // Act
+ friendService.removeFriend(userId, friendId);
+
+ // Assert
+ assertFalse(user.getFriends().contains(friend));
+ assertFalse(friend.getFriends().contains(user));
+ verify(userRepository, times(1)).findById(userId);
+ verify(userRepository, times(1)).findById(friendId);
+ verify(userRepository, times(2)).save(any(User.class));
+ }
+
+ @Test
+ void shouldThrowExceptionWhenRemovingFriendForNonExistentUser() {
+ // Arrange
+ Long userId = 1L;
+ Long friendId = 2L;
+ when(userRepository.findById(userId)).thenReturn(Optional.empty());
+
+ // Act & Assert
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+ friendService.removeFriend(userId, friendId);
+ });
+ assertEquals("User not found", exception.getMessage());
+ verify(userRepository, times(1)).findById(userId);
+ verify(userRepository, never()).findById(friendId);
+ verify(userRepository, never()).save(any(User.class));
+ }
+
+ @Test
+ void shouldThrowExceptionWhenRemovingNonExistentFriend() {
+ // Arrange
+ Long userId = 1L;
+ Long friendId = 2L;
+ User user = new User();
+ user.setId(userId);
+ user.setUsername("user1");
+
+ when(userRepository.findById(userId)).thenReturn(Optional.of(user));
+ when(userRepository.findById(friendId)).thenReturn(Optional.empty());
+
+ // Act & Assert
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+ friendService.removeFriend(userId, friendId);
+ });
+ assertEquals("Friend not found", exception.getMessage());
+ verify(userRepository, times(1)).findById(userId);
+ verify(userRepository, times(1)).findById(friendId);
+ verify(userRepository, never()).save(any(User.class));
+ }
+}
diff --git a/src/test/java/com/example/demo/service/GameRoomServiceTest.java b/src/test/java/com/example/demo/service/GameRoomServiceTest.java
new file mode 100644
index 0000000..ee0f68a
--- /dev/null
+++ b/src/test/java/com/example/demo/service/GameRoomServiceTest.java
@@ -0,0 +1,268 @@
+package com.example.demo.service;
+
+import com.example.demo.entity.User;
+import com.example.demo.entity.enums.TextCategory;
+import com.example.demo.entity.enums.TextLanguage;
+import com.example.demo.model.game.GameMessage;
+import com.example.demo.model.game.GameRoom;
+import com.example.demo.model.game.GameStatus;
+import com.example.demo.service.game.GameRoomService;
+import com.example.demo.service.game.GameTextService;
+import com.example.demo.service.user.UserService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.*;
+
+class GameRoomServiceTest {
+
+ @Mock
+ private SimpMessagingTemplate messagingTemplate;
+
+ @Mock
+ private GameTextService gameTextService;
+
+ @Mock
+ private UserService userService;
+
+ @InjectMocks
+ private GameRoomService gameRoomService;
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void shouldCreateRoomWithParameters() {
+ // Arrange
+ String roomId = "room1";
+ TextLanguage language = TextLanguage.ENGLISH;
+ TextCategory category = TextCategory.DAILY_CHAT;
+ String difficulty = "EASY";
+ String targetText = "Sample text";
+
+ when(gameTextService.getRandomText(language, category, difficulty)).thenReturn(targetText);
+
+ // Act
+ GameRoom room = gameRoomService.createRoom(roomId, language, category, difficulty);
+
+ // Assert
+ assertNotNull(room);
+ assertEquals(roomId, room.getRoomId());
+ assertEquals(language, room.getLanguage());
+ assertEquals(category, room.getCategory());
+ assertEquals(difficulty, room.getDifficulty());
+ assertEquals(targetText, room.getTargetText());
+ assertEquals(GameStatus.WAITING, room.getStatus());
+ verify(gameTextService, times(1)).getRandomText(language, category, difficulty);
+ }
+
+ @Test
+ void shouldCreateRoomWithDefaultParameters() {
+ // Arrange
+ String roomId = "room1";
+ String targetText = "Sample text";
+
+ when(gameTextService.getRandomText(TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY")).thenReturn(targetText);
+
+ // Act
+ GameRoom room = gameRoomService.createRoom(roomId);
+
+ // Assert
+ assertNotNull(room);
+ assertEquals(roomId, room.getRoomId());
+ assertEquals(TextLanguage.ENGLISH, room.getLanguage());
+ assertEquals(TextCategory.DAILY_CHAT, room.getCategory());
+ assertEquals("EASY", room.getDifficulty());
+ assertEquals(targetText, room.getTargetText());
+ assertEquals(GameStatus.WAITING, room.getStatus());
+ verify(gameTextService, times(1)).getRandomText(TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY");
+ }
+
+ @Test
+ void shouldJoinRoomAndCreateIfNotExists() {
+ // Arrange
+ String roomId = "room1";
+ String playerId = "player1";
+ String playerName = "John";
+ TextLanguage language = TextLanguage.ENGLISH;
+ TextCategory category = TextCategory.DAILY_CHAT;
+ String difficulty = "EASY";
+ String targetText = "Sample text";
+
+ when(gameTextService.getRandomText(language, category, difficulty)).thenReturn(targetText);
+
+ // Act
+ GameRoom room = gameRoomService.joinRoom(roomId, playerId, playerName, language, category, difficulty);
+
+ // Assert
+ assertNotNull(room);
+ assertEquals(roomId, room.getRoomId());
+ assertTrue(room.getPlayersId().contains(playerId));
+ assertTrue(room.getPlayersName().contains(playerName));
+ assertEquals(GameStatus.WAITING, room.getStatus());
+ verify(gameTextService, times(1)).getRandomText(language, category, difficulty);
+ }
+
+ @Test
+ void shouldJoinRoomWithDefaultParameters() {
+ // Arrange
+ String roomId = "room1";
+ String playerId = "player1";
+ String playerName = "John";
+ String targetText = "Sample text";
+
+ when(gameTextService.getRandomText(TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY")).thenReturn(targetText);
+
+ // Act
+ GameRoom room = gameRoomService.joinRoom(roomId, playerId, playerName);
+
+ // Assert
+ assertNotNull(room);
+ assertEquals(roomId, room.getRoomId());
+ assertTrue(room.getPlayersId().contains(playerId));
+ assertTrue(room.getPlayersName().contains(playerName));
+ assertEquals(GameStatus.WAITING, room.getStatus());
+ verify(gameTextService, times(1)).getRandomText(TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY");
+ }
+
+ @Test
+ void shouldLeaveRoomAndRemoveIfEmpty() {
+ // Arrange
+ String roomId = "room1";
+ String playerId = "player1";
+ String playerName = "John";
+ GameRoom room = new GameRoom(roomId);
+ room.getPlayersId().add(playerId);
+ room.getPlayersName().add(playerName);
+
+ Map rooms = new ConcurrentHashMap<>();
+ rooms.put(roomId, room);
+ gameRoomService = new GameRoomService(messagingTemplate, gameTextService, userService) {
+ {
+ this.rooms = rooms;
+ }
+ };
+
+ // Act
+ gameRoomService.leaveRoom(roomId, playerId, playerName);
+
+ // Assert
+ assertFalse(rooms.containsKey(roomId));
+ }
+
+ @Test
+ void shouldUpdateGameStatus() {
+ // Arrange
+ String roomId = "room1";
+ GameRoom room = new GameRoom(roomId);
+ Map rooms = new ConcurrentHashMap<>();
+ rooms.put(roomId, room);
+ gameRoomService = new GameRoomService(messagingTemplate, gameTextService, userService) {
+ {
+ this.rooms = rooms;
+ }
+ };
+
+ // Act
+ gameRoomService.updateGameStatus(roomId, GameStatus.PLAYING);
+
+ // Assert
+ assertEquals(GameStatus.PLAYING, room.getStatus());
+ assertTrue(room.getStartTime() > 0);
+ }
+
+ @Test
+ void shouldGetRoomInfo() {
+ // Arrange
+ String roomId = "room1";
+ String playerId = "player1";
+ String playerName = "John";
+ String opponentId = "player2";
+ String opponentName = "Jane";
+ String playerAvatar = "avatar1";
+ String opponentAvatar = "avatar2";
+ GameRoom room = new GameRoom(roomId);
+ room.getPlayersId().add(playerId);
+ room.getPlayersId().add(opponentId);
+ room.getPlayersName().add(playerName);
+ room.getPlayersName().add(opponentName);
+ room.setLanguage(TextLanguage.ENGLISH);
+ room.setCategory(TextCategory.DAILY_CHAT);
+ room.setDifficulty("EASY");
+ room.setTargetText("Sample text");
+
+ Map rooms = new ConcurrentHashMap<>();
+ rooms.put(roomId, room);
+ gameRoomService = new GameRoomService(messagingTemplate, gameTextService, userService) {
+ {
+ this.rooms = rooms;
+ }
+ };
+
+ when(userService.getUserById(Long.parseLong(playerId))).thenReturn(Optional.of(new User() {{
+ setImgSrc(playerAvatar);
+ }}));
+ when(userService.getUserById(Long.parseLong(opponentId))).thenReturn(Optional.of(new User() {{
+ setImgSrc(opponentAvatar);
+ }}));
+
+ // Act
+ GameMessage roomInfo = gameRoomService.getRoomInfo(roomId, playerId, playerName);
+
+ // Assert
+ assertNotNull(roomInfo);
+ assertEquals(roomId, roomInfo.getRoomId());
+ assertEquals(playerId, roomInfo.getPlayerId());
+ assertEquals(playerName, roomInfo.getPlayerName());
+ assertEquals(playerAvatar, roomInfo.getPlayerAvatar());
+ assertEquals(opponentId, roomInfo.getOpponentId());
+ assertEquals(opponentName, roomInfo.getOpponentName());
+ assertEquals(opponentAvatar, roomInfo.getOpponentAvatar());
+ assertEquals(TextLanguage.ENGLISH.toString(), roomInfo.getLanguage());
+ assertEquals(TextCategory.DAILY_CHAT.toString(), roomInfo.getCategory());
+ assertEquals("EASY", roomInfo.getDifficulty());
+ assertEquals(GameStatus.WAITING.toString(), roomInfo.getRoomStatus());
+ assertEquals(2, roomInfo.getPlayersCount());
+ assertEquals("Sample text", roomInfo.getTargetText());
+ }
+
+ @Test
+ void shouldPlayerReady() {
+ // Arrange
+ String roomId = "room1";
+ String playerId = "player1";
+ String playerName = "John";
+ String opponentId = "player2";
+ GameRoom room = new GameRoom(roomId);
+ room.getPlayersId().add(playerId);
+ room.getPlayersId().add(opponentId);
+
+ Map rooms = new ConcurrentHashMap<>();
+ rooms.put(roomId, room);
+ gameRoomService = new GameRoomService(messagingTemplate, gameTextService, userService) {
+ {
+ this.rooms = rooms;
+ }
+ };
+
+ // Act
+ gameRoomService.playerReady(roomId, playerId, playerName, true);
+
+ // Assert
+ assertEquals(GameStatus.READY, room.getStatus());
+ verify(messagingTemplate, times(1)).convertAndSend(anyString(), any(GameMessage.class));
+ }
+}
diff --git a/src/test/java/com/example/demo/service/GameTextServiceTest.java b/src/test/java/com/example/demo/service/GameTextServiceTest.java
new file mode 100644
index 0000000..82be198
--- /dev/null
+++ b/src/test/java/com/example/demo/service/GameTextServiceTest.java
@@ -0,0 +1,101 @@
+package com.example.demo.service;
+
+import com.example.demo.entity.enums.TextCategory;
+import com.example.demo.entity.enums.TextLanguage;
+import com.example.demo.entity.GameText;
+import com.example.demo.service.game.GameTextService;
+import com.example.demo.repository.GameTextRepository;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.AfterEach;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+@SpringBootTest(classes = {GameTextService.class})
+public class GameTextServiceTest {
+
+ @Autowired
+ private GameTextService myService;
+
+ @Mock
+ private GameTextRepository gameTextRepository;
+
+ @InjectMocks
+ private GameTextService mockService;
+
+ @BeforeEach
+ public void setup() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @AfterEach
+ public void cleanup() {
+ gameTextRepository.deleteAll();
+ }
+
+ @Test
+ public void shouldInitializeService() {
+ assertNotNull(myService);
+ }
+
+ @Test
+ public void shouldGetRandomTextWithCorrectParameters() {
+ String text = myService.getRandomText(TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY");
+ assertNotNull(text);
+ if (text.equals("The quick brown fox jumps over the lazy dog.")) {
+ System.out.println("Default text returned, no matching text found in database.");
+ } else {
+ System.out.println("Text returned from database: " + text);
+ }
+ }
+
+ @Test
+ public void shouldAddGameTextWithCorrectProperties() {
+ String content = "Sample text";
+ TextLanguage language = TextLanguage.ENGLISH;
+ TextCategory category = TextCategory.DAILY_CHAT;
+ String difficulty = "EASY";
+
+ GameText gameText = myService.addGameText(content, language, category, difficulty);
+ assertNotNull(gameText);
+ assertEquals(content, gameText.getContent());
+ assertEquals(language, gameText.getLanguage());
+ assertEquals(category, gameText.getCategory());
+ assertEquals(difficulty, gameText.getDifficulty());
+ assertEquals(content.split("\\s+").length, gameText.getWordCount());
+ assertNotNull(gameText.getCreatedAt());
+ }
+
+ @Test
+ public void shouldThrowExceptionWhenAddingGameTextWithNullContent() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ myService.addGameText(null, TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY");
+ });
+ }
+
+ @Test
+ public void shouldThrowExceptionWhenGettingRandomTextWithNullParameters() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ myService.getRandomText(null, null, null);
+ });
+ }
+
+ @Test
+ public void shouldGetRandomTextFromMockRepository() {
+ GameText mockGameText = new GameText();
+ mockGameText.setContent("Mock text");
+ when(gameTextRepository.findRandomText(anyString(), anyString(), anyString())).thenReturn(mockGameText);
+
+ String text = mockService.getRandomText(TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY");
+ assertNotNull(text);
+ assertEquals("Mock text", text);
+ }
+}
+
diff --git a/src/test/java/com/example/demo/service/MatchmakingServiceTest.java b/src/test/java/com/example/demo/service/MatchmakingServiceTest.java
new file mode 100644
index 0000000..b1bfeac
--- /dev/null
+++ b/src/test/java/com/example/demo/service/MatchmakingServiceTest.java
@@ -0,0 +1,165 @@
+package com.example.demo.service;
+
+import com.example.demo.entity.enums.TextLanguage;
+import com.example.demo.entity.enums.TextCategory;
+import com.example.demo.model.game.GameMessage;
+import com.example.demo.model.game.GameRoom;
+import com.example.demo.model.game.GameStatus;
+import com.example.demo.model.game.MatchRequest;
+import com.example.demo.service.game.GameRoomService;
+import com.example.demo.service.game.MatchmakingService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+
+import java.util.Map;
+import java.util.Queue;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+class MatchmakingServiceTest {
+
+ @Mock
+ private GameRoomService gameRoomService;
+
+ @Mock
+ private SimpMessagingTemplate messagingTemplate;
+
+ @InjectMocks
+ private MatchmakingService matchmakingService;
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void shouldAddToQueueAndFindMatch() {
+ // Arrange
+ long timestamp = System.currentTimeMillis();
+ MatchRequest player1 = new MatchRequest("player1", "Player 1", "avatar1", TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY", timestamp);
+ MatchRequest player2 = new MatchRequest("player2", "Player 2", "avatar2", TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY", timestamp);
+
+ // Act
+ matchmakingService.addToQueue(player1);
+ matchmakingService.addToQueue(player2);
+
+ // Assert
+ String queueKey = matchmakingService.generateQueueKey(TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY");
+ Queue queue = matchmakingService.getMatchmakingQueues().get(queueKey);
+ assertNotNull(queue);
+ assertEquals(0, queue.size()); // 队列应为空,因为匹配成功
+ verify(messagingTemplate, times(2)).convertAndSend(anyString(), any(GameMessage.class));
+ }
+
+ @Test
+ void shouldAddToQueueAndWaitForMatch() {
+ // Arrange
+ long timestamp = System.currentTimeMillis();
+ MatchRequest player1 = new MatchRequest("player1", "Player 1", "avatar1", TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY", timestamp);
+
+ // Act
+ matchmakingService.addToQueue(player1);
+
+ // Assert
+ String queueKey = matchmakingService.generateQueueKey(TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY");
+ Queue queue = matchmakingService.getMatchmakingQueues().get(queueKey);
+ assertNotNull(queue);
+ assertEquals(1, queue.size()); // 队列中应有一个玩家等待匹配
+ verify(messagingTemplate, never()).convertAndSend(anyString(), any(GameMessage.class));
+ }
+
+ @Test
+ void shouldRemoveFromQueue() {
+ // Arrange
+ long timestamp = System.currentTimeMillis();
+ MatchRequest player1 = new MatchRequest("player1", "Player 1", "avatar1", TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY", timestamp);
+ matchmakingService.addToQueue(player1);
+
+ // Act
+ matchmakingService.removeFromQueue(player1);
+
+ // Assert
+ String queueKey = matchmakingService.generateQueueKey(TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY");
+ Queue queue = matchmakingService.getMatchmakingQueues().get(queueKey);
+ assertNull(queue); // 队列应为空,并从 map 中移除
+ verify(messagingTemplate, times(1)).convertAndSend(eq("/queue/matchmaking/player1"), any(GameMessage.class));
+ }
+
+ @Test
+ void shouldCreateMatchedRoom() {
+ // Arrange
+ long timestamp = System.currentTimeMillis();
+ MatchRequest player1 = new MatchRequest("player1", "Player 1", "avatar1", TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY", timestamp);
+ MatchRequest player2 = new MatchRequest("player2", "Player 2", "avatar2", TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY", timestamp);
+
+ GameRoom mockRoom = new GameRoom();
+ mockRoom.setRoomId(UUID.randomUUID().toString());
+ when(gameRoomService.createRoom(anyString(), eq(TextLanguage.ENGLISH), eq(TextCategory.DAILY_CHAT), eq("EASY"))).thenReturn(mockRoom);
+
+ // Act
+ matchmakingService.createMatchedRoom(player1, player2);
+
+ // Assert
+ verify(gameRoomService, times(1)).createRoom(anyString(), eq(TextLanguage.ENGLISH), eq(TextCategory.DAILY_CHAT), eq("EASY"));
+ verify(gameRoomService, times(1)).addPlayer(mockRoom, "player1", "Player 1");
+ verify(gameRoomService, times(1)).addPlayer(mockRoom, "player2", "Player 2");
+ verify(messagingTemplate, times(2)).convertAndSend(anyString(), any(GameMessage.class));
+ }
+
+ @Test
+ void shouldGenerateQueueKey() {
+ // Arrange
+ TextLanguage language = TextLanguage.ENGLISH;
+ TextCategory category = TextCategory.DAILY_CHAT;
+ String difficulty = "EASY";
+
+ // Act
+ String queueKey = matchmakingService.generateQueueKey(language, category, difficulty);
+
+ // Assert
+ assertEquals("ENGLISH_DAILY_CHAT_EASY", queueKey);
+ }
+
+ @Test
+ void shouldFindMatchInQueue() {
+ // Arrange
+ long timestamp = System.currentTimeMillis();
+ MatchRequest player1 = new MatchRequest("player1", "Player 1", "avatar1", TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY", timestamp);
+ MatchRequest player2 = new MatchRequest("player2", "Player 2", "avatar2", TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY", timestamp);
+ Queue queue = new ConcurrentLinkedQueue<>();
+ queue.offer(player1);
+
+ // Act
+ MatchRequest match = matchmakingService.findMatch(queue, player2);
+
+ // Assert
+ assertNotNull(match);
+ assertEquals("player1", match.getPlayerId());
+ assertTrue(queue.isEmpty());
+ }
+
+ @Test
+ void shouldNotFindMatchInEmptyQueue() {
+ // Arrange
+ long timestamp = System.currentTimeMillis();
+ MatchRequest player2 = new MatchRequest("player2", "Player 2", "avatar2", TextLanguage.ENGLISH, TextCategory.DAILY_CHAT, "EASY", timestamp);
+ Queue queue = new ConcurrentLinkedQueue<>();
+
+ // Act
+ MatchRequest match = matchmakingService.findMatch(queue, player2);
+
+ // Assert
+ assertNull(match);
+ assertTrue(queue.isEmpty());
+ }
+}
diff --git a/src/test/java/com/example/demo/service/PlayerProfileServiceTest.java b/src/test/java/com/example/demo/service/PlayerProfileServiceTest.java
new file mode 100644
index 0000000..6d35042
--- /dev/null
+++ b/src/test/java/com/example/demo/service/PlayerProfileServiceTest.java
@@ -0,0 +1,174 @@
+package com.example.demo.service;
+
+import com.example.demo.entity.PlayerProfile;
+import com.example.demo.repository.PlayerProfileRepository;
+import com.example.demo.service.user.PlayerProfileService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+class PlayerProfileServiceTest {
+
+ @Mock
+ private PlayerProfileRepository playerProfileRepository;
+
+ @InjectMocks
+ private PlayerProfileService playerProfileService;
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void shouldUpdatePlayerStatsForVictory() {
+ // Arrange
+ Long playerId = 1L;
+ PlayerProfile profile = new PlayerProfile();
+ profile.setId(playerId);
+ profile.setMatchesPlayed(5);
+ profile.setWin(3);
+ profile.setSpeed(50.0);
+ profile.setWinRate(60.0);
+ profile.setAccuracyRate(80.0);
+ profile.setRankScore(1200);
+ profile.setUserLevel("不屈白银");
+
+ when(playerProfileRepository.findById(playerId)).thenReturn(Optional.of(profile));
+ when(playerProfileRepository.save(any(PlayerProfile.class))).thenReturn(profile);
+
+ // Act
+ PlayerProfile updatedProfile = playerProfileService.updatePlayerStats(playerId, 60.0, true, 85.0);
+
+ // Assert
+ assertNotNull(updatedProfile);
+ assertEquals(6, updatedProfile.getMatchesPlayed()); // 比赛场次 +1
+ assertEquals(4, updatedProfile.getWin()); // 胜场 +1
+ assertEquals(55.0, updatedProfile.getSpeed()); // 平均速度更新
+ assertEquals(66.67, updatedProfile.getWinRate(), 0.01); // 胜率更新
+ assertEquals(80.83, updatedProfile.getAccuracyRate(), 0.01); // 准确率更新
+ assertEquals(1300, updatedProfile.getRankScore()); // 段位积分 +100
+ assertEquals("不屈白银", updatedProfile.getUserLevel()); // 等级不变
+ verify(playerProfileRepository, times(1)).findById(playerId);
+ verify(playerProfileRepository, times(1)).save(profile);
+ }
+
+ @Test
+ void shouldUpdatePlayerStatsForDefeat() {
+ // Arrange
+ Long playerId = 1L;
+ PlayerProfile profile = new PlayerProfile();
+ profile.setId(playerId);
+ profile.setMatchesPlayed(5);
+ profile.setWin(3);
+ profile.setSpeed(50.0);
+ profile.setWinRate(60.0);
+ profile.setAccuracyRate(80.0);
+ profile.setRankScore(1200);
+ profile.setUserLevel("不屈白银");
+
+ when(playerProfileRepository.findById(playerId)).thenReturn(Optional.of(profile));
+ when(playerProfileRepository.save(any(PlayerProfile.class))).thenReturn(profile);
+
+ // Act
+ PlayerProfile updatedProfile = playerProfileService.updatePlayerStats(playerId, 60.0, false, 85.0);
+
+ // Assert
+ assertNotNull(updatedProfile);
+ assertEquals(6, updatedProfile.getMatchesPlayed()); // 比赛场次 +1
+ assertEquals(3, updatedProfile.getWin()); // 胜场不变
+ assertEquals(55.0, updatedProfile.getSpeed()); // 平均速度更新
+ assertEquals(50.0, updatedProfile.getWinRate(), 0.01); // 胜率更新
+ assertEquals(80.83, updatedProfile.getAccuracyRate(), 0.01); // 准确率更新
+ assertEquals(1150, updatedProfile.getRankScore()); // 段位积分 -50
+ assertEquals("不屈白银", updatedProfile.getUserLevel()); // 等级不变
+ verify(playerProfileRepository, times(1)).findById(playerId);
+ verify(playerProfileRepository, times(1)).save(profile);
+ }
+
+ @Test
+ void shouldUpdateUserLevelToHighest() {
+ // Arrange
+ Long playerId = 1L;
+ PlayerProfile profile = new PlayerProfile();
+ profile.setId(playerId);
+ profile.setMatchesPlayed(5);
+ profile.setWin(3);
+ profile.setSpeed(50.0);
+ profile.setWinRate(60.0);
+ profile.setAccuracyRate(80.0);
+ profile.setRankScore(4000); // 当前段位积分
+ profile.setUserLevel("超凡大师");
+
+ when(playerProfileRepository.findById(playerId)).thenReturn(Optional.of(profile));
+ when(playerProfileRepository.save(any(PlayerProfile.class))).thenReturn(profile);
+
+ // Act
+ PlayerProfile updatedProfile = playerProfileService.updatePlayerStats(playerId, 60.0, true, 85.0);
+
+ // Assert
+ assertNotNull(updatedProfile);
+ assertEquals(4100, updatedProfile.getRankScore()); // 段位积分 +100
+ assertEquals("最强王者", updatedProfile.getUserLevel()); // 等级更新
+ verify(playerProfileRepository, times(1)).findById(playerId);
+ verify(playerProfileRepository, times(1)).save(profile);
+ }
+
+ @Test
+ void shouldThrowExceptionWhenPlayerProfileNotFound() {
+ // Arrange
+ Long playerId = 1L;
+ when(playerProfileRepository.findById(playerId)).thenReturn(Optional.empty());
+
+ // Act & Assert
+ RuntimeException exception = assertThrows(RuntimeException.class, () -> {
+ playerProfileService.updatePlayerStats(playerId, 60.0, true, 85.0);
+ });
+ assertEquals("Player profile not found", exception.getMessage());
+ verify(playerProfileRepository, times(1)).findById(playerId);
+ verify(playerProfileRepository, never()).save(any(PlayerProfile.class));
+ }
+
+ @Test
+ void shouldGetPlayerProfileById() {
+ // Arrange
+ Long playerId = 1L;
+ PlayerProfile profile = new PlayerProfile();
+ profile.setId(playerId);
+ profile.setUserLevel("不屈白银");
+
+ when(playerProfileRepository.findById(playerId)).thenReturn(Optional.of(profile));
+
+ // Act
+ PlayerProfile result = playerProfileService.getPlayerProfileById(playerId);
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(playerId, result.getId());
+ assertEquals("不屈白银", result.getUserLevel());
+ verify(playerProfileRepository, times(1)).findById(playerId);
+ }
+
+ @Test
+ void shouldReturnNullWhenPlayerProfileNotFound() {
+ // Arrange
+ Long playerId = 1L;
+ when(playerProfileRepository.findById(playerId)).thenReturn(Optional.empty());
+
+ // Act
+ PlayerProfile result = playerProfileService.getPlayerProfileById(playerId);
+
+ // Assert
+ assertNull(result);
+ verify(playerProfileRepository, times(1)).findById(playerId);
+ }
+}
+
diff --git a/src/test/java/com/example/demo/service/UserServiceTest.java b/src/test/java/com/example/demo/service/UserServiceTest.java
new file mode 100644
index 0000000..4bd2397
--- /dev/null
+++ b/src/test/java/com/example/demo/service/UserServiceTest.java
@@ -0,0 +1,287 @@
+package com.example.demo.service;
+
+import com.example.demo.entity.User;
+import com.example.demo.entity.PlayerProfile;
+import com.example.demo.exception.InvalidPasswordException;
+import com.example.demo.exception.PasswordMismatchException;
+import com.example.demo.exception.ResourceNotFoundException;
+import com.example.demo.model.dto.UpdatePasswordDTO;
+import com.example.demo.model.dto.UpdateUserDTO;
+import com.example.demo.repository.UserRepository;
+import com.example.demo.repository.PlayerProfileRepository;
+import com.example.demo.service.user.UserService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+class UserServiceTest {
+
+ @Mock
+ private UserRepository userRepository;
+
+ @Mock
+ private PasswordEncoder passwordEncoder;
+
+ @Mock
+ private PlayerProfileRepository playerProfileRepository;
+
+ @InjectMocks
+ private UserService userService;
+
+ @BeforeEach
+ void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void shouldGetAllUsers() {
+ // Arrange
+ User user1 = new User(1L, "user1", "user1@example.com", "password", "avatar1");
+ User user2 = new User(2L, "user2", "user2@example.com", "password", "avatar2");
+ when(userRepository.findAll()).thenReturn(Arrays.asList(user1, user2));
+
+ // Act
+ List users = userService.getAllUsers();
+
+ // Assert
+ assertNotNull(users);
+ assertEquals(2, users.size());
+ verify(userRepository, times(1)).findAll();
+ }
+
+ @Test
+ void shouldGetUserById() {
+ // Arrange
+ Long userId = 1L;
+ User user = new User(userId, "user1", "user1@example.com", "password", "avatar1");
+ when(userRepository.findById(userId)).thenReturn(Optional.of(user));
+
+ // Act
+ Optional result = userService.getUserById(userId);
+
+ // Assert
+ assertTrue(result.isPresent());
+ assertEquals(userId, result.get().getId());
+ verify(userRepository, times(1)).findById(userId);
+ }
+
+ @Test
+ void shouldReturnEmptyWhenUserNotFoundById() {
+ // Arrange
+ Long userId = 1L;
+ when(userRepository.findById(userId)).thenReturn(Optional.empty());
+
+ // Act
+ Optional result = userService.getUserById(userId);
+
+ // Assert
+ assertFalse(result.isPresent());
+ verify(userRepository, times(1)).findById(userId);
+ }
+
+ @Test
+ void shouldGetUserByUsername() {
+ // Arrange
+ String username = "user1";
+ User user = new User(1L, username, "user1@example.com", "password", "avatar1");
+ when(userRepository.findByUsername(username)).thenReturn(Optional.of(user));
+
+ // Act
+ Optional result = userService.getUserByUsername(username);
+
+ // Assert
+ assertTrue(result.isPresent());
+ assertEquals(username, result.get().getUsername());
+ verify(userRepository, times(1)).findByUsername(username);
+ }
+
+ @Test
+ void shouldReturnEmptyWhenUserNotFoundByUsername() {
+ // Arrange
+ String username = "nonexistent";
+ when(userRepository.findByUsername(username)).thenReturn(Optional.empty());
+
+ // Act
+ Optional result = userService.getUserByUsername(username);
+
+ // Assert
+ assertFalse(result.isPresent());
+ verify(userRepository, times(1)).findByUsername(username);
+ }
+
+ @Test
+ void shouldSaveUser() {
+ // Arrange
+ User user = new User(1L, "user1", "user1@example.com", "password", "avatar1");
+ when(passwordEncoder.encode(user.getPassword())).thenReturn("encodedPassword");
+ when(userRepository.save(user)).thenReturn(user);
+
+ // Act
+ User savedUser = userService.saveUser(user);
+
+ // Assert
+ assertNotNull(savedUser);
+ assertEquals("encodedPassword", savedUser.getPassword());
+ verify(passwordEncoder, times(1)).encode(user.getPassword());
+ verify(userRepository, times(1)).save(user);
+ verify(playerProfileRepository, times(1)).save(any(PlayerProfile.class));
+ }
+
+ @Test
+ void shouldDeleteUser() {
+ // Arrange
+ Long userId = 1L;
+
+ // Act
+ userService.deleteUser(userId);
+
+ // Assert
+ verify(userRepository, times(1)).deleteById(userId);
+ }
+
+ @Test
+ void shouldCheckIfUsernameExists() {
+ // Arrange
+ String username = "user1";
+ when(userRepository.existsByUsername(username)).thenReturn(true);
+
+ // Act
+ boolean exists = userService.existsByUsername(username);
+
+ // Assert
+ assertTrue(exists);
+ verify(userRepository, times(1)).existsByUsername(username);
+ }
+
+ @Test
+ void shouldCheckIfEmailExists() {
+ // Arrange
+ String email = "user1@example.com";
+ when(userRepository.existsByEmail(email)).thenReturn(true);
+
+ // Act
+ boolean exists = userService.existsByEmail(email);
+
+ // Assert
+ assertTrue(exists);
+ verify(userRepository, times(1)).existsByEmail(email);
+ }
+
+ @Test
+ void shouldUpdateUserById() {
+ // Arrange
+ Long userId = 1L;
+ UpdateUserDTO updateUserDTO = new UpdateUserDTO("newUser", "newEmail@example.com", "newAvatar");
+ User user = new User(userId, "oldUser", "oldEmail@example.com", "password", "oldAvatar");
+ when(userRepository.findById(userId)).thenReturn(Optional.of(user));
+ when(userRepository.save(user)).thenReturn(user);
+
+ // Act
+ User updatedUser = userService.updateUserById(userId, updateUserDTO);
+
+ // Assert
+ assertNotNull(updatedUser);
+ assertEquals("newUser", updatedUser.getUsername());
+ assertEquals("newEmail@example.com", updatedUser.getEmail());
+ assertEquals("newAvatar", updatedUser.getImgSrc());
+ verify(userRepository, times(1)).findById(userId);
+ verify(userRepository, times(1)).save(user);
+ }
+
+ @Test
+ void shouldThrowExceptionWhenUpdatingNonExistentUser() {
+ // Arrange
+ Long userId = 1L;
+ UpdateUserDTO updateUserDTO = new UpdateUserDTO("newUser", "newEmail@example.com", "newAvatar");
+ when(userRepository.findById(userId)).thenReturn(Optional.empty());
+
+ // Act & Assert
+ ResourceNotFoundException exception = assertThrows(ResourceNotFoundException.class, () -> {
+ userService.updateUserById(userId, updateUserDTO);
+ });
+ assertEquals("User not found with id " + userId, exception.getMessage());
+ verify(userRepository, times(1)).findById(userId);
+ verify(userRepository, never()).save(any(User.class));
+ }
+
+ @Test
+ void shouldUpdatePassword() {
+ // Arrange
+ Long userId = 1L;
+ UpdatePasswordDTO updatePasswordDTO = new UpdatePasswordDTO();
+ updatePasswordDTO.setOldPassword("oldPassword");
+ updatePasswordDTO.setNewPassword("newPassword");
+ updatePasswordDTO.setConfirmPassword("newPassword");
+ User user = new User(userId, "user1", "user1@example.com", "encodedOldPassword", "avatar1");
+ when(userRepository.findById(userId)).thenReturn(Optional.of(user));
+ when(passwordEncoder.matches("oldPassword", "encodedOldPassword")).thenReturn(true);
+ when(passwordEncoder.encode("newPassword")).thenReturn("encodedNewPassword");
+
+ // Act
+ userService.updatePassword(userId, updatePasswordDTO);
+
+ // Assert
+ assertEquals("encodedNewPassword", user.getPassword());
+ verify(userRepository, times(1)).findById(userId);
+ verify(passwordEncoder, times(1)).matches("oldPassword", "encodedOldPassword");
+ verify(passwordEncoder, times(1)).encode("newPassword");
+ verify(userRepository, times(1)).save(user);
+ }
+
+ @Test
+ void shouldThrowExceptionWhenOldPasswordIsIncorrect() {
+ // Arrange
+ Long userId = 1L;
+ UpdatePasswordDTO updatePasswordDTO = new UpdatePasswordDTO();
+ updatePasswordDTO.setOldPassword("wrongPassword");
+ updatePasswordDTO.setNewPassword("newPassword");
+ updatePasswordDTO.setConfirmPassword("newPassword");
+ User user = new User(userId, "user1", "user1@example.com", "encodedOldPassword", "avatar1");
+ when(userRepository.findById(userId)).thenReturn(Optional.of(user));
+ when(passwordEncoder.matches("wrongPassword", "encodedOldPassword")).thenReturn(false);
+
+ // Act & Assert
+ InvalidPasswordException exception = assertThrows(InvalidPasswordException.class, () -> {
+ userService.updatePassword(userId, updatePasswordDTO);
+ });
+ assertEquals("旧密码不正确", exception.getMessage());
+ verify(userRepository, times(1)).findById(userId);
+ verify(passwordEncoder, times(1)).matches("wrongPassword", "encodedOldPassword");
+ verify(passwordEncoder, never()).encode(anyString());
+ verify(userRepository, never()).save(any(User.class));
+ }
+
+ @Test
+ void shouldThrowExceptionWhenNewPasswordsDoNotMatch() {
+ // Arrange
+ Long userId = 1L;
+ UpdatePasswordDTO updatePasswordDTO = new UpdatePasswordDTO();
+ updatePasswordDTO.setOldPassword("oldPassword");
+ updatePasswordDTO.setNewPassword("newPassword");
+ updatePasswordDTO.setConfirmPassword("differentPassword");
+ User user = new User(userId, "user1", "user1@example.com", "encodedOldPassword", "avatar1");
+ when(userRepository.findById(userId)).thenReturn(Optional.of(user));
+ when(passwordEncoder.matches("oldPassword", "encodedOldPassword")).thenReturn(true);
+
+ // Act & Assert
+ PasswordMismatchException exception = assertThrows(PasswordMismatchException.class, () -> {
+ userService.updatePassword(userId, updatePasswordDTO);
+ });
+ assertEquals("两次新密码不一致", exception.getMessage());
+ verify(userRepository, times(1)).findById(userId);
+ verify(passwordEncoder, times(1)).matches("oldPassword", "encodedOldPassword");
+ verify(passwordEncoder, never()).encode(anyString());
+ verify(userRepository, never()).save(any(User.class));
+ }
+}