From 2908bc201d5a2db7459a36ce88e77440af81ab48 Mon Sep 17 00:00:00 2001 From: willjsw Date: Mon, 27 Oct 2025 14:46:39 +0900 Subject: [PATCH 1/9] =?UTF-8?q?test=20#196:=20DeliveryAddressController=20?= =?UTF-8?q?WebMVC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/_delivery_address.adoc | 105 ++++ src/docs/asciidoc/index.adoc | 1 + .../DeliveryAddressControllerTest.java | 478 ++++++++++++++++++ 3 files changed, 584 insertions(+) create mode 100644 src/docs/asciidoc/_delivery_address.adoc create mode 100644 src/test/java/com/irum/come2us/domain/deliveryadress/DeliveryAddressControllerTest.java diff --git a/src/docs/asciidoc/_delivery_address.adoc b/src/docs/asciidoc/_delivery_address.adoc new file mode 100644 index 00000000..b3bfb6e8 --- /dev/null +++ b/src/docs/asciidoc/_delivery_address.adoc @@ -0,0 +1,105 @@ +== 배송지 주소 (Delivery Address) API + +=== 1. 배송지 주소 등록 +`POST /address` + +요청 본문: +include::{snippets}/delivery-address-register/request-fields.adoc[] + +응답: +include::{snippets}/delivery-address-register/http-response.adoc[] + +==== 배송지 주소 등록 실패: 유효성 검사 +* `우편번호 누락` +include::{snippets}/delivery-address-register-fail-postal-code-missing/http-response.adoc[] +include::{snippets}/delivery-address-register-fail-postal-code-missing/response-fields.adoc[] + +--- + +=== 2. 배송지 주소 조회 +`GET /address/{deliveryAddressId}/info` + +경로 파라미터 (Path Parameters): +include::{snippets}/delivery-address-info-get/path-parameters.adoc[] + +응답 본문: +include::{snippets}/delivery-address-info-get/response-fields.adoc[] + +응답: +include::{snippets}/delivery-address-info-get/http-response.adoc[] + +==== 배송지 주소 조회 실패: 존재하지 않는 주소 ID +include::{snippets}/delivery-address-info-get-fail-not-found/http-response.adoc[] +include::{snippets}/delivery-address-info-get-fail-not-found/response-fields.adoc[] + +--- + +=== 3. 배송지 주소 목록 조회 +`GET /address` + +쿼리 파라미터 (Query Parameters): +include::{snippets}/delivery-address-info-list-get/query-parameters.adoc[] + +응답 본문: +include::{snippets}/delivery-address-info-list-get/response-fields.adoc[] + +응답: +include::{snippets}/delivery-address-info-list-get/http-response.adoc[] + +--- + +=== 4. 수취인 정보 수정 +`PATCH /address/{deliveryAddressId}/recipient` + +경로 파라미터 (Path Parameters): +include::{snippets}/delivery-address-recipient-update/path-parameters.adoc[] + +요청 본문: +include::{snippets}/delivery-address-recipient-update/request-fields.adoc[] + +응답: +include::{snippets}/delivery-address-recipient-update/http-response.adoc[] + +==== 수취인 정보 수정 실패: 존재하지 않는 주소 ID +include::{snippets}/delivery-address-recipient-update-fail-not-found/http-response.adoc[] +include::{snippets}/delivery-address-recipient-update-fail-not-found/response-fields.adoc[] + +--- + +=== 5. 상세 주소 수정 +`PATCH /address/{deliveryAddressId}/detail` + +경로 파라미터 (Path Parameters): +include::{snippets}/delivery-address-detail-update/path-parameters.adoc[] + +요청 본문: +include::{snippets}/delivery-address-detail-update/request-fields.adoc[] + +응답: +include::{snippets}/delivery-address-detail-update/http-response.adoc[] + +--- + +=== 6. 기본 배송지 설정 +`PATCH /address/{deliveryAddressId}/default` + +경로 파라미터 (Path Parameters): +include::{snippets}/delivery-address-default-set/path-parameters.adoc[] + +응답: +include::{snippets}/delivery-address-default-set/http-response.adoc[] + +--- + +=== 7. 배송지 주소 삭제 +`DELETE /address/{deliveryAddressId}` + +경로 파라미터 (Path Parameters): +include::{snippets}/delivery-address-delete/path-parameters.adoc[] + +응답: +include::{snippets}/delivery-address-delete/http-response.adoc[] + +==== 배송지 주소 삭제 실패: 권한 없음 (다른 회원의 주소 삭제 시도) +include::{snippets}/delivery-address-delete-fail-no-permission/http-response.adoc[] +include::{snippets}/delivery-address-delete-fail-no-permission/response-fields.adoc[] \ No newline at end of file diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index bb7a95dc..51fbd65b 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -8,6 +8,7 @@ include::_member.adoc[] include::_manager.adoc[] +include::_manager.adoc[] // include::_product.adoc[] diff --git a/src/test/java/com/irum/come2us/domain/deliveryadress/DeliveryAddressControllerTest.java b/src/test/java/com/irum/come2us/domain/deliveryadress/DeliveryAddressControllerTest.java new file mode 100644 index 00000000..720485db --- /dev/null +++ b/src/test/java/com/irum/come2us/domain/deliveryadress/DeliveryAddressControllerTest.java @@ -0,0 +1,478 @@ +package com.irum.come2us.domain.deliveryadress; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.irum.come2us.domain.deliveryaddress.application.service.DeliveryAddressService; +import com.irum.come2us.domain.deliveryaddress.domain.entity.Address; +import com.irum.come2us.domain.deliveryaddress.presentation.controller.DeliveryAddressController; +import com.irum.come2us.domain.deliveryaddress.presentation.dto.request.AddressDetailUpdateRequest; +import com.irum.come2us.domain.deliveryaddress.presentation.dto.request.DeliveryAddressRegisterRequest; +import com.irum.come2us.domain.deliveryaddress.presentation.dto.request.RecipientUpdateRequest; +import com.irum.come2us.domain.deliveryaddress.presentation.dto.response.DeliveryAddressInfoListResponse; +import com.irum.come2us.domain.deliveryaddress.presentation.dto.response.DeliveryAddressInfoResponse; +import com.irum.come2us.global.config.SecurityTestConfig; +import com.irum.come2us.global.presentation.advice.exception.CommonException; +import com.irum.come2us.global.presentation.advice.exception.errorcode.DeliveryAddressErrorCode; +import com.irum.come2us.global.presentation.advice.exception.errorcode.MemberErrorCode; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(DeliveryAddressController.class) +@AutoConfigureRestDocs +@Import(SecurityTestConfig.class) +public class DeliveryAddressControllerTest { + + @Autowired private MockMvc mockMvc; + @Autowired private DeliveryAddressService deliveryAddressService; + @Autowired private ObjectMapper objectMapper; + + @TestConfiguration + static class TestConfig { + @Bean + public DeliveryAddressService deliveryAddressService() { + return Mockito.mock(DeliveryAddressService.class); + } + } + + @Test + @DisplayName("배송지 주소 등록 API 테스트") + void deliveryAddressRegisterApiTest() throws Exception { + DeliveryAddressRegisterRequest request = + new DeliveryAddressRegisterRequest( + "12345", "서울특별시", "강남구", "테헤란로 145", "더피나클 역삼2", "회원1", "010-1234-5678"); + String requestJson = objectMapper.writeValueAsString(request); + + doNothing() + .when(deliveryAddressService) + .createDeliveryAddress(any(DeliveryAddressRegisterRequest.class)); + mockMvc.perform( + post("/address") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.CREATED.value())) + .andDo( + document( + "delivery-address-register", + requestFields( + fieldWithPath("postalCode").description("우편번호"), + fieldWithPath("city").description("시/도명"), + fieldWithPath("sigungu").description("시/군/구"), + fieldWithPath("roadName").description("도로명"), + fieldWithPath("addressDetail").description("상세 주소"), + fieldWithPath("recipientName").description("수취인 이름"), + fieldWithPath("recipientContact").description("수취인 연락처")))); + } + + @Test + @DisplayName("배송지 주소 조회 API 테스트") + void deliveryAddressInfoGetApiTest() throws Exception { + UUID deliveryAddressId = UUID.randomUUID(); + Address address = Address.create("12345", "서울특별시", "강남구", "테헤란로 145", "더피나클 역삼2"); + DeliveryAddressInfoResponse response = + new DeliveryAddressInfoResponse( + deliveryAddressId, address, "회원1", "010-1234-5678", true); + when(deliveryAddressService.findDeliveryAddress(eq(deliveryAddressId))) + .thenReturn(response); + + mockMvc.perform(get("/address/{deliveryAddressId}/info", deliveryAddressId).with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.OK.value())) + .andExpect( + jsonPath("$.data.deliveryAddressId") + .value(response.deliveryAddressId().toString())) + .andExpect( + jsonPath("$.data.address.postalCode") + .value(response.address().getPostalCode())) + .andExpect(jsonPath("$.data.address.city").value(response.address().getCity())) + .andExpect( + jsonPath("$.data.address.sigungu").value(response.address().getSigungu())) + .andExpect( + jsonPath("$.data.address.roadName").value(response.address().getRoadName())) + .andExpect( + jsonPath("$.data.address.addressDetail") + .value(response.address().getAddressDetail())) + .andExpect(jsonPath("$.data.recipientName").value(response.recipientName())) + .andExpect(jsonPath("$.data.recipientContact").value(response.recipientContact())) + .andExpect(jsonPath("$.data.isDefault").value(response.isDefault())) + .andDo( + document( + "delivery-address-info-get", + pathParameters( + parameterWithName("deliveryAddressId") + .description("배송지 주소 ID")), + responseFields( + fieldWithPath("success").description("true"), + fieldWithPath("status").description("200"), + fieldWithPath("timestamp").description("응답 시간"), + fieldWithPath("data.deliveryAddressId") + .description("배송지 주소 아이디"), + fieldWithPath("data.address.postalCode") + .description("우편번호"), + fieldWithPath("data.address.city").description("시/도명"), + fieldWithPath("data.address.sigungu").description("시/군/구"), + fieldWithPath("data.address.roadName").description("도로명"), + fieldWithPath("data.address.addressDetail") + .description("상세 주소"), + fieldWithPath("data.recipientName").description("수취인 이름"), + fieldWithPath("data.recipientContact") + .description("수취인 연락처"), + fieldWithPath("data.isDefault").description("기본배송지 여부")))); + } + + @Test + @DisplayName("배송지 주소 목록 조회 API 테스트") + void deliveryAddressInfoListGetApiTest() throws Exception { + UUID deliveryAddressId1 = UUID.randomUUID(); + UUID deliveryAddressId2 = UUID.randomUUID(); + + Address address1 = Address.create("12345", "서울특별시", "강남구", "테헤란로 145", "더피나클 역삼1"); + Address address2 = Address.create("12345", "서울특별시", "강남구", "테헤란로 145", "더피나클 역삼2"); + + List addressInfoList = + List.of( + new DeliveryAddressInfoResponse( + deliveryAddressId2, address2, "회원1", "010-1234-5678", true), + new DeliveryAddressInfoResponse( + deliveryAddressId1, address1, "회원1", "010-1234-5678", true)); + + DeliveryAddressInfoListResponse response = + new DeliveryAddressInfoListResponse(addressInfoList, null, false); + when(deliveryAddressService.findDeliveryAddressList(any(), eq(10))).thenReturn(response); + + mockMvc.perform(get("/address").param("size", "10").with(csrf().asHeader())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.OK.value())) + .andExpect(jsonPath("$.data.hasNext").value(false)) + .andExpect(jsonPath("$.data.nextCursor").isEmpty()) + .andExpect( + jsonPath("$.data.deliveryAddressInfoList[0].deliveryAddressId") + .value(deliveryAddressId2.toString())) + .andDo( + document( + "delivery-address-info-list-get", + queryParameters( + parameterWithName("lastId") + .description("마지막으로 조회된 배송지 ID (첫 페이지는 생략)") + .optional(), + parameterWithName("size") + .description("조회할 페이지 크기 (기본값 10)") + .optional()), + responseFields( + fieldWithPath("success").description("true"), + fieldWithPath("status").description("200"), + fieldWithPath("timestamp").description("응답 시간"), + fieldWithPath("data.deliveryAddressInfoList[]") + .description("배송지 주소 목록"), + fieldWithPath( + "data.deliveryAddressInfoList[].deliveryAddressId") + .description("배송지 주소 아이디"), + fieldWithPath( + "data.deliveryAddressInfoList[].address.postalCode") + .description("우편번호"), + fieldWithPath("data.deliveryAddressInfoList[].address.city") + .description("시/도명"), + fieldWithPath( + "data.deliveryAddressInfoList[].address.sigungu") + .description("시/군/구"), + fieldWithPath( + "data.deliveryAddressInfoList[].address.roadName") + .description("도로명"), + fieldWithPath( + "data.deliveryAddressInfoList[].address.addressDetail") + .description("상세 주소"), + fieldWithPath( + "data.deliveryAddressInfoList[].recipientName") + .description("수취인 이름"), + fieldWithPath( + "data.deliveryAddressInfoList[].recipientContact") + .description("수취인 연락처"), + fieldWithPath("data.deliveryAddressInfoList[].isDefault") + .description("기본배송지 여부"), + fieldWithPath("data.nextCursor") + .description( + "다음 페이지 조회를 위한 커서 ID (다음 페이지 없으면 null)"), + fieldWithPath("data.hasNext") + .description("다음 페이지 존재 여부")))); + } + + @Test + @DisplayName("수취인 정보 수정 API 테스트") + void recipientInfoUpdateApiTest() throws Exception { + UUID deliveryAddressId = UUID.randomUUID(); + RecipientUpdateRequest request = new RecipientUpdateRequest("새 수취인", "010-9999-9999"); + String requestJson = objectMapper.writeValueAsString(request); + + doNothing() + .when(deliveryAddressService) + .changeRecipientInfo(eq(deliveryAddressId), any(RecipientUpdateRequest.class)); + + mockMvc.perform( + patch("/address/{deliveryAddressId}/recipient", deliveryAddressId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isNoContent()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.NO_CONTENT.value())) + .andDo( + document( + "delivery-address-recipient-update", + pathParameters( + parameterWithName("deliveryAddressId") + .description("수정할 배송지 ID")), + requestFields( + fieldWithPath("newRecipientName").description("변경할 수취인 이름"), + fieldWithPath("newRecipientContact") + .description("변경할 수취인 연락처")))); + } + + @Test + @DisplayName("상세 주소 수정 API 테스트") + void addressDetailUpdateApiTest() throws Exception { + UUID deliveryAddressId = UUID.randomUUID(); + AddressDetailUpdateRequest request = new AddressDetailUpdateRequest("새로운 상세 주소"); + String requestJson = objectMapper.writeValueAsString(request); + + doNothing() + .when(deliveryAddressService) + .changeAddressDetail(eq(deliveryAddressId), any(AddressDetailUpdateRequest.class)); + + mockMvc.perform( + patch("/address/{deliveryAddressId}/detail", deliveryAddressId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isNoContent()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.NO_CONTENT.value())) + .andDo( + document( + "delivery-address-detail-update", + pathParameters( + parameterWithName("deliveryAddressId") + .description("수정할 배송지 ID")), + requestFields( + fieldWithPath("newAddressDetail") + .description("변경할 상세 주소")))); + } + + @Test + @DisplayName("기본 배송지 설정 API 테스트") + void defaultDeliveryAddressSetApiTest() throws Exception { + UUID deliveryAddressId = UUID.randomUUID(); + + doNothing() + .when(deliveryAddressService) + .changeDefaultDeliveryAddress(eq(deliveryAddressId)); + + mockMvc.perform( + patch("/address/{deliveryAddressId}/default", deliveryAddressId) + .with(csrf())) + .andExpect(status().isNoContent()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.NO_CONTENT.value())) + .andDo( + document( + "delivery-address-default-set", + pathParameters( + parameterWithName("deliveryAddressId") + .description("기본 배송지로 설정할 ID")))); + } + + @Test + @DisplayName("배송지 주소 삭제 API 테스트") + void deliveryAddressDeleteApiTest() throws Exception { + UUID deliveryAddressId = UUID.randomUUID(); + + doNothing().when(deliveryAddressService).removeDeliveryAddress(eq(deliveryAddressId)); + + mockMvc.perform(delete("/address/{deliveryAddressId}", deliveryAddressId).with(csrf())) + .andExpect(status().isNoContent()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.NO_CONTENT.value())) + .andDo( + document( + "delivery-address-delete", + pathParameters( + parameterWithName("deliveryAddressId") + .description("삭제할 배송지 ID")))); + } + + @Test + @DisplayName("배송지 주소 등록 실패 - 우편번호 누락") + void deliveryAddressRegisterApiTest_Fail_PostalCodeMissing() throws Exception { + DeliveryAddressRegisterRequest request = + new DeliveryAddressRegisterRequest( + "", "서울특별시", "강남구", "테헤란로 145", "더피나클 역삼2", "회원1", "010-1234-5678"); + String requestJson = objectMapper.writeValueAsString(request); + + mockMvc.perform( + post("/address") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isBadRequest()) + .andExpect( + jsonPath("$.data.errorClassName").value("MethodArgumentNotValidException")) + .andExpect(jsonPath("$.data.message").value("우편번호는 필수 입력값입니다.")) + .andDo( + document( + "delivery-address-register-fail-postal-code-missing", + responseFields( + fieldWithPath("success").description("false"), + fieldWithPath("status").description("400"), + fieldWithPath("timestamp").description("에러 발생 시간"), + fieldWithPath("data.errorClassName").description("에러 종류"), + fieldWithPath("data.message").description("에러 메세지")))); + } + + @Test + @DisplayName("배송지 주소 조회 실패 - 존재하지 않는 주소 ID") + void deliveryAddressInfoGetApiTest_Fail_NotFound() throws Exception { + UUID notFoundId = UUID.randomUUID(); + doThrow(new CommonException(DeliveryAddressErrorCode.DELIVERY_ADDRESS_NOT_FOUND)) + .when(deliveryAddressService) + .findDeliveryAddress(eq(notFoundId)); + + mockMvc.perform(get("/address/{deliveryAddressId}/info", notFoundId).with(csrf())) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect( + jsonPath("$.status") + .value( + DeliveryAddressErrorCode.DELIVERY_ADDRESS_NOT_FOUND + .getHttpStatus() + .value())) + .andExpect( + jsonPath("$.data.errorClassName") + .value(DeliveryAddressErrorCode.DELIVERY_ADDRESS_NOT_FOUND.name())) + .andExpect( + jsonPath("$.data.message") + .value( + DeliveryAddressErrorCode.DELIVERY_ADDRESS_NOT_FOUND + .getMessage())) + .andDo( + document( + "delivery-address-info-get-fail-not-found", + pathParameters( + parameterWithName("deliveryAddressId") + .description("조회할 배송지 ID")), + responseFields( + fieldWithPath("success").description("false"), + fieldWithPath("status").description("에러 상태 코드 (404)"), + fieldWithPath("timestamp").description("에러 발생 시간"), + fieldWithPath("data.errorClassName") + .description("에러 코드 (DELIVERY_ADDRESS_NOT_FOUND)"), + fieldWithPath("data.message").description("에러 메세지")))); + } + + @Test + @DisplayName("수취인 정보 수정 실패 - 존재하지 않는 주소 ID") + void recipientInfoUpdateApiTest_Fail_NotFound() throws Exception { + UUID notFoundId = UUID.randomUUID(); + RecipientUpdateRequest request = new RecipientUpdateRequest("새 수취인", "010-9999-9999"); + String requestJson = objectMapper.writeValueAsString(request); + + doThrow(new CommonException(DeliveryAddressErrorCode.DELIVERY_ADDRESS_NOT_FOUND)) + .when(deliveryAddressService) + .changeRecipientInfo(eq(notFoundId), any(RecipientUpdateRequest.class)); + + mockMvc.perform( + patch("/address/{deliveryAddressId}/recipient", notFoundId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect( + jsonPath("$.status") + .value( + DeliveryAddressErrorCode.DELIVERY_ADDRESS_NOT_FOUND + .getHttpStatus() + .value())) + .andExpect( + jsonPath("$.data.errorClassName") + .value(DeliveryAddressErrorCode.DELIVERY_ADDRESS_NOT_FOUND.name())) + .andExpect( + jsonPath("$.data.message") + .value( + DeliveryAddressErrorCode.DELIVERY_ADDRESS_NOT_FOUND + .getMessage())) + .andDo( + document( + "delivery-address-recipient-update-fail-not-found", + pathParameters( + parameterWithName("deliveryAddressId") + .description("수정할 배송지 ID")), + responseFields( + fieldWithPath("success").description("false"), + fieldWithPath("status").description("에러 상태 코드 (404)"), + fieldWithPath("timestamp").description("에러 발생 시간"), + fieldWithPath("data.errorClassName") + .description("에러 코드 (DELIVERY_ADDRESS_NOT_FOUND)"), + fieldWithPath("data.message").description("에러 메세지")))); + } + + @Test + @DisplayName("배송지 주소 삭제 실패 - 다른 회원의 주소 삭제 시도") + void deliveryAddressDeleteApiTest_Fail_NoPermission() throws Exception { + UUID otherUserIdAddressId = UUID.randomUUID(); + + doThrow(new CommonException(MemberErrorCode.UNAUTHORIZED_ACCESS)) + .when(deliveryAddressService) + .removeDeliveryAddress(eq(otherUserIdAddressId)); + + mockMvc.perform(delete("/address/{deliveryAddressId}", otherUserIdAddressId).with(csrf())) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect( + jsonPath("$.status") + .value(MemberErrorCode.UNAUTHORIZED_ACCESS.getHttpStatus().value())) + .andExpect( + jsonPath("$.data.errorClassName") + .value(MemberErrorCode.UNAUTHORIZED_ACCESS.name())) + .andExpect( + jsonPath("$.data.message") + .value(MemberErrorCode.UNAUTHORIZED_ACCESS.getMessage())) + .andDo( + document( + "delivery-address-delete-fail-no-permission", + pathParameters( + parameterWithName("deliveryAddressId") + .description("삭제 시도할 배송지 ID")), + responseFields( + fieldWithPath("success").description("false"), + fieldWithPath("status").description("에러 상태 코드 (403)"), + fieldWithPath("timestamp").description("에러 발생 시간"), + fieldWithPath("data.errorClassName") + .description("에러 코드 (NO_PERMISSION_TO_RESOURCE)"), + fieldWithPath("data.message").description("에러 메세지")))); + } +} From 78a2b56afc255ed18f4de73de3bf3a216428ee8b Mon Sep 17 00:00:00 2001 From: willjsw Date: Mon, 27 Oct 2025 14:47:21 +0900 Subject: [PATCH 2/9] =?UTF-8?q?refactor=20#196:=20DeliveryController=20?= =?UTF-8?q?=ED=83=9C=EC=8A=A4=ED=8A=B8=20=EA=B2=B0=EA=B3=BC=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=EA=B8=B0=EC=A1=B4=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/DeliveryAddressService.java | 39 ++++++++++++------- .../controller/DeliveryAddressController.java | 33 +++++++++------- .../request/AddressDetailUpdateRequest.java | 4 +- .../dto/request/RecipientUpdateRequest.java | 5 +-- .../response/DeliveryAddressInfoResponse.java | 6 +-- 5 files changed, 49 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/irum/come2us/domain/deliveryaddress/application/service/DeliveryAddressService.java b/src/main/java/com/irum/come2us/domain/deliveryaddress/application/service/DeliveryAddressService.java index c67e2ccc..a7ea09d2 100644 --- a/src/main/java/com/irum/come2us/domain/deliveryaddress/application/service/DeliveryAddressService.java +++ b/src/main/java/com/irum/come2us/domain/deliveryaddress/application/service/DeliveryAddressService.java @@ -7,22 +7,24 @@ import com.irum.come2us.domain.deliveryaddress.presentation.dto.request.RecipientUpdateRequest; import com.irum.come2us.domain.deliveryaddress.presentation.dto.response.DeliveryAddressInfoListResponse; import com.irum.come2us.domain.deliveryaddress.presentation.dto.response.DeliveryAddressInfoResponse; -import com.irum.come2us.domain.member.application.util.MemberValidator; import com.irum.come2us.domain.member.domain.entity.Member; import com.irum.come2us.global.presentation.advice.exception.CommonException; import com.irum.come2us.global.presentation.advice.exception.errorcode.DeliveryAddressErrorCode; +import com.irum.come2us.global.presentation.advice.exception.errorcode.GlobalErrorCode; import com.irum.come2us.global.util.MemberUtil; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; @Service @RequiredArgsConstructor @Transactional +@Slf4j public class DeliveryAddressService { - private final MemberValidator memberValidator; private final MemberUtil memberUtil; private final DeliveryAddressRepository deliveryAddressRepository; @@ -52,30 +54,34 @@ public DeliveryAddressInfoResponse findDeliveryAddress(UUID deliveryAddressId) { } @Transactional(readOnly = true) - public DeliveryAddressInfoListResponse findDeliveryAddressList(UUID cursor, int pageSize) { - int limit = pageSize + 1; + public DeliveryAddressInfoListResponse findDeliveryAddressList(UUID cursor, Integer size) { + if (size == null || (size != 10 && size != 30 && size != 50)) { + log.warn("허용되지 않은 size 요청: {} -> 기본값 10으로 대체", size); + size = 10; + } + int limit = size + 1; List addressList = deliveryAddressRepository.findDeliveryAddressByCursor( memberUtil.getCurrentMember().getMemberId(), cursor, limit); - boolean hasNext = addressList.size() > pageSize; + boolean hasNext = addressList.size() > size; List resultList = - hasNext ? addressList.subList(0, pageSize) : addressList; + hasNext ? addressList.subList(0, size) : addressList; UUID nextCursor = null; if (hasNext) { DeliveryAddressInfoResponse lastItem = resultList.get(resultList.size() - 1); - nextCursor = lastItem.id(); + nextCursor = lastItem.deliveryAddressId(); } return new DeliveryAddressInfoListResponse(resultList, nextCursor, hasNext); } - public void changeRecipientInfo(RecipientUpdateRequest request) { - DeliveryAddress deliveryAddress = validDeliveryAddress(request.deliveryAddressId()); - deliveryAddress.updateRecipientName(request.newRecipientName()); - deliveryAddress.updateRecipientContact(request.newRecipientContact()); + public void changeRecipientInfo(UUID deliveryAddressId, RecipientUpdateRequest request) { + DeliveryAddress deliveryAddress = validDeliveryAddress(deliveryAddressId); + applyValidUpdate( + deliveryAddress, request.newRecipientName(), request.newRecipientContact()); } - public void changeAddressDetail(AddressDetailUpdateRequest request) { - DeliveryAddress deliveryAddress = validDeliveryAddress(request.deliveryAddressId()); + public void changeAddressDetail(UUID deliveryAddressId, AddressDetailUpdateRequest request) { + DeliveryAddress deliveryAddress = validDeliveryAddress(deliveryAddressId); deliveryAddress.updateAddressDetail(request.newAddressDetail()); } @@ -119,4 +125,11 @@ private DeliveryAddress getCurrentDefaultAddress() { new CommonException( DeliveryAddressErrorCode.DELIVERY_ADDRESS_NOT_FOUND)); } + + public void applyValidUpdate(DeliveryAddress address, String newName, String newContact) { + if (!StringUtils.hasText(newName) && !StringUtils.hasText(newContact)) + throw new CommonException(GlobalErrorCode.EMPTY_REQUEST); + if (StringUtils.hasText(newName)) address.updateRecipientName(newName); + if (StringUtils.hasText(newContact)) address.updateAddressDetail(newContact); + } } diff --git a/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/controller/DeliveryAddressController.java b/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/controller/DeliveryAddressController.java index a3902240..2e151736 100644 --- a/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/controller/DeliveryAddressController.java +++ b/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/controller/DeliveryAddressController.java @@ -7,6 +7,7 @@ import com.irum.come2us.domain.deliveryaddress.presentation.dto.response.DeliveryAddressInfoListResponse; import com.irum.come2us.domain.deliveryaddress.presentation.dto.response.DeliveryAddressInfoResponse; import jakarta.annotation.Nullable; +import jakarta.validation.Valid; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -21,44 +22,46 @@ public class DeliveryAddressController { @PostMapping public ResponseEntity registerDeliveryAddress( - @RequestBody DeliveryAddressRegisterRequest request) { + @Valid @RequestBody DeliveryAddressRegisterRequest request) { deliveryAddressService.createDeliveryAddress(request); return ResponseEntity.status(HttpStatus.CREATED).build(); } - @GetMapping("/info") + @GetMapping("/{deliveryAddressId}/info") public DeliveryAddressInfoResponse getDeliveryAddressInfo( - @RequestParam UUID deliveryAddressId) { + @PathVariable UUID deliveryAddressId) { return deliveryAddressService.findDeliveryAddress(deliveryAddressId); } @GetMapping public DeliveryAddressInfoListResponse getDeliveryAddressInfoList( - @Nullable @RequestParam UUID cursor, @RequestParam int pageSize) { - return deliveryAddressService.findDeliveryAddressList(cursor, pageSize); + @Nullable @RequestParam UUID cursor, @RequestParam Integer size) { + return deliveryAddressService.findDeliveryAddressList(cursor, size); } - @PatchMapping("/recipient") - public ResponseEntity updateRecipientInfo(@RequestBody RecipientUpdateRequest request) { - deliveryAddressService.changeRecipientInfo(request); + @PatchMapping("/{deliveryAddressId}/recipient") + public ResponseEntity updateRecipientInfo( + @PathVariable UUID deliveryAddressId, @RequestBody RecipientUpdateRequest request) { + deliveryAddressService.changeRecipientInfo(deliveryAddressId, request); return ResponseEntity.noContent().build(); } - @PatchMapping("/detail") + @PatchMapping("/{deliveryAddressId}/detail") public ResponseEntity updateAddressDetail( - @RequestBody AddressDetailUpdateRequest request) { - deliveryAddressService.changeAddressDetail(request); + @PathVariable UUID deliveryAddressId, + @Valid @RequestBody AddressDetailUpdateRequest request) { + deliveryAddressService.changeAddressDetail(deliveryAddressId, request); return ResponseEntity.noContent().build(); } - @PatchMapping("/default") - public ResponseEntity setDefaultDeliveryAddress(@RequestParam UUID deliveryAddressId) { + @PatchMapping("/{deliveryAddressId}/default") + public ResponseEntity setDefaultDeliveryAddress(@PathVariable UUID deliveryAddressId) { deliveryAddressService.changeDefaultDeliveryAddress(deliveryAddressId); return ResponseEntity.noContent().build(); } - @DeleteMapping - public ResponseEntity deleteDeliveryAddress(@RequestParam UUID deliveryAddressId) { + @DeleteMapping("/{deliveryAddressId}") + public ResponseEntity deleteDeliveryAddress(@PathVariable UUID deliveryAddressId) { deliveryAddressService.removeDeliveryAddress(deliveryAddressId); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/dto/request/AddressDetailUpdateRequest.java b/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/dto/request/AddressDetailUpdateRequest.java index 1fe9a431..a24fee61 100644 --- a/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/dto/request/AddressDetailUpdateRequest.java +++ b/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/dto/request/AddressDetailUpdateRequest.java @@ -1,5 +1,3 @@ package com.irum.come2us.domain.deliveryaddress.presentation.dto.request; -import java.util.UUID; - -public record AddressDetailUpdateRequest(UUID deliveryAddressId, String newAddressDetail) {} +public record AddressDetailUpdateRequest(String newAddressDetail) {} diff --git a/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/dto/request/RecipientUpdateRequest.java b/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/dto/request/RecipientUpdateRequest.java index c9eb6a8f..755b33c3 100644 --- a/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/dto/request/RecipientUpdateRequest.java +++ b/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/dto/request/RecipientUpdateRequest.java @@ -1,6 +1,3 @@ package com.irum.come2us.domain.deliveryaddress.presentation.dto.request; -import java.util.UUID; - -public record RecipientUpdateRequest( - UUID deliveryAddressId, String newRecipientName, String newRecipientContact) {} +public record RecipientUpdateRequest(String newRecipientName, String newRecipientContact) {} diff --git a/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/dto/response/DeliveryAddressInfoResponse.java b/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/dto/response/DeliveryAddressInfoResponse.java index edf1b85c..3283a589 100644 --- a/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/dto/response/DeliveryAddressInfoResponse.java +++ b/src/main/java/com/irum/come2us/domain/deliveryaddress/presentation/dto/response/DeliveryAddressInfoResponse.java @@ -4,18 +4,18 @@ import java.util.UUID; public record DeliveryAddressInfoResponse( - UUID id, + UUID deliveryAddressId, Address address, String recipientName, String recipientContact, Boolean isDefault) { public static DeliveryAddressInfoResponse of( - UUID id, + UUID deliveryAddressId, Address address, String recipientName, String recipientContact, Boolean isDefault) { return new DeliveryAddressInfoResponse( - id, address, recipientName, recipientContact, isDefault); + deliveryAddressId, address, recipientName, recipientContact, isDefault); } } From 340004798b852d5deb1f500139bf4542cc290ba6 Mon Sep 17 00:00:00 2001 From: willjsw Date: Mon, 27 Oct 2025 14:48:08 +0900 Subject: [PATCH 3/9] =?UTF-8?q?refactor=20#196:=20ManagerController=20API?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EA=B2=B0=EA=B3=BC=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/_manager.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/docs/asciidoc/_manager.adoc b/src/docs/asciidoc/_manager.adoc index a2c916d5..79a56d7d 100644 --- a/src/docs/asciidoc/_manager.adoc +++ b/src/docs/asciidoc/_manager.adoc @@ -87,5 +87,5 @@ include::{snippets}/manager-delete/path-parameters.adoc[] include::{snippets}/manager-delete/http-response.adoc[] ==== 매니저 삭제 실패: 권한 없음 -include::{snippets}/manager-delete-fail-not-manager/http-response.adoc[] -include::{snippets}/manager-delete-fail-not-manager/response-fields.adoc[] \ No newline at end of file +include::{snippets}/manager-delete-fail-self-delete/http-response.adoc[] +include::{snippets}/manager-delete-fail-self-delete/response-fields.adoc[] \ No newline at end of file From 8c923442f373d6874e82c69eb9c41fc51fc6eed0 Mon Sep 17 00:00:00 2001 From: willjsw Date: Mon, 27 Oct 2025 14:48:57 +0900 Subject: [PATCH 4/9] =?UTF-8?q?refactor=20#196:=20EMPTY=5FREQUEST=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EC=BD=94=EB=93=9C=20=EB=A0=88=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B3=80=EA=B2=BD(Me?= =?UTF-8?q?mber=20->=20Global)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/application/util/MemberValidator.java | 3 ++- .../advice/exception/errorcode/GlobalErrorCode.java | 4 ++-- .../advice/exception/errorcode/MemberErrorCode.java | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/irum/come2us/domain/member/application/util/MemberValidator.java b/src/main/java/com/irum/come2us/domain/member/application/util/MemberValidator.java index 3684e0f1..98c1f557 100644 --- a/src/main/java/com/irum/come2us/domain/member/application/util/MemberValidator.java +++ b/src/main/java/com/irum/come2us/domain/member/application/util/MemberValidator.java @@ -5,6 +5,7 @@ import com.irum.come2us.domain.member.domain.repository.MemberRepository; import com.irum.come2us.global.presentation.advice.exception.CommonException; import com.irum.come2us.global.presentation.advice.exception.errorcode.AuthErrorCode; +import com.irum.come2us.global.presentation.advice.exception.errorcode.GlobalErrorCode; import com.irum.come2us.global.presentation.advice.exception.errorcode.MemberErrorCode; import com.irum.come2us.global.security.MemberDetails; import java.util.Optional; @@ -108,7 +109,7 @@ public void assertMemberIsManager(Member member) { public void applyValidUpdate(Member member, String newName, String newContact) { if (!StringUtils.hasText(newName) && !StringUtils.hasText(newContact)) - throw new CommonException(MemberErrorCode.EMPTY_REQUEST); + throw new CommonException(GlobalErrorCode.EMPTY_REQUEST); if (StringUtils.hasText(newName)) member.updateName(newName); if (StringUtils.hasText(newContact)) member.updateContact(newContact); } diff --git a/src/main/java/com/irum/come2us/global/presentation/advice/exception/errorcode/GlobalErrorCode.java b/src/main/java/com/irum/come2us/global/presentation/advice/exception/errorcode/GlobalErrorCode.java index 67c55b41..44f326fe 100644 --- a/src/main/java/com/irum/come2us/global/presentation/advice/exception/errorcode/GlobalErrorCode.java +++ b/src/main/java/com/irum/come2us/global/presentation/advice/exception/errorcode/GlobalErrorCode.java @@ -8,8 +8,8 @@ @AllArgsConstructor public enum GlobalErrorCode implements BaseErrorCode { INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류입니다. 관리자에게 문의해주세요."), - INVALID_CURSOR(HttpStatus.BAD_REQUEST, "리소스를 조회할 수 없습니다."); - ; + INVALID_CURSOR(HttpStatus.BAD_REQUEST, "리소스를 조회할 수 없습니다."), + EMPTY_REQUEST(HttpStatus.BAD_REQUEST, "비어있는 요청입니다."); private final HttpStatus httpStatus; private final String message; diff --git a/src/main/java/com/irum/come2us/global/presentation/advice/exception/errorcode/MemberErrorCode.java b/src/main/java/com/irum/come2us/global/presentation/advice/exception/errorcode/MemberErrorCode.java index c254f464..c3d3f8fb 100644 --- a/src/main/java/com/irum/come2us/global/presentation/advice/exception/errorcode/MemberErrorCode.java +++ b/src/main/java/com/irum/come2us/global/presentation/advice/exception/errorcode/MemberErrorCode.java @@ -10,7 +10,6 @@ public enum MemberErrorCode implements BaseErrorCode { MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "회원 정보를 찾을 수 없습니다."), OWNER_UPGRADE_REQUIRED(HttpStatus.CONFLICT, "일반 회원으로 가입된 계정이 존재합니다."), MEMBER_ALREADY_EXISTS(HttpStatus.CONFLICT, "이미 존재하는 회원입니다."), - EMPTY_REQUEST(HttpStatus.BAD_REQUEST, "비어있는 요청입니다."), INVALID_EMAIL(HttpStatus.BAD_REQUEST, "유효하지 않은 이메일 형식입니다."), INVALID_CONTACT(HttpStatus.BAD_REQUEST, "유효하지 않은 연락처 형식입니다."), INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "유효한 비밀번호가 아닙니다."), From 22ad884834af07faf013faec6ff1cd11422f4a0e Mon Sep 17 00:00:00 2001 From: willjsw Date: Mon, 27 Oct 2025 15:01:48 +0900 Subject: [PATCH 5/9] =?UTF-8?q?refactor=20#211:=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=EB=AA=85=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeliveryAddressControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/com/irum/come2us/domain/{deliveryadress => deliveryaddress}/DeliveryAddressControllerTest.java (99%) diff --git a/src/test/java/com/irum/come2us/domain/deliveryadress/DeliveryAddressControllerTest.java b/src/test/java/com/irum/come2us/domain/deliveryaddress/DeliveryAddressControllerTest.java similarity index 99% rename from src/test/java/com/irum/come2us/domain/deliveryadress/DeliveryAddressControllerTest.java rename to src/test/java/com/irum/come2us/domain/deliveryaddress/DeliveryAddressControllerTest.java index 720485db..7f181866 100644 --- a/src/test/java/com/irum/come2us/domain/deliveryadress/DeliveryAddressControllerTest.java +++ b/src/test/java/com/irum/come2us/domain/deliveryaddress/DeliveryAddressControllerTest.java @@ -1,4 +1,4 @@ -package com.irum.come2us.domain.deliveryadress; +package com.irum.come2us.domain.deliveryaddress; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; From 9a0570986ec9b0fda74c30038ebdf5b548889d96 Mon Sep 17 00:00:00 2001 From: willjsw Date: Mon, 27 Oct 2025 15:17:13 +0900 Subject: [PATCH 6/9] =?UTF-8?q?refactor=20#211:=20TestConfig=20=EC=A4=91?= =?UTF-8?q?=EC=95=99=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeliveryAddressControllerTest.java | 14 ++------ .../domain/member/ManagerControllerTest.java | 14 ++------ .../domain/member/MemberControllerTest.java | 15 ++------- .../come2us/global/config/TestConfig.java | 32 +++++++++++++++++++ 4 files changed, 38 insertions(+), 37 deletions(-) create mode 100644 src/test/java/com/irum/come2us/global/config/TestConfig.java diff --git a/src/test/java/com/irum/come2us/domain/deliveryaddress/DeliveryAddressControllerTest.java b/src/test/java/com/irum/come2us/domain/deliveryaddress/DeliveryAddressControllerTest.java index 7f181866..0cb9d236 100644 --- a/src/test/java/com/irum/come2us/domain/deliveryaddress/DeliveryAddressControllerTest.java +++ b/src/test/java/com/irum/come2us/domain/deliveryaddress/DeliveryAddressControllerTest.java @@ -21,6 +21,7 @@ import com.irum.come2us.domain.deliveryaddress.presentation.dto.response.DeliveryAddressInfoListResponse; import com.irum.come2us.domain.deliveryaddress.presentation.dto.response.DeliveryAddressInfoResponse; import com.irum.come2us.global.config.SecurityTestConfig; +import com.irum.come2us.global.config.TestConfig; import com.irum.come2us.global.presentation.advice.exception.CommonException; import com.irum.come2us.global.presentation.advice.exception.errorcode.DeliveryAddressErrorCode; import com.irum.come2us.global.presentation.advice.exception.errorcode.MemberErrorCode; @@ -28,12 +29,9 @@ import java.util.UUID; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -41,21 +39,13 @@ @WebMvcTest(DeliveryAddressController.class) @AutoConfigureRestDocs -@Import(SecurityTestConfig.class) +@Import({SecurityTestConfig.class, TestConfig.class}) public class DeliveryAddressControllerTest { @Autowired private MockMvc mockMvc; @Autowired private DeliveryAddressService deliveryAddressService; @Autowired private ObjectMapper objectMapper; - @TestConfiguration - static class TestConfig { - @Bean - public DeliveryAddressService deliveryAddressService() { - return Mockito.mock(DeliveryAddressService.class); - } - } - @Test @DisplayName("배송지 주소 등록 API 테스트") void deliveryAddressRegisterApiTest() throws Exception { diff --git a/src/test/java/com/irum/come2us/domain/member/ManagerControllerTest.java b/src/test/java/com/irum/come2us/domain/member/ManagerControllerTest.java index c6823503..11544276 100644 --- a/src/test/java/com/irum/come2us/domain/member/ManagerControllerTest.java +++ b/src/test/java/com/irum/come2us/domain/member/ManagerControllerTest.java @@ -20,17 +20,15 @@ import com.irum.come2us.domain.member.presentation.dto.response.MemberInfoListResponse; import com.irum.come2us.domain.member.presentation.dto.response.MemberInfoResponse; import com.irum.come2us.global.config.SecurityTestConfig; +import com.irum.come2us.global.config.TestConfig; import com.irum.come2us.global.presentation.advice.exception.CommonException; import com.irum.come2us.global.presentation.advice.exception.errorcode.MemberErrorCode; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -38,20 +36,12 @@ @WebMvcTest(ManagerController.class) @AutoConfigureRestDocs -@Import(SecurityTestConfig.class) +@Import({SecurityTestConfig.class, TestConfig.class}) public class ManagerControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ManagerService managerService; @Autowired private ObjectMapper objectMapper; - @TestConfiguration - static class TestConfig { - @Bean - public ManagerService managerService() { - return Mockito.mock(ManagerService.class); - } - } - @Test @DisplayName("매니저 회원가입 API") void managerSignupApiTest() throws Exception { diff --git a/src/test/java/com/irum/come2us/domain/member/MemberControllerTest.java b/src/test/java/com/irum/come2us/domain/member/MemberControllerTest.java index e4a70c67..52b2ecd7 100644 --- a/src/test/java/com/irum/come2us/domain/member/MemberControllerTest.java +++ b/src/test/java/com/irum/come2us/domain/member/MemberControllerTest.java @@ -19,14 +19,12 @@ import com.irum.come2us.domain.member.presentation.dto.request.MemberPasswordUpdateRequest; import com.irum.come2us.domain.member.presentation.dto.response.MemberInfoResponse; import com.irum.come2us.global.config.SecurityTestConfig; +import com.irum.come2us.global.config.TestConfig; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -34,21 +32,12 @@ @WebMvcTest(MemberController.class) @AutoConfigureRestDocs -@Import(SecurityTestConfig.class) +@Import({SecurityTestConfig.class, TestConfig.class}) public class MemberControllerTest { @Autowired private MockMvc mockMvc; @Autowired private MemberService memberService; @Autowired private ObjectMapper objectMapper; - @TestConfiguration - static class TestConfig { - - @Bean - public MemberService memberService() { - return Mockito.mock(MemberService.class); - } - } - @Test @DisplayName("고객 회원가입 API") void customerSignupApiTest() throws Exception { diff --git a/src/test/java/com/irum/come2us/global/config/TestConfig.java b/src/test/java/com/irum/come2us/global/config/TestConfig.java new file mode 100644 index 00000000..d4b47541 --- /dev/null +++ b/src/test/java/com/irum/come2us/global/config/TestConfig.java @@ -0,0 +1,32 @@ +package com.irum.come2us.global.config; + +import com.irum.come2us.domain.auth.application.service.AuthService; +import com.irum.come2us.domain.deliveryaddress.application.service.DeliveryAddressService; +import com.irum.come2us.domain.member.application.service.ManagerService; +import com.irum.come2us.domain.member.application.service.MemberService; +import org.mockito.Mockito; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; + +@TestConfiguration +public class TestConfig { + @Bean + public MemberService memberService() { + return Mockito.mock(MemberService.class); + } + + @Bean + public ManagerService managerService() { + return Mockito.mock(ManagerService.class); + } + + @Bean + public AuthService authService() { + return Mockito.mock(AuthService.class); + } + + @Bean + public DeliveryAddressService deliveryAddressService() { + return Mockito.mock(DeliveryAddressService.class); + } +} From a888fa5ba63b4eae6920e776990df694a74973d2 Mon Sep 17 00:00:00 2001 From: willjsw Date: Mon, 27 Oct 2025 16:10:00 +0900 Subject: [PATCH 7/9] =?UTF-8?q?test=20#211:=20AuthController=20API=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/_auth.adoc | 14 ++ src/docs/asciidoc/index.adoc | 3 +- .../domain/auth/AuthControllerTest.java | 134 ++++++++++++++++++ 3 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 src/docs/asciidoc/_auth.adoc create mode 100644 src/test/java/com/irum/come2us/domain/auth/AuthControllerTest.java diff --git a/src/docs/asciidoc/_auth.adoc b/src/docs/asciidoc/_auth.adoc new file mode 100644 index 00000000..cbc55a2b --- /dev/null +++ b/src/docs/asciidoc/_auth.adoc @@ -0,0 +1,14 @@ +=== 회원 로그인 +`POST /auth/login` + +요청 본문: +include::{snippets}/member-login/request-fields.adoc[] + +응답 본문: +include::{snippets}/member-login/response-fields.adoc[] + +응답 헤더 (쿠키): +include::{snippets}/member-login/response-headers.adoc[] + +응답: +include::{snippets}/member-login/http-response.adoc[] \ No newline at end of file diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 51fbd65b..ea516504 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -8,7 +8,8 @@ include::_member.adoc[] include::_manager.adoc[] -include::_manager.adoc[] +include::_auth.adoc +include::_delivery_address.adoc // include::_product.adoc[] diff --git a/src/test/java/com/irum/come2us/domain/auth/AuthControllerTest.java b/src/test/java/com/irum/come2us/domain/auth/AuthControllerTest.java new file mode 100644 index 00000000..91499552 --- /dev/null +++ b/src/test/java/com/irum/come2us/domain/auth/AuthControllerTest.java @@ -0,0 +1,134 @@ +package com.irum.come2us.domain.auth; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.irum.come2us.domain.auth.application.service.AuthService; +import com.irum.come2us.domain.auth.application.service.JwtTokenService; +import com.irum.come2us.domain.auth.presentation.controller.AuthController; +import com.irum.come2us.domain.auth.presentation.dto.request.MemberLoginRequest; +import com.irum.come2us.domain.auth.presentation.dto.response.MemberLoginResponse; +import com.irum.come2us.domain.member.domain.entity.enums.Role; +import com.irum.come2us.global.config.SecurityTestConfig; +import com.irum.come2us.global.config.TestConfig; +import com.irum.come2us.global.util.CookieUtil; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(AuthController.class) +@AutoConfigureRestDocs +@Import({SecurityTestConfig.class, TestConfig.class}) +public class AuthControllerTest { + @Autowired MockMvc mockMvc; + @Autowired AuthService authService; + @Autowired CookieUtil cookieUtil; + @Autowired JwtTokenService jwtTokenService; + @Autowired private ObjectMapper objectMapper; + + @Test + @DisplayName("회원 로그인 API 테스트") + void memberLoginApiTest() throws Exception { + String mockAccessToken = "access-token"; + String mockRefreshToken = "refresh-token"; + + MemberLoginRequest request = new MemberLoginRequest("customer@example.com", "password123!"); + + MemberLoginResponse response = new MemberLoginResponse(mockAccessToken, mockRefreshToken); + String requestJson = objectMapper.writeValueAsString(request); + + HttpHeaders mockHeaders = new HttpHeaders(); + mockHeaders.add( + HttpHeaders.SET_COOKIE, + "refreshToken=" + mockRefreshToken + "; Path=/; Secure; HttpOnly; SameSite=None"); + + when(jwtTokenService.createAccessToken(anyLong(), any(Role.class))) + .thenReturn(mockAccessToken); + when(cookieUtil.generateRefreshTokenCookie(any(String.class))).thenReturn(mockHeaders); + when(jwtTokenService.createRefreshToken(anyLong())).thenReturn(mockRefreshToken); + when(authService.processMemberLogin(any(MemberLoginRequest.class))).thenReturn(response); + + mockMvc.perform( + post("/auth/login") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(requestJson)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.OK.value())) + .andExpect(jsonPath("$.data.accessToken").value(response.accessToken())) + .andExpect(jsonPath("$.data.refreshToken").doesNotExist()) + .andExpect(cookie().exists("refreshToken")) + .andExpect(cookie().value("refreshToken", mockRefreshToken)) + .andDo( + document( + "member-login", + requestFields( + fieldWithPath("email").description("가입할 이메일 (아이디)"), + fieldWithPath("password").description("가입할 비밀번호")), + responseFields( + fieldWithPath("success").description("true"), + fieldWithPath("status").description("200"), + fieldWithPath("timestamp").description("응답 시간"), + fieldWithPath("data.accessToken") + .description("가입할 이메일 (아이디)")), + responseHeaders( + headerWithName(HttpHeaders.SET_COOKIE) + .description( + "Refresh Token 쿠키 (이름: `refreshToken`).
" + + "정책: **HttpOnly**, **Secure**, Path=/, SameSite=None")))); + } + + @Test + @DisplayName("회원 로그아웃 API 테스트") + void memberLogoutApiTest() throws Exception { + + String deletedCookieValue = ""; // 삭제 시 쿠키 값 = 빈 문자열 + HttpHeaders deleteHeaders = new HttpHeaders(); + deleteHeaders.add( + HttpHeaders.SET_COOKIE, + "refreshToken=" + + deletedCookieValue + + "; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; Secure; HttpOnly; SameSite=None"); + + when(authService.processMemberLogout()).thenReturn(deleteHeaders); + + // When & Then + mockMvc.perform( + post("/auth/logout") + .with(csrf()) + // 인증 정보 추가(Authorization) + .with(user("1").roles("CUSTOMER"))) + .andExpect(status().isNoContent()) // 204 No Content 기대 + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.status").value(HttpStatus.NO_CONTENT.value())) + .andExpect(cookie().exists("refreshToken")) + .andExpect(cookie().value("refreshToken", "")) + .andExpect(cookie().maxAge("refreshToken", 0)) + .andDo( + document( + "member-logout", + responseHeaders( + headerWithName(HttpHeaders.SET_COOKIE) + .description( + "Refresh Token 쿠키를 삭제하기 위한 헤더.
" + + "정책: **Max-Age=0** 설정으로 즉시 만료 및 삭제됩니다.")))); + } +} From afb329f2ce86f33dd3da79b993f667d501e7c334 Mon Sep 17 00:00:00 2001 From: willjsw Date: Mon, 27 Oct 2025 16:10:39 +0900 Subject: [PATCH 8/9] =?UTF-8?q?refactor=20#211:=20AuthController=20?= =?UTF-8?q?=ED=83=9C=EC=8A=A4=ED=8A=B8=20=EA=B2=B0=EA=B3=BC=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=EA=B8=B0=EC=A1=B4=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/controller/AuthController.java | 13 +++++++++++-- .../com/irum/come2us/global/config/TestConfig.java | 12 ++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/irum/come2us/domain/auth/presentation/controller/AuthController.java b/src/main/java/com/irum/come2us/domain/auth/presentation/controller/AuthController.java index ea3e710c..f3e8b141 100644 --- a/src/main/java/com/irum/come2us/domain/auth/presentation/controller/AuthController.java +++ b/src/main/java/com/irum/come2us/domain/auth/presentation/controller/AuthController.java @@ -3,10 +3,12 @@ import com.irum.come2us.domain.auth.application.service.AuthService; import com.irum.come2us.domain.auth.presentation.dto.request.MemberLoginRequest; import com.irum.come2us.domain.auth.presentation.dto.response.MemberLoginResponse; +import com.irum.come2us.global.util.CookieUtil; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -16,11 +18,18 @@ @Slf4j public class AuthController { private final AuthService authService; + private final CookieUtil cookieUtil; @PostMapping("/login") - public MemberLoginResponse login(@RequestBody MemberLoginRequest request) { + public ResponseEntity login(@RequestBody MemberLoginRequest request) { log.info("회원 로그인 요청: {}", request); - return authService.processMemberLogin(request); + MemberLoginResponse responseBody = authService.processMemberLogin(request); + HttpHeaders cookieHeaders = + cookieUtil.generateRefreshTokenCookie(responseBody.refreshToken()); + log.info("로그인 성공-토큰 발급 완료: {}", request.email()); + log.trace("access token issued: {}", responseBody.accessToken()); + log.trace("refresh token issued: {}", responseBody.refreshToken()); + return ResponseEntity.status(HttpStatus.OK).headers(cookieHeaders).body(responseBody); } @PostMapping("/logout") diff --git a/src/test/java/com/irum/come2us/global/config/TestConfig.java b/src/test/java/com/irum/come2us/global/config/TestConfig.java index d4b47541..27aab871 100644 --- a/src/test/java/com/irum/come2us/global/config/TestConfig.java +++ b/src/test/java/com/irum/come2us/global/config/TestConfig.java @@ -1,9 +1,11 @@ package com.irum.come2us.global.config; import com.irum.come2us.domain.auth.application.service.AuthService; +import com.irum.come2us.domain.auth.application.service.JwtTokenService; import com.irum.come2us.domain.deliveryaddress.application.service.DeliveryAddressService; import com.irum.come2us.domain.member.application.service.ManagerService; import com.irum.come2us.domain.member.application.service.MemberService; +import com.irum.come2us.global.util.CookieUtil; import org.mockito.Mockito; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; @@ -29,4 +31,14 @@ public AuthService authService() { public DeliveryAddressService deliveryAddressService() { return Mockito.mock(DeliveryAddressService.class); } + + @Bean + public CookieUtil cookieUtil() { + return Mockito.mock(CookieUtil.class); + } + + @Bean + public JwtTokenService jwtTokenService() { + return Mockito.mock(JwtTokenService.class); + } } From bcad09d56ff284759646ae6a8a9e61a5792c86be Mon Sep 17 00:00:00 2001 From: willjsw Date: Mon, 27 Oct 2025 23:01:23 +0900 Subject: [PATCH 9/9] =?UTF-8?q?refactor=20#211:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=95=84=EC=9B=83=20API=20=EB=AA=85=EC=84=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EC=84=B9=EC=85=98=20=ED=97=A4=EB=8D=94?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/_auth.adoc | 15 +++++++++++++-- src/docs/asciidoc/index.adoc | 4 ++-- .../come2us/domain/auth/AuthControllerTest.java | 4 +--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/docs/asciidoc/_auth.adoc b/src/docs/asciidoc/_auth.adoc index cbc55a2b..77d00074 100644 --- a/src/docs/asciidoc/_auth.adoc +++ b/src/docs/asciidoc/_auth.adoc @@ -1,4 +1,6 @@ -=== 회원 로그인 +== 회원 인증 (Auth) API + +=== 1. 회원 로그인 `POST /auth/login` 요청 본문: @@ -11,4 +13,13 @@ include::{snippets}/member-login/response-fields.adoc[] include::{snippets}/member-login/response-headers.adoc[] 응답: -include::{snippets}/member-login/http-response.adoc[] \ No newline at end of file +include::{snippets}/member-login/http-response.adoc[] + +=== 2. 회원 로그아웃 +`POST /auth/logout` + +응답 헤더 (쿠키): +include::{snippets}/member-logout/response-headers.adoc[] + +응답: +include::{snippets}/member-logout/http-response.adoc[] diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index ea516504..37f83958 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -8,8 +8,8 @@ include::_member.adoc[] include::_manager.adoc[] -include::_auth.adoc -include::_delivery_address.adoc +include::_auth.adoc[] +include::_delivery_address.adoc[] // include::_product.adoc[] diff --git a/src/test/java/com/irum/come2us/domain/auth/AuthControllerTest.java b/src/test/java/com/irum/come2us/domain/auth/AuthControllerTest.java index 91499552..66acb09e 100644 --- a/src/test/java/com/irum/come2us/domain/auth/AuthControllerTest.java +++ b/src/test/java/com/irum/come2us/domain/auth/AuthControllerTest.java @@ -127,8 +127,6 @@ void memberLogoutApiTest() throws Exception { "member-logout", responseHeaders( headerWithName(HttpHeaders.SET_COOKIE) - .description( - "Refresh Token 쿠키를 삭제하기 위한 헤더.
" - + "정책: **Max-Age=0** 설정으로 즉시 만료 및 삭제됩니다.")))); + .description("Refresh Token 쿠키를 삭제하기 위한 헤더")))); } }