diff --git a/build.gradle b/build.gradle index 5dd477b..cbfa363 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-websocket' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' - developmentOnly 'org.springframework.boot:spring-boot-docker-compose' +// developmentOnly 'org.springframework.boot:spring-boot-docker-compose' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/index.adoc b/index.adoc new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/com/websocket/demo/controller/UserController.java b/src/main/java/com/websocket/demo/controller/UserController.java new file mode 100644 index 0000000..5802bae --- /dev/null +++ b/src/main/java/com/websocket/demo/controller/UserController.java @@ -0,0 +1,59 @@ +package com.websocket.demo.controller; + +import com.websocket.demo.request.AddFriendRequest; +import com.websocket.demo.request.CreateUserRequest; +import com.websocket.demo.request.LoginRequest; +import com.websocket.demo.service.UserService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +@Controller +@RequestMapping("/user") +@RequiredArgsConstructor +public class UserController { + + private final UserService userService; + + @PostMapping("/login") + public String loginUser(@ModelAttribute LoginRequest request, HttpServletRequest servletRequest){ + if (userService.login(request)) { + servletRequest.getSession(true).setAttribute("user", request); + return "redirect:/"; + } + return "login"; + } + + @PostMapping("/create") + public String createUser(@ModelAttribute CreateUserRequest request){ + try { + if (userService.create(request)) return "redirect:/"; + } catch (RuntimeException e){ + return "createUser"; + } + return "createUser"; + } + + @PostMapping("/friend") + public String newFriend(@ModelAttribute AddFriendRequest request, @SessionAttribute(name = "user", required = false) LoginRequest userInfo){ + if(userService.addFriend(request, userInfo.getNickname())) return "redirect:/"; + return "addFriend"; + } + + @GetMapping("/create") + public String createUserPage(){ + return "createUser"; + } + + @GetMapping("/login") + public String loginPage() { + return "login"; + } + + @GetMapping("/friend") + public String addFriendPage() { + return "addFriend"; + } +} + diff --git a/src/main/java/com/websocket/demo/domain/Friend.java b/src/main/java/com/websocket/demo/domain/Friend.java new file mode 100644 index 0000000..1f215c9 --- /dev/null +++ b/src/main/java/com/websocket/demo/domain/Friend.java @@ -0,0 +1,47 @@ +package com.websocket.demo.domain; + +import com.websocket.demo.response.FriendInfo; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Table(name = "FRIENDS") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Friend { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + @Column + private String userNickname; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "friend_nickname") + private User friend; + + @Builder + private Friend(String userNickname, User friend) { + this.userNickname = userNickname; + this.friend = friend; + } + + public String getUserNickname() { + return userNickname; + } + + public User getFriend() { + return this.friend; + } + + public FriendInfo toInfo() { + return new FriendInfo(getFriend().getNickname()); + } + + @Override + public String toString() { + return "Friend{" + + "id=" + id + + ", userNickname='" + userNickname + '\'' + + ", friend=" + friend.getNickname() + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/websocket/demo/domain/User.java b/src/main/java/com/websocket/demo/domain/User.java new file mode 100644 index 0000000..46e436d --- /dev/null +++ b/src/main/java/com/websocket/demo/domain/User.java @@ -0,0 +1,72 @@ +package com.websocket.demo.domain; + +import com.websocket.demo.request.LoginRequest; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Entity +@Table(name = "USERS") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class User { + + @Id + @Column(columnDefinition = "varchar(20)") + private String nickname; + @Column(columnDefinition = "varchar(20)") + private String password; + @OneToMany(mappedBy = "friend", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) + private List friends = new ArrayList<>(); + + @Builder + public User(String nickname, String password) { + this.nickname = nickname; + this.password = password; + } + + public void addFriends(User... friends) { + for (var friend : friends) { + this.friends.add(Friend.builder() + .userNickname(getNickname()) + .friend(friend) + .build() + ); + } + } + + public boolean match(LoginRequest request) { + return request.getNickname().equals(nickname) && + request.getPassword().equals(password); + } + + + public String getNickname() { + return nickname; + } + + public String getPassword() { + return password; + } + + public List getFriends() { + return friends; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return Objects.equals(nickname, user.nickname); + } + + @Override + public int hashCode() { + return Objects.hash(nickname); + } +} diff --git a/src/main/java/com/websocket/demo/repository/FriendRepository.java b/src/main/java/com/websocket/demo/repository/FriendRepository.java new file mode 100644 index 0000000..c172a4e --- /dev/null +++ b/src/main/java/com/websocket/demo/repository/FriendRepository.java @@ -0,0 +1,20 @@ +package com.websocket.demo.repository; + +import com.websocket.demo.domain.Friend; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface FriendRepository extends JpaRepository { + + @EntityGraph(attributePaths = "friend") + List findByUserNickname(String nickname); + + @Modifying(flushAutomatically = true) + @Query("delete from Friend f where f.userNickname=:userNickname and f.friend.nickname=:friendNickname") + void deleteByUserNicknameAndFriendNickname(@Param("userNickname") String userNickname,@Param("friendNickname") String friendNickname); +} \ No newline at end of file diff --git a/src/main/java/com/websocket/demo/repository/UserRepository.java b/src/main/java/com/websocket/demo/repository/UserRepository.java new file mode 100644 index 0000000..1904878 --- /dev/null +++ b/src/main/java/com/websocket/demo/repository/UserRepository.java @@ -0,0 +1,10 @@ +package com.websocket.demo.repository; + +import com.websocket.demo.domain.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { + User findByNickname(String nickname); + + boolean existsByNickname(String nickname); +} diff --git a/src/main/java/com/websocket/demo/request/AddFriendRequest.java b/src/main/java/com/websocket/demo/request/AddFriendRequest.java new file mode 100644 index 0000000..e821ad7 --- /dev/null +++ b/src/main/java/com/websocket/demo/request/AddFriendRequest.java @@ -0,0 +1,8 @@ +package com.websocket.demo.request; + +import lombok.Data; + +@Data +public class AddFriendRequest { + private String nickname; +} diff --git a/src/main/java/com/websocket/demo/request/CreateUserRequest.java b/src/main/java/com/websocket/demo/request/CreateUserRequest.java new file mode 100644 index 0000000..366caa5 --- /dev/null +++ b/src/main/java/com/websocket/demo/request/CreateUserRequest.java @@ -0,0 +1,10 @@ +package com.websocket.demo.request; + +import lombok.Data; + +@Data +public class CreateUserRequest { + + private String nickname; + private String password; +} diff --git a/src/main/java/com/websocket/demo/request/LoginRequest.java b/src/main/java/com/websocket/demo/request/LoginRequest.java new file mode 100644 index 0000000..ef9f622 --- /dev/null +++ b/src/main/java/com/websocket/demo/request/LoginRequest.java @@ -0,0 +1,9 @@ +package com.websocket.demo.request; + +import lombok.Data; + +@Data +public class LoginRequest { + private String nickname; + private String password; +} diff --git a/src/main/java/com/websocket/demo/response/FriendInfo.java b/src/main/java/com/websocket/demo/response/FriendInfo.java new file mode 100644 index 0000000..0f45f5c --- /dev/null +++ b/src/main/java/com/websocket/demo/response/FriendInfo.java @@ -0,0 +1,8 @@ +package com.websocket.demo.response; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class FriendInfo { + private String nickname; +} diff --git a/src/main/java/com/websocket/demo/service/UserService.java b/src/main/java/com/websocket/demo/service/UserService.java new file mode 100644 index 0000000..4cff889 --- /dev/null +++ b/src/main/java/com/websocket/demo/service/UserService.java @@ -0,0 +1,57 @@ +package com.websocket.demo.service; + +import com.websocket.demo.domain.Friend; +import com.websocket.demo.domain.User; +import com.websocket.demo.repository.FriendRepository; +import com.websocket.demo.repository.UserRepository; +import com.websocket.demo.request.AddFriendRequest; +import com.websocket.demo.request.CreateUserRequest; +import com.websocket.demo.request.LoginRequest; +import com.websocket.demo.response.FriendInfo; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; + +import java.util.List; + +@RequiredArgsConstructor +@Transactional +@Service +public class UserService { + + private final UserRepository userRepository; + private final FriendRepository friendRepository; + + public boolean login(LoginRequest request) { + User findUser = userRepository.findByNickname(request.getNickname()); + return findUser != null && findUser.match(request); + } + + public boolean create(CreateUserRequest request) { + if (userRepository.existsByNickname(request.getNickname())) return false; + userRepository.save(User.builder() + .nickname(request.getNickname()) + .password(request.getPassword()) + .build()); + return true; + } + + public boolean addFriend(AddFriendRequest request, String userNickname) { + User user = userRepository.findByNickname(userNickname); + User friend = userRepository.findByNickname(request.getNickname()); + if (friend == null || user == null) return false; + user.addFriends(friend); + return true; + } + + public List friendList(String nickname) { + User user = userRepository.findByNickname(nickname); + List byFieldsUserId = friendRepository.findByUserNickname(user.getNickname()); + return byFieldsUserId.stream().map(Friend::toInfo).toList(); + } + + public void removeFriendByNickname(String userNickname, String friendNickname) { + friendRepository.deleteByUserNicknameAndFriendNickname(userNickname, friendNickname); + } +} diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 0000000..ea0a24a --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,11 @@ +spring: + jpa: + show-sql: true + properties: + hibernate: + format_sql: true + + +#logging: +# level: +# org.hibernate.SQL: DEBUG \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 8b13789..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..19573e6 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,7 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb + h2: + console: + enabled: true + path: /db \ No newline at end of file diff --git a/src/main/resources/templates/addFriend.html b/src/main/resources/templates/addFriend.html new file mode 100644 index 0000000..a3be3e0 --- /dev/null +++ b/src/main/resources/templates/addFriend.html @@ -0,0 +1,16 @@ + + + + + 친구 추가 + + +
+

