diff --git a/README.md b/README.md
index efcb2542..b181f241 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
# 개발 및 배포 프로세스 & Git 컨벤션 가이드
해당 프로젝트는 `dev` 브랜치에서 개발하고, `main`브랜치에서 배포합니다.
아래에 브랜치 전략, 커밋/PR 컨벤션, 워크플로우 전략, 브랜치 보호 규칙, 응답 데이터 및 예외처리 전략을 정리하였습니다.
-개발 전에 꼭 읽어봐주세요!
+개발 전에 꼭 읽어봐주세요!
@@ -21,7 +21,7 @@
- 안정화된 코드를 머지하여 배포
- `dev` → `main` PR은 관리자 혹은 릴리즈 담당자만 생성 및 승인 가능
- 직접 push 및 외부 PR 제한
-
+
## 2. 커밋/PR 컨벤션
@@ -153,11 +153,11 @@ public ResponseEntity> handleCustomException(
// 응답 데이터 예시 (json)
{
- "code": ErrorCode.code,
- "message": ErrorCode.message,
- "data": {...} or {null},
- "success": false
-}
+ "code": ErrorCode.code,
+ "message": ErrorCode.message,
+ "data": {...} or {null},
+ "success": false
+ }
```
diff --git a/src/main/java/com/back/domain/studyroom/controller/RoomController.java b/src/main/java/com/back/domain/studyroom/controller/RoomController.java
index f775a04c..5d97e179 100644
--- a/src/main/java/com/back/domain/studyroom/controller/RoomController.java
+++ b/src/main/java/com/back/domain/studyroom/controller/RoomController.java
@@ -5,12 +5,12 @@
import com.back.domain.studyroom.entity.RoomMember;
import com.back.domain.studyroom.service.RoomService;
import com.back.global.common.dto.RsData;
+import com.back.global.security.CurrentUser;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.media.Content;
-import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
@@ -27,16 +27,18 @@
import java.util.stream.Collectors;
/**
- * - 모든 API는 Authorization 헤더 필요 (JWT 토큰)
- * - 현재는 임시로 하드코딩된 사용자 ID 사용
- * - JWT 연동 시 @CurrentUser 애노테이션으로 교체 예정
+ * 스터디 룸 관련 API 컨트롤러
+ * - JWT 인증 필수 (Spring Security + CurrentUser)
+ * - Swagger에서 테스트 시 "Authorize" 버튼으로 토큰 입력
*/
@RestController
@RequestMapping("/api/rooms")
@RequiredArgsConstructor
@Tag(name = "Room API", description = "스터디 룸 관련 API")
+@SecurityRequirement(name = "Bearer Authentication")
public class RoomController {
private final RoomService roomService;
+ private final CurrentUser currentUser;
@PostMapping
@Operation(
@@ -49,10 +51,9 @@ public class RoomController {
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity> createRoom(
- @Valid @RequestBody CreateRoomRequest request,
- @RequestHeader("Authorization") String authorization) {
+ @Valid @RequestBody CreateRoomRequest request) {
- Long currentUserId = 1L; // 임시 하드코딩 - JWT 연동 시 @CurrentUser로 교체
+ Long currentUserId = currentUser.getUserId();
Room room = roomService.createRoom(
request.getTitle(),
@@ -83,10 +84,9 @@ public ResponseEntity> createRoom(
})
public ResponseEntity> joinRoom(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId,
- @RequestBody(required = false) JoinRoomRequest request,
- @RequestHeader("Authorization") String authorization) {
+ @RequestBody(required = false) JoinRoomRequest request) {
- Long currentUserId = 1L; // 임시 하드코딩
+ Long currentUserId = currentUser.getUserId();
String password = null;
if (request != null) {
@@ -112,10 +112,9 @@ public ResponseEntity> joinRoom(
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity> leaveRoom(
- @Parameter(description = "방 ID", required = true) @PathVariable Long roomId,
- @RequestHeader("Authorization") String authorization) {
+ @Parameter(description = "방 ID", required = true) @PathVariable Long roomId) {
- Long currentUserId = 1L; // 임시 하드코딩
+ Long currentUserId = currentUser.getUserId();
roomService.leaveRoom(roomId, currentUserId);
@@ -169,10 +168,9 @@ public ResponseEntity>> getRooms(
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity> getRoomDetail(
- @Parameter(description = "방 ID", required = true) @PathVariable Long roomId,
- @RequestHeader("Authorization") String authorization) {
+ @Parameter(description = "방 ID", required = true) @PathVariable Long roomId) {
- Long currentUserId = 1L; // 임시 하드코딩
+ Long currentUserId = currentUser.getUserId();
Room room = roomService.getRoomDetail(roomId, currentUserId);
List members = roomService.getRoomMembers(roomId, currentUserId);
@@ -197,10 +195,9 @@ public ResponseEntity> getRoomDetail(
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
- public ResponseEntity>> getMyRooms(
- @RequestHeader("Authorization") String authorization) {
+ public ResponseEntity>> getMyRooms() {
- Long currentUserId = 1L; // 임시 하드코딩
+ Long currentUserId = currentUser.getUserId();
List rooms = roomService.getUserRooms(currentUserId);
@@ -230,10 +227,9 @@ public ResponseEntity>> getMyRooms(
})
public ResponseEntity> updateRoom(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId,
- @Valid @RequestBody UpdateRoomSettingsRequest request,
- @RequestHeader("Authorization") String authorization) {
+ @Valid @RequestBody UpdateRoomSettingsRequest request) {
- Long currentUserId = 1L; // 임시 하드코딩
+ Long currentUserId = currentUser.getUserId();
roomService.updateRoomSettings(
roomId,
@@ -263,10 +259,9 @@ public ResponseEntity> updateRoom(
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity> deleteRoom(
- @Parameter(description = "방 ID", required = true) @PathVariable Long roomId,
- @RequestHeader("Authorization") String authorization) {
+ @Parameter(description = "방 ID", required = true) @PathVariable Long roomId) {
- Long currentUserId = 1L; // 임시 하드코딩
+ Long currentUserId = currentUser.getUserId();
roomService.terminateRoom(roomId, currentUserId);
@@ -287,10 +282,9 @@ public ResponseEntity> deleteRoom(
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity>> getRoomMembers(
- @Parameter(description = "방 ID", required = true) @PathVariable Long roomId,
- @RequestHeader("Authorization") String authorization) {
+ @Parameter(description = "방 ID", required = true) @PathVariable Long roomId) {
- Long currentUserId = 1L; // 임시 하드코딩
+ Long currentUserId = currentUser.getUserId();
List members = roomService.getRoomMembers(roomId, currentUserId);
diff --git a/src/main/java/com/back/global/config/SpringDocConfig.java b/src/main/java/com/back/global/config/SpringDocConfig.java
index c75d985d..dba85c9a 100644
--- a/src/main/java/com/back/global/config/SpringDocConfig.java
+++ b/src/main/java/com/back/global/config/SpringDocConfig.java
@@ -1,16 +1,35 @@
package com.back.global.config;
-import io.swagger.v3.oas.annotations.OpenAPIDefinition;
-import io.swagger.v3.oas.annotations.info.Info;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
-@OpenAPIDefinition(info = @Info(title = "Catfe API 서버", version = "beta", description = "Catfe API 문서입니다."))
public class SpringDocConfig {
- // API 버전별 그룹화
+ @Bean
+ public OpenAPI openAPI() {
+ String securitySchemeName = "Bearer Authentication";
+
+ return new OpenAPI()
+ .info(new Info()
+ .title("Catfe API 서버")
+ .version("beta")
+ .description("Catfe API 문서입니다."))
+ .addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
+ .components(new Components()
+ .addSecuritySchemes(securitySchemeName, new SecurityScheme()
+ .name(securitySchemeName)
+ .type(SecurityScheme.Type.HTTP)
+ .scheme("bearer")
+ .bearerFormat("JWT")));
+ }
+
@Bean
public GroupedOpenApi groupApiV1() {
return GroupedOpenApi.builder()
diff --git a/src/main/java/com/back/global/security/SecurityConfig.java b/src/main/java/com/back/global/security/SecurityConfig.java
index 0a20b6c8..b781e8a7 100644
--- a/src/main/java/com/back/global/security/SecurityConfig.java
+++ b/src/main/java/com/back/global/security/SecurityConfig.java
@@ -30,7 +30,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/ws/**").permitAll()
- .requestMatchers("/api/rooms/**").permitAll() // 테스트용 임시 허용
+ .requestMatchers("/api/rooms/*/messages/**").permitAll() //스터디 룸 내에 잡혀있어 있는 채팅 관련 전체 허용
+ //.requestMatchers("/api/rooms/RoomChatApiControllerTest").permitAll() // 테스트용 임시 허용
.requestMatchers("/","/swagger-ui/**", "/v3/api-docs/**").permitAll() // Swagger 허용
.requestMatchers("/h2-console/**").permitAll() // H2 Console 허용
.anyRequest().authenticated()
diff --git a/src/test/java/com/back/domain/studyroom/controller/RoomControllerTest.java b/src/test/java/com/back/domain/studyroom/controller/RoomControllerTest.java
index f7497f40..43d315c7 100644
--- a/src/test/java/com/back/domain/studyroom/controller/RoomControllerTest.java
+++ b/src/test/java/com/back/domain/studyroom/controller/RoomControllerTest.java
@@ -8,6 +8,7 @@
import com.back.domain.user.entity.UserProfile;
import com.back.domain.user.entity.UserStatus;
import com.back.global.common.dto.RsData;
+import com.back.global.security.CurrentUser;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -32,12 +33,15 @@
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
-@DisplayName("RoomController 테스트")
+@DisplayName("RoomController 테스트 - JWT 인증 통합")
class RoomControllerTest {
@Mock
private RoomService roomService;
+ @Mock
+ private CurrentUser currentUser;
+
@InjectMocks
private RoomController roomController;
@@ -75,12 +79,16 @@ void setUp() {
// 테스트 멤버 생성
testMember = RoomMember.createHost(testRoom, testUser);
+
+ // CurrentUser Mock 설정은 각 테스트에서 필요할 때만 설정
}
@Test
- @DisplayName("방 생성 API 테스트")
+ @DisplayName("방 생성 API 테스트 - JWT 인증")
void createRoom() {
// given
+ given(currentUser.getUserId()).willReturn(1L);
+
CreateRoomRequest request = new CreateRoomRequest(
"테스트 방",
"테스트 설명",
@@ -95,59 +103,65 @@ void createRoom() {
anyBoolean(),
any(),
anyInt(),
- anyLong()
+ eq(1L)
)).willReturn(testRoom);
// when
- ResponseEntity> response = roomController.createRoom(request, "Bearer token");
-
+ ResponseEntity> response = roomController.createRoom(request);
// then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().isSuccess()).isTrue();
assertThat(response.getBody().getData().getTitle()).isEqualTo("테스트 방");
-
+
+ verify(currentUser, times(1)).getUserId();
verify(roomService, times(1)).createRoom(
anyString(),
anyString(),
anyBoolean(),
any(),
anyInt(),
- anyLong()
+ eq(1L)
);
}
@Test
- @DisplayName("방 입장 API 테스트")
+ @DisplayName("방 입장 API 테스트 - JWT 인증")
void joinRoom() {
// given
+ given(currentUser.getUserId()).willReturn(1L);
+
JoinRoomRequest request = new JoinRoomRequest(null);
- given(roomService.joinRoom(anyLong(), any(), anyLong())).willReturn(testMember);
+ given(roomService.joinRoom(eq(1L), any(), eq(1L))).willReturn(testMember);
// when
- ResponseEntity> response = roomController.joinRoom(1L, request, "Bearer token");
+ ResponseEntity> response = roomController.joinRoom(1L, request);
// then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().isSuccess()).isTrue();
-
- verify(roomService, times(1)).joinRoom(anyLong(), any(), anyLong());
+
+ verify(currentUser, times(1)).getUserId();
+ verify(roomService, times(1)).joinRoom(eq(1L), any(), eq(1L));
}
@Test
- @DisplayName("방 나가기 API 테스트")
+ @DisplayName("방 나가기 API 테스트 - JWT 인증")
void leaveRoom() {
// given
+ given(currentUser.getUserId()).willReturn(1L);
+
// when
- ResponseEntity> response = roomController.leaveRoom(1L, "Bearer token");
+ ResponseEntity> response = roomController.leaveRoom(1L);
// then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().isSuccess()).isTrue();
-
- verify(roomService, times(1)).leaveRoom(anyLong(), anyLong());
+
+ verify(currentUser, times(1)).getUserId();
+ verify(roomService, times(1)).leaveRoom(eq(1L), eq(1L));
}
@Test
@@ -174,29 +188,34 @@ void getRooms() {
}
@Test
- @DisplayName("방 상세 정보 조회 API 테스트")
+ @DisplayName("방 상세 정보 조회 API 테스트 - JWT 인증")
void getRoomDetail() {
// given
- given(roomService.getRoomDetail(anyLong(), anyLong())).willReturn(testRoom);
- given(roomService.getRoomMembers(anyLong(), anyLong())).willReturn(Arrays.asList(testMember));
+ given(currentUser.getUserId()).willReturn(1L);
+
+ given(roomService.getRoomDetail(eq(1L), eq(1L))).willReturn(testRoom);
+ given(roomService.getRoomMembers(eq(1L), eq(1L))).willReturn(Arrays.asList(testMember));
// when
- ResponseEntity> response = roomController.getRoomDetail(1L, "Bearer token");
+ ResponseEntity> response = roomController.getRoomDetail(1L);
// then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().isSuccess()).isTrue();
assertThat(response.getBody().getData().getTitle()).isEqualTo("테스트 방");
-
- verify(roomService, times(1)).getRoomDetail(anyLong(), anyLong());
- verify(roomService, times(1)).getRoomMembers(anyLong(), anyLong());
+
+ verify(currentUser, times(1)).getUserId();
+ verify(roomService, times(1)).getRoomDetail(eq(1L), eq(1L));
+ verify(roomService, times(1)).getRoomMembers(eq(1L), eq(1L));
}
@Test
- @DisplayName("내 참여 방 목록 조회 API 테스트")
+ @DisplayName("내 참여 방 목록 조회 API 테스트 - JWT 인증")
void getMyRooms() {
// given
+ given(currentUser.getUserId()).willReturn(1L);
+
// Room에 ID 설정 (리플렉션 사용)
try {
java.lang.reflect.Field idField = testRoom.getClass().getSuperclass().getDeclaredField("id");
@@ -205,12 +224,12 @@ void getMyRooms() {
} catch (Exception e) {
throw new RuntimeException(e);
}
-
- given(roomService.getUserRooms(anyLong())).willReturn(Arrays.asList(testRoom));
- given(roomService.getUserRoomRole(eq(1L), anyLong())).willReturn(RoomRole.HOST);
+
+ given(roomService.getUserRooms(eq(1L))).willReturn(Arrays.asList(testRoom));
+ given(roomService.getUserRoomRole(eq(1L), eq(1L))).willReturn(RoomRole.HOST);
// when
- ResponseEntity>> response = roomController.getMyRooms("Bearer token");
+ ResponseEntity>> response = roomController.getMyRooms();
// then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
@@ -218,14 +237,17 @@ void getMyRooms() {
assertThat(response.getBody().isSuccess()).isTrue();
assertThat(response.getBody().getData()).hasSize(1);
assertThat(response.getBody().getData().get(0).getTitle()).isEqualTo("테스트 방");
-
- verify(roomService, times(1)).getUserRooms(anyLong());
+
+ verify(currentUser, times(1)).getUserId();
+ verify(roomService, times(1)).getUserRooms(eq(1L));
}
@Test
- @DisplayName("방 설정 수정 API 테스트")
+ @DisplayName("방 설정 수정 API 테스트 - JWT 인증")
void updateRoom() {
// given
+ given(currentUser.getUserId()).willReturn(1L);
+
UpdateRoomSettingsRequest request = new UpdateRoomSettingsRequest(
"변경된 제목",
"변경된 설명",
@@ -236,48 +258,54 @@ void updateRoom() {
);
// when
- ResponseEntity> response = roomController.updateRoom(1L, request, "Bearer token");
+ ResponseEntity> response = roomController.updateRoom(1L, request);
// then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().isSuccess()).isTrue();
-
+
+ verify(currentUser, times(1)).getUserId();
verify(roomService, times(1)).updateRoomSettings(
- anyLong(),
+ eq(1L),
anyString(),
anyString(),
anyInt(),
anyBoolean(),
anyBoolean(),
anyBoolean(),
- anyLong()
+ eq(1L)
);
}
@Test
- @DisplayName("방 종료 API 테스트")
+ @DisplayName("방 종료 API 테스트 - JWT 인증")
void deleteRoom() {
// given
+ given(currentUser.getUserId()).willReturn(1L);
+
// when
- ResponseEntity> response = roomController.deleteRoom(1L, "Bearer token");
+ ResponseEntity> response = roomController.deleteRoom(1L);
// then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().isSuccess()).isTrue();
-
- verify(roomService, times(1)).terminateRoom(anyLong(), anyLong());
+
+ verify(currentUser, times(1)).getUserId();
+ verify(roomService, times(1)).terminateRoom(eq(1L), eq(1L));
}
@Test
- @DisplayName("방 멤버 목록 조회 API 테스트")
+ @DisplayName("방 멤버 목록 조회 API 테스트 - JWT 인증")
void getRoomMembers() {
// given
- given(roomService.getRoomMembers(anyLong(), anyLong())).willReturn(Arrays.asList(testMember));
+ given(currentUser.getUserId()).willReturn(1L);
+
+ given(roomService.getRoomMembers(eq(1L), eq(1L))).willReturn(Arrays.asList(testMember));
// when
- ResponseEntity>> response = roomController.getRoomMembers(1L, "Bearer token");
+ ResponseEntity>> response = roomController.getRoomMembers(1L);
// then
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
@@ -285,8 +313,9 @@ void getRoomMembers() {
assertThat(response.getBody().isSuccess()).isTrue();
assertThat(response.getBody().getData()).hasSize(1);
assertThat(response.getBody().getData().get(0).getNickname()).isEqualTo("테스트유저");
-
- verify(roomService, times(1)).getRoomMembers(anyLong(), anyLong());
+
+ verify(currentUser, times(1)).getUserId();
+ verify(roomService, times(1)).getRoomMembers(eq(1L), eq(1L));
}
@Test