From 80cce6a045c1a70429e1e795d8e306100bcd879f Mon Sep 17 00:00:00 2001 From: haazz Date: Sun, 19 Oct 2025 20:39:34 +0900 Subject: [PATCH 01/26] =?UTF-8?q?docs:=20GlobalExceptionHandler=20class=20?= =?UTF-8?q?java=20docs=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sunpick/global/exception/GlobalExceptionHandler.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sunpick/src/main/java/com/backend/sunpick/global/exception/GlobalExceptionHandler.java b/sunpick/src/main/java/com/backend/sunpick/global/exception/GlobalExceptionHandler.java index c567a45..8438432 100644 --- a/sunpick/src/main/java/com/backend/sunpick/global/exception/GlobalExceptionHandler.java +++ b/sunpick/src/main/java/com/backend/sunpick/global/exception/GlobalExceptionHandler.java @@ -10,6 +10,14 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +/** + * 전역 예외 처리 + *

+ * 컨트롤러에서 발생한 예외를 가로채 간단한 HTTP 상태 코드와 메시지로 변환 + *

+ * + * @author haazz + */ @RestControllerAdvice public class GlobalExceptionHandler { From d7f66c6c4a5fd23de0f5b60ed96f5fc0d706689a Mon Sep 17 00:00:00 2001 From: haazz Date: Sun, 19 Oct 2025 22:35:09 +0900 Subject: [PATCH 02/26] =?UTF-8?q?build:=20jakarta=20validation=20dependenc?= =?UTF-8?q?y=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sunpick/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/sunpick/build.gradle b/sunpick/build.gradle index b95c37e..930375f 100644 --- a/sunpick/build.gradle +++ b/sunpick/build.gradle @@ -27,6 +27,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 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.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.4' implementation 'mysql:mysql-connector-java:8.0.33' compileOnly 'org.projectlombok:lombok' From a607f01f8df091e4aa22886e7afbea020d4e4104 Mon Sep 17 00:00:00 2001 From: haazz Date: Sun, 19 Oct 2025 22:48:45 +0900 Subject: [PATCH 03/26] =?UTF-8?q?feat:=20=EC=83=81=EC=A0=90=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/controller/StoreController.java | 49 +++++++++++++++++++ .../store/dto/request/StoreCreateRequest.java | 23 +++++++++ .../domain/store/service/StoreService.java | 28 +++++++++++ 3 files changed, 100 insertions(+) create mode 100644 sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java create mode 100644 sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreCreateRequest.java create mode 100644 sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java new file mode 100644 index 0000000..c4f6345 --- /dev/null +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java @@ -0,0 +1,49 @@ +package com.backend.sunpick.domain.store.controller; + +import com.backend.sunpick.domain.store.dto.request.StoreCreateRequest; +import com.backend.sunpick.domain.store.service.StoreService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/store") +public class StoreController { + + private final StoreService storeService; + + @PostMapping + public ResponseEntity createStore(@RequestBody @Valid StoreCreateRequest request) { + storeService.createStore(request); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @GetMapping + public ResponseEntity getStoreAll() { + return null; + } + + @GetMapping("/{id}") + public ResponseEntity getStore() { + return null; + } + + @PatchMapping("{id}") + public ResponseEntity modifyStore() { + return null; + } + + @DeleteMapping("{id}") + public ResponseEntity deleteStore() { + return null; + } +} diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreCreateRequest.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreCreateRequest.java new file mode 100644 index 0000000..d8e4077 --- /dev/null +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreCreateRequest.java @@ -0,0 +1,23 @@ +package com.backend.sunpick.domain.store.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class StoreCreateRequest { + @NotNull(message = "회원 ID는 필수입니다.") + @Positive(message = "회원 ID는 1 이상의 값이어야 합니다.") + private Integer memberId; + + @NotBlank(message = "상점명은 필수입니다.") + @Size(max = 20, message = "상점명은 20자 이하로 입력해 주세요.") + private String storeName; + + @Size(max = 100, message = "상점 설명은 100자 이하로 입력해 주세요.") + private String description; +} diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java new file mode 100644 index 0000000..cccabed --- /dev/null +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java @@ -0,0 +1,28 @@ +package com.backend.sunpick.domain.store.service; + +import com.backend.sunpick.domain.member.entity.Member; +import com.backend.sunpick.domain.member.repository.MemberRepository; +import com.backend.sunpick.domain.store.dto.request.StoreCreateRequest; +import com.backend.sunpick.domain.store.entity.Store; +import com.backend.sunpick.domain.store.repository.StoreRepository; +import java.util.NoSuchElementException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class StoreService { + private final StoreRepository storeRepository; + private final MemberRepository memberRepository; + + public void createStore(StoreCreateRequest storeCreateRequest) { + Member member = memberRepository.findById(storeCreateRequest.getMemberId()) + .orElseThrow(() -> new NoSuchElementException("회원 ID: " + storeCreateRequest.getMemberId() + "가 존재하지 안습니다.")); + storeRepository.save(Store.builder() + .member(member) + .name(storeCreateRequest.getStoreName()) + .description(storeCreateRequest.getDescription()) + .ownerName(member.getName()) + .build()); + } +} From e6f57828cf9a993e5bf8ca06513ea75696ae1d9a Mon Sep 17 00:00:00 2001 From: haazz Date: Tue, 21 Oct 2025 20:27:10 +0900 Subject: [PATCH 04/26] =?UTF-8?q?refactor:=20StoreCreateRequest=20class=20?= =?UTF-8?q?record=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/dto/request/StoreCreateRequest.java | 14 ++++++-------- .../sunpick/domain/store/service/StoreService.java | 14 ++++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreCreateRequest.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreCreateRequest.java index d8e4077..f78d992 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreCreateRequest.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreCreateRequest.java @@ -4,20 +4,18 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.Size; -import lombok.Getter; -import lombok.NoArgsConstructor; -@Getter -@NoArgsConstructor -public class StoreCreateRequest { +public record StoreCreateRequest( @NotNull(message = "회원 ID는 필수입니다.") @Positive(message = "회원 ID는 1 이상의 값이어야 합니다.") - private Integer memberId; + Integer memberId, @NotBlank(message = "상점명은 필수입니다.") @Size(max = 20, message = "상점명은 20자 이하로 입력해 주세요.") - private String storeName; + String storeName, @Size(max = 100, message = "상점 설명은 100자 이하로 입력해 주세요.") - private String description; + String description +) { + } diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java index cccabed..7f872fb 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java @@ -12,17 +12,19 @@ @Service @RequiredArgsConstructor public class StoreService { + private final StoreRepository storeRepository; private final MemberRepository memberRepository; public void createStore(StoreCreateRequest storeCreateRequest) { - Member member = memberRepository.findById(storeCreateRequest.getMemberId()) - .orElseThrow(() -> new NoSuchElementException("회원 ID: " + storeCreateRequest.getMemberId() + "가 존재하지 안습니다.")); + Member member = memberRepository.findById(storeCreateRequest.memberId()) + .orElseThrow(() -> new NoSuchElementException( + "회원 ID: " + storeCreateRequest.memberId() + "가 존재하지 안습니다.")); storeRepository.save(Store.builder() - .member(member) - .name(storeCreateRequest.getStoreName()) - .description(storeCreateRequest.getDescription()) - .ownerName(member.getName()) + .member(member) + .name(storeCreateRequest.storeName()) + .description(storeCreateRequest.description()) + .ownerName(member.getName()) .build()); } } From 0eea5b583444252597cd93c9cb1bcf6f40109a9d Mon Sep 17 00:00:00 2001 From: haazz Date: Tue, 21 Oct 2025 20:40:01 +0900 Subject: [PATCH 05/26] =?UTF-8?q?feat:=20=EC=83=81=EC=A0=90=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/store/controller/StoreController.java | 7 +++++-- .../domain/store/dto/response/StoreResponse.java | 10 ++++++++++ .../sunpick/domain/store/service/StoreService.java | 10 ++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 sunpick/src/main/java/com/backend/sunpick/domain/store/dto/response/StoreResponse.java diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java index c4f6345..1007122 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java @@ -1,8 +1,10 @@ package com.backend.sunpick.domain.store.controller; import com.backend.sunpick.domain.store.dto.request.StoreCreateRequest; +import com.backend.sunpick.domain.store.dto.response.StoreResponse; import com.backend.sunpick.domain.store.service.StoreService; import jakarta.validation.Valid; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -28,8 +30,9 @@ public ResponseEntity createStore(@RequestBody @Valid StoreCreateRequest r } @GetMapping - public ResponseEntity getStoreAll() { - return null; + public ResponseEntity> getStoreAll() { + List response = storeService.getStoreAll(); + return ResponseEntity.status(HttpStatus.OK).body(response); } @GetMapping("/{id}") diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/response/StoreResponse.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/response/StoreResponse.java new file mode 100644 index 0000000..2a7bda3 --- /dev/null +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/response/StoreResponse.java @@ -0,0 +1,10 @@ +package com.backend.sunpick.domain.store.dto.response; + +public record StoreResponse( + Integer id, + String name, + String description, + String ownerName +) { + +} diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java index 7f872fb..2a9dae7 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java @@ -3,8 +3,10 @@ import com.backend.sunpick.domain.member.entity.Member; import com.backend.sunpick.domain.member.repository.MemberRepository; import com.backend.sunpick.domain.store.dto.request.StoreCreateRequest; +import com.backend.sunpick.domain.store.dto.response.StoreResponse; import com.backend.sunpick.domain.store.entity.Store; import com.backend.sunpick.domain.store.repository.StoreRepository; +import java.util.List; import java.util.NoSuchElementException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -27,4 +29,12 @@ public void createStore(StoreCreateRequest storeCreateRequest) { .ownerName(member.getName()) .build()); } + + public List getStoreAll() { + return storeRepository.findAll().stream() + .map(store -> new StoreResponse( + store.getId(), store.getName(), store.getDescription(), store.getOwnerName()) + ) + .toList(); + } } From 2fc1d2048cffac6610e3d4211e0c1d19adeb940a Mon Sep 17 00:00:00 2001 From: haazz Date: Tue, 21 Oct 2025 21:11:54 +0900 Subject: [PATCH 06/26] =?UTF-8?q?feat:=20=EC=83=81=EC=A0=90=20=EB=8B=A8?= =?UTF-8?q?=EC=9D=BC=20=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/store/controller/StoreController.java | 6 ++++-- .../domain/store/service/StoreService.java | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java index 1007122..e3852a9 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -36,8 +37,9 @@ public ResponseEntity> getStoreAll() { } @GetMapping("/{id}") - public ResponseEntity getStore() { - return null; + public ResponseEntity getStoreById(@PathVariable Integer id) { + StoreResponse response = storeService.getStoreById(id); + return ResponseEntity.status(HttpStatus.OK).body(response); } @PatchMapping("{id}") diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java index 2a9dae7..6e81938 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java @@ -32,9 +32,18 @@ public void createStore(StoreCreateRequest storeCreateRequest) { public List getStoreAll() { return storeRepository.findAll().stream() - .map(store -> new StoreResponse( - store.getId(), store.getName(), store.getDescription(), store.getOwnerName()) - ) + .map(this::toResponse) .toList(); } + + public StoreResponse getStoreById(Integer id) { + Store store = storeRepository.findById(id) + .orElseThrow(() -> new NoSuchElementException("상점 ID: " + id + "가 존재하지 않습니다.")); + return toResponse(store); + } + + private StoreResponse toResponse(Store store) { + return new StoreResponse(store.getId(), store.getName(), store.getDescription(), + store.getOwnerName()); + } } From 0a7b9d0c422ae365c8122ef069e01585ded677d0 Mon Sep 17 00:00:00 2001 From: haazz Date: Wed, 22 Oct 2025 10:13:46 +0900 Subject: [PATCH 07/26] =?UTF-8?q?feat:=20=EC=83=81=EC=A0=90=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../store/controller/StoreController.java | 16 ++++---- .../store/dto/request/StoreModifyRequest.java | 17 +++++++++ .../sunpick/domain/store/entity/Store.java | 18 +++++++++ .../domain/store/service/StoreService.java | 37 +++++++++++++++---- 4 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreModifyRequest.java diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java index e3852a9..546d768 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java @@ -1,6 +1,7 @@ package com.backend.sunpick.domain.store.controller; import com.backend.sunpick.domain.store.dto.request.StoreCreateRequest; +import com.backend.sunpick.domain.store.dto.request.StoreModifyRequest; import com.backend.sunpick.domain.store.dto.response.StoreResponse; import com.backend.sunpick.domain.store.service.StoreService; import jakarta.validation.Valid; @@ -36,18 +37,19 @@ public ResponseEntity> getStoreAll() { return ResponseEntity.status(HttpStatus.OK).body(response); } - @GetMapping("/{id}") - public ResponseEntity getStoreById(@PathVariable Integer id) { - StoreResponse response = storeService.getStoreById(id); + @GetMapping("/{storeId}") + public ResponseEntity getStoreById(@PathVariable Integer storeId) { + StoreResponse response = storeService.getStoreById(storeId); return ResponseEntity.status(HttpStatus.OK).body(response); } - @PatchMapping("{id}") - public ResponseEntity modifyStore() { - return null; + @PatchMapping("/{storeId}") + public ResponseEntity modifyStore(@PathVariable Integer storeId, @RequestBody @Valid StoreModifyRequest request) { + storeService.modifyStore(storeId, request); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } - @DeleteMapping("{id}") + @DeleteMapping("/{stored}") public ResponseEntity deleteStore() { return null; } diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreModifyRequest.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreModifyRequest.java new file mode 100644 index 0000000..ba757ba --- /dev/null +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreModifyRequest.java @@ -0,0 +1,17 @@ +package com.backend.sunpick.domain.store.dto.request; + +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; + +public record StoreModifyRequest( + @Positive(message = "회원 ID는 1 이상의 값이어야 합니다.") + Integer memberId, + + @Size(max = 20, message = "상점명은 20자 이하로 입력해 주세요.") + String name, + + @Size(max = 100, message = "상점 설명은 100자 이하로 입력해 주세요.") + String description +) { + +} diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/entity/Store.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/entity/Store.java index fea0bb3..8f78dd4 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/entity/Store.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/entity/Store.java @@ -40,4 +40,22 @@ public class Store extends BaseEntity { @Column(name = "owner_name", length = 6, nullable = false) private String ownerName; + public void changeName(String name) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("상점명은 비어있을 수 없습니다."); + } + this.name = name; + } + + public void changeDescription(String description) { + this.description = description; + } + + public void changeOwner(Member member) { + if (member == null || member.getName() == null || member.getName().isBlank()) { + throw new IllegalArgumentException("회원과 회원명은 비어있을 수 없습니다."); + } + this.member = member; + this.ownerName = member.getName(); + } } diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java index 6e81938..cdac301 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java @@ -3,6 +3,7 @@ import com.backend.sunpick.domain.member.entity.Member; import com.backend.sunpick.domain.member.repository.MemberRepository; import com.backend.sunpick.domain.store.dto.request.StoreCreateRequest; +import com.backend.sunpick.domain.store.dto.request.StoreModifyRequest; import com.backend.sunpick.domain.store.dto.response.StoreResponse; import com.backend.sunpick.domain.store.entity.Store; import com.backend.sunpick.domain.store.repository.StoreRepository; @@ -10,6 +11,7 @@ import java.util.NoSuchElementException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -18,14 +20,14 @@ public class StoreService { private final StoreRepository storeRepository; private final MemberRepository memberRepository; - public void createStore(StoreCreateRequest storeCreateRequest) { - Member member = memberRepository.findById(storeCreateRequest.memberId()) + public void createStore(StoreCreateRequest request) { + Member member = memberRepository.findById(request.memberId()) .orElseThrow(() -> new NoSuchElementException( - "회원 ID: " + storeCreateRequest.memberId() + "가 존재하지 안습니다.")); + "회원 ID: " + request.memberId() + "가 존재하지 안습니다.")); storeRepository.save(Store.builder() .member(member) - .name(storeCreateRequest.storeName()) - .description(storeCreateRequest.description()) + .name(request.storeName()) + .description(request.description()) .ownerName(member.getName()) .build()); } @@ -36,12 +38,31 @@ public List getStoreAll() { .toList(); } - public StoreResponse getStoreById(Integer id) { - Store store = storeRepository.findById(id) - .orElseThrow(() -> new NoSuchElementException("상점 ID: " + id + "가 존재하지 않습니다.")); + public StoreResponse getStoreById(Integer storeId) { + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new NoSuchElementException("상점 ID: " + storeId + "가 존재하지 않습니다.")); return toResponse(store); } + @Transactional + public void modifyStore(Integer storeId, StoreModifyRequest request) { + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new NoSuchElementException("상점 ID: " + storeId + "가 존재하지 않습니다.")); + + if (request.memberId() != null) { + Member member = memberRepository.findById(request.memberId()) + .orElseThrow(() -> new NoSuchElementException( + "회원 ID: " + request.memberId() + "가 존재하지 안습니다.")); + store.changeOwner(member); + } + if (request.name() != null) { + store.changeName(request.name()); + } + if (request.description() != null) { + store.changeDescription(request.description()); + } + } + private StoreResponse toResponse(Store store) { return new StoreResponse(store.getId(), store.getName(), store.getDescription(), store.getOwnerName()); From a4d0bf61e79fa3db13f4db752f25cc95bd6bfa0f Mon Sep 17 00:00:00 2001 From: haazz Date: Wed, 22 Oct 2025 10:15:23 +0900 Subject: [PATCH 08/26] =?UTF-8?q?refactor:=20StoreCreateRequest=20class=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit storeName -> name --- .../sunpick/domain/store/dto/request/StoreCreateRequest.java | 2 +- .../com/backend/sunpick/domain/store/service/StoreService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreCreateRequest.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreCreateRequest.java index f78d992..fdac600 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreCreateRequest.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/request/StoreCreateRequest.java @@ -12,7 +12,7 @@ public record StoreCreateRequest( @NotBlank(message = "상점명은 필수입니다.") @Size(max = 20, message = "상점명은 20자 이하로 입력해 주세요.") - String storeName, + String name, @Size(max = 100, message = "상점 설명은 100자 이하로 입력해 주세요.") String description diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java index cdac301..2bd948c 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java @@ -26,7 +26,7 @@ public void createStore(StoreCreateRequest request) { "회원 ID: " + request.memberId() + "가 존재하지 안습니다.")); storeRepository.save(Store.builder() .member(member) - .name(request.storeName()) + .name(request.name()) .description(request.description()) .ownerName(member.getName()) .build()); From 7ef8047881c222c4dc7e56511eff80e6a7559f1f Mon Sep 17 00:00:00 2001 From: haazz Date: Wed, 22 Oct 2025 14:27:42 +0900 Subject: [PATCH 09/26] =?UTF-8?q?feat:=20=EC=83=81=EC=A0=90=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/store/controller/StoreController.java | 7 ++++--- .../backend/sunpick/domain/store/entity/Store.java | 11 +++++++++++ .../sunpick/domain/store/service/StoreService.java | 12 +++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java index 546d768..cba377c 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/controller/StoreController.java @@ -49,8 +49,9 @@ public ResponseEntity modifyStore(@PathVariable Integer storeId, @RequestB return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } - @DeleteMapping("/{stored}") - public ResponseEntity deleteStore() { - return null; + @DeleteMapping("/{storeId}") + public ResponseEntity deleteStore(@PathVariable Integer storeId) { + storeService.deleteStore(storeId); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } } diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/entity/Store.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/entity/Store.java index 8f78dd4..575d072 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/entity/Store.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/entity/Store.java @@ -11,6 +11,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import java.util.NoSuchElementException; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -40,6 +41,9 @@ public class Store extends BaseEntity { @Column(name = "owner_name", length = 6, nullable = false) private String ownerName; + @Column(name = "is_deleted", nullable = false) + private boolean isDeleted; + public void changeName(String name) { if (name == null || name.isBlank()) { throw new IllegalArgumentException("상점명은 비어있을 수 없습니다."); @@ -58,4 +62,11 @@ public void changeOwner(Member member) { this.member = member; this.ownerName = member.getName(); } + + public void delete() { + if (isDeleted) { + throw new NoSuchElementException("상점 ID: " + id + "는 이미 삭제되었습니다."); + } + isDeleted = true; + } } diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java index 2bd948c..d3d12e5 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java @@ -49,10 +49,13 @@ public void modifyStore(Integer storeId, StoreModifyRequest request) { Store store = storeRepository.findById(storeId) .orElseThrow(() -> new NoSuchElementException("상점 ID: " + storeId + "가 존재하지 않습니다.")); + if (store.isDeleted()) { + throw new NoSuchElementException("상점 ID: " + storeId + "는 삭제되었습니다."); + } if (request.memberId() != null) { Member member = memberRepository.findById(request.memberId()) .orElseThrow(() -> new NoSuchElementException( - "회원 ID: " + request.memberId() + "가 존재하지 안습니다.")); + "회원 ID: " + request.memberId() + "가 존재하지 않습니다.")); store.changeOwner(member); } if (request.name() != null) { @@ -63,6 +66,13 @@ public void modifyStore(Integer storeId, StoreModifyRequest request) { } } + @Transactional + public void deleteStore(Integer storeId) { + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new NoSuchElementException("상점 ID: " + storeId + "가 존재하지 않습니다.")); + store.delete(); + } + private StoreResponse toResponse(Store store) { return new StoreResponse(store.getId(), store.getName(), store.getDescription(), store.getOwnerName()); From b1fa813585fda14936774b1cac5e69c3ea782817 Mon Sep 17 00:00:00 2001 From: haazz Date: Wed, 22 Oct 2025 15:31:48 +0900 Subject: [PATCH 10/26] =?UTF-8?q?feat:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/backend/sunpick/domain/store/service/StoreService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java index d3d12e5..9c16aeb 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java @@ -20,6 +20,7 @@ public class StoreService { private final StoreRepository storeRepository; private final MemberRepository memberRepository; + @Transactional public void createStore(StoreCreateRequest request) { Member member = memberRepository.findById(request.memberId()) .orElseThrow(() -> new NoSuchElementException( @@ -32,12 +33,14 @@ public void createStore(StoreCreateRequest request) { .build()); } + @Transactional(readOnly = true) public List getStoreAll() { return storeRepository.findAll().stream() .map(this::toResponse) .toList(); } + @Transactional(readOnly = true) public StoreResponse getStoreById(Integer storeId) { Store store = storeRepository.findById(storeId) .orElseThrow(() -> new NoSuchElementException("상점 ID: " + storeId + "가 존재하지 않습니다.")); From 12d0f9c19eff3bf9138b37a8bebc7dadecc4dfe4 Mon Sep 17 00:00:00 2001 From: haazz Date: Wed, 22 Oct 2025 15:47:58 +0900 Subject: [PATCH 11/26] =?UTF-8?q?refactor:=20null=20=EC=B2=B4=ED=81=AC=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/store/service/StoreService.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java index 9c16aeb..d154566 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java @@ -9,6 +9,7 @@ import com.backend.sunpick.domain.store.repository.StoreRepository; import java.util.List; import java.util.NoSuchElementException; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -55,18 +56,15 @@ public void modifyStore(Integer storeId, StoreModifyRequest request) { if (store.isDeleted()) { throw new NoSuchElementException("상점 ID: " + storeId + "는 삭제되었습니다."); } - if (request.memberId() != null) { - Member member = memberRepository.findById(request.memberId()) + + Optional.ofNullable(request.memberId()).ifPresent(memberId -> { + Member member = memberRepository.findById(memberId) .orElseThrow(() -> new NoSuchElementException( - "회원 ID: " + request.memberId() + "가 존재하지 않습니다.")); + "회원 ID: " + memberId + "가 존재하지 않습니다.")); store.changeOwner(member); - } - if (request.name() != null) { - store.changeName(request.name()); - } - if (request.description() != null) { - store.changeDescription(request.description()); - } + }); + Optional.ofNullable(request.name()).ifPresent(store::changeName); + Optional.ofNullable(request.description()).ifPresent(store::changeDescription); } @Transactional From 96ac439aa7cbf5bd30e571e3966bcffb89534faf Mon Sep 17 00:00:00 2001 From: haazz Date: Thu, 23 Oct 2025 23:16:06 +0900 Subject: [PATCH 12/26] feat: TestSecurityConfig class --- .../global/config/TestSecurityConfig.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 sunpick/src/test/java/com/backend/sunpick/global/config/TestSecurityConfig.java diff --git a/sunpick/src/test/java/com/backend/sunpick/global/config/TestSecurityConfig.java b/sunpick/src/test/java/com/backend/sunpick/global/config/TestSecurityConfig.java new file mode 100644 index 0000000..8d1ef93 --- /dev/null +++ b/sunpick/src/test/java/com/backend/sunpick/global/config/TestSecurityConfig.java @@ -0,0 +1,21 @@ +package com.backend.sunpick.global.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +@TestConfiguration +public class TestSecurityConfig { + + @Bean + public SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/store/**").permitAll() + .anyRequest().authenticated() + ); + return http.build(); + } +} From fb0e7e328730b207135e2405cc5a4b2aba259a46 Mon Sep 17 00:00:00 2001 From: haazz Date: Thu, 23 Oct 2025 23:16:38 +0900 Subject: [PATCH 13/26] =?UTF-8?q?feat:=20StoreControllerTest=20class=20?= =?UTF-8?q?=EC=83=81=EC=A0=90=20=EB=93=B1=EB=A1=9D=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sunpick/store/StoreControllerTest.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java diff --git a/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java b/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java new file mode 100644 index 0000000..03dcab7 --- /dev/null +++ b/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java @@ -0,0 +1,100 @@ +package com.backend.sunpick.store; + +import com.backend.sunpick.global.config.TestSecurityConfig; +import com.backend.sunpick.domain.store.controller.StoreController; +import com.backend.sunpick.domain.store.dto.request.StoreCreateRequest; +import com.backend.sunpick.domain.store.service.StoreService; +import com.fasterxml.jackson.databind.ObjectMapper; +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.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.ArgumentMatchers.any; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(StoreController.class) +@Import(TestSecurityConfig.class) +public class StoreControllerTest { + + @MockitoBean + private StoreService storeService; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + @DisplayName("POST /api/store - 상점 등록 201 Created") + void createStore_success() throws Exception { + + StoreCreateRequest request = new StoreCreateRequest(1, "storeName", "description"); + + mockMvc.perform(post("/api/store") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()); + Mockito.verify(storeService, Mockito.times(1)).createStore(any(StoreCreateRequest.class)); + } + + @Test + @DisplayName("POST /api/store - 상점 등록 memberId가 null이면 400 Bad Request") + void createStore_fail_memberIdNull() throws Exception { + StoreCreateRequest request = new StoreCreateRequest(null, "storeName", "description"); + + mockMvc.perform(post("/api/store") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + + Mockito.verify(storeService, Mockito.never()).createStore(any()); + } + + @Test + @DisplayName("POST /api/store - 상점 등록 memberId가 0이면 400 Bad Request") + void createStore_fail_memberIdZero() throws Exception { + StoreCreateRequest request = new StoreCreateRequest(0, "상점", "설명"); + + mockMvc.perform(post("/api/store") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + + Mockito.verify(storeService, Mockito.never()).createStore(any()); + } + + @Test + @DisplayName("POST /api/store - 상점 등록 name이 비어있으면 400 Bad Request") + void createStore_fail_nameBlank() throws Exception { + StoreCreateRequest request = new StoreCreateRequest(1, "", "설명"); + + mockMvc.perform(post("/api/store") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + + Mockito.verify(storeService, Mockito.never()).createStore(any()); + } + + @Test + @DisplayName("POST /api/store - 상점 등록 name이 20자 초과면 400 Bad Request") + void createStore_fail_nameTooLong() throws Exception { + String longName = "a".repeat(21); + StoreCreateRequest request = new StoreCreateRequest(1, longName, "설명"); + + mockMvc.perform(post("/api/store") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + + Mockito.verify(storeService, Mockito.never()).createStore(any()); + } +} From 81db18d36c54bfdd56144766c79001d64d4cb403 Mon Sep 17 00:00:00 2001 From: haazz Date: Sat, 25 Oct 2025 10:09:13 +0900 Subject: [PATCH 14/26] =?UTF-8?q?feat:=20StoreControllerTest=20class=20?= =?UTF-8?q?=EC=83=81=EC=A0=90=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sunpick/store/StoreControllerTest.java | 70 ++++++++++++++++++- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java b/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java index 03dcab7..e700863 100644 --- a/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java +++ b/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java @@ -1,10 +1,13 @@ package com.backend.sunpick.store; +import com.backend.sunpick.domain.store.dto.response.StoreResponse; import com.backend.sunpick.global.config.TestSecurityConfig; import com.backend.sunpick.domain.store.controller.StoreController; import com.backend.sunpick.domain.store.dto.request.StoreCreateRequest; import com.backend.sunpick.domain.store.service.StoreService; import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; +import java.util.NoSuchElementException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -16,7 +19,10 @@ import org.springframework.test.web.servlet.MockMvc; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(StoreController.class) @@ -61,7 +67,7 @@ void createStore_fail_memberIdNull() throws Exception { @Test @DisplayName("POST /api/store - 상점 등록 memberId가 0이면 400 Bad Request") void createStore_fail_memberIdZero() throws Exception { - StoreCreateRequest request = new StoreCreateRequest(0, "상점", "설명"); + StoreCreateRequest request = new StoreCreateRequest(0, "storeName", "description"); mockMvc.perform(post("/api/store") .contentType(MediaType.APPLICATION_JSON) @@ -74,7 +80,7 @@ void createStore_fail_memberIdZero() throws Exception { @Test @DisplayName("POST /api/store - 상점 등록 name이 비어있으면 400 Bad Request") void createStore_fail_nameBlank() throws Exception { - StoreCreateRequest request = new StoreCreateRequest(1, "", "설명"); + StoreCreateRequest request = new StoreCreateRequest(1, "", "description"); mockMvc.perform(post("/api/store") .contentType(MediaType.APPLICATION_JSON) @@ -88,7 +94,7 @@ void createStore_fail_nameBlank() throws Exception { @DisplayName("POST /api/store - 상점 등록 name이 20자 초과면 400 Bad Request") void createStore_fail_nameTooLong() throws Exception { String longName = "a".repeat(21); - StoreCreateRequest request = new StoreCreateRequest(1, longName, "설명"); + StoreCreateRequest request = new StoreCreateRequest(1, longName, "description"); mockMvc.perform(post("/api/store") .contentType(MediaType.APPLICATION_JSON) @@ -97,4 +103,62 @@ void createStore_fail_nameTooLong() throws Exception { Mockito.verify(storeService, Mockito.never()).createStore(any()); } + + @Test + @DisplayName("GET /api/store - 상점 목록 조회 200 OK") + void getStoreAll_success() throws Exception { + List responses = List.of( + new StoreResponse(1, "store1", "description1", "owner1"), + new StoreResponse(2, "store2", "description2", "owner2") + ); + when(storeService.getStoreAll()).thenReturn(responses); + + mockMvc.perform(get("/api/store")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].id").value(1)) + .andExpect(jsonPath("$[0].name").value("store1")) + .andExpect(jsonPath("$[0].description").value("description1")) + .andExpect(jsonPath("$[0].ownerName").value("owner1")) + .andExpect(jsonPath("$[1].id").value(2)) + .andExpect(jsonPath("$[1].name").value("store2")) + .andExpect(jsonPath("$[1].description").value("description2")) + .andExpect(jsonPath("$[1].ownerName").value("owner2")); + + Mockito.verify(storeService, Mockito.times(1)).getStoreAll(); + } + + @Test + @DisplayName("GET /api/store/{storeId} - 상점 단건 조회 200 OK") + void getStoreById_success() throws Exception { + int storeId = 1; + StoreResponse response = new StoreResponse(storeId, "store", "description", "owner"); + + when(storeService.getStoreById(storeId)).thenReturn(response); + + mockMvc.perform(get("/api/store/{storeId}", storeId)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(storeId)) + .andExpect(jsonPath("$.name").value("store")) + .andExpect(jsonPath("$.description").value("description")) + .andExpect(jsonPath("$.ownerName").value("owner")); + + Mockito.verify(storeService, Mockito.times(1)).getStoreById(storeId); + } + + + @Test + @DisplayName("GET /api/store/{storeId} - 상점이 없으면 404 Not Found") + void getStoreById_notFound() throws Exception { + int storeId = 999; + + when(storeService.getStoreById(storeId)) + .thenThrow(new NoSuchElementException("상점 ID: " + storeId + "가 존재하지 않습니다.")); + + mockMvc.perform(get("/api/store/{storeId}", storeId)) + .andExpect(status().isNotFound()); + + Mockito.verify(storeService, Mockito.times(1)).getStoreById(storeId); + } + + } From aba433b2142c2fecacb28d6a738333f589359766 Mon Sep 17 00:00:00 2001 From: haazz Date: Wed, 29 Oct 2025 20:28:02 +0900 Subject: [PATCH 15/26] =?UTF-8?q?feat:=20StoreControllerTest=20class=20?= =?UTF-8?q?=EC=83=81=EC=A0=90=20=EC=88=98=EC=A0=95=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sunpick/store/StoreControllerTest.java | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java b/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java index e700863..9e3d90e 100644 --- a/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java +++ b/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java @@ -1,5 +1,6 @@ package com.backend.sunpick.store; +import com.backend.sunpick.domain.store.dto.request.StoreModifyRequest; import com.backend.sunpick.domain.store.dto.response.StoreResponse; import com.backend.sunpick.global.config.TestSecurityConfig; import com.backend.sunpick.domain.store.controller.StoreController; @@ -20,7 +21,9 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +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.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -145,20 +148,46 @@ void getStoreById_success() throws Exception { Mockito.verify(storeService, Mockito.times(1)).getStoreById(storeId); } - @Test - @DisplayName("GET /api/store/{storeId} - 상점이 없으면 404 Not Found") - void getStoreById_notFound() throws Exception { - int storeId = 999; + @DisplayName("PATCH /api/store/{id} - 상점 수정 204 No Content") + void modifyStore_success() throws Exception { + StoreModifyRequest request = new StoreModifyRequest(1, "new store", "new description"); - when(storeService.getStoreById(storeId)) - .thenThrow(new NoSuchElementException("상점 ID: " + storeId + "가 존재하지 않습니다.")); + mockMvc.perform(patch("/api/store/{storeId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isNoContent()); - mockMvc.perform(get("/api/store/{storeId}", storeId)) - .andExpect(status().isNotFound()); + Mockito.verify(storeService, Mockito.times(1)) + .modifyStore(any(Integer.class), any(StoreModifyRequest.class)); + } - Mockito.verify(storeService, Mockito.times(1)).getStoreById(storeId); + @Test + @DisplayName("POST /api/store - 상점 수정 memberId가 0이면 400 Bad Request") + void modifyStore_fail_memberIdZero() throws Exception { + StoreModifyRequest request = new StoreModifyRequest(0, "storeName", "description"); + + mockMvc.perform(patch("/api/store/{storeId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + + Mockito.verify(storeService, Mockito.never()) + .modifyStore(any(Integer.class), any(StoreModifyRequest.class)); } + @Test + @DisplayName("PATCH /api/store/{id} - 상점 수정 name이 20자 초과면 400 Bad Request") + void modifyStore_fail_nameTooLong() throws Exception { + String longName = "a".repeat(21); + StoreModifyRequest request = new StoreModifyRequest(1, longName, "description"); + mockMvc.perform(patch("/api/store/{storeId}", 1) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isBadRequest()); + + Mockito.verify(storeService, Mockito.never()) + .modifyStore(any(Integer.class), any(StoreModifyRequest.class)); + } } From c99ebb42b96babffd57b9a5834746083ef0e9253 Mon Sep 17 00:00:00 2001 From: haazz Date: Wed, 29 Oct 2025 20:29:01 +0900 Subject: [PATCH 16/26] =?UTF-8?q?feat:=20StoreControllerTest=20class=20?= =?UTF-8?q?=EC=83=81=EC=A0=90=20=EC=82=AD=EC=A0=9C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/backend/sunpick/store/StoreControllerTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java b/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java index 9e3d90e..d3e8937 100644 --- a/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java +++ b/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java @@ -190,4 +190,13 @@ void modifyStore_fail_nameTooLong() throws Exception { Mockito.verify(storeService, Mockito.never()) .modifyStore(any(Integer.class), any(StoreModifyRequest.class)); } + + @Test + @DisplayName("DELETE /api/store/{id} - 상점 삭제 204 No Content") + void deleteStore_success() throws Exception { + mockMvc.perform(delete("/api/store/{storeId}", 1)) + .andExpect(status().isNoContent()); + + Mockito.verify(storeService, Mockito.times(1)).deleteStore(any(Integer.class)); + } } From 38290a520b6978f948abfb03f5725b45e4d6dc0c Mon Sep 17 00:00:00 2001 From: haazz Date: Wed, 29 Oct 2025 21:01:58 +0900 Subject: [PATCH 17/26] =?UTF-8?q?chore:=20GlobalExceptionHandler=20class?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sunpick/global/exception/GlobalExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunpick/src/main/java/com/backend/sunpick/global/exception/GlobalExceptionHandler.java b/sunpick/src/main/java/com/backend/sunpick/global/exception/GlobalExceptionHandler.java index 8438432..49f6e5e 100644 --- a/sunpick/src/main/java/com/backend/sunpick/global/exception/GlobalExceptionHandler.java +++ b/sunpick/src/main/java/com/backend/sunpick/global/exception/GlobalExceptionHandler.java @@ -38,7 +38,7 @@ public ResponseEntity handleIllegalArgument(IllegalArgumentException e) @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException e) { Optional fieldMsg = e.getBindingResult().getFieldErrors().stream() - .map(fe -> fe.getField() + ": " + messageOrDefault(fe.getDefaultMessage(), "유효성 검증 오류")) + .map(fe -> messageOrDefault(fe.getDefaultMessage(), "유효성 검증 오류")) .findFirst(); Optional globalMsg = e.getBindingResult().getGlobalErrors().stream() From c1069da230df9e68c42577c450c48cf8e8903d03 Mon Sep 17 00:00:00 2001 From: haazz Date: Wed, 29 Oct 2025 21:02:33 +0900 Subject: [PATCH 18/26] =?UTF-8?q?feat:=20StoreControllerTest=20class=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sunpick/store/StoreControllerTest.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java b/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java index d3e8937..599fe93 100644 --- a/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java +++ b/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java @@ -25,6 +25,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -62,7 +63,8 @@ void createStore_fail_memberIdNull() throws Exception { mockMvc.perform(post("/api/store") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andExpect(content().string("회원 ID는 필수입니다.")); Mockito.verify(storeService, Mockito.never()).createStore(any()); } @@ -75,7 +77,8 @@ void createStore_fail_memberIdZero() throws Exception { mockMvc.perform(post("/api/store") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andExpect(content().string("회원 ID는 1 이상의 값이어야 합니다.")); Mockito.verify(storeService, Mockito.never()).createStore(any()); } @@ -88,7 +91,8 @@ void createStore_fail_nameBlank() throws Exception { mockMvc.perform(post("/api/store") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andExpect(content().string("상점명은 필수입니다.")); Mockito.verify(storeService, Mockito.never()).createStore(any()); } @@ -102,7 +106,8 @@ void createStore_fail_nameTooLong() throws Exception { mockMvc.perform(post("/api/store") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andExpect(content().string("상점명은 20자 이하로 입력해 주세요.")); Mockito.verify(storeService, Mockito.never()).createStore(any()); } @@ -170,7 +175,8 @@ void modifyStore_fail_memberIdZero() throws Exception { mockMvc.perform(patch("/api/store/{storeId}", 1) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andExpect(content().string("회원 ID는 1 이상의 값이어야 합니다.")); Mockito.verify(storeService, Mockito.never()) .modifyStore(any(Integer.class), any(StoreModifyRequest.class)); @@ -185,7 +191,8 @@ void modifyStore_fail_nameTooLong() throws Exception { mockMvc.perform(patch("/api/store/{storeId}", 1) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andExpect(content().string("상점명은 20자 이하로 입력해 주세요.")); Mockito.verify(storeService, Mockito.never()) .modifyStore(any(Integer.class), any(StoreModifyRequest.class)); From d489150108126961b62d941930e1298d8fddc055 Mon Sep 17 00:00:00 2001 From: haazz Date: Mon, 3 Nov 2025 23:21:59 +0900 Subject: [PATCH 19/26] =?UTF-8?q?feat:=20StoreServiceTest=20class=20create?= =?UTF-8?q?Store()=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sunpick/store/StoreServiceTest.java | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java diff --git a/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java b/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java new file mode 100644 index 0000000..eaad394 --- /dev/null +++ b/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java @@ -0,0 +1,127 @@ +package com.backend.sunpick.store; + +import com.backend.sunpick.domain.member.entity.Member; +import com.backend.sunpick.domain.member.repository.MemberRepository; +import com.backend.sunpick.domain.store.dto.request.StoreCreateRequest; +import com.backend.sunpick.domain.store.entity.Store; +import com.backend.sunpick.domain.store.repository.StoreRepository; +import com.backend.sunpick.domain.store.service.StoreService; +import java.util.NoSuchElementException; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +@ExtendWith(MockitoExtension.class) +public class StoreServiceTest { + @Mock + private StoreRepository storeRepository; + + @Mock + private MemberRepository memberRepository; + + @InjectMocks + private StoreService storeService; + + private static final int NON_EXISTENT_ID = Integer.MAX_VALUE; + + @Test + @DisplayName("createStore() - 성공") + void createStore_success() { + StoreCreateRequest request = new StoreCreateRequest(1, "storeName", "description"); + Member member = mock(Member.class); + when(member.getName()).thenReturn("memberName"); + when(memberRepository.findById(1)).thenReturn(Optional.of(member)); + + storeService.createStore(request); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Store.class); + verify(storeRepository, Mockito.times(1)).save(captor.capture()); + Store saved = captor.getValue(); + + assertThat(saved.getName()).isEqualTo("storeName"); + assertThat(saved.getDescription()).isEqualTo("description"); + assertThat(saved.getMember()).isEqualTo(member); + assertThat(saved.getOwnerName()).isEqualTo("memberName"); + } + + @Test + @DisplayName("createStore() - 실패 memberId가 존재하지 않는 경우") + void createStore_fail_memberNotFound() { + StoreCreateRequest request = new StoreCreateRequest(NON_EXISTENT_ID, "storeName", "description"); + when(memberRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + + NoSuchElementException exception = assertThrows(NoSuchElementException.class, + () -> storeService.createStore(request)); + assertEquals("회원 ID: " + NON_EXISTENT_ID + "가 존재하지 않습니다.", exception.getMessage()); + verify(storeRepository, Mockito.never()).save(any(Store.class)); + } + + @Test + @DisplayName("modifyStore() - 성공") + void modifyStore_success() { + + } + + @Test + @DisplayName("modifyStore() - 실패 storeId가 존재하지 않는 경우") + void modifyStore_fail_storeNotFound() { + + } + + @Test + @DisplayName("modifyStore() - 실패 storeId가 삭제된 경우") + void modifyStore_fail_deletedStore() { + + } + + @Test + @DisplayName("modifyStore() - 실패 memberId가 존재하지 않는 경우") + void modifyStore_fail_memberNotFound() { + + } + + @Test + @DisplayName("deleteStore() - 성공") + void deleteStore_success() { + + } + + @Test + @DisplayName("deleteStore() - 실패 storeId가 존재하지 않는 경우") + void deleteStore_fail_storeNotFound() { + + } + + @Test + @DisplayName("getStoreAll() - 성공") + void getStoreAll_success() { + + } + + @Test + @DisplayName("getStoreById() - 성공") + void getStoreById_success() { + + } + + @Test + @DisplayName("getStoreById() - 실패 storeId가 존재하지 않는 경우") + void getStoreById_fail_storeNotFound() { + + } +} From 06f94a4f695553ebb4181d4d76b8616b23741929 Mon Sep 17 00:00:00 2001 From: haazz Date: Thu, 6 Nov 2025 14:39:43 +0900 Subject: [PATCH 20/26] =?UTF-8?q?chore:=20StoreService=20class=20=EC=98=A4?= =?UTF-8?q?=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 --- .../com/backend/sunpick/domain/store/service/StoreService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java index d154566..9defde0 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java @@ -25,7 +25,7 @@ public class StoreService { public void createStore(StoreCreateRequest request) { Member member = memberRepository.findById(request.memberId()) .orElseThrow(() -> new NoSuchElementException( - "회원 ID: " + request.memberId() + "가 존재하지 안습니다.")); + "회원 ID: " + request.memberId() + "가 존재하지 않습니다.")); storeRepository.save(Store.builder() .member(member) .name(request.name()) From c10c06e59479af8e99c8d2a91aa1e01ebcb47443 Mon Sep 17 00:00:00 2001 From: haazz Date: Thu, 6 Nov 2025 14:53:59 +0900 Subject: [PATCH 21/26] =?UTF-8?q?feat:=20StoreServiceTest=20class=20modify?= =?UTF-8?q?Store()=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sunpick/store/StoreServiceTest.java | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java b/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java index eaad394..2985650 100644 --- a/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java +++ b/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java @@ -3,6 +3,7 @@ import com.backend.sunpick.domain.member.entity.Member; import com.backend.sunpick.domain.member.repository.MemberRepository; import com.backend.sunpick.domain.store.dto.request.StoreCreateRequest; +import com.backend.sunpick.domain.store.dto.request.StoreModifyRequest; import com.backend.sunpick.domain.store.entity.Store; import com.backend.sunpick.domain.store.repository.StoreRepository; import com.backend.sunpick.domain.store.service.StoreService; @@ -14,14 +15,16 @@ import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -50,7 +53,7 @@ void createStore_success() { storeService.createStore(request); ArgumentCaptor captor = ArgumentCaptor.forClass(Store.class); - verify(storeRepository, Mockito.times(1)).save(captor.capture()); + verify(storeRepository, times(1)).save(captor.capture()); Store saved = captor.getValue(); assertThat(saved.getName()).isEqualTo("storeName"); @@ -68,31 +71,73 @@ void createStore_fail_memberNotFound() { NoSuchElementException exception = assertThrows(NoSuchElementException.class, () -> storeService.createStore(request)); assertEquals("회원 ID: " + NON_EXISTENT_ID + "가 존재하지 않습니다.", exception.getMessage()); - verify(storeRepository, Mockito.never()).save(any(Store.class)); + verify(storeRepository, never()).save(any(Store.class)); } @Test @DisplayName("modifyStore() - 성공") void modifyStore_success() { - + Member oldOwner = mock(Member.class); + Store store = Store.builder() + .name("name") + .description("description") + .ownerName("ownerName") + .member(oldOwner) + .build(); + when(storeRepository.findById(1)).thenReturn(Optional.of(store)); + + Member newOwner = mock(Member.class); + when(newOwner.getName()).thenReturn("newOwnerName"); + when(memberRepository.findById(2)).thenReturn(Optional.of(newOwner)); + + StoreModifyRequest request = new StoreModifyRequest(2, "newName", "newDescription"); + + storeService.modifyStore(1, request); + + assertEquals("newName", store.getName()); + assertEquals("newDescription", store.getDescription()); + assertEquals(newOwner, store.getMember()); + assertEquals("newOwnerName", store.getOwnerName()); } @Test @DisplayName("modifyStore() - 실패 storeId가 존재하지 않는 경우") void modifyStore_fail_storeNotFound() { + StoreModifyRequest request = new StoreModifyRequest(1, "newName", "newDescription"); + when(storeRepository.findById(any(Integer.class))).thenReturn(Optional.empty()); + + NoSuchElementException exception = assertThrows(NoSuchElementException.class, + () -> storeService.modifyStore(NON_EXISTENT_ID, request)); + assertEquals("상점 ID: " + NON_EXISTENT_ID + "가 존재하지 않습니다.", exception.getMessage()); } @Test @DisplayName("modifyStore() - 실패 storeId가 삭제된 경우") void modifyStore_fail_deletedStore() { + Store store = mock(Store.class); + when(store.isDeleted()).thenReturn(true); + when(storeRepository.findById(1)).thenReturn(Optional.of(store)); + StoreModifyRequest request = new StoreModifyRequest(1, "newName", "newDescription"); + NoSuchElementException exception = assertThrows(NoSuchElementException.class, + () -> storeService.modifyStore(1, request)); + assertEquals("상점 ID: " + 1 + "는 삭제되었습니다.", exception.getMessage()); } @Test @DisplayName("modifyStore() - 실패 memberId가 존재하지 않는 경우") void modifyStore_fail_memberNotFound() { + Store store = mock(Store.class); + when(store.isDeleted()).thenReturn(false); + when(storeRepository.findById(1)).thenReturn(Optional.of(store)); + when(memberRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + + StoreModifyRequest request = new StoreModifyRequest(NON_EXISTENT_ID, "newName", "newDescription"); + NoSuchElementException exception = assertThrows(NoSuchElementException.class, + () -> storeService.modifyStore(1, request)); + assertEquals("회원 ID: " + NON_EXISTENT_ID + "가 존재하지 않습니다.", exception.getMessage()); } @Test From c120036655fc632968a3309de4ec58e480726abe Mon Sep 17 00:00:00 2001 From: haazz Date: Thu, 6 Nov 2025 14:55:45 +0900 Subject: [PATCH 22/26] =?UTF-8?q?feat:=20StoreServiceTest=20class=20create?= =?UTF-8?q?Store()=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit storeRepository save 검증 추가 --- .../test/java/com/backend/sunpick/store/StoreServiceTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java b/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java index 2985650..1c1b9d8 100644 --- a/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java +++ b/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java @@ -60,6 +60,7 @@ void createStore_success() { assertThat(saved.getDescription()).isEqualTo("description"); assertThat(saved.getMember()).isEqualTo(member); assertThat(saved.getOwnerName()).isEqualTo("memberName"); + verify(storeRepository, times(1)).save(any(Store.class)); } @Test From f5060b702f9cb82ed8bfb7705a4cb5f23e47d6d4 Mon Sep 17 00:00:00 2001 From: haazz Date: Thu, 6 Nov 2025 15:00:06 +0900 Subject: [PATCH 23/26] =?UTF-8?q?feat:=20StoreServiceTest=20class=20delete?= =?UTF-8?q?Store()=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/sunpick/store/StoreServiceTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java b/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java index 1c1b9d8..3cfe0de 100644 --- a/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java +++ b/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java @@ -144,13 +144,27 @@ void modifyStore_fail_memberNotFound() { @Test @DisplayName("deleteStore() - 성공") void deleteStore_success() { + Store store = Store.builder() + .name("name") + .description("description") + .ownerName("ownerName") + .build(); + when(storeRepository.findById(1)).thenReturn(Optional.of(store)); + storeService.deleteStore(1); + + assertTrue(store.isDeleted()); } @Test @DisplayName("deleteStore() - 실패 storeId가 존재하지 않는 경우") void deleteStore_fail_storeNotFound() { + when(storeRepository.findById(any(Integer.class))).thenReturn(Optional.empty()); + NoSuchElementException exception = assertThrows(NoSuchElementException.class, + () -> storeService.deleteStore(NON_EXISTENT_ID)); + + assertEquals("상점 ID: " + NON_EXISTENT_ID + "가 존재하지 않습니다.", exception.getMessage()); } @Test From f5bbf41eb13ef774a8418bd9f6036334be3be3b0 Mon Sep 17 00:00:00 2001 From: haazz Date: Thu, 6 Nov 2025 15:08:25 +0900 Subject: [PATCH 24/26] =?UTF-8?q?feat:=20StoreResponse=20class=20ownerId?= =?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 --- .../sunpick/domain/store/dto/response/StoreResponse.java | 1 + .../sunpick/domain/store/service/StoreService.java | 2 +- .../com/backend/sunpick/store/StoreControllerTest.java | 8 +++++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/response/StoreResponse.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/response/StoreResponse.java index 2a7bda3..0012a62 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/response/StoreResponse.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/dto/response/StoreResponse.java @@ -4,6 +4,7 @@ public record StoreResponse( Integer id, String name, String description, + Integer ownerId, String ownerName ) { diff --git a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java index 9defde0..b6ee1ff 100644 --- a/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java +++ b/sunpick/src/main/java/com/backend/sunpick/domain/store/service/StoreService.java @@ -76,6 +76,6 @@ public void deleteStore(Integer storeId) { private StoreResponse toResponse(Store store) { return new StoreResponse(store.getId(), store.getName(), store.getDescription(), - store.getOwnerName()); + store.getMember().getId(), store.getOwnerName()); } } diff --git a/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java b/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java index 599fe93..1972d33 100644 --- a/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java +++ b/sunpick/src/test/java/com/backend/sunpick/store/StoreControllerTest.java @@ -116,8 +116,8 @@ void createStore_fail_nameTooLong() throws Exception { @DisplayName("GET /api/store - 상점 목록 조회 200 OK") void getStoreAll_success() throws Exception { List responses = List.of( - new StoreResponse(1, "store1", "description1", "owner1"), - new StoreResponse(2, "store2", "description2", "owner2") + new StoreResponse(1, "store1", "description1", 1, "owner1"), + new StoreResponse(2, "store2", "description2", 2, "owner2") ); when(storeService.getStoreAll()).thenReturn(responses); @@ -126,10 +126,12 @@ void getStoreAll_success() throws Exception { .andExpect(jsonPath("$[0].id").value(1)) .andExpect(jsonPath("$[0].name").value("store1")) .andExpect(jsonPath("$[0].description").value("description1")) + .andExpect(jsonPath("$[0].ownerId").value(1)) .andExpect(jsonPath("$[0].ownerName").value("owner1")) .andExpect(jsonPath("$[1].id").value(2)) .andExpect(jsonPath("$[1].name").value("store2")) .andExpect(jsonPath("$[1].description").value("description2")) + .andExpect(jsonPath("$[1].ownerId").value(2)) .andExpect(jsonPath("$[1].ownerName").value("owner2")); Mockito.verify(storeService, Mockito.times(1)).getStoreAll(); @@ -139,7 +141,7 @@ void getStoreAll_success() throws Exception { @DisplayName("GET /api/store/{storeId} - 상점 단건 조회 200 OK") void getStoreById_success() throws Exception { int storeId = 1; - StoreResponse response = new StoreResponse(storeId, "store", "description", "owner"); + StoreResponse response = new StoreResponse(storeId, "store", "description", 1, "owner"); when(storeService.getStoreById(storeId)).thenReturn(response); From 760a0c741f2817ed41ea0706cb76167075464b9a Mon Sep 17 00:00:00 2001 From: haazz Date: Thu, 6 Nov 2025 15:26:58 +0900 Subject: [PATCH 25/26] =?UTF-8?q?feat:=20StoreServiceTest=20class=20getSto?= =?UTF-8?q?re()=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sunpick/store/StoreServiceTest.java | 80 +++++++++++++++++-- 1 file changed, 72 insertions(+), 8 deletions(-) diff --git a/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java b/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java index 3cfe0de..655c2a5 100644 --- a/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java +++ b/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java @@ -4,9 +4,11 @@ import com.backend.sunpick.domain.member.repository.MemberRepository; import com.backend.sunpick.domain.store.dto.request.StoreCreateRequest; import com.backend.sunpick.domain.store.dto.request.StoreModifyRequest; +import com.backend.sunpick.domain.store.dto.response.StoreResponse; import com.backend.sunpick.domain.store.entity.Store; import com.backend.sunpick.domain.store.repository.StoreRepository; import com.backend.sunpick.domain.store.service.StoreService; +import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; import org.junit.jupiter.api.DisplayName; @@ -31,6 +33,7 @@ @ExtendWith(MockitoExtension.class) public class StoreServiceTest { + @Mock private StoreRepository storeRepository; @@ -66,7 +69,8 @@ void createStore_success() { @Test @DisplayName("createStore() - 실패 memberId가 존재하지 않는 경우") void createStore_fail_memberNotFound() { - StoreCreateRequest request = new StoreCreateRequest(NON_EXISTENT_ID, "storeName", "description"); + StoreCreateRequest request = new StoreCreateRequest(NON_EXISTENT_ID, "storeName", + "description"); when(memberRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); NoSuchElementException exception = assertThrows(NoSuchElementException.class, @@ -80,11 +84,11 @@ void createStore_fail_memberNotFound() { void modifyStore_success() { Member oldOwner = mock(Member.class); Store store = Store.builder() - .name("name") - .description("description") - .ownerName("ownerName") - .member(oldOwner) - .build(); + .name("name") + .description("description") + .ownerName("ownerName") + .member(oldOwner) + .build(); when(storeRepository.findById(1)).thenReturn(Optional.of(store)); Member newOwner = mock(Member.class); @@ -134,7 +138,8 @@ void modifyStore_fail_memberNotFound() { when(storeRepository.findById(1)).thenReturn(Optional.of(store)); when(memberRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); - StoreModifyRequest request = new StoreModifyRequest(NON_EXISTENT_ID, "newName", "newDescription"); + StoreModifyRequest request = new StoreModifyRequest(NON_EXISTENT_ID, "newName", + "newDescription"); NoSuchElementException exception = assertThrows(NoSuchElementException.class, () -> storeService.modifyStore(1, request)); @@ -170,18 +175,77 @@ void deleteStore_fail_storeNotFound() { @Test @DisplayName("getStoreAll() - 성공") void getStoreAll_success() { - + Member member1 = mock(Member.class); + when(member1.getId()).thenReturn(1); + + Store store1 = mock(Store.class); + when(store1.getId()).thenReturn(1); + when(store1.getName()).thenReturn("store1"); + when(store1.getDescription()).thenReturn("description1"); + when(store1.getMember()).thenReturn(member1); + when(store1.getOwnerName()).thenReturn("ownerName1"); + + Member member2 = mock(Member.class); + when(member2.getId()).thenReturn(2); + + Store store2 = mock(Store.class); + when(store2.getId()).thenReturn(2); + when(store2.getName()).thenReturn("store2"); + when(store2.getDescription()).thenReturn("description2"); + when(store2.getMember()).thenReturn(member2); + when(store2.getOwnerName()).thenReturn("ownerName2"); + + when(storeRepository.findAll()).thenReturn(List.of(store1, store2)); + + List response = storeService.getStoreAll(); + + assertEquals(2, response.size()); + + assertEquals(1, response.get(0).id()); + assertEquals("store1", response.get(0).name()); + assertEquals("description1", response.get(0).description()); + assertEquals(1, response.get(0).ownerId()); + assertEquals("ownerName1", response.get(0).ownerName()); + + assertEquals(2, response.get(1).id()); + assertEquals("store2", response.get(1).name()); + assertEquals("description2", response.get(1).description()); + assertEquals(2, response.get(1).ownerId()); + assertEquals("ownerName2", response.get(1).ownerName()); } @Test @DisplayName("getStoreById() - 성공") void getStoreById_success() { + Member member = mock(Member.class); + when(member.getId()).thenReturn(1); + Store store = mock(Store.class); + when(store.getId()).thenReturn(1); + when(store.getName()).thenReturn("store"); + when(store.getDescription()).thenReturn("description"); + when(store.getMember()).thenReturn(member); + when(store.getOwnerName()).thenReturn("ownerName"); + + when(storeRepository.findById(1)).thenReturn(Optional.of(store)); + + StoreResponse response = storeService.getStoreById(1); + + assertEquals(1, response.id()); + assertEquals("store", response.name()); + assertEquals("description", response.description()); + assertEquals(1, response.ownerId()); + assertEquals("ownerName", response.ownerName()); } @Test @DisplayName("getStoreById() - 실패 storeId가 존재하지 않는 경우") void getStoreById_fail_storeNotFound() { + when(storeRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + NoSuchElementException exception = assertThrows(NoSuchElementException.class, + () -> storeService.getStoreById(NON_EXISTENT_ID)); + + assertEquals("상점 ID: " + NON_EXISTENT_ID + "가 존재하지 않습니다.", exception.getMessage()); } } From 96d9b33b32994ae8d1dfc6ec7835016dec2eee4a Mon Sep 17 00:00:00 2001 From: haazz Date: Thu, 6 Nov 2025 15:29:02 +0900 Subject: [PATCH 26/26] =?UTF-8?q?chore:=20StoreServiceTest=20class=20NON?= =?UTF-8?q?=5FEXISTENT=5FID=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sunpick/store/StoreServiceTest.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java b/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java index 655c2a5..967ac92 100644 --- a/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java +++ b/sunpick/src/test/java/com/backend/sunpick/store/StoreServiceTest.java @@ -43,8 +43,6 @@ public class StoreServiceTest { @InjectMocks private StoreService storeService; - private static final int NON_EXISTENT_ID = Integer.MAX_VALUE; - @Test @DisplayName("createStore() - 성공") void createStore_success() { @@ -69,13 +67,13 @@ void createStore_success() { @Test @DisplayName("createStore() - 실패 memberId가 존재하지 않는 경우") void createStore_fail_memberNotFound() { - StoreCreateRequest request = new StoreCreateRequest(NON_EXISTENT_ID, "storeName", + StoreCreateRequest request = new StoreCreateRequest(1, "storeName", "description"); - when(memberRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + when(memberRepository.findById(1)).thenReturn(Optional.empty()); NoSuchElementException exception = assertThrows(NoSuchElementException.class, () -> storeService.createStore(request)); - assertEquals("회원 ID: " + NON_EXISTENT_ID + "가 존재하지 않습니다.", exception.getMessage()); + assertEquals("회원 ID: " + 1 + "가 존재하지 않습니다.", exception.getMessage()); verify(storeRepository, never()).save(any(Store.class)); } @@ -112,9 +110,9 @@ void modifyStore_fail_storeNotFound() { when(storeRepository.findById(any(Integer.class))).thenReturn(Optional.empty()); NoSuchElementException exception = assertThrows(NoSuchElementException.class, - () -> storeService.modifyStore(NON_EXISTENT_ID, request)); + () -> storeService.modifyStore(1, request)); - assertEquals("상점 ID: " + NON_EXISTENT_ID + "가 존재하지 않습니다.", exception.getMessage()); + assertEquals("상점 ID: " + 1 + "가 존재하지 않습니다.", exception.getMessage()); } @Test @@ -136,14 +134,14 @@ void modifyStore_fail_memberNotFound() { Store store = mock(Store.class); when(store.isDeleted()).thenReturn(false); when(storeRepository.findById(1)).thenReturn(Optional.of(store)); - when(memberRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + when(memberRepository.findById(1)).thenReturn(Optional.empty()); - StoreModifyRequest request = new StoreModifyRequest(NON_EXISTENT_ID, "newName", + StoreModifyRequest request = new StoreModifyRequest(1, "newName", "newDescription"); NoSuchElementException exception = assertThrows(NoSuchElementException.class, () -> storeService.modifyStore(1, request)); - assertEquals("회원 ID: " + NON_EXISTENT_ID + "가 존재하지 않습니다.", exception.getMessage()); + assertEquals("회원 ID: " + 1 + "가 존재하지 않습니다.", exception.getMessage()); } @Test @@ -167,9 +165,9 @@ void deleteStore_fail_storeNotFound() { when(storeRepository.findById(any(Integer.class))).thenReturn(Optional.empty()); NoSuchElementException exception = assertThrows(NoSuchElementException.class, - () -> storeService.deleteStore(NON_EXISTENT_ID)); + () -> storeService.deleteStore(1)); - assertEquals("상점 ID: " + NON_EXISTENT_ID + "가 존재하지 않습니다.", exception.getMessage()); + assertEquals("상점 ID: " + 1 + "가 존재하지 않습니다.", exception.getMessage()); } @Test @@ -241,11 +239,11 @@ void getStoreById_success() { @Test @DisplayName("getStoreById() - 실패 storeId가 존재하지 않는 경우") void getStoreById_fail_storeNotFound() { - when(storeRepository.findById(NON_EXISTENT_ID)).thenReturn(Optional.empty()); + when(storeRepository.findById(1)).thenReturn(Optional.empty()); NoSuchElementException exception = assertThrows(NoSuchElementException.class, - () -> storeService.getStoreById(NON_EXISTENT_ID)); + () -> storeService.getStoreById(1)); - assertEquals("상점 ID: " + NON_EXISTENT_ID + "가 존재하지 않습니다.", exception.getMessage()); + assertEquals("상점 ID: " + 1 + "가 존재하지 않습니다.", exception.getMessage()); } }