init: 클린 기반 auth 서버 설계
This commit is contained in:
+55
@@ -0,0 +1,55 @@
|
||||
package com.project.auth.infrastructure.persistence.user;
|
||||
|
||||
import com.project.auth.application.auth.identity.port.out.LoadKeycloakUserPort;
|
||||
import com.project.auth.application.auth.identity.port.out.RegisterKeycloakUserPort;
|
||||
import com.project.auth.domain.user.model.AuthProvider;
|
||||
import com.project.auth.domain.user.model.User;
|
||||
import com.project.auth.domain.user.model.UserEmail;
|
||||
import com.project.auth.infrastructure.persistence.user.entity.UserJpaEntity;
|
||||
import com.project.auth.infrastructure.persistence.user.mapper.UserPersistenceMapper;
|
||||
import com.project.auth.infrastructure.persistence.user.repository.UserJpaRepository;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class JpaUserRepositoryAdapter implements LoadKeycloakUserPort, RegisterKeycloakUserPort {
|
||||
|
||||
private final UserJpaRepository userJpaRepository;
|
||||
private final UserPersistenceMapper userPersistenceMapper;
|
||||
|
||||
public JpaUserRepositoryAdapter(UserJpaRepository userJpaRepository, UserPersistenceMapper userPersistenceMapper) {
|
||||
this.userJpaRepository = Objects.requireNonNull(userJpaRepository, "userJpaRepository must not be null");
|
||||
this.userPersistenceMapper = Objects.requireNonNull(
|
||||
userPersistenceMapper,
|
||||
"userPersistenceMapper must not be null"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<User> findByProviderAndProviderSubject(
|
||||
AuthProvider provider,
|
||||
String providerSubject
|
||||
) {
|
||||
return userJpaRepository.findByProviderAndProviderSubject(provider, providerSubject)
|
||||
.map(userPersistenceMapper::toDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existsByEmail(UserEmail email) {
|
||||
UserEmail nonNullEmail = Objects.requireNonNull(email, "email must not be null");
|
||||
|
||||
return userJpaRepository.existsByEmail(nonNullEmail.value());
|
||||
}
|
||||
|
||||
@Override
|
||||
public User register(User user) {
|
||||
return save(user);
|
||||
}
|
||||
|
||||
public User save(User user) {
|
||||
User nonNullUser = Objects.requireNonNull(user, "user must not be null");
|
||||
|
||||
UserJpaEntity savedUser = userJpaRepository.save(userPersistenceMapper.toEntity(nonNullUser));
|
||||
return userPersistenceMapper.toDomain(savedUser);
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package com.project.auth.infrastructure.persistence.user.entity;
|
||||
|
||||
import com.project.auth.domain.user.model.AuthProvider;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
public class UserJpaEntity {
|
||||
|
||||
@Id
|
||||
@Column(name = "id", nullable = false, updatable = false)
|
||||
private UUID id;
|
||||
|
||||
@Column(name = "email", nullable = false, columnDefinition = "text")
|
||||
private String email;
|
||||
|
||||
@Column(name = "name", nullable = false, columnDefinition = "text")
|
||||
private String name;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "provider", nullable = false, columnDefinition = "text")
|
||||
private AuthProvider provider;
|
||||
|
||||
@Column(name = "provider_subject", nullable = false, columnDefinition = "text")
|
||||
private String providerSubject;
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private Instant createdAt;
|
||||
|
||||
@Column(name = "updated_at", nullable = false, insertable = false, updatable = false)
|
||||
private Instant updatedAt;
|
||||
|
||||
protected UserJpaEntity() {
|
||||
}
|
||||
|
||||
private UserJpaEntity(
|
||||
UUID id,
|
||||
String email,
|
||||
String name,
|
||||
AuthProvider provider,
|
||||
String providerSubject,
|
||||
Instant createdAt
|
||||
) {
|
||||
this.id = Objects.requireNonNull(id, "id must not be null");
|
||||
this.email = Objects.requireNonNull(email, "email must not be null");
|
||||
this.name = Objects.requireNonNull(name, "name must not be null");
|
||||
this.provider = Objects.requireNonNull(provider, "provider must not be null");
|
||||
this.providerSubject = Objects.requireNonNull(providerSubject, "providerSubject must not be null");
|
||||
this.createdAt = Objects.requireNonNull(createdAt, "createdAt must not be null");
|
||||
}
|
||||
|
||||
public static UserJpaEntity of(
|
||||
UUID id,
|
||||
String email,
|
||||
String name,
|
||||
AuthProvider provider,
|
||||
String providerSubject,
|
||||
Instant createdAt
|
||||
) {
|
||||
return new UserJpaEntity(id, email, name, provider, providerSubject, createdAt);
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public AuthProvider getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public String getProviderSubject() {
|
||||
return providerSubject;
|
||||
}
|
||||
|
||||
public Instant getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.project.auth.infrastructure.persistence.user.mapper;
|
||||
|
||||
import com.project.auth.domain.user.exception.DomainException;
|
||||
import com.project.auth.domain.user.model.User;
|
||||
import com.project.auth.domain.user.model.UserEmail;
|
||||
import com.project.auth.domain.user.model.UserName;
|
||||
import com.project.auth.infrastructure.persistence.user.entity.UserJpaEntity;
|
||||
import com.project.auth.infrastructure.support.exception.InfrastructureErrorCode;
|
||||
import com.project.auth.infrastructure.support.exception.InfrastructureException;
|
||||
|
||||
public class UserPersistenceMapper {
|
||||
|
||||
public UserJpaEntity toEntity(User user) {
|
||||
return UserJpaEntity.of(
|
||||
user.getId(),
|
||||
user.getEmail(),
|
||||
user.getName(),
|
||||
user.getProvider(),
|
||||
user.getProviderSubject(),
|
||||
user.getCreatedAt()
|
||||
);
|
||||
}
|
||||
|
||||
public User toDomain(UserJpaEntity userJpaEntity) {
|
||||
try {
|
||||
return User.restore(
|
||||
userJpaEntity.getId(),
|
||||
UserEmail.from(userJpaEntity.getEmail()),
|
||||
UserName.from(userJpaEntity.getName()),
|
||||
userJpaEntity.getProvider(),
|
||||
userJpaEntity.getProviderSubject(),
|
||||
userJpaEntity.getCreatedAt()
|
||||
);
|
||||
} catch (DomainException | IllegalArgumentException | NullPointerException exception) {
|
||||
throw new InfrastructureException(
|
||||
InfrastructureErrorCode.PERSISTED_DATA_INVALID,
|
||||
exception
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.project.auth.infrastructure.persistence.user.repository;
|
||||
|
||||
import com.project.auth.domain.user.model.AuthProvider;
|
||||
import com.project.auth.infrastructure.persistence.user.entity.UserJpaEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface UserJpaRepository extends JpaRepository<UserJpaEntity, UUID> {
|
||||
|
||||
Optional<UserJpaEntity> findByProviderAndProviderSubject(AuthProvider provider, String providerSubject);
|
||||
|
||||
boolean existsByEmail(String email);
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.project.auth.infrastructure.support.exception;
|
||||
|
||||
import com.project.auth.application.support.exception.ExternalErrorCode;
|
||||
|
||||
/**
|
||||
* 내부 전용 인프라 장애 코드. 로그·모니터링·알람 라우팅 분류 용도로만 사용한다.
|
||||
* 외부 HTTP 응답에서는 의도적으로 COMMON-999로 정규화되어 노출된다.
|
||||
*/
|
||||
public enum InfrastructureErrorCode implements ExternalErrorCode {
|
||||
PERSISTED_DATA_INVALID("INFRA-003", "저장된 데이터가 도메인 규칙에 맞지 않습니다."),
|
||||
EXTERNAL_SERVICE_ERROR("INFRA-999", "외부 시스템 연동 중 오류가 발생했습니다.");
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
||||
InfrastructureErrorCode(String code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String message() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.project.auth.infrastructure.support.exception;
|
||||
|
||||
/**
|
||||
* 인프라 계층(DB, 외부 HTTP 클라이언트, 시크릿 저장소 등) 장애를 표현하는 내부 전용 예외.
|
||||
*
|
||||
* 등록된 @ExceptionHandler는 ERROR 레벨로 전체 스택트레이스와 함께 {@code detailMessage}를
|
||||
* 로깅한다. 따라서 어댑터는 {@code detailMessage}에 민감 정보를 절대 포함시키면 안 된다.
|
||||
*
|
||||
* detailMessage에 금지: 커넥션 문자열, DB 자격 증명, Vault 토큰, JWT 페이로드 내용,
|
||||
* 정제되지 않은 사용자 입력, 외부 호출의 전체 요청/응답 바디.
|
||||
*
|
||||
* detailMessage에 허용: 식별 불가 형태의 상관 ID, 호스트/서비스 이름, 정제된 상태 코드,
|
||||
* 장애 모드를 설명하는 일반 문장. 이 detail은 운영자 트리아지용이며, 클라이언트는 항상
|
||||
* COMMON-999만 받는다.
|
||||
*/
|
||||
public class InfrastructureException extends RuntimeException {
|
||||
|
||||
private final InfrastructureErrorCode errorCode;
|
||||
|
||||
public InfrastructureException(InfrastructureErrorCode errorCode) {
|
||||
super(errorCode.message());
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public InfrastructureException(InfrastructureErrorCode errorCode, String detailMessage) {
|
||||
super(detailMessage);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public InfrastructureException(InfrastructureErrorCode errorCode, Throwable cause) {
|
||||
super(errorCode.message(), cause);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public InfrastructureException(InfrastructureErrorCode errorCode, String detailMessage, Throwable cause) {
|
||||
super(detailMessage, cause);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public InfrastructureErrorCode getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
create schema if not exists auth;
|
||||
|
||||
create or replace function auth.set_updated_at()
|
||||
returns trigger as $$
|
||||
begin
|
||||
new.updated_at := current_timestamp;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
create table auth.users (
|
||||
id uuid not null,
|
||||
email text not null,
|
||||
encoded_password text not null,
|
||||
name text not null,
|
||||
provider text not null,
|
||||
created_at timestamp with time zone not null default current_timestamp,
|
||||
updated_at timestamp with time zone not null default current_timestamp,
|
||||
constraint pk_users primary key (id),
|
||||
constraint uq_users__email unique (email),
|
||||
constraint ck_users__provider check (provider in ('LOCAL', 'GOOGLE', 'GITHUB'))
|
||||
);
|
||||
|
||||
create index ix_users__created_at on auth.users (created_at);
|
||||
|
||||
create trigger trg_users__set_updated_at
|
||||
before update on auth.users
|
||||
for each row
|
||||
when (old.* is distinct from new.*)
|
||||
execute function auth.set_updated_at();
|
||||
@@ -0,0 +1,6 @@
|
||||
alter table auth.users alter column encoded_password drop not null;
|
||||
|
||||
alter table auth.users add column provider_subject text;
|
||||
|
||||
alter table auth.users
|
||||
add constraint uq_users__provider_provider_subject unique (provider, provider_subject);
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
alter table auth.users
|
||||
add constraint ck_users__local_password_required
|
||||
check (
|
||||
(provider = 'LOCAL' and encoded_password is not null and provider_subject is null)
|
||||
or (provider <> 'LOCAL')
|
||||
);
|
||||
|
||||
alter table auth.users
|
||||
add constraint ck_users__social_subject_required
|
||||
check (
|
||||
(provider <> 'LOCAL' and provider_subject is not null and encoded_password is null)
|
||||
or (provider = 'LOCAL')
|
||||
);
|
||||
@@ -0,0 +1,4 @@
|
||||
alter table auth.users drop constraint ck_users__provider;
|
||||
|
||||
alter table auth.users
|
||||
add constraint ck_users__provider check (provider in ('LOCAL', 'KEYCLOAK', 'GOOGLE', 'GITHUB'));
|
||||
@@ -0,0 +1,10 @@
|
||||
alter table auth.users drop constraint if exists ck_users__local_password_required;
|
||||
alter table auth.users drop constraint if exists ck_users__social_subject_required;
|
||||
alter table auth.users drop constraint if exists ck_users__provider;
|
||||
|
||||
alter table auth.users drop column if exists encoded_password;
|
||||
|
||||
alter table auth.users alter column provider_subject set not null;
|
||||
|
||||
alter table auth.users
|
||||
add constraint ck_users__provider check (provider = 'KEYCLOAK');
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
package com.project.auth.infrastructure.persistence.user;
|
||||
|
||||
import com.project.auth.domain.user.model.AuthProvider;
|
||||
import com.project.auth.domain.user.model.User;
|
||||
import com.project.auth.domain.user.model.UserEmail;
|
||||
import com.project.auth.domain.user.model.UserName;
|
||||
import com.project.auth.infrastructure.persistence.user.entity.UserJpaEntity;
|
||||
import com.project.auth.infrastructure.persistence.user.mapper.UserPersistenceMapper;
|
||||
import com.project.auth.infrastructure.persistence.user.repository.UserJpaRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
|
||||
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
|
||||
import org.springframework.boot.jpa.test.autoconfigure.TestEntityManager;
|
||||
import org.springframework.boot.persistence.autoconfigure.EntityScan;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.DynamicPropertyRegistry;
|
||||
import org.springframework.test.context.DynamicPropertySource;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.flywaydb.core.Flyway;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
@DataJpaTest
|
||||
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
|
||||
@ContextConfiguration(classes = {
|
||||
UserJpaRepositoryTest.TestApplication.class,
|
||||
UserJpaRepositoryTest.TestFlywayConfiguration.class
|
||||
})
|
||||
@Testcontainers
|
||||
@TestPropertySource(properties = {
|
||||
"spring.jpa.hibernate.ddl-auto=validate",
|
||||
"spring.jpa.properties.hibernate.default_schema=auth"
|
||||
})
|
||||
class UserJpaRepositoryTest {
|
||||
|
||||
@Container
|
||||
private static final PostgreSQLContainer<?> POSTGRES =
|
||||
new PostgreSQLContainer<>("postgres:16-alpine");
|
||||
|
||||
@Autowired
|
||||
private UserJpaRepository userJpaRepository;
|
||||
|
||||
@Autowired
|
||||
private TestEntityManager entityManager;
|
||||
|
||||
private JpaUserRepositoryAdapter adapter;
|
||||
|
||||
@DynamicPropertySource
|
||||
static void registerDataSourceProperties(DynamicPropertyRegistry registry) {
|
||||
registry.add("spring.datasource.url", POSTGRES::getJdbcUrl);
|
||||
registry.add("spring.datasource.username", POSTGRES::getUsername);
|
||||
registry.add("spring.datasource.password", POSTGRES::getPassword);
|
||||
registry.add("spring.datasource.driver-class-name", POSTGRES::getDriverClassName);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
adapter = new JpaUserRepositoryAdapter(userJpaRepository, new UserPersistenceMapper());
|
||||
}
|
||||
|
||||
@Test
|
||||
void savePersistsKeycloakUserAndReloadsFromDatabase() {
|
||||
User user = keycloakUser(
|
||||
"11111111-1111-1111-1111-111111111111",
|
||||
"tester@example.com",
|
||||
"keycloak-subject-1"
|
||||
);
|
||||
|
||||
adapter.save(user);
|
||||
entityManager.flush();
|
||||
entityManager.clear();
|
||||
|
||||
assertThat(userJpaRepository.findByProviderAndProviderSubject(AuthProvider.KEYCLOAK, "keycloak-subject-1"))
|
||||
.isPresent()
|
||||
.get()
|
||||
.satisfies(entity -> {
|
||||
assertThat(entity.getEmail()).isEqualTo("tester@example.com");
|
||||
assertThat(entity.getProvider()).isEqualTo(AuthProvider.KEYCLOAK);
|
||||
assertThat(entity.getCreatedAt()).isEqualTo(Instant.parse("2026-04-17T00:00:00Z"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveRejectsDuplicateEmailOnFlush() {
|
||||
userJpaRepository.save(keycloakEntity(
|
||||
"11111111-1111-1111-1111-111111111111",
|
||||
"duplicate@example.com",
|
||||
"keycloak-subject-1"
|
||||
));
|
||||
userJpaRepository.flush();
|
||||
entityManager.clear();
|
||||
|
||||
userJpaRepository.save(keycloakEntity(
|
||||
"22222222-2222-2222-2222-222222222222",
|
||||
"duplicate@example.com",
|
||||
"keycloak-subject-2"
|
||||
));
|
||||
|
||||
assertThatThrownBy(() -> userJpaRepository.flush())
|
||||
.isInstanceOf(DataIntegrityViolationException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveRejectsDuplicateProviderSubjectOnFlush() {
|
||||
userJpaRepository.save(keycloakEntity(
|
||||
"11111111-1111-1111-1111-111111111111",
|
||||
"first@example.com",
|
||||
"keycloak-subject-1"
|
||||
));
|
||||
userJpaRepository.flush();
|
||||
entityManager.clear();
|
||||
|
||||
userJpaRepository.save(keycloakEntity(
|
||||
"22222222-2222-2222-2222-222222222222",
|
||||
"second@example.com",
|
||||
"keycloak-subject-1"
|
||||
));
|
||||
|
||||
assertThatThrownBy(() -> userJpaRepository.flush())
|
||||
.isInstanceOf(DataIntegrityViolationException.class);
|
||||
}
|
||||
|
||||
private static User keycloakUser(String id, String email, String providerSubject) {
|
||||
return User.registerKeycloak(
|
||||
UUID.fromString(id),
|
||||
UserEmail.from(email),
|
||||
UserName.from("테스터"),
|
||||
providerSubject,
|
||||
Instant.parse("2026-04-17T00:00:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
private static UserJpaEntity keycloakEntity(String id, String email, String providerSubject) {
|
||||
return UserJpaEntity.of(
|
||||
UUID.fromString(id),
|
||||
email,
|
||||
"테스터",
|
||||
AuthProvider.KEYCLOAK,
|
||||
providerSubject,
|
||||
Instant.parse("2026-04-17T00:00:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@EntityScan(basePackageClasses = UserJpaEntity.class)
|
||||
@EnableJpaRepositories(basePackageClasses = UserJpaRepository.class)
|
||||
static class TestApplication {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestFlywayConfiguration {
|
||||
|
||||
private static final String MIGRATION_LOCATION = "classpath:db/migration";
|
||||
private static final String SCHEMA = "auth";
|
||||
|
||||
@Bean
|
||||
Flyway flyway(DataSource dataSource) {
|
||||
return Flyway.configure()
|
||||
.dataSource(dataSource)
|
||||
.locations(MIGRATION_LOCATION)
|
||||
.defaultSchema(SCHEMA)
|
||||
.schemas(SCHEMA)
|
||||
.load();
|
||||
}
|
||||
|
||||
@Bean("testFlywayMigrationInitializer")
|
||||
InitializingBean testFlywayMigrationInitializer(Flyway flyway) {
|
||||
return flyway::migrate;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class FlywayDependsOnPostProcessor extends AbstractDependsOnBeanFactoryPostProcessor {
|
||||
|
||||
FlywayDependsOnPostProcessor() {
|
||||
super(EntityManagerFactory.class, "testFlywayMigrationInitializer");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.project.auth.infrastructure.persistence.user.mapper;
|
||||
|
||||
import com.project.auth.domain.user.model.AuthProvider;
|
||||
import com.project.auth.infrastructure.persistence.user.entity.UserJpaEntity;
|
||||
import com.project.auth.infrastructure.support.exception.InfrastructureErrorCode;
|
||||
import com.project.auth.infrastructure.support.exception.InfrastructureException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class UserPersistenceMapperTest {
|
||||
|
||||
private final UserPersistenceMapper mapper = new UserPersistenceMapper();
|
||||
|
||||
@Test
|
||||
void toDomain_translates_invalid_persisted_user_state_to_infrastructure_exception() {
|
||||
UserJpaEntity invalidEntity = UserJpaEntity.of(
|
||||
UUID.fromString("11111111-1111-1111-1111-111111111111"),
|
||||
"not-an-email",
|
||||
"테스터",
|
||||
AuthProvider.KEYCLOAK,
|
||||
"keycloak-subject-1",
|
||||
Instant.parse("2026-04-17T00:00:00Z")
|
||||
);
|
||||
|
||||
assertThatThrownBy(() -> mapper.toDomain(invalidEntity))
|
||||
.isInstanceOf(InfrastructureException.class)
|
||||
.extracting(exception -> ((InfrastructureException) exception).getErrorCode())
|
||||
.isEqualTo(InfrastructureErrorCode.PERSISTED_DATA_INVALID);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user