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
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package com.recyclestudy.member.controller;

import com.recyclestudy.member.controller.request.DeviceDeleteRequest;
import com.recyclestudy.member.domain.DeviceIdentifier;
import com.recyclestudy.member.domain.Email;
import com.recyclestudy.member.service.MemberService;
import com.recyclestudy.member.service.input.DeviceDeleteInput;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/api/v1/device")
Expand All @@ -24,4 +30,13 @@ public String authenticateDevice(
memberService.authenticateDevice(Email.from(email), DeviceIdentifier.from(deviceIdentifier));
return "auth_success";
}

@DeleteMapping
@ResponseBody
public ResponseEntity<Void> deleteDevice(@RequestBody final DeviceDeleteRequest request) {
final DeviceDeleteInput input = DeviceDeleteInput.from(request.email(), request.deviceIdentifier(),
request.targetDeviceIdentifier());
memberService.deleteDevice(input);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.recyclestudy.member.controller.request;

public record DeviceDeleteRequest(String email, String deviceIdentifier, String targetDeviceIdentifier) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ public interface DeviceRepository extends JpaRepository<Device, Long> {
List<Device> findAllByMemberEmail(@Param("email") Email email);

Optional<Device> findByIdentifier(DeviceIdentifier deviceIdentifier);

void deleteByIdentifier(DeviceIdentifier identifier);
}
10 changes: 10 additions & 0 deletions src/main/java/com/recyclestudy/member/service/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.recyclestudy.member.domain.Member;
import com.recyclestudy.member.repository.DeviceRepository;
import com.recyclestudy.member.repository.MemberRepository;
import com.recyclestudy.member.service.input.DeviceDeleteInput;
import com.recyclestudy.member.service.input.MemberFindInput;
import com.recyclestudy.member.service.input.MemberSaveInput;
import com.recyclestudy.member.service.output.MemberFindOutput;
Expand Down Expand Up @@ -69,6 +70,15 @@ public void authenticateDevice(final Email email, final DeviceIdentifier deviceI
device.activate(LocalDateTime.now(clock));
}

@Transactional
public void deleteDevice(final DeviceDeleteInput input) {
final Device device = deviceRepository.findByIdentifier(input.deviceIdentifier())
.orElseThrow(() -> new UnauthorizedException("유효하지 않은 디바이스 아이디입니다: %s"
.formatted(input.deviceIdentifier().getValue())));
device.verifyOwner(input.email());
deviceRepository.deleteByIdentifier(input.targetDeviceIdentifier());
}

private Member saveNewMember(final Email email) {
final Optional<Member> memberOptional = memberRepository.findByEmail(email);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.recyclestudy.member.service.input;

import com.recyclestudy.member.domain.DeviceIdentifier;
import com.recyclestudy.member.domain.Email;

public record DeviceDeleteInput(Email email, DeviceIdentifier deviceIdentifier,
DeviceIdentifier targetDeviceIdentifier) {

public static DeviceDeleteInput from(final String emailValue, final String identifier,
final String targetIdentifier) {
final Email email = Email.from(emailValue);
final DeviceIdentifier deviceIdentifier = DeviceIdentifier.from(identifier);
final DeviceIdentifier targetDeviceIdentifier = DeviceIdentifier.from(targetIdentifier);
return new DeviceDeleteInput(email, deviceIdentifier, targetDeviceIdentifier);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.recyclestudy.exception.BadRequestException;
import com.recyclestudy.exception.DeviceActivationExpiredException;
import com.recyclestudy.exception.UnauthorizedException;
import com.recyclestudy.member.controller.request.DeviceDeleteRequest;
import com.recyclestudy.member.domain.DeviceIdentifier;
import com.recyclestudy.member.domain.Email;
import com.recyclestudy.member.service.MemberService;
Expand All @@ -12,11 +14,13 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.context.bean.override.mockito.MockitoBean;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
Expand Down Expand Up @@ -92,4 +96,62 @@ void authenticateDevice_Expired() {
.statusCode(HttpStatus.BAD_REQUEST.value())
.body("message", equalTo("인증 유효 시간이 만료되었습니다."));
}

@Test
@DisplayName("디바이스 삭제 시 204 응답을 반환한다")
void deleteDevice() {
// given
final DeviceDeleteRequest request = new DeviceDeleteRequest("test@test.com", "device-id", "target-id");

doNothing().when(memberService).deleteDevice(any());

// when & then
given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(request)
.when()
.delete("/api/v1/device")
.then()
.statusCode(HttpStatus.NO_CONTENT.value());
}

@Test
@DisplayName("유효하지 않은 디바이스 아이디로 삭제 시 401 응답을 반환한다")
void deleteDevice_InvalidIdentifier() {
// given
final DeviceDeleteRequest request = new DeviceDeleteRequest("test@test.com", "not-existed", "target-id");

doThrow(new UnauthorizedException("유효하지 않은 디바이스 아이디입니다"))
.when(memberService).deleteDevice(any());

// when & then
given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(request)
.when()
.delete("/api/v1/device")
.then()
.statusCode(HttpStatus.UNAUTHORIZED.value())
.body("message", equalTo("유효하지 않은 디바이스 아이디입니다"));
}

@Test
@DisplayName("소유자가 아닌 디바이스 삭제 시 400 응답을 반환한다")
void deleteDevice_NotOwner() {
// given
final DeviceDeleteRequest request = new DeviceDeleteRequest("other@test.com", "device-id", "target-id");

doThrow(new BadRequestException("디바이스 소유자가 아닙니다."))
.when(memberService).deleteDevice(any());

// when & then
given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(request)
.when()
.delete("/api/v1/device")
.then()
.statusCode(HttpStatus.BAD_REQUEST.value())
.body("message", equalTo("디바이스 소유자가 아닙니다."));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.recyclestudy.member.domain.Member;
import com.recyclestudy.member.repository.DeviceRepository;
import com.recyclestudy.member.repository.MemberRepository;
import com.recyclestudy.member.service.input.DeviceDeleteInput;
import com.recyclestudy.member.service.input.MemberFindInput;
import com.recyclestudy.member.service.input.MemberSaveInput;
import com.recyclestudy.member.service.output.MemberFindOutput;
Expand Down Expand Up @@ -241,4 +242,61 @@ void authenticateDevice_fail_owner() {
.isInstanceOf(BadRequestException.class)
.hasMessage("디바이스 소유자가 아닙니다.");
}

@Test
@DisplayName("디바이스를 삭제할 수 있다")
void deleteDevice() {
// given
final Email email = Email.from("test@test.com");
final DeviceIdentifier deviceIdentifier = DeviceIdentifier.from("test");
final DeviceIdentifier targetDeviceIdentifier = DeviceIdentifier.from("target");
final DeviceDeleteInput input = DeviceDeleteInput.from(
email.getValue(), deviceIdentifier.getValue(), targetDeviceIdentifier.getValue());
final Member member = Member.withoutId(email);
final Device device = Device.withoutId(member, deviceIdentifier, true, ActivationExpiredDateTime.create(now));

given(deviceRepository.findByIdentifier(deviceIdentifier)).willReturn(Optional.of(device));

// when
memberService.deleteDevice(input);

// then
verify(deviceRepository).deleteByIdentifier(targetDeviceIdentifier);
}

@Test
@DisplayName("유효하지 않은 디바이스 아이디로 삭제 시도 시 예외를 던진다")
void deleteDevice_fail_invalidIdentifier() {
// given
final Email email = Email.from("test@test.com");
final DeviceIdentifier deviceIdentifier = DeviceIdentifier.from("not-existed");
final DeviceDeleteInput input = DeviceDeleteInput.from(
email.getValue(), deviceIdentifier.getValue(), "target");

given(deviceRepository.findByIdentifier(deviceIdentifier)).willReturn(Optional.empty());

// when & then
assertThatThrownBy(() -> memberService.deleteDevice(input))
.isInstanceOf(UnauthorizedException.class);
}

@Test
@DisplayName("소유자가 아닌 디바이스로 삭제 시도 시 예외를 던진다")
void deleteDevice_fail_owner() {
// given
final Email email = Email.from("test@test.com");
final Email otherEmail = Email.from("other@test.com");
final DeviceIdentifier deviceIdentifier = DeviceIdentifier.from("test");
final DeviceDeleteInput input = DeviceDeleteInput.from(
otherEmail.getValue(), deviceIdentifier.getValue(), "target");
final Member member = Member.withoutId(email);
final Device device = Device.withoutId(member, deviceIdentifier, true, ActivationExpiredDateTime.create(now));

given(deviceRepository.findByIdentifier(deviceIdentifier)).willReturn(Optional.of(device));

// when & then
assertThatThrownBy(() -> memberService.deleteDevice(input))
.isInstanceOf(BadRequestException.class)
.hasMessage("디바이스 소유자가 아닙니다.");
}
}