Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/plan/#26-auth-command-query-fix/checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# auth BC: Command/Query 패턴 미적용 수정 검증 체크리스트

## 필수 항목
- [x] 아키텍처 원칙 준수 (docs/architecture.md 기준)
- [x] 레이어 의존성 규칙 위반 없음
- [x] 테스트 코드 작성 완료 (Domain, Application 필수)
- [x] 모든 테스트 통과
- [x] 기존 테스트 깨지지 않음

## 선택 항목 (해당 시)
- [x] auth BC 파일만 수정 (다른 BC 미수정)
- [x] Command/Query 데이터 흐름 일관성 확보
12 changes: 12 additions & 0 deletions docs/plan/#26-auth-command-query-fix/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# auth BC: Command/Query 패턴 미적용 수정 계획

> Issue: #26

## 단계

- [x] 1단계: Domain — MemberCommand에 Login variant 추가, Register에 encodedPassword 포함 (TDD)
- [x] 2단계: Domain — MemberRepository.save() 시그니처에서 encodedPassword 파라미터 제거
- [x] 3단계: Application — AuthService.login()이 MemberCommand.Login을 수신하도록 변경, register()에서 command.copy로 encodedPassword 전달 (TDD)
- [x] 4단계: Infrastructure — ExposedMemberRepository.save() 구현 수정
- [x] 5단계: Presentation — AuthDataFetcher.login()에서 MemberCommand.Login 변환
- [x] 6단계: 전체 테스트 통과 검증
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package kr.io.team.loop.auth.application.service

import kr.io.team.loop.auth.application.dto.AuthTokenDto
import kr.io.team.loop.auth.domain.model.LoginId
import kr.io.team.loop.auth.domain.model.MemberCommand
import kr.io.team.loop.auth.domain.repository.MemberRepository
import kr.io.team.loop.common.config.JwtTokenProvider
Expand All @@ -24,20 +23,17 @@ class AuthService(
throw DuplicateEntityException("LoginId already exists: ${command.loginId.value}")
}
val encodedPassword = checkNotNull(passwordEncoder.encode(command.rawPassword))
val member = memberRepository.save(command, encodedPassword)
val member = memberRepository.save(command.copy(encodedPassword = encodedPassword))
val token = jwtTokenProvider.generateToken(member.id.value)
return AuthTokenDto(accessToken = token)
}

@Transactional(readOnly = true)
fun login(
loginId: LoginId,
rawPassword: String,
): AuthTokenDto {
fun login(command: MemberCommand.Login): AuthTokenDto {
val member =
memberRepository.findByLoginId(loginId)
?: throw EntityNotFoundException("Member not found: ${loginId.value}")
if (!passwordEncoder.matches(rawPassword, member.password)) {
memberRepository.findByLoginId(command.loginId)
?: throw EntityNotFoundException("Member not found: ${command.loginId.value}")
if (!passwordEncoder.matches(command.rawPassword, member.password)) {
throw AuthenticationException("Password does not match")
}
val token = jwtTokenProvider.generateToken(member.id.value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,11 @@ sealed interface MemberCommand {
val loginId: LoginId,
val nickname: Nickname,
val rawPassword: String,
val encodedPassword: String? = null,
) : MemberCommand

data class Login(
val loginId: LoginId,
val rawPassword: String,
) : MemberCommand
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import kr.io.team.loop.auth.domain.model.Member
import kr.io.team.loop.auth.domain.model.MemberCommand

interface MemberRepository {
fun save(
command: MemberCommand.Register,
encodedPassword: String,
): Member
fun save(command: MemberCommand.Register): Member

fun findByLoginId(loginId: LoginId): Member?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ import java.time.OffsetDateTime

@Repository
class ExposedMemberRepository : MemberRepository {
override fun save(
command: MemberCommand.Register,
encodedPassword: String,
): Member {
override fun save(command: MemberCommand.Register): Member {
val encodedPassword = checkNotNull(command.encodedPassword)
val now = OffsetDateTime.now()
val row =
MemberTable.insert {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ class AuthDataFetcher(
fun login(
@InputArgument input: LoginInput,
): AuthToken {
val result =
authService.login(
val command =
MemberCommand.Login(
loginId = LoginId(input.loginId),
rawPassword = input.password,
)
val result = authService.login(command)
return AuthToken(accessToken = result.accessToken)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ class AuthServiceTest :
When("유효한 정보이면") {
every { memberRepository.existsByLoginId(registerCommand.loginId) } returns false
every { passwordEncoder.encode("password123") } returns "encoded_password"
every { memberRepository.save(registerCommand, "encoded_password") } returns savedMember
every {
memberRepository.save(registerCommand.copy(encodedPassword = "encoded_password"))
} returns savedMember
every { jwtTokenProvider.generateToken(1L) } returns "jwt-token"

val result = authService.register(registerCommand)
Expand Down Expand Up @@ -76,7 +78,12 @@ class AuthServiceTest :
every { passwordEncoder.matches("password123", "encoded_password") } returns true
every { jwtTokenProvider.generateToken(1L) } returns "jwt-token"

val result = authService.login(LoginId("testuser"), "password123")
val loginCommand =
MemberCommand.Login(
loginId = LoginId("testuser"),
rawPassword = "password123",
)
val result = authService.login(loginCommand)

Then("accessToken을 반환한다") {
result.accessToken.shouldNotBeBlank()
Expand All @@ -88,8 +95,13 @@ class AuthServiceTest :
every { memberRepository.findByLoginId(LoginId("unknown")) } returns null

Then("예외가 발생한다") {
val loginCommand =
MemberCommand.Login(
loginId = LoginId("unknown"),
rawPassword = "password123",
)
shouldThrow<EntityNotFoundException> {
authService.login(LoginId("unknown"), "password123")
authService.login(loginCommand)
}
}
}
Expand All @@ -99,8 +111,13 @@ class AuthServiceTest :
every { passwordEncoder.matches("wrongpassword", "encoded_password") } returns false

Then("예외가 발생한다") {
val loginCommand =
MemberCommand.Login(
loginId = LoginId("testuser"),
rawPassword = "wrongpassword",
)
shouldThrow<AuthenticationException> {
authService.login(LoginId("testuser"), "wrongpassword")
authService.login(loginCommand)
}
}
}
Expand Down
42 changes: 42 additions & 0 deletions src/test/kotlin/kr/io/team/loop/auth/domain/model/MemberTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,47 @@ class MemberTest :
command.rawPassword shouldBe "password123"
}
}

When("encodedPassword를 포함하면") {
val command =
MemberCommand.Register(
loginId = LoginId("newuser"),
nickname = Nickname("새사용자"),
rawPassword = "password123",
encodedPassword = "encoded_password",
)

Then("encodedPassword가 설정된다") {
command.encodedPassword shouldBe "encoded_password"
}
}

When("encodedPassword를 생략하면") {
val command =
MemberCommand.Register(
loginId = LoginId("newuser"),
nickname = Nickname("새사용자"),
rawPassword = "password123",
)

Then("encodedPassword는 null이다") {
command.encodedPassword shouldBe null
}
}
}

Given("MemberCommand.Login 생성 시") {
When("유효한 값이면") {
val command =
MemberCommand.Login(
loginId = LoginId("testuser"),
rawPassword = "password123",
)

Then("정상 생성된다") {
command.loginId.value shouldBe "testuser"
command.rawPassword shouldBe "password123"
}
}
}
})