Conversation
Walkthrough그룹 참석 취소 기능을 구현합니다. GroupService에 cancelAttendGroup 메서드를 추가하고, 호스트 탈퇴 금지 및 미참석 회원 검증 에러코드 2개를 등록합니다. 컨트롤러 엔드포인트와 통합 테스트 시나리오를 함께 추가합니다. Changes
Sequence DiagramsequenceDiagram
participant Client
participant Controller as GroupController
participant Service as GroupService
participant Repo as Repository<br/>(Group, GroupUser)
participant DB as Database
Client->>Controller: POST /cancel<br/>groupId, userId
Controller->>Service: cancelAttendGroup(groupId, userId)
Service->>Repo: loadGroup(groupId)<br/>excludeSoftDeleted
Repo->>DB: Query Group
DB-->>Repo: Group Entity
Repo-->>Service: Group
Service->>Repo: loadMember(memberId)
Repo->>DB: Query Member
DB-->>Repo: Member Entity
Repo-->>Service: Member
Service->>Repo: findGroupUser(group, member)
Repo->>DB: Query GroupUser
DB-->>Repo: GroupUser
Repo-->>Service: GroupUser
Note over Service: Validate:<br/>1. Not HOST<br/>2. Status ≠ LEFT
alt Validation Fails
Service-->>Controller: Throw Exception<br/>(HOST_CANNOT_LEAVE_OWN_GROUP<br/>or NOT_ATTEND_GROUP)
Controller-->>Client: 400 Bad Request
else Validation Passes
Service->>Repo: leave() on GroupUser
Repo->>DB: Update GroupUser Status
DB-->>Repo: Success
Service->>Repo: buildGetGroupResponse(group)
Repo-->>Service: GetGroupResponse
Service-->>Controller: GetGroupResponse
Controller-->>Client: 200 OK<br/>+ GetGroupResponse
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
추가 주의사항:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR implements group attendance cancellation functionality, allowing members to leave groups they have joined. The implementation includes proper validation to prevent hosts from leaving their own groups and ensures members can only cancel if they are currently attending.
- Added
cancelAttendGroupendpoint and service method with business rule validation - Introduced two new error codes for handling cancellation edge cases
- Created HTTP test file for manual testing of the cancellation flow
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/main/java/team/wego/wegobackend/group/presentation/GroupController.java |
Added /cancel POST endpoint to handle group attendance cancellation requests |
src/main/java/team/wego/wegobackend/group/application/service/GroupService.java |
Implemented cancelAttendGroup method with validation for HOST restriction and attendance status |
src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java |
Added error codes for non-attending members and HOST leave prevention |
src/test/http/group/cancel.http |
Added manual test scenarios for the cancellation feature with member registration and login flows |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| > {% | ||
| // 응답 구조에 따라 수정 필요 | ||
| // 예: { "userId": 1, ... } 라면 아래처럼 유지 | ||
| client.global.set("memberId1", response.body.data.userId); |
There was a problem hiding this comment.
The variable name should be memberId2 instead of memberId1 for MEMBER 2. This overwrites the previously set memberId1 value from line 109.
| client.global.set("memberId1", response.body.data.userId); | |
| client.global.set("memberId2", response.body.data.userId); |
| Content-Type: application/json | ||
|
|
||
| { | ||
| "email": "test1@example.com", |
There was a problem hiding this comment.
The login credentials are incorrect. This should use test2@example.com to match the MEMBER 2 account created in line 140, not test1@example.com.
| "email": "test1@example.com", | |
| "email": "test2@example.com", |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/main/java/team/wego/wegobackend/group/presentation/GroupController.java (1)
50-53: 모임 취소 엔드포인트 구현 방향 적절함
attendGroup와 동일한 패턴으로cancelAttendGroup을 구성해서 응답 형태와 상태코드가 일관적이고, 서비스 레이어 위임도 명확합니다. 지금은userId를 쿼리 파라미터로 받되 TODO로 인증 연동을 명시해 둔 것도 괜찮아 보입니다. 추후 인증 정보를 붙일 때에는 토큰의 사용자 ID와userId파라미터 불일치에 대한 방어 로직까지 같이 고려해 주시면 좋겠습니다.Also applies to: 56-68
src/main/java/team/wego/wegobackend/group/application/service/GroupService.java (1)
247-276: 참석 취소 도메인 로직 일관적이며 예외 처리도 적절합니다모임/회원 조회 →
GroupUser조회 → HOST 방지 → 이미 LEFT 상태 방지 →leave()후 최신 상세 응답 반환까지 플로우가attendGroup과 대칭적으로 잘 잡혀 있습니다. 예외 코드도NOT_ATTEND_GROUP/HOST_CANNOT_LEAVE_OWN_GROUP와 정확히 매핑되어 이해하기 쉽습니다.추가로, 나중에 정리할 때
attendGroup/cancelAttendGroup에서 공통으로 사용하는 그룹·회원·GroupUser 조회 부분을 private 메서드로 추출하면 중복이 줄어서 유지보수에 더 좋을 것 같습니다.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/main/java/team/wego/wegobackend/group/application/service/GroupService.java(1 hunks)src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java(1 hunks)src/main/java/team/wego/wegobackend/group/presentation/GroupController.java(1 hunks)src/test/http/group/cancel.http(1 hunks)
⏰ 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: CodeQL analysis (java)
- GitHub Check: Agent
🔇 Additional comments (1)
src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java (1)
22-24: 취소 플로우용 에러 코드 정의가 서비스 구현과 잘 맞습니다
NOT_ATTEND_GROUP,HOST_CANNOT_LEAVE_OWN_GROUP모두 메시지와 포맷 인자(모임 ID, 회원 ID)가cancelAttendGroup에서 던지는 예외와 정확히 대응하고 있어 도메인 규칙 표현이 명확합니다. 추가 수정 필요 없어 보입니다.
| ### 회원가입(MEMBER 1) | ||
| POST http://localhost:8080/api/v1/auth/signup | ||
| Content-Type: application/json | ||
|
|
||
| { | ||
| "email": "test1@example.com", | ||
| "password": "Test1234!@#", | ||
| "nickName": "Heemo", | ||
| "phoneNumber": "010-1234-5678" | ||
| } | ||
|
|
||
| > {% | ||
| // 응답 구조에 따라 수정 필요 | ||
| // 예: { "userId": 1, ... } 라면 아래처럼 유지 | ||
| client.global.set("memberId1", response.body.data.userId); | ||
| %} | ||
|
|
||
| ### 로그인 (MEMBER 1) | ||
| POST http://localhost:8080/api/v1/auth/login | ||
| Content-Type: application/json | ||
|
|
||
| { | ||
| "email": "test1@example.com", | ||
| "password": "Test1234!@#" | ||
| } | ||
|
|
||
| > {% | ||
| client.global.set("accessTokenByMember1", response.body.data.accessToken); | ||
| %} | ||
|
|
||
| ### 모임 참여 (MEMBER 1) | ||
| POST http://localhost:8080/api/v1/groups/{{groupId_png_jpg}}/attend | ||
| ?userId=2 | ||
| Content-Type: application/json | ||
| Authorization: Bearer {{accessTokenByMember1}} | ||
|
|
||
| { | ||
|
|
||
| } | ||
|
|
||
| ### 회원가입(MEMBER 2) | ||
| POST http://localhost:8080/api/v1/auth/signup | ||
| Content-Type: application/json | ||
|
|
||
| { | ||
| "email": "test2@example.com", | ||
| "password": "Test1234!@#", | ||
| "nickName": "Qeemo", | ||
| "phoneNumber": "010-1234-5678" | ||
| } | ||
|
|
||
| > {% | ||
| // 응답 구조에 따라 수정 필요 | ||
| // 예: { "userId": 1, ... } 라면 아래처럼 유지 | ||
| client.global.set("memberId1", response.body.data.userId); | ||
| %} | ||
|
|
||
| ### 로그인 (MEMBER 2) | ||
| POST http://localhost:8080/api/v1/auth/login | ||
| Content-Type: application/json | ||
|
|
||
| { | ||
| "email": "test1@example.com", | ||
| "password": "Test1234!@#" | ||
| } | ||
|
|
||
| > {% | ||
| client.global.set("accessTokenByMember2", response.body.data.accessToken); | ||
| %} | ||
|
|
||
| ### 모임 참여 (MEMBER 2) | ||
| POST http://localhost:8080/api/v1/groups/{{groupId_png_jpg}}/attend | ||
| ?userId=3 | ||
| Content-Type: application/json | ||
| Authorization: Bearer {{accessTokenByMember2}} | ||
|
|
||
| { | ||
|
|
||
| } | ||
|
|
||
| ### 모임 취소 (MEMBER 1) | ||
| POST http://localhost:8080/api/v1/groups/{{groupId_png_jpg}}/cancel | ||
| ?userId=2 | ||
| Content-Type: application/json | ||
| Authorization: Bearer {{accessTokenByMember1}} | ||
|
|
||
| { | ||
|
|
||
| } No newline at end of file |
There was a problem hiding this comment.
MEMBER 1/2 시나리오의 userId 및 로그인 정보 불일치로 테스트가 신뢰하기 어려운 상태입니다
모임 참석/취소 플로우 쪽에 몇 가지 문제가 있습니다.
- MEMBER 1 참석 요청에서
?userId=2를 하드코딩하고 있어, 실제로 발급된memberId1과 DB 상태(선행 데이터)에 따라 불일치할 수 있습니다. - MEMBER 2 가입 후에도 전역 변수 이름을
memberId1로 다시 덮어쓰고 있어, MEMBER 1/2 ID가 섞입니다. - MEMBER 2 로그인 요청이
email: "test1@example.com"으로 MEMBER 1 계정을 다시 로그인하고 있습니다. - MEMBER 2 참석, MEMBER 1 취소 요청의
userId도 각각3,2로 하드코딩되어 있어, 실제 응답 값과 동기화되지 않습니다.
지금 상태로는 “어떤 유저가 어떤 토큰과 어떤 userId로 참석/취소하는지”가 꼬일 수 있어서, 새로 구현한 취소 기능을 검증하기 어렵습니다. 아래처럼 응답에서 받은 ID를 실제로 사용하도록 바꾸는 것을 제안드립니다.
### 모임 참여 (MEMBER 1)
POST http://localhost:8080/api/v1/groups/{{groupId_png_jpg}}/attend
- ?userId=2
+ ?userId={{memberId1}}
Content-Type: application/json
Authorization: Bearer {{accessTokenByMember1}}
@@
### 회원가입(MEMBER 2)
@@
- client.global.set("memberId1", response.body.data.userId);
+ client.global.set("memberId2", response.body.data.userId);
@@
### 로그인 (MEMBER 2)
@@
{
- "email": "test1@example.com",
+ "email": "test2@example.com",
"password": "Test1234!@#"
}
@@
### 모임 참여 (MEMBER 2)
POST http://localhost:8080/api/v1/groups/{{groupId_png_jpg}}/attend
- ?userId=3
+ ?userId={{memberId2}}
@@
### 모임 취소 (MEMBER 1)
POST http://localhost:8080/api/v1/groups/{{groupId_png_jpg}}/cancel
- ?userId=2
+ ?userId={{memberId1}}
Content-Type: application/json
Authorization: Bearer {{accessTokenByMember1}}이렇게 수정하면 각 요청이 “해당 토큰으로 로그인한 실제 회원 ID”와 일치하게 되어, 참석/취소 로직을 더 정확하게 검증할 수 있습니다.
🤖 Prompt for AI Agents
In src/test/http/group/cancel.http around lines 95-183, the test hardcodes
userId values and reuses/overwrites global variables and login info causing
member IDs and tokens to be mismatched; change the script to store distinct
globals (e.g., memberId1 and memberId2) from each signup response, correct
MEMBER 2 login to use test2@example.com and store its accessToken into
accessTokenByMember2, and replace every hardcoded ?userId=2/3 with the
corresponding saved variable (use the memberIdX globals) so each attend/cancel
request uses the token and userId that were actually returned by the
signup/login responses.
📝 Pull Request
📌 PR 종류
해당하는 항목에 체크해주세요.
✨ 변경 내용
모임 참여 취소를 구현합니다.
🔍 관련 이슈
🧪 테스트
변경된 기능에 대한 테스트 범위 또는 테스트 결과를 작성해주세요.
🚨 확인해야 할 사항 (Checklist)
PR을 제출하기 전에 아래 항목들을 확인해주세요.
🙋 기타 참고 사항
리뷰어가 참고하면 좋을 만한 추가 설명이 있다면 적어주세요.
Summary by CodeRabbit
릴리스 노트
✏️ Tip: You can customize this high-level summary in your review settings.