diff --git a/.gitignore b/.gitignore index 2d19054a..ec44ee4d 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ application-local.yml ### Kotlin ### .kotlin +/src/main/resources/application.yml diff --git a/build.gradle.kts b/build.gradle.kts index a4011f94..b0d21fa7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,11 +29,15 @@ configurations { repositories { mavenCentral() + maven{url = uri("https://jitpack.io") } } dependencies { - implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") { + exclude(group = "org.hibernate", module = "hibernate-core") + } implementation("org.springframework.boot:spring-boot-starter-validation") + implementation("org.springframework.boot:spring-boot-starter-mail") implementation("org.springframework.boot:spring-boot-starter-web") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") @@ -41,9 +45,11 @@ dependencies { implementation("com.github.f4b6a3:ulid-creator:5.2.3") implementation("org.mariadb.jdbc:mariadb-java-client:2.7.3") implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0") - implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("org.springframework.cloud:spring-cloud-starter-openfeign") implementation ("io.awspring.cloud:spring-cloud-starter-aws:2.4.4") + implementation("com.github.in-seo:univcert:master-SNAPSHOT") { + exclude(group = "org.hamcrest", module= "harmcest-core") + } compileOnly("org.projectlombok:lombok") runtimeOnly("com.mysql:mysql-connector-j") runtimeOnly("com.h2database:h2") diff --git a/src/main/kotlin/com/dobby/backend/DobbyBackendApplication.kt b/src/main/kotlin/com/dobby/backend/DobbyBackendApplication.kt index b45b744b..4d21b68b 100644 --- a/src/main/kotlin/com/dobby/backend/DobbyBackendApplication.kt +++ b/src/main/kotlin/com/dobby/backend/DobbyBackendApplication.kt @@ -7,6 +7,7 @@ import org.springframework.boot.context.properties.ConfigurationPropertiesScan import org.springframework.cloud.openfeign.EnableFeignClients import org.springframework.context.annotation.ComponentScan import org.springframework.context.annotation.FilterType +import org.springframework.data.jpa.repository.config.EnableJpaAuditing @ComponentScan( includeFilters = [ComponentScan.Filter( @@ -17,6 +18,7 @@ import org.springframework.context.annotation.FilterType @SpringBootApplication @ConfigurationPropertiesScan @EnableFeignClients +@EnableJpaAuditing class DobbyBackendApplication fun main(args: Array) { diff --git a/src/main/kotlin/com/dobby/backend/application/mapper/SignupMapper.kt b/src/main/kotlin/com/dobby/backend/application/mapper/SignupMapper.kt index 33079701..6f6d6fe7 100644 --- a/src/main/kotlin/com/dobby/backend/application/mapper/SignupMapper.kt +++ b/src/main/kotlin/com/dobby/backend/application/mapper/SignupMapper.kt @@ -2,9 +2,11 @@ package com.dobby.backend.application.mapper import com.dobby.backend.infrastructure.database.entity.MemberEntity import com.dobby.backend.infrastructure.database.entity.ParticipantEntity +import com.dobby.backend.infrastructure.database.entity.ResearcherEntity import com.dobby.backend.infrastructure.database.entity.enum.MemberStatus import com.dobby.backend.infrastructure.database.entity.enum.RoleType import com.dobby.backend.presentation.api.dto.request.signup.ParticipantSignupRequest +import com.dobby.backend.presentation.api.dto.request.signup.ResearcherSignupRequest import com.dobby.backend.infrastructure.database.entity.AddressInfo as AddressInfo import com.dobby.backend.presentation.api.dto.request.signup.AddressInfo as DtoAddressInfo @@ -15,7 +17,7 @@ object SignupMapper { dto.area ) } - fun toMember(req: ParticipantSignupRequest): MemberEntity { + fun toParticipantMember(req: ParticipantSignupRequest): MemberEntity { return MemberEntity( id = 0, // Auto-generated oauthEmail = req.oauthEmail, @@ -23,8 +25,19 @@ object SignupMapper { status = MemberStatus.ACTIVE, role = RoleType.PARTICIPANT, contactEmail = req.contactEmail, + name = req.name + ) + } + + fun toResearcherMember(req: ResearcherSignupRequest): MemberEntity { + return MemberEntity( + id = 0, // Auto-generated + oauthEmail = req.oauthEmail, + provider = req.provider, + status = MemberStatus.ACTIVE, + role = RoleType.RESEARCHER, + contactEmail = req.contactEmail, name = req.name, - birthDate = req.birthDate ) } fun toParticipant( @@ -36,7 +49,23 @@ object SignupMapper { basicAddressInfo = toAddressInfo(req.basicAddressInfo), additionalAddressInfo = req.additionalAddressInfo?.let { toAddressInfo(it) }, preferType = req.preferType, - gender = req.gender + gender = req.gender, + birthDate = req.birthDate + ) + } + + fun toResearcher( + member: MemberEntity, + req: ResearcherSignupRequest + ): ResearcherEntity{ + return ResearcherEntity( + member = member, + univEmail = req.univEmail, + emailVerified = req.emailVerified, + univName = req.univName, + major = req.major, + labInfo = req.labInfo ) } -} \ No newline at end of file + +} diff --git a/src/main/kotlin/com/dobby/backend/application/mapper/VerificationMapper.kt b/src/main/kotlin/com/dobby/backend/application/mapper/VerificationMapper.kt new file mode 100644 index 00000000..de4019fc --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/application/mapper/VerificationMapper.kt @@ -0,0 +1,33 @@ +package com.dobby.backend.application.mapper + +import com.dobby.backend.infrastructure.database.entity.VerificationEntity +import com.dobby.backend.infrastructure.database.entity.enum.VerificationStatus +import com.dobby.backend.presentation.api.dto.request.signup.EmailSendRequest +import com.dobby.backend.presentation.api.dto.response.signup.EmailSendResponse +import com.dobby.backend.presentation.api.dto.response.signup.EmailVerificationResponse + +object VerificationMapper { + fun toEntity(req: EmailSendRequest, code : String): VerificationEntity { + return VerificationEntity( + id= 0, + univMail = req.univEmail, + verificationCode = code, + status = VerificationStatus.HOLD, + expiresAt = null + ) + } + + fun toSendResDto() : EmailSendResponse{ + return EmailSendResponse( + isSuccess = true, + message = "해당 학교 이메일로 성공적으로 코드를 전송했습니다. 10분 이내로 인증을 완료해주세요." + ) + } + + fun toVerifyResDto() : EmailVerificationResponse { + return EmailVerificationResponse( + isSuccess = true, + message = "학교 메일 인증이 완료되었습니다." + ) + } +} diff --git a/src/main/kotlin/com/dobby/backend/application/service/EmailService.kt b/src/main/kotlin/com/dobby/backend/application/service/EmailService.kt new file mode 100644 index 00000000..374930ee --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/application/service/EmailService.kt @@ -0,0 +1,26 @@ +package com.dobby.backend.application.service + +import com.dobby.backend.application.usecase.SignupUseCase.email.EmailCodeSendUseCase +import com.dobby.backend.application.usecase.SignupUseCase.email.EmailVerificationUseCase +import com.dobby.backend.presentation.api.dto.request.signup.EmailSendRequest +import com.dobby.backend.presentation.api.dto.request.signup.EmailVerificationRequest +import com.dobby.backend.presentation.api.dto.response.signup.EmailSendResponse +import com.dobby.backend.presentation.api.dto.response.signup.EmailVerificationResponse +import jakarta.transaction.Transactional +import org.springframework.stereotype.Service + +@Service +class EmailService( + private val emailCodeSendUseCase: EmailCodeSendUseCase, + private val emailVerificationUseCase: EmailVerificationUseCase +) { + @Transactional + fun sendEmail(req: EmailSendRequest) : EmailSendResponse{ + return emailCodeSendUseCase.execute(req) + } + + @Transactional + fun verifyCode(req: EmailVerificationRequest) : EmailVerificationResponse { + return emailVerificationUseCase.execute(req) + } +} diff --git a/src/main/kotlin/com/dobby/backend/application/service/OauthService.kt b/src/main/kotlin/com/dobby/backend/application/service/OauthService.kt index 48b4141b..f69c5695 100644 --- a/src/main/kotlin/com/dobby/backend/application/service/OauthService.kt +++ b/src/main/kotlin/com/dobby/backend/application/service/OauthService.kt @@ -2,7 +2,7 @@ package com.dobby.backend.application.service import com.dobby.backend.application.usecase.FetchGoogleUserInfoUseCase import com.dobby.backend.application.usecase.FetchNaverUserInfoUseCase -import com.dobby.backend.presentation.api.dto.request.auth.GoogleOauthLoginRequest +import com.dobby.backend.presentation.api.dto.request.auth.google.GoogleOauthLoginRequest import com.dobby.backend.presentation.api.dto.request.auth.NaverOauthLoginRequest import com.dobby.backend.presentation.api.dto.response.auth.OauthLoginResponse import org.springframework.stereotype.Service diff --git a/src/main/kotlin/com/dobby/backend/application/service/SignupService.kt b/src/main/kotlin/com/dobby/backend/application/service/SignupService.kt index 850f6e26..270d96ee 100644 --- a/src/main/kotlin/com/dobby/backend/application/service/SignupService.kt +++ b/src/main/kotlin/com/dobby/backend/application/service/SignupService.kt @@ -1,17 +1,34 @@ package com.dobby.backend.application.service -import com.dobby.backend.application.usecase.ParticipantSignupUseCase +import com.dobby.backend.application.usecase.SignupUseCase.CreateResearcherUseCase +import com.dobby.backend.application.usecase.SignupUseCase.ParticipantSignupUseCase +import com.dobby.backend.application.usecase.SignupUseCase.VerifyResearcherEmailUseCase +import com.dobby.backend.domain.exception.EmailNotValidateException import com.dobby.backend.presentation.api.dto.request.signup.ParticipantSignupRequest +import com.dobby.backend.presentation.api.dto.request.signup.ResearcherSignupRequest import com.dobby.backend.presentation.api.dto.response.signup.SignupResponse import jakarta.transaction.Transactional import org.springframework.stereotype.Service @Service class SignupService( - private val participantSignupUseCase: ParticipantSignupUseCase + private val participantSignupUseCase: ParticipantSignupUseCase, + private val createResearcherUseCase: CreateResearcherUseCase, + private val verifyResearcherEmailUseCase: VerifyResearcherEmailUseCase ) { @Transactional fun participantSignup(input: ParticipantSignupRequest): SignupResponse { - return participantSignupUseCase.execute(input) + return participantSignupUseCase.execute(input) } -} \ No newline at end of file + + @Transactional + fun researcherSignup(input: ResearcherSignupRequest) : SignupResponse{ + if(!input.emailVerified) { + throw EmailNotValidateException() + } + verifyResearcherEmailUseCase.execute(input.univEmail) + + return createResearcherUseCase.execute(input) + } + +} diff --git a/src/main/kotlin/com/dobby/backend/application/usecase/FetchGoogleUserInfoUseCase.kt b/src/main/kotlin/com/dobby/backend/application/usecase/FetchGoogleUserInfoUseCase.kt index 1a2bd9ab..abbaaae8 100644 --- a/src/main/kotlin/com/dobby/backend/application/usecase/FetchGoogleUserInfoUseCase.kt +++ b/src/main/kotlin/com/dobby/backend/application/usecase/FetchGoogleUserInfoUseCase.kt @@ -9,9 +9,9 @@ import com.dobby.backend.infrastructure.database.repository.MemberRepository import com.dobby.backend.infrastructure.feign.google.GoogleAuthFeignClient import com.dobby.backend.infrastructure.feign.google.GoogleUserInfoFeginClient import com.dobby.backend.infrastructure.token.JwtTokenProvider -import com.dobby.backend.presentation.api.dto.request.auth.GoogleOauthLoginRequest -import com.dobby.backend.presentation.api.dto.request.auth.GoogleTokenRequest -import com.dobby.backend.presentation.api.dto.response.auth.GoogleTokenResponse +import com.dobby.backend.presentation.api.dto.request.auth.google.GoogleTokenRequest +import com.dobby.backend.presentation.api.dto.request.auth.google.GoogleOauthLoginRequest +import com.dobby.backend.presentation.api.dto.response.auth.google.GoogleTokenResponse import com.dobby.backend.presentation.api.dto.response.auth.OauthLoginResponse import com.dobby.backend.util.AuthenticationUtils diff --git a/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/CreateResearcherUseCase.kt b/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/CreateResearcherUseCase.kt new file mode 100644 index 00000000..80f33748 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/CreateResearcherUseCase.kt @@ -0,0 +1,31 @@ +package com.dobby.backend.application.usecase.SignupUseCase + +import com.dobby.backend.application.mapper.SignupMapper +import com.dobby.backend.application.usecase.UseCase +import com.dobby.backend.infrastructure.database.repository.ResearcherRepository +import com.dobby.backend.infrastructure.token.JwtTokenProvider +import com.dobby.backend.presentation.api.dto.request.signup.ResearcherSignupRequest +import com.dobby.backend.presentation.api.dto.response.MemberResponse +import com.dobby.backend.presentation.api.dto.response.signup.SignupResponse +import com.dobby.backend.util.AuthenticationUtils + +class CreateResearcherUseCase( + private val researcherRepository: ResearcherRepository, + private val jwtTokenProvider: JwtTokenProvider +) : UseCase { + override fun execute(input: ResearcherSignupRequest): SignupResponse { + val memberEntity = SignupMapper.toResearcherMember(input) + val newResearcher = SignupMapper.toResearcher(memberEntity, input) + researcherRepository.save(newResearcher) + + val authentication = AuthenticationUtils.createAuthentication(memberEntity) + val accessToken = jwtTokenProvider.generateAccessToken(authentication) + val refreshToken = jwtTokenProvider.generateRefreshToken(authentication) + + return SignupResponse( + accessToken = accessToken, + refreshToken = refreshToken, + memberInfo = MemberResponse.fromDomain(newResearcher.member.toDomain()) + ) + } +} diff --git a/src/main/kotlin/com/dobby/backend/application/usecase/ParticipantSignupUseCase.kt b/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/ParticipantSignupUseCase.kt similarity index 84% rename from src/main/kotlin/com/dobby/backend/application/usecase/ParticipantSignupUseCase.kt rename to src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/ParticipantSignupUseCase.kt index ceaec532..53edf69a 100644 --- a/src/main/kotlin/com/dobby/backend/application/usecase/ParticipantSignupUseCase.kt +++ b/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/ParticipantSignupUseCase.kt @@ -1,6 +1,7 @@ -package com.dobby.backend.application.usecase +package com.dobby.backend.application.usecase.SignupUseCase import com.dobby.backend.application.mapper.SignupMapper +import com.dobby.backend.application.usecase.UseCase import com.dobby.backend.infrastructure.database.repository.ParticipantRepository import com.dobby.backend.infrastructure.token.JwtTokenProvider import com.dobby.backend.presentation.api.dto.request.signup.ParticipantSignupRequest @@ -11,10 +12,10 @@ import com.dobby.backend.util.AuthenticationUtils class ParticipantSignupUseCase ( private val participantRepository: ParticipantRepository, private val jwtTokenProvider: JwtTokenProvider -):UseCase +): UseCase { override fun execute(input: ParticipantSignupRequest): SignupResponse { - val memberEntity = SignupMapper.toMember(input) + val memberEntity = SignupMapper.toParticipantMember(input) val participantEntity = SignupMapper.toParticipant(memberEntity, input) val newParticipant = participantRepository.save(participantEntity) @@ -28,4 +29,4 @@ class ParticipantSignupUseCase ( refreshToken = refreshToken ) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/VerifyResearcherEmailUseCase.kt b/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/VerifyResearcherEmailUseCase.kt new file mode 100644 index 00000000..d177a99c --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/VerifyResearcherEmailUseCase.kt @@ -0,0 +1,22 @@ +package com.dobby.backend.application.usecase.SignupUseCase + +import com.dobby.backend.application.usecase.UseCase +import com.dobby.backend.domain.exception.EmailNotValidateException +import com.dobby.backend.domain.exception.VerifyInfoNotFoundException +import com.dobby.backend.infrastructure.database.entity.VerificationEntity +import com.dobby.backend.infrastructure.database.entity.enum.VerificationStatus +import com.dobby.backend.infrastructure.database.repository.VerificationRepository + +class VerifyResearcherEmailUseCase( + private val verificationRepository: VerificationRepository +) : UseCase { + override fun execute(input: String): VerificationEntity { + val verificationEntity = verificationRepository.findByUnivMail(input) + ?: throw VerifyInfoNotFoundException() + + if (verificationEntity.status != VerificationStatus.VERIFIED) { + throw EmailNotValidateException() + } + return verificationEntity + } +} diff --git a/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/email/EmailCodeSendUseCase.kt b/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/email/EmailCodeSendUseCase.kt new file mode 100644 index 00000000..18c0a4ef --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/email/EmailCodeSendUseCase.kt @@ -0,0 +1,71 @@ +package com.dobby.backend.application.usecase.SignupUseCase.email + +import com.dobby.backend.application.mapper.VerificationMapper +import com.dobby.backend.application.usecase.UseCase +import com.dobby.backend.domain.exception.* +import com.dobby.backend.domain.gateway.EmailGateway +import com.dobby.backend.infrastructure.database.entity.enum.VerificationStatus +import com.dobby.backend.infrastructure.database.repository.VerificationRepository +import com.dobby.backend.presentation.api.dto.request.signup.EmailSendRequest +import com.dobby.backend.presentation.api.dto.response.signup.EmailSendResponse +import com.dobby.backend.util.EmailUtils +import java.time.LocalDateTime + +class EmailCodeSendUseCase( + private val verificationRepository: VerificationRepository, + private val emailGateway: EmailGateway +) : UseCase { + override fun execute(input: EmailSendRequest): EmailSendResponse { + validateEmail(input.univEmail) + + val code = EmailUtils.generateCode() + reflectVerification(input, code) + + sendVerificationEmail(input, code) + return VerificationMapper.toSendResDto() + } + + private fun validateEmail(email : String){ + if(!EmailUtils.isDomainExists(email)) throw EmailDomainNotFoundException() + if(!EmailUtils.isUnivMail(email)) throw EmailNotUnivException() + } + + private fun reflectVerification(input: EmailSendRequest, code: String) { + val existingInfo = verificationRepository.findByUnivMail(input.univEmail) + + if (existingInfo != null) { + when (existingInfo.status) { + VerificationStatus.HOLD -> { + existingInfo.verificationCode = code + existingInfo.expiresAt = LocalDateTime.now().plusMinutes(10) + verificationRepository.save(existingInfo) + } + + VerificationStatus.VERIFIED -> { + throw EmailAlreadyVerifiedException() + } + } + } else { + val newVerificationInfo = VerificationMapper.toEntity(input, code) + verificationRepository.save(newVerificationInfo) + } + } + + private fun sendVerificationEmail(input: EmailSendRequest, code: String) { + val content = EMAIL_CONTENT_TEMPLATE.format(code) + emailGateway.sendEmail(input.univEmail, EMAIL_SUBJECT, content) + } + + companion object { + private const val EMAIL_SUBJECT = "그라밋 - 이메일 인증 코드 입니다." + private const val EMAIL_CONTENT_TEMPLATE = """ + 안녕하세요, 그라밋입니다. + + 아래의 코드는 이메일 인증을 위한 코드입니다: + + %s + + 10분 이내에 인증을 완료해주세요. + """ + } +} diff --git a/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/email/EmailVerificationUseCase.kt b/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/email/EmailVerificationUseCase.kt new file mode 100644 index 00000000..ef00c2ab --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/application/usecase/SignupUseCase/email/EmailVerificationUseCase.kt @@ -0,0 +1,36 @@ +package com.dobby.backend.application.usecase.SignupUseCase.email + +import com.dobby.backend.application.mapper.VerificationMapper +import com.dobby.backend.application.usecase.UseCase +import com.dobby.backend.domain.exception.CodeExpiredException +import com.dobby.backend.domain.exception.CodeNotCorrectException +import com.dobby.backend.domain.exception.VerifyInfoNotFoundException +import com.dobby.backend.infrastructure.database.entity.enum.VerificationStatus +import com.dobby.backend.infrastructure.database.repository.VerificationRepository +import com.dobby.backend.presentation.api.dto.request.signup.EmailVerificationRequest +import com.dobby.backend.presentation.api.dto.response.signup.EmailVerificationResponse +import java.time.LocalDateTime + + +class EmailVerificationUseCase( + private val verificationRepository: VerificationRepository +) : UseCase { + + override fun execute(input: EmailVerificationRequest): EmailVerificationResponse { + val info = verificationRepository.findByUnivMailAndStatus( + input.univEmail, + VerificationStatus.HOLD) + ?: throw VerifyInfoNotFoundException() + + if(input.inputCode != info.verificationCode) + throw CodeNotCorrectException() + + if(info.expiresAt?.isBefore(LocalDateTime.now()) == true) + throw CodeExpiredException() + + info.status = VerificationStatus.VERIFIED + verificationRepository.save(info) + + return VerificationMapper.toVerifyResDto() + } +} diff --git a/src/main/kotlin/com/dobby/backend/domain/exception/ErrorCode.kt b/src/main/kotlin/com/dobby/backend/domain/exception/ErrorCode.kt index 26f1b51c..2b81ae74 100644 --- a/src/main/kotlin/com/dobby/backend/domain/exception/ErrorCode.kt +++ b/src/main/kotlin/com/dobby/backend/domain/exception/ErrorCode.kt @@ -49,10 +49,22 @@ enum class ErrorCode( */ SIGNUP_ALREADY_MEMBER("SIGN_UP_001", "You've already joined", HttpStatus.CONFLICT), SIGNUP_UNSUPPORTED_ROLE("SIGN_UP_002", "Requested RoleType does not supported", HttpStatus.BAD_REQUEST), + SIGNUP_EMAIL_NOT_VALIDATED("SIGN_UP_003", "You should validate your school email first", HttpStatus.BAD_REQUEST), /** * Signin error codes */ SIGNIN_MEMBER_NOT_FOUND("SIGN_IN_001", "Member Not Found. Please signup. (Redirect URI must be /v1/auth/{role}/signup)", HttpStatus.NOT_FOUND), - SIGNIN_ROLE_MISMATCH("SIGN_IN_002", "Already registered member as %s. Please login as %s", HttpStatus.BAD_REQUEST) + SIGNIN_ROLE_MISMATCH("SIGN_IN_002", "Already registered member as %s. Please login as %s", HttpStatus.BAD_REQUEST), + + /** + * Email Verification error codes + */ + VERIFY_EMAIL_NOT_FOUND("VE001", "Email domain not found as real email domain", HttpStatus.BAD_REQUEST), + VERIFY_UNIV_NOT_FOUND("VER002", "Email domain not found as university email", HttpStatus.BAD_REQUEST), + VERIFY_INFO_NOT_FOUND("VER003", "Verification information is not found", HttpStatus.NOT_FOUND), + VERIFY_CODE_NOT_CORRECT("VER004", "Verification code is not correct", HttpStatus.BAD_REQUEST), + VERIFY_CODE_EXPIRED("VER005", "Verification code is expired", HttpStatus.BAD_REQUEST), + VERIFY_EMAIL_INVALID_FORMAT("VE006", "Email is invalid format", HttpStatus.BAD_REQUEST), + VERIFY_ALREADY_VERIFIED("VE007", "This email is already verified", HttpStatus.CONFLICT), } diff --git a/src/main/kotlin/com/dobby/backend/domain/exception/MemberException.kt b/src/main/kotlin/com/dobby/backend/domain/exception/MemberException.kt index 5d08f637..b7744061 100644 --- a/src/main/kotlin/com/dobby/backend/domain/exception/MemberException.kt +++ b/src/main/kotlin/com/dobby/backend/domain/exception/MemberException.kt @@ -7,4 +7,5 @@ open class MemberException( class MemberNotFoundException : MemberException(ErrorCode.MEMBER_NOT_FOUND) class AlreadyMemberException: MemberException(ErrorCode.SIGNUP_ALREADY_MEMBER) class SignInMemberException: MemberException(ErrorCode.SIGNIN_MEMBER_NOT_FOUND) -class RoleUnsupportedException: MemberException(ErrorCode.SIGNUP_UNSUPPORTED_ROLE) \ No newline at end of file +class RoleUnsupportedException: MemberException(ErrorCode.SIGNUP_UNSUPPORTED_ROLE) +class EmailNotValidateException: MemberException(ErrorCode.SIGNUP_EMAIL_NOT_VALIDATED) diff --git a/src/main/kotlin/com/dobby/backend/domain/exception/VerificationException.kt b/src/main/kotlin/com/dobby/backend/domain/exception/VerificationException.kt new file mode 100644 index 00000000..b997dad2 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/domain/exception/VerificationException.kt @@ -0,0 +1,13 @@ +package com.dobby.backend.domain.exception + +open class VerificationException ( + errorCode: ErrorCode, +) : DomainException(errorCode) + +class EmailFormatInvalidException : VerificationException(ErrorCode.VERIFY_EMAIL_INVALID_FORMAT) +class EmailDomainNotFoundException : VerificationException(ErrorCode.VERIFY_EMAIL_NOT_FOUND) +class EmailNotUnivException : VerificationException(ErrorCode.VERIFY_UNIV_NOT_FOUND) +class VerifyInfoNotFoundException: VerificationException(ErrorCode.VERIFY_INFO_NOT_FOUND) +class CodeNotCorrectException : VerificationException(ErrorCode.VERIFY_CODE_NOT_CORRECT) +class CodeExpiredException: VerificationException(ErrorCode.VERIFY_CODE_EXPIRED) +class EmailAlreadyVerifiedException: VerificationException(ErrorCode.VERIFY_ALREADY_VERIFIED) diff --git a/src/main/kotlin/com/dobby/backend/domain/gateway/EmailGateway.kt b/src/main/kotlin/com/dobby/backend/domain/gateway/EmailGateway.kt new file mode 100644 index 00000000..d5720070 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/domain/gateway/EmailGateway.kt @@ -0,0 +1,5 @@ +package com.dobby.backend.domain.gateway + +interface EmailGateway { + fun sendEmail(to: String, subject: String, content: String) +} diff --git a/src/main/kotlin/com/dobby/backend/domain/model/Member.kt b/src/main/kotlin/com/dobby/backend/domain/model/Member.kt index 8930b217..60cee40f 100644 --- a/src/main/kotlin/com/dobby/backend/domain/model/Member.kt +++ b/src/main/kotlin/com/dobby/backend/domain/model/Member.kt @@ -13,7 +13,6 @@ data class Member( val provider: ProviderType, val status: MemberStatus, val role: RoleType?, - val birthDate: LocalDate? ) { companion object { @@ -25,7 +24,6 @@ data class Member( provider: ProviderType, status: MemberStatus, role: RoleType, - birthDate: LocalDate, ) = Member( memberId = memberId, name = name, @@ -33,8 +31,7 @@ data class Member( contactEmail = contactEmail, provider = provider, status = status, - role = role, - birthDate = birthDate, + role = role ) } } diff --git a/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/MemberEntity.kt b/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/MemberEntity.kt index 7d78e9a5..77a03814 100644 --- a/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/MemberEntity.kt +++ b/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/MemberEntity.kt @@ -6,23 +6,22 @@ import com.dobby.backend.infrastructure.database.entity.enum.MemberStatus import com.dobby.backend.infrastructure.database.entity.enum.ProviderType import com.dobby.backend.infrastructure.database.entity.enum.RoleType import jakarta.persistence.* -import java.time.LocalDate @Entity(name = "member") @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn(name = "role_type") -class MemberEntity ( +class MemberEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") val id: Long, @Column(name = "oauth_email", length = 100, nullable = false, unique = true) - val oauthEmail : String, + val oauthEmail: String, @Column(name = "oauth_provider", nullable = false) @Enumerated(EnumType.STRING) - val provider : ProviderType, + val provider: ProviderType, @Column(nullable = false) @Enumerated(EnumType.STRING) @@ -33,13 +32,10 @@ class MemberEntity ( val role: RoleType?, @Column(name = "contact_email", length = 100, nullable = true) - val contactEmail : String?, + val contactEmail: String?, @Column(name = "name", length = 10, nullable = true) - val name : String?, - - @Column(name = "birth_date", nullable = true) - val birthDate : LocalDate?, + val name: String?, ) : AuditingEntity() { fun toDomain() = Member( @@ -49,21 +45,19 @@ class MemberEntity ( contactEmail = contactEmail, provider = provider, status = status, - role = role, - birthDate = birthDate + role = role ) companion object { fun fromDomain(member: Member) = with(member) { MemberEntity( id = memberId, - name = name, oauthEmail = oauthEmail, - contactEmail = contactEmail, provider = provider, status = status, role = role, - birthDate = birthDate + contactEmail = contactEmail, + name = name ) } } diff --git a/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/ParticipantEntity.kt b/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/ParticipantEntity.kt index c4737b9a..efc0d06c 100644 --- a/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/ParticipantEntity.kt +++ b/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/ParticipantEntity.kt @@ -2,7 +2,6 @@ package com.dobby.backend.infrastructure.database.entity import com.dobby.backend.infrastructure.database.entity.enum.GenderType import com.dobby.backend.infrastructure.database.entity.enum.MatchType -import com.dobby.backend.infrastructure.database.entity.enum.ProviderType import com.dobby.backend.infrastructure.database.entity.enum.RoleType import com.dobby.backend.infrastructure.database.entity.enum.areaInfo.Area import com.dobby.backend.infrastructure.database.entity.enum.areaInfo.Region @@ -20,6 +19,9 @@ class ParticipantEntity ( @Enumerated(EnumType.STRING) val gender: GenderType, + @Column(name = "birth_date", nullable = false) + val birthDate : LocalDate, + @Embedded @AttributeOverrides( AttributeOverride(name = "region", column = Column(name = "basic_region", nullable = false)), @@ -42,10 +44,9 @@ class ParticipantEntity ( id= member.id, oauthEmail = member.oauthEmail, provider = member.provider, - contactEmail= member.contactEmail, role = RoleType.PARTICIPANT, - name = member.name, - birthDate = member.birthDate + contactEmail= member.contactEmail, + name = member.name ) @Embeddable diff --git a/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/ResearcherEntity.kt b/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/ResearcherEntity.kt index a55466b5..a381fb7f 100644 --- a/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/ResearcherEntity.kt +++ b/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/ResearcherEntity.kt @@ -1,9 +1,7 @@ package com.dobby.backend.infrastructure.database.entity -import com.dobby.backend.infrastructure.database.entity.enum.ProviderType import com.dobby.backend.infrastructure.database.entity.enum.RoleType import jakarta.persistence.* -import java.time.LocalDate @Entity(name = "researcher") @DiscriminatorValue("RESEARCHER") @@ -25,13 +23,12 @@ class ResearcherEntity ( val major : String, @Column(name = "lab_info", length = 100, nullable = true) - val labInfo : String, + val labInfo : String?, ) : MemberEntity( id= member.id, oauthEmail = member.oauthEmail, provider = member.provider, - contactEmail= member.contactEmail, role = RoleType.RESEARCHER, - name = member.name, - birthDate = member.birthDate + contactEmail= member.contactEmail, + name = member.name ) diff --git a/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/VerificationEntity.kt b/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/VerificationEntity.kt new file mode 100644 index 00000000..7a2aafc6 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/VerificationEntity.kt @@ -0,0 +1,30 @@ +package com.dobby.backend.infrastructure.database.entity + +import AuditingEntity +import com.dobby.backend.infrastructure.database.entity.enum.VerificationStatus +import jakarta.persistence.* +import java.time.LocalDateTime + +@Entity(name = "VERIFICATION") +class VerificationEntity ( + @Id + @Column(name = "verification_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + val id: Long, + + @Column(name = "univ_mail", nullable = false, unique = true) + val univMail : String, + + @Column(name = "verification_code", length = 6, nullable = false, unique = true) + var verificationCode: String, + + @Column(name = "status", nullable = false) + @Enumerated(EnumType.STRING) + var status: VerificationStatus= VerificationStatus.HOLD, + + @Column(name = "expires_at", nullable = false) + var expiresAt : LocalDateTime ? = null +): AuditingEntity() { + @PrePersist + fun prePersist(){ if(expiresAt == null) expiresAt = LocalDateTime.now().plusMinutes(10)} +} diff --git a/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/enum/VerificationStatus.kt b/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/enum/VerificationStatus.kt new file mode 100644 index 00000000..77b38616 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/infrastructure/database/entity/enum/VerificationStatus.kt @@ -0,0 +1,6 @@ +package com.dobby.backend.infrastructure.database.entity.enum + +enum class VerificationStatus { + HOLD, + VERIFIED +} diff --git a/src/main/kotlin/com/dobby/backend/infrastructure/database/repository/ResearcherRepository.kt b/src/main/kotlin/com/dobby/backend/infrastructure/database/repository/ResearcherRepository.kt new file mode 100644 index 00000000..2f204106 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/infrastructure/database/repository/ResearcherRepository.kt @@ -0,0 +1,7 @@ +package com.dobby.backend.infrastructure.database.repository + +import com.dobby.backend.infrastructure.database.entity.ResearcherEntity +import org.springframework.data.jpa.repository.JpaRepository + +interface ResearcherRepository: JpaRepository { +} \ No newline at end of file diff --git a/src/main/kotlin/com/dobby/backend/infrastructure/database/repository/VerificationRepository.kt b/src/main/kotlin/com/dobby/backend/infrastructure/database/repository/VerificationRepository.kt new file mode 100644 index 00000000..aaa7e204 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/infrastructure/database/repository/VerificationRepository.kt @@ -0,0 +1,12 @@ +package com.dobby.backend.infrastructure.database.repository + +import com.dobby.backend.infrastructure.database.entity.VerificationEntity +import com.dobby.backend.infrastructure.database.entity.enum.VerificationStatus +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import java.time.LocalDateTime + +interface VerificationRepository : JpaRepository { + fun findByUnivMailAndStatus(univEmail: String, verified: VerificationStatus): VerificationEntity? + fun findByUnivMail(univEmail: String): VerificationEntity? +} diff --git a/src/main/kotlin/com/dobby/backend/infrastructure/feign/google/GoogleAuthFeignClient.kt b/src/main/kotlin/com/dobby/backend/infrastructure/feign/google/GoogleAuthFeignClient.kt index 014e7f11..d7bd791b 100644 --- a/src/main/kotlin/com/dobby/backend/infrastructure/feign/google/GoogleAuthFeignClient.kt +++ b/src/main/kotlin/com/dobby/backend/infrastructure/feign/google/GoogleAuthFeignClient.kt @@ -1,7 +1,7 @@ package com.dobby.backend.infrastructure.feign.google -import com.dobby.backend.presentation.api.dto.request.auth.GoogleTokenRequest -import com.dobby.backend.presentation.api.dto.response.auth.GoogleTokenResponse +import com.dobby.backend.presentation.api.dto.request.auth.google.GoogleTokenRequest +import com.dobby.backend.presentation.api.dto.response.auth.google.GoogleTokenResponse import org.springframework.cloud.openfeign.FeignClient import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody diff --git a/src/main/kotlin/com/dobby/backend/infrastructure/feign/google/GoogleUserInfoFeginClient.kt b/src/main/kotlin/com/dobby/backend/infrastructure/feign/google/GoogleUserInfoFeginClient.kt index deafd511..05d6dbab 100644 --- a/src/main/kotlin/com/dobby/backend/infrastructure/feign/google/GoogleUserInfoFeginClient.kt +++ b/src/main/kotlin/com/dobby/backend/infrastructure/feign/google/GoogleUserInfoFeginClient.kt @@ -1,6 +1,6 @@ package com.dobby.backend.infrastructure.feign.google -import com.dobby.backend.presentation.api.dto.response.auth.GoogleInfoResponse +import com.dobby.backend.presentation.api.dto.response.auth.google.GoogleInfoResponse import org.springframework.cloud.openfeign.FeignClient import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable diff --git a/src/main/kotlin/com/dobby/backend/infrastructure/gateway/EmailGatewayImpl.kt b/src/main/kotlin/com/dobby/backend/infrastructure/gateway/EmailGatewayImpl.kt new file mode 100644 index 00000000..7a68c594 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/infrastructure/gateway/EmailGatewayImpl.kt @@ -0,0 +1,26 @@ +package com.dobby.backend.infrastructure.gateway + +import com.dobby.backend.domain.gateway.EmailGateway +import org.springframework.mail.SimpleMailMessage +import org.springframework.mail.javamail.JavaMailSender +import org.springframework.stereotype.Component + +@Component +class EmailGatewayImpl( + private val mailSender: JavaMailSender +) : EmailGateway { + + override fun sendEmail(to: String, subject: String, content: String) { + val message = SimpleMailMessage() + message.setTo(to) + message.subject = subject + message.text = content + + try { + mailSender.send(message) + } catch (ex: Exception) { + throw IllegalStateException("이메일 발송 실패: ${ex.message}") + } + } +} + diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/controller/AuthController.kt b/src/main/kotlin/com/dobby/backend/presentation/api/controller/AuthController.kt index 6fa243ab..745b138e 100644 --- a/src/main/kotlin/com/dobby/backend/presentation/api/controller/AuthController.kt +++ b/src/main/kotlin/com/dobby/backend/presentation/api/controller/AuthController.kt @@ -6,7 +6,7 @@ import com.dobby.backend.presentation.api.dto.response.auth.OauthLoginResponse import com.dobby.backend.application.usecase.GenerateTokenWithRefreshToken import com.dobby.backend.application.usecase.GetMemberById import com.dobby.backend.infrastructure.database.entity.enum.RoleType -import com.dobby.backend.presentation.api.dto.request.auth.GoogleOauthLoginRequest +import com.dobby.backend.presentation.api.dto.request.auth.google.GoogleOauthLoginRequest import com.dobby.backend.presentation.api.dto.request.auth.MemberRefreshTokenRequest import com.dobby.backend.presentation.api.dto.request.auth.NaverOauthLoginRequest import com.dobby.backend.presentation.api.dto.response.MemberResponse @@ -16,7 +16,7 @@ import io.swagger.v3.oas.annotations.tags.Tag import jakarta.validation.Valid import org.springframework.web.bind.annotation.* -@Tag(name = "인증 관련 API") +@Tag(name = "인증 관련 API - /v1/auth ") @RestController @RequestMapping("/v1/auth") class AuthController( diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/controller/EmailController.kt b/src/main/kotlin/com/dobby/backend/presentation/api/controller/EmailController.kt new file mode 100644 index 00000000..8666b916 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/presentation/api/controller/EmailController.kt @@ -0,0 +1,43 @@ +package com.dobby.backend.presentation.api.controller + +import com.dobby.backend.application.service.EmailService +import com.dobby.backend.presentation.api.dto.payload.ApiResponse +import com.dobby.backend.presentation.api.dto.request.signup.EmailSendRequest +import com.dobby.backend.presentation.api.dto.request.signup.EmailVerificationRequest +import com.dobby.backend.presentation.api.dto.response.signup.EmailSendResponse +import com.dobby.backend.presentation.api.dto.response.signup.EmailVerificationResponse +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.validation.Valid +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 + +@Tag(name = "이메일 인증 API - /v1/emails") +@RestController +@RequestMapping("/v1/emails") +class EmailController( + private val emailService: EmailService +) { + @PostMapping("/send") + @Operation( + summary = "학교 메일 코드 전송 API- 연구자 회원가입 과정", + description = "연구자 회원가입 시, 학교 메일 인증 코드를 전송하는 API입니다." + ) + fun sendCode(@RequestBody @Valid emailSendRequest: EmailSendRequest) + : ApiResponse { + return ApiResponse.onSuccess(emailService.sendEmail(emailSendRequest)) + } + + @PostMapping("/verify") + @Operation( + summary = "학교 메일 코드 인증 API- 연구자 회원가입 과정", + description = "연구자 회원가입 시, 코드를 인증하는 API입니다." + ) + fun verifyCode(@RequestBody @Valid emailVerificationRequest: EmailVerificationRequest) + : ApiResponse { + return ApiResponse.onSuccess(emailService.verifyCode(emailVerificationRequest)) + } + +} diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/controller/SignupController.kt b/src/main/kotlin/com/dobby/backend/presentation/api/controller/SignupController.kt new file mode 100644 index 00000000..a16f3d9b --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/presentation/api/controller/SignupController.kt @@ -0,0 +1,42 @@ +package com.dobby.backend.presentation.api.controller + +import com.dobby.backend.application.service.SignupService +import com.dobby.backend.presentation.api.dto.request.signup.ParticipantSignupRequest +import com.dobby.backend.presentation.api.dto.request.signup.ResearcherSignupRequest +import com.dobby.backend.presentation.api.dto.response.signup.SignupResponse +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.validation.Valid +import org.springframework.web.bind.annotation.* + +@Tag(name = "회원가입 API - /v1/members/signup") +@RestController +@RequestMapping("/v1/members") +class SignupController( + private val signupService: SignupService +) { + @PostMapping("/signup/participant") + @Operation( + summary = "참여자 회원가입 API- OAuth 로그인 필수", + description = "참여자 OAuth 로그인 실패 시, 리다이렉팅하여 참여자 회원가입하는 API입니다." + ) + fun signupParticipants( + @RequestBody @Valid req: ParticipantSignupRequest + ): SignupResponse { + return signupService.participantSignup(req) + } + + @PostMapping("/signup/researcher") + @Operation( + summary = "연구자 회원가입 API - OAuth 로그인 필수", + description = """ + 연구자 OAuth 로그인 실패 시, 리다이렉팅하여 연구자 회원가입하는 API입니다. + ⚠️ 이메일 인증 성공 시에만 회원가입이 완료되어야 합니다. + """ + ) + fun signupResearchers( + @RequestBody @Valid req: ResearcherSignupRequest + ) : SignupResponse { + return signupService.researcherSignup(req) + } +} diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/controller/SignupController/ParticipantSignupController.kt b/src/main/kotlin/com/dobby/backend/presentation/api/controller/SignupController/ParticipantSignupController.kt deleted file mode 100644 index d50ef774..00000000 --- a/src/main/kotlin/com/dobby/backend/presentation/api/controller/SignupController/ParticipantSignupController.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.dobby.backend.presentation.api.controller.SignupController - -import com.dobby.backend.application.service.SignupService -import com.dobby.backend.infrastructure.database.entity.enum.RoleType.* -import com.dobby.backend.presentation.api.dto.request.signup.ParticipantSignupRequest -import com.dobby.backend.presentation.api.dto.response.signup.SignupResponse -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.tags.Tag -import jakarta.validation.Valid -import org.springframework.web.bind.annotation.* - -@Tag(name = "회원가입 API") -@RestController -@RequestMapping("/v1/participants") -class ParticipantSignupController( - private val signupService: SignupService -) { - @PostMapping("/signup") - @Operation( - summary = "참여자 회원가입 API- OAuth 로그인 필수", - description = "참여자 OAuth 로그인 실패 시, 리다이렉팅하여 참여자 회원가입하는 API입니다." - ) - fun signup( - @RequestBody @Valid req: ParticipantSignupRequest - ): SignupResponse { - return signupService.participantSignup(req) - } -} diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/auth/GoogleOauthLoginRequest.kt b/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/auth/OauthLoginRequest.kt similarity index 100% rename from src/main/kotlin/com/dobby/backend/presentation/api/dto/request/auth/GoogleOauthLoginRequest.kt rename to src/main/kotlin/com/dobby/backend/presentation/api/dto/request/auth/OauthLoginRequest.kt diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/auth/google/GoogleOauthLoginRequest.kt b/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/auth/google/GoogleOauthLoginRequest.kt new file mode 100644 index 00000000..2dc90527 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/auth/google/GoogleOauthLoginRequest.kt @@ -0,0 +1,8 @@ +package com.dobby.backend.presentation.api.dto.request.auth.google + +import jakarta.validation.constraints.NotBlank + +data class GoogleOauthLoginRequest( + @NotBlank(message = "authorizationCode는 공백일 수 없습니다.") + val authorizationCode: String +) diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/auth/GoogleTokenRequest.kt b/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/auth/google/GoogleTokenRequest.kt similarity index 85% rename from src/main/kotlin/com/dobby/backend/presentation/api/dto/request/auth/GoogleTokenRequest.kt rename to src/main/kotlin/com/dobby/backend/presentation/api/dto/request/auth/google/GoogleTokenRequest.kt index 22d5455b..9fddc59c 100644 --- a/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/auth/GoogleTokenRequest.kt +++ b/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/auth/google/GoogleTokenRequest.kt @@ -1,7 +1,6 @@ -package com.dobby.backend.presentation.api.dto.request.auth +package com.dobby.backend.presentation.api.dto.request.auth.google import com.fasterxml.jackson.annotation.JsonProperty - data class GoogleTokenRequest ( @JsonProperty("code") val code: String, @@ -17,4 +16,4 @@ data class GoogleTokenRequest ( @JsonProperty("grant_type") val grantType: String = "authorization_code" -) \ No newline at end of file +) diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/signup/EmailSendRequest.kt b/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/signup/EmailSendRequest.kt new file mode 100644 index 00000000..abf1c493 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/signup/EmailSendRequest.kt @@ -0,0 +1,10 @@ +package com.dobby.backend.presentation.api.dto.request.signup + +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.NotNull + +data class EmailSendRequest ( + @Email(message = "학교 이메일이 유효하지 않습니다.") + @NotNull(message = "학교 이메일은 공백일 수 없습니다.") + val univEmail : String, +) diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/signup/EmailVerificationRequest.kt b/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/signup/EmailVerificationRequest.kt new file mode 100644 index 00000000..30e2d8a8 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/signup/EmailVerificationRequest.kt @@ -0,0 +1,13 @@ +package com.dobby.backend.presentation.api.dto.request.signup + +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.NotNull + +data class EmailVerificationRequest ( + @Email(message = "학교 이메일이 유효하지 않습니다.") + @NotNull(message = "학교 이메일은 공백일 수 없습니다.") + val univEmail : String, + + @NotNull(message = "인증 코드는 공백일 수 없습니다.") + val inputCode: String, +) diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/signup/ParticipantSignupRequest.kt b/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/signup/ParticipantSignupRequest.kt index c640d658..3712cbff 100644 --- a/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/signup/ParticipantSignupRequest.kt +++ b/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/signup/ParticipantSignupRequest.kt @@ -50,4 +50,4 @@ data class AddressInfo( @NotBlank(message = "시/군/구 정보는 공백일 수 없습니다.") val area: Area -) \ No newline at end of file +) diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/signup/ResearcherSignupRequest.kt b/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/signup/ResearcherSignupRequest.kt new file mode 100644 index 00000000..b5a02de7 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/presentation/api/dto/request/signup/ResearcherSignupRequest.kt @@ -0,0 +1,37 @@ +package com.dobby.backend.presentation.api.dto.request.signup + +import com.dobby.backend.infrastructure.database.entity.enum.ProviderType +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull + +data class ResearcherSignupRequest( + @Email(message = "OAuth 이메일이 유효하지 않습니다.") + @NotBlank(message = "OAuth 이메일은 공백일 수 없습니다.") + val oauthEmail: String, + + @NotBlank(message = "OAuth provider은 공백일 수 없습니다.") + val provider: ProviderType, + + @Email(message= "연락 받을 이메일이 유효하지 않습니다.") + @NotBlank(message = "연락 받을 이메일은 공백일 수 없습니다.") + val contactEmail: String, + + @Email(message = "학교 이메일이 유효하지 않습니다.") + @NotBlank(message = "학교 이메일은 공백일 수 없습니다.") + val univEmail : String, + + @NotNull(message = "이메일 인증 여부는 공백일 수 없습니다.") + var emailVerified: Boolean, + + @NotBlank(message = "이름은 공백일 수 없습니다.") + val name: String, + + @NotBlank(message = "학교명은 공백일 수 없습니다.") + val univName: String, + + @NotBlank(message = "전공명은 공백일 수 없습니다.") + val major: String, + + val labInfo: String? +) diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/auth/GoogleInfoResponse.kt b/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/auth/google/GoogleInfoResponse.kt similarity index 73% rename from src/main/kotlin/com/dobby/backend/presentation/api/dto/response/auth/GoogleInfoResponse.kt rename to src/main/kotlin/com/dobby/backend/presentation/api/dto/response/auth/google/GoogleInfoResponse.kt index ea50e2df..46652cd6 100644 --- a/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/auth/GoogleInfoResponse.kt +++ b/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/auth/google/GoogleInfoResponse.kt @@ -1,4 +1,4 @@ -package com.dobby.backend.presentation.api.dto.response.auth +package com.dobby.backend.presentation.api.dto.response.auth.google import com.fasterxml.jackson.annotation.JsonProperty diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/auth/GoogleTokenResponse.kt b/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/auth/google/GoogleTokenResponse.kt similarity index 68% rename from src/main/kotlin/com/dobby/backend/presentation/api/dto/response/auth/GoogleTokenResponse.kt rename to src/main/kotlin/com/dobby/backend/presentation/api/dto/response/auth/google/GoogleTokenResponse.kt index e2cbe595..03dfd297 100644 --- a/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/auth/GoogleTokenResponse.kt +++ b/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/auth/google/GoogleTokenResponse.kt @@ -1,8 +1,8 @@ -package com.dobby.backend.presentation.api.dto.response.auth +package com.dobby.backend.presentation.api.dto.response.auth.google import com.fasterxml.jackson.annotation.JsonProperty data class GoogleTokenResponse ( @JsonProperty("access_token") val accessToken: String -) \ No newline at end of file +) diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/signup/EmailSendResponse.kt b/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/signup/EmailSendResponse.kt new file mode 100644 index 00000000..08f2ea5a --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/signup/EmailSendResponse.kt @@ -0,0 +1,11 @@ +package com.dobby.backend.presentation.api.dto.response.signup + +import io.swagger.v3.oas.annotations.media.Schema + +data class EmailSendResponse( + @Schema(description = "학교 이메일 인증을 성공하여, 코드를 성공적으로 전송했는지 여부입니다.") + val isSuccess: Boolean, + + @Schema(description = "반환 성공 메시지 입니다.") + val message : String +) diff --git a/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/signup/EmailVerificationResponse.kt b/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/signup/EmailVerificationResponse.kt new file mode 100644 index 00000000..b4238353 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/presentation/api/dto/response/signup/EmailVerificationResponse.kt @@ -0,0 +1,11 @@ +package com.dobby.backend.presentation.api.dto.response.signup + +import io.swagger.v3.oas.annotations.media.Schema + +data class EmailVerificationResponse( + @Schema(description = "학교 이메일 인증이 성공했는지 여부입니다.") + val isSuccess: Boolean, + + @Schema(description = "인증 코드 인증 성공 메시지 입니다.") + val message : String +) diff --git a/src/main/kotlin/com/dobby/backend/util/EmailUtils.kt b/src/main/kotlin/com/dobby/backend/util/EmailUtils.kt new file mode 100644 index 00000000..11457358 --- /dev/null +++ b/src/main/kotlin/com/dobby/backend/util/EmailUtils.kt @@ -0,0 +1,40 @@ +package com.dobby.backend.util + +import com.dobby.backend.domain.exception.EmailFormatInvalidException +import java.util.* +import javax.naming.directory.Attributes +import javax.naming.directory.InitialDirContext + +object EmailUtils{ + private fun extractDomain(email:String): String { + if(!email.contains("@")) throw EmailFormatInvalidException() + return email.substringAfter("@") + } + fun isDomainExists(email: String): Boolean { + val domain = extractDomain(email) + return try { + val env = Hashtable() + env["java.naming.factory.initial"] = "com.sun.jndi.dns.DnsContextFactory" + val ctx = InitialDirContext(env) + val attributes: Attributes = ctx.getAttributes(domain, arrayOf("MX")) + val mxRecords = attributes["MX"] + mxRecords != null + } catch (ex: Exception) { + false + } + } + + fun isUnivMail(email: String): Boolean { + val eduDomains = setOf( + "postech.edu", + "kaist.edu", + "handong.edu", + "ewhain.net" + ) + return email.endsWith("@ac.kr") || eduDomains.any { email.endsWith(it) } + } + fun generateCode(): String { + val randomNum = (0..999999).random() + return String.format("%06d", randomNum) + } +} diff --git a/src/test/kotlin/com/dobby/backend/DobbyBackendApplicationTests.kt b/src/test/kotlin/com/dobby/backend/DobbyBackendApplicationTests.kt index b45d5902..1601c726 100644 --- a/src/test/kotlin/com/dobby/backend/DobbyBackendApplicationTests.kt +++ b/src/test/kotlin/com/dobby/backend/DobbyBackendApplicationTests.kt @@ -2,16 +2,27 @@ package com.dobby.backend import org.junit.jupiter.api.Test import org.mockito.Mockito +import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.boot.test.context.SpringBootTest +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration import org.springframework.data.jpa.repository.config.EnableJpaAuditing +import org.springframework.mail.javamail.JavaMailSender import org.springframework.test.context.ActiveProfiles @SpringBootTest -@EnableJpaAuditing @ActiveProfiles("test") class DobbyBackendApplicationTests { + @Test fun contextLoads() { } + @Configuration + class MockConfig { + @Bean + fun javaMailSender() : JavaMailSender { + return Mockito.mock(JavaMailSender::class.java) + } + } } diff --git a/src/test/kotlin/com/dobby/backend/application/mapper/SignupMapperTest.kt b/src/test/kotlin/com/dobby/backend/application/mapper/SignupMapperTest.kt index 61534482..62e559bf 100644 --- a/src/test/kotlin/com/dobby/backend/application/mapper/SignupMapperTest.kt +++ b/src/test/kotlin/com/dobby/backend/application/mapper/SignupMapperTest.kt @@ -23,7 +23,7 @@ class SignupMapperTest : BehaviorSpec({ } } - `when`("toMember 메서드가 호출되면") { + `when`("toParticipantMember 메서드가 호출되면") { val request = ParticipantSignupRequest( oauthEmail = "test@example.com", provider = ProviderType.GOOGLE, @@ -35,14 +35,13 @@ class SignupMapperTest : BehaviorSpec({ preferType = MatchType.HYBRID, gender = GenderType.FEMALE ) - val result = SignupMapper.toMember(request) + val result = SignupMapper.toParticipantMember(request) then("올바른 MemberEntity 객체가 반환되어야 한다") { result.oauthEmail shouldBe "test@example.com" result.provider shouldBe ProviderType.GOOGLE result.contactEmail shouldBe "contact@example.com" result.name shouldBe "Test User" - result.birthDate shouldBe LocalDate.of(2002, 11, 21) result.role shouldBe RoleType.PARTICIPANT result.status shouldBe MemberStatus.ACTIVE } @@ -60,16 +59,17 @@ class SignupMapperTest : BehaviorSpec({ preferType = MatchType.HYBRID, gender = GenderType.FEMALE ) - val member = SignupMapper.toMember(request) + val member = SignupMapper.toParticipantMember(request) val result = SignupMapper.toParticipant(member, request) then("올바른 ParticipantEntity 객체가 반환되어야 한다") { result.member.oauthEmail shouldBe "test@example.com" result.basicAddressInfo.region shouldBe Region.SEOUL result.basicAddressInfo.area shouldBe Area.SEOUL_ALL + result.birthDate shouldBe LocalDate.of(2002, 11, 21) result.additionalAddressInfo?.region shouldBe Region.GYEONGGI result.additionalAddressInfo?.area shouldBe Area.GWANGMYEONGSI } } } -}) \ No newline at end of file +}) diff --git a/src/test/kotlin/com/dobby/backend/application/mapper/VerificationMapperTest.kt b/src/test/kotlin/com/dobby/backend/application/mapper/VerificationMapperTest.kt new file mode 100644 index 00000000..63ae9119 --- /dev/null +++ b/src/test/kotlin/com/dobby/backend/application/mapper/VerificationMapperTest.kt @@ -0,0 +1,54 @@ +package com.dobby.backend.application.mapper + +import com.dobby.backend.infrastructure.database.entity.enum.VerificationStatus +import com.dobby.backend.presentation.api.dto.request.signup.EmailSendRequest +import com.dobby.backend.presentation.api.dto.response.signup.EmailSendResponse +import com.dobby.backend.presentation.api.dto.response.signup.EmailVerificationResponse +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe + +class VerificationMapperTest : BehaviorSpec({ + + given("EmailSendRequest와 인증 코드가 주어졌을 때") { + val request = EmailSendRequest(univEmail = "test@postech.edu") + val code = "123456" + + `when`("toEntity 메서드가 호출되면") { + val result = VerificationMapper.toEntity(request, code) + + then("VerificationEntity 객체를 반환해야 한다") { + result.univMail shouldBe request.univEmail + result.verificationCode shouldBe code + result.status shouldBe VerificationStatus.HOLD + result.expiresAt shouldBe null + } + } + } + + given("toSendResDto 메서드가 호출되었을 때") { + `when`("호출되면") { + val result = VerificationMapper.toSendResDto() + + then("EmailSendResponse 객체를 반환해야 한다") { + result shouldBe EmailSendResponse( + isSuccess = true, + message = "해당 학교 이메일로 성공적으로 코드를 전송했습니다. 10분 이내로 인증을 완료해주세요." + ) + } + } + } + + given("toVerifyResDto 메서드가 호출되었을 때") { + `when`("호출되면") { + val result = VerificationMapper.toVerifyResDto() + + then("EmailVerificationResponse 객체를 반환해야 한다") { + result shouldBe EmailVerificationResponse( + isSuccess = true, + message = "학교 메일 인증이 완료되었습니다." + ) + } + } + } +}) + diff --git a/src/test/kotlin/com/dobby/backend/application/service/OauthServiceTest.kt b/src/test/kotlin/com/dobby/backend/application/service/OauthServiceTest.kt index 2aabab9d..b2b94d8e 100644 --- a/src/test/kotlin/com/dobby/backend/application/service/OauthServiceTest.kt +++ b/src/test/kotlin/com/dobby/backend/application/service/OauthServiceTest.kt @@ -3,7 +3,7 @@ import com.dobby.backend.application.usecase.FetchGoogleUserInfoUseCase import com.dobby.backend.application.usecase.FetchNaverUserInfoUseCase import com.dobby.backend.infrastructure.database.entity.enum.ProviderType import com.dobby.backend.infrastructure.database.entity.enum.RoleType -import com.dobby.backend.presentation.api.dto.request.auth.GoogleOauthLoginRequest +import com.dobby.backend.presentation.api.dto.request.auth.google.GoogleOauthLoginRequest import com.dobby.backend.presentation.api.dto.response.MemberResponse import com.dobby.backend.presentation.api.dto.response.auth.OauthLoginResponse import io.kotest.core.spec.style.BehaviorSpec diff --git a/src/test/kotlin/com/dobby/backend/application/service/SignupServiceTest.kt b/src/test/kotlin/com/dobby/backend/application/service/SignupServiceTest.kt new file mode 100644 index 00000000..1ab70f6c --- /dev/null +++ b/src/test/kotlin/com/dobby/backend/application/service/SignupServiceTest.kt @@ -0,0 +1,65 @@ +package com.dobby.backend.application.service + +import com.dobby.backend.application.usecase.SignupUseCase.CreateResearcherUseCase +import com.dobby.backend.application.usecase.SignupUseCase.ParticipantSignupUseCase +import com.dobby.backend.application.usecase.SignupUseCase.VerifyResearcherEmailUseCase +import com.dobby.backend.domain.exception.EmailNotValidateException +import com.dobby.backend.infrastructure.database.entity.VerificationEntity +import com.dobby.backend.infrastructure.database.entity.enum.* +import com.dobby.backend.presentation.api.dto.request.signup.ParticipantSignupRequest +import com.dobby.backend.presentation.api.dto.request.signup.ResearcherSignupRequest +import com.dobby.backend.presentation.api.dto.response.signup.SignupResponse +import com.dobby.backend.infrastructure.database.entity.enum.areaInfo.Area +import com.dobby.backend.infrastructure.database.entity.enum.areaInfo.Region +import com.dobby.backend.infrastructure.database.repository.MemberRepository +import com.dobby.backend.infrastructure.token.JwtTokenProvider +import com.dobby.backend.presentation.api.dto.request.signup.AddressInfo +import com.dobby.backend.presentation.api.dto.response.MemberResponse +import io.kotest.matchers.shouldBe +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.BehaviorSpec +import io.mockk.* +import java.time.LocalDate + +class SignupServiceTest : BehaviorSpec({ + + val participantSignupUseCase = mockk(relaxed = true) + val createResearcherUseCase = mockk(relaxed = true) + val verifyResearcherEmailUseCase = mockk(relaxed = true) + + val signupService = SignupService( + participantSignupUseCase, + createResearcherUseCase, + verifyResearcherEmailUseCase + ) + + beforeTest { + clearMocks(verifyResearcherEmailUseCase, createResearcherUseCase, participantSignupUseCase) + } + + given("emailVerified가 false인 ResearcherSignupRequest가 주어졌을 때") { + val invalidRequest = ResearcherSignupRequest( + oauthEmail = "test@example.com", + provider = ProviderType.GOOGLE, + contactEmail = "contact@example.com", + univEmail = "ss@ewha.ac.kr", + emailVerified = false, + name = "Test User", + univName = "이화여자대학교", + major = "인공지능・소프트웨어학부 인공지능융합전공", + labInfo = "불안정한 통신 상황에서 자생적 엣지 네트워크 구성을 통한 분산 학습 아키텍처 개발" + ) + + `when`("SignupService의 researcherSignup이 호출되면") { + println("테스트 실행: emailVerified = ${invalidRequest.emailVerified}, univEmail = ${invalidRequest.univEmail}") + + then("EmailNotValidateException이 발생한다") { + shouldThrow { + signupService.researcherSignup(invalidRequest) + } + verify(exactly = 0) { verifyResearcherEmailUseCase.execute(any()) } + } + } + } +}) + diff --git a/src/test/kotlin/com/dobby/backend/application/usecase/FetchGoogleUserInfoTest.kt b/src/test/kotlin/com/dobby/backend/application/usecase/FetchGoogleUserInfoTest.kt index 7aa36ad6..24eba67b 100644 --- a/src/test/kotlin/com/dobby/backend/application/usecase/FetchGoogleUserInfoTest.kt +++ b/src/test/kotlin/com/dobby/backend/application/usecase/FetchGoogleUserInfoTest.kt @@ -8,14 +8,13 @@ import com.dobby.backend.infrastructure.database.repository.MemberRepository import com.dobby.backend.infrastructure.feign.google.GoogleAuthFeignClient import com.dobby.backend.infrastructure.feign.google.GoogleUserInfoFeginClient import com.dobby.backend.infrastructure.token.JwtTokenProvider -import com.dobby.backend.presentation.api.dto.request.auth.GoogleOauthLoginRequest -import com.dobby.backend.presentation.api.dto.response.auth.GoogleTokenResponse +import com.dobby.backend.presentation.api.dto.request.auth.google.GoogleOauthLoginRequest +import com.dobby.backend.presentation.api.dto.response.auth.google.GoogleTokenResponse import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk import org.springframework.test.context.ActiveProfiles -import java.time.LocalDate @ActiveProfiles("test") class FetchGoogleUserInfoUseCaseTest : BehaviorSpec({ @@ -38,12 +37,11 @@ class FetchGoogleUserInfoUseCaseTest : BehaviorSpec({ val mockMember = MemberEntity( id = 1L, oauthEmail = "test@example.com", - name = "Test User", + provider = ProviderType.GOOGLE, status = MemberStatus.ACTIVE, role = RoleType.PARTICIPANT, - birthDate = LocalDate.of(2002, 11, 21), contactEmail = "contact@example.com", - provider = ProviderType.GOOGLE + name = "Test User" ) every { googleAuthProperties.clientId } returns "mock-client-id" @@ -70,4 +68,4 @@ class FetchGoogleUserInfoUseCaseTest : BehaviorSpec({ } } } -}) \ No newline at end of file +}) diff --git a/src/test/kotlin/com/dobby/backend/application/usecase/FetchNaverUserInfoTest.kt b/src/test/kotlin/com/dobby/backend/application/usecase/FetchNaverUserInfoTest.kt index a14507f6..0d0d4691 100644 --- a/src/test/kotlin/com/dobby/backend/application/usecase/FetchNaverUserInfoTest.kt +++ b/src/test/kotlin/com/dobby/backend/application/usecase/FetchNaverUserInfoTest.kt @@ -42,7 +42,6 @@ class FetchNaverUserInfoUseCaseTest : BehaviorSpec({ name = "Test User", status = MemberStatus.ACTIVE, role = RoleType.PARTICIPANT, - birthDate = LocalDate.of(2002, 11, 21), contactEmail = "contact@example.com", provider = ProviderType.NAVER ) diff --git a/src/test/kotlin/com/dobby/backend/application/usecase/GenerateTestTokenTest.kt b/src/test/kotlin/com/dobby/backend/application/usecase/GenerateTestTokenTest.kt index 51334c99..134cac42 100644 --- a/src/test/kotlin/com/dobby/backend/application/usecase/GenerateTestTokenTest.kt +++ b/src/test/kotlin/com/dobby/backend/application/usecase/GenerateTestTokenTest.kt @@ -20,7 +20,7 @@ class GenerateTestTokenTest: BehaviorSpec({ given("memberId가 주어졌을 때") { val member = Member(memberId = 1, oauthEmail = "dlawotn3@naver.com", contactEmail = "dlawotn3@naver.com", provider = ProviderType.NAVER, role = RoleType.PARTICIPANT, name = "dobby", - birthDate = LocalDate.of(2000, 7, 8), status = MemberStatus.ACTIVE) + status = MemberStatus.ACTIVE) val accessToken = "testAccessToken" val refreshToken = "testRefreshToken" diff --git a/src/test/kotlin/com/dobby/backend/application/usecase/GenerateTokenWithRefreshTokenTest.kt b/src/test/kotlin/com/dobby/backend/application/usecase/GenerateTokenWithRefreshTokenTest.kt index 263bc08c..4b5039e2 100644 --- a/src/test/kotlin/com/dobby/backend/application/usecase/GenerateTokenWithRefreshTokenTest.kt +++ b/src/test/kotlin/com/dobby/backend/application/usecase/GenerateTokenWithRefreshTokenTest.kt @@ -21,7 +21,7 @@ class GenerateTokenWithRefreshTokenTest : BehaviorSpec({ val validRefreshToken = "validRefreshToken" val member = Member(memberId = 1, oauthEmail = "dlawotn3@naver.com", contactEmail = "dlawotn3@naver.com", provider = ProviderType.NAVER, role = RoleType.PARTICIPANT, name = "dobby", - birthDate = LocalDate.of(2000, 7, 8), status = MemberStatus.ACTIVE) + status = MemberStatus.ACTIVE) val accessToken = "newAccessToken" val newRefreshToken = "newRefreshToken" diff --git a/src/test/kotlin/com/dobby/backend/application/usecase/SignupUseCase/CreateResearcherUseCaseTest.kt b/src/test/kotlin/com/dobby/backend/application/usecase/SignupUseCase/CreateResearcherUseCaseTest.kt new file mode 100644 index 00000000..e8cb62ca --- /dev/null +++ b/src/test/kotlin/com/dobby/backend/application/usecase/SignupUseCase/CreateResearcherUseCaseTest.kt @@ -0,0 +1,65 @@ +package com.dobby.backend.application.usecase.SignupUseCase + +import com.dobby.backend.application.mapper.SignupMapper +import com.dobby.backend.infrastructure.database.entity.MemberEntity +import com.dobby.backend.infrastructure.database.entity.ResearcherEntity +import com.dobby.backend.infrastructure.database.entity.enum.ProviderType +import com.dobby.backend.infrastructure.database.entity.enum.RoleType +import com.dobby.backend.infrastructure.database.repository.ResearcherRepository +import com.dobby.backend.infrastructure.token.JwtTokenProvider +import com.dobby.backend.presentation.api.dto.request.signup.ResearcherSignupRequest +import com.dobby.backend.presentation.api.dto.response.signup.SignupResponse +import com.dobby.backend.util.AuthenticationUtils +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.verify +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken + +class CreateResearcherUseCaseTest: BehaviorSpec ({ + + given("유효한 ResearcherSignupRequest가 주어졌을 때") { + + val researcherRepository = mockk(relaxed = true) + val jwtTokenProvider = mockk(relaxed = true) + val useCase = CreateResearcherUseCase(researcherRepository , jwtTokenProvider) + + val request = ResearcherSignupRequest( + oauthEmail = "test@example.com", + provider = ProviderType.GOOGLE, + contactEmail = "contact@example.com", + univEmail = "univ@ewha.ac.kr", + emailVerified = true, + name = "Test User", + univName = "이화여자대학교", + major = "인공지능・소프트웨어학부 인공지능융합전공", + labInfo = "불안정한 통신 상황에서 자생적 엣지 네트워크 구성을 통한 분산 학습 아키텍처 개발" + ) + + val member = SignupMapper.toResearcherMember(request) + val newResearcher = SignupMapper.toResearcher(member, request) + + every { researcherRepository.save(any()) } returns newResearcher + every { jwtTokenProvider.generateAccessToken(any()) } returns "mock-access-token" + every { jwtTokenProvider.generateRefreshToken(any()) } returns "mock-refresh-token" + + `when`("CreateResearcherUseCase가 실행되면") { + val response: SignupResponse = useCase.execute(request) + + then("ResearcherRepository에 엔티티가 저장되고, 올바른 MemberResponse가 반환되어야 한다") { + response.accessToken shouldBe "mock-access-token" + response.refreshToken shouldBe "mock-refresh-token" + response.memberInfo.oauthEmail shouldBe "test@example.com" + response.memberInfo.name shouldBe "Test User" + response.memberInfo.role shouldBe RoleType.RESEARCHER + response.memberInfo.provider shouldBe ProviderType.GOOGLE + + verify(exactly = 1) { researcherRepository.save(any()) } + verify(exactly = 1) { jwtTokenProvider.generateAccessToken(any()) } + verify (exactly = 1){ jwtTokenProvider.generateRefreshToken(any()) } + } + } + } +}) diff --git a/src/test/kotlin/com/dobby/backend/application/usecase/ParticipantSignupUseCaseTest.kt b/src/test/kotlin/com/dobby/backend/application/usecase/SignupUseCase/ParticipantSignupUseCaseTest.kt similarity index 82% rename from src/test/kotlin/com/dobby/backend/application/usecase/ParticipantSignupUseCaseTest.kt rename to src/test/kotlin/com/dobby/backend/application/usecase/SignupUseCase/ParticipantSignupUseCaseTest.kt index 1a34ec58..aa1a5948 100644 --- a/src/test/kotlin/com/dobby/backend/application/usecase/ParticipantSignupUseCaseTest.kt +++ b/src/test/kotlin/com/dobby/backend/application/usecase/SignupUseCase/ParticipantSignupUseCaseTest.kt @@ -1,10 +1,11 @@ -package com.dobby.backend.application.usecase +package com.dobby.backend.application.usecase.SignupUseCase import com.dobby.backend.application.mapper.SignupMapper import com.dobby.backend.infrastructure.database.entity.ParticipantEntity import com.dobby.backend.infrastructure.database.entity.enum.GenderType import com.dobby.backend.infrastructure.database.entity.enum.MatchType import com.dobby.backend.infrastructure.database.entity.enum.ProviderType +import com.dobby.backend.infrastructure.database.entity.enum.RoleType import com.dobby.backend.infrastructure.database.entity.enum.areaInfo.Area import com.dobby.backend.infrastructure.database.entity.enum.areaInfo.Region import com.dobby.backend.infrastructure.database.repository.ParticipantRepository @@ -19,10 +20,9 @@ import io.mockk.verify import java.time.LocalDate class ParticipantSignupUseCaseTest : BehaviorSpec({ - given("유효한 ParticipantSignupRequest가 주어졌을 때") { - val participantRepository = mockk() - val jwtTokenProvider = mockk() + val participantRepository = mockk(relaxed = true) + val jwtTokenProvider = mockk(relaxed = true) val useCase = ParticipantSignupUseCase(participantRepository, jwtTokenProvider) val request = ParticipantSignupRequest( @@ -37,26 +37,26 @@ class ParticipantSignupUseCaseTest : BehaviorSpec({ gender = GenderType.FEMALE ) - val member = SignupMapper.toMember(request) - val participant = SignupMapper.toParticipant(member, request) + val member = SignupMapper.toParticipantMember(request) + val newParticipant = SignupMapper.toParticipant(member, request) - every { participantRepository.save(any()) } returns participant + every { participantRepository.save(any()) } returns newParticipant every { jwtTokenProvider.generateAccessToken(any()) } returns "mock-access-token" every { jwtTokenProvider.generateRefreshToken(any()) } returns "mock-refresh-token" `when`("ParticipantSignupUseCase가 실행되면") { val response = useCase.execute(request) - then("ParticipantRepository에 엔티티가 저장되고, SignupResponse가 반환되어야 한다") { + then("ParticipantRepository에 엔티티가 저장되고, 올바른 SignupResponse가 반환되어야 한다") { response.accessToken shouldBe "mock-access-token" response.refreshToken shouldBe "mock-refresh-token" + response.memberInfo.oauthEmail shouldBe "test@example.com" response.memberInfo.name shouldBe "Test User" + response.memberInfo.provider shouldBe ProviderType.GOOGLE + response.memberInfo.role shouldBe RoleType.PARTICIPANT - // 엔티티 저장 verify(exactly = 1) { participantRepository.save(any()) } - - // JWT 토큰 발급 verify(exactly = 1) { jwtTokenProvider.generateAccessToken(any()) } verify(exactly = 1) { jwtTokenProvider.generateRefreshToken(any()) } } diff --git a/src/test/kotlin/com/dobby/backend/application/usecase/SignupUseCase/email/EmailCodeSendUseCaseTest.kt b/src/test/kotlin/com/dobby/backend/application/usecase/SignupUseCase/email/EmailCodeSendUseCaseTest.kt new file mode 100644 index 00000000..793ee15e --- /dev/null +++ b/src/test/kotlin/com/dobby/backend/application/usecase/SignupUseCase/email/EmailCodeSendUseCaseTest.kt @@ -0,0 +1,72 @@ +package com.dobby.backend.application.usecase.SignupUseCase.email + +import com.dobby.backend.domain.exception.EmailNotUnivException +import com.dobby.backend.domain.gateway.EmailGateway +import com.dobby.backend.infrastructure.database.entity.VerificationEntity +import com.dobby.backend.infrastructure.database.entity.enum.VerificationStatus +import com.dobby.backend.infrastructure.database.repository.VerificationRepository +import com.dobby.backend.presentation.api.dto.request.signup.EmailSendRequest +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import io.mockk.* +import java.time.LocalDateTime + +class EmailCodeSendUseCaseTest : BehaviorSpec({ + val mockVerificationRepository = mockk() + val mockEmailGateway = mockk() + + val emailCodeSendUseCase = EmailCodeSendUseCase( + verificationRepository = mockVerificationRepository, + emailGateway = mockEmailGateway + ) + + given("유효한 대학 이메일이 주어졌을 때") { + val request = EmailSendRequest(univEmail = "test@postech.edu") + + + coEvery { mockVerificationRepository.findByUnivMail(request.univEmail) } returns null + val mockEntity = VerificationEntity( + id = 1L, + univMail = "test@postech.edu", + verificationCode = "123456", + status = VerificationStatus.HOLD, + expiresAt = LocalDateTime.now().plusMinutes(10) + ) + coEvery { mockVerificationRepository.save(any()) } returns mockEntity + coEvery { mockEmailGateway.sendEmail(any(), any(), any()) } returns Unit + + + `when`("emailCodeSendUseCase가 실행되면") { + val result = emailCodeSendUseCase.execute(request) + + then("정상적으로 EmailSendResponse를 반환해야 한다") { + result.isSuccess shouldBe true + } + + then("save 메서드가 호출되어야 한다") { + coVerify { mockVerificationRepository.save(any()) } + } + + then("이메일이 발송되어야 한다") { + coVerify { mockEmailGateway.sendEmail( + "test@postech.edu", + any(), + any()) } + } + } + } + + given("대학 이메일이 아닌 경우") { + val request = EmailSendRequest(univEmail = "test@gmail.com") + + `when`("코드 전송 요청을 하면") { + then("EmailNotUnivException 예외가 발생해야 한다") { + val exception = shouldThrow { + emailCodeSendUseCase.execute(request) + } + } + } + } +}) + diff --git a/src/test/kotlin/com/dobby/backend/application/usecase/SignupUseCase/email/EmailVerificationUseCaseTest.kt b/src/test/kotlin/com/dobby/backend/application/usecase/SignupUseCase/email/EmailVerificationUseCaseTest.kt new file mode 100644 index 00000000..5577bf1d --- /dev/null +++ b/src/test/kotlin/com/dobby/backend/application/usecase/SignupUseCase/email/EmailVerificationUseCaseTest.kt @@ -0,0 +1,109 @@ +package com.dobby.backend.application.usecase.SignupUseCase.email + +import com.dobby.backend.domain.exception.CodeExpiredException +import com.dobby.backend.domain.exception.CodeNotCorrectException +import com.dobby.backend.domain.exception.VerifyInfoNotFoundException +import com.dobby.backend.infrastructure.database.entity.VerificationEntity +import com.dobby.backend.infrastructure.database.entity.enum.VerificationStatus +import com.dobby.backend.infrastructure.database.repository.VerificationRepository +import com.dobby.backend.presentation.api.dto.request.signup.EmailVerificationRequest +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import java.time.LocalDateTime + +class EmailVerificationUseCaseTest : BehaviorSpec({ + + val mockVerificationRepository = mockk() + val emailVerificationUseCase = EmailVerificationUseCase(mockVerificationRepository) + + given("유효한 인증 요청이 주어졌을 때") { + val request = EmailVerificationRequest(univEmail = "test@postech.edu", inputCode = "123456") + val mockEntity = VerificationEntity( + id = 1L, + univMail = "test@postech.edu", + verificationCode = "123456", + status = VerificationStatus.HOLD, + expiresAt = LocalDateTime.now().plusMinutes(10) + ) + + coEvery { mockVerificationRepository.findByUnivMailAndStatus(request.univEmail, VerificationStatus.HOLD) } returns mockEntity + coEvery { mockVerificationRepository.save(any()) } returns mockEntity + + `when`("EmailVerificationUseCase가 실행되면") { + val result = emailVerificationUseCase.execute(request) + + then("정상적으로 EmailVerificationResponse를 반환해야 한다") { + result.isSuccess shouldBe true + } + + then("인증 상태가 VERIFIED로 업데이트되어야 한다") { + coVerify { + mockVerificationRepository.save(withArg { + it.status shouldBe VerificationStatus.VERIFIED + }) + } + } + } + } + + given("존재하지 않는 인증 정보가 주어졌을 때") { + val request = EmailVerificationRequest(univEmail = "test@postech.edu", inputCode = "123456") + + coEvery { mockVerificationRepository.findByUnivMailAndStatus(request.univEmail, VerificationStatus.HOLD) } returns null + + `when`("EmailVerificationUseCase가 실행되면") { + then("VerifyInfoNotFoundException 예외가 발생해야 한다") { + val exception = shouldThrow { + emailVerificationUseCase.execute(request) + } + } + } + } + + given("유효하지 않은 인증 코드가 주어졌을 때") { + val request = EmailVerificationRequest(univEmail = "test@postech.edu", inputCode = "wrong-code") + val mockEntity = VerificationEntity( + id = 1L, + univMail = "test@postech.edu", + verificationCode = "123456", + status = VerificationStatus.HOLD, + expiresAt = LocalDateTime.now().plusMinutes(10) + ) + + coEvery { mockVerificationRepository.findByUnivMailAndStatus(request.univEmail, VerificationStatus.HOLD) } returns mockEntity + + `when`("EmailVerificationUseCase가 실행되면") { + then("CodeNotCorrectException 예외가 발생해야 한다") { + val exception = shouldThrow { + emailVerificationUseCase.execute(request) + } + } + } + } + + given("인증 코드가 만료된 경우") { + val request = EmailVerificationRequest(univEmail = "test@postech.edu", inputCode = "123456") + val mockEntity = VerificationEntity( + id = 1L, + univMail = "test@postech.edu", + verificationCode = "123456", + status = VerificationStatus.HOLD, + expiresAt = LocalDateTime.now().minusMinutes(1) + ) + + coEvery { mockVerificationRepository.findByUnivMailAndStatus(request.univEmail, VerificationStatus.HOLD) } returns mockEntity + + `when`("EmailVerificationUseCase가 실행되면") { + then("CodeExpiredException 예외가 발생해야 한다") { + val exception = shouldThrow { + emailVerificationUseCase.execute(request) + } + } + } + } +}) + diff --git a/src/test/kotlin/com/dobby/backend/infrastructure/database/entity/TestEntity.kt b/src/test/kotlin/com/dobby/backend/infrastructure/database/entity/TestEntity.kt deleted file mode 100644 index 2db0db51..00000000 --- a/src/test/kotlin/com/dobby/backend/infrastructure/database/entity/TestEntity.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.dobby.backend.infrastructure.database.entity - -import AuditingEntity -import jakarta.persistence.Entity -import jakarta.persistence.GeneratedValue -import jakarta.persistence.GenerationType -import jakarta.persistence.Id - -@Entity -class TestEntity ( - var name : String -) : AuditingEntity() { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - var id: Long? = null -} \ No newline at end of file diff --git a/src/test/kotlin/com/dobby/backend/infrastructure/database/entity/common/AuditingEntityTest.kt b/src/test/kotlin/com/dobby/backend/infrastructure/database/entity/common/AuditingEntityTest.kt deleted file mode 100644 index f543dd39..00000000 --- a/src/test/kotlin/com/dobby/backend/infrastructure/database/entity/common/AuditingEntityTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.dobby.backend.infrastructure.entity - -import com.dobby.backend.infrastructure.database.entity.TestEntity -import com.dobby.backend.infrastructure.repository.TestEntityRepository -import io.kotest.core.spec.style.BehaviorSpec -import io.kotest.matchers.date.shouldBeAfter -import io.kotest.matchers.shouldBe -import io.kotest.matchers.shouldNotBe -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.data.jpa.repository.config.EnableJpaAuditing -import org.springframework.test.context.ActiveProfiles -import java.time.LocalDateTime - -@SpringBootTest -@EnableJpaAuditing -@ActiveProfiles("test") -class AuditingEntityTest @Autowired constructor( - private val testEntityRepository: TestEntityRepository -) : BehaviorSpec({ - given("AuditingEntity가 저장될 때") { - `when`("createdAt과 updatedAt이 자동으로 설정되면") { - val entity = TestEntity(name = "Test Entity") - val savedEntity = testEntityRepository.save(entity) - - then("createdAt과 updatedAt이 null이 아니어야 한다") { - savedEntity.createdAt shouldNotBe null - savedEntity.updatedAt shouldNotBe null - } - then("createdAt과 updatedAt이 동일해야 한다") { - savedEntity.createdAt shouldBe savedEntity.updatedAt - } - } - } - - given("AuditingEntity의 setter 메서드를 호출할 때") { - `when`("setCreatedAt과 setUpdatedAt을 호출하면") { - val entity = TestEntity(name = "Test Entity") - - then("setter 메서드가 정상적으로 호출된다") { - val now = LocalDateTime.now() - entity.createdAt = now - entity.updatedAt = now - - entity.createdAt shouldBe now - entity.updatedAt shouldBe now - } - } - } - - given("AuditingEntity의 Getter 메서드를 호출할 때") { - `when`("createdAt과 updatedAt이 설정되었다면") { - val entity = TestEntity(name = "Test Entity") - val now = LocalDateTime.now() - - entity.createdAt = now - entity.updatedAt = now - - then("getCreatedAt과 getUpdatedAt이 정상적으로 값을 반환해야 한다") { - entity.createdAt shouldBe now - entity.updatedAt shouldBe now - } - } - - `when`("createdAt과 updatedAt이 설정되지 않았다면") { - val entity = testEntityRepository.save(TestEntity(name = "Test Entity")) - - then("getCreatedAt과 getUpdatedAt은 null이 아니어야 한다") { - entity.createdAt shouldNotBe null - entity.updatedAt shouldNotBe null - } - - then("getCreatedAt과 getUpdatedAt이 저장 시점의 값이어야 한다") { - entity.updatedAt shouldBe entity.createdAt - } - } - } - - given("AuditingEntity가 수정될 때") { - `when`("updatedAt이 변경되면") { - val entity = testEntityRepository.save(TestEntity(name = "Initial Name")) - - Thread.sleep(1000) // 수정 시간을 구분하기 위해 대기 - entity.name = "Updated Name" - val updatedEntity = testEntityRepository.save(entity) - - then("updatedAt, createdAt은 null이 아니어야 한다") { - updatedEntity.updatedAt shouldNotBe null - updatedEntity.createdAt shouldNotBe null - } - then("updatedAt은 createdAt 이후의 시각이어야 한다") { - updatedEntity.updatedAt shouldNotBe entity.createdAt - updatedEntity.updatedAt shouldBeAfter entity.createdAt - } - } - } - - given("AuditingEntity가 삭제될 때") { - `when`("삭제 작업이 수행되면") { - val entity = testEntityRepository.save(TestEntity(name = "Entity to Delete")) - testEntityRepository.delete(entity) - then("해당 엔티티는 더 이상 데이터베이스에 존재하지 않아야 한다") { - val isDeleted = testEntityRepository.findById(entity.id!!).isEmpty - isDeleted shouldBe true - } - } - } -} -) diff --git a/src/test/kotlin/com/dobby/backend/infrastructure/repository/TestEntityRepository.kt b/src/test/kotlin/com/dobby/backend/infrastructure/repository/TestEntityRepository.kt deleted file mode 100644 index c26e44ac..00000000 --- a/src/test/kotlin/com/dobby/backend/infrastructure/repository/TestEntityRepository.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.dobby.backend.infrastructure.repository - -import com.dobby.backend.infrastructure.database.entity.TestEntity -import org.springframework.data.jpa.repository.JpaRepository - -interface TestEntityRepository: JpaRepository \ No newline at end of file diff --git a/src/test/kotlin/com/dobby/backend/infrastructure/token/JwtTokenProviderTest.kt b/src/test/kotlin/com/dobby/backend/infrastructure/token/JwtTokenProviderTest.kt index 475f0c19..a36b3947 100644 --- a/src/test/kotlin/com/dobby/backend/infrastructure/token/JwtTokenProviderTest.kt +++ b/src/test/kotlin/com/dobby/backend/infrastructure/token/JwtTokenProviderTest.kt @@ -13,7 +13,6 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.test.context.ActiveProfiles -import java.time.LocalDate import kotlin.test.assertFailsWith @SpringBootTest @@ -27,7 +26,7 @@ class JwtTokenProviderTest : BehaviorSpec() { given("회원 정보가 주어지고") { val member = Member(memberId = 1, oauthEmail = "dlawotn3@naver.com", contactEmail = "dlawotn3@naver.com", provider = ProviderType.NAVER, role = RoleType.PARTICIPANT, name = "dobby", - birthDate = LocalDate.of(2000, 7, 8), status = MemberStatus.ACTIVE) + status = MemberStatus.ACTIVE) val authorities = listOf(SimpleGrantedAuthority(member.role?.name ?: "PARTICIPANT")) val authentication = UsernamePasswordAuthenticationToken(member.memberId, null, authorities) @@ -43,7 +42,7 @@ class JwtTokenProviderTest : BehaviorSpec() { given("유효한 JWT 토큰이 주어지고") { val member = Member(memberId = 1, oauthEmail = "dlawotn3@naver.com", contactEmail = "dlawotn3@naver.com", provider = ProviderType.NAVER, role = RoleType.PARTICIPANT, name = "dobby", - birthDate = LocalDate.of(2000, 7, 8), status = MemberStatus.ACTIVE) + status = MemberStatus.ACTIVE) val authorities = listOf(SimpleGrantedAuthority(member.role?.name ?: "PARTICIPANT")) val authentication = UsernamePasswordAuthenticationToken(member.memberId, null, authorities) val validToken = jwtTokenProvider.generateAccessToken(authentication) diff --git a/src/test/kotlin/com/dobby/backend/util/AuthenticationUtilsTest.kt b/src/test/kotlin/com/dobby/backend/util/AuthenticationUtilsTest.kt new file mode 100644 index 00000000..25f22e56 --- /dev/null +++ b/src/test/kotlin/com/dobby/backend/util/AuthenticationUtilsTest.kt @@ -0,0 +1,40 @@ +package com.dobby.backend.util + +import com.dobby.backend.infrastructure.database.entity.MemberEntity +import com.dobby.backend.infrastructure.database.entity.enum.ProviderType +import com.dobby.backend.infrastructure.database.entity.enum.RoleType +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.Authentication + +class AuthenticationUtilsTest : BehaviorSpec({ + given("유효한 MemberEntity가 주어졌을 때") { + val member = MemberEntity( + id = 1L, + oauthEmail = "test@example.com", + contactEmail = "contact@example.com", + provider = ProviderType.GOOGLE, + name = "Test User", + role = RoleType.RESEARCHER + ) + + `when`("AuthenticationUtils.createAuthentication이 호출되면") { + val authentication: Authentication = AuthenticationUtils.createAuthentication(member) + + then("UsernamePasswordAuthenticationToken 객체가 반환되어야 한다") { + authentication shouldBe UsernamePasswordAuthenticationToken( + member, // principal + null, // credential + emptyList() // granted authorized sizes + ) + } + + then("Authentication의 필드가 올바르게 설정되어야 한다") { + authentication.principal shouldBe member + authentication.credentials shouldBe null + authentication.authorities.size shouldBe 0 + } + } + } +}) diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index d73629b7..0c02f7d8 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -21,6 +21,18 @@ spring: google: client-id: "dummy-client-id" client-secret: "dummy-client-secret" + mail: + host: smtp.example.com + port: 587 + username: gradmeet_test@example.com + password: testpassword + properties: + mail: + smtp: + auth: true + starttls: + enable: true + app: token: secret_key: testtesttestteyyysttestteyyystte @@ -28,6 +40,7 @@ app: access: 86400 refresh: 604800 + cloud: aws: s3: