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

✨ User, Oauth Entity Soft Delete 반영 #43

Merged
merged 10 commits into from
Apr 12, 2024
11 changes: 4 additions & 7 deletions pennyway-domain/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 = ?")
asn6878 marked this conversation as resolved.
Show resolved Hide resolved
public class User extends DateAuditable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down Expand Up @@ -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 +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
asn6878 marked this conversation as resolved.
Show resolved Hide resolved

@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");
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
7 changes: 7 additions & 0 deletions pennyway-domain/src/test/resources/logback-test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<logger name="org.springframework" level="INFO"/>
<include resource="kr.co.pennyway"/>
<logger name="kr.co.pennyway" level="DEBUG"/>
</configuration>
Loading