친구 추가

+
+
+

닉네임

+

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/createUser.html b/src/main/resources/templates/createUser.html new file mode 100644 index 0000000..5d096b3 --- /dev/null +++ b/src/main/resources/templates/createUser.html @@ -0,0 +1,17 @@ + + + + + 회원가입 페이지 + + +
+

회원가입 페이지

+
+
+

닉네임

+

비밀번호

+

+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..d04e03a --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,17 @@ + + + + + 로그인 페이지 + + +
+

로그인

+
+
+

닉네임

+

비밀번호

+

+
+ + \ No newline at end of file diff --git a/src/test/java/com/websocket/demo/SpringTest.java b/src/test/java/com/websocket/demo/SpringTest.java new file mode 100644 index 0000000..6f58196 --- /dev/null +++ b/src/test/java/com/websocket/demo/SpringTest.java @@ -0,0 +1,10 @@ +package com.websocket.demo; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles("test") +public class SpringTest { + +} diff --git a/src/test/java/com/websocket/demo/controller/RestDocs.java b/src/test/java/com/websocket/demo/controller/RestDocs.java new file mode 100644 index 0000000..ea1e3f9 --- /dev/null +++ b/src/test/java/com/websocket/demo/controller/RestDocs.java @@ -0,0 +1,26 @@ +package com.websocket.demo.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.websocket.demo.SpringTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; + +@ExtendWith({RestDocumentationExtension.class}) +public abstract class RestDocs extends SpringTest { + protected MockMvc mockMvc; + protected ObjectMapper mapper = new ObjectMapper(); + + @BeforeEach + void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) { + this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) + .apply(documentationConfiguration(restDocumentation)) + .build(); + } +} diff --git a/src/test/java/com/websocket/demo/controller/UserControllerTest.java b/src/test/java/com/websocket/demo/controller/UserControllerTest.java new file mode 100644 index 0000000..b4f7fa6 --- /dev/null +++ b/src/test/java/com/websocket/demo/controller/UserControllerTest.java @@ -0,0 +1,195 @@ +package com.websocket.demo.controller; + + +import com.websocket.demo.request.LoginRequest; +import com.websocket.demo.service.UserService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.dao.DataIntegrityViolationException; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.request.RequestDocumentation.formParameters; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class UserControllerTest extends RestDocs { + + @MockBean + UserService userService; + + @DisplayName("회원가입 페이지") + @Test + public void createUserPage() throws Exception { + //given //when //then + mockMvc.perform(get("/user/create")) + .andExpect(status().isOk()) + .andDo(document("create-user-page")); + } + + @DisplayName("로그인 페이지") + @Test + public void loginPage() throws Exception { + //given //when //then + mockMvc.perform(get("/user/login")) + .andExpect(status().isOk()) + .andDo(document("login-page")); + } + + @DisplayName("친구 추가 페이지") + @Test + public void addFriendPage() throws Exception { + //given //when //then + mockMvc.perform(get("/user/friend")) + .andExpect(status().isOk()) + .andDo(document("add-friend-page")); + } + + @DisplayName("메인 페이지") + @Test + public void mainPage() throws Exception { + //given //when //then + mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andDo(document("main-page")); + } + + @DisplayName("유저 생성 성공") + @Test + public void createUser() throws Exception { + //given + given(userService.create(any())) + .willReturn(true); + // when //then + mockMvc.perform(post("/user/create") + .param("nickname", "신기방기") + .param("password", "1234")) + .andExpectAll( + status().is3xxRedirection(), + header().string("Location", "/") + ) + .andDo(print()) + .andDo(document("create-user-success", + formParameters( + parameterWithName("nickname").description("닉네임"), + parameterWithName("password").description("비밀번호") + ), + responseHeaders( + headerWithName("Location").description("redirect path") + ) + )); + } + + @DisplayName("유저 생성 실패") + @Test + public void createUserWhenFail() throws Exception { + //given + given(userService.create(any())) + .willThrow(RuntimeException.class); + // when //then + mockMvc.perform(post("/user/create") + .param("nickname", "신기방기") + .param("password", "1234")) + .andExpectAll( + status().isOk() + ) + .andDo(print()) + .andDo(document("create-user-fail", + formParameters( + parameterWithName("nickname").description("닉네임"), + parameterWithName("password").description("비밀번호") + ) + )); + } + + @DisplayName("로그인 처리 성공") + @Test + public void login() throws Exception { + //given + given(userService.login(any())) + .willReturn(true); + // when //then + mockMvc.perform(post("/user/login") + .param("nickname", "신기방기") + .param("password", "1234")) + .andExpectAll( + status().is3xxRedirection(), + header().string("Location", "/") + ) + .andDo(document("login-process-success", + formParameters( + parameterWithName("nickname").description("닉네임"), + parameterWithName("password").description("비밀번호") + ), + responseHeaders( + headerWithName("Location").description("redirect path") + ) + )); + } + + @DisplayName("로그인 처리 실패") + @Test + public void loginWhenFail() throws Exception { + //given + given(userService.login(any())) + .willReturn(false); + // when //then + mockMvc.perform(post("/user/login") + .param("nickname", "신기방기") + .param("password", "1234")) + .andExpectAll( + status().isOk() + ) + .andDo(document("login-process-fail", + formParameters( + parameterWithName("nickname").description("닉네임"), + parameterWithName("password").description("비밀번호") + ) + )); + } + + @DisplayName("친구 추가 성공") + @Test + public void newFriend() throws Exception { + given(userService.addFriend(any(), any())).willReturn(true); + var loginInfo = new LoginRequest(); + loginInfo.setNickname("hello"); + // when //then + mockMvc.perform(post("/user/friend") + .param("nickname", "hello1234") + .sessionAttr("user", loginInfo) + ).andExpectAll( + status().is3xxRedirection(), + header().string("Location", "/") + ).andDo(print()) + .andDo(document("new-friend-success", + responseHeaders( + headerWithName("Location").description("redirect path") + ) + )); + } + + @DisplayName("친구 추가 실패") + @Test + public void newFriendWhenFail() throws Exception { + given(userService.addFriend(any(), any())).willReturn(false); + var loginInfo = new LoginRequest(); + loginInfo.setNickname("hello"); + // when //then + mockMvc.perform(post("/user/friend") + .param("nickname", "hello1234") + .sessionAttr("user", loginInfo) + ).andExpectAll( + status().isOk() + ).andDo(print()) + .andDo(document("new-friend-fail")); + } +} diff --git a/src/test/java/com/websocket/demo/domain/FriendTest.java b/src/test/java/com/websocket/demo/domain/FriendTest.java new file mode 100644 index 0000000..40de4f0 --- /dev/null +++ b/src/test/java/com/websocket/demo/domain/FriendTest.java @@ -0,0 +1,45 @@ +package com.websocket.demo.domain; + +import com.websocket.demo.response.FriendInfo; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class FriendTest { + + @DisplayName("친구 목록은 빌더를 사용해서 만들 수 있다.") + @Test + public void createByBuilder() { + //given + var user = new User("my", "1234"); + var friend = new User("fri", "1234"); + Friend friendInfo = createFriend(user, friend); + //when //then + assertThat(friendInfo.getUserNickname()).isEqualTo(user.getNickname()); + assertThat(friendInfo.getFriend()).isEqualTo(friend); + } + + @DisplayName("친구 정보를 담은 객체를 반환한다.") + @Test + public void toInfo() { + //given + var user = new User("my", "1234"); + var friend = new User("fri", "1234"); + Friend friendCol = createFriend(user, friend); + //when + FriendInfo info = friendCol.toInfo(); + //then + assertThat(info).extracting("nickname") + .isEqualTo("fri"); + } + + private Friend createFriend(User user, User friend) { + var friendInfo = Friend.builder() + .friend(friend) + .userNickname(user.getNickname()) + .build(); + return friendInfo; + } + +} \ No newline at end of file diff --git a/src/test/java/com/websocket/demo/domain/UserTest.java b/src/test/java/com/websocket/demo/domain/UserTest.java new file mode 100644 index 0000000..a9537a7 --- /dev/null +++ b/src/test/java/com/websocket/demo/domain/UserTest.java @@ -0,0 +1,58 @@ +package com.websocket.demo.domain; + +import com.websocket.demo.request.LoginRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class UserTest { + + @DisplayName("유저의 닉네임과 비밀번호를 조회할 수 있다.") + @Test + public void getNickName() { + //given //when + var nickname = "test1324"; + var password = "my password"; + var user = new User(nickname, password); + //then + assertThat(user.getNickname()).isEqualTo(nickname); + assertThat(user.getPassword()).isEqualTo(password); + } + + @DisplayName("로그인 요청 정보가 일치하면 true를 반환한다.") + @Test + public void matchLoginWhenSuccess() { + //given + var user = new User("test1234", "my password"); + var request = new LoginRequest(); + request.setNickname("test1234"); + request.setPassword("my password"); + //when //then + assertThat(user.match(request)).isTrue(); + } + + @DisplayName("로그인 요청 정보(비밀번호)가 일치하지 않으면 false를 반환한다.") + @Test + public void matchLoginWhenFailCaseThatPasswordIsNotCorrect() { + //given + var user = new User("test1324", "my password"); + var request = new LoginRequest(); + request.setNickname("test1234"); + request.setPassword("my passworddfkdfd"); + //when //then + assertThat(user.match(request)).isFalse(); + } + + @DisplayName("로그인 요청 정보(닉네임)이 일치하지 않으면 false를 반환한다.") + @Test + public void matchLoginWhenFailCaseUserNotFound() { + //given + var user = new User("test1324", "my password"); + var request = new LoginRequest(); + request.setNickname("test"); + request.setPassword("my password"); + //when //then + assertThat(user.match(request)).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/com/websocket/demo/repository/FriendRepositoryTest.java b/src/test/java/com/websocket/demo/repository/FriendRepositoryTest.java new file mode 100644 index 0000000..8422821 --- /dev/null +++ b/src/test/java/com/websocket/demo/repository/FriendRepositoryTest.java @@ -0,0 +1,61 @@ +package com.websocket.demo.repository; + +import com.websocket.demo.SpringTest; +import com.websocket.demo.domain.Friend; +import com.websocket.demo.domain.User; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class FriendRepositoryTest extends SpringTest { + + @Autowired + UserRepository userRepository; + @Autowired + FriendRepository friendRepository; + + @DisplayName("유저 닉네임으로 유저 정보를 조회한다.") + @Test + public void findByUserNickname() { + //given + User user1 = saveUser("hello1", "1234"); + User user2 = saveUser("hello2", "1234"); + user1.addFriends(user2); + userRepository.saveAndFlush(user1); + //when + List friends = friendRepository.findByUserNickname(user1.getNickname()); + //then + assertThat(friends).extracting("userNickname") + .contains(user1.getNickname()); + assertThat(friends).extracting("friend.nickname") + .contains(user2.getNickname()); + } + + @Transactional + @DisplayName("유저 닉네임과 친구 닉네임이 일치하는 친구 목록을 삭제한다.") + @Test + public void deleteByUserNicknameAndFriendNickName() { + //given + User user1 = saveUser("hello1", "1234"); + User user2 = saveUser("hello2", "1234"); + user1.addFriends(user2); + userRepository.saveAndFlush(user1); + //when + friendRepository.deleteByUserNicknameAndFriendNickname(user1.getNickname(), user2.getNickname()); + //then + assertThat(friendRepository.findByUserNickname(user1.getNickname())).hasSize(0); + } + + private User saveUser(String nickname, String password) { + return userRepository.saveAndFlush( + User.builder() + .nickname(nickname) + .password(password).build() + ); + } +} \ No newline at end of file diff --git a/src/test/java/com/websocket/demo/repository/UserRepositoryTest.java b/src/test/java/com/websocket/demo/repository/UserRepositoryTest.java new file mode 100644 index 0000000..8c416a9 --- /dev/null +++ b/src/test/java/com/websocket/demo/repository/UserRepositoryTest.java @@ -0,0 +1,76 @@ +package com.websocket.demo.repository; + +import com.websocket.demo.domain.Friend; +import com.websocket.demo.domain.User; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Transactional +class UserRepositoryTest { + + @Autowired + UserRepository userRepository; + + @DisplayName("유저가 저장된다.") + @Test + public void save() { + //given + var nickname = "test1324"; + var password = "my password"; + var user = saveUser(nickname, password); + //when + User savedUser = userRepository.saveAndFlush(user); + //then + assertThat(savedUser).isNotNull(); + } + + @DisplayName("유저의 친구 목록을 조회할 수 있다.") + @Test + public void getFriendsList() { + //given + var user = saveUser("user1", "1234"); + var friend1 = saveUser("friend1", "1234"); + var friend2 = saveUser("friend2", "1234"); + user.addFriends(friend1, friend2); + //when + List friends = user.getFriends(); + //then + assertThat(friends).extracting("friend.nickname") + .containsExactly(friend1.getNickname(), friend2.getNickname()); + } + + @DisplayName("유저 닉네임을 통해서 유저 엔티티를 조회할 수 있다.") + @Test + public void findByNickname() { + //given + User user = saveUser("testNick", "1234"); + //when + User findUser = userRepository.findByNickname("testNick"); + //then + assertThat(findUser).isNotNull() + .extracting("nickname", "password") + .containsExactly("testNick", "1234"); + } + + @DisplayName("닉네임 중복 여부를 검사한다.") + @Test + public void existsByNickname() { + //given + User user = saveUser("testNick", "1234"); + //when + boolean result = userRepository.existsByNickname(user.getNickname()); + //then + assertThat(result).isTrue(); + } + private User saveUser(String nickname, String password) { + return userRepository.saveAndFlush(new User(nickname, password)); + } +} \ No newline at end of file diff --git a/src/test/java/com/websocket/demo/service/UserServiceTest.java b/src/test/java/com/websocket/demo/service/UserServiceTest.java new file mode 100644 index 0000000..644acf4 --- /dev/null +++ b/src/test/java/com/websocket/demo/service/UserServiceTest.java @@ -0,0 +1,193 @@ +package com.websocket.demo.service; + +import com.websocket.demo.SpringTest; +import com.websocket.demo.domain.User; +import com.websocket.demo.repository.UserRepository; +import com.websocket.demo.request.AddFriendRequest; +import com.websocket.demo.request.CreateUserRequest; +import com.websocket.demo.request.LoginRequest; +import com.websocket.demo.response.FriendInfo; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class UserServiceTest extends SpringTest { + + @Autowired + UserRepository userRepository; + + @Autowired + UserService userService; + + @BeforeEach + @AfterEach + void init() { + userRepository.deleteAll(); + } + + @DisplayName("회원가입 요청이 오면") + @Nested + class WhenCreateUserRequest { + + @DisplayName("닉네임이 중복되지 않는다면 회원가입시킨다.") + @Test + public void isNotDuplicatedThenCreateUser() { + //given + var request = new CreateUserRequest(); + request.setNickname("newHello"); + request.setPassword("1234"); + //when + boolean result = userService.create(request); + //then + assertThat(result).isTrue(); + } + + @DisplayName("닉네임이 중복되면 회원가입을 시키지 않는다.") + @Test + public void isDuplicatedThenDoingNoting() { + //given + saveUser("hello", "124"); + var request = new CreateUserRequest(); + request.setNickname("hello"); + request.setPassword("1234"); + //when //then + assertThat(userService.create(request)).isFalse(); + } + } + + @Nested + @DisplayName("로그인 요청시") + class login { + @DisplayName("닉네임과 비밀번호가 일치하면 로그인 성공") + @Test + public void successLogin() { + //given + saveUser("hello", "1234"); + var request = new LoginRequest(); + request.setNickname("hello"); + request.setPassword("1234"); + //when + boolean isSuccess = userService.login(request); + //then + assertThat(isSuccess).isTrue(); + } + + @DisplayName("닉네임과 비밀번호가 일치하지 않으면 로그인 실패") + @Test + public void failLogin() { + //given + saveUser("hello", "124"); + var request = new LoginRequest(); + request.setNickname("hello"); + request.setPassword("1234"); + //when + boolean isSuccess = userService.login(request); + //then + assertThat(isSuccess).isFalse(); + } + } + + @DisplayName("친구 추가할 때") + @Nested + class WhenAddFriend { + + @BeforeEach + void init() { + saveUser("hello", "1234"); + saveUser("friend1", "1234"); + } + + @Transactional + @DisplayName("올바른 친구 닉네임이고 로그인이 되었을 때") + @Test + public void successCase() { + //given + var loginUserNickname = "hello"; + var request = new AddFriendRequest(); + request.setNickname("friend1"); + //when + boolean result = userService.addFriend(request, loginUserNickname); + //then + assertThat(result).isTrue(); + assertThat(userService.friendList(loginUserNickname)).hasSize(1); + } + + @DisplayName("친구 닉네임이 존재하지 않을때") + @Test + public void failCase1() { + //given + var loginUserNickname = "hello"; + var request = new AddFriendRequest(); + request.setNickname("friend2"); + //when + boolean result = userService.addFriend(request, loginUserNickname); + //then + assertThat(result).isFalse(); + } + + @DisplayName("로그인하지 않았을 때") + @Test + public void failCase2() { + //given + var request = new AddFriendRequest(); + request.setNickname("friend1"); + //when + boolean result = userService.addFriend(request, null); + //then + assertThat(result).isFalse(); + } + } + + @DisplayName("유저의 친구 목록을 조회할 수 있다.") + @Test + public void friendList() { + //given + User user = saveUser("hello", "1234"); + + User friend1= saveUser("friend1", "1234"); + User friend2= saveUser("friend2", "1234"); + + user.addFriends(friend1); + user.addFriends(friend2); + userRepository.saveAndFlush(user); + //when + List friends = userService.friendList(user.getNickname()); + + //then + assertThat(friends).extracting("nickname").containsExactly(friend1.getNickname(), friend2.getNickname()); + } + + @DisplayName("친구를 삭제시 친구 목록에서 제외된다.") + @Test + public void deleteFriend() { + //given + User user = saveUser("hello", "1234"); + + User friend1= saveUser("friend1", "1234"); + User friend2= saveUser("friend2", "1234"); + + user.addFriends(friend1); + user.addFriends(friend2); + userRepository.saveAndFlush(user); + //when + userService.removeFriendByNickname(user.getNickname(), friend1.getNickname()); + //then + assertThat(userService.friendList(user.getNickname())) + .extracting("nickname") + .contains(friend2.getNickname()) + .doesNotContain(friend1.getNickname()); + } + + private User saveUser(String nickname, String password) { + return userRepository.saveAndFlush( + User.builder() + .nickname(nickname) + .password(password).build() + ); + } +} \ No newline at end of file