diff --git a/pennyway-domain/build.gradle b/pennyway-domain/build.gradle index 36e6a3717..c7b559878 100644 --- a/pennyway-domain/build.gradle +++ b/pennyway-domain/build.gradle @@ -19,7 +19,10 @@ dependencies { /* Redis */ implementation 'org.springframework.boot:spring-boot-starter-data-redis' - testImplementation group: 'org.testcontainers', name: 'testcontainers', version: '1.17.2' + testImplementation "org.testcontainers:junit-jupiter:1.19.7" + testImplementation "org.testcontainers:testcontainers:1.19.7" + testImplementation "org.testcontainers:mysql:1.19.7" + testImplementation "com.redis.testcontainers:testcontainers-redis-junit:1.6.4" } def querydslDir = 'src/main/generated' @@ -38,10 +41,4 @@ tasks.withType(JavaCompile).configureEach { clean.doLast { file(querydslDir).deleteDir() -} - -configurations.configureEach { - exclude group: 'commons-logging', module: 'commons-logging' - exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' - exclude group: 'ch.qos.logback', module: 'logback-classic' } \ No newline at end of file diff --git a/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/oauth/domain/Oauth.java b/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/oauth/domain/Oauth.java index cde953be3..eec8ad267 100644 --- a/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/oauth/domain/Oauth.java +++ b/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/oauth/domain/Oauth.java @@ -9,6 +9,8 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -20,6 +22,8 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @EntityListeners(AuditingEntityListener.class) @DynamicInsert +@SQLRestriction("deleted_at IS NULL") +@SQLDelete(sql = "UPDATE oauth SET deleted_at = NOW() WHERE id = ?") public class Oauth { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/user/domain/User.java b/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/user/domain/User.java index f527796b3..7c198833a 100644 --- a/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/user/domain/User.java +++ b/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/user/domain/User.java @@ -12,6 +12,8 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; import java.time.LocalDateTime; @@ -20,6 +22,8 @@ @Table(name = "user") @NoArgsConstructor(access = AccessLevel.PROTECTED) @DynamicInsert +@SQLRestriction("deleted_at IS NULL") +@SQLDelete(sql = "UPDATE user SET deleted_at = NOW() WHERE id = ?") public class User extends DateAuditable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -64,4 +68,15 @@ public void updatePassword(String password) { this.password = password; this.passwordUpdatedAt = LocalDateTime.now(); } + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", username='" + username + '\'' + + ", name='" + name + '\'' + + ", role=" + role + + ", deletedAt=" + deletedAt + + '}'; + } } diff --git a/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/user/service/UserService.java b/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/user/service/UserService.java index 66726392e..8051472f9 100644 --- a/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/user/service/UserService.java +++ b/pennyway-domain/src/main/java/kr/co/pennyway/domain/domains/user/service/UserService.java @@ -42,4 +42,9 @@ public boolean isExistUser(Long id) { public boolean isExistUsername(String username) { return userRepository.existsByUsername(username); } + + @Transactional + public void deleteUser(User user) { + userRepository.delete(user); + } } diff --git a/pennyway-domain/src/test/java/kr/co/pennyway/domain/config/ContainerMySqlTestConfig.java b/pennyway-domain/src/test/java/kr/co/pennyway/domain/config/ContainerMySqlTestConfig.java new file mode 100644 index 000000000..3a2511284 --- /dev/null +++ b/pennyway-domain/src/test/java/kr/co/pennyway/domain/config/ContainerMySqlTestConfig.java @@ -0,0 +1,34 @@ +package kr.co.pennyway.domain.config; + +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +@Testcontainers +@ActiveProfiles("test") +public class ContainerMySqlTestConfig { + private static final String MYSQL_CONTAINER_IMAGE = "mysql:8.0.26"; + + private static final MySQLContainer MYSQL_CONTAINER; + + static { + MYSQL_CONTAINER = + new MySQLContainer<>(DockerImageName.parse(MYSQL_CONTAINER_IMAGE)) + .withDatabaseName("pennyway") + .withUsername("root") + .withPassword("testpass") + .withReuse(true); + + MYSQL_CONTAINER.start(); + } + + @DynamicPropertySource + public static void setRedisProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", () -> String.format("jdbc:mysql://%s:%s/pennyway?serverTimezone=UTC&characterEncoding=utf8", MYSQL_CONTAINER.getHost(), MYSQL_CONTAINER.getMappedPort(3306))); + registry.add("spring.datasource.username", () -> "root"); + registry.add("spring.datasource.password", () -> "testpass"); + } +} diff --git a/pennyway-domain/src/test/java/kr/co/pennyway/domain/domains/user/repository/UserSoftDeleteTest.java b/pennyway-domain/src/test/java/kr/co/pennyway/domain/domains/user/repository/UserSoftDeleteTest.java new file mode 100644 index 000000000..a535d29b1 --- /dev/null +++ b/pennyway-domain/src/test/java/kr/co/pennyway/domain/domains/user/repository/UserSoftDeleteTest.java @@ -0,0 +1,119 @@ +package kr.co.pennyway.domain.domains.user.repository; + +import jakarta.persistence.EntityManager; +import kr.co.pennyway.domain.config.ContainerMySqlTestConfig; +import kr.co.pennyway.domain.config.JpaConfig; +import kr.co.pennyway.domain.domains.user.domain.User; +import kr.co.pennyway.domain.domains.user.service.UserService; +import kr.co.pennyway.domain.domains.user.type.ProfileVisibility; +import kr.co.pennyway.domain.domains.user.type.Role; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.transaction.annotation.Transactional; + +import static org.springframework.test.util.AssertionErrors.*; + +@DataJpaTest(properties = "spring.jpa.hibernate.ddl-auto=create") +@ContextConfiguration(classes = {JpaConfig.class, UserService.class}) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@ActiveProfiles("test") +public class UserSoftDeleteTest extends ContainerMySqlTestConfig { + @Autowired + private UserService userService; + + @Autowired + private EntityManager em; + + private User user; + + @BeforeEach + public void setUp() { + user = User.builder() + .username("test") + .name("pannyway") + .password("test") + .phone("01012345678") + .role(Role.USER) + .profileVisibility(ProfileVisibility.PUBLIC) + .build(); + } + + @Test + @DisplayName("[명제] em.createNativeQuery를 사용해도 영속성 컨텍스트에 저장된 엔티티를 조회할 수 있다.") + @Transactional + public void findByEntityMangerUsingNativeQuery() { + // given + User savedUser = userService.createUser(user); + Long userId = savedUser.getId(); + + // when + Object foundUser = em.createNativeQuery("SELECT * FROM user WHERE id = ?", User.class) + .setParameter(1, userId) + .getSingleResult(); + + // then + assertNotNull("foundUser는 nll이 아니어야 한다.", foundUser); + assertEquals("동등성 보장에 성공해야 한다.", savedUser, foundUser); + assertTrue("동일성 보장에 성공해야 한다.", savedUser == foundUser); + System.out.println("foundUser = " + foundUser); + } + + @Test + @DisplayName("유저가 삭제되면 deletedAt이 업데이트된다.") + @Transactional + public void deleteUser() { + // given + User savedUser = userService.createUser(user); + Long userId = savedUser.getId(); + + // when + userService.deleteUser(savedUser); + Object deletedUser = em.createNativeQuery("SELECT * FROM user WHERE id = ?", User.class) + .setParameter(1, userId) + .getSingleResult(); + + // then + assertNotNull("유저가 삭제되면 deletedAt이 업데이트된다. ", ((User) deletedUser).getDeletedAt()); + System.out.println("deletedUser = " + deletedUser); + } + + @Test + @DisplayName("유저가 삭제되면 findBy와 existsBy로 조회할 수 없다.") + @Transactional + public void deleteUserAndFindById() { + // given + User savedUser = userService.createUser(user); + Long userId = savedUser.getId(); + + // when + userService.deleteUser(savedUser); + + // then + assertFalse("유저가 삭제되면 existsById로 조회할 수 없다. ", userService.isExistUser(userId)); + assertNull("유저가 삭제되면 findById로 조회할 수 없다. ", userService.readUser(userId).orElse(null)); + System.out.println("after delete: savedUser = " + savedUser); + } + + @Test + @DisplayName("유저가 삭제되지 않으면 findById로 조회할 수 있다.") + @Transactional + public void findUserNotDeleted() { + // given + User savedUser = userService.createUser(user); + Long userId = savedUser.getId(); + + // when + User foundUser = userService.readUser(userId).orElse(null); + + // then + assertNotNull("foundUser는 null이 아니어야 한다.", foundUser); + assertEquals("foundUser는 savedUser와 같아야 한다.", savedUser, foundUser); + System.out.println("foundUser = " + foundUser); + } +} diff --git a/pennyway-domain/src/test/resources/logback-test.xml b/pennyway-domain/src/test/resources/logback-test.xml new file mode 100644 index 000000000..198192602 --- /dev/null +++ b/pennyway-domain/src/test/resources/logback-test.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file