Skip to content

Conversation

@HyejeongSon
Copy link
Contributor

📌 연관된 이슈 번호

🌱 주요 변경 사항

1. 비즈니스 로직 분리

  • API 레이어와 서비스 레이어 분리
    • app/api/challenge/claim/route.ts: 인증/권한 검증 및 API 응답 처리
    • app/actions/get-challenge.ts: 서버 액션으로 챌린지 목록 조회
    • services/challenge/challenge-claim.ts: 순수 비즈니스 로직 (보상 지급 처리)
    • services/challenge/challenge-status.ts: 챌린지 상태 계산 공통 로직

2. 공통 로직 추출

  • 챌린지 상태 계산 로직 통합
// Before: 중복된 상태 판단 로직
const existingClaim = await tx.userChallengeClaim.findFirst(...)
if (existingClaim) return { success: false }
  
// After: 공통 함수 사용
const status = calculateChallengeStatus(challenge, userId)
if (status === "CLAIMED") return { canClaim: false }

3. 테스트 작성

  • 디렉토리 구조

    __tests__/
    ├── services/
       ├── challenge-status.test.ts    # 상태 계산 로직 테스트
       └── challenge-claim.test.ts     # 보상 수령 로직 테스트
    └── api/
        └── challenge-claim-api.test.ts # API 레이어 테스트
  • 테스트 실행

    npx jest __tests__/services/challenge-status.test.ts
    npx jest __tests__/services/challenge-claim.test.ts
    npx jest __tests__/api/challenge-claim-api.test.ts

Challenge Status Service 테스트 (challenge-status.test.ts)

calculateChallengeStatus 테스트

  1. [ONCE] 이미 보상을 수령한 경우 → CLAIMED
    • userChallengeClaims에 항목이 있는 경우 무조건 CLAIMED 상태를 반환해야 함
  2. [ONCE] 진행도가 1 이상이면 → ACHIEVABLE
    • 보상은 아직 수령하지 않았고, progressVal이 1 이상이면 수령 가능한 상태
  3. [ONCE] 진행도가 0이면 → INCOMPLETE
    • 진행도는 있지만 0이라면 미완료 상태로 판단
  4. [STREAK] 7일 연속이고 오늘 업데이트된 경우 → ACHIEVABLE
    • streak가 7 이상이고 updatedAt이 오늘이라면 수령 가능
  5. [STREAK] 7일 연속이지만 오늘 업데이트되지 않은 경우 → INCOMPLETE
    • 어제까지는 연속이지만 오늘 날짜로 갱신되지 않았다면 수령 불가
  6. [DAILY] 진행도가 있고 오늘 업데이트됨 → ACHIEVABLE
    • progressVal이 있고 updatedAt이 오늘이면 수령 가능
  7. [DAILY] 오늘 생성되었지만 업데이트 없음 → ACHIEVABLE
    • updatedAt은 없어도 createdAt이 오늘이고 진행도가 있다면 수령 가능
  8. [DAILY] 오늘 이미 보상 수령한 경우 → CLAIMED
    • userChallengeClaims에 오늘 날짜가 있는 경우 CLAIMED 반환
  9. [DAILY] 진행도 0인데 오늘 업데이트됨 → INCOMPLETE
    • 시도는 있었지만 progressVal이 0이라면 미완료 상태
  10. progress 데이터가 아예 없음 → INCOMPLETE
    • userChallengeProgresses 배열이 비어 있는 경우 도전 자체를 안 한 상태로 간주

canClaimChallenge 테스트

  1. 챌린지 자체가 존재하지 않음 → false, 이유: "Challenge not found"
    • challenge.findUnique 결과가 null인 경우
  2. 이미 보상을 수령함 → false, 이유: "Already claimed"
    • userChallengeClaims에 해당 챌린지가 존재하면 수령 불가
  3. 수령 가능 조건 충족 → true
    • calculateChallengeStatus 결과가 ACHIEVABLE이면 수령 가능
  4. 수령 조건을 충족하지 못함 → false, 이유: "Challenge not completed"
    • 도전 기록은 있으나 진행도가 부족해 달성 기준을 만족하지 않은 경우

