Skip to content

feat: 회원 데이터 CRUD API#10

Merged
bebeis merged 50 commits intomainfrom
development
Jun 16, 2025
Merged

feat: 회원 데이터 CRUD API#10
bebeis merged 50 commits intomainfrom
development

Conversation

@bebeis
Copy link
Contributor

@bebeis bebeis commented Jun 15, 2025

Summary by CodeRabbit

  • 신규 기능

    • 예산(일/월) 및 지출 관리, 그룹(학교 등) 관리, 회원 인증/계정/선호도/주소 관리 등 다양한 REST API 컨트롤러가 추가되었습니다.
    • 신용카드 승인 문자 자동 파싱 기능이 추가되었습니다.
    • 예산, 지출, 그룹, 회원, 선호도 등 다양한 도메인 및 DTO 클래스가 도입되었습니다.
    • 연월(YearMonth) 포맷 지원 및 커스텀 포매터가 추가되었습니다.
  • 버그 수정 및 개선

    • 예외 처리 및 에러 메시지가 개선되고, 사용자 친화적으로 변경되었습니다.
    • 엔티티 그래프 및 유니크 제약 등 JPA 관련 설정이 보완되었습니다.
  • 테스트

    • 예산, 지출, 그룹, 회원, 선호도 등 주요 도메인 및 서비스에 대한 통합/단위 테스트가 다수 추가되었습니다.
    • 신용카드 문자 파서, 외부 API, 리포지토리 등에 대한 상세 테스트가 도입되었습니다.
  • 리팩터/정리

    • 불필요하거나 중복된 테스트 및 컨트롤러, 서비스 코드가 삭제 및 리팩터링되었습니다.
    • 내부 예외 계층 구조 및 메서드 시그니처가 정비되었습니다.
  • 기타

    • 빌드 설정에 Spring AI, protobuf 등 의존성이 추가되었습니다.

bebeis and others added 30 commits May 29, 2025 22:13
providerId에 유니크 컬럼 적용
전역 Exception Handler에서 정적 리소스 예외 잡도록 수정
1. 외부 Api 호출 발생 시 발생하는 예외 통일
2. 잘못된 예외 객체 사용 수정
ID 값을 포함하여 반환화도록 합니다.
1. /profile -> /profiles
2. AddressCUReqeust -> MemberAddressCURequest
캐스팅 시 타입 체크를 추가하고, 동시에 pattern variable을 적용합니다.
1. 학교 그룹을 생성, 수정, 삭제할 수 있는 REST API 엔드포인트가 추가되었습니다.
2. 학교 그룹 등록 시 도로명 주소, 상세 주소, 이름, 학교 유형을 입력할 수 있습니다.
3. 학교 그룹 정보 수정 및 삭제 기능이 제공됩니다.
YearMonthFormatter를 적용한 파라미터의 타입을 변경합니다.
신규 기능
- 일별 및 월별 예산을 날짜 또는 연월 기준으로 조회할 수 있는 API가 추가되었습니다.
- 일별/월별 예산의 한도를 등록 및 수정할 수 있는 API가 추가되었습니다.
- 지정한 날짜가 포함된 주간의 일별 예산 목록을 조회할 수 있는 기능이 제공됩니다.
- 연월 포맷을 지원하는 커스텀 날짜 포맷터 및 어노테이션이 도입되었습니다.

버그 수정
- 예산 조회 및 저장 시 존재하지 않는 프로필이나 예산에 대해 예외 메시지가 개선되었습니다.

테스트
- 예산 도메인, 서비스, 저장소의 동작을 검증하는 다양한 통합 및 단위 테스트가 추가되었습니다.
- 대용량 데이터, 엣지 케이스, 다중 사용자 시나리오 등 다양한 상황에 대한 테스트가 포함되었습니다.

문서화
- API 응답 구조가 일별/월별 예산의 상세 정보를 포함하도록 개선되었습니다.
MemberProfileController의 책임을 분리
여러번의 저장 쿼리를 saveAll 한 번으로 개선합니다.
클래스 책임에 맞게 클래스 구조를 리팩토링
bebeis and others added 20 commits June 12, 2025 16:54
기능 개선 및 변경

그룹 및 회원 프로필 관련 엔드포인트가 분리·재구성되어 관리가 용이해졌습니다.
기존에 제공되던 일부 그룹, 회원, 예산, 선호도 관련 API가 통합 또는 분리되었습니다.
1. 다양한 카드사(KB국민, NH농협, 신한) 승인 문자에서 결제 정보를 자동으로 추출하는 기능이 추가되었습니다.
2. 카드사별 문자 형식이 인식되지 않을 경우, AI 기반 파서를 통해 결제 내역을 분석할 수 있습니다.
1. 무한 스크롤 페이징 지출 내역 조회 API
2. 결제 메시지 파싱 API
3. 지출 내역 등록 API
4. 지출 내역 수정 API
5. 지출 내역 삭제 API
1. 불필요한 쿼리 메서드 제거
2. 접근 권한 예외 처리 추가
3. 검증 애노테이션 추가
1. 무한 스크롤 페이징 지출 내역 조회 API
2. 결제 메시지 파싱 API
3. 지출 내역 등록 API
4. 지출 내역 수정 API
5. 지출 내역 삭제 API
6. 테스트 코드 추가
@coderabbitai
Copy link

coderabbitai bot commented Jun 15, 2025

Walkthrough

이번 변경사항은 예산 및 지출 관리, 그룹 및 회원 관리, 신용카드 메시지 파싱, 예외 처리, 인증/인가, 주소 관리 등 핵심 도메인 전반에 걸쳐 대규모 기능 확장과 리팩토링, 그리고 테스트 코드의 대대적 추가를 포함합니다. REST 컨트롤러, 서비스, 도메인, 예외, 저장소, DTO, 포맷터, 테스트 코드가 다수 신설·변경되었습니다.

Changes

