Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/main/java/com/dodo/backend/pet/controller/PetController.java
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,39 @@ public ResponseEntity<PetDeviceUpdateResponse> 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\": \"서버 내부 오류가 발생했습니다.\"}")))
})
Comment on lines +524 to +540
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

서비스 레이어(PetServiceImpl)에서 중복된 디바이스 ID에 대해 예외를 던지는 대신 available: false를 포함한 200 OK 응답을 반환하도록 변경할 경우, 컨트롤러의 Swagger 문서에서도 409 Conflict 응답 정의를 제거하고 200 OK 응답 설명을 그에 맞게 수정해야 합니다.

    @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<PetDeviceCheckResponse> checkDeviceId(
@Valid @RequestBody PetDeviceCheckRequest request) {

log.info("디바이스 ID 중복 확인 요청 - DeviceId: {}", request.getDeviceId());

return ResponseEntity.ok(petService.checkDeviceIdAvailability(request));
}
Comment on lines +542 to +548
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

현재 checkDeviceId 메서드에서 @AuthenticationPrincipal UserDetails userDetails를 받아 userId를 파싱하고 로그를 남기고 있지만, 실제 비즈니스 로직인 petService.checkDeviceIdAvailability(request)에는 userId가 전혀 사용되지 않고 있습니다.

만약 이 API가 로그인 여부와 상관없이 디바이스 ID의 중복 여부만 확인하는 퍼블릭 API라면, userDetails 매개변수를 제거하여 비인증 사용자도 접근할 수 있도록 개선할 수 있습니다. 반대로 반드시 인증된 사용자만 호출할 수 있어야 하는 보안 요구사항이 있다면 현재 구조를 유지하되, 단순히 로그 기록용으로만 사용되는 userId 파싱 과정의 필요성을 재검토해 보시는 것을 권장합니다.


/**
* 반려동물 상세 정보를 조회합니다.
*
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/dodo/backend/pet/dto/request/PetRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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입니다.
*/
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/com/dodo/backend/pet/dto/response/PetResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -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입니다.
*/
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/com/dodo/backend/pet/service/PetService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/**
* 반려동물 특이사항을 생성합니다.
*
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/com/dodo/backend/pet/service/PetServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
}
Comment on lines +535 to +541
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

현재 checkDeviceIdAvailability 메서드는 디바이스 ID가 중복될 경우 PetException(DEVICE_ID_DUPLICATED) 예외를 던지도록 구현되어 있습니다. 하지만 응답 DTO인 PetDeviceCheckResponse에는 available이라는 boolean 필드가 존재합니다. 예외를 던지게 되면 클라이언트는 항상 availabletrue인 응답만 받거나 에러 응답(409 Conflict)을 받게 되므로, available 필드가 무색해집니다.

중복 확인(Check) API는 예외를 던지기보다 200 OK 응답과 함께 available: false를 반환하는 것이 일반적인 REST API 설계에 부합하며, 클라이언트 측에서도 불필요한 에러 핸들링 로직을 줄일 수 있습니다. 중복 여부에 따라 적절한 메시지와 available 값을 반환하도록 변경하는 것을 권장합니다.

Suggested change
public PetDeviceCheckResponse checkDeviceIdAvailability(PetDeviceCheckRequest request) {
if (petRepository.existsByDeviceId(request.getDeviceId())) {
throw new PetException(DEVICE_ID_DUPLICATED);
}
return PetDeviceCheckResponse.toDto("사용 가능한 디바이스 ID입니다.", true);
}
public PetDeviceCheckResponse checkDeviceIdAvailability(PetDeviceCheckRequest request) {
if (petRepository.existsByDeviceId(request.getDeviceId())) {
return PetDeviceCheckResponse.toDto("이미 다른 반려동물에 등록된 디바이스 ID입니다.", false);
}
return PetDeviceCheckResponse.toDto("사용 가능한 디바이스 ID입니다.", true);
}


/**
* 반려동물 특이사항을 생성합니다.
* <p>
Expand Down
87 changes: 87 additions & 0 deletions src/test/java/com/dodo/backend/pet/service/PetServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 중복 펫 등록 실패 테스트가 통과되었습니다.");
}

/**
* 반려동물 정보 수정 성공 시나리오를 테스트합니다.
*/
Expand Down Expand Up @@ -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 중복 확인 실패 테스트가 통과되었습니다.");
}
Comment on lines +634 to +657
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

PetServiceImpl에서 checkDeviceIdAvailability 메서드가 예외를 던지는 대신 available: false를 반환하도록 변경됨에 따라, 관련 테스트 코드도 예외 발생 여부 대신 반환된 DTO의 available 필드 값을 검증하도록 수정해야 합니다.

    @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 중복 확인 실패 테스트가 통과되었습니다.");
    }


/**
* 펫 특이사항 생성 성공 시나리오를 테스트합니다.
*/
Expand Down
Loading