Challenge Claim Service 테스트 (challenge-claim.test.ts)

  1. ONCE 타입 챌린지 보상 수령 성공
    • 챌린지 타입이 ONCE인 경우 보상 수령이 정상적으로 처리되는지 테스트합니다.
    • userChallengeClaim.create 호출 확인
    • userChallengeProgress.updateMany 호출되지 않아야 함
    • eTFTransaction.create가 올바르게 호출되고 transactionId 확인
  2. DAILY 타입 챌린지 보상 수령 성공 및 진행도 초기화
    • 챌린지 타입이 DAILY인 경우 보상이 정상적으로 수령되고 진행도가 초기화되어야 합니다.
    • userChallengeClaim.create 호출 확인
    • userChallengeProgress.updateManyprogressVal: 0으로 호출되었는지 확인
    • eTFTransaction.create 호출 및 transactionId 반환 검증
  3. 존재하지 않는 챌린지일 경우 실패
    • challenge.findUniquenull인 경우 챌린지를 찾을 수 없어야 합니다.
    • 함수는 success: false"Challenge not found" 메시지를 반환해야 함
  4. ISA 계좌가 없을 경우 실패
    • 사용자가 ISA 계좌를 가지고 있지 않은 경우 보상 수령은 실패해야 합니다.
    • "ISA account not found" 메시지가 반환되어야 함
  5. 최신 ETF 종가가 없을 경우 실패
    • etfDailyTrading.findFirst 결과가 null인 경우 최신 종가가 없다고 판단해야 합니다.
    • "Latest ETF price not found" 메시지를 반환해야 하며 이후 로직은 실행되지 않아야 함
  6. 기존 ETF 보유량이 있을 경우 평균단가 재계산
    • 사용자가 해당 ETF를 이미 보유한 경우 기존 수량과 평균단가를 기반으로 새 평균단가를 계산해야 합니다.
    • 평균단가 계산이 (10×1500 + 5×2000)/15 = 1666.67로 정확히 수행되어야 함
    • eTFHolding.upsert 호출 시 해당 값이 정확히 반영되어야 함
  7. 수량과 가격이 소수점일 경우 평균단가 정확성 검증
    • quantityprice가 소수점을 포함하더라도 평균단가 계산이 정확해야 합니다.
    • 예: (3.5×1400.25 + 2.5×1500.75)/6.0 = 1442.125 계산 정확도 검증
    • 계산된 값이 eTFHolding.upsert에 정확히 전달되어야 함

challenge claim API 테스트 (challenge-claim-api.test.ts)

  1. 인증되지 않은 사용자는 401 반환
    • getServerSession 결과가 null인 경우 인증되지 않은 사용자로 간주
    • 401 Unauthorized 상태 코드와 "Unauthorized" 메시지를 반환해야 함
  2. challengeId가 없으면 400 반환
    • 요청 본문에 challengeId가 누락된 경우
    • 400 Bad Request 상태 코드와 "Challenge ID is required" 메시지를 반환해야 함
  3. 정상적인 보상 수령 요청 처리
    • 인증된 사용자가 유효한 challengeId로 요청한 경우
    • canClaimChallengetrue, claimChallengeRewardsuccess: true일 때
    • 200 OK 상태 코드와 "Reward claimed successfully" 메시지, transactionId가 반환되어야 함
  4. 보상 수령 불가한 경우 500 반환
    • canClaimChallengefalse이며 reason이 존재하는 경우
    • 예외로 throw된 에러 메시지가 "Already claimed"인지 확인
    • 상태 코드는 500 Internal Server Error로 반환되어야 함
  5. 보상 수령 중 서비스 내부 실패 시 400 반환
    • canClaimChallenge는 통과했지만 claimChallengeRewardsuccess: false를 반환한 경우
    • 예: ISA 계좌 없음 → "ISA account not found" 메시지
    • 400 Bad Request 상태 코드와 해당 메시지를 반환해야 함
  6. 예상치 못한 에러가 발생할 경우 500 반환
    • Prisma 트랜잭션 또는 내부 로직에서 예외가 발생한 경우 (mockRejectedValue)
    • 500 Internal Server Error 상태 코드와 "Database error" 메시지를 반환해야 함

📸 스크린샷

  • npx jest __tests__/services/challenge-status.test.ts

스크린샷 2025-06-28 155314

  • npx jest __tests__/services/challenge-claim.test.ts

스크린샷 2025-06-28 163520

  • npx jest __tests_/api/challenge-claim-api.test.ts

스크린샷 2025-06-28 163706

@HyejeongSon HyejeongSon self-assigned this Jun 28, 2025
@HyejeongSon HyejeongSon added the enhancement New feature or request label Jun 28, 2025
@HyejeongSon HyejeongSon linked an issue Jun 28, 2025 that may be closed by this pull request
@KimGiii
Copy link
Contributor

KimGiii commented Jun 28, 2025

테스트 describe 왜 죄다 영어로 해놓으셨는지...ㅋㅋㅋㅋ
한글로 바꾸주시면 안될까요?

@HyejeongSon
Copy link
Contributor Author

테스트 describe 왜 죄다 영어로 해놓으셨는지...ㅋㅋㅋㅋ 한글로 바꾸주시면 안될까요?

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

@HyejeongSon HyejeongSon merged commit 8303b53 into develop Jun 28, 2025
1 check passed
@VarGun VarGun deleted the test/#191-challenge-test branch July 2, 2025 00:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ Feat ] Challenge 테스트 코드 작성

4 participants