diff --git a/docs/20260625_메인_홈_팔로잉_탭_API/plan-task.md b/docs/20260625_메인_홈_팔로잉_탭_API/plan-task.md index 079ac588..f76694c4 100644 --- a/docs/20260625_메인_홈_팔로잉_탭_API/plan-task.md +++ b/docs/20260625_메인_홈_팔로잉_탭_API/plan-task.md @@ -349,7 +349,7 @@ data class HomeFollowingNewsInboxRecord( ### Phase 1: 응답 DTO, 도메인 모델, Security 기본 골격 -- [ ] **Task 1.1: 팔로잉 탭 응답 DTO와 domain model 추가** +- [x] **Task 1.1: 팔로잉 탭 응답 DTO와 domain model 추가** - Files: - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/following/dto/HomeFollowingTabResponse.kt` - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/home/following/domain/HomeFollowing.kt` @@ -362,7 +362,7 @@ data class HomeFollowingNewsInboxRecord( - 통과 확인: 같은 단일 테스트 명령 실행, PASS 확인. - REFACTOR: import, `JsonProperty`, nullable 필드 정리 후 `./gradlew --no-daemon ktlintCheck` 실행. -- [ ] **Task 1.2: Controller와 Security permitAll 추가** +- [x] **Task 1.2: Controller와 Security permitAll 추가** - Files: - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/following/adapter/in/web/HomeFollowingController.kt` - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/following/application/HomeFollowingFacade.kt` @@ -377,7 +377,7 @@ data class HomeFollowingNewsInboxRecord( ### Phase 2: 최근 소식 Inbox 저장소 -- [ ] **Task 2.1: Inbox Entity/JPA repository/DDL 정합성 구현** +- [x] **Task 2.1: Inbox Entity/JPA repository/DDL 정합성 구현** - Files: - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/home/following/adapter/out/persistence/HomeFollowingNewsInbox.kt` - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/home/following/adapter/out/persistence/HomeFollowingNewsInboxJpaRepository.kt` @@ -390,7 +390,7 @@ data class HomeFollowingNewsInboxRecord( - 통과 확인: 같은 단일 테스트 명령 실행, PASS 확인. - REFACTOR: 컬럼명, enum 저장 방식, timestamp nullable 정책이 DDL과 맞는지 비교한다. -- [ ] **Task 2.2: Inbox persistence adapter 구현** +- [x] **Task 2.2: Inbox persistence adapter 구현** - Files: - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/home/following/port/out/HomeFollowingNewsInboxPort.kt` - Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/home/following/adapter/out/persistence/HomeFollowingNewsInboxPersistenceAdapter.kt` @@ -398,9 +398,9 @@ data class HomeFollowingNewsInboxRecord( - RED: `insertIgnoreAll(records)`가 중복 source key를 예외 없이 무시하고 신규 row만 저장하는 테스트를 작성한다. - RED: `findActiveFollowerIds(creatorId)`가 활성 팔로워만 반환하는 테스트를 작성한다. - 실패 확인: Task 2.1과 같은 단일 테스트 명령 실행, port/adapter 미구현 실패 확인. - - GREEN: MySQL `INSERT IGNORE` 기반 bulk 저장으로 unique 충돌을 무시하는 idempotent 저장을 최소 구현한다. + - GREEN: JPA `saveAndFlush`와 unique 제약 기반 `DataIntegrityViolationException` 처리로 중복 source key를 예외 없이 무시하는 idempotent 저장을 최소 구현한다. - 통과 확인: 같은 단일 테스트 명령 실행, PASS 확인. - - REFACTOR: batch insert가 불필요하게 N+1 flush를 만들지 않도록 adapter 내부를 정리한다. + - REFACTOR: H2/MySQL dialect 분기 없이 단일 JPA 경로를 유지하고, 동시 적재 시 inserted count는 best-effort임을 검증 기록에 남긴다. ### Phase 3: 팔로잉 탭 조회 Repository/Service @@ -461,6 +461,15 @@ data class HomeFollowingNewsInboxRecord( - 통과 확인: 같은 단일 테스트 명령 실행, PASS 확인. - REFACTOR: `nowProvider: () -> LocalDateTime`을 주입해 테스트 시간을 고정한다. +- [ ] **Task 3.6: Inbox 중복 insert 충돌 통합 테스트 보강** + - Files: + - Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/home/following/adapter/out/persistence/HomeFollowingNewsInboxPersistenceAdapterTest.kt` + - RED: 실제 `HomeFollowingNewsInboxJpaRepository`로 동일 `memberId/newsType/sourceKey` unique 충돌을 발생시킨 뒤, 같은 테스트 흐름에서 `insertIgnoreAll(records)` 또는 repository 조회가 예외 없이 동작하는 통합 테스트를 작성한다. + - 실패 확인: `./gradlew --no-daemon test --tests "kr.co.vividnext.sodalive.v2.home.following.adapter.out.persistence.HomeFollowingNewsInboxPersistenceAdapterTest"` 실행, 실제 DB 충돌 후 persistence context/transaction 상태 검증 실패를 확인한다. + - GREEN: 필요 시 adapter의 중복 충돌 처리에서 persistence context 정리 또는 트랜잭션 경계를 최소 보강한다. + - 통과 확인: 같은 단일 테스트 명령 실행, PASS 확인. + - REFACTOR: mock 기반 race 테스트와 통합 테스트의 책임을 분리해, mock은 분기 검증만 하고 통합 테스트는 실제 Hibernate 세션/트랜잭션 유효성을 검증하도록 정리한다. + ### Phase 4: 최근 소식 Publish Service와 기존 이벤트 연결 - [ ] **Task 4.1: sourceKey 생성 정책 구현** @@ -588,4 +597,9 @@ data class HomeFollowingNewsInboxRecord( ## 6. 검증 기록 -- 문서 작성 시점에는 구현 검증 기록 없음. +- 2026-06-25 Phase 1-2 구현 검증: + - `./gradlew --no-daemon test --tests "kr.co.vividnext.sodalive.v2.api.home.following.dto.HomeFollowingTabResponseTest"` 실행 결과 `BUILD SUCCESSFUL`. + - `./gradlew --no-daemon test --tests "kr.co.vividnext.sodalive.v2.api.home.following.adapter.in.web.HomeFollowingControllerTest"` 실행 결과 `BUILD SUCCESSFUL`. + - `./gradlew --no-daemon test --tests "kr.co.vividnext.sodalive.v2.home.following.adapter.out.persistence.HomeFollowingNewsInboxPersistenceAdapterTest"` 실행 결과 `BUILD SUCCESSFUL`. + - 병렬 Gradle 실행 중 `build/snapshot/kotlin/kaptGenerateStubsKotlin` 삭제 충돌이 1회 발생해 동일 명령을 순차 재실행했다. + - `insertIgnoreAll`은 H2/MySQL dialect 분기 없이 JPA `saveAndFlush`와 unique 제약 기반 중복 예외 재확인 단일 경로로 검증했다.