diff --git a/.gitignore b/.gitignore index 6a8f52d..3bb3d9c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ .idea/**/dbnavigator.xml .idea/**/gradle.xml .idea/**/libraries +.idea/codeStyles +.idea src/main/resources/jwt-env.properties src/main/resources/database-env.properties diff --git a/build.gradle b/build.gradle index 0faab72..7be20d3 100644 --- a/build.gradle +++ b/build.gradle @@ -30,12 +30,21 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.mindrot:jbcrypt:0.4' + + //redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation("it.ozimov:embedded-redis:0.7.2") + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' + // Mail + implementation 'org.springframework.boot:spring-boot-starter-mail' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + // Querydsl implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" @@ -43,7 +52,7 @@ dependencies { annotationProcessor "jakarta.persistence:jakarta.persistence-api" // acceptance test - testImplementation 'io.rest-assured:rest-assured:5.3.0' + testImplementation 'io.rest-assured:rest-assured:5.3.1' testImplementation 'org.springframework.boot:spring-boot-starter-test' } @@ -89,7 +98,9 @@ jacocoTestReport { '**/*ExceptionHandler*', '**/LoggingUtils', '**/*Url*', - '**/*AdminController*' + '**/*AdminController*', + '**/*Config*', + '**/*Wrapper*' ]) }) ) @@ -113,7 +124,7 @@ jacocoTestCoverageVerification { limit { counter = 'LINE' value = 'COVEREDRATIO' - minimum = 0.9 + minimum = 0.7 } excludes = [ @@ -130,7 +141,8 @@ jacocoTestCoverageVerification { '*.LoggingUtils', '*.*Url', '*.*AdminController', - "*.*QuerydslConfig" + '*.*Config', + '*.*Wrapper' ] + Qdomains } } diff --git a/src/main/java/com/integrated/techhub/auth/application/AuthQueryService.java b/src/main/java/com/integrated/techhub/auth/application/AuthQueryService.java new file mode 100644 index 0000000..33fc2f8 --- /dev/null +++ b/src/main/java/com/integrated/techhub/auth/application/AuthQueryService.java @@ -0,0 +1,20 @@ +package com.integrated.techhub.auth.application; + +import com.integrated.techhub.member.domain.repository.MemberRepository; +import com.integrated.techhub.member.exception.MemberExistsException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthQueryService { + + private final MemberRepository memberRepository; + + public void validateExistedMember(final String email) { + if (memberRepository.existsByEmail(email)) { + throw new MemberExistsException(); + } + } + +} diff --git a/src/main/java/com/integrated/techhub/auth/application/AuthService.java b/src/main/java/com/integrated/techhub/auth/application/AuthService.java new file mode 100644 index 0000000..cfbba16 --- /dev/null +++ b/src/main/java/com/integrated/techhub/auth/application/AuthService.java @@ -0,0 +1,27 @@ +package com.integrated.techhub.auth.application; + +import com.integrated.techhub.auth.domain.PasswordEncoder; +import com.integrated.techhub.auth.dto.SignUpRequest; +import com.integrated.techhub.member.domain.Member; +import com.integrated.techhub.member.domain.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class AuthService { + + private final MemberRepository memberRepository; + private final AuthQueryService authQueryService; + private final PasswordEncoder passwordEncoder; + + public Long registerMember(final SignUpRequest request) { + authQueryService.validateExistedMember(request.email()); + Member member = request.toEntity(); + member.encodePassword(passwordEncoder); + return memberRepository.save(member).getId(); + } + +} diff --git a/src/main/java/com/integrated/techhub/auth/domain/PasswordEncoder.java b/src/main/java/com/integrated/techhub/auth/domain/PasswordEncoder.java new file mode 100644 index 0000000..816d894 --- /dev/null +++ b/src/main/java/com/integrated/techhub/auth/domain/PasswordEncoder.java @@ -0,0 +1,9 @@ +package com.integrated.techhub.auth.domain; + +public interface PasswordEncoder { + + String encode(String rawPassword); + + boolean isMatch(String rawPassword, String encryptedPassword); + +} \ No newline at end of file diff --git a/src/main/java/com/integrated/techhub/auth/dto/SignUpRequest.java b/src/main/java/com/integrated/techhub/auth/dto/SignUpRequest.java new file mode 100644 index 0000000..4db4aad --- /dev/null +++ b/src/main/java/com/integrated/techhub/auth/dto/SignUpRequest.java @@ -0,0 +1,48 @@ +package com.integrated.techhub.auth.dto; + +import com.integrated.techhub.member.domain.Member; +import com.integrated.techhub.member.domain.Position; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; + +public record SignUpRequest( + @Email(message = "이메일 형식이어야 합니다. 올바른 형식인지 확인해주세요.") + @NotBlank(message = "Null 또는 공백이 포함될 수 없습니다. 올바른 값인지 확인해주세요.") + String email, + + @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d).{8,}$", message = "최소 8자, 문자 1개와 숫자 1개 이상을 포함해야합니다. 다시 입력해주세요.") + @NotBlank(message = "Null 또는 공백이 포함될 수 없습니다. 올바른 값인지 확인해주세요.") + String password, + + @NotBlank(message = "Null 또는 공백이 포함될 수 없습니다. 올바른 값인지 확인해주세요.") + String nickname, + + @NotBlank(message = "Null 또는 공백이 포함될 수 없습니다. 올바른 값인지 확인해주세요.") + String githubUserName, + + @NotBlank(message = "Null 또는 공백이 포함될 수 없습니다. 올바른 값인지 확인해주세요.") + String name, + + @Pattern(regexp = "^(BE|FE|AOS)$", message = "BE, FE, AOS만 입력할 수 있습니다. 알맞은 포지션인지 확인해주세요.") + @NotBlank(message = "Null 또는 공백이 포함될 수 없습니다. 올바른 값인지 확인해주세요.") + String position, + + @NotNull(message = "Null이 포함될 수 없습니다. 올바른 값인지 확인해주세요.") + Integer cardinalNumber +) { + + public Member toEntity() { + return Member.builder() + .email(email) + .password(password) + .nickname(nickname) + .githubUsername(githubUserName) + .name(name) + .position(Position.valueOf(position)) + .cardinalNumber(cardinalNumber) + .build(); + } + +} diff --git a/src/main/java/com/integrated/techhub/auth/infra/encode/BCryptPasswordEncoder.java b/src/main/java/com/integrated/techhub/auth/infra/encode/BCryptPasswordEncoder.java new file mode 100644 index 0000000..f4024f4 --- /dev/null +++ b/src/main/java/com/integrated/techhub/auth/infra/encode/BCryptPasswordEncoder.java @@ -0,0 +1,20 @@ +package com.integrated.techhub.auth.infra.encode; + +import com.integrated.techhub.auth.domain.PasswordEncoder; +import org.mindrot.jbcrypt.BCrypt; +import org.springframework.stereotype.Component; + +@Component +public class BCryptPasswordEncoder implements PasswordEncoder { + + @Override + public String encode(String rawPassword) { + return BCrypt.hashpw(rawPassword, BCrypt.gensalt()); + } + + @Override + public boolean isMatch(String rawPassword, String encryptedPassword) { + return BCrypt.checkpw(rawPassword, encryptedPassword); + } + +} \ No newline at end of file diff --git a/src/main/java/com/integrated/techhub/auth/presentation/AuthController.java b/src/main/java/com/integrated/techhub/auth/presentation/AuthController.java new file mode 100644 index 0000000..0e7e941 --- /dev/null +++ b/src/main/java/com/integrated/techhub/auth/presentation/AuthController.java @@ -0,0 +1,28 @@ +package com.integrated.techhub.auth.presentation; + +import com.integrated.techhub.auth.application.AuthService; +import com.integrated.techhub.auth.dto.SignUpRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.net.URI; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/auth") +public class AuthController { + + private final AuthService authService; + + @PostMapping("/sign-up") + public ResponseEntity signUp(@RequestBody @Valid final SignUpRequest request) { + final Long memberId = authService.registerMember(request); + return ResponseEntity.created(URI.create("/members/" + memberId)).build(); + } + +} diff --git a/src/main/java/com/integrated/techhub/common/config/AsyncConfig.java b/src/main/java/com/integrated/techhub/common/config/AsyncConfig.java new file mode 100644 index 0000000..9b27121 --- /dev/null +++ b/src/main/java/com/integrated/techhub/common/config/AsyncConfig.java @@ -0,0 +1,37 @@ +package com.integrated.techhub.common.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +@Slf4j +@EnableAsync +@Configuration +public class AsyncConfig implements AsyncConfigurer { + + @Override + @Bean(name = "MailExecutor") + public Executor getAsyncExecutor() { + final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(2); + executor.setMaxPoolSize(5); + executor.setQueueCapacity(10); + executor.setThreadNamePrefix("MailExecutor-"); + executor.initialize(); + return executor; + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return (ex, method, params) -> + log.error("Exception handler for async method '" + method.toGenericString() + + "' threw unexpected exception itself", ex); + } + +} diff --git a/src/main/java/com/integrated/techhub/common/config/EmbeddedRedisConfig.java b/src/main/java/com/integrated/techhub/common/config/EmbeddedRedisConfig.java new file mode 100644 index 0000000..25032f9 --- /dev/null +++ b/src/main/java/com/integrated/techhub/common/config/EmbeddedRedisConfig.java @@ -0,0 +1,88 @@ +package com.integrated.techhub.common.config; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; +import redis.embedded.RedisServer; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +@Slf4j +@Configuration +public class EmbeddedRedisConfig { + + @Value("${spring.data.redis.port}") + private int redisPort; + + private RedisServer redisServer; + + @PostConstruct + public void redisServer() throws IOException { + int port = isRedisRunning()? findAvailablePort() : redisPort; + redisServer = new RedisServer(port); + redisServer.start(); + } + + /** + * Embedded Redis가 현재 실행중인지 확인 + */ + private boolean isRedisRunning() throws IOException { + return isRunning(executeGrepProcessCommand(redisPort)); + } + + /** + * 해당 port를 사용중인 프로세스 확인하는 sh 실행 + */ + private Process executeGrepProcessCommand(int port) throws IOException { + String command = String.format("netstat -nat | grep LISTEN|grep %d", port); + String[] shell = {"/bin/sh", "-c", command}; + return Runtime.getRuntime().exec(shell); + } + + /** + * 해당 Process가 현재 실행중인지 확인 + */ + private boolean isRunning(Process process) { + String line; + StringBuilder pidInfo = new StringBuilder(); + + try (BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + + while ((line = input.readLine()) != null) { + pidInfo.append(line); + } + + } catch (Exception e) { + } + + return !StringUtils.isEmpty(pidInfo.toString()); + } + + /** + * 현재 PC/서버에서 사용가능한 포트 조회 + */ + public int findAvailablePort() throws IOException { + + for (int port = 10000; port <= 65535; port++) { + Process process = executeGrepProcessCommand(port); + if (!isRunning(process)) { + return port; + } + } + + throw new IllegalArgumentException("Not Found Available port: 10000 ~ 65535"); + } + + @PreDestroy + public void stopRedis() { + if (redisServer != null) { + redisServer.stop(); + } + } + +} diff --git a/src/main/java/com/integrated/techhub/common/exception/ErrorCode.java b/src/main/java/com/integrated/techhub/common/exception/ErrorCode.java new file mode 100644 index 0000000..dbe1b36 --- /dev/null +++ b/src/main/java/com/integrated/techhub/common/exception/ErrorCode.java @@ -0,0 +1,10 @@ +package com.integrated.techhub.common.exception; + +import org.springframework.http.HttpStatus; + +public record ErrorCode( + HttpStatus status, + String message +) { + +} diff --git a/src/main/java/com/integrated/techhub/common/exception/ErrorResponse.java b/src/main/java/com/integrated/techhub/common/exception/ErrorResponse.java new file mode 100644 index 0000000..4250891 --- /dev/null +++ b/src/main/java/com/integrated/techhub/common/exception/ErrorResponse.java @@ -0,0 +1,13 @@ +package com.integrated.techhub.common.exception; + +public record ErrorResponse(int status, String message) { + + @Override + public String toString() { + return "{\n" + + "\t\"status\": " + status + + ",\n\t\"message\": \"" + message + '\"' + + "\n}"; + } + +} diff --git a/src/main/java/com/integrated/techhub/common/exception/TechHubException.java b/src/main/java/com/integrated/techhub/common/exception/TechHubException.java new file mode 100644 index 0000000..9b08732 --- /dev/null +++ b/src/main/java/com/integrated/techhub/common/exception/TechHubException.java @@ -0,0 +1,15 @@ +package com.integrated.techhub.common.exception; + +import lombok.Getter; + +@Getter +public class TechHubException extends RuntimeException { + + private final ErrorCode errorCode; + + public TechHubException(ErrorCode errorCode) { + super(errorCode.message()); + this.errorCode = errorCode; + } + +} diff --git a/src/main/java/com/integrated/techhub/mail/application/MailService.java b/src/main/java/com/integrated/techhub/mail/application/MailService.java new file mode 100644 index 0000000..8dcf74a --- /dev/null +++ b/src/main/java/com/integrated/techhub/mail/application/MailService.java @@ -0,0 +1,74 @@ +package com.integrated.techhub.mail.application; + +import com.integrated.techhub.mail.domain.AuthorityCode; +import com.integrated.techhub.mail.domain.AuthorityCodeRepository; +import com.integrated.techhub.mail.domain.ITemplateEngine; +import com.integrated.techhub.mail.dto.MailSendRequest; +import com.integrated.techhub.mail.dto.MailValidateRequest; +import com.integrated.techhub.mail.exception.AuthorityCodeNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.mail.javamail.MimeMessagePreparator; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.thymeleaf.context.Context; + +import java.util.concurrent.ThreadLocalRandom; + +import static com.integrated.techhub.mail.domain.type.MailInfo.DOMAIN; +import static com.integrated.techhub.mail.domain.type.MailInfo.TITLE; +import static java.nio.charset.StandardCharsets.UTF_8; + + +@Service +@Transactional +@RequiredArgsConstructor +public class MailService { + + private static final int MIN_BOUNDARY = 100000; + private static final int MAX_BOUNDARY = 999999; + private static final String TEMPLATE_NAME = "mail"; + private static final String TEMPLATE_CODE_NAME = "code"; + + private final JavaMailSender mailSender; + private final ITemplateEngine templateEngine; + private final AuthorityCodeRepository authorityCodeRepository; + + @Async + public void sendMail(final MailSendRequest request) { + final int randomAuthCode = createRandomAuthCode(); + final String template = createTemplate(String.valueOf(randomAuthCode)); + final MimeMessagePreparator mimeMessagePreparator = createMimeMessagePreparator(request.email(), template); + mailSender.send(mimeMessagePreparator); + authorityCodeRepository.save(new AuthorityCode(request.email(), randomAuthCode)); + } + + private MimeMessagePreparator createMimeMessagePreparator(final String receiverEmail, final String template) { + return mimeMessage -> { + final MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, UTF_8.name()); + helper.setFrom(DOMAIN.getValue()); + helper.setTo(receiverEmail); + helper.setSubject(TITLE.getValue()); + helper.setText(template, true); + }; + } + + private int createRandomAuthCode() { + return ThreadLocalRandom.current().nextInt(MIN_BOUNDARY, MAX_BOUNDARY + 1); + } + + private String createTemplate(final String randomAuthCode) { + final Context context = new Context(); + context.setVariable(TEMPLATE_CODE_NAME, randomAuthCode); + return templateEngine.process(TEMPLATE_NAME, context); + } + + public void validateAuthorityCode(final MailValidateRequest request) { + final AuthorityCode authorityCode = authorityCodeRepository.findById(request.email()) + .orElseThrow(AuthorityCodeNotFoundException::new); + authorityCode.validateAuthorityCode(request.authorityCode()); + } + +} diff --git a/src/main/java/com/integrated/techhub/member/domain/AuthorityCode.java b/src/main/java/com/integrated/techhub/mail/domain/AuthorityCode.java similarity index 57% rename from src/main/java/com/integrated/techhub/member/domain/AuthorityCode.java rename to src/main/java/com/integrated/techhub/mail/domain/AuthorityCode.java index 331509b..c8bee9e 100644 --- a/src/main/java/com/integrated/techhub/member/domain/AuthorityCode.java +++ b/src/main/java/com/integrated/techhub/mail/domain/AuthorityCode.java @@ -1,5 +1,6 @@ -package com.integrated.techhub.member.domain; +package com.integrated.techhub.mail.domain; +import com.integrated.techhub.mail.exception.AuthorityCodeNotMatchException; import lombok.Getter; import org.springframework.data.annotation.Id; import org.springframework.data.redis.core.RedisHash; @@ -18,4 +19,10 @@ public AuthorityCode(String email, Integer value) { this.value = value; } + public void validateAuthorityCode(int authorityCode) { + if (!value.equals(authorityCode)) { + throw new AuthorityCodeNotMatchException(); + } + } + } diff --git a/src/main/java/com/integrated/techhub/member/domain/repository/AuthorityCodeRepository.java b/src/main/java/com/integrated/techhub/mail/domain/AuthorityCodeRepository.java similarity index 64% rename from src/main/java/com/integrated/techhub/member/domain/repository/AuthorityCodeRepository.java rename to src/main/java/com/integrated/techhub/mail/domain/AuthorityCodeRepository.java index cacae74..c1f1201 100644 --- a/src/main/java/com/integrated/techhub/member/domain/repository/AuthorityCodeRepository.java +++ b/src/main/java/com/integrated/techhub/mail/domain/AuthorityCodeRepository.java @@ -1,6 +1,5 @@ -package com.integrated.techhub.member.domain.repository; +package com.integrated.techhub.mail.domain; -import com.integrated.techhub.member.domain.AuthorityCode; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/integrated/techhub/mail/domain/ITemplateEngine.java b/src/main/java/com/integrated/techhub/mail/domain/ITemplateEngine.java new file mode 100644 index 0000000..4d60da2 --- /dev/null +++ b/src/main/java/com/integrated/techhub/mail/domain/ITemplateEngine.java @@ -0,0 +1,9 @@ +package com.integrated.techhub.mail.domain; + +import org.thymeleaf.context.IContext; + +public interface ITemplateEngine { + + String process(String template, IContext context); + +} diff --git a/src/main/java/com/integrated/techhub/mail/domain/type/MailInfo.java b/src/main/java/com/integrated/techhub/mail/domain/type/MailInfo.java new file mode 100644 index 0000000..44b5362 --- /dev/null +++ b/src/main/java/com/integrated/techhub/mail/domain/type/MailInfo.java @@ -0,0 +1,17 @@ +package com.integrated.techhub.mail.domain.type; + +import lombok.Getter; + +@Getter +public enum MailInfo { + + DOMAIN("tech.hub.kr"), + TITLE("TechHub 인증번호 이메일입니다."); + + private final String value; + + MailInfo(final String value) { + this.value = value; + } + +} diff --git a/src/main/java/com/integrated/techhub/mail/dto/MailSendRequest.java b/src/main/java/com/integrated/techhub/mail/dto/MailSendRequest.java new file mode 100644 index 0000000..c950bec --- /dev/null +++ b/src/main/java/com/integrated/techhub/mail/dto/MailSendRequest.java @@ -0,0 +1,12 @@ +package com.integrated.techhub.mail.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +public record MailSendRequest( + @Email(message = "이메일 형식이어야 합니다. 올바른 형식인지 확인해주세요.") + @NotBlank(message = "Null 또는 공백이 포함될 수 없습니다. 올바른 값인지 확인해주세요.") + String email +) { + +} diff --git a/src/main/java/com/integrated/techhub/mail/dto/MailValidateRequest.java b/src/main/java/com/integrated/techhub/mail/dto/MailValidateRequest.java new file mode 100644 index 0000000..d4d5ef3 --- /dev/null +++ b/src/main/java/com/integrated/techhub/mail/dto/MailValidateRequest.java @@ -0,0 +1,16 @@ +package com.integrated.techhub.mail.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record MailValidateRequest( + @Email(message = "이메일 형식이어야 합니다. 올바른 형식인지 확인해주세요.") + @NotBlank(message = "Null 또는 공백이 포함될 수 없습니다. 올바른 값인지 확인해주세요.") + String email, + + @NotNull(message = "Null이 포함될 수 없습니다. 올바른 값인지 확인해주세요.") + Integer authorityCode +) { + +} diff --git a/src/main/java/com/integrated/techhub/mail/exception/AuthorityCodeNotFoundException.java b/src/main/java/com/integrated/techhub/mail/exception/AuthorityCodeNotFoundException.java new file mode 100644 index 0000000..d460ed0 --- /dev/null +++ b/src/main/java/com/integrated/techhub/mail/exception/AuthorityCodeNotFoundException.java @@ -0,0 +1,14 @@ +package com.integrated.techhub.mail.exception; + +import com.integrated.techhub.common.exception.ErrorCode; +import com.integrated.techhub.common.exception.TechHubException; + +import static org.springframework.http.HttpStatus.NOT_FOUND; + +public class AuthorityCodeNotFoundException extends TechHubException { + + public AuthorityCodeNotFoundException() { + super(new ErrorCode(NOT_FOUND, "인증코드가 일치하지 않습니다. 인증코드를 알맞게 입력했는지 확인해주세요.")); + } + +} diff --git a/src/main/java/com/integrated/techhub/mail/exception/AuthorityCodeNotMatchException.java b/src/main/java/com/integrated/techhub/mail/exception/AuthorityCodeNotMatchException.java new file mode 100644 index 0000000..6ac3695 --- /dev/null +++ b/src/main/java/com/integrated/techhub/mail/exception/AuthorityCodeNotMatchException.java @@ -0,0 +1,15 @@ +package com.integrated.techhub.mail.exception; + +import com.integrated.techhub.common.exception.ErrorCode; +import com.integrated.techhub.common.exception.TechHubException; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; + +public class AuthorityCodeNotMatchException extends TechHubException { + + public AuthorityCodeNotMatchException() { + super(new ErrorCode(BAD_REQUEST, "일치하는 인증 코드를 찾을 수 없습니다. 인증 코드와 만료 기간이 유효한지 확인해주세요.")); + } + +} diff --git a/src/main/java/com/integrated/techhub/mail/infra/mail/SpringTemplateEngineWrapper.java b/src/main/java/com/integrated/techhub/mail/infra/mail/SpringTemplateEngineWrapper.java new file mode 100644 index 0000000..648a3a6 --- /dev/null +++ b/src/main/java/com/integrated/techhub/mail/infra/mail/SpringTemplateEngineWrapper.java @@ -0,0 +1,20 @@ +package com.integrated.techhub.mail.infra.mail; + +import com.integrated.techhub.mail.domain.ITemplateEngine; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.thymeleaf.context.IContext; +import org.thymeleaf.spring6.SpringTemplateEngine; + +@Component +@RequiredArgsConstructor +public class SpringTemplateEngineWrapper implements ITemplateEngine { + + private final SpringTemplateEngine springTemplateEngine; + + @Override + public String process(String template, IContext context) { + return springTemplateEngine.process(template, context); + } + +} diff --git a/src/main/java/com/integrated/techhub/mail/presentation/MailController.java b/src/main/java/com/integrated/techhub/mail/presentation/MailController.java new file mode 100644 index 0000000..1775513 --- /dev/null +++ b/src/main/java/com/integrated/techhub/mail/presentation/MailController.java @@ -0,0 +1,36 @@ +package com.integrated.techhub.mail.presentation; + +import com.integrated.techhub.auth.application.AuthQueryService; +import com.integrated.techhub.mail.dto.MailSendRequest; +import com.integrated.techhub.mail.application.MailService; +import com.integrated.techhub.mail.dto.MailValidateRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/mail") +public class MailController { + + private final AuthQueryService authQueryService; + private final MailService mailService; + + @PostMapping("/authorization-code") + public ResponseEntity sendMail(@RequestBody @Valid final MailSendRequest request) { + authQueryService.validateExistedMember(request.email()); + mailService.sendMail(request); + return ResponseEntity.ok().build(); + } + + @PostMapping("/validation") + public ResponseEntity validateAuthorityCode(@RequestBody @Valid final MailValidateRequest request) { + mailService.validateAuthorityCode(request); + return ResponseEntity.ok().build(); + } + +} diff --git a/src/main/java/com/integrated/techhub/member/domain/Member.java b/src/main/java/com/integrated/techhub/member/domain/Member.java index 890f755..dd94ac7 100644 --- a/src/main/java/com/integrated/techhub/member/domain/Member.java +++ b/src/main/java/com/integrated/techhub/member/domain/Member.java @@ -1,10 +1,13 @@ package com.integrated.techhub.member.domain; +import com.integrated.techhub.auth.domain.PasswordEncoder; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -14,6 +17,8 @@ @Getter @Entity +@Builder +@AllArgsConstructor @NoArgsConstructor(access = PROTECTED) public class Member { @@ -30,6 +35,12 @@ public class Member { @Column(length = 4, nullable = false) private String nickname; + @Column(nullable = false) + private Integer cardinalNumber; + + @Column(nullable = false) + private String githubUsername; + @Column(length = 6, nullable = false) private String name; @@ -40,12 +51,8 @@ public class Member { @Enumerated(value = STRING) private Position position; - public Member(String email, String password, String nickname, String name, String bio, Position position) { - this.email = email; - this.password = password; - this.nickname = nickname; - this.name = name; - this.bio = bio; - this.position = position; + public void encodePassword(PasswordEncoder passwordEncoder) { + this.password = passwordEncoder.encode(this.password); } + } diff --git a/src/main/java/com/integrated/techhub/member/domain/repository/MemberRepository.java b/src/main/java/com/integrated/techhub/member/domain/repository/MemberRepository.java index 87f3365..4d20c4b 100644 --- a/src/main/java/com/integrated/techhub/member/domain/repository/MemberRepository.java +++ b/src/main/java/com/integrated/techhub/member/domain/repository/MemberRepository.java @@ -5,4 +5,7 @@ public interface MemberRepository extends JpaRepository { + boolean existsByEmail(String email); + + } diff --git a/src/main/java/com/integrated/techhub/member/exception/MemberExistsException.java b/src/main/java/com/integrated/techhub/member/exception/MemberExistsException.java new file mode 100644 index 0000000..e9152a0 --- /dev/null +++ b/src/main/java/com/integrated/techhub/member/exception/MemberExistsException.java @@ -0,0 +1,14 @@ +package com.integrated.techhub.member.exception; + +import com.integrated.techhub.common.exception.ErrorCode; +import com.integrated.techhub.common.exception.TechHubException; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +public class MemberExistsException extends TechHubException { + + public MemberExistsException() { + super(new ErrorCode(BAD_REQUEST, "이미 존재하는 회원입니다.")); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 560f1a1..a86bc9f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -22,4 +22,20 @@ spring: secret: ${JWT_SECRET} accessExp: ${JWT_ACCESS_EXP} refreshExp: ${JWT_REFRESH_EXP} - prefix: ${JWT_PREFIX} \ No newline at end of file + prefix: ${JWT_PREFIX} + +--- +# mail +spring: + mail: + host: ${MAIL_HOST} + port: ${MAIL_PORT} + username: ${MAIL_USERNAME} + password: ${MAIL_PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true + required: true \ No newline at end of file diff --git a/src/main/resources/templates/mail.html b/src/main/resources/templates/mail.html new file mode 100644 index 0000000..ab6703d --- /dev/null +++ b/src/main/resources/templates/mail.html @@ -0,0 +1,11 @@ + + + +
+ +

