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..39217a4 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,39 @@ 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 = "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) { + + log.info("디바이스 ID 중복 확인 요청 - DeviceId: {}", 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..28e3340 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())) { + 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 70bbe09..81613fb 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면 false를 반환한다.") + 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("디바이스 ID 중복 확인 서비스 로직을 호출합니다."); + PetResponse.PetDeviceCheckResponse response = petService.checkDeviceIdAvailability(request); + + // then + log.info("사용 가능 여부가 false이고 적절한 메시지가 반환되었는지 검증합니다."); + assertNotNull(response); + assertFalse(response.isAvailable()); + assertEquals("이미 다른 반려동물에 등록된 디바이스 ID입니다.", response.getMessage()); + log.info("디바이스 ID 중복 확인 실패 테스트가 통과되었습니다."); + } + /** * 펫 특이사항 생성 성공 시나리오를 테스트합니다. */