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