+ +
+ + diff --git a/src/test/java/com/integrated/techhub/TechhubApplicationTests.java b/src/test/java/com/integrated/techhub/TechhubApplicationTests.java index 882abbd..0d0cca3 100644 --- a/src/test/java/com/integrated/techhub/TechhubApplicationTests.java +++ b/src/test/java/com/integrated/techhub/TechhubApplicationTests.java @@ -3,7 +3,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -@SpringBootTest(properties = {"spring.location=classpath:application.yml"}) +@SpringBootTest class TechhubApplicationTests { @Test diff --git a/src/test/java/com/integrated/techhub/auth/application/AuthQueryServiceTest.java b/src/test/java/com/integrated/techhub/auth/application/AuthQueryServiceTest.java new file mode 100644 index 0000000..c4642da --- /dev/null +++ b/src/test/java/com/integrated/techhub/auth/application/AuthQueryServiceTest.java @@ -0,0 +1,33 @@ +package com.integrated.techhub.auth.application; + +import com.integrated.techhub.member.domain.Member; +import com.integrated.techhub.member.domain.repository.MemberRepository; +import com.integrated.techhub.member.exception.MemberExistsException; +import com.integrated.techhub.member.fixture.MemberFixture; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Transactional +@SpringBootTest +class AuthQueryServiceTest { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + AuthQueryService authQueryService; + + @Test + void validateExistedMember() { + //given + Member member = memberRepository.save(MemberFixture.무민); + + //when, then + assertThrows(MemberExistsException.class, () -> authQueryService.validateExistedMember(member.getEmail())); + } + +} \ No newline at end of file diff --git a/src/test/java/com/integrated/techhub/auth/application/AuthServiceTest.java b/src/test/java/com/integrated/techhub/auth/application/AuthServiceTest.java new file mode 100644 index 0000000..13d8346 --- /dev/null +++ b/src/test/java/com/integrated/techhub/auth/application/AuthServiceTest.java @@ -0,0 +1,35 @@ +package com.integrated.techhub.auth.application; + +import com.integrated.techhub.auth.dto.SignUpRequest; +import com.integrated.techhub.member.domain.repository.MemberRepository; +import com.integrated.techhub.member.fixture.MemberFixture; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + + +@Transactional +@SpringBootTest +class AuthServiceTest { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private AuthService authService; + + @Test + void registerMember() { + //given + SignUpRequest request = MemberFixture.무민_회원가입_요청; + + //when + authService.registerMember(request); + + //then + Assertions.assertThat(memberRepository.findAll().size()).isEqualTo(1); + } + +} \ No newline at end of file diff --git a/src/test/java/com/integrated/techhub/auth/infra/encode/BCryptPasswordEncoderTest.java b/src/test/java/com/integrated/techhub/auth/infra/encode/BCryptPasswordEncoderTest.java new file mode 100644 index 0000000..42abe79 --- /dev/null +++ b/src/test/java/com/integrated/techhub/auth/infra/encode/BCryptPasswordEncoderTest.java @@ -0,0 +1,21 @@ +package com.integrated.techhub.auth.infra.encode; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BCryptPasswordEncoderTest { + + private static final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + + @Test + void encodeAndMatch() { + //given + int rawPassword = 1234; + String encodedPassword = passwordEncoder.encode(String.valueOf(rawPassword)); + + //when, then + assertTrue(passwordEncoder.isMatch(String.valueOf(rawPassword), encodedPassword)); + } + +} \ No newline at end of file diff --git a/src/test/java/com/integrated/techhub/auth/presentation/AuthControllerTest.java b/src/test/java/com/integrated/techhub/auth/presentation/AuthControllerTest.java new file mode 100644 index 0000000..1ece377 --- /dev/null +++ b/src/test/java/com/integrated/techhub/auth/presentation/AuthControllerTest.java @@ -0,0 +1,31 @@ +package com.integrated.techhub.auth.presentation; + +import com.integrated.techhub.common.acceptance.AcceptanceTest; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; + +import static com.integrated.techhub.member.fixture.MemberFixture.무민_회원가입_요청; +import static io.restassured.RestAssured.given; +import static io.restassured.http.ContentType.JSON; + +class AuthControllerTest extends AcceptanceTest { + + @Test + void signUp() { + //given + var body = 무민_회원가입_요청; + + //when + var 응답 = given() + .body(body) + .contentType(JSON) + .when() + .post("/auth/sign-up") + .then() + .extract(); + + //then + 응답.response().then().statusCode(HttpStatus.CREATED.value()); + } + +} diff --git a/src/test/java/com/integrated/techhub/common/acceptance/AcceptanceTest.java b/src/test/java/com/integrated/techhub/common/acceptance/AcceptanceTest.java index 485b8d2..11fe080 100644 --- a/src/test/java/com/integrated/techhub/common/acceptance/AcceptanceTest.java +++ b/src/test/java/com/integrated/techhub/common/acceptance/AcceptanceTest.java @@ -3,17 +3,17 @@ import io.restassured.RestAssured; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.test.context.jdbc.Sql; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.*; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + @Sql("/truncate.sql") -@DisplayNameGeneration(ReplaceUnderscores.class) @SpringBootTest(webEnvironment = RANDOM_PORT) +@DisplayNameGeneration(ReplaceUnderscores.class) public class AcceptanceTest { @LocalServerPort @@ -25,3 +25,4 @@ void setUp() { } } + diff --git a/src/test/java/com/integrated/techhub/mail/application/MailServiceMockTest.java b/src/test/java/com/integrated/techhub/mail/application/MailServiceMockTest.java new file mode 100644 index 0000000..db9a161 --- /dev/null +++ b/src/test/java/com/integrated/techhub/mail/application/MailServiceMockTest.java @@ -0,0 +1,48 @@ +package com.integrated.techhub.mail.application; + +import com.integrated.techhub.mail.domain.AuthorityCodeRepository; +import com.integrated.techhub.mail.domain.ITemplateEngine; +import com.integrated.techhub.mail.dto.MailSendRequest; +import com.integrated.techhub.mail.infra.mail.TestTemplateEngineWrapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessagePreparator; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class MailServiceMockTest { + + + private final ITemplateEngine iTemplateEngine = new TestTemplateEngineWrapper(); + + @Mock + private JavaMailSender mailSender; + + @Mock + private AuthorityCodeRepository authorityCodeRepository; + + private MailService mailService; + + @BeforeEach + void setUp() { + mailService = new MailService(mailSender, iTemplateEngine, authorityCodeRepository); + } + + @Test + void sendMail() { + //when + mailService.sendMail(new MailSendRequest("moomin@gmail.com")); + + //then + verify(mailSender, times(1)).send(any(MimeMessagePreparator.class)); + verify(authorityCodeRepository, times(1)).save(any()); + } + +} diff --git a/src/test/java/com/integrated/techhub/mail/application/MailServiceTest.java b/src/test/java/com/integrated/techhub/mail/application/MailServiceTest.java new file mode 100644 index 0000000..2f24b6d --- /dev/null +++ b/src/test/java/com/integrated/techhub/mail/application/MailServiceTest.java @@ -0,0 +1,67 @@ +package com.integrated.techhub.mail.application; + +import com.integrated.techhub.mail.domain.AuthorityCode; +import com.integrated.techhub.mail.domain.AuthorityCodeRepository; +import com.integrated.techhub.mail.dto.MailValidateRequest; +import com.integrated.techhub.mail.exception.AuthorityCodeNotFoundException; +import com.integrated.techhub.mail.exception.AuthorityCodeNotMatchException; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Transactional +@SpringBootTest +class MailServiceTest { + + @Autowired + private AuthorityCodeRepository authorityCodeRepository; + + @Autowired + private MailService mailService; + + + @Nested + class validateAuthorityCode { + + @Test + void 정상_인증코드_검증() { + //given + String email = "moomin@gmail.com"; + int code = 123456; + authorityCodeRepository.save(new AuthorityCode(email, code)); + MailValidateRequest request = new MailValidateRequest(email, code); + + //when, then + assertDoesNotThrow(() -> mailService.validateAuthorityCode(request)); + } + + @Test + void 존재하지_않는_이메일_인증코드_검증() { + //given + int code = 123456; + authorityCodeRepository.save(new AuthorityCode("moomin@gmail.com", code)); + MailValidateRequest request = new MailValidateRequest("wrong@gmail.com", code); + + //when, then + assertThrows(AuthorityCodeNotFoundException.class, () -> mailService.validateAuthorityCode(request)); + } + + @Test + void 인증코드_검증_실패() { + //given + String email = "moomin@gmail.com"; + authorityCodeRepository.save(new AuthorityCode(email, 123456)); + MailValidateRequest request = new MailValidateRequest(email, 654321); + + //when, then + assertThrows(AuthorityCodeNotMatchException.class, () -> mailService.validateAuthorityCode(request)); + } + + } + +} \ No newline at end of file diff --git a/src/test/java/com/integrated/techhub/mail/domain/type/MailInfoTest.java b/src/test/java/com/integrated/techhub/mail/domain/type/MailInfoTest.java new file mode 100644 index 0000000..fef52eb --- /dev/null +++ b/src/test/java/com/integrated/techhub/mail/domain/type/MailInfoTest.java @@ -0,0 +1,16 @@ +package com.integrated.techhub.mail.domain.type; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MailInfoTest { + + @Test + void 생성자() { + MailInfo mailInfo = MailInfo.DOMAIN; + + assertThat(mailInfo.getValue()).isEqualTo("tech.hub.kr"); + } + +} \ No newline at end of file diff --git a/src/test/java/com/integrated/techhub/mail/infra/mail/TestTemplateEngineWrapper.java b/src/test/java/com/integrated/techhub/mail/infra/mail/TestTemplateEngineWrapper.java new file mode 100644 index 0000000..24d037d --- /dev/null +++ b/src/test/java/com/integrated/techhub/mail/infra/mail/TestTemplateEngineWrapper.java @@ -0,0 +1,14 @@ +package com.integrated.techhub.mail.infra.mail; + +import com.integrated.techhub.mail.domain.ITemplateEngine; +import org.thymeleaf.context.IContext; + + +public class TestTemplateEngineWrapper implements ITemplateEngine { + + @Override + public String process(String template, IContext context) { + return "Test Template Engine"; + } + +} diff --git a/src/test/java/com/integrated/techhub/mail/presentation/MailControllerMockTest.java b/src/test/java/com/integrated/techhub/mail/presentation/MailControllerMockTest.java new file mode 100644 index 0000000..f3d12ed --- /dev/null +++ b/src/test/java/com/integrated/techhub/mail/presentation/MailControllerMockTest.java @@ -0,0 +1,55 @@ +package com.integrated.techhub.mail.presentation; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.integrated.techhub.auth.application.AuthQueryService; +import com.integrated.techhub.mail.application.MailService; +import com.integrated.techhub.member.fixture.MemberFixture; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@ExtendWith(SpringExtension.class) +@SuppressWarnings("NonAsciiCharacters") +@WebMvcTest(controllers = MailController.class) +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +class MailControllerMockTest { + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private MockMvc mockMvc; + + @MockBean + private AuthQueryService authQueryService; + + @MockBean + private MailService mailService; + + @Test + void sendMail() throws Exception { + //given + var content = objectMapper.writeValueAsString(MemberFixture.무민_회원가입_요청); + + //when + var 요청 = mockMvc.perform( + post("/mail/authorization-code") + .contentType(MediaType.APPLICATION_JSON) + .content(content)); + + //then + 요청.andExpect(status().isOk()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/integrated/techhub/mail/presentation/MailControllerSteps.java b/src/test/java/com/integrated/techhub/mail/presentation/MailControllerSteps.java new file mode 100644 index 0000000..68398a6 --- /dev/null +++ b/src/test/java/com/integrated/techhub/mail/presentation/MailControllerSteps.java @@ -0,0 +1,22 @@ +package com.integrated.techhub.mail.presentation; + +import com.integrated.techhub.mail.dto.MailValidateRequest; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; + +import static io.restassured.RestAssured.given; +import static io.restassured.http.ContentType.JSON; + +public class MailControllerSteps { + + public static ExtractableResponse 인증코드_유효한지_요청(MailValidateRequest body) { + return given() + .body(body) + .contentType(JSON) + .when() + .post("/mail/validation") + .then() + .extract(); + } + +} diff --git a/src/test/java/com/integrated/techhub/mail/presentation/MailControllerTest.java b/src/test/java/com/integrated/techhub/mail/presentation/MailControllerTest.java new file mode 100644 index 0000000..7d766d7 --- /dev/null +++ b/src/test/java/com/integrated/techhub/mail/presentation/MailControllerTest.java @@ -0,0 +1,33 @@ +package com.integrated.techhub.mail.presentation; + +import com.integrated.techhub.common.acceptance.AcceptanceTest; +import com.integrated.techhub.mail.domain.AuthorityCode; +import com.integrated.techhub.mail.domain.AuthorityCodeRepository; +import com.integrated.techhub.mail.dto.MailValidateRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; + +import static com.integrated.techhub.mail.presentation.MailControllerSteps.인증코드_유효한지_요청; + +@DisplayName("메일 인수 테스트") +class MailControllerTest extends AcceptanceTest { + + @Autowired + private AuthorityCodeRepository authorityCodeRepository; + + @Test + void validateAuthorityCode() { + //given + var email = "moomin@gmail.com"; + var code = 123456; + authorityCodeRepository.save(new AuthorityCode(email, code)); + MailValidateRequest body = new MailValidateRequest(email, code); + + var 응답 = 인증코드_유효한지_요청(body); + + 응답.response().then().statusCode(HttpStatus.OK.value()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/integrated/techhub/member/domain/AuthorityCodeTest.java b/src/test/java/com/integrated/techhub/member/domain/AuthorityCodeTest.java index 85423cd..edc4c5e 100644 --- a/src/test/java/com/integrated/techhub/member/domain/AuthorityCodeTest.java +++ b/src/test/java/com/integrated/techhub/member/domain/AuthorityCodeTest.java @@ -1,5 +1,6 @@ package com.integrated.techhub.member.domain; +import com.integrated.techhub.mail.domain.AuthorityCode; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; diff --git a/src/test/java/com/integrated/techhub/member/domain/MemberTest.java b/src/test/java/com/integrated/techhub/member/domain/MemberTest.java index 1b00f14..f4b3ccd 100644 --- a/src/test/java/com/integrated/techhub/member/domain/MemberTest.java +++ b/src/test/java/com/integrated/techhub/member/domain/MemberTest.java @@ -1,5 +1,8 @@ package com.integrated.techhub.member.domain; +import com.integrated.techhub.auth.infra.encode.BCryptPasswordEncoder; +import com.integrated.techhub.member.fixture.MemberFixture; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -8,8 +11,18 @@ class MemberTest { @Test void 생성자를_생성할수_있다() { - assertDoesNotThrow(() -> new Member("moomin@gmail.com", "moomin12", "무민", - "홍길동", "반갑습니다", Position.BE)); + assertDoesNotThrow(() -> MemberFixture.무민); + } + + @Test + void encodePassword() { + BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); + Member 무민 = MemberFixture.무민; + String rawPassword = 무민.getPassword(); + + 무민.encodePassword(bCryptPasswordEncoder); + + Assertions.assertThat(rawPassword).isNotEqualTo(무민.getPassword()); } } \ No newline at end of file diff --git a/src/test/java/com/integrated/techhub/member/fixture/MemberFixture.java b/src/test/java/com/integrated/techhub/member/fixture/MemberFixture.java new file mode 100644 index 0000000..fdac7ef --- /dev/null +++ b/src/test/java/com/integrated/techhub/member/fixture/MemberFixture.java @@ -0,0 +1,30 @@ +package com.integrated.techhub.member.fixture; + +import com.integrated.techhub.auth.dto.SignUpRequest; +import com.integrated.techhub.member.domain.Member; +import com.integrated.techhub.member.domain.Position; + +public class MemberFixture { + + public static final SignUpRequest 무민_회원가입_요청 = new SignUpRequest( + "moomin@gmail.com", + "moomin12", + "무민", + "ParkMooMin", + "홍길동", + "BE", + 5 + ); + + public static final Member 무민 = Member.builder() + .email("moomin@gmail.com") + .password("moomin12") + .nickname("무민") + .cardinalNumber(5) + .githubUsername("moomin") + .name("홍길동") + .bio("반갑습니다.") + .position(Position.BE) + .build(); + +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 76ca71e..2ebe907 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -38,3 +38,19 @@ logging: type: descriptor: sql: trace + +--- +# mail +spring: + mail: + host: localhost + port: 8080 + username: admin + password: admin + properties: + mail: + smtp: + auth: true + starttls: + enable: true + required: true \ No newline at end of file