-
Notifications
You must be signed in to change notification settings - Fork 2
[FEAT]: 테스트코드 추가 및 개선 #148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
d135740
eb8c3b7
f7c249c
9003e0e
16762cc
c63af64
4fe69ca
e6270c1
c3bdf23
625aaec
01059ef
b975b16
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| # ============================================ | ||
| # Eatsfine Backend - 환경변수 설정 가이드 | ||
| # ============================================ | ||
| # 이 파일을 `.env`로 복사한 뒤, 실제 값을 입력하세요. | ||
| # cp .env.example .env | ||
| # | ||
| # ⚠️ 주의: .env 파일은 절대 Git에 커밋하지 마세요! | ||
| # ============================================ | ||
|
|
||
| # ── Database (MySQL) ────────────────────────── | ||
| DB_HOST=localhost | ||
| DB_PORT=3306 | ||
| DB_NAME=eatsfine_local | ||
| DB_USERNAME=root | ||
| DB_PASSWORD=your_password | ||
|
|
||
| # ── Redis ───────────────────────────────────── | ||
| REDIS_HOST=localhost | ||
| REDIS_PORT=6379 | ||
|
|
||
| # ── JWT ─────────────────────────────────────── | ||
| SECRET_KEY=your_jwt_secret_key_must_be_long_enough_32chars | ||
|
|
||
| # ── Toss Payments ───────────────────────────── | ||
| # 토스페이먼츠 개발자센터에서 테스트 시크릿 키 발급 | ||
| # https://developers.tosspayments.com | ||
| TOSS_WIDGET_SECRET_KEY=test_sk_xxxxxxxxxxxxxxxx | ||
|
|
||
| # ── OAuth2 (Google) ─────────────────────────── | ||
| # Google Cloud Console에서 OAuth 2.0 클라이언트 ID 발급 | ||
| # https://console.cloud.google.com/apis/credentials | ||
| GOOGLE_CLIENT_ID=your_google_client_id | ||
| GOOGLE_CLIENT_SECRET=your_google_client_secret | ||
|
|
||
| # ── OAuth2 (Kakao) ──────────────────────────── | ||
| # 카카오 개발자센터에서 앱 키 발급 | ||
| # https://developers.kakao.com | ||
| KAKAO_CLIENT_ID=your_kakao_client_id | ||
| KAKAO_CLIENT_SECRET=your_kakao_client_secret | ||
|
|
||
| # ── AWS S3 ──────────────────────────────────── | ||
| # AWS IAM에서 액세스 키 발급 (로컬 개발 시 필수) | ||
| # https://console.aws.amazon.com/iam | ||
| AWS_ACCESS_KEY_ID=your_aws_access_key_id | ||
| AWS_SECRET_ACCESS_KEY=your_aws_secret_access_key | ||
| AWS_REGION=ap-northeast-2 | ||
| AWS_S3_BUCKET=your-bucket-name | ||
| AWS_S3_BASE_URL=https://your-bucket-name.s3.ap-northeast-2.amazonaws.com | ||
|
Comment on lines
+44
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial AWS S3 섹션 키 순서 —
올바른 순서: ♻️ 제안: 알파벳 순서로 재정렬 # ── AWS S3 ────────────────────────────────────
# AWS IAM에서 액세스 키 발급 (로컬 개발 시 필수)
# https://console.aws.amazon.com/iam
AWS_ACCESS_KEY_ID=your_aws_access_key_id
-AWS_SECRET_ACCESS_KEY=your_aws_secret_access_key
AWS_REGION=ap-northeast-2
-AWS_S3_BUCKET=your-bucket-name
AWS_S3_BASE_URL=https://your-bucket-name.s3.ap-northeast-2.amazonaws.com
+AWS_S3_BUCKET=your-bucket-name
+AWS_SECRET_ACCESS_KEY=your_aws_secret_access_key🧰 Tools🪛 dotenv-linter (4.0.0)[warning] 46-46: [UnorderedKey] The AWS_REGION key should go before the AWS_SECRET_ACCESS_KEY key (UnorderedKey) [warning] 47-47: [UnorderedKey] The AWS_S3_BUCKET key should go before the AWS_SECRET_ACCESS_KEY key (UnorderedKey) [warning] 48-48: [UnorderedKey] The AWS_S3_BASE_URL key should go before the AWS_S3_BUCKET key (UnorderedKey) 🤖 Prompt for AI Agents |
||
|
|
||
| # ── 사업자등록 조회 API ─────────────────────── | ||
| # 공공데이터포털에서 API 키 발급 | ||
| BIZ_API_KEY=your_biz_api_key | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,11 +12,16 @@ | |
| import com.eatsfine.eatsfine.domain.payment.enums.PaymentProvider; | ||
| import com.eatsfine.eatsfine.domain.payment.enums.PaymentStatus; | ||
| import com.eatsfine.eatsfine.domain.payment.enums.PaymentType; | ||
| import com.eatsfine.eatsfine.domain.user.entity.User; | ||
| import com.eatsfine.eatsfine.domain.user.enums.Role; | ||
| import com.eatsfine.eatsfine.domain.payment.repository.PaymentRepository; | ||
| import com.eatsfine.eatsfine.domain.payment.exception.PaymentException; | ||
| import com.eatsfine.eatsfine.domain.payment.status.PaymentErrorStatus; | ||
| import com.eatsfine.eatsfine.global.apiPayload.code.status.ErrorStatus; | ||
| import com.eatsfine.eatsfine.global.apiPayload.exception.GeneralException; | ||
| import com.eatsfine.eatsfine.domain.user.repository.UserRepository; | ||
| import com.eatsfine.eatsfine.domain.user.exception.UserException; | ||
| import com.eatsfine.eatsfine.domain.user.status.UserErrorStatus; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.data.domain.Page; | ||
|
|
@@ -38,6 +43,7 @@ public class PaymentService { | |
|
|
||
| private final PaymentRepository paymentRepository; | ||
| private final BookingRepository bookingRepository; | ||
| private final UserRepository userRepository; | ||
| private final TossPaymentService tossPaymentService; | ||
|
|
||
| @Transactional | ||
|
|
@@ -122,7 +128,6 @@ public PaymentResponseDTO.PaymentSuccessResultDTO confirmPayment(PaymentConfirmD | |
| log.info("Booking confirmed for OrderID: {}", dto.orderId()); | ||
| } | ||
|
|
||
|
|
||
| log.info("Payment confirmed for OrderID: {}", dto.orderId()); | ||
|
|
||
| return new PaymentResponseDTO.PaymentSuccessResultDTO( | ||
|
|
@@ -136,7 +141,6 @@ public PaymentResponseDTO.PaymentSuccessResultDTO confirmPayment(PaymentConfirmD | |
| payment.getReceiptUrl()); | ||
| } | ||
|
|
||
|
|
||
| @Transactional(noRollbackFor = GeneralException.class) | ||
| public PaymentResponseDTO.CancelPaymentResultDTO cancelPayment(String paymentKey, | ||
| PaymentRequestDTO.CancelPaymentDTO dto) { | ||
|
|
@@ -162,28 +166,30 @@ public PaymentResponseDTO.CancelPaymentResultDTO cancelPayment(String paymentKey | |
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public PaymentResponseDTO.PaymentListResponseDTO getPaymentList(Long userId, Integer page, Integer limit, | ||
| public PaymentResponseDTO.PaymentListResponseDTO getPaymentList(String email, Integer page, Integer limit, | ||
| String status) { | ||
| // limit 기본값 처리 (만약 null이면 10) | ||
| int size = (limit != null) ? limit : 10; | ||
| User user = userRepository.findByEmail(email) | ||
| .orElseThrow(() -> new UserException(UserErrorStatus.MEMBER_NOT_FOUND)); | ||
| // limit 기본값 처리 (null이거나 0 이하이면 10) | ||
| int size = (limit != null && limit > 0) ? limit : 10; | ||
| // page 기본값 처리 (만약 null이면 1, 0보다 작으면 1로 보정). Spring Data는 0-based index이므로 -1 | ||
| int pageNumber = (page != null && page > 0) ? page - 1 : 0; | ||
|
|
||
| Pageable pageable = PageRequest.of(pageNumber, size); | ||
|
|
||
| PaymentStatus paymentStatus = parsePaymentStatus(status); | ||
|
|
||
| Page<Payment> paymentPage; | ||
| if (status != null && !status.isEmpty()) { | ||
| PaymentStatus paymentStatus; | ||
| try { | ||
| paymentStatus = PaymentStatus.valueOf(status.toUpperCase()); | ||
| } catch (IllegalArgumentException e) { | ||
| // 유효하지 않은 status가 들어오면 BadRequest 예외 발생 | ||
| throw new GeneralException(ErrorStatus._BAD_REQUEST); | ||
| } | ||
| paymentPage = paymentRepository.findAllByBooking_User_IdAndPaymentStatus(userId, paymentStatus, | ||
| pageable); | ||
| if (user.getRole() == Role.ROLE_OWNER) { | ||
| paymentPage = (paymentStatus != null) | ||
| ? paymentRepository.findAllByOwnerIdAndStatusWithDetails(user.getId(), | ||
| paymentStatus, pageable) | ||
| : paymentRepository.findAllByOwnerIdWithDetails(user.getId(), pageable); | ||
| } else { | ||
| paymentPage = paymentRepository.findAllByBooking_User_Id(userId, pageable); | ||
| paymentPage = (paymentStatus != null) | ||
| ? paymentRepository.findAllByUserIdAndStatusWithDetails(user.getId(), | ||
| paymentStatus, pageable) | ||
| : paymentRepository.findAllByUserIdWithDetails(user.getId(), pageable); | ||
| } | ||
|
Comment on lines
168
to
193
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 이메일 기반 접근 제어와 역할 분기 로직이 깔끔하게 구현되었습니다.
한 가지 개선 사항으로, ♻️ limit 상한 제한 제안- int size = (limit != null && limit > 0) ? limit : 10;
+ int size = (limit != null && limit > 0) ? Math.min(limit, 100) : 10;As per coding guidelines, "캐시 적용 가능성이 있거나, 과도한 연산이 반복되는 구간이 있는지" 검토 항목에 해당합니다. 🤖 Prompt for AI Agents |
||
|
|
||
| List<PaymentResponseDTO.PaymentHistoryResultDTO> payments = paymentPage.getContent().stream() | ||
|
|
@@ -211,10 +217,19 @@ public PaymentResponseDTO.PaymentListResponseDTO getPaymentList(Long userId, Int | |
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public PaymentResponseDTO.PaymentDetailResultDTO getPaymentDetail(Long paymentId) { | ||
| Payment payment = paymentRepository.findById(paymentId) | ||
| public PaymentResponseDTO.PaymentDetailResultDTO getPaymentDetail(Long paymentId, String email) { | ||
| User user = userRepository.findByEmail(email) | ||
| .orElseThrow(() -> new UserException(UserErrorStatus.MEMBER_NOT_FOUND)); | ||
| Payment payment = paymentRepository.findByIdWithDetails(paymentId) | ||
| .orElseThrow(() -> new PaymentException(PaymentErrorStatus._PAYMENT_NOT_FOUND)); | ||
|
|
||
| boolean isBooker = payment.getBooking().getUser().getId().equals(user.getId()); | ||
| boolean isStoreOwner = payment.getBooking().getStore().getOwner().getId().equals(user.getId()); | ||
|
|
||
| if (!isBooker && !isStoreOwner) { | ||
| throw new PaymentException(PaymentErrorStatus._PAYMENT_ACCESS_DENIED); | ||
| } | ||
|
Comment on lines
219
to
+231
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# 1. Booking 엔티티 파일 찾기 및 user, store 필드의 nullable 설정 확인
echo "=== Booking 엔티티 검색 ==="
fd -e java -i "booking" | grep -i entity
echo ""
# 2. 엔티티 정의 확인
find . -path "*/entity/*" -name "*.java" | grep -i booking | head -5
find . -path "*/entity/*" -name "*.java" | grep -i store | head -5
find . -path "*/entity/*" -name "*.java" | grep -i payment | head -5Repository: Eatsfine/BE Length of output: 781 🏁 Script executed: #!/bin/bash
# Booking 엔티티의 user와 store 필드 정의 확인
echo "=== Booking Entity 분석 ==="
find . -type f -name "Booking.java" -not -path "*/test/*" -not -path "*/.git/*" | while read f; do
echo "File: $f"
cat -n "$f" | grep -A 3 -B 1 '@ManyToOne\|@OneToOne\|private.*user\|private.*store\|@JoinColumn'
doneRepository: Eatsfine/BE Length of output: 577 🏁 Script executed: #!/bin/bash
# Store 엔티티의 owner 필드 정의 확인
echo "=== Store Entity owner 필드 분석 ==="
find . -type f -name "Store.java" -not -path "*/test/*" | while read f; do
echo "File: $f"
cat -n "$f" | grep -A 3 -B 1 'owner\|@ManyToOne.*owner\|@JoinColumn.*owner'
doneRepository: Eatsfine/BE Length of output: 414 🏁 Script executed: #!/bin/bash
# findByIdWithDetails 메서드 구현 확인
echo "=== findByIdWithDetails 구현 검색 ==="
rg "findByIdWithDetails" -A 5 -B 2Repository: Eatsfine/BE Length of output: 2944 쿼리에서 store의 owner를 함께 로드해야 합니다. 소유권 검증 로직 자체는 올바릅니다. 다만 현재 쿼리: SELECT p FROM Payment p JOIN FETCH p.booking b JOIN FETCH b.store JOIN FETCH b.user WHERE p.id = :paymentId이 쿼리는 🤖 Prompt for AI Agents |
||
|
|
||
| return new PaymentResponseDTO.PaymentDetailResultDTO( | ||
| payment.getId(), | ||
| payment.getBooking().getId(), | ||
|
|
@@ -306,4 +321,15 @@ public void processWebhook(PaymentWebhookDTO dto) { | |
| log.info("Webhook processed: Payment {} status updated to REFUNDED", data.orderId()); | ||
| } | ||
| } | ||
|
|
||
| private PaymentStatus parsePaymentStatus(String status) { | ||
| if (status == null || status.isBlank()) { | ||
| return null; | ||
| } | ||
| try { | ||
| return PaymentStatus.valueOf(status.toUpperCase()); | ||
| } catch (IllegalArgumentException e) { | ||
| throw new GeneralException(ErrorStatus._BAD_REQUEST); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -60,14 +60,16 @@ spring: | |||||||||||
|
|
||||||||||||
| payment: | ||||||||||||
| toss: | ||||||||||||
| widget-secret-key: test_gsk_docs_OaPz8L5KdmQXkzRz3y47BMw6 | ||||||||||||
| widget-secret-key: ${TOSS_WIDGET_SECRET_KEY} | ||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
|
|
||||||||||||
| cloud: | ||||||||||||
| aws: | ||||||||||||
| region: ap-northeast-2 | ||||||||||||
| region: ${AWS_REGION} | ||||||||||||
| s3: | ||||||||||||
| bucket: eatsfine-images | ||||||||||||
| base-url: https://eatsfine-images.s3.ap-northeast-2.amazonaws.com | ||||||||||||
| bucket: ${AWS_S3_BUCKET} | ||||||||||||
| base-url: ${AWS_S3_BASE_URL} | ||||||||||||
|
|
||||||||||||
| api: | ||||||||||||
| service-key: ${BIZ_API_KEY} | ||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
| jwt: | ||||||||||||
|
Comment on lines
+73
to
74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
파일 내 다른 최상위 섹션들( ✏️ 빈 줄 추가 제안 api:
service-key: ${BIZ_API_KEY}
+
jwt:
secret: ${SECRET_KEY}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents🧹 Nitpick | 🔵 Trivial
파일 내 다른 최상위 섹션들( ✏️ 빈 줄 추가 제안 api:
service-key: ${BIZ_API_KEY}
+
jwt:
secret: ${SECRET_KEY}🤖 Prompt for AI Agents |
||||||||||||
| secret: ${SECRET_KEY} | ||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
dotenv-linter경고: DB 섹션 키 순서dotenv-linter가DB_NAME(N < P)과DB_PASSWORD(PAS < POR)가 알파벳 순서상DB_PORT앞에 와야 한다고 경고합니다. 현재 순서(HOST→PORT→NAME→USERNAME→PASSWORD)가 의미적으로 읽기 쉽다면 무시할 수도 있지만, 린터 일관성을 원할 경우 아래와 같이 정렬하세요.🔧 제안: 알파벳 순서로 정렬
📝 Committable suggestion
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 13-13: [UnorderedKey] The DB_NAME key should go before the DB_PORT key
(UnorderedKey)
[warning] 15-15: [UnorderedKey] The DB_PASSWORD key should go before the DB_PORT key
(UnorderedKey)
🤖 Prompt for AI Agents