Conversation
feat: 유저 삭제 테스트 api 구현
WalkthroughAdds a DELETE /api/v1/test/user endpoint that triggers cascading user deletion. Introduces new deletion ports and services for articles, categories, push subscriptions, and users. Changes remind-articles retrieval to use an explicit start/end datetime range. Adds repository methods to support bulk deletions by user. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Client
participant API as TestController
participant App as TestUsecase
participant Art as ArticleDeleteServicePort
participant Cat as CategoryDeleteServicePort
participant Push as PushSubscriptionDeleteServicePort
participant User as UserDeleteServicePort
rect rgb(230,245,255)
Client->>API: DELETE /api/v1/test/user
API->>App: deleteUser(currentUser)
App->>Art: deleteAllByUser(user)
App->>Cat: deleteAllByUser(user)
App->>Push: deleteByUser(user)
App->>User: delete(user)
App-->>API: void
API-->>Client: 200 OK (ResponseDto<Void>)
end
sequenceDiagram
autonumber
actor Client
participant App as GetArticleUsecase
participant Svc as ArticleGetServicePort
rect rgb(240,255,230)
Client->>App: getRemindArticles(user, date, pageable, isRead)
App->>App: compute startOfDay / endOfDay
App->>Svc: findTodayRemindWithCount(user, startOfDay, endOfDay, pageable, isRead)
Svc-->>App: RemindArticlesWithCountDto
App-->>Client: RemindArticlesWithCountDto
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings, 1 inconclusive)
✨ Finishing touches
🧪 Generate unit tests
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
application/src/main/java/com/pinback/application/article/usecase/query/GetArticleUsecase.java (1)
123-131: Remove unused helper methodgetRemindDateTimein GetArticleUsecase; it’s never referenced—delete its definition (lines 123–131).infrastructure/src/main/java/com/pinback/infrastructure/article/repository/ArticleRepository.java (1)
24-25: LGTM. Article entity only has LAZY @manytoone relations—no cascade concerns. For large user article sets, derived deletes load entities into memory; consider a bulk DELETE JPQL:+ @Modifying + @Query("DELETE FROM Article a WHERE a.user = :user") + void deleteAllByUser(@Param("user") User user); - void deleteAllByUser(User user);infrastructure/src/main/java/com/pinback/infrastructure/notification/repository/PushSubscriptionRepository.java (1)
22-24: Consider standardizing deletion method patterns.The repository now has two deletion methods:
deleteByUserId(UUID userId)anddeleteByUser(User user). While both are valid, consider whether one pattern should be preferred consistently across repositories to improve maintainability.api/src/main/java/com/pinback/api/test/controller/TestController.java (1)
65-71: Add error handling for cascade deletion failures.The endpoint always returns
ResponseDto.ok()without handling potential failures during cascading deletions. If any deletion step fails (articles, categories, push subscriptions, or the user itself), the response should reflect the error state.Consider wrapping the
testPort.deleteUser(user)call with appropriate exception handling or allowing exceptions to propagate to a global exception handler.infrastructure/src/main/java/com/pinback/infrastructure/article/service/ArticleGetService.java (1)
83-92: LGTM! Consider updating the related method for consistency.The change to accept explicit
startDateTimeandendDateTimeparameters is cleaner and gives callers more control over the query range.However, note that
findTodayRemind(lines 77-80) still calculates the date range internally usingremindDateTime.plusDays(1). Consider updating it to match this pattern for consistency.Apply this diff to align
findTodayRemindwith the new pattern:@Override -public Page<Article> findTodayRemind(User user, LocalDateTime remindDateTime, Pageable pageable, Boolean isRead) { - return articleRepository.findTodayRemind(user.getId(), pageable, remindDateTime, remindDateTime.plusDays(1), +public Page<Article> findTodayRemind(User user, LocalDateTime startDateTime, LocalDateTime endDateTime, Pageable pageable, Boolean isRead) { + return articleRepository.findTodayRemind(user.getId(), pageable, startDateTime, endDateTime, isRead); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
api/src/main/java/com/pinback/api/test/controller/TestController.java(1 hunks)application/src/main/java/com/pinback/application/article/port/out/ArticleDeleteServicePort.java(1 hunks)application/src/main/java/com/pinback/application/article/port/out/ArticleGetServicePort.java(1 hunks)application/src/main/java/com/pinback/application/article/usecase/query/GetArticleUsecase.java(1 hunks)application/src/main/java/com/pinback/application/category/port/out/CategoryDeleteServicePort.java(1 hunks)application/src/main/java/com/pinback/application/notification/port/out/PushSubscriptionDeleteServicePort.java(1 hunks)application/src/main/java/com/pinback/application/test/port/in/TestPort.java(1 hunks)application/src/main/java/com/pinback/application/test/usecase/TestUsecase.java(3 hunks)application/src/main/java/com/pinback/application/user/port/out/UserDeleteServicePort.java(1 hunks)infrastructure/src/main/java/com/pinback/infrastructure/article/repository/ArticleRepository.java(1 hunks)infrastructure/src/main/java/com/pinback/infrastructure/article/service/ArticleDeleteService.java(1 hunks)infrastructure/src/main/java/com/pinback/infrastructure/article/service/ArticleGetService.java(1 hunks)infrastructure/src/main/java/com/pinback/infrastructure/category/repository/CategoryRepository.java(1 hunks)infrastructure/src/main/java/com/pinback/infrastructure/category/service/CategoryDeleteService.java(2 hunks)infrastructure/src/main/java/com/pinback/infrastructure/notification/repository/PushSubscriptionRepository.java(1 hunks)infrastructure/src/main/java/com/pinback/infrastructure/notification/service/PushSubscriptionDeleteService.java(1 hunks)infrastructure/src/main/java/com/pinback/infrastructure/user/service/UserDeleteService.java(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
infrastructure/src/main/java/com/pinback/infrastructure/user/service/UserDeleteService.java (4)
application/src/main/java/com/pinback/application/test/usecase/TestUsecase.java (1)
Service(31-127)infrastructure/src/main/java/com/pinback/infrastructure/article/service/ArticleDeleteService.java (1)
Service(13-34)infrastructure/src/main/java/com/pinback/infrastructure/category/service/CategoryDeleteService.java (1)
Service(13-29)infrastructure/src/main/java/com/pinback/infrastructure/notification/service/PushSubscriptionDeleteService.java (1)
Service(12-22)
infrastructure/src/main/java/com/pinback/infrastructure/notification/service/PushSubscriptionDeleteService.java (4)
application/src/main/java/com/pinback/application/test/usecase/TestUsecase.java (1)
Service(31-127)infrastructure/src/main/java/com/pinback/infrastructure/article/service/ArticleDeleteService.java (1)
Service(13-34)infrastructure/src/main/java/com/pinback/infrastructure/category/service/CategoryDeleteService.java (1)
Service(13-29)infrastructure/src/main/java/com/pinback/infrastructure/user/service/UserDeleteService.java (1)
Service(12-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build (21)
🔇 Additional comments (16)
application/src/main/java/com/pinback/application/article/usecase/query/GetArticleUsecase.java (1)
105-110: LGTM! Date-range logic correctly replaces single datetime.The refactored logic correctly computes
startOfDayandendOfDayfrom the providednowtimestamp and passes the range to the service port. The nanosecond precision onendOfDayensures inclusive end-of-day coverage.application/src/main/java/com/pinback/application/test/port/in/TestPort.java (1)
15-16: LGTM! New test port method for cascading user deletion.The
deleteUser(User user)method addition is clean and aligns with the cascading deletion flow described in the AI summary (articles, categories, push subscriptions, then user).application/src/main/java/com/pinback/application/category/port/out/CategoryDeleteServicePort.java (2)
4-4: LGTM! Import added for User type.
8-9: LGTM! Bulk-delete method for categories by user.The
deleteAllByUser(User user)method addition mirrors the pattern inArticleDeleteServicePortand supports the cascading deletion flow.infrastructure/src/main/java/com/pinback/infrastructure/article/service/ArticleDeleteService.java (1)
29-33: LGTM! Bulk-delete implementation correctly delegates to repository.The
deleteAllByUser(User user)implementation follows the established pattern and correctly delegates toarticleRepository.deleteAllByUser(user). Transactional behavior is inherited from the class-level@Transactionalannotation.application/src/main/java/com/pinback/application/article/port/out/ArticleDeleteServicePort.java (1)
10-11: LGTM! Bulk-delete port method for articles by user.The
deleteAllByUser(User user)method addition is clean and aligns with the implementation inArticleDeleteService.infrastructure/src/main/java/com/pinback/infrastructure/category/service/CategoryDeleteService.java (2)
8-8: LGTM! Import added for User type.
24-28: LGTM! Bulk-delete implementation correctly delegates to repository.The
deleteAllByUser(User user)implementation mirrors the pattern inArticleDeleteServiceand correctly delegates tocategoryRepository.deleteAllByUser(user). Transactional behavior is inherited from the class-level@Transactionalannotation.application/src/main/java/com/pinback/application/article/port/out/ArticleGetServicePort.java (1)
35-36: LGTM! Confirm removal of the deprecatedfindTodayRemindWithCount(User, LocalDateTime remindDateTime, Pageable)signature from all implementations and ensure no callers remain.application/src/main/java/com/pinback/application/user/port/out/UserDeleteServicePort.java (1)
1-7: LGTM!Clean port interface following hexagonal architecture principles. The method signature is straightforward and enables user deletion through the application layer.
application/src/main/java/com/pinback/application/notification/port/out/PushSubscriptionDeleteServicePort.java (1)
1-7: LGTM!Clean port interface consistent with the cascading deletion architecture. The method signature enables deletion of push subscriptions by user.
infrastructure/src/main/java/com/pinback/infrastructure/notification/service/PushSubscriptionDeleteService.java (1)
12-22: LGTM!Clean implementation following the standard service pattern. The
@Transactionalannotation ensures atomic deletion, and the delegation to the repository is straightforward.infrastructure/src/main/java/com/pinback/infrastructure/user/service/UserDeleteService.java (1)
18-21: LGTM!Clean implementation with proper transactional handling. The use of
deleteByIdis appropriate for the cascading deletion flow.infrastructure/src/main/java/com/pinback/infrastructure/category/repository/CategoryRepository.java (1)
18-18: Verify cascade configuration before bulk deletion.
Ensure theCategoryentity declares a@OneToManytoArticlewith appropriatecascade/orphanRemovalsettings or that dependentArticlerecords are removed beforehand to prevent foreign-key constraint violations.application/src/main/java/com/pinback/application/test/usecase/TestUsecase.java (2)
14-14: LGTM! Proper dependency injection setup.The new service port imports and field declarations follow Spring's dependency injection best practices and are necessary for the cascading user deletion flow.
Also applies to: 17-17, 22-22, 42-44
122-125: No changes needed for deletion order. Verified Article → Category → PushSubscription → User respects all FK relationships and avoids constraint violations.
| @DeleteMapping("/user") | ||
| public ResponseDto<Void> deleteUser( | ||
| @Parameter(hidden = true) @CurrentUser User user | ||
| ) { | ||
| testPort.deleteUser(user); | ||
| return ResponseDto.ok(); | ||
| } |
There was a problem hiding this comment.
❓ Verification inconclusive
Verify production environment protection for test endpoints.
This DELETE endpoint exposes user deletion functionality through a test controller. Ensure that test endpoints are properly disabled or protected in production environments to prevent accidental or malicious data loss.
Run the following script to check for environment-based test endpoint protection:
🏁 Script executed:
#!/bin/bash
# Description: Check for test endpoint protection in production
# Check for Spring profiles or conditional beans on TestController
rg -nP --type=java -B5 'class TestController' | rg -P '@Profile|@ConditionalOn|@ActiveProfiles'
# Check application configuration for test endpoint controls
fd -e yml -e yaml -e properties | xargs rg -l 'test.*endpoint|test.*controller' Length of output: 0
Enforce production protection for TestController
The DELETE /user endpoint in api/src/main/java/com/pinback/api/test/controller/TestController.java is always enabled—add a conditional annotation (e.g., @Profile("test") or @ConditionalOnProperty) or otherwise disable this controller in non-test environments.
🤖 Prompt for AI Agents
In api/src/main/java/com/pinback/api/test/controller/TestController.java around
lines 65 to 71, the DELETE /user endpoint is exposed in all environments;
restrict it to test environments by annotating the controller (or the specific
method) with a test-only condition such as @Profile("test") or
@ConditionalOnProperty(name="app.enableTestControllers", havingValue="true",
matchIfMissing=false); add the chosen annotation to the controller class (or to
the deleteUser method) and update application configuration or docs to ensure
the property/profile is only active in tests.
| @Override | ||
| public void deleteUser(User user) { | ||
| User getUser = userGetServicePort.findById(user.getId()); | ||
| articleDeleteServicePort.deleteAllByUser(getUser); | ||
| categoryDeleteServicePort.deleteAllByUser(getUser); | ||
| pushSubscriptionDeleteServicePort.deleteByUser(getUser); | ||
| userDeleteServicePort.delete(getUser); | ||
| } |
There was a problem hiding this comment.
Add @transactional to ensure atomicity of cascading deletions.
The deleteUser method performs multiple deletion operations without explicit transaction management. If any deletion fails partway through (e.g., after articles are deleted but before categories), the data will be left in an inconsistent state with no rollback.
Apply this diff to wrap the entire cascade in a transaction:
+ @Transactional
@Override
public void deleteUser(User user) {
User getUser = userGetServicePort.findById(user.getId());
articleDeleteServicePort.deleteAllByUser(getUser);
categoryDeleteServicePort.deleteAllByUser(getUser);
pushSubscriptionDeleteServicePort.deleteByUser(getUser);
userDeleteServicePort.delete(getUser);
}Additionally, import the annotation at the top of the file:
+import org.springframework.transaction.annotation.Transactional;📝 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.
| @Override | |
| public void deleteUser(User user) { | |
| User getUser = userGetServicePort.findById(user.getId()); | |
| articleDeleteServicePort.deleteAllByUser(getUser); | |
| categoryDeleteServicePort.deleteAllByUser(getUser); | |
| pushSubscriptionDeleteServicePort.deleteByUser(getUser); | |
| userDeleteServicePort.delete(getUser); | |
| } | |
| // add this import alongside your other imports | |
| import org.springframework.transaction.annotation.Transactional; | |
| @Transactional | |
| @Override | |
| public void deleteUser(User user) { | |
| User getUser = userGetServicePort.findById(user.getId()); | |
| articleDeleteServicePort.deleteAllByUser(getUser); | |
| categoryDeleteServicePort.deleteAllByUser(getUser); | |
| pushSubscriptionDeleteServicePort.deleteByUser(getUser); | |
| userDeleteServicePort.delete(getUser); | |
| } |
🤖 Prompt for AI Agents
In
application/src/main/java/com/pinback/application/test/usecase/TestUsecase.java
around lines 119 to 126, the deleteUser method performs multiple deletions
without transaction boundaries; annotate the method with @Transactional to
ensure the cascade is atomic and will rollback on failure, and add the import
for the annotation (org.springframework.transaction.annotation.Transactional) at
the top of the file; no other logic changes needed.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
infrastructure/src/test/java/com/pinback/infrastructure/article/service/ArticleGetServiceTest.java (1)
264-265: Consider the necessity of nanosecond precision.All three tests use
LocalDateTime.of(2025, 8, 18, 23, 59, 59, 999999999)for the end-of-day boundary. While this provides maximum precision, consider whether nanosecond-level accuracy is required for your use case. If article timestamps are stored with lower precision (e.g., seconds or milliseconds), you might simplify toLocalDateTime.of(date, LocalTime.MAX)or even usedate.plusDays(1).atStartOfDay()as an exclusive upper bound.If simplification is desired, consider this approach:
-LocalDateTime endOfDay = LocalDateTime.of(2025, 8, 18, 23, 59, 59, 999999999); +LocalDateTime endOfDay = LocalDateTime.of(2025, 8, 18, 0, 0).plusDays(1);Then update the repository query to use
<instead of<=for the upper bound. This avoids potential precision issues and is more idiomatic.Also applies to: 293-294, 311-312
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
application/src/test/java/com/pinback/application/article/usecase/query/GetArticleUsecaseTest.java(4 hunks)infrastructure/src/test/java/com/pinback/infrastructure/article/service/ArticleGetServiceTest.java(3 hunks)
👮 Files not reviewed due to content moderation or server errors (2)
- application/src/test/java/com/pinback/application/article/usecase/query/GetArticleUsecaseTest.java
- infrastructure/src/test/java/com/pinback/infrastructure/article/service/ArticleGetServiceTest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: build (21)
- GitHub Check: build (21)
🔇 Additional comments (22)
application/src/test/java/com/pinback/application/article/usecase/query/GetArticleUsecaseTest.java (12)
185-186: Approve the full-day window approach.The refactor from a single
remindDateTimeto an explicitstartOfDayandendOfDayrange improves clarity and correctly covers the entire day.
214-215: Consistent implementation across test cases.The unread status test uses the same date-range pattern as the read status test, ensuring consistent coverage.
223-223: Mock verification matches expectations.The verification calls correctly match the mock setup with
startOfDayandendOfDayparameters for both read and unread scenarios.Also applies to: 232-232
194-194: Inclusive boundary handling confirmedThe repository uses
remindAt.goe(startAt).and(remindAt.loe(endAt)), ensuring both start and end are inclusive—no further changes needed.
177-204: LGTM! Test correctly updated to use full-day time window.The test now uses explicit
startOfDayandendOfDayboundaries, properly bracketing the entire day (00:00:00 to 23:59:59.999999999). Mock setup and verification are consistent with the updated service signature.
206-233: LGTM! Test correctly updated to use full-day time window.The test mirrors the read-status scenario with correct day boundaries and mock expectations. The pattern is consistent across both test cases.
185-186: LGTM! Full-day window correctly defined.The startOfDay and endOfDay variables correctly define a complete 24-hour window for the reminder query, with endOfDay set to the last nanosecond of the day.
194-194: LGTM! Mock setup correctly uses date range.The mock setup properly reflects the updated method signature with startOfDay and endOfDay parameters.
203-203: LGTM! Verification matches mock setup.The verification correctly confirms the service was called with the expected date range parameters.
214-215: LGTM! Consistent with ReadStatus test.The date range definition follows the same pattern as the previous test method, ensuring consistency across test cases.
223-223: LGTM! Mock correctly configured for unread status.The mock setup properly uses the date range with the false parameter for unread articles.
232-232: LGTM! Verification aligns with test intent.The verification correctly confirms the service method was called with the date range and unread status parameter.
infrastructure/src/test/java/com/pinback/infrastructure/article/service/ArticleGetServiceTest.java (10)
262-265: Good boundary testing with yesterday's article.The test correctly verifies that articles from the previous day are excluded from the count by creating a
yesterdayarticle (line 261) and then using the full-day range for today. This ensures the date-range filtering works as expected.
268-268: Service call updated correctly for read status test.The service invocation properly uses
startOfDayandendOfDayinstead of a single timestamp, matching the updated signature.
293-294: Consistent date-range usage in unread status test.The unread status test follows the same pattern as the read status test, using the full-day window approach consistently.
Also applies to: 297-297
311-312: Edge case coverage with no articles.The test verifies behavior when no articles exist within the date range, ensuring the counts return 0. This is important for preventing null-pointer or empty-result issues.
Also applies to: 316-316
244-275: LGTM! Test correctly implements full-day range for read articles.The test properly uses
startOfDay(00:00) andendOfDay(23:59:59.999999999) to cover the entire day. The yesterday article is correctly excluded from the count, validating that the date range works as intended.
277-304: LGTM! Test correctly implements full-day range for unread articles.Consistent with the read-status test, properly using day boundaries and verifying counts. The pattern is correct across both read/unread scenarios.
306-322: LGTM! Edge case properly tested with no articles.The test correctly verifies that when no articles exist within the day range, all counts return zero. This validates the boundary behavior of the updated service.
262-268: LGTM! Date range correctly filters today's reminders.The startOfDay and endOfDay variables properly define the query window for 2025-08-18, ensuring that only today's reminders are retrieved (excluding yesterday's article at line 261).
291-297: LGTM! Unread status test correctly configured.The date range and service call properly test the unread articles scenario with the correct parameters.
311-316: LGTM! Edge case for empty results handled correctly.The test properly verifies the service behavior when no articles exist in the specified date range, ensuring zero counts are returned.
🚀 PR 요약
HOTFIX내용을 메인으로 머지합니다
✨ PR 상세 내용
조회 조건 수정했어요
🚨 주의 사항
없습니다.
✅ 체크 리스트
Summary by CodeRabbit