Test #452
@@ -0,0 +1,62 @@
|
||||
# 캔 조회 회원 2 강제 통화 제거 구현 계획
|
||||
|
||||
- 상태: 구현 완료
|
||||
- 작성일: 2026-09-18
|
||||
- 요구사항: [prd.md](prd.md)
|
||||
- 현재 Phase: 1 완료
|
||||
- 활성 Goal: 없음
|
||||
- 실행 순서: CAN-P1-T1 → CAN-P1-GATE
|
||||
|
||||
### Phase 1: 회원 2의 기본 통화 적용과 회귀 확인
|
||||
|
||||
- [x] **Task 1.1: 회원 2의 강제 통화 제거** (`CAN-P1-T1`)
|
||||
- Objective: 회원 2가 다른 일반 회원처럼 요청 국가에 따른 통화의 캔 상품을 조회한다.
|
||||
- 시작 조건: PRD 확정, 기존 컨트롤러·서비스 및 테스트 확인 완료.
|
||||
- 완료 증거: RED/GREEN 결과, 실제 컨트롤러·서비스·저장소 통합 테스트와 캔 패키지 회귀 통과.
|
||||
- 범위 밖: 다른 회원 예외 제거, 국가 결정·API 스키마·서비스 리팩터링.
|
||||
- 수정: `src/main/kotlin/kr/co/vividnext/sodalive/can/CanController.kt`
|
||||
- 생성: `src/test/kotlin/kr/co/vividnext/sodalive/can/CanControllerIntegrationTest.kt`
|
||||
- 확인: `src/main/kotlin/kr/co/vividnext/sodalive/can/CanService.kt`, `src/test/kotlin/kr/co/vividnext/sodalive/can/CanServiceTest.kt`
|
||||
- [x] **RED:** 회원 2의 KRW/USD, 유지할 예외·일반 회원·비로그인·국가 누락 통합 테스트를 작성한다.
|
||||
- [x] **RED 확인:** `./gradlew test --tests 'kr.co.vividnext.sodalive.can.CanControllerIntegrationTest'`로 회원 2의 JPY 반환 assertion 실패를 확인한다.
|
||||
- [x] **GREEN:** 컨트롤러 조건에서 `member.id == 2L ||`만 제거한다.
|
||||
- [x] **GREEN 확인:** 동일 focused test가 통과하는지 확인한다.
|
||||
- [x] **REFACTOR:** 불필요한 정리 없이 `./gradlew test --tests 'kr.co.vividnext.sodalive.can.*'` 및 변경 파일 lint로 회귀를 확인한다.
|
||||
|
||||
#### Task 검증 기록
|
||||
- 2026-09-18: 기존 `getCans` 및 `/can` 직접 테스트가 없음을 확인했다. 새 테스트는 `@SpringBootTest`와 H2, 요청 범위의 실제 CountryContext를 사용하고 트랜잭션 롤백으로 상품 데이터를 격리한다.
|
||||
- 2026-09-18 RED: 테스트 작성 중 `Member.password` 필수 인자 누락을 수정한 뒤 production/test 컴파일이 통과했다. 이후 공통 JWT 기본 키의 길이 부족으로 발생한 컨텍스트 로딩 실패는 테스트 실행에만 임시 키를 제공하여 해결했다. 이 두 실패는 동작 RED에 포함하지 않았다.
|
||||
- 2026-09-18 RED 확인: 아래 환경 설정 후 `./gradlew test --tests 'kr.co.vividnext.sodalive.can.CanControllerIntegrationTest' --console=plain` 실행. 13개 중 회원 2의 4개만 assertion 실패(exit 1): KR은 KRW, US/JP/국가 누락은 USD 기대였으나 실제 JPY. 나머지 9개 통과.
|
||||
- 2026-09-18 GREEN 확인: 조건 제거 후 같은 focused 명령으로 13개 전부 통과(exit 0), production/test 컴파일 성공.
|
||||
- 2026-09-18 회귀 확인: `./gradlew test --tests 'kr.co.vividnext.sodalive.can.*' --console=plain` 성공(exit 0). 신규 13개와 기존 14개, 총 5개 클래스 27개 테스트 통과(실패·오류·skip 0). 기존 테스트를 완화하거나 수정할 필요가 없었다.
|
||||
|
||||
- [x] **Phase Gate: 변경 범위와 검증 기록 확인** (`CAN-P1-GATE`)
|
||||
- Objective: 요구사항과 최소 변경, 검증 증거가 일치함을 확인한다.
|
||||
- 시작 조건: CAN-P1-T1 완료.
|
||||
- 완료 증거: 캔 패키지 테스트 통과, 문서 명령·diff 확인 및 PRD 체크박스 완료.
|
||||
- 범위 밖: 전체 서비스 실행과 관련 없는 기능 수정.
|
||||
- 확인 파일: 이 문서, `docs/20260918_캔조회_회원2_강제통화제거/prd.md`, 위 Task의 코드·테스트.
|
||||
- TDD 예외 사유: 구현 없는 최종 검증 단계.
|
||||
- 대체 검증 방법: `./gradlew tasks --all`, `git diff --check`, 변경 파일 대조.
|
||||
|
||||
## 검증 범위
|
||||
- 단일 루트 프로젝트이며 변경은 컨트롤러의 조건 하나에 한정된다. 전체 회귀 실행 조건(공통 코드·여러 도메인 변경·영향 불명확 실패·사용자 명시 요청)이 발생하지 않으면 전체 테스트는 생략하고 캔 패키지 전체를 실행한다.
|
||||
- 테스트 실행으로 production/test 컴파일을 함께 확인한다. 새 API나 실행 환경 변경이 없어 별도 애플리케이션 구동은 하지 않는다.
|
||||
|
||||
## Progress
|
||||
- 2026-09-18: 요구사항 및 TDD 계획 작성, CAN-P1-T1 준비.
|
||||
- 2026-09-18: CAN-P1-T1 RED 확인 완료, 회원 2 조건 제거 진행.
|
||||
- 2026-09-18: CAN-P1-T1 완료. 조건 한 항만 제거했고 신규 13개 및 기존 14개 테스트가 통과했다.
|
||||
- 2026-09-18: CAN-P1-GATE 완료. API 스키마·다른 회원 예외가 유지되는 diff와 PRD 수용 기준, 검증 기록을 대조했다. 남은 작업 없음.
|
||||
|
||||
## 공통 검증 기록
|
||||
- 셸에 기본 Java가 없어 기존 Gradle 캐시의 Java 17을 사용했다. 설정 파일은 변경하지 않았다.
|
||||
- 테스트 JWT 기본값은 디코딩 후 키 길이가 부족하므로 실행 프로세스에만 임시 키를 전달했다. 키 값은 저장·출력하지 않는다. 아래 환경 설정 뒤 문서의 검증 명령을 실행한다.
|
||||
|
||||
```bash
|
||||
export JAVA_HOME=~/.gradle/jdks/eclipse_adoptium-17-x86_64-os_x.2/jdk-17.0.19+10/Contents/Home
|
||||
export JWT_SECRET="$(openssl rand -base64 64 | tr -d '\n')"
|
||||
```
|
||||
|
||||
- 2026-09-18: `./gradlew tasks --all --console=plain`, `git diff --check` 성공(exit 0). 변경 Kotlin 파일의 IDE 정적 검사에서 오류 없음. 신규 테스트는 경고도 없으며 기존 컨트롤러의 수정하지 않은 인증 SpEL 식에는 변수 해석 경고 8개가 있다.
|
||||
- 2026-09-18: 전체 테스트·전체 ktlint 및 별도 서버 실행은 생략했다. 변경은 컨트롤러 조건 한 곳이고 다른 도메인·공통 코드·하위 모듈 변경이 없어 focused 통합 테스트, 캔 패키지 전체 회귀, 변경 파일 정적 검사로 검증했다.
|
||||
@@ -0,0 +1,27 @@
|
||||
# 캔 조회 회원 2 강제 통화 제거
|
||||
|
||||
## 문서 정보
|
||||
- 상태: 구현 완료
|
||||
- 작성일: 2026-09-18
|
||||
- 관련 계획: [plan-task.md](plan-task.md)
|
||||
|
||||
## 문제와 목표
|
||||
`GET /can`의 `getCans`는 회원 2·4·44144에게 JPY를 강제 적용한다.
|
||||
사용자 요청에 따라 회원 2만 제외하여 기존 국가별 통화 선택을 따르게 한다.
|
||||
|
||||
## 확정 요구사항과 수용 기준
|
||||
| ID | 요구사항 | 수용 기준 | Goal |
|
||||
|---|---|---|---|
|
||||
| CAN-CURRENCY-001 | 회원 2의 JPY 강제 조건 제거 | KR 요청은 KRW, 그 외 국가는 USD 상품 반환 | CAN-P1-T1 |
|
||||
| CAN-CURRENCY-002 | 나머지 동작 유지 | 회원 4·44144는 JPY, 일반 회원·비로그인은 기존 국가별 통화 적용 | CAN-P1-T1 |
|
||||
| CAN-CURRENCY-003 | 관련 테스트 검증 | 변경 전 실패 재현, 변경 후 캔 패키지 테스트 통과 | CAN-P1-GATE |
|
||||
|
||||
## 범위 밖
|
||||
- 다른 진입점의 회원 ID 조건, 국가 판정 및 성인콘텐츠 정책 변경.
|
||||
- 서비스의 KR → KRW / 그 외 → USD 정책 변경.
|
||||
- 공개 API 요청·응답 스키마, 인증 및 상품 판매 상태 필터 변경.
|
||||
|
||||
## 성공 기준
|
||||
- [x] 회원 2에 대한 회귀 테스트가 변경 전 실패하고 변경 후 통과한다.
|
||||
- [x] 회원 4·44144, 일반 회원, 비로그인 및 국가 누락의 기존 동작이 유지된다.
|
||||
- [x] 관련 기존 테스트가 통과하고 검증 결과를 계획 문서에 기록한다.
|
||||
@@ -17,7 +17,7 @@ class CanController(private val service: CanService) {
|
||||
fun getCans(
|
||||
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||
): ApiResponse<List<CanResponse>> {
|
||||
val forcedCurrency = if (member != null && (member.id == 2L || member.id == 4L || member.id == 44144L)) {
|
||||
val forcedCurrency = if (member != null && (member.id == 4L || member.id == 44144L)) {
|
||||
"JPY"
|
||||
} else {
|
||||
null
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package kr.co.vividnext.sodalive.can
|
||||
|
||||
import kr.co.vividnext.sodalive.common.CountryContext
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import kr.co.vividnext.sodalive.support.EmbeddedRedisInitializer
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.CsvSource
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.test.annotation.DirtiesContext
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.math.BigDecimal
|
||||
import javax.persistence.EntityManager
|
||||
|
||||
@SpringBootTest(properties = ["spring.cache.type=none"])
|
||||
@ContextConfiguration(initializers = [EmbeddedRedisInitializer::class])
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
|
||||
@Transactional
|
||||
class CanControllerIntegrationTest @Autowired constructor(
|
||||
private val controller: CanController,
|
||||
private val countryContext: CountryContext,
|
||||
private val entityManager: EntityManager
|
||||
) {
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
listOf("KRW", "USD", "JPY").forEach { currency ->
|
||||
entityManager.persist(
|
||||
Can(
|
||||
title = "test-$currency",
|
||||
can = 100,
|
||||
rewardCan = 0,
|
||||
price = BigDecimal("100"),
|
||||
currency = currency,
|
||||
status = CanStatus.SALE
|
||||
)
|
||||
)
|
||||
}
|
||||
entityManager.flush()
|
||||
entityManager.clear()
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(
|
||||
"2, KR, KRW",
|
||||
"2, US, USD",
|
||||
"2, JP, USD",
|
||||
"2, , USD",
|
||||
"4, KR, JPY",
|
||||
"4, US, JPY",
|
||||
"44144, KR, JPY",
|
||||
"44144, US, JPY",
|
||||
"3, KR, KRW",
|
||||
"3, US, USD",
|
||||
", KR, KRW",
|
||||
", US, USD",
|
||||
", , USD"
|
||||
)
|
||||
@DisplayName("회원 2는 국가별 통화를 사용하고 기존 JPY 예외와 일반·비로그인 조회는 유지한다")
|
||||
fun shouldSelectCurrencyByCountryExceptRemainingForcedMembers(
|
||||
memberId: Long?,
|
||||
countryCode: String?,
|
||||
expectedCurrency: String
|
||||
) {
|
||||
val member = memberId?.let { id ->
|
||||
Member(nickname = "test-$id", password = "").apply { this.id = id }
|
||||
}
|
||||
countryContext.setCountryCode(countryCode)
|
||||
|
||||
val response = controller.getCans(member)
|
||||
|
||||
assertEquals(true, response.success)
|
||||
assertEquals(listOf(expectedCurrency), response.data?.map { it.currency })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user