파일/경로 그룹 변경 요약
build.gradle Spring AI, Google protobuf 등 신규 의존성 및 BOM 관리 추가
component/creditmessage/* (7개 파일) 신용카드 메시지 파싱 매니저, 파서 인터페이스 및 카드사별/AI 파서, DTO 클래스 신설
domain/Budget/* (5개 파일) Expenditure 엔티티 신설, Budget/Expenditure 관련 메서드 추가, 일부 공백 추가
domain/group/* (2개 파일) Group, SchoolGroup 생성자·수정 메서드 추가
domain/member/MemberProfile.java defaultMonthlyLimit, defaultDailyLimit 필드 및 관련 메서드 삭제
domain/social/SocialAccount.java providerUserId 필드에 unique 제약 추가
exception/* (5개 파일) BizLogicException 신설, 기존 예외 계층 개편 및 생성자 추가, 예외 상속 구조 변경
infrastructure/KakaoAddressApiService.java 주소 생성 시 예외 처리 및 응답 검증 로직 추가
infrastructure/SocialAuthService.java 외부 API 호출 시 예외 처리 로직 추가
infrastructure/dto/AddressRequest.java alias 필드 삭제
repository/* (6개 파일) Budget, Expenditure 등 쿼리 메서드 확장/리팩토링, 페이징·기간·정렬 지원, Slice 기반 조회 등
service/BudgetService.java 예산 관련 신규 메서드 다수 추가/수정, 예외 타입 변경
service/ExpenditureService.java Expenditure 등록/수정/삭제/조회 서비스 신설
service/GroupService.java, GroupServiceImpl.java 학교 그룹 생성/수정/삭제 메서드 및 주소 API 연동 추가
service/MemberCategoryPreferenceService.java 예외 타입 변경
service/MemberProfileService.java 예외 타입 변경, registerDefaultBudgets 메서드 삭제
service/MemberService.java 예외 메시지 및 타입 변경, Javadoc 삭제
service/SocialAccountService.java 예외 메시지 및 타입 변경
web/WebConfig.java YearMonth 포맷터 등록 메서드 오버라이드
web/controller/* (13개 파일) 인증, 회원, 그룹, 예산, 지출, 선호도 등 REST 컨트롤러 신설/분리/삭제/경로변경/DTO 정비
web/dto/group/* (3개 파일) 그룹/학교그룹 관련 DTO 신설
web/exhandler/ExControllerAdvice.java 예외 핸들러 추가/수정/삭제, 메시지 개선
web/validation/* (2개 파일) YearMonthFormat 어노테이션 및 포맷터 팩토리 신설
test/java/com/stcom/smartmealtable/* (대부분의 test/* 파일) 도메인, 서비스, 저장소, 컨트롤러 등 통합·단위 테스트 대규모 신설 및 기존 테스트 삭제/대체

Sequence Diagram(s)

Expenditure 등록 및 예산 반영

sequenceDiagram
    participant User
    participant MemberExpenditureController
    participant ExpenditureService
    participant ExpenditureRepository
    participant BudgetRepository

    User->>MemberExpenditureController: POST /api/v1/members/me/expenditures
    MemberExpenditureController->>ExpenditureService: registerExpenditure(profileId, spentDate, amount, tradeName)
    ExpenditureService->>BudgetRepository: findDailyBudgetByMemberProfileIdAndDate(...)
    ExpenditureService->>BudgetRepository: findMonthlyBudgetByMemberProfileIdAndYearMonth(...)
    ExpenditureService->>ExpenditureRepository: save(Expenditure)
    ExpenditureService->>BudgetRepository: 일/월 예산 spent 값 증가
    ExpenditureService-->>MemberExpenditureController: 완료
    MemberExpenditureController-->>User: 성공 응답
Loading

신용카드 메시지 파싱

sequenceDiagram
    participant User
    participant MemberExpenditureController
    participant CreditMessageManager
    participant VendorParser
    participant GeminiCreditMessageParser

    User->>MemberExpenditureController: POST /messages/parse (message)
    MemberExpenditureController->>CreditMessageManager: parseMessage(message)
    CreditMessageManager->>VendorParser: checkVendor(message)
    alt VendorParser 인식 성공
        CreditMessageManager->>VendorParser: parse(message)
        alt 파싱 성공
            VendorParser-->>CreditMessageManager: ExpenditureDto
        else 파싱 실패
            CreditMessageManager->>GeminiCreditMessageParser: parse(message)
        end
    else 모든 VendorParser 실패
        CreditMessageManager->>GeminiCreditMessageParser: parse(message)
    end
    CreditMessageManager-->>MemberExpenditureController: ExpenditureDto
    MemberExpenditureController-->>User: 파싱 결과 응답
Loading

학교 그룹 생성/수정/삭제

sequenceDiagram
    participant User
    participant SchoolGroupController
    participant GroupService
    participant KakaoAddressApiService
    participant GroupRepository

    User->>SchoolGroupController: POST/PATCH/DELETE /api/v1/schools
    SchoolGroupController->>GroupService: create/change/deleteSchoolGroup(...)
    GroupService->>KakaoAddressApiService: createAddressFromRequest(...)
    GroupService->>GroupRepository: save/find/delete(...)
    GroupService-->>SchoolGroupController: 완료
    SchoolGroupController-->>User: 성공 응답
Loading

Possibly related PRs

Poem

  (  )   (   )  )
   ) (   )  (  (
  ( )  (    ) )
 .-""-.-""-.-""-.
(  예산과 지출, 그룹과 카드! )
 '-..-'-..-'-..-'
새 컨트롤러, 파서, 예외까지,
토끼는 깡총깡총 기뻐하네!
테스트도 빵빵, 기능은 탄탄,
우리 프로젝트, 이제는 만점!
🥕🎉
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@bebeis bebeis changed the title chore(release): 0.1.0 feat(release): 0.1.0 Jun 15, 2025
@bebeis bebeis changed the title feat(release): 0.1.0 feat(release): 데이터 CRUD API Jun 15, 2025
@bebeis bebeis changed the title feat(release): 데이터 CRUD API feat(release): 회원 데이터 CRUD API Jun 15, 2025
@bebeis bebeis changed the title feat(release): 회원 데이터 CRUD API feat: 회원 데이터 CRUD API Jun 15, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 52

🧹 Nitpick comments (90)
src/main/java/com/stcom/smartmealtable/exception/BizLogicException.java (1)

3-12: serialVersionUID와 추가 생성자 제공 권장

RuntimeException을 상속한 커스텀 예외라도 직렬화를 고려해 serialVersionUID를 선언하는 것이 관례입니다.
또한 원인 체인을 보존할 수 있도록 (String message, Throwable cause)(Throwable cause) 생성자도 함께 제공해 두면 추후 디버깅에 도움이 됩니다.

 public class BizLogicException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
@@
     public BizLogicException(String message) {
         super(message);
     }
+
+    public BizLogicException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public BizLogicException(Throwable cause) {
+        super(cause);
+    }
 }
src/main/java/com/stcom/smartmealtable/web/validation/YearMonthFormat.java (1)

9-15: 포맷 패턴 상수화 검토

"yyyy-MM" 문자열이 여러 곳에서 재사용될 가능성이 있으니 상수로 분리해두면 유지보수성이 향상됩니다.
현재 형태도 문제는 없으므로 참고만 해주세요.

src/main/java/com/stcom/smartmealtable/web/validation/YearMonthAnnotationFormatterFactory.java (1)

15-17: Set.of 사용으로 코드 간결화 가능

Java 9 이상이라면 굳이 List를 거치지 않고 Set.of(YearMonth.class)로 바로 초기화할 수 있습니다.

-    private static final Set<Class<?>> FIELD_TYPES =
-            Set.copyOf(List.of(YearMonth.class));
+    private static final Set<Class<?>> FIELD_TYPES = Set.of(YearMonth.class);
src/main/java/com/stcom/smartmealtable/domain/Budget/DailyBudget.java (1)

24-24: 불필요한 빈 줄 추가는 Diff 노이즈를 유발합니다
논리적 의미‧가독성 모두에 영향이 없는 빈 줄만 추가되었습니다. 향후 PR에서는 스타일 전용 변경을 기능 변경과 분리하거나, IDE / formatter 규칙을 통일해 불필요한 변경이 올라오지 않도록 해주세요.

src/main/java/com/stcom/smartmealtable/domain/Budget/MonthlyBudget.java (1)

27-27: 형식만 변경된 빈 줄
DailyBudget와 동일하게 의미 없는 빈 줄이 추가되었습니다. 코드 스타일 자동화로 관리하는 편이 PR diff를 깔끔하게 유지하는 데 도움이 됩니다.

src/main/java/com/stcom/smartmealtable/service/MemberCategoryPreferenceService.java (1)

26-27: 예외 타입 통일은 긍정적이지만, 도메인별 커스텀 예외를 고려해 보세요
IllegalStateExceptionIllegalArgumentException 변경으로 “잘못된 입력” 의미가 명확해졌습니다. 다만 동일한 “엔티티 부재”를 여러 서비스에서 반복 정의 중이라면, NotFoundException 과 같이 공통 커스텀 예외를 두고 ControllerAdvice에서 일관된 4xx 응답을 매핑하는 편이 유지보수에 유리합니다.

src/test/java/com/stcom/smartmealtable/integration/MemberIntegrationTest.java (1)

53-53: 하드코딩된 URL 문자열 추출 권장
엔드포인트가 /api/v1/auth/signup 로 변경되면서 테스트도 수정되었습니다. 추후 버전업 때 경로가 또 바뀔 경우 다수 테스트를 일일이 고쳐야 하므로,
private static final String SIGNUP_URL = "/api/v1/auth/signup";
과 같이 상수로 추출하거나, 컨트롤러의 RequestMapping 값에서 가져오는 방식(스프링 REST Docs 이용 등)을 고려해 주세요.

src/main/java/com/stcom/smartmealtable/domain/group/Group.java (2)

35-38: 불변성 및 널 검사 추가 고려

생성자에서 addressname에 대한 널 검사가 없어 잘못된 값이 그대로 저장될 수 있습니다. JPA 엔티티이므로 한 번 저장된 후 변경이 어려운 경우가 많아, 생성 시점에 검증을 넣는 편이 안전합니다.

 public Group(Address address, String name) {
-        this.address = address;
-        this.name = name;
+        if (address == null || name == null || name.isBlank()) {
+            throw new IllegalArgumentException("주소와 이름은 필수입니다");
+        }
+        this.address = address;
+        this.name = name;
 }

42-45: 메서드 명세 보강 필요

changeNameAndAddress는 이름과 주소를 모두 변경하지만, 변경 후 유효성 검사를 하지 않습니다. 추후 도메인 규칙(예: 이름 길이 제한, 주소 필수 등)이 생길 가능성을 고려해 검증 로직 또는 주석을 추가하면 유지보수성이 높아집니다.

src/main/java/com/stcom/smartmealtable/component/creditmessage/CreditMessageParser.java (1)

3-9: 인터페이스 문서화 및 반환 타입 개선 제안

  1. 퍼블릭 API이므로 메서드 목적·포맷 예시 등을 JavaDoc으로 명확히 해두면 구현체 작성과 테스트 작성이 수월해집니다.
  2. parse가 실패할 경우 예외를 던질지 null을 리턴할지 명확하지 않습니다. 실패 가능성을 표현하려면 Optional<ExpenditureDto>로 감싸거나, 명시적 예외 타입을 선언하는 방안을 고려해 주세요.
src/test/java/com/stcom/smartmealtable/domain/group/SchoolGroupTest.java (1)

32-36: 테스트 커버리지 확장 제안

비즈니스 메서드가 주소/이름·타입을 모두 변경하므로, 기존 값과 달라졌음을 검증하는 assert도 함께 넣어 두면 리팩토링 시 회귀 방지에 도움이 됩니다.

src/main/java/com/stcom/smartmealtable/service/MemberProfileService.java (2)

28-32: 중복된 예외 메시지 상수화 고려

"존재하지 않는 프로필입니다" 문자열이 여러 메서드에서 반복됩니다. 상수 또는 커스텀 예외로 추출하여 유지보수성을 높여 주세요.


56-57: 도메인 예외 유형 정비 필요

IllegalArgumentException은 매개변수 오류용입니다. “조회 결과 없음” 상황엔 EntityNotFoundException 같은 의미 있는 예외를 사용하면 글로벌 예외 처리에서 상태코드 매핑이 명확해집니다.

Also applies to: 69-70, 78-79, 92-93, 101-102

src/main/java/com/stcom/smartmealtable/infrastructure/SocialAuthService.java (1)

31-32: 중복 맵 조회 제거로 가독성 개선

socialMap.get(provider)를 두 번 호출하고 있습니다. 반환 객체를 지역 변수에 저장하면 NPE 발생 지점을 줄이고 가독성이 향상됩니다.

-ResponseSpec responseSpec = socialMap.get(provider).getRequestMessage(client, code).retrieve();
-return socialMap.get(provider).getTokenResponse(responseSpec);
+SocialHttpMessage msg = socialMap.get(provider);
+ResponseSpec responseSpec = msg.getRequestMessage(client, code).retrieve();
+return msg.getTokenResponse(responseSpec);
src/test/java/com/stcom/smartmealtable/domain/group/CompanyGroupTest.java (2)

20-23: ReflectionTestUtils 남용 주의

도메인 객체의 캡슐화를 우회하기 위해 리플렉션으로 필드를 직접 설정하고 있습니다.
가능하면 테스트 대상 메서드(changeNameAndAddress)만으로 상태를 준비하거나, 테스트 전용 생성자/빌더를 제공해 우회를 최소화해 주세요. 캡슐화 무력화는 리팩터링 시 테스트 안정성을 떨어뜨립니다.


29-31: 테스트 목적 불명확

changeNameAndAddress로 이름·주소를 변경한 뒤, 곧바로 industryType을 리플렉션으로 다시 덮어쓰고 있습니다.
이 테스트는 industryType 변경 로직을 검증하지 않으며 오히려 동알 필요 없는 코드만 추가합니다. 해당 라인 제거 혹은 별도 테스트로 분리해 목적을 명확히 해주세요.

src/test/java/com/stcom/smartmealtable/repository/GroupRepositoryTest.java (1)

34-35: FQN 대신 import 사용으로 가독성 향상

List<com.stcom.smartmealtable.domain.group.Group>처럼 완전 수식 경로를 사용하고 있습니다. 동일 파일 상단에 이미 여러 import가 있으니 Group을 import하여 코드 길이를 줄여 주세요.

src/test/java/com/stcom/smartmealtable/component/creditmessage/KBCreditMessageParserTest.java (1)

13-30: 벤더 판별 테스트를 파라미터화하면 중복을 줄일 수 있습니다
양(正/負) 케이스가 거의 동일한 구조를 가지므로 @ParameterizedTest@CsvSource(또는 @MethodSource)를 사용하면 테스트 코드량을 크게 줄이고 가독성을 높일 수 있습니다.

-@DisplayName("국민은행 결제 메시지인지 판별한다.")
-@Test
-void checkVendor() throws Exception {
-    // given
-    String kbMessage = ...
-    // when & then
-    assertThat(parser.checkVendor(kbMessage)).isTrue();
-}
-
-@DisplayName("잘못된 벤더사의 메시지인지 확인한다.")
-@Test
-void checkVendor2() throws Exception {
-    // given
-    String illegalMessage = ...
-    // when & then
-    assertThat(parser.checkVendor(illegalMessage)).isFalse();
-}
+@ParameterizedTest(name = "[{index}] {0} → {1}")
+@CsvSource(textBlock = """
+    [KB국민카드] 07/16 12:28 승인 11,000원 일시불 롯데시네마 평촌, true
+    [우리] 07/16 12:28 승인 11,000원 일시불 롯데시네마 평촌, false
+""")
+@DisplayName("벤더 판별")
+void checkVendor(String msg, boolean expected) {
+    assertThat(parser.checkVendor(msg)).isEqualTo(expected);
+}
src/test/java/com/stcom/smartmealtable/service/MemberServiceFailureIntegrationTest.java (3)

27-34: 예외 메시지까지 검증해 정확도를 높이세요
현재 changePassword_memberNotFound 테스트는 메시지를 hasMessageContaining으로만 확인합니다. 도메인 규격이 고정돼 있다면 hasMessage("회원이 존재하지 않습니다")로 명시적으로 검증하거나, 상수를 사용해 변경 시 컴파일 타임에 감지되도록 하는 편이 좋습니다.


38-41: 트랜잭션 롤백 전략 통일 필요
일부 테스트(checkPasswordDoublyMismatch, deleteMember_notFound)는 @Rollback이 없고, 일부는 선언돼 있습니다. 클래스 레벨에 이미 @Transactional이 존재하므로 Spring-Test 기본 정책(자동 롤백)을 따르든지, 전 테스트에 @Rollback을 명시적으로 부여하든지 일관성을 맞춰주세요.


50-63: 중복 이메일 검증 테스트에서 메시지도 확인하세요
실제 서비스 로직이 특정 메시지를 반환하도록 보장하려면 아래와 같이 메시지까지 검증해 주세요.

-assertThatThrownBy(() -> memberService.validateDuplicatedEmail("dup@test.com"))
-        .isInstanceOf(IllegalArgumentException.class);
+assertThatThrownBy(() -> memberService.validateDuplicatedEmail("dup@test.com"))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("이미 존재하는 이메일 입니다");
src/test/java/com/stcom/smartmealtable/infrastructure/SocialAuthServiceTest.java (1)

73-79: 예외 메시지 검증 추가 제안
외부 API 예외가 래핑될 때 메시지 보존 여부를 함께 검증하면, 문제 분석 시 정보 손실 여부를 사전에 확인할 수 있습니다.

-assertThrows(ExternApiStatusError.class,
-        () -> socialAuthService.getTokenResponse("kakao", "authCode"));
+ExternApiStatusError ex = assertThrows(ExternApiStatusError.class,
+        () -> socialAuthService.getTokenResponse("kakao", "authCode"));
+assertThat(ex).hasMessageContaining("api error");
src/test/java/com/stcom/smartmealtable/component/creditmessage/NHCreditMessageParserTest.java (1)

40-44: assertAll로 단일 Assertion 그룹화 제안

테스트의 가독성을 높이고, 첫 번째 실패 이후 나머지 검증이 스킵되는 문제를 방지하려면 여러 개의 assertThat 호출을 assertAll 블록으로 묶는 편이 좋습니다.
예시:

-        assertThat(expenditure.getVendor()).isEqualTo("NH");
-        assertThat(expenditure.getSpentDate()).isEqualTo(
-                LocalDateTime.of(LocalDateTime.now().getYear(), 10, 21, 8, 33));
-        assertThat(expenditure.getAmount()).isEqualTo(5700);
-        assertThat(expenditure.getTradeName()).isEqualTo("(주)티머니 개인택");
+        assertAll(
+                () -> assertThat(expenditure.getVendor()).isEqualTo("NH"),
+                () -> assertThat(expenditure.getSpentDate()).isEqualTo(
+                        LocalDateTime.of(LocalDateTime.now().getYear(), 10, 21, 8, 33)),
+                () -> assertThat(expenditure.getAmount()).isEqualTo(5700),
+                () -> assertThat(expenditure.getTradeName()).isEqualTo("(주)티머니 개인택")
+        );
src/test/java/com/stcom/smartmealtable/component/creditmessage/SHCreditMessageParserTest.java (3)

13-20: @DisplayName가 벤더명을 잘못 표기함

DisplayName 에서 “농협”이라고 되어 있어 테스트 설명이 실제 대상(신한카드)과 불일치합니다. 오해를 방지하려면 다음과 같이 수정해주세요.

-    @DisplayName("농협 결제 메시지인지 판별한다.")
+    @DisplayName("신한카드 결제 메시지인지 판별한다.")

22-30: @DisplayName 오타 동일

아래 테스트도 동일한 오타가 있습니다.

-    @DisplayName("잘못된 벤더사의 메시지인지 확인한다.")
+    @DisplayName("타 벤더사의 메시지 여부를 확인한다.")

32-44: 파싱 테스트 설명과 내용 불일치

테스트 설명에 “농협”이 포함되어 있어 신한카드 파서 테스트임을 명확히 하지 못합니다. 동시에 상단과 동일하게 assertAll 적용을 고려해주세요.

-    @DisplayName("농협 결제 메시지를 파싱한다.")
+    @DisplayName("신한카드 결제 메시지를 파싱한다.")
src/main/java/com/stcom/smartmealtable/domain/member/MemberProfile.java (1)

59-60: 불필요한 FQN 사용으로 가독성 저하

이미 상단에 java.util.List를 import했으므로 new ArrayList<>() 만으로 충분합니다. FQN은 삭제하는 편이 읽기 쉽습니다.

-        this.addressHistory = (addressHistory == null) ? new java.util.ArrayList<>() : addressHistory;
+        this.addressHistory = (addressHistory == null) ? new ArrayList<>() : addressHistory;
src/test/java/com/stcom/smartmealtable/repository/SocialAccountRepositoryTest.java (1)

20-27: 필드 명확성 향상 제안

repository 필드명이 범용적이라 테스트 가독성이 떨어집니다. socialAccountRepository 로 명시하면 의도가 분명해집니다.

-    private SocialAccountRepository repository;
+    private SocialAccountRepository socialAccountRepository;

이후 코드의 repository 참조도 동일하게 변경해 주세요.

src/main/java/com/stcom/smartmealtable/web/dto/group/GroupDto.java (2)

10-11: 기본 생성자(@NoArgsConstructor) 추가 고려

Jackson 등이 역직렬화를 수행해야 할 경우 기본 생성자가 없으면 실패할 수 있습니다. 현재는 응답 전용 DTO지만 재사용 가능성을 감안하면 @NoArgsConstructor를 추가해 두는 편이 안전합니다.


18-23: 도메인 의존성 제거를 위한 매핑 책임 분리 제안

DTO 내부에서 직접 Group 엔티티를 받아 필드를 매핑하면 웹-계층이 도메인에 강하게 결합됩니다.
MapStruct 등 매퍼 컴포넌트로 변환 책임을 분리하면 테스트 용이성과 계층 독립성이 개선됩니다.

src/test/java/com/stcom/smartmealtable/component/creditmessage/GeminiCreditMessageParserTest.java (1)

48-53: 날짜 검증 방식 강화

substring으로 잘라 비교하면 포맷 변경 시 테스트가 잘못된 통과/실패를 낼 수 있습니다.
예를 들어:

assertEquals(LocalDateTime.of(2025, 6, 12, 10, 20), dto.getSpentDate());

처럼 LocalDateTime 객체 자체를 비교하면 더 견고합니다.

src/main/java/com/stcom/smartmealtable/component/creditmessage/CreditMessageManager.java (1)

27-31: 예외 무시보다는 최소한의 로깅 필요

catch (Exception ignore)로 삼켜 버리면 실제 파싱 오류 원인 분석이 어려워집니다.
디버그 수준이라도 로그를 남겨 추적 가능하게 해 주세요.

- } catch (Exception ignore) {
+ } catch (Exception ex) {
+     log.debug("Rule-based parser [{}] failed, fallback to Gemini: {}", 
+               parser.getClass().getSimpleName(), ex.getMessage());
src/main/java/com/stcom/smartmealtable/component/creditmessage/ExpenditureDto.java (1)

8-16: record 사용으로 불변 DTO 단순화 가능

Java 21을 사용 중이므로, 단순 데이터 보관용 클래스는 record로 선언해 보일러플레이트를 제거하고 불변성을 확보할 수 있습니다.

-@Data
-@AllArgsConstructor
-@ToString
-public class ExpenditureDto {
-    private String vendor;
-    private LocalDateTime spentDate;
-    private Long amount;
-    private String tradeName;
-}
+public record ExpenditureDto(
+        String vendor,
+        LocalDateTime spentDate,
+        Long amount,
+        String tradeName
+) {}
src/test/java/com/stcom/smartmealtable/repository/FoodCategoryRepositoryTest.java (1)

29-31: ReflectionTestUtils 사용 최소화 권장

도메인 객체의 캡슐화를 깨는 ReflectionTestUtils 대신,
팩터리 메서드·생성자·빌더 패턴 등을 통해 name 필드를 세팅하면 테스트 유지보수성이 좋아집니다.

-FoodCategory category = new FoodCategory();
-ReflectionTestUtils.setField(category, "name", "한식");
+FoodCategory category = FoodCategory.builder()
+    .name("한식")
+    .build();
src/test/java/com/stcom/smartmealtable/repository/TermAgreementRepositoryTest.java (1)

43-48: 검증 항목에 isAgreed 포함 권장

TermAgreement의 핵심 값인 동의 여부가 검증 대상에서 빠져 있습니다. 추가 asserting 으로 회귀를 방지하세요.

assertThat(found.get().isAgreed()).isTrue();
src/test/java/com/stcom/smartmealtable/service/MemberProfileServiceIntegrationTest.java (2)

35-37: @Transactional 테스트에서 @Rollback 중복

@SpringBootTest + @Transactional 조합은 기본적으로 롤백됩니다. @Rollback 어노테이션은 중복이므로 제거하여 선언을 간소화할 수 있습니다.


48-60: 동일 세션 객체 재사용 시 지연로딩 주의

saveNewAddress() 호출 후 즉시 profile.getAddressHistory() 를 사용하고 있습니다.
profile 은 영속 컨텍스트에 연결된 상태라면 정상 작동하지만, 서비스 내부에서 flush·clear 를 수행할 경우 LAZY 컬렉션 접근 시 LazyInitializationException 이 발생할 수 있습니다.

안전하게 새로 조회하거나 메서드에서 반환값을 이용하는 형태로 수정하는 편이 좋습니다.

src/test/java/com/stcom/smartmealtable/service/GroupServiceIntegrationTest.java (1)

49-53: ReflectionTestUtils 사용 최소화 권장

엔티티 내부 필드를 리플렉션으로 조작하면 도메인 모델이 리팩터링될 때 테스트가 쉽게 깨집니다.
생성자나 빌더를 통해 필요한 값을 세팅하도록 개선하는 편이 유지보수에 유리합니다.

src/main/java/com/stcom/smartmealtable/component/creditmessage/NHCreditMessageParser.java (2)

18-20: 벤더 식별 로직이 과도하게 느슨함

message.contains("NH") 만으로 판별하면 "NH" 가 우연히 포함된 다른 카드사 문자도 오탐될 수 있습니다.
정규식 패턴을 재사용하여 벤더 여부를 확인하거나, "NH"/"농협" 이 카드사 구문 맨 앞에 오는지 등을 추가로 확인해 주세요.


33-37: 연도 추정 로직의 경계 조건 주의

문자에 연도가 포함되지 않아 현재 연도를 보정하는 방식인데, 연말‧연초(예: 1월 1일에 12/31 승인 문자 수신) 상황에서 실제 연도와 어긋날 수 있습니다.
SMS 형식상 연도가 포함될 가능성을 다시 확인하거나, 월·일이 현재 날짜보다 미래인 경우 전년도 처리 등 보정 로직을 고려해 주세요.

src/test/java/com/stcom/smartmealtable/service/MemberCategoryPreferenceServiceAdditionalIntegrationTest.java (2)

97-103: 컬렉션 순서 의존성 제거 권장

스트림으로 얻은 likedPrefs 순서가 저장/조회 구현 세부사항에 따라 달라질 수 있습니다.
containsExactlyInAnyOrder 등의 어서션을 사용해 ‘원소 집합’만 검증하면 테스트 안정성이 높아집니다.


69-80: FoodCategory 필드 설정에 리플렉션 사용

테스트에서조차 빌더나 테스트 픽스처 패턴을 활용하면 도메인 모델 변경 시 영향을 줄일 수 있습니다.
가능하면 FoodCategory 생성자를 열어두거나 테스트 전용 팩토리를 도입해 주세요.

src/test/java/com/stcom/smartmealtable/service/MemberServiceIntegrationTest.java (1)

10-23: @transactional 어노테이션 패키지 혼용

본 테스트는 jakarta.transaction.Transactional 을 임포트했지만, 다른 테스트 클래스는 org.springframework.transaction.annotation.Transactional 을 사용합니다.
동일 스택 내에서는 한 가지를 통일하여 혼란을 줄이고, 스프링 테스트 롤백 정책이 동일하게 적용되는지 확인해 주세요.

src/test/java/com/stcom/smartmealtable/service/MemberProfileServiceAdditionalIntegrationTest2.java (2)

101-108: 예외 메시지 단정 시 hasMessageContaining 사용 권장
서비스 구현이 일부 표현을 변경하면 테스트가 쉽게 깨집니다. hasMessageContaining("존재하지 않는 프로필") 과 같이 핵심 키워드만 검증해 결합도를 낮춰 주세요.

Also applies to: 128-131, 138-144, 150-156, 162-172, 178-188, 194-204, 212-216, 224-228


44-72: 테스트 준비 코드 중복 제거
invalidProfileId 값 선언과 예외 검증 로직이 반복됩니다. @ParameterizedTest + MethodSource 로 케이스를 모아 관리하면 가독성과 유지보수성이 향상됩니다.

src/test/java/com/stcom/smartmealtable/service/LoginServiceIntegrationTest.java (1)

5-7: 사용되지 않는 import 제거
PasswordFailedExceededException 는 본 파일에서 사용되지 않습니다. 불필요한 import 는 제거해 주세요.

src/main/java/com/stcom/smartmealtable/component/creditmessage/SHCreditMessageParser.java (1)

19-22: 벤더 판별 시 대소문자/공백 변형 대응
contains("신한카드") 는 대소문자·공백·특수문자 변형에 민감합니다.

-return message != null && message.contains("신한카드");
+return message != null && message.replaceAll("\\s+", "").contains("신한카드");

또는 Pattern.CASE_INSENSITIVE 플래그로 처리해도 됩니다.

src/main/java/com/stcom/smartmealtable/web/dto/group/SchoolGroupCreateRequest.java (1)

13-24: 주소·이름 유효성 제한 강화 제안
@NotEmpty 만으로는 길이·형식 검증이 부족합니다. @Size(max = …) 혹은 정규식 검증(@Pattern)을 추가해 입력 값을 조기에 차단하는 편이 안전합니다.

src/main/java/com/stcom/smartmealtable/component/creditmessage/KBCreditMessageParser.java (1)

11-14: 정규식이 실제 메시지를 모두 커버하지 못할 가능성

현재 패턴은 [KB국민카드] 뒤에 공백 없이 바로 날짜가 오는 형태만 허용합니다.
[Web발신] [KB국민카드] 처럼 prefix 가 붙거나 줄바꿈이 있을 경우 매칭 실패합니다.
현업 문자 패턴을 재확인하고 여유 있는 whitespace 처리 또는 .*KB국민카드.* 전처리를 고려해 주세요.

src/test/java/com/stcom/smartmealtable/repository/MemberProfileRepositoryTest.java (1)

48-51: fetch 조인 검증이 충분하지 않습니다

현재는 member.email만 확인합니다. @EntityGraph로 가져오려는 addressHistory, group
초기화되었는지 PersistenceUtil.isLoaded 또는 프록시 타입 검사를 통해 명시적으로 검증하면 테스트 목적이 더 명확해집니다.

src/main/java/com/stcom/smartmealtable/service/GroupService.java (1)

20-20: deleteGroup 파라미터에도 제약을 명시하세요

@NotNull Long id 를 추가하면 컨트롤러-서비스 간 계약이 더 명확해집니다.

src/test/java/com/stcom/smartmealtable/service/SocialAccountServiceCompleteIntegrationTest.java (1)

137-139: 예외 메시지 전체 매칭은 불안정
hasMessage("…")로 전체 문자열을 단정하면 사소한 메시지 수정에도 테스트가 깨집니다.
hasMessageContaining("회원 엔티티가 존재하지 않은 상태") 등 부분 매칭으로 변경을 권장합니다.

src/test/java/com/stcom/smartmealtable/repository/TermRepositoryTest.java (1)

42-56: 직접 리플렉션 대신 ReflectionTestUtils로 통일성 확보
다른 리포지터리 테스트들은 ReflectionTestUtils.setField를 사용합니다.
여기만 수동 리플렉션을 쓰면 중복 코드·가독성 저하가 발생하니 동일 유틸로 통일하는 편이 좋습니다.

- java.lang.reflect.Field titleField = Term.class.getDeclaredField("title");
- titleField.setAccessible(true);
- titleField.set(term, title);
+ ReflectionTestUtils.setField(term, "title", title);
src/main/java/com/stcom/smartmealtable/web/controller/MemberController.java (1)

16-39: DTO에 기본 생성자 없음 → 직렬화 호환성 저하
@AllArgsConstructor만 선언돼 있어 Jackson 등에서 역직렬화할 때 실패할 수 있습니다.
과거 바이너리 호환 목적이라도, 무해성이 중요하므로 @NoArgsConstructor(access = PROTECTED) 추가를 권장합니다.

-@Data
-@AllArgsConstructor
+@Data
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@AllArgsConstructor
src/test/java/com/stcom/smartmealtable/domain/Budget/ExpenditureTest.java (1)

1-1: 패키지명 대문자 사용
Java 패키지는 소문자 사용이 관례입니다. domain.Budgetdomain.budget 로 이동하면 IDE 경고와 빌드 도구 호환성 이슈를 예방할 수 있습니다.

src/test/java/com/stcom/smartmealtable/service/BudgetServiceAdditionalIntegrationTest2.java (1)

6-8: 사용되지 않는 import 존재
MonthlyBudget가 본 파일에서 사용되지 않습니다. 정리하여 가독성을 높여주세요.

src/test/java/com/stcom/smartmealtable/service/GroupServiceCompleteIntegrationTest.java (2)

88-92: 예외 메시지의 도메인 용어가 부적절
그룹을 찾지 못한 경우 "존재하지 않는 회원입니다" 라는 메시지를 사용하고 있습니다. ‘회원’(Member) 이 아닌 ‘그룹’(Group) 으로 표현하는 것이 도메인 일관성에 맞습니다.

Also applies to: 194-197


200-204: 미구현 테스트 케이스는 @disabled 로 명시
TODO 성격의 블록이 주석으로 남아있습니다. 실행되지 않을 테스트라면 @Disabled 를 붙여 의도를 명확히 하거나, 실제 CompanyGroup 추가 후 구현하세요.

src/main/java/com/stcom/smartmealtable/service/GroupServiceImpl.java (1)

58-62: deleteGroup 단순화 가능
PK 기준 삭제만 수행한다면 groupRepository.deleteById(id) 로 간결히 표현할 수 있습니다. 조회 → 삭제 두 번의 DB hit 를 줄여도 무방합니다.

src/test/java/com/stcom/smartmealtable/repository/ExpenditureRepositoryTest.java (2)

94-99: 정렬 검증 로직이 타임스탬프 중복 시 실패 가능
sorted(...).toList().equals(original) 방식은 동일 spentDate 를 가진 행이 있을 때 리스트 equals 가 실패할 수 있습니다. assertThat(slice.getContent()).isSortedAccordingTo(...) 와 같이 Comparator 기반 검증으로 바꾸면 안전합니다.


71-80: 테스트 데이터 생성 시 시간 의존성 줄이기
LocalDateTime.now() 를 루프 안에서 여러 번 호출하면 수행 시간에 따라 미세한 오차가 생길 수 있습니다. 기준 시각을 변수에 담아 사용하는 편이 안정적입니다.

src/test/java/com/stcom/smartmealtable/repository/BudgetRepositoryTest.java (2)

31-62: 중복 코드 제거 제안
세 개의 테스트에서 Member·Profile 생성 코드가 반복됩니다. @BeforeEach 로 공통화하거나 Test Fixture Builder 를 도입하면 가독성과 유지보수성이 향상됩니다.


145-163: 기간 조회 기대 건수 하드코딩
assertThat(dailyBudgets.size()).isEqualTo(4); 와 같이 생성 로직을 분석한 값이 하드코딩되어 있습니다. 생성 개수 변경 시 테스트 수정이 필요하므로, 범위 필터링 로직 자체를 검증하는 방향(예: 모든 returned date 가 start/end 사이인지)으로 개선할 수 있습니다.

src/test/java/com/stcom/smartmealtable/service/MemberProfileServiceCompleteIntegrationTest.java (2)

124-145: 주소 저장 시 ‘기본 주소’ 플래그까지 검증하는 것을 고려하세요

saveNewAddress() 테스트는 주소가 정상적으로 저장됐는지만 확인하고, 해당 주소가 primary 로 저장됐는지 여부는 확인하지 않습니다. 서비스 구현이 “새 주소를 기본값으로 설정”하도록 의도되었거나 향후 변경될 수 있으므로, 플래그까지 검증하면 회귀를 방지할 수 있습니다.

-assertThat(updatedProfile.getAddressHistory()).hasSize(1);
+assertThat(updatedProfile.getAddressHistory()).hasSize(1);
+assertThat(updatedProfile.findPrimaryAddress().getId())
+        .isEqualTo(updatedProfile.getAddressHistory().get(0).getId());

155-160: ‘기본 주소’ 변경 테스트에서 addressHistory null-safe 체크 권장

changeAddressToPrimary() 호출 후 곧바로 findPrimaryAddress() 를 사용하고 있습니다. 만약 서비스 로직 변경으로 주소가 제거되거나 primary 항목이 삭제될 경우 NPE가 발생할 수 있습니다. 테스트 코드에서도 isNotNull()hasValue() 식의 방어적 검증을 추가하면 문제 지점을 빠르게 파악할 수 있습니다.

src/test/java/com/stcom/smartmealtable/service/SocialAccountServiceAdditionalIntegrationTest.java (2)

35-37: 클래스 레벨 @transactional 환경에서는 @Rollback 생략이 가능합니다

테스트 클래스에 이미 @Transactional 을 선언했기 때문에 각 테스트 메서드는 기본적으로 롤백됩니다. @Rollback 애노테이션은 중복 선언이므로 제거하여 의도를 간결히 드러내는 편이 좋습니다.


55-57: 토큰 만료 시각·리프레시 토큰까지 함께 검증하면 테스트 신뢰도가 높아집니다

현재는 accessToken 값만 검증하고 있습니다. updateToken() 의 핵심 동작은 access / refresh / expiry 세 값을 모두 갱신하는 것이므로 다음과 같이 추가 검증을 권장합니다.

 assertThat(updated.getAccessToken()).isEqualTo("new");
+assertThat(updated.getRefreshToken()).isEqualTo("new_r");
+assertThat(updated.getTokenExpiresAt()).isAfter(LocalDateTime.now());
src/test/java/com/stcom/smartmealtable/service/ExpenditureServiceIntegrationTest.java (1)

100-118: 정렬 검증이 ‘같은 시각 INSERT’ 상황에서도 통과하도록 안정성을 높일 수 있습니다

현재 spentDate1/2/3 를 동일한 ‘오늘’ 객체에서 시·분만 변경해 생성하고 있습니다. DB TIMEZONE 또는 밀리초 단위 반올림 영향으로 동시간대 저장 시 정렬이 불안정해질 수 있습니다. minusSeconds()minusMinutes() 를 사용해 명시적으로 간격을 두면 테스트가 더 견고해집니다.

src/test/java/com/stcom/smartmealtable/service/MemberProfileServiceAdditionalIntegrationTest.java (1)

68-71: 주소 ID 조회 부분에서 트랜잭션 flush 없이 영속성 컨텍스트 강제 업데이트 고려

saveNewAddress() 호출 직후 같은 트랜잭션 내 profileRepository.findById() 로 조회하고 있습니다. 영속성 컨텍스트에 이미 profile 이 존재하므로 실제 쿼리가 날아가지 않아 addressHistory 가 비어 있을 가능성이 있습니다. flush() 후 clear 하거나 서비스 반환값을 활용하면 테스트 신뢰도를 높일 수 있습니다.

src/main/java/com/stcom/smartmealtable/web/controller/MemberAddressController.java (2)

25-30: 요청 실패 시 HTTP 상태 매핑을 명시적으로 처리하세요

memberProfileService 에서 IllegalArgumentException 이 발생하면 전역 ExceptionHandler 가 400 으로 변환한다고 가정하지만, 컨트롤러 내에서 @ExceptionHandler 혹은 @ResponseStatus 정책을 명확히 두면 API 규약을 추적하기 쉽습니다.


25-26: HTTP 메서드语语 일관성 재고 – ‘기본 주소 변경’ 엔드포인트는 PATCH 가 더 자연스럽습니다

POST /{id}/primary 는 자원을 “생성” 하는 의미보다는 상태 변경의 의미가 강하므로, RFC-5789 기준 갱신 동작에 해당하는 PATCH 가 REST 관점에서 좀 더 직관적입니다.

src/main/java/com/stcom/smartmealtable/web/controller/SchoolGroupController.java (1)

28-33: 메서드명 개선 제안

registerSchoolGroup 대신 createSchoolGroup이 RESTful 관례에 더 부합합니다.

-    public ApiResponse<Void> registerSchoolGroup(@RequestBody @Valid SchoolGroupCreateRequest request) {
+    public ApiResponse<Void> createSchoolGroup(@RequestBody @Valid SchoolGroupCreateRequest request) {
src/test/java/com/stcom/smartmealtable/service/LoginServiceCompleteIntegrationTest.java (2)

70-80: 테스트 시간 의존성 제거 권장

LocalDateTime.now()를 그대로 사용하면 일광 절약제 전환·서버 시간차 등으로 인해 간헐 실패(flaky) 테스트가 발생할 여지가 있습니다.
Clock 주입 또는 LocalDateTime.of(...) 정적값을 사용해 시간을 고정해 재현성을 높여주세요.


80-84: member 저장 중복 호출 제거

memberRepository.save(memberWithProfile)setUp()에서 두 번 호출되고 있습니다.
두 번째 호출(82 라인)은 불필요하므로 제거해 깔끔하게 유지하세요.

-        memberRepository.save(memberWithProfile);
src/main/java/com/stcom/smartmealtable/web/controller/MemberProfileController.java (2)

54-58: 기본·상세 주소 사이 구분자가 없음

도로명·상세주소를 바로 이어붙여 "...RoadAddressDetailAddress" 형태로 반환합니다.
가독성을 위해 공백 또는 , 구분자를 삽입하는 것이 좋습니다.

-            this.primaryAddress = profile.findPrimaryAddress().getAddress().getRoadAddress()
-                    + profile.findPrimaryAddress().getAddress().getDetailAddress();
+            this.primaryAddress = profile.findPrimaryAddress().getAddress().getRoadAddress() + " "
+                    + profile.findPrimaryAddress().getAddress().getDetailAddress();

67-71: 요청 DTO 필드 검증 확대 제안

groupId, memberType도 필수라면 @NotNull을 부여하고, 선택값이라면 Optional 타입 사용을 고려해 명확성을 높여주세요.

src/main/java/com/stcom/smartmealtable/web/controller/MemberAccountController.java (1)

45-51: 비밀번호 변경 DTO에 검증 애노테이션 추가

originPassword, newPassword, confirmPassword 모두 공백 허용 시 서비스 단에서 빈 비밀번호 변경이 가능해집니다.
@NotEmpty 또는 커스텀 패턴 검증을 추가해 주세요.

src/test/java/com/stcom/smartmealtable/service/LoginServiceAdditionalIntegrationTest.java (1)

108-110: LocalDateTime.now()로 인한 플러키(flaky) 테스트 위험

tokenExpiresAt(LocalDateTime.now().plusHours(1)) 역시 시스템 시간이 변하면 간헐 실패할 수 있습니다.
고정 시각을 사용하거나 Clock 모킹을 추천드립니다.

src/main/java/com/stcom/smartmealtable/service/ExpenditureService.java (1)

87-90: 음수로 내려갈 가능성 방지
subtractSpent 호출 후 예산의 spendAmount가 음수가 되지 않도록, 도메인 모델에서 하한 검증을 추가하는 것이 안전합니다.

src/test/java/com/stcom/smartmealtable/service/GroupServiceAdditionalIntegrationTest.java (1)

70-78: 주소 변경 검증도 함께 추가하면 테스트 완성도가 높아집니다
changeSchoolGroup에서는 이름과 타입만 검증하고 주소는 누락되었습니다. Address 변경 여부까지 assert 하면 회귀 방지가 확실해집니다.

src/test/java/com/stcom/smartmealtable/service/BudgetServiceIntegrationTest.java (1)

133-137: 반복 루프는 Stream API로 간결화 가능
테스트 가독성을 위해 IntStream.range(0,7).forEach(i -> …) 형태로 작성하면 불필요한 인덱스 변수 및 로직을 줄일 수 있습니다.

src/main/java/com/stcom/smartmealtable/web/controller/MemberPreferenceController.java (1)

24-45: 우선순위 기준 정렬 필요
프론트에 일관된 순서를 제공하려면 priority 기준 sorted() 후 전달하는 편이 안전합니다.

src/main/java/com/stcom/smartmealtable/repository/BudgetRepository.java (1)

24-27: Pageable.ofSize() 사용 시 정렬 보장 확인
Pageable.ofSize(1)는 정렬 정보가 없으므로, 쿼리의 order by에만 의존합니다. 미래에 쿼리 수정 시 영향을 최소화하려면 PageRequest.of(0,1, Sort.by(...))로 명시적 정렬을 전달하는 편이 안전합니다.

src/main/java/com/stcom/smartmealtable/service/BudgetService.java (1)

109-112: 파라미터 명확성 개선

변수명 parse 는 의미가 모호합니다. referenceDate 등 의도가 드러나는 이름으로 교체하면 가독성이 향상됩니다.

src/test/java/com/stcom/smartmealtable/integration/BudgetIntegrationTest.java (1)

119-123: BigDecimal 초기화 방식 개선

BigDecimal.valueOf(99999.99) 는 이진 부동소수 오차에 노출될 수 있습니다. 문자열 기반 생성자를 사용하면 안전합니다.

- savedBudget.addSpent(99999.99);
+ savedBudget.addSpent(new BigDecimal("99999.99"));
src/test/java/com/stcom/smartmealtable/repository/BudgetRepositoryAdvancedTest.java (1)

342-347: ID 기반 정렬 가정은 불안정

현재 테스트는 ID 증가 순이 날짜 순이라고 가정합니다. DB 설정(IDENTITY / SEQUENCE)이나 병렬 삽입 상황에서 깨질 수 있으니 명시적 날짜 정렬을 검증하도록 수정해 주세요.

src/main/java/com/stcom/smartmealtable/web/controller/MemberExpenditureController.java (1)

47-53: 페이지 사이즈 상한선 검토 필요

size 파라미터에 대한 제한이 없으므로, 클라이언트가 과도한 사이즈를 요청해 서비스·DB에 부담을 줄 수 있습니다.
예: @Max(100) 같은 Bean Validation 혹은 서비스 레벨에서 하드코딩된 상한선을 두는 것을 권장합니다.

src/main/java/com/stcom/smartmealtable/web/controller/MemberBudgetController.java (1)

42-46: limit 파라미터에 음수/0 허용 가능성

예산 한도는 양수 값이어야 자연스럽습니다. @Positive 또는 @PositiveOrZero 애너테이션으로 간단히 방어할 수 있습니다.

- @RequestParam("limit") Long limit
+ @RequestParam("limit") @Positive Long limit

Also applies to: 53-56, 95-101, 106-111

src/main/java/com/stcom/smartmealtable/web/exhandler/ExControllerAdvice.java (1)

78-90: RuntimeException·Exception 핸들러 중복

runtimeExHandlerException 파라미터를 받으므로 exHandler와 완전히 동일 범위를 커버합니다.
실제로는 먼저 선언된 메서드가 우선 처리되지만, 가독성과 유지보수 측면에서 둘 중 하나로 통합하는 편이 명확합니다.

src/test/java/com/stcom/smartmealtable/domain/Budget/BudgetDomainIntegrationTest.java (1)

85-113: 테스트 결정성 확보를 위해 Clock 주입 고려

LocalDate.now()YearMonth.now() 를 직접 호출하면 타임존이나 시스템 날짜에 따라 CI 환경에서 테스트가 실패할 수 있습니다.

  • 고정된 Clock.fixed(...) 를 Budget/Date 생성자에 주입하거나
  • 테스트용 상수를 사용하면 안정성이 높아집니다.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e7eaca and 4e14ffc.

📒 Files selected for processing (107)
  • build.gradle (3 hunks)
  • src/main/java/com/stcom/smartmealtable/component/creditmessage/CreditMessageManager.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/component/creditmessage/CreditMessageParser.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/component/creditmessage/ExpenditureDto.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/component/creditmessage/GeminiCreditMessageParser.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/component/creditmessage/KBCreditMessageParser.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/component/creditmessage/NHCreditMessageParser.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/component/creditmessage/SHCreditMessageParser.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/domain/Budget/Budget.java (2 hunks)
  • src/main/java/com/stcom/smartmealtable/domain/Budget/DailyBudget.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/domain/Budget/Expenditure.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/domain/Budget/MonthlyBudget.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/domain/group/Group.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/domain/group/SchoolGroup.java (2 hunks)
  • src/main/java/com/stcom/smartmealtable/domain/member/MemberProfile.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/domain/social/SocialAccount.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/exception/BizLogicException.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/exception/ExternApiStatusError.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/exception/PasswordFailedExceededException.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/exception/PasswordPolicyException.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/infrastructure/KakaoAddressApiService.java (2 hunks)
  • src/main/java/com/stcom/smartmealtable/infrastructure/SocialAuthService.java (2 hunks)
  • src/main/java/com/stcom/smartmealtable/infrastructure/dto/AddressRequest.java (0 hunks)
  • src/main/java/com/stcom/smartmealtable/repository/BudgetRepository.java (2 hunks)
  • src/main/java/com/stcom/smartmealtable/repository/ExpenditureRepository.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/repository/MemberProfileRepository.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/service/BudgetService.java (3 hunks)
  • src/main/java/com/stcom/smartmealtable/service/ExpenditureService.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/service/GroupService.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/service/GroupServiceImpl.java (3 hunks)
  • src/main/java/com/stcom/smartmealtable/service/MemberCategoryPreferenceService.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/service/MemberProfileService.java (6 hunks)
  • src/main/java/com/stcom/smartmealtable/service/MemberService.java (2 hunks)
  • src/main/java/com/stcom/smartmealtable/service/SocialAccountService.java (2 hunks)
  • src/main/java/com/stcom/smartmealtable/web/WebConfig.java (2 hunks)
  • src/main/java/com/stcom/smartmealtable/web/controller/AuthController.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/web/controller/AuthTokenController.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/web/controller/GroupController.java (2 hunks)
  • src/main/java/com/stcom/smartmealtable/web/controller/LoginController.java (0 hunks)
  • src/main/java/com/stcom/smartmealtable/web/controller/MemberAccountController.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/web/controller/MemberAddressController.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/web/controller/MemberBudgetController.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/web/controller/MemberController.java (2 hunks)
  • src/main/java/com/stcom/smartmealtable/web/controller/MemberExpenditureController.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/web/controller/MemberPreferenceController.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/web/controller/MemberProfileController.java (3 hunks)
  • src/main/java/com/stcom/smartmealtable/web/controller/OAuth2Controller.java (0 hunks)
  • src/main/java/com/stcom/smartmealtable/web/controller/SchoolGroupController.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/web/dto/group/GroupDto.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/web/dto/group/SchoolGroupCreateRequest.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/web/dto/group/SchoolGroupUpdateRequest.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/web/exhandler/ExControllerAdvice.java (3 hunks)
  • src/main/java/com/stcom/smartmealtable/web/validation/YearMonthAnnotationFormatterFactory.java (1 hunks)
  • src/main/java/com/stcom/smartmealtable/web/validation/YearMonthFormat.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/component/creditmessage/CreditMessageManagerTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/component/creditmessage/GeminiCreditMessageParserTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/component/creditmessage/KBCreditMessageParserTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/component/creditmessage/NHCreditMessageParserTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/component/creditmessage/SHCreditMessageParserTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/domain/Budget/BudgetDomainIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/domain/Budget/ExpenditureTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/domain/group/CompanyGroupTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/domain/group/SchoolGroupTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/infrastructure/KakaoAddressApiServiceTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/infrastructure/SocialAuthServiceTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/infrastructure/persistence/YearMonthConverterTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/integration/BudgetIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/integration/MemberIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/repository/AddressEntityRepositoryTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/repository/BudgetRepositoryAdvancedTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/repository/BudgetRepositoryTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/repository/ExpenditureRepositoryTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/repository/FoodCategoryRepositoryTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/repository/GroupRepositoryTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/repository/MemberCategoryPreferenceRepositoryTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/repository/MemberProfileRepositoryTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/repository/SocialAccountRepositoryTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/repository/TermAgreementRepositoryTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/repository/TermRepositoryTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/BudgetServiceAdditionalIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/BudgetServiceAdditionalIntegrationTest2.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/BudgetServiceCompleteIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/BudgetServiceIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/BudgetServiceTest.java (0 hunks)
  • src/test/java/com/stcom/smartmealtable/service/ExpenditureServiceIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/GroupServiceAdditionalIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/GroupServiceCompleteIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/GroupServiceIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/GroupServiceTest.java (0 hunks)
  • src/test/java/com/stcom/smartmealtable/service/LoginServiceAdditionalIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/LoginServiceCompleteIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/LoginServiceIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/LoginServiceTest.java (0 hunks)
  • src/test/java/com/stcom/smartmealtable/service/MemberCategoryPreferenceServiceAdditionalIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/MemberCategoryPreferenceServiceIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/MemberCategoryPreferenceServiceTest.java (0 hunks)
  • src/test/java/com/stcom/smartmealtable/service/MemberProfileServiceAdditionalIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/MemberProfileServiceAdditionalIntegrationTest2.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/MemberProfileServiceCompleteIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/MemberProfileServiceIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/MemberProfileServiceTest.java (0 hunks)
  • src/test/java/com/stcom/smartmealtable/service/MemberServiceAdditionalIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/MemberServiceFailureIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/MemberServiceIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/MemberServiceTest.java (0 hunks)
  • src/test/java/com/stcom/smartmealtable/service/SocialAccountServiceAdditionalIntegrationTest.java (1 hunks)
  • src/test/java/com/stcom/smartmealtable/service/SocialAccountServiceCompleteIntegrationTest.java (1 hunks)
⛔ Files not processed due to max files limit (19)
  • src/test/java/com/stcom/smartmealtable/service/SocialAccountServiceIntegrationTest.java
  • src/test/java/com/stcom/smartmealtable/service/SocialAccountServiceTest.java
  • src/test/java/com/stcom/smartmealtable/service/TermServiceAdditionalIntegrationTest.java
  • src/test/java/com/stcom/smartmealtable/service/TermServiceIntegrationTest.java
  • src/test/java/com/stcom/smartmealtable/service/TermServiceTest.java
  • src/test/java/com/stcom/smartmealtable/service/exception/ResourceNotFoundException.java
  • src/test/java/com/stcom/smartmealtable/web/controller/AuthControllerTest.java
  • src/test/java/com/stcom/smartmealtable/web/controller/AuthTokenControllerTest.java
  • src/test/java/com/stcom/smartmealtable/web/controller/ControllerTestSupport.java
  • src/test/java/com/stcom/smartmealtable/web/controller/GroupControllerTest.java
  • src/test/java/com/stcom/smartmealtable/web/controller/MemberAccountControllerTest.java
  • src/test/java/com/stcom/smartmealtable/web/controller/MemberAddressControllerTest.java
  • src/test/java/com/stcom/smartmealtable/web/controller/MemberBudgetControllerTest.java
  • src/test/java/com/stcom/smartmealtable/web/controller/MemberControllerTest.java
  • src/test/java/com/stcom/smartmealtable/web/controller/MemberExpenditureControllerTest.java
  • src/test/java/com/stcom/smartmealtable/web/controller/MemberPreferenceControllerTest.java
  • src/test/java/com/stcom/smartmealtable/web/controller/MemberProfileControllerTest.java
  • src/test/java/com/stcom/smartmealtable/web/controller/SchoolGroupControllerTest.java
  • src/test/java/com/stcom/smartmealtable/web/exhandler/ExControllerAdviceTest.java
💤 Files with no reviewable changes (9)
  • src/main/java/com/stcom/smartmealtable/infrastructure/dto/AddressRequest.java
  • src/test/java/com/stcom/smartmealtable/service/GroupServiceTest.java
  • src/test/java/com/stcom/smartmealtable/service/MemberServiceTest.java
  • src/test/java/com/stcom/smartmealtable/service/BudgetServiceTest.java
  • src/main/java/com/stcom/smartmealtable/web/controller/LoginController.java
  • src/test/java/com/stcom/smartmealtable/service/MemberProfileServiceTest.java
  • src/test/java/com/stcom/smartmealtable/service/LoginServiceTest.java
  • src/test/java/com/stcom/smartmealtable/service/MemberCategoryPreferenceServiceTest.java
  • src/main/java/com/stcom/smartmealtable/web/controller/OAuth2Controller.java
🧰 Additional context used
🧬 Code Graph Analysis (19)
src/main/java/com/stcom/smartmealtable/web/WebConfig.java (1)
src/main/java/com/stcom/smartmealtable/web/validation/YearMonthAnnotationFormatterFactory.java (1)
  • YearMonthAnnotationFormatterFactory (13-50)
src/main/java/com/stcom/smartmealtable/infrastructure/SocialAuthService.java (1)
src/main/java/com/stcom/smartmealtable/exception/ExternApiStatusError.java (1)
  • ExternApiStatusError (3-19)
src/test/java/com/stcom/smartmealtable/repository/BudgetRepositoryAdvancedTest.java (1)
src/test/java/com/stcom/smartmealtable/repository/BudgetRepositoryTest.java (1)
  • DataJpaTest (18-164)
src/test/java/com/stcom/smartmealtable/service/MemberServiceFailureIntegrationTest.java (2)
src/main/java/com/stcom/smartmealtable/exception/PasswordPolicyException.java (1)
  • PasswordPolicyException (3-12)
src/test/java/com/stcom/smartmealtable/service/MemberServiceIntegrationTest.java (1)
  • SpringBootTest (21-88)
src/test/java/com/stcom/smartmealtable/service/ExpenditureServiceIntegrationTest.java (1)
src/test/java/com/stcom/smartmealtable/repository/ExpenditureRepositoryTest.java (1)
  • DataJpaTest (29-101)
src/main/java/com/stcom/smartmealtable/web/dto/group/SchoolGroupUpdateRequest.java (1)
src/main/java/com/stcom/smartmealtable/web/dto/group/SchoolGroupCreateRequest.java (1)
  • Data (9-24)
src/test/java/com/stcom/smartmealtable/infrastructure/SocialAuthServiceTest.java (1)
src/main/java/com/stcom/smartmealtable/exception/ExternApiStatusError.java (1)
  • ExternApiStatusError (3-19)
src/main/java/com/stcom/smartmealtable/domain/Budget/Expenditure.java (3)
src/main/java/com/stcom/smartmealtable/domain/Budget/DailyBudget.java (1)
  • Entity (11-25)
src/main/java/com/stcom/smartmealtable/domain/Budget/MonthlyBudget.java (1)
  • Entity (13-28)
src/main/java/com/stcom/smartmealtable/domain/Budget/Budget.java (1)
  • Getter (20-81)
src/test/java/com/stcom/smartmealtable/repository/TermRepositoryTest.java (2)
src/test/java/com/stcom/smartmealtable/repository/FoodCategoryRepositoryTest.java (1)
  • DataJpaTest (15-42)
src/test/java/com/stcom/smartmealtable/repository/TermAgreementRepositoryTest.java (1)
  • DataJpaTest (16-60)
src/test/java/com/stcom/smartmealtable/infrastructure/KakaoAddressApiServiceTest.java (1)
src/main/java/com/stcom/smartmealtable/exception/ExternApiStatusError.java (1)
  • ExternApiStatusError (3-19)
src/test/java/com/stcom/smartmealtable/repository/ExpenditureRepositoryTest.java (1)
src/test/java/com/stcom/smartmealtable/service/ExpenditureServiceIntegrationTest.java (1)
  • DataJpaTest (27-208)
src/test/java/com/stcom/smartmealtable/service/SocialAccountServiceAdditionalIntegrationTest.java (1)
src/test/java/com/stcom/smartmealtable/service/SocialAccountServiceIntegrationTest.java (1)
  • SpringBootTest (15-49)
src/main/java/com/stcom/smartmealtable/infrastructure/KakaoAddressApiService.java (1)
src/main/java/com/stcom/smartmealtable/exception/ExternApiStatusError.java (1)
  • ExternApiStatusError (3-19)
src/test/java/com/stcom/smartmealtable/repository/BudgetRepositoryTest.java (1)
src/test/java/com/stcom/smartmealtable/repository/BudgetRepositoryAdvancedTest.java (1)
  • DataJpaTest (21-398)
src/test/java/com/stcom/smartmealtable/repository/MemberCategoryPreferenceRepositoryTest.java (2)
src/test/java/com/stcom/smartmealtable/repository/FoodCategoryRepositoryTest.java (1)
  • DataJpaTest (15-42)
src/test/java/com/stcom/smartmealtable/repository/MemberProfileRepositoryTest.java (1)
  • DataJpaTest (15-52)
src/test/java/com/stcom/smartmealtable/service/BudgetServiceIntegrationTest.java (1)
src/test/java/com/stcom/smartmealtable/integration/BudgetIntegrationTest.java (1)
  • SpringBootTest (25-309)
src/main/java/com/stcom/smartmealtable/web/controller/MemberExpenditureController.java (1)
src/main/java/com/stcom/smartmealtable/web/exhandler/ExControllerAdvice.java (1)
  • Slf4j (18-97)
src/main/java/com/stcom/smartmealtable/web/exhandler/ExControllerAdvice.java (1)
src/main/java/com/stcom/smartmealtable/exception/BizLogicException.java (1)
  • BizLogicException (3-12)
src/main/java/com/stcom/smartmealtable/web/controller/SchoolGroupController.java (1)
src/main/java/com/stcom/smartmealtable/web/controller/GroupController.java (1)
  • RestController (16-39)
🔇 Additional comments (33)
src/main/java/com/stcom/smartmealtable/web/WebConfig.java (1)

27-30: 커스텀 포매터 등록 확인 – 문제없음

addFormatterForFieldAnnotation를 통해 YearMonthAnnotationFormatterFactory를 등록한 방식이 Spring MVC 권장 패턴과 일치합니다.
특별한 문제 없이 동작할 것으로 보입니다.

src/main/java/com/stcom/smartmealtable/service/SocialAccountService.java (1)

68-69: 예외 타입 변경 👍

updateToken에서 IllegalArgumentException으로 변경하여 입력 오류 상황과 일관되게 처리한 점이 좋습니다.

src/main/java/com/stcom/smartmealtable/service/MemberService.java (1)

42-43: 예외 메시지·타입 일관성 확보 👍

중복 이메일, 비밀번호 불일치, 존재하지 않는 회원 등에 대해 IllegalArgumentException을 사용하도록 정리되어 서비스 계층 전반의 예외 규칙이 명확해졌습니다.

Also applies to: 48-50, 54-56

src/main/java/com/stcom/smartmealtable/repository/MemberProfileRepository.java (1)

18-19: EntityGraph 수정 👍

member 연관까지 함께 패치하도록 수정돼 N+1 문제가 줄어들 것으로 보입니다. 변경점에 문제 없습니다.

src/main/java/com/stcom/smartmealtable/exception/PasswordPolicyException.java (1)

3-11: BizLogicException 상속 전환 확인 완료

PasswordPolicyExceptionBizLogicException을 상속하도록 변경된 부분이 일관된 예외 계층 구조를 만듭니다. 추가 수정 필요 없어 보입니다.

src/test/java/com/stcom/smartmealtable/component/creditmessage/KBCreditMessageParserTest.java (1)

41-43: 시간 의존성 제거 권장
LocalDateTime.now().getYear()는 연말/연초 경계에서 플래키(Test flakiness)를 유발할 수 있습니다. Clock 주입 또는 @FixedClock(Java Time Module 지원 라이브러리)로 현재 연도를 고정해두면 안정적인 테스트가 가능합니다.

src/test/java/com/stcom/smartmealtable/infrastructure/persistence/YearMonthConverterTest.java (1)

70-79: 양방향 변환 커버리지 👍
bidirectionalConversion 테스트로 convertToDatabaseColumnconvertToEntityAttribute를 한 번에 검증한 점이 인상적입니다. 실사용 시 회귀 가능성이 큰 부분을 잘 커버하고 있습니다.

src/main/java/com/stcom/smartmealtable/exception/ExternApiStatusError.java (1)

8-18: 다양한 생성자 추가 👍
원인 추적 및 메시지 전파에 유연성이 생겼습니다. 글로벌 예외 처리기에도 동일한 포맷으로 로그가 남는지 확인하면 좋겠습니다.

src/main/java/com/stcom/smartmealtable/exception/PasswordFailedExceededException.java (1)

3-6: Runtime 예외로 변경된 부분 확인 완료

BizLogicException을 상속하도록 변경되어 도메인 예외 계층 구조가 일관성 있게 유지됩니다. 추가 수정 사항은 필요 없어 보입니다.

build.gradle (1)

63-66: 중복 BOM 선언 확인

이미 위에서 개별 의존성에 버전을 명시한 상태라면 dependencyManagement의 BOM 선언이 중복 관리가 될 수 있습니다.
버전 선언 위치를 하나로 통일하여 관리 부담을 줄여주세요.

src/test/java/com/stcom/smartmealtable/component/creditmessage/CreditMessageManagerTest.java (1)

61-63: 체이닝 스텁이 실제 호출 시그니처와 일치하는지 재확인 요청

prompt().user(...).call().content() 체인이 spring-ai 0.8.x API와 동일한지 확인해 주세요. 버전 업으로 메서드 명이 변경되면 테스트가 통과하지만 실제 코드가 실패할 수 있습니다.

src/test/java/com/stcom/smartmealtable/repository/AddressEntityRepositoryTest.java (1)

5-7: 패키지 경로의 대문자 사용 확인 필요

com.stcom.smartmealtable.domain.Address.Address 처럼 디렉터리명에 대문자 Address 가 포함되어 있습니다.
리눅스·macOS 케이스-센서티브 환경에서 불일치가 발생할 수 있으니 실제 디렉터리 구조를 한번 더 확인해주세요.

src/test/java/com/stcom/smartmealtable/service/GroupServiceIntegrationTest.java (1)

55-61: 검색 기준 가정 확인 필요

"서울" 키워드가 주소에도 포함되어 있어, findGroupsByKeyword 로직이 주소까지 검색하도록 변경될 경우 이 테스트는 실패합니다.
검색 대상 범위(이름만인지, 주소도 포함인지)를 명확히 정의하고, 테스트 데이터/검증 로직을 그 정의에 맞춰 조정해 주세요.

src/test/java/com/stcom/smartmealtable/service/LoginServiceIntegrationTest.java (1)

44-46: newUser 플래그 의도 확인 필요
가입 직후 저장한 회원으로 로그인했는데 isNewUser()true 인 것이 비즈니스 요구사항에 부합하는지 한번 더 확인해 주세요. 의도와 다르다면 서비스 로직 또는 테스트 기대값을 조정해야 합니다.

src/main/java/com/stcom/smartmealtable/component/creditmessage/SHCreditMessageParser.java (1)

34-38: 연도 자동 보정 한계 주의
SMS 가 연말(12 월) – 연초(1 월) 사이에 발송됐을 때 현재 연도를 무조건 붙이면 실제 거래 연도와 불일치할 수 있습니다.
가능하다면 카드사 메시지에 포함된 정산 연도를 파싱하거나, 연-월-일 조합이 미래일 경우 ‑1 년 보정 로직을 추가해 보세요.

src/main/java/com/stcom/smartmealtable/component/creditmessage/KBCreditMessageParser.java (1)

41-44: 연도 추론 로직이 연초-연말 경계에서 오동작할 수 있습니다

LocalDate.now().getYear()를 사용하면 1월 1일에 12월 31일 결제 문자를 파싱할 때 미래 날짜가 됩니다.
카드사가 연도를 함께 주지 않는다면, 결제 월·일이 현재 날짜보다 미래일 경우 작년으로 보정하는 로직을 추가하세요.

src/main/java/com/stcom/smartmealtable/service/GroupServiceImpl.java (1)

46-54: 엔티티 상태 변경 후 save 누락 여부 확인 요청
JPA 영속 컨텍스트 상에서 변경 감지가 동작하므로 save() 호출이 필수는 아니지만, 팀 코딩 규칙에 따라 명시적 save 를 요구한다면 누락된 것으로 볼 수 있습니다. 확인 바랍니다.

src/test/java/com/stcom/smartmealtable/repository/MemberCategoryPreferenceRepositoryTest.java (1)

77-83: 정렬 보장 조건 확인 필요
findDefaultByMemberProfileId 쿼리가 LIKE 우선 정렬을 보장한다는 전제하에 첫 엔트리의 타입을 검증하고 있습니다. JPQL order by type desc 와 같은 명시가 없다면 DB 구현에 따라 순서가 바뀔 수 있습니다. 쿼리 정의를 확인하거나, 테스트에서 extracting(MemberCategoryPreference::getType).containsExactly(…) 형태로 명시적 순서를 검증하세요.

src/test/java/com/stcom/smartmealtable/service/BudgetServiceCompleteIntegrationTest.java (3)

25-58: 테스트 설정이 잘 구성되어 있습니다.

@transactional을 사용한 테스트 격리와 테스트 프로필 활용이 적절합니다.


60-75: 미래 날짜를 사용한 안정적인 테스트 구현

2025년 날짜를 사용하여 시간 경과에 따른 테스트 실패를 방지한 점이 좋습니다.


118-140: 날짜 범위 계산과 검증이 정확합니다.

7월 15일부터 31일까지 17일간의 예산 생성을 정확히 검증하고 있으며, 주석으로 의도를 명확히 표현한 점이 좋습니다.

src/test/java/com/stcom/smartmealtable/service/BudgetServiceAdditionalIntegrationTest.java (1)

25-180: 에러 케이스에 대한 포괄적인 테스트 커버리지

존재하지 않는 프로필 ID와 데이터에 대한 예외 처리를 체계적으로 검증하고 있습니다.

src/main/java/com/stcom/smartmealtable/web/controller/SchoolGroupController.java (1)

18-49: RESTful API 설계가 잘 구현되어 있습니다.

컨트롤러가 적절히 서비스 계층에 위임하고 있으며, 입력 검증과 응답 처리가 일관성 있게 구현되어 있습니다.

src/test/java/com/stcom/smartmealtable/infrastructure/KakaoAddressApiServiceTest.java (2)

21-46: RestClient 모킹 설정이 적절합니다.

복잡한 플루언트 API 체인을 정확히 모킹하고 있으며, 테스트에서의 리플렉션 사용도 적절합니다.


155-187: 실제 주소 데이터를 사용한 현실적인 테스트

서울 강남구의 실제 주소를 사용하여 테스트의 현실성을 높인 점이 좋습니다.

src/test/java/com/stcom/smartmealtable/service/MemberServiceAdditionalIntegrationTest.java (1)

17-121: 회원 서비스에 대한 포괄적인 통합 테스트

성공 케이스와 실패 케이스를 균형있게 다루고 있으며, 예외 처리 검증이 체계적입니다.

src/test/java/com/stcom/smartmealtable/service/LoginServiceCompleteIntegrationTest.java (2)

92-96: isNewUser 값 기대치 재확인 필요

프로필이 없는 기존 회원 로그인 시 isNewUser = true로 단정하고 있습니다.
서비스 구현이 “회원 최초 로그인”이 아닌 “프로필 존재 여부”를 기준으로 값을 세팅하는지 다시 한 번 확인 바랍니다.


145-149: 소셜 로그인 isNewUser 플래그 검증 로직 동일 확인

위와 동일한 맥락입니다. 기존 소셜 계정 갱신 시에도 isNewUser = true를 기대하고 있는데, 서비스 로직과 일치하는지 점검해주세요.

src/main/java/com/stcom/smartmealtable/service/ExpenditureService.java (1)

52-55: 지출 등록 후 예산 엔티티 영속 여부 확인 필요
dailyBudget.addSpent() 호출 뒤 별도 save 없이 flush-to-DB 가 기대됩니다. 영속 상태가 아닐 경우 spent 값이 반영되지 않을 수 있으니, 두 엔티티가 동일 트랜잭션에서 영속 상태인지 확인해 주세요.

src/test/java/com/stcom/smartmealtable/integration/BudgetIntegrationTest.java (1)

197-206: 리스트 순서 의존성

weeklyBudgets.get(i) 로 요일별 검증을 하려면 Repository 쿼리가 날짜 오름차순 정렬을 보장해야 합니다. 정렬이 확실치 않다면 weeklyBudgets.sort(Comparator.comparing(DailyBudget::getDate)) 후 검증하거나, 쿼리에 ORDER BY date 를 명시해 주세요.

src/test/java/com/stcom/smartmealtable/repository/BudgetRepositoryAdvancedTest.java (1)

264-276: 실행 시간 제한 플래키 위험

executionTime < 5000ms 조건은 CI 환경 성능에 따라 간헐적으로 실패할 수 있습니다. 퍼포먼스 테스트는 별도 벤치마킹으로 분리하거나, 충분히 여유 있는 임계값/조건 없는 검증을 권장드립니다.

src/test/java/com/stcom/smartmealtable/domain/Budget/BudgetDomainIntegrationTest.java (2)

69-79: 한도 감소 후 잔액 음수 계산 검증 OK

로직 자체는 명확하며 경계값 검증이 잘 되어 있습니다. 인자 타입 수정만 이루어지면 테스트가 유의미하게 통과할 것으로 보입니다.


145-169: BigDecimal scale 비교 주의

expectedUsageRateBigDecimal.valueOf(50.00) 로 생성해 scale 2 이지만, usageRatesetScale(2) 로 동일 scale 이라 OK. 반면 다른 구간(예: 111-113 라인)에서 BigDecimal.valueOf(40.00) 대신 BigDecimal.valueOf(40) 를 쓰면 scale 0 으로 equals 실패가 날 수 있습니다. 전 구간 동일 scale 유지 또는 compareTo 기반 검증을 권장합니다.

Comment on lines +9 to +10
@Data
@AllArgsConstructor
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Jackson 역직렬화를 위해 @NoArgsConstructor 추가 필요

Spring MVC에서 JSON → DTO 변환 시 기본 생성자가 없으면 역직렬화에 실패합니다.
@AllArgsConstructor만 존재하므로 @NoArgsConstructor를 추가하거나 @Builder 패턴으로 명시적 생성자 매핑을 해야 합니다.

 @Data
-@AllArgsConstructor
+@AllArgsConstructor
+@NoArgsConstructor
 import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Data
@AllArgsConstructor
// other imports...
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SchoolGroupUpdateRequest {
// existing fields and methods...
}
🤖 Prompt for AI Agents
In
src/main/java/com/stcom/smartmealtable/web/dto/group/SchoolGroupUpdateRequest.java
at lines 9 to 10, the class lacks a no-argument constructor required for Jackson
deserialization during JSON to DTO conversion in Spring MVC. Add the Lombok
annotation @NoArgsConstructor to generate a default constructor, ensuring proper
deserialization without affecting the existing all-args constructor.

Comment on lines +33 to +48
private static class YearMonthFormatter implements Printer<YearMonth>, Parser<YearMonth> {
private final DateTimeFormatter formatter;

public YearMonthFormatter(String pattern) {
this.formatter = DateTimeFormatter.ofPattern(pattern);
}

@Override
public String print(YearMonth yearMonth, Locale locale) {
return yearMonth.format(formatter);
}

@Override
public YearMonth parse(String text, Locale locale) throws ParseException {
return YearMonth.parse(text, formatter);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

DateTimeParseExceptionParseException으로 변환하지 않아 API 일관성이 깨짐

Parser#parseParseException을 기대하지만 현재 구현은 DateTimeParseException(런타임) 그대로 전파합니다.
스프링 포매터 생태계와 일관성을 맞추려면 체크 예외로 감싸 반환하는 편이 안전합니다. 또한 locale 인자를 DateTimeFormatter#withLocale로 반영하면 국제화 대응이 완비됩니다.

-        public YearMonth parse(String text, Locale locale) throws ParseException {
-            return YearMonth.parse(text, formatter);
+        public YearMonth parse(String text, Locale locale) throws ParseException {
+            try {
+                return YearMonth.parse(text, formatter.withLocale(locale));
+            } catch (DateTimeParseException e) {
+                throw new ParseException("Invalid YearMonth format: " + text, 0);
+            }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static class YearMonthFormatter implements Printer<YearMonth>, Parser<YearMonth> {
private final DateTimeFormatter formatter;
public YearMonthFormatter(String pattern) {
this.formatter = DateTimeFormatter.ofPattern(pattern);
}
@Override
public String print(YearMonth yearMonth, Locale locale) {
return yearMonth.format(formatter);
}
@Override
public YearMonth parse(String text, Locale locale) throws ParseException {
return YearMonth.parse(text, formatter);
}
private static class YearMonthFormatter implements Printer<YearMonth>, Parser<YearMonth> {
private final DateTimeFormatter formatter;
public YearMonthFormatter(String pattern) {
this.formatter = DateTimeFormatter.ofPattern(pattern);
}
@Override
public String print(YearMonth yearMonth, Locale locale) {
return yearMonth.format(formatter);
}
@Override
public YearMonth parse(String text, Locale locale) throws ParseException {
try {
return YearMonth.parse(text, formatter.withLocale(locale));
} catch (DateTimeParseException e) {
throw new ParseException("Invalid YearMonth format: " + text, 0);
}
}
}
🤖 Prompt for AI Agents
In
src/main/java/com/stcom/smartmealtable/web/validation/YearMonthAnnotationFormatterFactory.java
between lines 33 and 48, the parse method currently throws a runtime
DateTimeParseException instead of the expected checked ParseException, breaking
API consistency. Modify the parse method to catch DateTimeParseException and
rethrow it as a ParseException. Also, apply the locale parameter to the
DateTimeFormatter using withLocale(locale) before parsing to properly support
internationalization.

Comment on lines +34 to 35
@Column(unique = true)
private String providerUserId;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

providerUserId 단독 UNIQUE 제약은 충돌 가능성이 있습니다
OAuth 공급자마다 동일한 providerUserId가 존재할 수 있어,
google:123 / kakao:123 두 계정을 동시에 저장해야 하는 상황에서 제약 조건이 충돌할 위험이 있습니다.
provider + providerUserId 복합 유니크로 변경하는 편이 안전합니다.

-@Entity
+@Entity
+@Table(
+    name = "social_account",
+    uniqueConstraints = @UniqueConstraint(columnNames = {"provider", "provider_user_id"})
+)
 ...
-    @Column(unique = true)
-    private String providerUserId;
+    @Column(name = "provider_user_id")
+    private String providerUserId;

DB 마이그레이션 스크립트도 함께 추가해 주세요.

🤖 Prompt for AI Agents
In src/main/java/com/stcom/smartmealtable/domain/social/SocialAccount.java at
lines 34-35, the unique constraint on providerUserId alone can cause conflicts
because the same providerUserId may exist across different OAuth providers.
Modify the unique constraint to be a composite unique constraint on both
provider and providerUserId fields. Additionally, create and include a database
migration script that updates the schema to enforce this composite unique
constraint safely.

Comment on lines 41 to 43
Member member = memberRepository.findByEmail(tokenDto.getEmail())
.orElseThrow(() -> new IllegalStateException("회원이 null일 수는 없습니다"));
.orElseThrow(() -> new IllegalStateException("회원 엔티티가 존재하지 않은 상태로 소셜 계정 연결을 시도했습니다."));

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

예외 타입 통일 필요

linkSocialAccount 메서드는 회원을 찾지 못했을 때 IllegalStateException을 던지고, 같은 클래스의 updateToken 메서드는 IllegalArgumentException을 사용합니다. 입력 데이터가 잘못되었거나 존재하지 않을 때는 IllegalArgumentException으로 통일하는 편이 서비스 전반의 예외 정책과 맞아 가독성이 높아집니다.

-                .orElseThrow(() -> new IllegalStateException("회원 엔티티가 존재하지 않은 상태로 소셜 계정 연결을 시도했습니다."));
+                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다"));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Member member = memberRepository.findByEmail(tokenDto.getEmail())
.orElseThrow(() -> new IllegalStateException("회원이 null일 수는 없습니다"));
.orElseThrow(() -> new IllegalStateException("회원 엔티티가 존재하지 않은 상태로 소셜 계정 연결을 시도했습니다."));
Member member = memberRepository.findByEmail(tokenDto.getEmail())
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다"));
🤖 Prompt for AI Agents
In src/main/java/com/stcom/smartmealtable/service/SocialAccountService.java
around lines 41 to 43, the exception thrown when a member is not found in the
linkSocialAccount method is IllegalStateException, while updateToken uses
IllegalArgumentException. To maintain consistency and align with the service's
overall exception policy, change the exception in linkSocialAccount to
IllegalArgumentException with an appropriate message.

Comment on lines +71 to +76
public void changeLimit(BigDecimal limit) {
if (limit == null || limit.signum() < 0) {
throw new IllegalArgumentException("예산 한도는 0 이상이어야 합니다.");
}
this.limit = limit;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

예산 한도 변경 메서드 검증 로직은 적절하지만 추가 validation 고려 필요

null·음수 체크는 좋습니다. 다만 limit.compareTo(spendAmount) < 0 인 경우(이미 초과 사용 중인데 한도를 더 낮추는 상황)도 예외로 막을지, 비즈니스 요건을 다시 확인해 주세요.

🤖 Prompt for AI Agents
In src/main/java/com/stcom/smartmealtable/domain/Budget/Budget.java around lines
71 to 76, the changeLimit method currently validates that the new limit is not
null and not negative, but it does not check if the new limit is less than the
current spendAmount. To fix this, add a validation that throws an exception if
limit.compareTo(spendAmount) < 0, preventing setting a limit lower than the
already spent amount, unless business requirements specify otherwise.

Comment on lines 54 to +58
public MemberProfilePageResponse(MemberProfile profile, MemberDto memberDto) {
this.nickName = profile.getNickName();
this.email = memberDto.getEmail();
Address address = profile.findPrimaryAddress().getAddress();
this.primaryAddress = address.getRoadAddress() + address.getDetailAddress();
this.primaryAddress = profile.findPrimaryAddress().getAddress().getRoadAddress()
+ profile.findPrimaryAddress().getAddress().getDetailAddress();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Primary Address 미존재 시 NPE 가능성

findPrimaryAddress()null을 반환할 경우 NPE가 발생합니다.
Optional 처리 또는 예외 변환으로 방어 로직을 추가해 주세요.

🤖 Prompt for AI Agents
In
src/main/java/com/stcom/smartmealtable/web/controller/MemberProfileController.java
around lines 54 to 58, the code calls findPrimaryAddress() without checking for
null, which can cause a NullPointerException if no primary address exists. To
fix this, modify the code to handle the case when findPrimaryAddress() returns
null by using Optional or by adding a null check before accessing getAddress().
If null, either provide a default value or throw a meaningful exception to
prevent NPE.

Comment on lines +29 to +33
public void registerExpenditure(Long profileId,
LocalDateTime spentDate,
Long amount,
String tradeName) {

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

금액은 Long보다 BigDecimal 사용을 권장합니다
금액을 Long으로 받으면 원 단위만 표현할 수 있고, 향후 소수점(예: 외화)‧큰 금액 처리 시 오버플로우 위험이 있습니다. 서비스 계층부터 BigDecimal로 통일하면 형 변환 오버헤드와 오용을 줄일 수 있습니다.

🤖 Prompt for AI Agents
In src/main/java/com/stcom/smartmealtable/service/ExpenditureService.java around
lines 29 to 33, the amount parameter is currently a Long, which limits precision
and risks overflow for large or fractional values. Change the amount parameter
type to BigDecimal to support decimal values and large amounts consistently from
the service layer, reducing conversion overhead and misuse.

Comment on lines +63 to +73
public void editExpenditure(Long profileId, Long expenditureId, LocalDateTime spentDate, Long amount,
String tradeName) {
Expenditure expenditure = expenditureRepository.findById(expenditureId)
.orElseThrow(() -> new IllegalArgumentException("지출 내역이 존재하지 않습니다."));

if (!expenditure.getDailyBudget().getMemberProfile().getId().equals(profileId)) {
throw new IllegalArgumentException("해당 지출 내역 등록자와 접근자가 다릅니다.");
}

expenditure.edit(spentDate, amount, tradeName);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

금액 변경 시 기존 spent 보정 로직 확인 필요
Expenditure.edit() 내부에서 변경 전‧후 금액 차이를 일일/월별 예산에 반영하지 않으면 spent 값이 불일치합니다. 메서드 내부에서 dailyBudget.updateSpentDiff(...) 처리가 있는지 확인 바랍니다.

🤖 Prompt for AI Agents
In src/main/java/com/stcom/smartmealtable/service/ExpenditureService.java lines
63 to 73, the editExpenditure method calls expenditure.edit() but does not
ensure that changes in amount update the spent values in daily and monthly
budgets. Verify if expenditure.edit() internally calls
dailyBudget.updateSpentDiff() to adjust spent amounts; if not, modify
editExpenditure to calculate the amount difference and explicitly call
dailyBudget.updateSpentDiff() to keep spent values consistent.

Comment on lines +48 to +55
public ApiResponse<Void> saveCategoryPreferences(@UserContext MemberDto memberDto,
@RequestBody PreferencesRequest request) {
memberCategoryPreferenceService.savePreferences(
memberDto.getProfileId(),
request.getLiked(),
request.getDisliked());
return ApiResponse.createSuccessWithNoContent();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

DTO 검증 및 HTTP 메서드 선택 검토

  • PreferencesRequest 필드가 null 일 때 NPE 우려가 있으니 @Valid + @NotNull 적용을 권장합니다.
  • 선호도 “저장/덮어쓰기” 동작이라면 REST 관점에서 PUT이 의미상 더 적합합니다.
🤖 Prompt for AI Agents
In
src/main/java/com/stcom/smartmealtable/web/controller/MemberPreferenceController.java
around lines 48 to 55, add @Valid and @NotNull annotations to the
PreferencesRequest parameter to ensure DTO validation and prevent null pointer
exceptions. Also, change the HTTP method annotation from POST to PUT to better
reflect the semantics of saving or overwriting preferences in a RESTful manner.

Comment on lines +41 to 53
@Query("select b from Budget b where type(b) = DailyBudget and b.memberProfile.id = :profileId and treat(b as DailyBudget).date = :date")
Optional<DailyBudget> findDailyBudgetByMemberProfileIdAndDate(Long profileId, LocalDate date);

@Query("select b from Budget b where type(b) = MonthlyBudget and b.memberProfile.id = :profileId and treat(b as MonthlyBudget).yearMonth = :yearMonth")
Optional<MonthlyBudget> findMonthlyBudgetByMemberProfileIdAndYearMonth(Long profileId, YearMonth yearMonth);

@Query("select b from Budget b where type(b) = DailyBudget and b.memberProfile.id = :profileId and treat(b as DailyBudget).date between :startOfWeek and :endOfWeek order by treat(b as DailyBudget).date asc")
List<DailyBudget> findDailyBudgetsByMemberProfileIdAndDateBetween(Long profileId, LocalDate startOfWeek,
LocalDate endOfWeek);

@Query("select b from Budget b where type(b) = MonthlyBudget and b.memberProfile.id = :profileId and treat(b as MonthlyBudget).yearMonth < :from order by treat(b as MonthlyBudget).yearMonth desc ")
List<MonthlyBudget> findMonthlyBudgetsByMemberProfileIdAndYearMonthBefore(Long profileId, YearMonth from);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

@param 누락으로 NamedParameter 예외 발생 가능
profileId, date, yearMonth, startOfWeek, endOfWeek, from 파라미터에 @Param이 빠져 있습니다. 컴파일 옵션에 따라 메서드 파라미터 이름이 유지되지 않으면 런타임 오류가 발생합니다.

-Optional<DailyBudget> findDailyBudgetByMemberProfileIdAndDate(Long profileId, LocalDate date);
+Optional<DailyBudget> findDailyBudgetByMemberProfileIdAndDate(@Param("profileId") Long profileId,
+                                                              @Param("date") LocalDate date);

동일 방식으로 다른 메서드에도 적용해 주세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Query("select b from Budget b where type(b) = DailyBudget and b.memberProfile.id = :profileId and treat(b as DailyBudget).date = :date")
Optional<DailyBudget> findDailyBudgetByMemberProfileIdAndDate(Long profileId, LocalDate date);
@Query("select b from Budget b where type(b) = MonthlyBudget and b.memberProfile.id = :profileId and treat(b as MonthlyBudget).yearMonth = :yearMonth")
Optional<MonthlyBudget> findMonthlyBudgetByMemberProfileIdAndYearMonth(Long profileId, YearMonth yearMonth);
@Query("select b from Budget b where type(b) = DailyBudget and b.memberProfile.id = :profileId and treat(b as DailyBudget).date between :startOfWeek and :endOfWeek order by treat(b as DailyBudget).date asc")
List<DailyBudget> findDailyBudgetsByMemberProfileIdAndDateBetween(Long profileId, LocalDate startOfWeek,
LocalDate endOfWeek);
@Query("select b from Budget b where type(b) = MonthlyBudget and b.memberProfile.id = :profileId and treat(b as MonthlyBudget).yearMonth < :from order by treat(b as MonthlyBudget).yearMonth desc ")
List<MonthlyBudget> findMonthlyBudgetsByMemberProfileIdAndYearMonthBefore(Long profileId, YearMonth from);
}
@Query("select b from Budget b where type(b) = DailyBudget and b.memberProfile.id = :profileId and treat(b as DailyBudget).date = :date")
Optional<DailyBudget> findDailyBudgetByMemberProfileIdAndDate(
@Param("profileId") Long profileId,
@Param("date") LocalDate date);
@Query("select b from Budget b where type(b) = MonthlyBudget and b.memberProfile.id = :profileId and treat(b as MonthlyBudget).yearMonth = :yearMonth")
Optional<MonthlyBudget> findMonthlyBudgetByMemberProfileIdAndYearMonth(Long profileId, YearMonth yearMonth);
@Query("select b from Budget b where type(b) = DailyBudget and b.memberProfile.id = :profileId and treat(b as DailyBudget).date between :startOfWeek and :endOfWeek order by treat(b as DailyBudget).date asc")
List<DailyBudget> findDailyBudgetsByMemberProfileIdAndDateBetween(Long profileId, LocalDate startOfWeek,
LocalDate endOfWeek);
@Query("select b from Budget b where type(b) = MonthlyBudget and b.memberProfile.id = :profileId and treat(b as MonthlyBudget).yearMonth < :from order by treat(b as MonthlyBudget).yearMonth desc ")
List<MonthlyBudget> findMonthlyBudgetsByMemberProfileIdAndYearMonthBefore(Long profileId, YearMonth from);
}
🤖 Prompt for AI Agents
In src/main/java/com/stcom/smartmealtable/repository/BudgetRepository.java
around lines 41 to 53, the query method parameters lack @Param annotations for
profileId, date, yearMonth, startOfWeek, endOfWeek, and from. This can cause
runtime NamedParameter exceptions if parameter names are not retained during
compilation. To fix this, add @Param annotations to each method parameter
matching the named parameters in the queries, and apply the same fix to all
similar methods in the file.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review continued from previous batch...

Comment on lines +61 to +70
public void registerDefaultDailyBudgetBy(Long profileId, Long dailyLimit, LocalDate startDate) {
MemberProfile profile = memberProfileRepository.findById(profileId)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 프로필로 접근"));
// 일일 예산은 오늘 ~ 이번달 말일까지 디폴트 daily Limit로 여러 개 생성해준다.
List<DailyBudget> budgets = new ArrayList<>();
for (LocalDate date = startDate; date.getMonth() == startDate.getMonth(); date = date.plusDays(1)) {
budgets.add(new DailyBudget(profile, BigDecimal.valueOf(dailyLimit), date));
}
budgetRepository.saveAll(budgets);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

중복 일일 예산 생성 가능성

이미 동일한 날짜의 DailyBudget가 존재할 경우 중복 레코드가 저장됩니다. saveAll 전에 기존 예산 존재 여부를 체크하거나, DB에 유니크 제약을 두는 방식을 고려해 주세요.

🤖 Prompt for AI Agents
In src/main/java/com/stcom/smartmealtable/service/BudgetService.java around
lines 61 to 70, the method registerDefaultDailyBudgetBy can create duplicate
DailyBudget entries for the same date. To fix this, before adding a new
DailyBudget to the list, query the budgetRepository to check if a DailyBudget
for the given profile and date already exists and skip adding duplicates.
Alternatively, ensure the database schema has a unique constraint on profile and
date to prevent duplicates at the DB level.

Comment on lines +81 to +90
@Data
@AllArgsConstructor
public static class EmailLoginRequest {
@NotEmpty(message = "이메일은 비어있을 수 없습니다")
@Email(message = "유효한 이메일 형식이 아닙니다")
private String email;
@NotEmpty(message = "비밀번호는 비어있을 수 없습니다")
private String password;
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

DTO 역직렬화를 위한 기본 생성자 필요

@AllArgsConstructor 만 선언되어 있어 Jackson이 기본 생성자를 찾지 못할 수 있습니다. @NoArgsConstructor(access = AccessLevel.PROTECTED) 추가를 권장합니다.

🤖 Prompt for AI Agents
In
src/main/java/com/stcom/smartmealtable/web/controller/AuthTokenController.java
around lines 81 to 90, the EmailLoginRequest DTO lacks a default constructor
which Jackson requires for deserialization. Add the annotation
@NoArgsConstructor with access set to AccessLevel.PROTECTED to provide a
protected no-argument constructor alongside the existing @AllArgsConstructor.

Comment on lines +53 to +58
public ApiResponse<Void> logout(HttpServletRequest request) {
String jwt = request.getHeader("Authorization");
if (Objects.nonNull(jwt)) {
jwtBlacklistService.addToBlacklist(jwt);
}
return ApiResponse.createSuccessWithNoContent();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Authorization 헤더 처리 오류

Authorization: Bearer <JWT> 형식 그대로 블랙리스트에 저장하면 토큰 검증 시 불일치가 발생할 수 있습니다.
jwt.replace("Bearer ", "") 등으로 Bearer 프리픽스를 제거한 순수 토큰만 저장해 주세요.

🤖 Prompt for AI Agents
In
src/main/java/com/stcom/smartmealtable/web/controller/AuthTokenController.java
around lines 53 to 58, the logout method stores the entire Authorization header
including the "Bearer " prefix in the blacklist, which causes token verification
mismatches. Modify the code to remove the "Bearer " prefix from the jwt string
before adding it to the blacklist by using jwt.replace("Bearer ", "") or
equivalent, so only the pure token is stored.

Comment on lines +74 to +79
@PostMapping("/token/refresh")
public ApiResponse<AccessTokenRefreshResponse> refreshAccessToken(@UserContext MemberDto memberDto,
@Valid @RequestBody RefreshTokenRequest request) {
String accessToken = jwtTokenService.createAccessToken(memberDto.getMemberId(), memberDto.getProfileId());
return ApiResponse.createSuccess(new AccessTokenRefreshResponse(accessToken, 3600, "Bearer"));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Refresh Token 미검증

클라이언트가 전송한 refreshToken 값을 전혀 사용하지 않으므로, 누구나 만료된 Access Token으로도 재발급을 받을 수 있습니다.
jwtTokenService.validateRefreshToken(request.getRefreshToken()) 등 검증 절차를 추가하고, 필요 시 블랙리스트 처리까지 고려해 주세요.

🤖 Prompt for AI Agents
In
src/main/java/com/stcom/smartmealtable/web/controller/AuthTokenController.java
lines 74 to 79, the refreshAccessToken method does not validate the refreshToken
from the request, allowing token reissuance without proper verification. To fix
this, add a call to jwtTokenService.validateRefreshToken with the refreshToken
from the request to verify its validity before creating a new access token.
Additionally, implement logic to handle invalid tokens, such as returning an
error response or adding the token to a blacklist if necessary.

Comment on lines +41 to +44
@PostMapping("/messages/parse")
public ApiResponse<ExpenditureDto> parseCreditMessage(@RequestBody ParseRequest request) {
return ApiResponse.createSuccess(creditMessageManager.parseMessage(request.getMessage()));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

ParseRequest에 대한 Bean Validation이 동작하지 않습니다

@RequestBody ParseRequest request만 선언되어 있어 @NotEmpty 검증이 수행되지 않습니다.
@Validated(또는 @Valid)를 파라미터에 추가해 주세요.

- public ApiResponse<ExpenditureDto> parseCreditMessage(@RequestBody ParseRequest request) {
+ public ApiResponse<ExpenditureDto> parseCreditMessage(@RequestBody @Validated ParseRequest request) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@PostMapping("/messages/parse")
public ApiResponse<ExpenditureDto> parseCreditMessage(@RequestBody ParseRequest request) {
return ApiResponse.createSuccess(creditMessageManager.parseMessage(request.getMessage()));
}
@PostMapping("/messages/parse")
public ApiResponse<ExpenditureDto> parseCreditMessage(@RequestBody @Validated ParseRequest request) {
return ApiResponse.createSuccess(creditMessageManager.parseMessage(request.getMessage()));
}
🤖 Prompt for AI Agents
In
src/main/java/com/stcom/smartmealtable/web/controller/MemberExpenditureController.java
around lines 41 to 44, the method parameter for ParseRequest lacks validation
annotations, so Bean Validation such as @NotEmpty is not triggered. To fix this,
add the @Valid or @Validated annotation to the ParseRequest parameter in the
parseCreditMessage method signature to enable validation of the request body.

Comment on lines +72 to +80
// 해당 일자가 속한 달을 포함하여, 이전 6개월 조회
@GetMapping("/montly/{date}")
public ApiResponse<List<MonthlyBudgetResponse>> monthlyBudgetsByDate(@UserContext MemberDto memberDto,
@PathVariable("date") @DateTimeFormat(iso = ISO.DATE) LocalDate date) {
List<MonthlyBudget> monthlyBudgets = budgetService.getMonthlyBudgetsBy(memberDto.getProfileId(),
date, 6);

List<MonthlyBudgetResponse> responses = monthlyBudgets.stream()
.map(MonthlyBudgetResponse::of)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

엔드포인트 경로 오타 – montlymonthly

/montly/{date}는 오타로 보이며, 동일 컨트롤러 내 /monthly/... 와 불일치합니다.
API 소비자 혼란 및 유지보수 비용 증가 요소이므로 반드시 수정하십시오.

- @GetMapping("/montly/{date}")
+ @GetMapping("/monthly/{date}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 해당 일자가 속한 달을 포함하여, 이전 6개월 조회
@GetMapping("/montly/{date}")
public ApiResponse<List<MonthlyBudgetResponse>> monthlyBudgetsByDate(@UserContext MemberDto memberDto,
@PathVariable("date") @DateTimeFormat(iso = ISO.DATE) LocalDate date) {
List<MonthlyBudget> monthlyBudgets = budgetService.getMonthlyBudgetsBy(memberDto.getProfileId(),
date, 6);
List<MonthlyBudgetResponse> responses = monthlyBudgets.stream()
.map(MonthlyBudgetResponse::of)
// 해당 일자가 속한 달을 포함하여, 이전 6개월 조회
@GetMapping("/monthly/{date}")
public ApiResponse<List<MonthlyBudgetResponse>> monthlyBudgetsByDate(@UserContext MemberDto memberDto,
@PathVariable("date") @DateTimeFormat(iso = ISO.DATE) LocalDate date) {
List<MonthlyBudget> monthlyBudgets = budgetService.getMonthlyBudgetsBy(memberDto.getProfileId(),
date, 6);
List<MonthlyBudgetResponse> responses = monthlyBudgets.stream()
.map(MonthlyBudgetResponse::of)
🤖 Prompt for AI Agents
In
src/main/java/com/stcom/smartmealtable/web/controller/MemberBudgetController.java
around lines 72 to 80, the endpoint path has a typo: "/montly/{date}" should be
corrected to "/monthly/{date}" to match the naming convention and avoid
confusion. Update the @GetMapping annotation to use the correct spelling
"/monthly/{date}".

Comment on lines 64 to 69
@ExceptionHandler(IllegalStateException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Object> illegalStateExHandler(IllegalArgumentException e) {
log.error("[IllegalStateException] ex", e);
return ApiResponse.createError("파라미터나 API 스펙을 확인해보세요" + e.getMessage());
}

@ExceptionHandler(PasswordFailedExceededException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ApiResponse<Object> passwordFailedExceededExHandler(PasswordFailedExceededException e) {
log.error("[PasswordFailedExceededException] ex", e);
return ApiResponse.createError(e.getMessage());
return ApiResponse.createError("서버 내부 동작 오류입니다. 사유: " + e.getMessage());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

예외 타입 불일치로 핸들러가 작동하지 않습니다

@ExceptionHandler(IllegalStateException.class)인데 메서드 파라미터가 IllegalArgumentException입니다.
타입이 호환되지 않아 매핑이 실패하므로 IllegalStateException으로 수정해야 합니다.

- public ApiResponse<Object> illegalStateExHandler(IllegalArgumentException e) {
+ public ApiResponse<Object> illegalStateExHandler(IllegalStateException e) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@ExceptionHandler(IllegalStateException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Object> illegalStateExHandler(IllegalArgumentException e) {
log.error("[IllegalStateException] ex", e);
return ApiResponse.createError("파라미터나 API 스펙을 확인해보세요" + e.getMessage());
}
@ExceptionHandler(PasswordFailedExceededException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ApiResponse<Object> passwordFailedExceededExHandler(PasswordFailedExceededException e) {
log.error("[PasswordFailedExceededException] ex", e);
return ApiResponse.createError(e.getMessage());
return ApiResponse.createError("서버 내부 동작 오류입니다. 사유: " + e.getMessage());
}
@ExceptionHandler(IllegalStateException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Object> illegalStateExHandler(IllegalStateException e) {
log.error("[IllegalStateException] ex", e);
return ApiResponse.createError("서버 내부 동작 오류입니다. 사유: " + e.getMessage());
}
🤖 Prompt for AI Agents
In src/main/java/com/stcom/smartmealtable/web/exhandler/ExControllerAdvice.java
lines 64 to 69, the exception handler method is annotated to handle
IllegalStateException but its parameter type is incorrectly set to
IllegalArgumentException. To fix this, change the method parameter type to
IllegalStateException so it matches the annotation and the handler functions
correctly.

Comment on lines +23 to +24
memberProfile = new MemberProfile();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

MemberProfile 기본 생성자 사용 주의

new MemberProfile() 로 생성하고 있지만, MemberProfile이 JPA 엔티티라면 필수 필드를 세팅하지 않은 객체가 만들어집니다. 도메인 무결성을 깨뜨릴 수 있으므로 빌더 패턴이나 테스트-전용 팩토리를 사용해 필수 값까지 채워 주세요.

🤖 Prompt for AI Agents
In
src/test/java/com/stcom/smartmealtable/domain/Budget/BudgetDomainIntegrationTest.java
at lines 23-24, avoid using the default constructor new MemberProfile() directly
because it creates an incomplete JPA entity missing required fields. Instead,
instantiate MemberProfile using a builder pattern or a test-specific factory
method that sets all mandatory fields to maintain domain integrity in tests.

Comment on lines +66 to +188
budget.addSpent(20000); // 20,000원 지출

// when & then - 한도 증가
budget.changeLimit(BigDecimal.valueOf(50000));
assertThat(budget.getLimit()).isEqualTo(BigDecimal.valueOf(50000));
assertThat(budget.getAvailableAmount()).isEqualTo(BigDecimal.valueOf(30000));
assertThat(budget.isOverLimit()).isFalse();

// when & then - 한도 감소 (한도 초과 상황)
budget.changeLimit(BigDecimal.valueOf(15000));
assertThat(budget.getLimit()).isEqualTo(BigDecimal.valueOf(15000));
assertThat(budget.getAvailableAmount()).isEqualTo(BigDecimal.valueOf(-5000));
assertThat(budget.isOverLimit()).isTrue();
}

@DisplayName("복잡한 지출 패턴 계산")
@Test
void complexSpendingPatternCalculation() {
// given
DailyBudget weeklyBudget = new DailyBudget(
memberProfile,
BigDecimal.valueOf(150000),
LocalDate.now()
);

// 다양한 금액으로 여러 번 지출
weeklyBudget.addSpent(25000);
weeklyBudget.addSpent(15000);
weeklyBudget.addSpent(7500);
weeklyBudget.addSpent(12500); // 총 60,000원 지출

// when
BigDecimal totalSpent = weeklyBudget.getSpendAmount();
BigDecimal remaining = weeklyBudget.getAvailableAmount();

// 사용률 계산: 60,000 / 150,000 = 0.4 (40%)
BigDecimal usageRate = totalSpent
.divide(weeklyBudget.getLimit(), 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100))
.setScale(2, RoundingMode.HALF_UP);

// then - 정확한 계산 검증
assertThat(totalSpent).isEqualTo(BigDecimal.valueOf(60000));
assertThat(remaining).isEqualTo(BigDecimal.valueOf(90000)); // 150,000 - 60,000

BigDecimal expectedUsageRate = BigDecimal.valueOf(40.00);
assertThat(usageRate).isCloseTo(expectedUsageRate, within(BigDecimal.valueOf(0.01)));
}

@DisplayName("예산 리셋 기능")
@Test
void budgetResetFunctionality() {
// given
DailyBudget budget = new DailyBudget(
memberProfile,
BigDecimal.valueOf(50000),
LocalDate.now()
);

budget.addSpent(30000);
assertThat(budget.getSpendAmount()).isEqualTo(BigDecimal.valueOf(30000));

// when - 예산 지출 리셋
budget.resetSpent();

// then
assertThat(budget.getSpendAmount()).isEqualTo(BigDecimal.ZERO);
assertThat(budget.getAvailableAmount()).isEqualTo(BigDecimal.valueOf(50000));

// 사용률 = 0 / 50000 * 100 = 0% (0 나누기 문제 없음)
BigDecimal usageRate = budget.getSpendAmount()
.multiply(BigDecimal.valueOf(100))
.divide(budget.getLimit(), 2, RoundingMode.HALF_UP);
assertThat(usageRate.stripTrailingZeros()).isEqualByComparingTo(BigDecimal.ZERO);
}

@DisplayName("예산의 부동소수점 정밀도 테스트")
@Test
void budgetFloatingPointPrecisionTest() {
// given
MonthlyBudget budget = new MonthlyBudget(
memberProfile,
BigDecimal.valueOf(1000000),
YearMonth.of(2025, 6)
);

// when - 소수점을 포함한 복잡한 계산
budget.addSpent(333333); // 33.3333%
budget.addSpent(166667); // 추가로 16.6667%

// then
BigDecimal totalSpent = budget.getSpendAmount();

// 사용률 계산: 500,000 / 1,000,000 = 0.5 (50%)
BigDecimal usageRate = totalSpent
.divide(budget.getLimit(), 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100))
.setScale(2, RoundingMode.HALF_UP);

assertThat(totalSpent).isEqualTo(BigDecimal.valueOf(500000));

BigDecimal expectedUsageRate = BigDecimal.valueOf(50.00);
assertThat(usageRate).isCloseTo(expectedUsageRate, within(BigDecimal.valueOf(0.01)));

// 남은 금액 검증
assertThat(budget.getAvailableAmount()).isEqualTo(BigDecimal.valueOf(500000));
assertThat(budget.isOverLimit()).isFalse();
}

@DisplayName("예산 한도 초과 경계값 테스트")
@Test
void budgetOverLimitBoundaryTest() {
// given
DailyBudget budget = new DailyBudget(memberProfile, BigDecimal.valueOf(100000), LocalDate.now());

// when & then - 한도와 정확히 같은 금액 지출
budget.addSpent(100000);
assertThat(budget.isOverLimit()).isFalse();
assertThat(budget.getAvailableAmount()).isEqualTo(BigDecimal.ZERO);

// when & then - 1원 초과
budget.addSpent(1);
assertThat(budget.isOverLimit()).isTrue();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

addSpent 인자 타입 불일치로 컴파일 오류 가능

다음 호출부에서 모두 int/long 리터럴을 넘기고 있습니다.

  • 66, 92-95, 125, 153-154, 182, 187, 193, 210-211, 223, 238, 242 라인

Budget.addSpent 시그니처가 addSpent(BigDecimal amount) 로 정의돼 있다면 자동 변환이 되지 않아 전체 테스트가 컴파일되지 않습니다.

-budget.addSpent(20000);
+budget.addSpent(BigDecimal.valueOf(20_000));

모든 호출부를 BigDecimal.valueOf(...) 로 수정하거나, 실제로 primitive 오버로드가 필요한지 검토해 주세요.

Also applies to: 210-242

🤖 Prompt for AI Agents
In
src/test/java/com/stcom/smartmealtable/domain/Budget/BudgetDomainIntegrationTest.java
from lines 66 to 188 and also lines 210 to 242, the calls to budget.addSpent use
int or long literals which cause compilation errors if addSpent expects a
BigDecimal parameter. To fix this, wrap all numeric literals passed to addSpent
with BigDecimal.valueOf(...) to match the method signature and ensure proper
compilation. Alternatively, verify if an overloaded addSpent method for
primitives is needed, but the immediate fix is to convert all arguments to
BigDecimal using BigDecimal.valueOf.

@@ -0,0 +1,280 @@
package com.stcom.smartmealtable.domain.Budget;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

패키지 명명 규칙 위반

package com.stcom.smartmealtable.domain.Budget; 에서 Budget 이 대문자로 시작합니다. Java 패키지는 소문자 사용이 관례이며, 운영체제별 경로 구분‧빌드 도구 캐싱 문제를 일으킬 수 있습니다.

-package com.stcom.smartmealtable.domain.Budget;
+package com.stcom.smartmealtable.domain.budget;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
package com.stcom.smartmealtable.domain.Budget;
package com.stcom.smartmealtable.domain.budget;
🤖 Prompt for AI Agents
In
src/test/java/com/stcom/smartmealtable/domain/Budget/BudgetDomainIntegrationTest.java
at line 1, the package name uses an uppercase 'Budget', which violates Java
package naming conventions. Rename the package to use all lowercase letters,
changing 'Budget' to 'budget' in the package declaration and ensure the
directory structure matches this lowercase naming to avoid OS path and build
caching issues.

@bebeis bebeis merged commit 5499ff5 into main Jun 16, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant