From 025169079f02af33e8dfc82329e5bb91b4eb79ee Mon Sep 17 00:00:00 2001 From: Baek HyeonBin <81628455+WhiteBin-bin@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:28:43 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20Feat:=20=ED=8E=AB=20=EB=94=94?= =?UTF-8?q?=EB=B0=94=EC=9D=B4=EC=8A=A4=20ID=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 펫 등록 전 디바이스 ID 중복 여부를 확인하는 API 추가 - 펫 등록 시점에도 디바이스 ID 중복 검증 적용 - 디바이스 ID 중복 확인 요청/응답 DTO 및 서비스 로직 추가 - 디바이스 ID 중복 확인 서비스 테스트 추가 Closes #151 --- .../backend/pet/controller/PetController.java | 39 +++++++++ .../backend/pet/dto/request/PetRequest.java | 14 +++ .../backend/pet/dto/response/PetResponse.java | 23 +++++ .../dodo/backend/pet/service/PetService.java | 8 ++ .../backend/pet/service/PetServiceImpl.java | 21 +++++ .../backend/pet/service/PetServiceTest.java | 87 +++++++++++++++++++ 6 files changed, 192 insertions(+) diff --git a/src/main/java/com/dodo/backend/pet/controller/PetController.java b/src/main/java/com/dodo/backend/pet/controller/PetController.java index d9fe882..30ed9a5 100644 --- a/src/main/java/com/dodo/backend/pet/controller/PetController.java +++ b/src/main/java/com/dodo/backend/pet/controller/PetController.java @@ -514,6 +514,45 @@ public ResponseEntity updateDevice( return ResponseEntity.ok(response); } + /** + * 디바이스 ID 중복 여부를 확인합니다. + * + * @param request 확인할 디바이스 ID + * @param userDetails 인증된 사용자 정보 + * @return 디바이스 ID 사용 가능 여부 응답 + */ + @Operation(summary = "디바이스 ID 중복 확인", description = "펫 등록 전에 디바이스 ID가 이미 다른 반려동물에 등록되어 있는지 확인합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "사용 가능한 디바이스 ID입니다.", + content = @Content(schema = @Schema(implementation = PetDeviceCheckResponse.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청입니다.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "400 Bad Request", value = "{\"status\": 400, \"message\": \"잘못된 요청입니다.\"}"))), + @ApiResponse(responseCode = "401", description = "로그인이 필요한 기능입니다.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "401 Unauthorized", value = "{\"status\": 401, \"message\": \"로그인이 필요한 기능입니다.\"}"))), + @ApiResponse(responseCode = "409", description = "이미 다른 반려동물에 등록된 디바이스 ID입니다.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "409 Conflict", value = "{\"status\": 409, \"message\": \"이미 다른 반려동물에 등록된 디바이스 ID입니다.\"}"))), + @ApiResponse(responseCode = "500", description = "서버 내부 오류가 발생했습니다.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "500 Internal Server Error", value = "{\"status\": 500, \"message\": \"서버 내부 오류가 발생했습니다.\"}"))) + }) + @PostMapping("/device/check") + public ResponseEntity checkDeviceId( + @Valid @RequestBody PetDeviceCheckRequest request, + @AuthenticationPrincipal UserDetails userDetails) { + + UUID userId = UUID.fromString(userDetails.getUsername()); + log.info("디바이스 ID 중복 확인 요청 - User: {}, DeviceId: {}", userId, request.getDeviceId()); + + return ResponseEntity.ok(petService.checkDeviceIdAvailability(request)); + } + /** * 반려동물 상세 정보를 조회합니다. * diff --git a/src/main/java/com/dodo/backend/pet/dto/request/PetRequest.java b/src/main/java/com/dodo/backend/pet/dto/request/PetRequest.java index a4c2c98..ba6d1d2 100644 --- a/src/main/java/com/dodo/backend/pet/dto/request/PetRequest.java +++ b/src/main/java/com/dodo/backend/pet/dto/request/PetRequest.java @@ -179,6 +179,20 @@ public static class PetDeviceUpdateRequest { private String deviceId; } + /** + * 디바이스 ID 중복 확인 요청 DTO입니다. + */ + @Getter + @NoArgsConstructor + @AllArgsConstructor + @Builder + @Schema(description = "디바이스 ID 중복 확인 요청") + public static class PetDeviceCheckRequest { + @NotBlank(message = "디바이스 ID는 필수입니다.") + @Schema(description = "확인할 디바이스 ID", example = "ABC123XYZ") + private String deviceId; + } + /** * 반려동물 특이사항 생성을 위한 요청 DTO입니다. */ diff --git a/src/main/java/com/dodo/backend/pet/dto/response/PetResponse.java b/src/main/java/com/dodo/backend/pet/dto/response/PetResponse.java index b64276a..9e2625e 100644 --- a/src/main/java/com/dodo/backend/pet/dto/response/PetResponse.java +++ b/src/main/java/com/dodo/backend/pet/dto/response/PetResponse.java @@ -504,6 +504,29 @@ public static PetDeviceUpdateResponse toDto(Long petId, String petName, String o } } + /** + * 디바이스 ID 중복 확인 응답 DTO입니다. + */ + @Getter + @Builder + @AllArgsConstructor + @Schema(description = "디바이스 ID 중복 확인 결과 응답") + public static class PetDeviceCheckResponse { + + @Schema(description = "처리 결과 메시지", example = "사용 가능한 디바이스 ID입니다.") + private String message; + + @Schema(description = "디바이스 ID 사용 가능 여부", example = "true") + private boolean available; + + public static PetDeviceCheckResponse toDto(String message, boolean available) { + return PetDeviceCheckResponse.builder() + .message(message) + .available(available) + .build(); + } + } + /** * 반려동물 특이사항 생성 결과 응답 DTO입니다. */ diff --git a/src/main/java/com/dodo/backend/pet/service/PetService.java b/src/main/java/com/dodo/backend/pet/service/PetService.java index c771318..7e2e9a2 100644 --- a/src/main/java/com/dodo/backend/pet/service/PetService.java +++ b/src/main/java/com/dodo/backend/pet/service/PetService.java @@ -114,6 +114,14 @@ public interface PetService { */ PetDeviceUpdateResponse updateDevice(UUID userId, Long petId, PetDeviceUpdateRequest request); + /** + * 디바이스 ID 중복 여부를 확인합니다. + * + * @param request 확인할 디바이스 ID가 포함된 요청 DTO + * @return 디바이스 ID 사용 가능 여부 응답 + */ + PetDeviceCheckResponse checkDeviceIdAvailability(PetDeviceCheckRequest request); + /** * 반려동물 특이사항을 생성합니다. * diff --git a/src/main/java/com/dodo/backend/pet/service/PetServiceImpl.java b/src/main/java/com/dodo/backend/pet/service/PetServiceImpl.java index bac893e..6a73099 100644 --- a/src/main/java/com/dodo/backend/pet/service/PetServiceImpl.java +++ b/src/main/java/com/dodo/backend/pet/service/PetServiceImpl.java @@ -3,6 +3,7 @@ import com.dodo.backend.activityhistory.entity.ActivityHistory; import com.dodo.backend.activityhistory.repository.ActivityHistoryRepository; import com.dodo.backend.imagefile.service.ImageFileService; +import com.dodo.backend.pet.dto.request.PetRequest.PetDeviceCheckRequest; import com.dodo.backend.pet.dto.request.PetRequest.PetDeviceUpdateRequest; import com.dodo.backend.pet.dto.request.PetRequest.PetFamilyJoinRequest; import com.dodo.backend.pet.dto.request.PetRequest.PetRegisterRequest; @@ -87,6 +88,10 @@ public PetRegisterResponse registerPet(UUID userId, PetRegisterRequest request) } } + if (petRepository.existsByDeviceId(request.getDeviceId())) { + throw new PetException(DEVICE_ID_DUPLICATED); + } + Pet pet = request.toEntity(); Pet savedPet = petRepository.save(pet); @@ -519,6 +524,22 @@ public PetDeviceUpdateResponse updateDevice(UUID userId, Long petId, PetDeviceUp ); } + /** + * 디바이스 ID 중복 여부를 확인합니다. + * + * @param request 확인할 디바이스 ID가 포함된 요청 DTO + * @return 디바이스 ID 사용 가능 여부 응답 + */ + @Transactional(readOnly = true) + @Override + public PetDeviceCheckResponse checkDeviceIdAvailability(PetDeviceCheckRequest request) { + if (petRepository.existsByDeviceId(request.getDeviceId())) { + throw new PetException(DEVICE_ID_DUPLICATED); + } + + return PetDeviceCheckResponse.toDto("사용 가능한 디바이스 ID입니다.", true); + } + /** * 반려동물 특이사항을 생성합니다. *

diff --git a/src/test/java/com/dodo/backend/pet/service/PetServiceTest.java b/src/test/java/com/dodo/backend/pet/service/PetServiceTest.java index 70bbe09..a2585c7 100644 --- a/src/test/java/com/dodo/backend/pet/service/PetServiceTest.java +++ b/src/test/java/com/dodo/backend/pet/service/PetServiceTest.java @@ -173,6 +173,37 @@ void registerPet_Fail_DuplicateRegistrationNumber() { log.info("등록번호 중복 실패 테스트가 통과되었습니다."); } + /** + * 이미 등록된 디바이스 ID로 펫 등록을 시도할 때 예외 발생을 테스트합니다. + */ + @Test + @DisplayName("펫 등록 실패: 이미 다른 펫이 사용 중인 디바이스 ID면 예외가 발생한다.") + void registerPet_Fail_DuplicateDeviceId() { + log.info("디바이스 ID 중복으로 인한 펫 등록 실패 테스트를 시작합니다."); + // given + UUID userId = UUID.randomUUID(); + String duplicateDeviceId = "DUPLICATE_DEV"; + PetRequest.PetRegisterRequest request = PetRequest.PetRegisterRequest.builder() + .deviceId(duplicateDeviceId) + .build(); + + log.info("사용자는 존재하고 디바이스 ID가 이미 등록된 상황을 설정합니다."); + given(userPetService.existsUser(userId)).willReturn(true); + given(petRepository.existsByDeviceId(duplicateDeviceId)).willReturn(true); + + // when + log.info("중복된 디바이스 ID로 펫 등록 요청 시 예외가 발생하는지 확인합니다."); + PetException exception = assertThrows(PetException.class, () -> + petService.registerPet(userId, request) + ); + + // then + log.info("발생한 예외 코드가 DEVICE_ID_DUPLICATED인지 검증합니다."); + assertEquals(PetErrorCode.DEVICE_ID_DUPLICATED, exception.getErrorCode()); + verify(petRepository, times(0)).save(any(Pet.class)); + log.info("디바이스 ID 중복 펫 등록 실패 테스트가 통과되었습니다."); + } + /** * 반려동물 정보 수정 성공 시나리오를 테스트합니다. */ @@ -569,6 +600,62 @@ void updateDevice_Fail_DuplicateDeviceId() { log.info("ID 중복 재등록 실패 테스트가 통과되었습니다."); } + /** + * 디바이스 ID 중복 확인 성공 시나리오를 테스트합니다. + */ + @Test + @DisplayName("디바이스 ID 중복 확인 성공: 사용 가능한 ID면 true를 반환한다.") + void checkDeviceIdAvailability_Success() { + log.info("디바이스 ID 중복 확인 성공 테스트를 시작합니다."); + // given + String deviceId = "AVAILABLE_DEV"; + PetRequest.PetDeviceCheckRequest request = PetRequest.PetDeviceCheckRequest.builder() + .deviceId(deviceId) + .build(); + + log.info("요청한 디바이스 ID가 존재하지 않는 상황을 설정합니다."); + given(petRepository.existsByDeviceId(deviceId)).willReturn(false); + + // when + log.info("디바이스 ID 중복 확인 서비스 로직을 호출합니다."); + PetResponse.PetDeviceCheckResponse response = petService.checkDeviceIdAvailability(request); + + // then + log.info("사용 가능 여부와 메시지를 검증합니다."); + assertNotNull(response); + assertTrue(response.isAvailable()); + assertEquals("사용 가능한 디바이스 ID입니다.", response.getMessage()); + log.info("디바이스 ID 중복 확인 성공 테스트가 통과되었습니다."); + } + + /** + * 디바이스 ID 중복 확인 실패 시나리오를 테스트합니다. + */ + @Test + @DisplayName("디바이스 ID 중복 확인 실패: 이미 사용 중인 ID면 예외가 발생한다.") + void checkDeviceIdAvailability_Fail_DuplicateDeviceId() { + log.info("디바이스 ID 중복 확인 실패 테스트를 시작합니다."); + // given + String duplicateDeviceId = "DUPLICATE_DEV"; + PetRequest.PetDeviceCheckRequest request = PetRequest.PetDeviceCheckRequest.builder() + .deviceId(duplicateDeviceId) + .build(); + + log.info("요청한 디바이스 ID가 이미 존재한다고 설정합니다."); + given(petRepository.existsByDeviceId(duplicateDeviceId)).willReturn(true); + + // when + log.info("중복 확인 요청 시 예외가 발생하는지 확인합니다."); + PetException exception = assertThrows(PetException.class, () -> + petService.checkDeviceIdAvailability(request) + ); + + // then + log.info("발생한 예외 코드가 DEVICE_ID_DUPLICATED인지 검증합니다."); + assertEquals(PetErrorCode.DEVICE_ID_DUPLICATED, exception.getErrorCode()); + log.info("디바이스 ID 중복 확인 실패 테스트가 통과되었습니다."); + } + /** * 펫 특이사항 생성 성공 시나리오를 테스트합니다. */ From 9529d7050f044a50ba3b2ca593fe0fc6596a057b Mon Sep 17 00:00:00 2001 From: Baek HyeonBin <81628455+WhiteBin-bin@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:37:04 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=94=A8=20Refactor:=20=EB=94=94?= =?UTF-8?q?=EB=B0=94=EC=9D=B4=EC=8A=A4=20ID=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20API=20=EC=9D=B8=EC=A6=9D=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 디바이스 ID 중복 확인 컨트롤러에서 불필요한 UserDetails 파라미터 제거 - 비즈니스 로직에 사용하지 않는 userId 파싱 로직 제거 - 디바이스 ID 기준 로그만 남기도록 정리 Closes #151 --- .../java/com/dodo/backend/pet/controller/PetController.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/dodo/backend/pet/controller/PetController.java b/src/main/java/com/dodo/backend/pet/controller/PetController.java index 30ed9a5..3720451 100644 --- a/src/main/java/com/dodo/backend/pet/controller/PetController.java +++ b/src/main/java/com/dodo/backend/pet/controller/PetController.java @@ -544,11 +544,9 @@ public ResponseEntity updateDevice( }) @PostMapping("/device/check") public ResponseEntity checkDeviceId( - @Valid @RequestBody PetDeviceCheckRequest request, - @AuthenticationPrincipal UserDetails userDetails) { + @Valid @RequestBody PetDeviceCheckRequest request) { - UUID userId = UUID.fromString(userDetails.getUsername()); - log.info("디바이스 ID 중복 확인 요청 - User: {}, DeviceId: {}", userId, request.getDeviceId()); + log.info("디바이스 ID 중복 확인 요청 - DeviceId: {}", request.getDeviceId()); return ResponseEntity.ok(petService.checkDeviceIdAvailability(request)); } From a29055977480e24a9a49ffefc43d624af107756b Mon Sep 17 00:00:00 2001 From: Baek HyeonBin <81628455+WhiteBin-bin@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:43:46 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=94=A8=20Refactor:=20=EB=94=94?= =?UTF-8?q?=EB=B0=94=EC=9D=B4=EC=8A=A4=20ID=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EC=9D=91=EB=8B=B5=20=EB=B0=A9=EC=8B=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 디바이스 ID 중복 확인 시 예외 대신 available=false 응답 반환 - 중복 확인 API Swagger 문서에서 409 응답 제거 - 중복 확인 실패 테스트를 응답 DTO 검증 방식으로 수정 Closes #151 --- .../dodo/backend/pet/controller/PetController.java | 6 +----- .../dodo/backend/pet/service/PetServiceImpl.java | 2 +- .../dodo/backend/pet/service/PetServiceTest.java | 14 +++++++------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/dodo/backend/pet/controller/PetController.java b/src/main/java/com/dodo/backend/pet/controller/PetController.java index 3720451..39217a4 100644 --- a/src/main/java/com/dodo/backend/pet/controller/PetController.java +++ b/src/main/java/com/dodo/backend/pet/controller/PetController.java @@ -523,7 +523,7 @@ public ResponseEntity updateDevice( */ @Operation(summary = "디바이스 ID 중복 확인", description = "펫 등록 전에 디바이스 ID가 이미 다른 반려동물에 등록되어 있는지 확인합니다.") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "사용 가능한 디바이스 ID입니다.", + @ApiResponse(responseCode = "200", description = "디바이스 ID 중복 확인 결과", content = @Content(schema = @Schema(implementation = PetDeviceCheckResponse.class))), @ApiResponse(responseCode = "400", description = "잘못된 요청입니다.", content = @Content(mediaType = "application/json", @@ -533,10 +533,6 @@ public ResponseEntity updateDevice( content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "401 Unauthorized", value = "{\"status\": 401, \"message\": \"로그인이 필요한 기능입니다.\"}"))), - @ApiResponse(responseCode = "409", description = "이미 다른 반려동물에 등록된 디바이스 ID입니다.", - content = @Content(mediaType = "application/json", - schema = @Schema(implementation = ErrorResponse.class), - examples = @ExampleObject(name = "409 Conflict", value = "{\"status\": 409, \"message\": \"이미 다른 반려동물에 등록된 디바이스 ID입니다.\"}"))), @ApiResponse(responseCode = "500", description = "서버 내부 오류가 발생했습니다.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), diff --git a/src/main/java/com/dodo/backend/pet/service/PetServiceImpl.java b/src/main/java/com/dodo/backend/pet/service/PetServiceImpl.java index 6a73099..28e3340 100644 --- a/src/main/java/com/dodo/backend/pet/service/PetServiceImpl.java +++ b/src/main/java/com/dodo/backend/pet/service/PetServiceImpl.java @@ -534,7 +534,7 @@ public PetDeviceUpdateResponse updateDevice(UUID userId, Long petId, PetDeviceUp @Override public PetDeviceCheckResponse checkDeviceIdAvailability(PetDeviceCheckRequest request) { if (petRepository.existsByDeviceId(request.getDeviceId())) { - throw new PetException(DEVICE_ID_DUPLICATED); + return PetDeviceCheckResponse.toDto("이미 다른 반려동물에 등록된 디바이스 ID입니다.", false); } return PetDeviceCheckResponse.toDto("사용 가능한 디바이스 ID입니다.", true); diff --git a/src/test/java/com/dodo/backend/pet/service/PetServiceTest.java b/src/test/java/com/dodo/backend/pet/service/PetServiceTest.java index a2585c7..81613fb 100644 --- a/src/test/java/com/dodo/backend/pet/service/PetServiceTest.java +++ b/src/test/java/com/dodo/backend/pet/service/PetServiceTest.java @@ -632,7 +632,7 @@ void checkDeviceIdAvailability_Success() { * 디바이스 ID 중복 확인 실패 시나리오를 테스트합니다. */ @Test - @DisplayName("디바이스 ID 중복 확인 실패: 이미 사용 중인 ID면 예외가 발생한다.") + @DisplayName("디바이스 ID 중복 확인 실패: 이미 사용 중인 ID면 false를 반환한다.") void checkDeviceIdAvailability_Fail_DuplicateDeviceId() { log.info("디바이스 ID 중복 확인 실패 테스트를 시작합니다."); // given @@ -645,14 +645,14 @@ void checkDeviceIdAvailability_Fail_DuplicateDeviceId() { given(petRepository.existsByDeviceId(duplicateDeviceId)).willReturn(true); // when - log.info("중복 확인 요청 시 예외가 발생하는지 확인합니다."); - PetException exception = assertThrows(PetException.class, () -> - petService.checkDeviceIdAvailability(request) - ); + log.info("디바이스 ID 중복 확인 서비스 로직을 호출합니다."); + PetResponse.PetDeviceCheckResponse response = petService.checkDeviceIdAvailability(request); // then - log.info("발생한 예외 코드가 DEVICE_ID_DUPLICATED인지 검증합니다."); - assertEquals(PetErrorCode.DEVICE_ID_DUPLICATED, exception.getErrorCode()); + log.info("사용 가능 여부가 false이고 적절한 메시지가 반환되었는지 검증합니다."); + assertNotNull(response); + assertFalse(response.isAvailable()); + assertEquals("이미 다른 반려동물에 등록된 디바이스 ID입니다.", response.getMessage()); log.info("디바이스 ID 중복 확인 실패 테스트가 통과되었습니다."); }