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)); + } +}