Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

회원 탈퇴 api 추가 #284

Merged
merged 9 commits into from
Aug 11, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,12 @@ public ReadUserDto readById(final Long userId) {

return ReadUserDto.from(user);
}

@Transactional
public void deleteById(final Long userId) {
final User user = userRepository.findByIdAndDeletedIsFalse(userId)
.orElseThrow(() -> new UserNotFoundException("사용자 정보를 사용할 수 없습니다."));

user.withdrawal();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
@Table(name = "users")
public class User extends BaseTimeEntity {

private static final boolean DELETED_STATUS = true;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand All @@ -36,6 +38,9 @@ public class User extends BaseTimeEntity {
@Column(unique = true)
private String oauthId;

@Column(name = "is_deleted")
private boolean deleted = false;

@Builder
private User(
final String name,
Expand All @@ -48,4 +53,8 @@ private User(
this.reliability = reliability;
this.oauthId = oauthId;
}

public void withdrawal() {
this.deleted = DELETED_STATUS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@
public interface JpaUserRepository extends JpaRepository<User, Long> {

Optional<User> findByOauthId(final String oauthId);

Optional<User> findByIdAndDeletedIsFalse(final Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.ddang.ddang.user.presentation.dto.ReadUserResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -25,4 +26,12 @@ public ResponseEntity<ReadUserResponse> readById(@AuthenticateUser final Authent

return ResponseEntity.ok(response);
}

@DeleteMapping("/withdrawal")
public ResponseEntity<Void> delete(@AuthenticateUser final AuthenticationUserInfo userInfo) {
userService.deleteById(userInfo.userId());

return ResponseEntity.noContent()
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package com.ddang.ddang.user.application;

import static org.assertj.core.api.Assertions.assertThatThrownBy;

import com.ddang.ddang.configuration.IsolateDatabase;
import com.ddang.ddang.user.application.dto.ReadUserDto;
import com.ddang.ddang.user.application.exception.UserNotFoundException;
import com.ddang.ddang.user.domain.User;
import com.ddang.ddang.user.infrastructure.persistence.JpaUserRepository;
import java.util.Optional;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import static org.assertj.core.api.Assertions.assertThatThrownBy;

@IsolateDatabase
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
Expand Down Expand Up @@ -57,4 +58,58 @@ class UserServiceTest {
.isInstanceOf(UserNotFoundException.class)
.hasMessage("사용자 정보를 사용할 수 없습니다.");
}

@Test
void 회원_탈퇴한다() {
// given
final User user = User.builder()
.name("사용자")
.profileImage("profile.png")
.reliability(4.7d)
.oauthId("12345")
.build();

userRepository.save(user);

// when
userService.deleteById(user.getId());

// then
final Optional<User> actual = userRepository.findById(user.getId());

SoftAssertions.assertSoftly(softAssertions -> {
softAssertions.assertThat(actual).isPresent();
softAssertions.assertThat(actual.get().isDeleted()).isTrue();
});
}

@Test
void 회원_탈퇴할때_이미_탈퇴한_회원이면_예외가_발생한다() {
// given
final User user = User.builder()
.name("사용자")
.profileImage("profile.png")
.reliability(4.7d)
.oauthId("12345")
.build();

user.withdrawal();
userRepository.save(user);

// when & then
assertThatThrownBy(() -> userService.deleteById(user.getId()))
.isInstanceOf(UserNotFoundException.class)
.hasMessage("사용자 정보를 사용할 수 없습니다.");
}

@Test
void 회원_탈퇴할때_존재하지_않는_사용자_정보_조회시_예외를_반환한다() {
// given
final Long invalidUserId = -999L;

// when & then
assertThatThrownBy(() -> userService.deleteById(invalidUserId))
.isInstanceOf(UserNotFoundException.class)
.hasMessage("사용자 정보를 사용할 수 없습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.ddang.ddang.user.domain;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
class UserTest {

@Test
void 회원_탈퇴한다() {
// given
final User user = User.builder()
.name("kakao12345")
.build();

// when
user.withdrawal();

// then
assertThat(user.isDeleted()).isTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class JpaUserRepositoryTest {
JpaUserRepository userRepository;

@Test
void 존재하는_oauthId를_전달하면_해당_회원을_Optional로_반환한다() {
void 존재하는_oauthId를_전달하면_해당_회원을_Optional로_감싸_반환한다() {
// given
final User user = User.builder()
.name("회원")
Expand All @@ -52,7 +52,7 @@ class JpaUserRepositoryTest {
}

@Test
void 존재하지_않는_oauthId를_전달하면_해당_회원을_Optional로_반환한다() {
void 존재하지_않는_oauthId를_전달하면_해당_회원을_빈_Optional로_반환한다() {
// given
final String invalidOauthId = "invalidOauthId";

Expand All @@ -62,4 +62,61 @@ class JpaUserRepositoryTest {
// then
assertThat(actual).isEmpty();
}

@Test
void 회원가입과_탈퇴하지_않은_회원_id를_전달하면_해당_회원을_Optional로_감싸_반환한다() {
// given
final User user = User.builder()
.name("회원")
.profileImage("profile.png")
.reliability(4.7d)
.oauthId("12345")
.build();

userRepository.save(user);

em.flush();
em.clear();

// when
final Optional<User> actual = userRepository.findByIdAndDeletedIsFalse(user.getId());

// then
assertThat(actual).isPresent();
}

@Test
void 회원탈퇴한_회원의_id를_전달하면_빈_Optional을_반환한다() {
// given
final User user = User.builder()
.name("회원")
.profileImage("profile.png")
.reliability(4.7d)
.oauthId("12345")
.build();

user.withdrawal();
userRepository.save(user);

em.flush();
em.clear();

// when
final Optional<User> actual = userRepository.findByIdAndDeletedIsFalse(user.getId());

// then
assertThat(actual).isEmpty();
}

@Test
void 없는_id를_전달하면_빈_Optional을_반환한다() {
// given
final Long invalidUserId = -999L;

// when
final Optional<User> actual = userRepository.findByIdAndDeletedIsFalse(invalidUserId);

// then
assertThat(actual).isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willDoNothing;
import static org.mockito.Mockito.mock;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
Expand Down Expand Up @@ -128,4 +130,20 @@ void setUp() {
jsonPath("$.message", is(userNotFoundException.getMessage()))
);
}

@Test
void 회원_탈퇴한다() throws Exception {
// given
final PrivateClaims privateClaims = new PrivateClaims(1L);

given(mockTokenDecoder.decode(eq(TokenType.ACCESS), anyString())).willReturn(Optional.of(privateClaims));
willDoNothing().given(userService).deleteById(anyLong());

// when & then
mockMvc.perform(delete("/users/withdrawal")
.header(HttpHeaders.AUTHORIZATION, "Bearer accessToken")
).andExpectAll(
status().isNoContent()
);
}
}