diff --git a/Dockerfile b/Dockerfile index 4f3db3cd..c57f141d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,3 @@ FROM eclipse-temurin:17-jdk-alpine - -# Redis 설치 -RUN apk add --no-cache redis - COPY ./build/libs/*SNAPSHOT.jar project.jar -ENTRYPOINT redis-server & java -jar project.jar \ No newline at end of file +ENTRYPOINT ["java", "-jar", "project.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index ebf2d2d8..2e3c9d44 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-data-redis' // Lombok compileOnly 'org.projectlombok:lombok' diff --git a/src/main/java/com/example/mate/common/config/SecurityConfig.java b/src/main/java/com/example/mate/common/config/SecurityConfig.java index 507ef6c8..0d296f01 100644 --- a/src/main/java/com/example/mate/common/config/SecurityConfig.java +++ b/src/main/java/com/example/mate/common/config/SecurityConfig.java @@ -1,5 +1,7 @@ package com.example.mate.common.config; +//import com.example.mate.common.security.filter.JwtCheckFilter; + import com.example.mate.common.security.filter.JwtCheckFilter; import java.util.List; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/example/mate/common/config/SwaggerConfig.java b/src/main/java/com/example/mate/common/config/SwaggerConfig.java index b761482f..862b8e76 100644 --- a/src/main/java/com/example/mate/common/config/SwaggerConfig.java +++ b/src/main/java/com/example/mate/common/config/SwaggerConfig.java @@ -5,9 +5,12 @@ 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 io.swagger.v3.oas.models.servers.Server; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.util.List; + @Configuration public class SwaggerConfig { @Bean @@ -19,6 +22,7 @@ public OpenAPI openAPI() { return new OpenAPI() .info(info) + .servers(List.of(new Server().url("http://localhost:8080"))) .addSecurityItem(new SecurityRequirement().addList("Bearer Authentication")) .components(new Components().addSecuritySchemes("Bearer Authentication", createAPIKeyScheme())); } diff --git a/src/main/java/com/example/mate/common/config/WebMvcConfig.java b/src/main/java/com/example/mate/common/config/WebMvcConfig.java deleted file mode 100644 index 49532ab8..00000000 --- a/src/main/java/com/example/mate/common/config/WebMvcConfig.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.example.mate.common.config; - -import com.example.mate.common.validator.ValidPageableArgumentResolver; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -import java.util.List; - -@Configuration -public class WebMvcConfig implements WebMvcConfigurer { - - private final ValidPageableArgumentResolver validPageableArgumentResolver; - - public WebMvcConfig(ValidPageableArgumentResolver validPageableArgumentResolver) { - this.validPageableArgumentResolver = validPageableArgumentResolver; - } - - @Override - public void addArgumentResolvers(List resolvers) { - resolvers.add(validPageableArgumentResolver); - } -} diff --git a/src/main/java/com/example/mate/common/error/ErrorCode.java b/src/main/java/com/example/mate/common/error/ErrorCode.java index 86a3de47..dad30a4e 100644 --- a/src/main/java/com/example/mate/common/error/ErrorCode.java +++ b/src/main/java/com/example/mate/common/error/ErrorCode.java @@ -15,7 +15,6 @@ public enum ErrorCode { AUTH_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "A002", "인증에 실패했습니다. 유효한 토큰을 제공해주세요."), AUTH_FORBIDDEN(HttpStatus.FORBIDDEN, "A003", "접근 권한이 없습니다. 권한을 확인해주세요."), UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "A004", "인증되지 않은 사용자입니다"), - INVALID_AUTH_TOKEN(HttpStatus.BAD_REQUEST, "A005", "잘못된 토큰 형식입니다."), // Team TEAM_NOT_FOUND(HttpStatus.NOT_FOUND, "T001", "팀을 찾을 수 없습니다"), diff --git a/src/main/java/com/example/mate/common/response/PageResponse.java b/src/main/java/com/example/mate/common/response/PageResponse.java index 0ab1e521..caa4251d 100644 --- a/src/main/java/com/example/mate/common/response/PageResponse.java +++ b/src/main/java/com/example/mate/common/response/PageResponse.java @@ -6,6 +6,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; @Getter @Builder @@ -39,21 +41,13 @@ public static PageResponse from(Page page, List content) { .build(); } - /** - * Page 객체를 기반으로 PageResponse 를 생성하는 팩토리 메서드 - * - * @param page Spring Data JPA 의 Page 객체 - * @param 변환된 데이터 타입 - * @return PageResponse - */ - public static PageResponse from(Page page) { - return PageResponse.builder() - .content(page.getContent()) - .totalPages(page.getTotalPages()) - .totalElements(page.getTotalElements()) - .hasNext(page.hasNext()) - .pageNumber(page.getNumber()) - .pageSize(page.getSize()) - .build(); + // Pageable 검증 메서드 + public static Pageable validatePageable(Pageable pageable) { + // pageNumber 검증: 0보다 작은 값은 0으로 처리 + int pageNumber = Math.max(pageable.getPageNumber(), 0); + + // pageSize 검증: 0 이하이면 기본값 10으로 설정 + int pageSize = pageable.getPageSize() <= 0 ? 10 : pageable.getPageSize(); + return PageRequest.of(pageNumber, pageSize, pageable.getSort()); } } \ No newline at end of file diff --git a/src/main/java/com/example/mate/common/security/filter/JwtCheckFilter.java b/src/main/java/com/example/mate/common/security/filter/JwtCheckFilter.java index 83f394bf..381c1724 100644 --- a/src/main/java/com/example/mate/common/security/filter/JwtCheckFilter.java +++ b/src/main/java/com/example/mate/common/security/filter/JwtCheckFilter.java @@ -2,10 +2,13 @@ import com.example.mate.common.security.auth.AuthMember; import com.example.mate.common.security.util.JwtUtil; -import com.example.mate.domain.member.service.LogoutRedisService; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -13,17 +16,11 @@ import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; -import java.io.IOException; -import java.util.Arrays; -import java.util.Map; -import java.util.stream.Collectors; - @Component @RequiredArgsConstructor public class JwtCheckFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; - private final LogoutRedisService logoutRedisService; // 필터링 적용하지 않을 URI 체크 @Override @@ -47,15 +44,8 @@ protected void doFilterInternal(HttpServletRequest request, return; } + // 토큰 유효성 검증 후 SecurityContext 설정 String accessToken = headerAuth.substring(7); // "Bearer " 제외한 토큰 저장 - - // 액세스 토큰이 블랙리스트에 있는지 확인 - if (logoutRedisService.isTokenBlacklisted(accessToken)) { - handleException(response, new Exception("ACCESS TOKEN IS BLACKLISTED")); - return; - } - - // 액세스 토큰의 모든 유효성 검증 후 SecurityContext 설정 try { Map claims = jwtUtil.validateToken(accessToken); setAuthentication(claims); // 인증 정보 설정 @@ -72,7 +62,6 @@ private boolean isExcludedPath(String requestURI) { requestURI.startsWith("/api/auth") || requestURI.startsWith("/api/members/join") || requestURI.startsWith("/swagger-ui") || - requestURI.startsWith("/v3/api-docs") || requestURI.startsWith("/api/members/login"); } diff --git a/src/main/java/com/example/mate/common/validator/EnumValidator.java b/src/main/java/com/example/mate/common/utils/validator/EnumValidator.java similarity index 94% rename from src/main/java/com/example/mate/common/validator/EnumValidator.java rename to src/main/java/com/example/mate/common/utils/validator/EnumValidator.java index c526b6b6..360f1b64 100644 --- a/src/main/java/com/example/mate/common/validator/EnumValidator.java +++ b/src/main/java/com/example/mate/common/utils/validator/EnumValidator.java @@ -1,4 +1,4 @@ -package com.example.mate.common.validator; +package com.example.mate.common.utils.validator; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; diff --git a/src/main/java/com/example/mate/common/validator/ValidEnum.java b/src/main/java/com/example/mate/common/utils/validator/ValidEnum.java similarity index 93% rename from src/main/java/com/example/mate/common/validator/ValidEnum.java rename to src/main/java/com/example/mate/common/utils/validator/ValidEnum.java index f713fa74..91f92c65 100644 --- a/src/main/java/com/example/mate/common/validator/ValidEnum.java +++ b/src/main/java/com/example/mate/common/utils/validator/ValidEnum.java @@ -1,4 +1,4 @@ -package com.example.mate.common.validator; +package com.example.mate.common.utils.validator; import jakarta.validation.Constraint; import jakarta.validation.Payload; diff --git a/src/main/java/com/example/mate/common/validator/ValidPageable.java b/src/main/java/com/example/mate/common/validator/ValidPageable.java deleted file mode 100644 index 1fc1391e..00000000 --- a/src/main/java/com/example/mate/common/validator/ValidPageable.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.example.mate.common.validator; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.springframework.core.annotation.AliasFor; -import org.springframework.data.domain.Sort.Direction; - -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.PARAMETER}) -public @interface ValidPageable { - - @AliasFor("size") - int value() default 10; - - @AliasFor("value") - int size() default 10; - - int page() default 0; - - String[] sort() default {}; - - Direction direction() default Direction.ASC; -} diff --git a/src/main/java/com/example/mate/common/validator/ValidPageableArgumentResolver.java b/src/main/java/com/example/mate/common/validator/ValidPageableArgumentResolver.java deleted file mode 100644 index 6ad566cb..00000000 --- a/src/main/java/com/example/mate/common/validator/ValidPageableArgumentResolver.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.example.mate.common.validator; - -import org.springframework.core.MethodParameter; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PageableHandlerMethodArgumentResolver; -import org.springframework.stereotype.Component; -import org.springframework.web.bind.support.WebDataBinderFactory; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.method.support.ModelAndViewContainer; - -/** - * 커스텀 Pageable 리졸버로, {@code @ValidPageable} 어노테이션이 붙은 Pageable 매개변수의 유효성 검사를 수행합니다. - */ -@Component -public class ValidPageableArgumentResolver extends PageableHandlerMethodArgumentResolver { - - @Override - public Pageable resolveArgument(MethodParameter methodParameter, - ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, - WebDataBinderFactory binderFactory) { - - // 기본 Pageable 생성 - Pageable pageable = super.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory); - - // @ValidPageable 어노테이션 확인 - ValidPageable validPageable = methodParameter.getParameterAnnotation(ValidPageable.class); - - int pageNumber = Math.max(pageable.getPageNumber(), validPageable != null ? validPageable.page() : 0); - int pageSize = pageable.getPageSize() > 0 - ? pageable.getPageSize() - : validPageable != null ? validPageable.size() : 10; - - return PageRequest.of(pageNumber, pageSize, pageable.getSort()); - } -} \ No newline at end of file diff --git a/src/main/java/com/example/mate/domain/goods/controller/GoodsController.java b/src/main/java/com/example/mate/domain/goods/controller/GoodsController.java index c8dbb023..635d6cff 100644 --- a/src/main/java/com/example/mate/domain/goods/controller/GoodsController.java +++ b/src/main/java/com/example/mate/domain/goods/controller/GoodsController.java @@ -3,7 +3,6 @@ import com.example.mate.common.response.ApiResponse; import com.example.mate.common.response.PageResponse; import com.example.mate.common.security.auth.AuthMember; -import com.example.mate.common.validator.ValidPageable; import com.example.mate.domain.goods.dto.request.GoodsPostRequest; import com.example.mate.domain.goods.dto.request.GoodsReviewRequest; import com.example.mate.domain.goods.dto.response.GoodsPostResponse; @@ -15,6 +14,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.validation.annotation.Validated; @@ -84,7 +84,7 @@ public ResponseEntity>> getGoodsPosts public ResponseEntity>> getGoodsPosts( @Parameter(description = "팀 ID") @RequestParam(required = false) Long teamId, @Parameter(description = "카테고리") @RequestParam(required = false) String category, - @Parameter(description = "페이징 정보", required = true) @ValidPageable Pageable pageable + @Parameter(description = "페이징 정보", required = true) @PageableDefault Pageable pageable ) { PageResponse pageGoodsPosts = goodsService.getPageGoodsPosts(teamId, category, pageable); diff --git a/src/main/java/com/example/mate/domain/goods/dto/request/GoodsPostRequest.java b/src/main/java/com/example/mate/domain/goods/dto/request/GoodsPostRequest.java index 542b6152..db9c2b21 100644 --- a/src/main/java/com/example/mate/domain/goods/dto/request/GoodsPostRequest.java +++ b/src/main/java/com/example/mate/domain/goods/dto/request/GoodsPostRequest.java @@ -1,6 +1,6 @@ package com.example.mate.domain.goods.dto.request; -import com.example.mate.common.validator.ValidEnum; +import com.example.mate.common.utils.validator.ValidEnum; import com.example.mate.domain.goods.dto.LocationInfo; import com.example.mate.domain.goods.entity.Category; import com.example.mate.domain.goods.entity.GoodsPost; diff --git a/src/main/java/com/example/mate/domain/goods/dto/request/GoodsReviewRequest.java b/src/main/java/com/example/mate/domain/goods/dto/request/GoodsReviewRequest.java index 12dc621c..10802cc3 100644 --- a/src/main/java/com/example/mate/domain/goods/dto/request/GoodsReviewRequest.java +++ b/src/main/java/com/example/mate/domain/goods/dto/request/GoodsReviewRequest.java @@ -1,6 +1,6 @@ package com.example.mate.domain.goods.dto.request; -import com.example.mate.common.validator.ValidEnum; +import com.example.mate.common.utils.validator.ValidEnum; import com.example.mate.domain.constant.Rating; import com.example.mate.domain.goods.entity.GoodsPost; import com.example.mate.domain.goods.entity.GoodsReview; diff --git a/src/main/java/com/example/mate/domain/goodsChat/controller/GoodsChatRoomController.java b/src/main/java/com/example/mate/domain/goodsChat/controller/GoodsChatRoomController.java index 727d36e6..55c23e1c 100644 --- a/src/main/java/com/example/mate/domain/goodsChat/controller/GoodsChatRoomController.java +++ b/src/main/java/com/example/mate/domain/goodsChat/controller/GoodsChatRoomController.java @@ -3,18 +3,15 @@ import com.example.mate.common.response.ApiResponse; import com.example.mate.common.response.PageResponse; import com.example.mate.common.security.auth.AuthMember; -import com.example.mate.common.validator.ValidPageable; import com.example.mate.domain.goodsChat.dto.response.GoodsChatMessageResponse; import com.example.mate.domain.goodsChat.dto.response.GoodsChatRoomResponse; import com.example.mate.domain.goodsChat.dto.response.GoodsChatRoomSummaryResponse; import com.example.mate.domain.goodsChat.service.GoodsChatService; import com.example.mate.domain.member.dto.response.MemberSummaryResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; @@ -28,68 +25,51 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/goods/chat") -@Tag(name = "GoodsChatRoomController", description = "굿즈거래 채팅방 관련 API") public class GoodsChatRoomController { private final GoodsChatService goodsChatService; @PostMapping - @Operation(summary = "굿즈거래 채팅방 입장 및 생성", description = "굿즈 거래 게시글에 대한 채팅방을 생성하거나 기존 채팅방 정보를 조회합니다.") - public ResponseEntity> createGoodsChatRoom( - @AuthenticationPrincipal AuthMember member, - @Parameter(description = "판매글 ID", required = true) @RequestParam Long goodsPostId - ) { + public ResponseEntity> createGoodsChatRoom(@AuthenticationPrincipal AuthMember member, + @RequestParam Long goodsPostId) { GoodsChatRoomResponse response = goodsChatService.getOrCreateGoodsChatRoom(member.getMemberId(), goodsPostId); return ResponseEntity.ok(ApiResponse.success(response)); } @GetMapping("/{chatRoomId}/message") - @Operation(summary = "굿즈거래 채팅방 메시지 조회", description = "지정된 채팅방의 메시지를 페이징 처리하여 조회합니다.") public ResponseEntity>> getGoodsChatRoomMessages( @AuthenticationPrincipal AuthMember member, - @Parameter(description = "채팅방 ID", required = true) @PathVariable Long chatRoomId, - @Parameter(description = "페이징 정보") @ValidPageable Pageable pageable + @PathVariable Long chatRoomId, + @PageableDefault(page = 1, size = 20) Pageable pageable ) { PageResponse response = goodsChatService.getMessagesForChatRoom(chatRoomId, member.getMemberId(), pageable); return ResponseEntity.ok(ApiResponse.success(response)); } @GetMapping - @Operation(summary = "사용자의 굿즈거래 채팅방 목록 조회", description = "사용자가 참여 중인 굿즈거래 채팅방 목록을 조회합니다.") - public ResponseEntity>> getGoodsChatRooms( - @AuthenticationPrincipal AuthMember member, - @Parameter(description = "페이징 정보") @ValidPageable Pageable pageable - ) { + public ResponseEntity>> getGoodsChatRooms(@AuthenticationPrincipal AuthMember member, + @PageableDefault Pageable pageable) { PageResponse response = goodsChatService.getGoodsChatRooms(member.getMemberId(), pageable); return ResponseEntity.ok(ApiResponse.success(response)); } @DeleteMapping("/{chatRoomId}") - @Operation(summary = "굿즈거래 채팅방 나가기", description = "사용자가 지정된 굿즈거래 채팅방을 나갑니다. 만약 모든 사용자가 나가면 채팅방이 삭제됩니다.") - public ResponseEntity leaveGoodsChatRoom( - @AuthenticationPrincipal AuthMember member, - @Parameter(description = "채팅방 ID", required = true) @PathVariable Long chatRoomId - ) { + public ResponseEntity leaveGoodsChatRoom(@AuthenticationPrincipal AuthMember member, @PathVariable Long chatRoomId) { goodsChatService.deactivateGoodsChatPart(member.getMemberId(), chatRoomId); + return ResponseEntity.noContent().build(); } @GetMapping("/{chatRoomId}") - @Operation(summary = "굿즈거래 채팅방 입장", description = "굿즈 거래 채팅방의 정보를 조회합니다.") - public ResponseEntity> getGoodsChatRoomInfo( - @AuthenticationPrincipal AuthMember member, - @Parameter(description = "채팅방 ID", required = true) @PathVariable Long chatRoomId - ) { + public ResponseEntity> getGoodsChatRoomInfo(@AuthenticationPrincipal AuthMember member, + @PathVariable Long chatRoomId) { GoodsChatRoomResponse response = goodsChatService.getGoodsChatRoomInfo(member.getMemberId(), chatRoomId); return ResponseEntity.ok(ApiResponse.success(response)); } @GetMapping("/{chatRoomId}/members") - @Operation(summary = "굿즈거래 채팅방 인원 조회", description = "지정된 채팅방에 참여 중인 사용자 목록을 조회합니다.") - public ResponseEntity>> getGoodsChatRoomMembers( - @AuthenticationPrincipal AuthMember member, - @Parameter(description = "채팅방 ID", required = true) @PathVariable Long chatRoomId - ) { + public ResponseEntity>> getGoodsChatRoomMembers(@AuthenticationPrincipal AuthMember member, + @PathVariable Long chatRoomId) { List responses = goodsChatService.getChatRoomMembers(member.getMemberId(), chatRoomId); return ResponseEntity.ok(ApiResponse.success(responses)); } diff --git a/src/main/java/com/example/mate/domain/mate/controller/MateController.java b/src/main/java/com/example/mate/domain/mate/controller/MateController.java index 1f656353..df5bba30 100644 --- a/src/main/java/com/example/mate/domain/mate/controller/MateController.java +++ b/src/main/java/com/example/mate/domain/mate/controller/MateController.java @@ -2,7 +2,7 @@ import com.example.mate.common.response.ApiResponse; import com.example.mate.common.response.PageResponse; -import com.example.mate.common.validator.ValidPageable; +import com.example.mate.common.security.auth.AuthMember; import com.example.mate.domain.constant.Gender; import com.example.mate.domain.mate.dto.request.*; import com.example.mate.domain.mate.dto.response.*; @@ -16,8 +16,10 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -33,12 +35,12 @@ public class MateController { @PostMapping @Operation(summary = "메이트 구인글 등록", description = "메이트 구인글 페이지에서 등록합니다.") - public ResponseEntity> createMatePost( - @Parameter(description = "구인글 등록 데이터", required = true) @Valid @RequestPart(value = "data") MatePostCreateRequest request, - @Parameter(description = "구인글 대표사진", required = true) @RequestPart(value = "file", required = false) MultipartFile file - ) { - //TODO - member 정보를 request가 아니라 @AuthenticationPrincipal Long memberId로 받도록 변경 - MatePostResponse response = mateService.createMatePost(request, file); + public ResponseEntity> createMatePost(@Parameter(description = "구인글 등록 데이터", required = true) + @Valid @RequestPart(value = "data") MatePostCreateRequest request, + @Parameter(description = "구인글 대표사진", required = true) + @RequestPart(value = "file", required = false) MultipartFile file, + @AuthenticationPrincipal AuthMember member) { + MatePostResponse response = mateService.createMatePost(request, file, member.getMemberId()); return ResponseEntity.ok(ApiResponse.success(response)); } @@ -52,15 +54,21 @@ public ResponseEntity>> getMainPagePos @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) @Operation(summary = "메이트 구인글 페이징 조회", description = "메이트 구인글 페이지에서 팀/카테고리 기준으로 페이징 조회합니다.") - public ResponseEntity>> getMatePagePosts( - @Parameter(description = "팀 ID") @RequestParam(required = false) Long teamId, - @Parameter(description = "정렬 기준") @RequestParam(required = false) String sortType, - @Parameter(description = "연령대 카테고리") @RequestParam(required = false) String age, - @Parameter(description = "성별 카테고리") @RequestParam(required = false) String gender, - @Parameter(description = "모집인원 수") @RequestParam(required = false) Integer maxParticipants, - @Parameter(description = "이동수단 카테고리") @RequestParam(required = false) String transportType, - @Parameter(description = "페이징 정보") @ValidPageable Pageable pageable - ) { + public ResponseEntity>> getMatePagePosts(@Parameter(description = "팀 ID") + @RequestParam(required = false) Long teamId, + @Parameter(description = "정렬 기준") + @RequestParam(required = false) String sortType, + @Parameter(description = "연령대 카테고리") + @RequestParam(required = false) String age, + @Parameter(description = "성별 카테고리") + @RequestParam(required = false) String gender, + @Parameter(description = "모집인원 수") + @RequestParam(required = false) Integer maxParticipants, + @Parameter(description = "이동수단 카테고리") + @RequestParam(required = false) String transportType, + @Parameter(description = "페이징 정보") + @PageableDefault Pageable pageable) { + MatePostSearchRequest request = MatePostSearchRequest.builder() .teamId(teamId) .sortType(sortType != null ? SortType.from(sortType) : null) @@ -83,65 +91,66 @@ public ResponseEntity> getMatePostDetail(@Pa return ResponseEntity.ok(ApiResponse.success(response)); } - @PatchMapping("/{memberId}/{postId}") + @PutMapping("/{postId}") @Operation(summary = "메이트 구인글 수정", description = "메이트 구인글 상세 페이지에서 수정합니다.") - public ResponseEntity> updateMatePost( - @Parameter(description = "작성자 ID (삭제 예정)", required = true) @PathVariable Long memberId, - @Parameter(description = "구인글 ID", required = true) @PathVariable Long postId, - @Parameter(description = "수정할 구인글 데이터", required = true) @Valid @RequestPart(value = "data") MatePostUpdateRequest request, - @Parameter(description = "수정할 대표사진 파일 ", required = true) @RequestPart(value = "file", required = false) MultipartFile file - ) { - MatePostResponse response = mateService.updateMatePost(memberId, postId, request, file); + public ResponseEntity> updateMatePost( @AuthenticationPrincipal AuthMember member, + @Parameter(description = "구인글 ID", required = true) + @PathVariable Long postId, + @Parameter(description = "수정할 구인글 데이터", required = true) + @Valid @RequestPart(value = "data") MatePostUpdateRequest request, + @Parameter(description = "수정할 대표사진 파일 ", required = true) + @RequestPart(value = "file", required = false) MultipartFile file) { + + MatePostResponse response = mateService.updateMatePost(member.getMemberId(), postId, request, file); return ResponseEntity.ok(ApiResponse.success(response)); } - // TODO: @PathVariable Long memberId -> @AuthenticationPrincipal 로 변경 // 메이트 게시글 모집 상태 변경 - @PatchMapping("/{memberId}/{postId}/status") + @PatchMapping("/{postId}/status") @Operation(summary = "메이트 구인글 모집상태 변경", description = "메이트 구인글 채팅방에서 모집상태를 변경합니다.") - public ResponseEntity> updateMatePostStatus( - @Parameter(description = "작성자 ID (삭제 예정)", required = true) @PathVariable(value = "memberId") Long memberId, - @Parameter(description = "구인글 ID", required = true) @PathVariable(value = "postId") Long postId, - @Parameter(description = "변경할 모집상태와 현재 참여자 리스트 ID", required = true) @Valid @RequestBody MatePostStatusRequest request - ) { - MatePostResponse response = mateService.updateMatePostStatus(memberId, postId, request); + public ResponseEntity> updateMatePostStatus( @AuthenticationPrincipal AuthMember member, + @Parameter(description = "구인글 ID", required = true) + @PathVariable(value = "postId") Long postId, + @Parameter(description = "변경할 모집상태와 현재 참여자 리스트 ID", required = true) + @Valid @RequestBody MatePostStatusRequest request) { + + MatePostResponse response = mateService.updateMatePostStatus(member.getMemberId(), postId, request); return ResponseEntity.ok(ApiResponse.success(response)); } - // TODO: @PathVariable Long memberId -> @AuthenticationPrincipal 로 변경 - @DeleteMapping("/{memberId}/{postId}") + @DeleteMapping("/{postId}") @Operation(summary = "메이트 구인글 삭제", description = "메이트 구인글 상세 페이지에서 삭제합니다.") - public ResponseEntity deleteMatePost(@Parameter(description = "작성자 ID (삭제 예정)", required = true) - @PathVariable Long memberId, + public ResponseEntity deleteMatePost( @AuthenticationPrincipal AuthMember member, @Parameter(description = "삭제할 구인글 ID", required = true) - @PathVariable Long postId) { + @PathVariable Long postId) { - mateService.deleteMatePost(memberId, postId); + mateService.deleteMatePost(member.getMemberId(), postId); return ResponseEntity.noContent().build(); } - // TODO: @PathVariable Long memberId -> @AuthenticationPrincipal 로 변경 - @PatchMapping("/{memberId}/{postId}/complete") + @PatchMapping("/{postId}/complete") @Operation(summary = "직관완료 처리", description = "메이트 구인글 채팅방에서 직관완료 처리를 진행합니다.") - public ResponseEntity> completeVisit( - @Parameter(description = "작성자 ID (삭제 예정)", required = true) @PathVariable Long memberId, - @Parameter(description = "구인글 ID", required = true) @PathVariable Long postId, - @Parameter(description = "실제 직관 참여자 리스트 ID", required = true) @Valid @RequestBody MatePostCompleteRequest request - ) { - MatePostCompleteResponse response = mateService.completeVisit(memberId, postId, request); + public ResponseEntity> completeVisit( @AuthenticationPrincipal AuthMember member, + @Parameter(description = "구인글 ID", required = true) + @PathVariable Long postId, + @Parameter(description = "실제 직관 참여자 리스트 ID", required = true) + @Valid @RequestBody MatePostCompleteRequest request) { + + MatePostCompleteResponse response = mateService.completeVisit(member.getMemberId(), postId, request); return ResponseEntity.ok(ApiResponse.success(response)); } - // TODO: @PathVariable Long memberId -> @AuthenticationPrincipal 로 변경 - @PostMapping("/{memberId}/{postId}/reviews") + @PostMapping("/{postId}/reviews") @Operation(summary = "메이트 직관 후기 등록", description = "직관 타임라인 페이지에서 메이트에 대한 후기를 등록합니다.") - public ResponseEntity> createMateReview( - @Parameter(description = "작성자 ID (삭제 예정)", required = true) @PathVariable Long memberId, - @Parameter(description = "구인글 ID", required = true) @PathVariable Long postId, - @Parameter(description = "리뷰 대상자 ID와 평점 및 코멘트", required = true) @Valid @RequestBody MateReviewCreateRequest request + public ResponseEntity> createMateReview( @AuthenticationPrincipal AuthMember member, + @Parameter(description = "구인글 ID", required = true) + @PathVariable Long postId, + @Parameter(description = "리뷰 대상자 ID와 평점 및 코멘트", required = true) + @Valid @RequestBody MateReviewCreateRequest request ) { - MateReviewCreateResponse response = mateService.createReview(postId, memberId, request); + + MateReviewCreateResponse response = mateService.createReview(postId, member.getMemberId(), request); return ResponseEntity.ok(ApiResponse.success(response)); } } \ No newline at end of file diff --git a/src/main/java/com/example/mate/domain/mate/dto/request/MatePostCreateRequest.java b/src/main/java/com/example/mate/domain/mate/dto/request/MatePostCreateRequest.java index 3748d003..77f593e9 100644 --- a/src/main/java/com/example/mate/domain/mate/dto/request/MatePostCreateRequest.java +++ b/src/main/java/com/example/mate/domain/mate/dto/request/MatePostCreateRequest.java @@ -19,7 +19,6 @@ @NoArgsConstructor @AllArgsConstructor public class MatePostCreateRequest { - private Long memberId; @NotNull(message = "팀 ID는 필수 입력 값입니다.") @Min(value = 0, message = "팀 ID는 0 이상이어야 합니다.") diff --git a/src/main/java/com/example/mate/domain/mate/dto/request/MatePostSearchRequest.java b/src/main/java/com/example/mate/domain/mate/dto/request/MatePostSearchRequest.java index d6e67c33..75b01fa5 100644 --- a/src/main/java/com/example/mate/domain/mate/dto/request/MatePostSearchRequest.java +++ b/src/main/java/com/example/mate/domain/mate/dto/request/MatePostSearchRequest.java @@ -1,6 +1,6 @@ package com.example.mate.domain.mate.dto.request; -import com.example.mate.common.validator.ValidEnum; +import com.example.mate.common.utils.validator.ValidEnum; import com.example.mate.domain.constant.Gender; import com.example.mate.domain.mate.entity.Age; import com.example.mate.domain.mate.entity.SortType; diff --git a/src/main/java/com/example/mate/domain/mate/dto/request/MatePostStatusRequest.java b/src/main/java/com/example/mate/domain/mate/dto/request/MatePostStatusRequest.java index 3bc80432..91adc734 100644 --- a/src/main/java/com/example/mate/domain/mate/dto/request/MatePostStatusRequest.java +++ b/src/main/java/com/example/mate/domain/mate/dto/request/MatePostStatusRequest.java @@ -1,6 +1,6 @@ package com.example.mate.domain.mate.dto.request; -import com.example.mate.common.validator.ValidEnum; +import com.example.mate.common.utils.validator.ValidEnum; import com.example.mate.domain.mate.entity.Status; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; diff --git a/src/main/java/com/example/mate/domain/mate/dto/request/MatePostUpdateRequest.java b/src/main/java/com/example/mate/domain/mate/dto/request/MatePostUpdateRequest.java index 33c7307f..e553f3e5 100644 --- a/src/main/java/com/example/mate/domain/mate/dto/request/MatePostUpdateRequest.java +++ b/src/main/java/com/example/mate/domain/mate/dto/request/MatePostUpdateRequest.java @@ -1,6 +1,6 @@ package com.example.mate.domain.mate.dto.request; -import com.example.mate.common.validator.ValidEnum; +import com.example.mate.common.utils.validator.ValidEnum; import com.example.mate.domain.constant.Gender; import com.example.mate.domain.mate.entity.Age; import com.example.mate.domain.mate.entity.TransportType; diff --git a/src/main/java/com/example/mate/domain/mate/service/MateService.java b/src/main/java/com/example/mate/domain/mate/service/MateService.java index 245d7619..95b6caa3 100644 --- a/src/main/java/com/example/mate/domain/mate/service/MateService.java +++ b/src/main/java/com/example/mate/domain/mate/service/MateService.java @@ -41,8 +41,8 @@ public class MateService { private final MateReviewRepository mateReviewRepository; private final FileService fileService; - public MatePostResponse createMatePost(MatePostCreateRequest request, MultipartFile file) { - Member author = findMemberById(request.getMemberId()); + public MatePostResponse createMatePost(MatePostCreateRequest request, MultipartFile file, Long memberId) { + Member author = findMemberById(memberId); Match match = findMatchById(request.getMatchId()); diff --git a/src/main/java/com/example/mate/domain/mateChat/controller/MateChatRoomController.java b/src/main/java/com/example/mate/domain/mateChat/controller/MateChatRoomController.java index 69697ee2..105da16e 100644 --- a/src/main/java/com/example/mate/domain/mateChat/controller/MateChatRoomController.java +++ b/src/main/java/com/example/mate/domain/mateChat/controller/MateChatRoomController.java @@ -3,7 +3,6 @@ import com.example.mate.common.response.ApiResponse; import com.example.mate.common.response.PageResponse; import com.example.mate.common.security.auth.AuthMember; -import com.example.mate.common.validator.ValidPageable; import com.example.mate.domain.mateChat.dto.response.MateChatMessageResponse; import com.example.mate.domain.mateChat.dto.response.MateChatRoomListResponse; import com.example.mate.domain.mateChat.dto.response.MateChatRoomResponse; @@ -14,6 +13,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -31,7 +31,7 @@ public class MateChatRoomController { @Operation(summary = "메이트 게시글 -> 채팅방 생성/입장", description = "메이트 게시글 페이지에서 채팅방으로 입장") public ResponseEntity> createOrJoinChatRoomFromPost( @Parameter(description = "메이트 게시글 ID") @PathVariable Long matePostId, - @AuthenticationPrincipal AuthMember member + @AuthenticationPrincipal AuthMember member ) { MateChatRoomResponse response = chatRoomService.createOrJoinChatRoomFromPost(matePostId, member.getMemberId()); return ResponseEntity.ok(ApiResponse.success(response)); @@ -40,9 +40,10 @@ public ResponseEntity> createOrJoinChatRoomFro @GetMapping("/me") @Operation(summary = "채팅방 목록 조회", description = "사용자의 채팅방 목록을 조회합니다.") public ResponseEntity>> getMyChatRooms( - @Parameter(description = "페이지 정보") @ValidPageable Pageable pageable, - @AuthenticationPrincipal AuthMember member - ) { + @Parameter(description = "페이지 정보") @PageableDefault Pageable pageable, + @AuthenticationPrincipal AuthMember member + + ) { PageResponse response = chatRoomService.getMyChatRooms(member.getMemberId(), pageable); return ResponseEntity.ok(ApiResponse.success(response)); } @@ -61,8 +62,8 @@ public ResponseEntity> joinExistingChatRoom( @Operation(summary = "채팅방 메세지 조회", description = "메시지 내역을 조회합니다.") public ResponseEntity>> getChatMessages( @Parameter(description = "채팅방 ID") @PathVariable Long chatroomId, - @AuthenticationPrincipal AuthMember member, - @Parameter(description = "페이지 정보") @ValidPageable(page = 1, size = 20) Pageable pageable + @AuthenticationPrincipal AuthMember member, + @Parameter(description = "페이지 정보") @PageableDefault(page = 1, size = 20) Pageable pageable ) { PageResponse messages = chatRoomService.getChatMessages(chatroomId, member.getMemberId(), pageable); return ResponseEntity.ok(ApiResponse.success(messages)); @@ -72,7 +73,7 @@ public ResponseEntity>> getCha @Operation(summary = "채팅방 나가기", description = "채팅방에서 퇴장합니다.") public ResponseEntity leaveChatRoom( @Parameter(description = "채팅방 ID") @PathVariable Long chatroomId, - @AuthenticationPrincipal AuthMember member + @AuthenticationPrincipal AuthMember member ) { chatRoomService.leaveChatRoom(chatroomId, member.getMemberId()); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/example/mate/domain/member/controller/FollowController.java b/src/main/java/com/example/mate/domain/member/controller/FollowController.java index 08048be8..191e7a29 100644 --- a/src/main/java/com/example/mate/domain/member/controller/FollowController.java +++ b/src/main/java/com/example/mate/domain/member/controller/FollowController.java @@ -1,9 +1,10 @@ package com.example.mate.domain.member.controller; +import static com.example.mate.common.response.PageResponse.validatePageable; + import com.example.mate.common.response.ApiResponse; import com.example.mate.common.response.PageResponse; import com.example.mate.common.security.auth.AuthMember; -import com.example.mate.common.validator.ValidPageable; import com.example.mate.domain.member.dto.response.MemberSummaryResponse; import com.example.mate.domain.member.service.FollowService; import io.swagger.v3.oas.annotations.Operation; @@ -11,6 +12,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; @@ -50,8 +52,9 @@ public ResponseEntity unfollowMember( @GetMapping("{memberId}/followings") public ResponseEntity>> getFollowings( @Parameter(description = "특정 회원 ID") @PathVariable Long memberId, - @Parameter(description = "페이지 요청 정보") @ValidPageable Pageable pageable + @Parameter(description = "페이지 요청 정보") @PageableDefault Pageable pageable ) { + pageable = validatePageable(pageable); PageResponse response = followService.getFollowingsPage(memberId, pageable); return ResponseEntity.ok(ApiResponse.success(response)); } @@ -60,8 +63,9 @@ public ResponseEntity>> getFollo @GetMapping("{memberId}/followers") public ResponseEntity>> getFollowers( @Parameter(description = "특정 회원 ID") @PathVariable Long memberId, - @Parameter(description = "페이지 요청 정보") @ValidPageable Pageable pageable + @Parameter(description = "페이지 요청 정보") @PageableDefault Pageable pageable ) { + pageable = validatePageable(pageable); PageResponse response = followService.getFollowersPage(memberId, pageable); return ResponseEntity.ok(ApiResponse.success(response)); diff --git a/src/main/java/com/example/mate/domain/member/controller/MemberController.java b/src/main/java/com/example/mate/domain/member/controller/MemberController.java index cdec1540..f81207f0 100644 --- a/src/main/java/com/example/mate/domain/member/controller/MemberController.java +++ b/src/main/java/com/example/mate/domain/member/controller/MemberController.java @@ -9,7 +9,6 @@ import com.example.mate.domain.member.dto.response.MemberLoginResponse; import com.example.mate.domain.member.dto.response.MemberProfileResponse; import com.example.mate.domain.member.dto.response.MyProfileResponse; -import com.example.mate.domain.member.service.LogoutRedisService; import com.example.mate.domain.member.service.MemberService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -28,7 +27,6 @@ public class MemberController { private final MemberService memberService; - private final LogoutRedisService logoutRedisService; @Operation(summary = "자체 회원가입 기능") @PostMapping("/join") @@ -47,15 +45,6 @@ public ResponseEntity> catchMiLogin( return ResponseEntity.ok(ApiResponse.success(response)); } - @Operation(summary = "CATCH Mi 서비스 로그아웃", description = "캐치미 서비스에 로그아웃합니다.") - @PostMapping("/logout") - public ResponseEntity catchMiLogout( - @Parameter(description = "회원 로그인 토큰 헤더", required = true) @RequestHeader("Authorization") String token - ) { - logoutRedisService.addTokenToBlacklist(token); - return ResponseEntity.noContent().build(); - } - @Operation(summary = "내 프로필 조회") @GetMapping("/me") public ResponseEntity> findMyInfo( diff --git a/src/main/java/com/example/mate/domain/member/controller/ProfileController.java b/src/main/java/com/example/mate/domain/member/controller/ProfileController.java index c2140ba6..6de3c51c 100644 --- a/src/main/java/com/example/mate/domain/member/controller/ProfileController.java +++ b/src/main/java/com/example/mate/domain/member/controller/ProfileController.java @@ -1,11 +1,12 @@ package com.example.mate.domain.member.controller; +import static com.example.mate.common.response.PageResponse.validatePageable; + import com.example.mate.common.error.CustomException; import com.example.mate.common.error.ErrorCode; import com.example.mate.common.response.ApiResponse; import com.example.mate.common.response.PageResponse; import com.example.mate.common.security.auth.AuthMember; -import com.example.mate.common.validator.ValidPageable; import com.example.mate.domain.member.dto.response.MyGoodsRecordResponse; import com.example.mate.domain.member.dto.response.MyReviewResponse; import com.example.mate.domain.member.dto.response.MyVisitResponse; @@ -15,6 +16,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; @@ -34,8 +36,9 @@ public class ProfileController { @GetMapping("/{memberId}/review/goods") public ResponseEntity>> getGoodsReviews( @Parameter(description = "회원 ID") @PathVariable Long memberId, - @Parameter(description = "페이지 요청 정보") @ValidPageable Pageable pageable + @Parameter(description = "페이지 요청 정보") @PageableDefault Pageable pageable ) { + validatePageable(pageable); PageResponse response = profileService.getGoodsReviewPage(memberId, pageable); return ResponseEntity.ok(ApiResponse.success(response)); } @@ -44,8 +47,9 @@ public ResponseEntity>> getGoodsRevie @GetMapping("{memberId}/review/mate") public ResponseEntity>> getMateReviews( @Parameter(description = "회원 ID") @PathVariable Long memberId, - @Parameter(description = "페이지 요청 정보") @ValidPageable Pageable pageable + @Parameter(description = "페이지 요청 정보") @PageableDefault Pageable pageable ) { + validatePageable(pageable); PageResponse response = profileService.getMateReviewPage(memberId, pageable); return ResponseEntity.ok(ApiResponse.success(response)); } @@ -54,8 +58,9 @@ public ResponseEntity>> getMateReview @GetMapping("/timeline") public ResponseEntity>> getMyVisits( @Parameter(description = "회원 로그인 정보") @AuthenticationPrincipal AuthMember authMember, - @Parameter(description = "페이지 요청 정보") @ValidPageable Pageable pageable + @Parameter(description = "페이지 요청 정보") @PageableDefault Pageable pageable ) { + validatePageable(pageable); PageResponse response = profileService.getMyVisitPage(authMember.getMemberId(), pageable); return ResponseEntity.ok(ApiResponse.success(response)); } @@ -64,8 +69,9 @@ public ResponseEntity>> getMyVisits( @GetMapping("/{memberId}/goods/sold") public ResponseEntity>> getSoldGoods( @Parameter(description = "회원 ID") @PathVariable Long memberId, - @Parameter(description = "페이지 요청 정보") @ValidPageable Pageable pageable + @Parameter(description = "페이지 요청 정보") @PageableDefault Pageable pageable ) { + pageable = validatePageable(pageable); PageResponse response = profileService.getSoldGoodsPage(memberId, pageable); return ResponseEntity.ok(ApiResponse.success(response)); } @@ -74,12 +80,13 @@ public ResponseEntity>> getSoldG @GetMapping("/{memberId}/goods/bought") public ResponseEntity>> getBoughtGoods( @Parameter(description = "회원 ID") @PathVariable Long memberId, - @Parameter(description = "페이지 요청 정보") @ValidPageable Pageable pageable, + @Parameter(description = "페이지 요청 정보") @PageableDefault Pageable pageable, @Parameter(description = "회원 로그인 정보") @AuthenticationPrincipal AuthMember authMember ) { if (!authMember.getMemberId().equals(memberId)) { throw new CustomException(ErrorCode.MEMBER_UNAUTHORIZED_ACCESS); } + pageable = validatePageable(pageable); PageResponse response = profileService.getBoughtGoodsPage(memberId, pageable); return ResponseEntity.ok(ApiResponse.success(response)); } diff --git a/src/main/java/com/example/mate/domain/member/service/FollowService.java b/src/main/java/com/example/mate/domain/member/service/FollowService.java index af18f517..2536ec16 100644 --- a/src/main/java/com/example/mate/domain/member/service/FollowService.java +++ b/src/main/java/com/example/mate/domain/member/service/FollowService.java @@ -56,7 +56,14 @@ public PageResponse getFollowingsPage(Long memberId, Page .map(MemberSummaryResponse::from) .toList(); - return PageResponse.from(followingsPage, content); + return PageResponse.builder() + .content(content) + .totalPages(followingsPage.getTotalPages()) + .totalElements(followingsPage.getTotalElements()) + .hasNext(followingsPage.hasNext()) + .pageNumber(followingsPage.getNumber()) + .pageSize(followingsPage.getSize()) + .build(); } // 특정 회원의 팔로워 리스트 페이징 조회 @@ -71,7 +78,14 @@ public PageResponse getFollowersPage(Long memberId, Pagea .map(MemberSummaryResponse::from) .toList(); - return PageResponse.from(followingsPage, content); + return PageResponse.builder() + .content(content) + .totalPages(followingsPage.getTotalPages()) + .totalElements(followingsPage.getTotalElements()) + .hasNext(followingsPage.hasNext()) + .pageNumber(followingsPage.getNumber()) + .pageSize(followingsPage.getSize()) + .build(); } private Map isValidMemberFollow(Long followerId, Long followingId) { diff --git a/src/main/java/com/example/mate/domain/member/service/LogoutRedisService.java b/src/main/java/com/example/mate/domain/member/service/LogoutRedisService.java deleted file mode 100644 index eb4a7f74..00000000 --- a/src/main/java/com/example/mate/domain/member/service/LogoutRedisService.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.mate.domain.member.service; - -import com.example.mate.common.error.CustomException; -import com.example.mate.common.error.ErrorCode; -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -import java.util.concurrent.TimeUnit; - -@Service -@RequiredArgsConstructor -public class LogoutRedisService { - - private final RedisTemplate redisTemplate; - - // 로그아웃 시 블랙리스트에 토큰 추가 - public void addTokenToBlacklist(String token) { - if (token == null || !token.startsWith("Bearer ")) { - throw new CustomException(ErrorCode.INVALID_AUTH_TOKEN); - } - - // TODO : 테스트용 1분 유효를 변경 - redisTemplate.opsForValue().set("blacklist:" + token.substring(7), "blacklisted", 1, TimeUnit.MINUTES); - } - - // 블랙리스트에 토큰 있는지 확인 - public boolean isTokenBlacklisted(String accessToken) { - return redisTemplate.hasKey("blacklist:" + accessToken); - } -} diff --git a/src/main/java/com/example/mate/domain/member/service/ProfileService.java b/src/main/java/com/example/mate/domain/member/service/ProfileService.java index 0df2fe3e..efed4fc9 100644 --- a/src/main/java/com/example/mate/domain/member/service/ProfileService.java +++ b/src/main/java/com/example/mate/domain/member/service/ProfileService.java @@ -4,6 +4,7 @@ import com.example.mate.common.error.ErrorCode; import com.example.mate.common.response.PageResponse; import com.example.mate.domain.goods.entity.GoodsPost; +import com.example.mate.domain.goods.entity.GoodsPostImage; import com.example.mate.domain.goods.entity.Status; import com.example.mate.domain.goods.repository.GoodsPostRepository; import com.example.mate.domain.goods.repository.GoodsReviewRepositoryCustom; @@ -55,7 +56,14 @@ public PageResponse getSoldGoodsPage(Long memberId, Pagea List content = soldGoodsPage.getContent().stream() .map(this::convertToRecordResponse).toList(); - return PageResponse.from(soldGoodsPage, content); + return PageResponse.builder() + .content(content) + .totalPages(soldGoodsPage.getTotalPages()) + .totalElements(soldGoodsPage.getTotalElements()) + .hasNext(soldGoodsPage.hasNext()) + .pageNumber(soldGoodsPage.getNumber()) + .pageSize(soldGoodsPage.getSize()) + .build(); } // 굿즈 구매기록 페이징 조회 @@ -69,7 +77,14 @@ public PageResponse getBoughtGoodsPage(Long memberId, Pag List content = boughtGoodsPage.getContent().stream() .map(this::convertToRecordResponse).toList(); - return PageResponse.from(boughtGoodsPage, content); + return PageResponse.builder() + .content(content) + .totalPages(boughtGoodsPage.getTotalPages()) + .totalElements(boughtGoodsPage.getTotalElements()) + .hasNext(boughtGoodsPage.hasNext()) + .pageNumber(boughtGoodsPage.getNumber()) + .pageSize(boughtGoodsPage.getSize()) + .build(); } private void validateMemberId(Long memberId) { @@ -90,7 +105,14 @@ public PageResponse getMateReviewPage(Long memberId, Pageable Page mateReviewPage = mateReviewRepositoryCustom.findMateReviewsByRevieweeId( memberId, pageable); - return PageResponse.from(mateReviewPage); + return PageResponse.builder() + .content(mateReviewPage.getContent()) + .totalPages(mateReviewPage.getTotalPages()) + .totalElements(mateReviewPage.getTotalElements()) + .hasNext(mateReviewPage.hasNext()) + .pageNumber(mateReviewPage.getNumber()) + .pageSize(mateReviewPage.getSize()) + .build(); } // 굿즈거래 후기 페이징 조회 @@ -101,7 +123,14 @@ public PageResponse getGoodsReviewPage(Long memberId, Pageable Page goodsReviewPage = goodsReviewRepositoryCustom.findGoodsReviewsByRevieweeId( memberId, pageable); - return PageResponse.from(goodsReviewPage); + return PageResponse.builder() + .content(goodsReviewPage.getContent()) + .totalPages(goodsReviewPage.getTotalPages()) + .totalElements(goodsReviewPage.getTotalElements()) + .hasNext(goodsReviewPage.hasNext()) + .pageNumber(goodsReviewPage.getNumber()) + .pageSize(goodsReviewPage.getSize()) + .build(); } // TODO : 쿼리가 너무 많이 나오는 문제 -> 멘토링 및 리팩토링 필요 diff --git a/src/test/java/com/example/mate/domain/file/FileValidatorTest.java b/src/test/java/com/example/mate/common/utils/file/FileValidatorTest.java similarity index 97% rename from src/test/java/com/example/mate/domain/file/FileValidatorTest.java rename to src/test/java/com/example/mate/common/utils/file/FileValidatorTest.java index e5efd2ea..36720d1f 100644 --- a/src/test/java/com/example/mate/domain/file/FileValidatorTest.java +++ b/src/test/java/com/example/mate/common/utils/file/FileValidatorTest.java @@ -1,7 +1,8 @@ -package com.example.mate.domain.file; +package com.example.mate.common.utils.file; import com.example.mate.common.error.CustomException; import com.example.mate.common.error.ErrorCode; +import com.example.mate.domain.file.FileValidator; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/src/test/java/com/example/mate/domain/goodsChat/controller/GoodsChatRoomControllerTest.java b/src/test/java/com/example/mate/domain/goodsChat/controller/GoodsChatRoomControllerTest.java index 366e3034..876c5c83 100644 --- a/src/test/java/com/example/mate/domain/goodsChat/controller/GoodsChatRoomControllerTest.java +++ b/src/test/java/com/example/mate/domain/goodsChat/controller/GoodsChatRoomControllerTest.java @@ -13,8 +13,6 @@ import com.example.mate.common.error.ErrorCode; import com.example.mate.common.response.PageResponse; import com.example.mate.common.security.util.JwtUtil; -import com.example.mate.common.security.filter.JwtCheckFilter; -import com.example.mate.common.security.util.JwtUtil; import com.example.mate.config.WithAuthMember; import com.example.mate.domain.goodsChat.dto.response.GoodsChatMessageResponse; import com.example.mate.domain.goodsChat.dto.response.GoodsChatRoomResponse; @@ -50,9 +48,6 @@ class GoodsChatRoomControllerTest { @MockBean private JwtUtil jwtUtil; - @MockBean - private JwtCheckFilter jwtCheckFilter; - @Test @DisplayName("굿즈거래 채팅방 생성 성공 - 기존 채팅방이 있을 경우 해당 채팅방을 반환한다.") void returnExistingChatRoom() throws Exception { diff --git a/src/test/java/com/example/mate/domain/mate/controller/MateControllerTest.java b/src/test/java/com/example/mate/domain/mate/controller/MateControllerTest.java index 19c84865..7978227c 100644 --- a/src/test/java/com/example/mate/domain/mate/controller/MateControllerTest.java +++ b/src/test/java/com/example/mate/domain/mate/controller/MateControllerTest.java @@ -1,26 +1,10 @@ package com.example.mate.domain.mate.controller; -import static com.example.mate.common.error.ErrorCode.ALREADY_COMPLETED_POST; -import static com.example.mate.common.error.ErrorCode.MATE_POST_NOT_FOUND_BY_ID; -import static com.example.mate.common.error.ErrorCode.MATE_POST_UPDATE_NOT_ALLOWED; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - import com.example.mate.common.error.CustomException; import com.example.mate.common.error.ErrorCode; import com.example.mate.common.response.PageResponse; import com.example.mate.common.security.util.JwtUtil; +import com.example.mate.config.WithAuthMember; import com.example.mate.domain.constant.Gender; import com.example.mate.domain.mate.dto.request.MatePostCreateRequest; import com.example.mate.domain.mate.dto.request.MatePostUpdateRequest; @@ -31,15 +15,14 @@ import com.example.mate.domain.mate.entity.Status; import com.example.mate.domain.mate.entity.TransportType; import com.example.mate.domain.mate.service.MateService; +import com.example.mate.domain.member.service.LogoutRedisService; import com.fasterxml.jackson.databind.ObjectMapper; -import java.time.LocalDateTime; -import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; import org.springframework.http.HttpMethod; @@ -48,10 +31,22 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -//@WebMvcTest(MateController.class) -@SpringBootTest +import java.time.LocalDateTime; +import java.util.List; + +import static com.example.mate.common.error.ErrorCode.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(MateController.class) @MockBean(JpaMetamodelMappingContext.class) @AutoConfigureMockMvc(addFilters = false) +@WithAuthMember class MateControllerTest { @Autowired @@ -66,6 +61,9 @@ class MateControllerTest { @MockBean private JwtUtil jwtUtil; + @MockBean + private LogoutRedisService logoutRedisService; + private MatePostSummaryResponse createMatePostSummaryResponse() { return MatePostSummaryResponse.builder() .imageUrl("test-image.jpg") @@ -87,7 +85,6 @@ class CreateMatePost { private MatePostCreateRequest createMatePostRequest() { return MatePostCreateRequest.builder() - .memberId(1L) .teamId(1L) .matchId(1L) .title("테스트 제목") @@ -127,7 +124,7 @@ void createMatePost_success() throws Exception { "test image content".getBytes() ); - given(mateService.createMatePost(any(MatePostCreateRequest.class), any())) + given(mateService.createMatePost(any(MatePostCreateRequest.class), any(), any())) .willReturn(response); // when & then @@ -146,6 +143,7 @@ void createMatePost_success() throws Exception { @DisplayName("메이트 게시글 작성 성공 - 이미지 없음") void createMatePost_successWithoutImage() throws Exception { // given + Long memberId = 1L; MatePostCreateRequest request = createMatePostRequest(); MatePostResponse response = createMatePostResponse(); @@ -156,7 +154,7 @@ void createMatePost_successWithoutImage() throws Exception { objectMapper.writeValueAsBytes(request) ); - given(mateService.createMatePost(any(MatePostCreateRequest.class), any())) + given(mateService.createMatePost(any(MatePostCreateRequest.class), any(), any())) .willReturn(response); // when & then @@ -468,7 +466,6 @@ private MatePostResponse createMatePostResponse() { @DisplayName("메이트 게시글 수정 성공") void updateMatePost_Success() throws Exception { // given - Long memberId = 1L; Long postId = 1L; MatePostUpdateRequest request = createMatePostUpdateRequest(); MatePostResponse response = createMatePostResponse(); @@ -487,12 +484,12 @@ void updateMatePost_Success() throws Exception { "test image content".getBytes() ); - given(mateService.updateMatePost(eq(memberId), eq(postId), any(MatePostUpdateRequest.class), any())) + given(mateService.updateMatePost(any(), eq(postId), any(MatePostUpdateRequest.class), any())) .willReturn(response); // when & then mockMvc.perform(MockMvcRequestBuilders - .multipart(HttpMethod.PATCH, "/api/mates/{memberId}/{postId}", memberId, postId) + .multipart(HttpMethod.PUT, "/api/mates/{postId}", postId) .file(file) .file(data)) .andDo(print()) @@ -502,14 +499,13 @@ void updateMatePost_Success() throws Exception { .andExpect(jsonPath("$.data.status").value("모집중")) .andExpect(jsonPath("$.code").value(200)); - verify(mateService).updateMatePost(eq(memberId), eq(postId), any(MatePostUpdateRequest.class), any()); + verify(mateService).updateMatePost(any(), eq(postId), any(MatePostUpdateRequest.class), any()); } @Test @DisplayName("메이트 게시글 수정 성공 - 이미지 없음") void updateMatePost_SuccessWithoutImage() throws Exception { // given - Long memberId = 1L; Long postId = 1L; MatePostUpdateRequest request = createMatePostUpdateRequest(); MatePostResponse response = createMatePostResponse(); @@ -521,12 +517,12 @@ void updateMatePost_SuccessWithoutImage() throws Exception { objectMapper.writeValueAsBytes(request) ); - given(mateService.updateMatePost(eq(memberId), eq(postId), any(MatePostUpdateRequest.class), isNull())) + given(mateService.updateMatePost(any(), eq(postId), any(MatePostUpdateRequest.class), isNull())) .willReturn(response); // when & then mockMvc.perform(MockMvcRequestBuilders - .multipart(HttpMethod.PATCH, "/api/mates/{memberId}/{postId}", memberId, postId) + .multipart(HttpMethod.PUT, "/api/mates/{postId}", postId) .file(data)) .andDo(print()) .andExpect(status().isOk()) @@ -535,14 +531,13 @@ void updateMatePost_SuccessWithoutImage() throws Exception { .andExpect(jsonPath("$.data.status").value("모집중")) .andExpect(jsonPath("$.code").value(200)); - verify(mateService).updateMatePost(eq(memberId), eq(postId), any(MatePostUpdateRequest.class), isNull()); + verify(mateService).updateMatePost(any(), eq(postId), any(MatePostUpdateRequest.class), isNull()); } @Test @DisplayName("메이트 게시글 수정 실패 - 유효하지 않은 요청 데이터") void updateMatePost_FailWithInvalidRequest() throws Exception { // given - Long memberId = 1L; Long postId = 1L; MatePostUpdateRequest request = MatePostUpdateRequest.builder() .teamId(null) // 필수 값 누락 @@ -564,7 +559,7 @@ void updateMatePost_FailWithInvalidRequest() throws Exception { // when & then mockMvc.perform(MockMvcRequestBuilders - .multipart(HttpMethod.PATCH, "/api/mates/{memberId}/{postId}", memberId, postId) + .multipart(HttpMethod.PUT, "/api/mates/{postId}", postId) .file(data)) .andDo(print()) .andExpect(status().isBadRequest()); @@ -576,7 +571,6 @@ void updateMatePost_FailWithInvalidRequest() throws Exception { @DisplayName("메이트 게시글 수정 실패 - 존재하지 않는 게시글") void updateMatePost_FailWithPostNotFound() throws Exception { // given - Long memberId = 1L; Long postId = 999L; MatePostUpdateRequest request = createMatePostUpdateRequest(); @@ -592,7 +586,7 @@ void updateMatePost_FailWithPostNotFound() throws Exception { // when & then mockMvc.perform(MockMvcRequestBuilders - .multipart(HttpMethod.PATCH, "/api/mates/{memberId}/{postId}", memberId, postId) + .multipart(HttpMethod.PUT, "/api/mates/{postId}", postId) .file(data)) .andDo(print()) .andExpect(status().isNotFound()) @@ -604,7 +598,6 @@ void updateMatePost_FailWithPostNotFound() throws Exception { @DisplayName("메이트 게시글 수정 실패 - 권한 없음") void updateMatePost_FailWithUnauthorized() throws Exception { // given - Long memberId = 999L; Long postId = 1L; MatePostUpdateRequest request = createMatePostUpdateRequest(); @@ -620,7 +613,7 @@ void updateMatePost_FailWithUnauthorized() throws Exception { // when & then mockMvc.perform(MockMvcRequestBuilders - .multipart(HttpMethod.PATCH, "/api/mates/{memberId}/{postId}", memberId, postId) + .multipart(HttpMethod.PUT, "/api/mates/{postId}", postId) .file(data)) .andDo(print()) .andExpect(status().isForbidden()) @@ -632,7 +625,6 @@ void updateMatePost_FailWithUnauthorized() throws Exception { @DisplayName("메이트 게시글 수정 실패 - 이미 완료된 게시글") void updateMatePost_FailWithCompletedPost() throws Exception { // given - Long memberId = 1L; Long postId = 1L; MatePostUpdateRequest request = createMatePostUpdateRequest(); @@ -648,7 +640,7 @@ void updateMatePost_FailWithCompletedPost() throws Exception { // when & then mockMvc.perform(MockMvcRequestBuilders - .multipart(HttpMethod.PATCH, "/api/mates/{memberId}/{postId}", memberId, postId) + .multipart(HttpMethod.PUT, "/api/mates/{postId}", postId) .file(data)) .andDo(print()) .andExpect(status().isForbidden()) @@ -669,7 +661,7 @@ void deleteMatePost_success() throws Exception { Long postId = 1L; // when & then - mockMvc.perform(delete("/api/mates/{memberId}/{postId}", memberId, postId) + mockMvc.perform(delete("/api/mates/{postId}", postId) .contentType(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().isNoContent()); @@ -689,7 +681,7 @@ void deleteMatePost_failPostNotFound() throws Exception { .deleteMatePost(memberId, nonExistentPostId); // when & then - mockMvc.perform(delete("/api/mates/{memberId}/{postId}", memberId, nonExistentPostId) + mockMvc.perform(delete("/api/mates/{postId}", nonExistentPostId) .contentType(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().isNotFound()) @@ -700,6 +692,7 @@ void deleteMatePost_failPostNotFound() throws Exception { @Test @DisplayName("메이트 게시글 삭제 실패 - 삭제 권한 없음") + @WithAuthMember(memberId = 2L) void deleteMatePost_failNotAllowed() throws Exception { // given Long memberId = 2L; // 작성자가 아닌 다른 사용자 @@ -710,7 +703,7 @@ void deleteMatePost_failNotAllowed() throws Exception { .deleteMatePost(memberId, postId); // when & then - mockMvc.perform(delete("/api/mates/{memberId}/{postId}", memberId, postId) + mockMvc.perform(delete("/api/mates/{postId}", postId) .contentType(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().isForbidden()) diff --git a/src/test/java/com/example/mate/domain/mate/controller/MateReviewControllerTest.java b/src/test/java/com/example/mate/domain/mate/controller/MateReviewControllerTest.java index f6bb84ab..0809a45a 100644 --- a/src/test/java/com/example/mate/domain/mate/controller/MateReviewControllerTest.java +++ b/src/test/java/com/example/mate/domain/mate/controller/MateReviewControllerTest.java @@ -1,13 +1,7 @@ package com.example.mate.domain.mate.controller; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - import com.example.mate.common.security.filter.JwtCheckFilter; +import com.example.mate.config.WithAuthMember; import com.example.mate.domain.constant.Rating; import com.example.mate.domain.mate.dto.request.MateReviewCreateRequest; import com.example.mate.domain.mate.dto.response.MateReviewCreateResponse; @@ -24,9 +18,17 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + @WebMvcTest(MateController.class) @MockBean(JpaMetamodelMappingContext.class) @AutoConfigureMockMvc(addFilters = false) +@WithAuthMember class MateReviewControllerTest { @Autowired @@ -68,7 +70,6 @@ private MateReviewCreateResponse createMateReviewResponse() { @DisplayName("메이트 직관 후기 작성 성공") void createMateReview_success() throws Exception { // given - Long memberId = 1L; Long postId = 1L; MateReviewCreateRequest request = createMateReviewRequest(); MateReviewCreateResponse response = createMateReviewResponse(); @@ -77,7 +78,7 @@ void createMateReview_success() throws Exception { .willReturn(response); // when & then - mockMvc.perform(post("/api/mates/{memberId}/{postId}/reviews", memberId, postId) + mockMvc.perform(post("/api/mates/{postId}/reviews", postId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -96,7 +97,6 @@ void createMateReview_success() throws Exception { @DisplayName("메이트 직관 후기 작성 실패 - 리뷰 대상자 ID 누락") void createMateReview_failWithoutRevieweeId() throws Exception { // given - Long memberId = 1L; Long postId = 1L; MateReviewCreateRequest request = MateReviewCreateRequest.builder() .rating(Rating.GOOD) @@ -104,7 +104,7 @@ void createMateReview_failWithoutRevieweeId() throws Exception { .build(); // when & then - mockMvc.perform(post("/api/mates/{memberId}/{postId}/reviews", memberId, postId) + mockMvc.perform(post("/api/mates/{postId}/reviews", postId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -128,7 +128,7 @@ void createMateReview_failWithLongContent() throws Exception { .build(); // when & then - mockMvc.perform(post("/api/mates/{memberId}/{postId}/reviews", memberId, postId) + mockMvc.perform(post("/api/mates/{postId}/reviews", memberId, postId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) diff --git a/src/test/java/com/example/mate/domain/mate/controller/MateStatusControllerTest.java b/src/test/java/com/example/mate/domain/mate/controller/MateStatusControllerTest.java index 33be6016..f1f282cb 100644 --- a/src/test/java/com/example/mate/domain/mate/controller/MateStatusControllerTest.java +++ b/src/test/java/com/example/mate/domain/mate/controller/MateStatusControllerTest.java @@ -1,25 +1,8 @@ package com.example.mate.domain.mate.controller; -import static com.example.mate.common.error.ErrorCode.ALREADY_COMPLETED_POST; -import static com.example.mate.common.error.ErrorCode.INVALID_MATE_POST_PARTICIPANT_IDS; -import static com.example.mate.common.error.ErrorCode.MATE_POST_COMPLETE_TIME_NOT_ALLOWED; -import static com.example.mate.common.error.ErrorCode.MATE_POST_MAX_PARTICIPANTS_EXCEEDED; -import static com.example.mate.common.error.ErrorCode.MATE_POST_NOT_FOUND_BY_ID; -import static com.example.mate.common.error.ErrorCode.MATE_POST_UPDATE_NOT_ALLOWED; -import static com.example.mate.common.error.ErrorCode.NOT_CLOSED_STATUS_FOR_COMPLETION; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - import com.example.mate.common.error.CustomException; -import com.example.mate.common.error.ErrorCode; import com.example.mate.common.security.filter.JwtCheckFilter; +import com.example.mate.config.WithAuthMember; import com.example.mate.domain.mate.dto.request.MatePostCompleteRequest; import com.example.mate.domain.mate.dto.request.MatePostStatusRequest; import com.example.mate.domain.mate.dto.response.MatePostCompleteResponse; @@ -27,7 +10,6 @@ import com.example.mate.domain.mate.entity.Status; import com.example.mate.domain.mate.service.MateService; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -39,9 +21,23 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import java.util.List; + +import static com.example.mate.common.error.ErrorCode.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + @WebMvcTest(MateController.class) @MockBean(JpaMetamodelMappingContext.class) @AutoConfigureMockMvc(addFilters = false) +@WithAuthMember class MateStatusControllerTest { @Autowired @@ -64,7 +60,6 @@ class UpdateMatePostStatus { @DisplayName("메이트 게시글 상태 변경 성공 - OPEN 상태로 변경") void updateMatePostStatus_successToOpen() throws Exception { // given - Long memberId = 1L; Long postId = 1L; List participantIds = List.of(2L, 3L); MatePostStatusRequest request = new MatePostStatusRequest(Status.OPEN, participantIds); @@ -73,11 +68,11 @@ void updateMatePostStatus_successToOpen() throws Exception { .status(Status.OPEN) .build(); - given(mateService.updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class))) + given(mateService.updateMatePostStatus(any(), eq(postId), any(MatePostStatusRequest.class))) .willReturn(response); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", memberId, postId) + mockMvc.perform(patch("/api/mates/{postId}/status", postId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -87,14 +82,13 @@ void updateMatePostStatus_successToOpen() throws Exception { .andExpect(jsonPath("$.data.status").value("모집중")) .andExpect(jsonPath("$.code").value(200)); - verify(mateService).updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class)); + verify(mateService).updateMatePostStatus(any(), eq(postId), any(MatePostStatusRequest.class)); } @Test @DisplayName("메이트 게시글 상태 변경 성공 - CLOSED 상태로 변경") void updateMatePostStatus_successToClosed() throws Exception { // given - Long memberId = 1L; Long postId = 1L; List participantIds = List.of(2L, 3L, 4L); MatePostStatusRequest request = new MatePostStatusRequest(Status.CLOSED, participantIds); @@ -103,11 +97,11 @@ void updateMatePostStatus_successToClosed() throws Exception { .status(Status.CLOSED) .build(); - given(mateService.updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class))) + given(mateService.updateMatePostStatus(any(), eq(postId), any(MatePostStatusRequest.class))) .willReturn(response); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", memberId, postId) + mockMvc.perform(patch("/api/mates/{postId}/status", postId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -117,24 +111,23 @@ void updateMatePostStatus_successToClosed() throws Exception { .andExpect(jsonPath("$.data.status").value("모집완료")) .andExpect(jsonPath("$.code").value(200)); - verify(mateService).updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class)); + verify(mateService).updateMatePostStatus(any(), eq(postId), any(MatePostStatusRequest.class)); } @Test @DisplayName("메이트 게시글 상태 변경 실패 - 존재하지 않는 게시글") void updateMatePostStatus_failPostNotFound() throws Exception { // given - Long memberId = 1L; Long nonExistentPostId = 999L; List participantIds = List.of(2L, 3L); MatePostStatusRequest request = new MatePostStatusRequest(Status.CLOSED, participantIds); - given(mateService.updateMatePostStatus(eq(memberId), eq(nonExistentPostId), + given(mateService.updateMatePostStatus(any(), eq(nonExistentPostId), any(MatePostStatusRequest.class))) .willThrow(new CustomException(MATE_POST_NOT_FOUND_BY_ID)); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", memberId, nonExistentPostId) + mockMvc.perform(patch("/api/mates/{postId}/status", nonExistentPostId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -143,49 +136,23 @@ void updateMatePostStatus_failPostNotFound() throws Exception { .andExpect(jsonPath("$.message").exists()) .andExpect(jsonPath("$.code").value(404)); - verify(mateService).updateMatePostStatus(eq(memberId), eq(nonExistentPostId), + verify(mateService).updateMatePostStatus(any(), eq(nonExistentPostId), any(MatePostStatusRequest.class)); } - @Test - @DisplayName("메이트 게시글 상태 변경 실패 - 권한 없음") - void updateMatePostStatus_failNotAuthorized() throws Exception { - // given - Long memberId = 2L; - Long postId = 1L; - List participantIds = List.of(2L, 3L); - MatePostStatusRequest request = new MatePostStatusRequest(Status.CLOSED, participantIds); - - given(mateService.updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class))) - .willThrow(new CustomException(ErrorCode.MATE_POST_UPDATE_NOT_ALLOWED)); - - // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", memberId, postId) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andDo(print()) - .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.status").value("ERROR")) - .andExpect(jsonPath("$.message").exists()) - .andExpect(jsonPath("$.code").value(403)); - - verify(mateService).updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class)); - } - @Test @DisplayName("메이트 게시글 상태 변경 실패 - VISIT_COMPLETE로 변경 시도") void updateMatePostStatus_failWithCompleteStatus() throws Exception { // given - Long memberId = 1L; Long postId = 1L; List participantIds = List.of(2L, 3L); MatePostStatusRequest request = new MatePostStatusRequest(Status.VISIT_COMPLETE, participantIds); - given(mateService.updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class))) + given(mateService.updateMatePostStatus(any(), eq(postId), any(MatePostStatusRequest.class))) .willThrow(new CustomException(ALREADY_COMPLETED_POST)); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", memberId, postId) + mockMvc.perform(patch("/api/mates/{postId}/status", postId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -194,23 +161,22 @@ void updateMatePostStatus_failWithCompleteStatus() throws Exception { .andExpect(jsonPath("$.message").exists()) .andExpect(jsonPath("$.code").value(403)); - verify(mateService).updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class)); + verify(mateService).updateMatePostStatus(any(), eq(postId), any(MatePostStatusRequest.class)); } @Test @DisplayName("메이트 게시글 상태 변경 실패 - 이미 완료된 게시글") void updateMatePostStatus_failAlreadyCompleted() throws Exception { // given - Long memberId = 1L; Long postId = 1L; List participantIds = List.of(2L, 3L); MatePostStatusRequest request = new MatePostStatusRequest(Status.CLOSED, participantIds); - given(mateService.updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class))) + given(mateService.updateMatePostStatus(any(), eq(postId), any(MatePostStatusRequest.class))) .willThrow(new CustomException(ALREADY_COMPLETED_POST)); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", memberId, postId) + mockMvc.perform(patch("/api/mates/{postId}/status", postId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -219,24 +185,23 @@ void updateMatePostStatus_failAlreadyCompleted() throws Exception { .andExpect(jsonPath("$.message").exists()) .andExpect(jsonPath("$.code").value(403)); - verify(mateService).updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class)); + verify(mateService).updateMatePostStatus(any(), eq(postId), any(MatePostStatusRequest.class)); } @Test @DisplayName("메이트 게시글 상태 변경 실패 - 참여자 수 초과") void updateMatePostStatus_failMaxParticipantsExceeded() throws Exception { // given - Long memberId = 1L; Long postId = 1L; // @Size(max = 9) 제약조건을 통과하도록 9명으로 수정 List participantIds = List.of(2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L); MatePostStatusRequest request = new MatePostStatusRequest(Status.CLOSED, participantIds); - given(mateService.updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class))) + given(mateService.updateMatePostStatus(any(), eq(postId), any(MatePostStatusRequest.class))) .willThrow(new CustomException(MATE_POST_MAX_PARTICIPANTS_EXCEEDED)); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", memberId, postId) + mockMvc.perform(patch("/api/mates/{postId}/status", postId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -245,21 +210,20 @@ void updateMatePostStatus_failMaxParticipantsExceeded() throws Exception { .andExpect(jsonPath("$.message").value(MATE_POST_MAX_PARTICIPANTS_EXCEEDED.getMessage())) .andExpect(jsonPath("$.code").value(400)); - verify(mateService).updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class)); + verify(mateService).updateMatePostStatus(any(), eq(postId), any(MatePostStatusRequest.class)); } @Test @DisplayName("메이트 게시글 상태 변경 실패 - 참여자 수 validation 실패") void updateMatePostStatus_failMaxParticipantsValidation() throws Exception { // given - Long memberId = 1L; Long postId = 1L; // @Size(max = 9) 제약조건을 초과하는 10명 List participantIds = List.of(2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L); MatePostStatusRequest request = new MatePostStatusRequest(Status.CLOSED, participantIds); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", memberId, postId) + mockMvc.perform(patch("/api/mates/{postId}/status", postId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -276,16 +240,15 @@ void updateMatePostStatus_failMaxParticipantsValidation() throws Exception { @DisplayName("메이트 게시글 상태 변경 실패 - 잘못된 참여자 ID") void updateMatePostStatus_failInvalidParticipantIds() throws Exception { // given - Long memberId = 1L; Long postId = 1L; List participantIds = List.of(999L, 998L); // 존재하지 않는 참여자 ID MatePostStatusRequest request = new MatePostStatusRequest(Status.CLOSED, participantIds); - given(mateService.updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class))) + given(mateService.updateMatePostStatus(any(), eq(postId), any(MatePostStatusRequest.class))) .willThrow(new CustomException(INVALID_MATE_POST_PARTICIPANT_IDS)); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", memberId, postId) + mockMvc.perform(patch("/api/mates/{postId}/status", postId) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -294,7 +257,7 @@ void updateMatePostStatus_failInvalidParticipantIds() throws Exception { .andExpect(jsonPath("$.message").exists()) .andExpect(jsonPath("$.code").value(400)); - verify(mateService).updateMatePostStatus(eq(memberId), eq(postId), any(MatePostStatusRequest.class)); + verify(mateService).updateMatePostStatus(any(), eq(postId), any(MatePostStatusRequest.class)); } } @@ -327,7 +290,7 @@ void completeVisit_success() throws Exception { .willReturn(response); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/complete", MEMBER_ID, POST_ID) + mockMvc.perform(patch("/api/mates/{postId}/complete", POST_ID) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -352,7 +315,7 @@ void completeVisit_failPostNotFound() throws Exception { .willThrow(new CustomException(MATE_POST_NOT_FOUND_BY_ID)); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/complete", MEMBER_ID, POST_ID) + mockMvc.perform(patch("/api/mates/{postId}/complete", POST_ID) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -372,7 +335,7 @@ void completeVisit_failNotAuthorized() throws Exception { .willThrow(new CustomException(MATE_POST_UPDATE_NOT_ALLOWED)); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/complete", MEMBER_ID, POST_ID) + mockMvc.perform(patch("/api/mates/{postId}/complete", POST_ID) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -392,7 +355,7 @@ void completeVisit_failBeforeMatchTime() throws Exception { .willThrow(new CustomException(MATE_POST_COMPLETE_TIME_NOT_ALLOWED)); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/complete", MEMBER_ID, POST_ID) + mockMvc.perform(patch("/api/mates/{postId}/complete", POST_ID) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -412,7 +375,7 @@ void completeVisit_failNotClosedStatus() throws Exception { .willThrow(new CustomException(NOT_CLOSED_STATUS_FOR_COMPLETION)); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/complete", MEMBER_ID, POST_ID) + mockMvc.perform(patch("/api/mates/{postId}/complete", POST_ID) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) @@ -432,7 +395,7 @@ void completeVisit_failExceededParticipants() throws Exception { .willThrow(new CustomException(MATE_POST_MAX_PARTICIPANTS_EXCEEDED)); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/complete", MEMBER_ID, POST_ID) + mockMvc.perform(patch("/api/mates/{postId}/complete", POST_ID) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) diff --git a/src/test/java/com/example/mate/domain/mate/integration/MateIntegrationTest.java b/src/test/java/com/example/mate/domain/mate/integration/MateIntegrationTest.java index 12a13a7c..8f3d4a66 100644 --- a/src/test/java/com/example/mate/domain/mate/integration/MateIntegrationTest.java +++ b/src/test/java/com/example/mate/domain/mate/integration/MateIntegrationTest.java @@ -1,6 +1,7 @@ package com.example.mate.domain.mate.integration; import com.example.mate.common.security.util.JwtUtil; +import com.example.mate.config.WithAuthMember; import com.example.mate.domain.constant.Gender; import com.example.mate.domain.match.entity.Match; import com.example.mate.domain.match.repository.MatchRepository; @@ -24,6 +25,7 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.PageRequest; import org.springframework.http.MediaType; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; @@ -60,6 +62,9 @@ public class MateIntegrationTest { @Autowired private MateRepository mateRepository; + @Autowired + private JdbcTemplate jdbcTemplate; + @MockBean private JwtUtil jwtUtil; @@ -78,6 +83,8 @@ void setUp() { matchRepository.deleteAll(); memberRepository.deleteAll(); + jdbcTemplate.execute("ALTER TABLE member ALTER COLUMN id RESTART WITH 1"); + // 테스트 멤버 생성 testMember = createTestMember(); @@ -151,10 +158,10 @@ class CreateMatePost { @Test @DisplayName("메이트 게시글 작성 성공") + @WithAuthMember void createMatePost_Success() throws Exception { // given MatePostCreateRequest request = MatePostCreateRequest.builder() - .memberId(testMember.getId()) .teamId(1L) .matchId(futureMatch.getId()) .title("통합 테스트 제목") @@ -198,36 +205,11 @@ void createMatePost_Success() throws Exception { assertThat(savedPost.getTransport()).isEqualTo(request.getTransportType()); } - @Test - @DisplayName("존재하지 않는 회원으로 메이트 게시글 작성 시 실패") - void createMatePost_WithInvalidMember() throws Exception { - MatePostCreateRequest request = MatePostCreateRequest.builder() - .memberId(999L) - .teamId(1L) - .matchId(futureMatch.getId()) - .title("통합 테스트 제목") - .content("통합 테스트 내용") - .age(Age.TWENTIES) - .maxParticipants(4) - .gender(Gender.FEMALE) - .transportType(TransportType.PUBLIC) - .build(); - - MockMultipartFile data = new MockMultipartFile( - "data", - "", - MediaType.APPLICATION_JSON_VALUE, - objectMapper.writeValueAsBytes(request) - ); - - performErrorTest(data, MEMBER_NOT_FOUND_BY_ID.getMessage(), 404); - } - @Test @DisplayName("존재하지 않는 경기로 메이트 게시글 작성 시 실패") + @WithAuthMember void createMatePost_WithInvalidMatch() throws Exception { MatePostCreateRequest request = MatePostCreateRequest.builder() - .memberId(testMember.getId()) .teamId(1L) .matchId(999L) .title("통합 테스트 제목") @@ -584,9 +566,10 @@ class DeleteMatePost { @Test @DisplayName("메이트 게시글 삭제 성공") + @WithAuthMember void deleteMatePost_Success() throws Exception { // when & then - mockMvc.perform(delete("/api/mates/{memberId}/{postId}", testMember.getId(), openPost.getId()) + mockMvc.perform(delete("/api/mates/{postId}", openPost.getId()) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()) .andDo(print()); @@ -597,9 +580,10 @@ void deleteMatePost_Success() throws Exception { @Test @DisplayName("메이트 게시글 삭제 실패 - 존재하지 않는 게시글") + @WithAuthMember void deleteMatePost_NotFound() throws Exception { // when & then - mockMvc.perform(delete("/api/mates/{memberId}/{postId}", testMember.getId(), 999L) + mockMvc.perform(delete("/api/mates/{postId}", 999L) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.status").value("ERROR")) @@ -610,51 +594,5 @@ void deleteMatePost_NotFound() throws Exception { // DB 검증 - 기존 게시글들은 여전히 존재 assertThat(mateRepository.findAll()).hasSize(3); } - - @Test - @DisplayName("메이트 게시글 삭제 실패 - 권한 없음") - void deleteMatePost_NotAllowed() throws Exception { - // given - Member otherMember = memberRepository.save(Member.builder() - .name("다른유저") - .email("other@test.com") - .nickname("다른계정") - .imageUrl("other.jpg") - .gender(Gender.MALE) - .age(30) - .manner(0.3f) - .build()); - - // when & then - mockMvc.perform(delete("/api/mates/{memberId}/{postId}", otherMember.getId(), openPost.getId()) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.status").value("ERROR")) - .andExpect(jsonPath("$.message").value(MATE_POST_UPDATE_NOT_ALLOWED.getMessage())) - .andExpect(jsonPath("$.code").value(403)) - .andDo(print()); - - // DB 검증 - 게시글이 삭제되지 않음 - assertThat(mateRepository.findById(openPost.getId())).isPresent(); - } -// -// @Test -// @DisplayName("직관 완료된 게시글 삭제 시 Visit 엔티티와 연관관계 제거") -// void deleteMatePost_WithCompletedStatus() throws Exception { -// // given -// MatePost post = createMatePost(futureMatch, 1L, Status.CLOSED); // CLOSED 상태로 생성 -// post.completeVisit(List.of(testMember.getId())); // completeVisit 호출하여 COMPLETE로 변경 -// Visit visit = post.getVisit(); -// -// // when -// mockMvc.perform(delete("/api/mates/{memberId}/{postId}", testMember.getId(), post.getId()) -// .contentType(MediaType.APPLICATION_JSON)) -// .andExpect(status().isNoContent()) -// .andDo(print()); -// -// // then -// assertThat(mateRepository.findById(post.getId())).isEmpty(); -// assertThat(visit.getPost()).isNull(); -// } } } \ No newline at end of file diff --git a/src/test/java/com/example/mate/domain/mate/integration/MateStatusIntegrationTest.java b/src/test/java/com/example/mate/domain/mate/integration/MateStatusIntegrationTest.java index a62c9faf..5e1a7488 100644 --- a/src/test/java/com/example/mate/domain/mate/integration/MateStatusIntegrationTest.java +++ b/src/test/java/com/example/mate/domain/mate/integration/MateStatusIntegrationTest.java @@ -18,6 +18,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.example.mate.common.security.util.JwtUtil; +import com.example.mate.config.WithAuthMember; import com.example.mate.domain.constant.Gender; import com.example.mate.domain.match.entity.Match; import com.example.mate.domain.match.repository.MatchRepository; @@ -44,6 +45,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; @@ -51,6 +53,7 @@ @SpringBootTest @AutoConfigureMockMvc(addFilters = false) @Transactional +@WithAuthMember public class MateStatusIntegrationTest { @Autowired @@ -68,6 +71,9 @@ public class MateStatusIntegrationTest { @Autowired private MateRepository mateRepository; + @Autowired + private JdbcTemplate jdbcTemplate; + private Member testMember; private Member participant1; private Member participant2; @@ -87,6 +93,8 @@ void setUp() { matchRepository.deleteAll(); memberRepository.deleteAll(); + jdbcTemplate.execute("ALTER TABLE member ALTER COLUMN id RESTART WITH 1"); + // 테스트 멤버와 참여자들 생성 testMember = createTestMember("testMember"); participant1 = createTestMember("part1"); @@ -151,7 +159,7 @@ void updateMatePostStatus_OpenToClosed_Success() throws Exception { MatePostStatusRequest request = new MatePostStatusRequest(Status.CLOSED, participantIds); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", testMember.getId(), openPost.getId()) + mockMvc.perform(patch("/api/mates/{postId}/status", openPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) @@ -174,7 +182,7 @@ void updateMatePostStatus_ClosedToOpen_Success() throws Exception { MatePostStatusRequest request = new MatePostStatusRequest(Status.OPEN, participantIds); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", testMember.getId(), closedPost.getId()) + mockMvc.perform(patch("/api/mates/{postId}/status", closedPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) @@ -197,7 +205,7 @@ void updateMatePostStatus_ToComplete_Failure() throws Exception { MatePostStatusRequest request = new MatePostStatusRequest(Status.VISIT_COMPLETE, participantIds); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", testMember.getId(), openPost.getId()) + mockMvc.perform(patch("/api/mates/{postId}/status", openPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isForbidden()) @@ -219,7 +227,7 @@ void updateMatePostStatus_AlreadyCompleted_Failure() throws Exception { MatePostStatusRequest request = new MatePostStatusRequest(Status.OPEN, participantIds); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", testMember.getId(), completedPost.getId()) + mockMvc.perform(patch("/api/mates/{postId}/status", completedPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isForbidden()) @@ -233,30 +241,6 @@ void updateMatePostStatus_AlreadyCompleted_Failure() throws Exception { assertThat(unchangedPost.getStatus()).isEqualTo(Status.VISIT_COMPLETE); } - @Test - @DisplayName("게시글 작성자가 아닌 사용자가 상태 변경 시도시 실패") - void updateMatePostStatus_NotAuthor_Failure() throws Exception { - // given - Member otherMember = createTestMember("otherMem"); - List participantIds = Arrays.asList(participant1.getId(), participant2.getId()); - MatePostStatusRequest request = new MatePostStatusRequest(Status.CLOSED, participantIds); - - // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", otherMember.getId(), openPost.getId()) - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isForbidden()) - .andExpect(jsonPath("$.status").value("ERROR")) - .andExpect(jsonPath("$.message").value(MATE_POST_UPDATE_NOT_ALLOWED.getMessage())) - .andExpect(jsonPath("$.code").value(403)) - .andDo(print()); - - // DB 검증 - MatePost unchangedPost = mateRepository.findById(openPost.getId()).orElseThrow(); - assertThat(unchangedPost.getStatus()).isEqualTo(Status.OPEN); - } - - @Test @DisplayName("존재하지 않는 게시글의 상태 변경 시도시 실패") void updateMatePostStatus_PostNotFound_Failure() throws Exception { @@ -265,7 +249,7 @@ void updateMatePostStatus_PostNotFound_Failure() throws Exception { MatePostStatusRequest request = new MatePostStatusRequest(Status.CLOSED, participantIds); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", testMember.getId(), 999L) + mockMvc.perform(patch("/api/mates/{postId}/status", 999L) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isNotFound()) @@ -288,7 +272,7 @@ void updateMatePostStatus_ExceedMaxParticipants_Failure() throws Exception { MatePostStatusRequest request = new MatePostStatusRequest(Status.CLOSED, participantIds); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", testMember.getId(), openPost.getId()) + mockMvc.perform(patch("/api/mates/{postId}/status", openPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()) @@ -310,7 +294,7 @@ void updateMatePostStatus_InvalidParticipantId_Failure() throws Exception { MatePostStatusRequest request = new MatePostStatusRequest(Status.CLOSED, participantIds); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/status", testMember.getId(), openPost.getId()) + mockMvc.perform(patch("/api/mates/{postId}/status", openPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andExpect(status().isBadRequest()) @@ -338,8 +322,8 @@ void completeVisit_Success() throws Exception { ); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/complete", - testMember.getId(), closedPost.getId()) + mockMvc.perform(patch("/api/mates/{postId}/complete", + closedPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsBytes(request))) .andExpect(status().isOk()) @@ -362,6 +346,7 @@ void completeVisit_Success() throws Exception { @Test @DisplayName("직관 완료 처리 실패 - 권한 없음") + @WithAuthMember(memberId = 2L) void completeVisit_Fail_NotAuthor() throws Exception { // given MatePostCompleteRequest request = new MatePostCompleteRequest( @@ -369,8 +354,8 @@ void completeVisit_Fail_NotAuthor() throws Exception { ); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/complete", - participant1.getId(), closedPost.getId()) + mockMvc.perform(patch("/api/mates/{postId}/complete" + ,closedPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsBytes(request))) .andExpect(status().isForbidden()) @@ -395,8 +380,8 @@ void completeVisit_Fail_NotClosedStatus() throws Exception { ); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/complete", - testMember.getId(), openPost.getId()) + mockMvc.perform(patch("/api/mates/{postId}/complete", + openPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsBytes(request))) .andExpect(status().isBadRequest()) @@ -421,8 +406,8 @@ void completeVisit_Fail_BeforeMatchTime() throws Exception { ); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/complete", - testMember.getId(), futureClosedPost.getId()) + mockMvc.perform(patch("/api/mates/{postId}/complete", + futureClosedPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsBytes(request))) .andExpect(status().isForbidden()) @@ -447,8 +432,8 @@ void completeVisit_Fail_InvalidParticipant() throws Exception { ); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/complete", - testMember.getId(), closedPost.getId()) + mockMvc.perform(patch("/api/mates/{postId}/complete", + closedPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsBytes(request))) .andExpect(status().isBadRequest()) @@ -473,8 +458,8 @@ void completeVisit_Fail_ExceedMaxParticipants() throws Exception { ); // when & then - mockMvc.perform(patch("/api/mates/{memberId}/{postId}/complete", - testMember.getId(), closedPost.getId()) + mockMvc.perform(patch("/api/mates/{postId}/complete", + closedPost.getId()) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsBytes(request))) .andExpect(status().isBadRequest()) diff --git a/src/test/java/com/example/mate/domain/mate/service/MateServiceTest.java b/src/test/java/com/example/mate/domain/mate/service/MateServiceTest.java index e0df0e6d..8eb5c642 100644 --- a/src/test/java/com/example/mate/domain/mate/service/MateServiceTest.java +++ b/src/test/java/com/example/mate/domain/mate/service/MateServiceTest.java @@ -95,7 +95,6 @@ void createMatePost_Success() { Match testMatch = createTestMatch(); MatePostCreateRequest request = MatePostCreateRequest.builder() - .memberId(TEST_MEMBER_ID) .teamId(TEST_MATCH_ID) .matchId(1L) .title("테스트 제목") @@ -120,7 +119,7 @@ void createMatePost_Success() { .transport(TransportType.PUBLIC) .build(); - given(memberRepository.findById(request.getMemberId())) + given(memberRepository.findById(TEST_MEMBER_ID)) .willReturn(Optional.of(testMember)); given(matchRepository.findById(request.getMatchId())) .willReturn(Optional.of(testMatch)); @@ -128,7 +127,7 @@ void createMatePost_Success() { .willReturn(matePost); // when - MatePostResponse response = mateService.createMatePost(request, null); + MatePostResponse response = mateService.createMatePost(request, null, TEST_MEMBER_ID); // then assertThat(response.getStatus()).isEqualTo(Status.OPEN); @@ -142,7 +141,6 @@ void createMatePost_Success() { void createMatePost_FailWithInvalidMember() { // given MatePostCreateRequest request = MatePostCreateRequest.builder() - .memberId(TEST_MEMBER_ID) .teamId(TEST_MATCH_ID) .matchId(1L) .title("테스트 제목") @@ -153,11 +151,11 @@ void createMatePost_FailWithInvalidMember() { .transportType(TransportType.PUBLIC) .build(); - given(memberRepository.findById(request.getMemberId())) + given(memberRepository.findById(TEST_MEMBER_ID)) .willReturn(Optional.empty()); // when & then - assertThatThrownBy(() -> mateService.createMatePost(request, null)) + assertThatThrownBy(() -> mateService.createMatePost(request, null, TEST_MEMBER_ID)) .isInstanceOf(CustomException.class) .hasFieldOrPropertyWithValue("errorCode", MEMBER_NOT_FOUND_BY_ID); @@ -172,7 +170,6 @@ void createMatePost_FailWithInvalidMatch() { // given Member testMember = createTestMember(); MatePostCreateRequest request = MatePostCreateRequest.builder() - .memberId(TEST_MEMBER_ID) .teamId(TEST_MATCH_ID) .matchId(1L) .title("테스트 제목") @@ -183,13 +180,13 @@ void createMatePost_FailWithInvalidMatch() { .transportType(TransportType.PUBLIC) .build(); - given(memberRepository.findById(request.getMemberId())) + given(memberRepository.findById(TEST_MEMBER_ID)) .willReturn(Optional.of(testMember)); given(matchRepository.findById(request.getMatchId())) .willReturn(Optional.empty()); // when & then - assertThatThrownBy(() -> mateService.createMatePost(request, null)) + assertThatThrownBy(() -> mateService.createMatePost(request, null, TEST_MEMBER_ID)) .isInstanceOf(CustomException.class) .hasFieldOrPropertyWithValue("errorCode", MATCH_NOT_FOUND_BY_ID); diff --git a/src/test/java/com/example/mate/domain/member/controller/MemberControllerTest.java b/src/test/java/com/example/mate/domain/member/controller/MemberControllerTest.java index 29d915f3..2535f5cc 100644 --- a/src/test/java/com/example/mate/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/com/example/mate/domain/member/controller/MemberControllerTest.java @@ -11,7 +11,6 @@ import com.example.mate.domain.member.dto.response.MemberLoginResponse; import com.example.mate.domain.member.dto.response.MemberProfileResponse; import com.example.mate.domain.member.dto.response.MyProfileResponse; -import com.example.mate.domain.member.service.LogoutRedisService; import com.example.mate.domain.member.service.MemberService; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.DisplayName; @@ -22,7 +21,6 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; @@ -54,9 +52,6 @@ class MemberControllerTest { @MockBean private JwtCheckFilter jwtCheckFilter; - @MockBean - private LogoutRedisService logoutRedisService; - private MyProfileResponse createMyProfileResponse() { return MyProfileResponse.builder() .nickname("tester") @@ -424,41 +419,4 @@ void login_member_success() throws Exception { .andDo(print()); } } - - @Nested - @DisplayName("회원 로그아웃") - class LogoutMember { - - @Test - @DisplayName("회원 로그아웃 성공") - void logout_member_success() throws Exception { - // given - String token = "Bearer accessToken"; - - doNothing().when(logoutRedisService).addTokenToBlacklist(anyString()); - - // when & then - mockMvc.perform(post("/api/members/logout") - .header(HttpHeaders.AUTHORIZATION, token)) - .andExpect(status().isNoContent()); - - verify(logoutRedisService).addTokenToBlacklist(token); - } - - @Test - @DisplayName("로그아웃 실패 - 잘못된 토큰 형식") - void catchMiLogout_invalid_token_format() throws Exception { - // given - String invalidToken = "InvalidToken"; - - willThrow(new CustomException(ErrorCode.INVALID_AUTH_TOKEN)) - .given(logoutRedisService).addTokenToBlacklist(invalidToken); - - - // when & then - mockMvc.perform(post("/api/members/logout") - .header(HttpHeaders.AUTHORIZATION, invalidToken)) - .andExpect(status().isBadRequest()); - } - } } \ No newline at end of file diff --git a/src/test/java/com/example/mate/domain/member/integration/MemberIntegrationTest.java b/src/test/java/com/example/mate/domain/member/integration/MemberIntegrationTest.java index 2d356f30..42f6081d 100644 --- a/src/test/java/com/example/mate/domain/member/integration/MemberIntegrationTest.java +++ b/src/test/java/com/example/mate/domain/member/integration/MemberIntegrationTest.java @@ -25,7 +25,6 @@ import com.example.mate.domain.member.entity.Member; import com.example.mate.domain.member.repository.FollowRepository; import com.example.mate.domain.member.repository.MemberRepository; -import com.example.mate.domain.member.service.LogoutRedisService; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.persistence.EntityManager; import org.junit.jupiter.api.BeforeEach; @@ -36,9 +35,6 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ValueOperations; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; @@ -48,13 +44,9 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.concurrent.TimeUnit; import static com.example.mate.domain.match.entity.MatchStatus.SCHEDULED; import static com.example.mate.domain.mate.entity.Status.CLOSED; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -104,15 +96,6 @@ class MemberIntegrationTest { @Autowired private EntityManager entityManager; - @Autowired - private LogoutRedisService logoutRedisService; - - @MockBean - private RedisTemplate redisTemplate; - - @MockBean - private ValueOperations valueOperations; - @MockBean private JwtUtil jwtUtil; @@ -494,41 +477,4 @@ void login_member_fail_non_exists_email() throws Exception { .andExpect(jsonPath("$.message").value("해당 이메일의 회원 정보를 찾을 수 없습니다.")); } } - - @Nested - @DisplayName("회원 로그아웃") - class LogoutMember { - - @Test - @DisplayName("회원 로그아웃 성공") - @WithAuthMember(userId = "customUser", memberId = 1L) - void logout_member_success_with_my_info_denied() throws Exception { - // given - String token = "Bearer accessToken"; - - // when & then - when(redisTemplate.opsForValue()).thenReturn(valueOperations); - doNothing().when(valueOperations).set( - eq("blacklist:" + token.substring(7)), - eq("blacklisted"), - eq(1L), - eq(TimeUnit.MINUTES)); - - mockMvc.perform(post("/api/members/logout") - .header(HttpHeaders.AUTHORIZATION, token)) - .andExpect(status().isNoContent()); - } - - @Test - @DisplayName("회원 로그아웃 실패 - 잘못된 토큰 형식") - void catchMiLogout_invalid_token_format() throws Exception { - // given - String invalidToken = "InvalidToken"; - - // when & then - mockMvc.perform(post("/api/members/logout") - .header(HttpHeaders.AUTHORIZATION, invalidToken)) - .andExpect(status().isBadRequest()); - } - } } \ No newline at end of file diff --git a/src/test/java/com/example/mate/domain/member/service/LogoutRedisServiceTest.java b/src/test/java/com/example/mate/domain/member/service/LogoutRedisServiceTest.java deleted file mode 100644 index 31096c4b..00000000 --- a/src/test/java/com/example/mate/domain/member/service/LogoutRedisServiceTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.example.mate.domain.member.service; - -import com.example.mate.common.error.CustomException; -import com.example.mate.common.error.ErrorCode; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ValueOperations; - -import java.util.concurrent.TimeUnit; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class LogoutRedisServiceTest { - - @InjectMocks - private LogoutRedisService logoutRedisService; - - @Mock - private RedisTemplate redisTemplate; - - @Mock - private ValueOperations valueOperations; - - @Nested - @DisplayName("회원 로그아웃") - class LogoutMember { - - @Test - @DisplayName("회원 로그아웃 성공 - 블랙리스트에 토큰 추가") - void add_token_to_blacklist_success() { - // given - String token = "Bearer accessToken"; - when(redisTemplate.opsForValue()).thenReturn(valueOperations); - doNothing().when(valueOperations).set( - "blacklist:" + token.substring(7), "blacklisted", 1, TimeUnit.MINUTES - ); - - // when & then - assertDoesNotThrow(() -> logoutRedisService.addTokenToBlacklist(token)); - verify(redisTemplate.opsForValue(), times(1)).set( - "blacklist:accessToken", "blacklisted", 1, TimeUnit.MINUTES - ); - } - - @Test - @DisplayName("회원 로그아웃 실패 - 잘못된 토큰으로 블랙리스트 추가 시 CustomException") - void add_token_to_blacklist_fail_invalid_token() { - // given - String invalidToken = "InvalidToken"; - - // when & then - assertThatThrownBy(() -> logoutRedisService.addTokenToBlacklist(invalidToken)) - .isInstanceOf(CustomException.class) - .hasMessage(ErrorCode.INVALID_AUTH_TOKEN.getMessage()); - } - } - - @Nested - @DisplayName("블랙리스트에 토큰 여부 확인") - class CheckBlacklist { - - @Test - @DisplayName("블랙리스트에 토큰 여부 확인 성공") - void is_token_blacklisted_success() { - // given - String accessToken = "accessToken"; - when(redisTemplate.hasKey("blacklist:" + accessToken)).thenReturn(true); - - // when - boolean result = logoutRedisService.isTokenBlacklisted(accessToken); - - // then - verify(redisTemplate, times(1)).hasKey("blacklist:accessToken"); - assert result; - } - - @Test - @DisplayName("블랙리스트에 토큰 여부 확인 실패 - 블랙리스트에 존재하지 않는 토큰 확인") - void is_token_blacklisted_fail_not_exists_token() { - // given - String accessToken = "accessToken"; - when(redisTemplate.hasKey("blacklist:" + accessToken)).thenReturn(false); - - // when - boolean result = logoutRedisService.isTokenBlacklisted(accessToken); - - // then - verify(redisTemplate, times(1)).hasKey("blacklist:accessToken"); - assert !result; - } - } -}