diff --git a/week3/seminar3/build.gradle b/week3/seminar3/build.gradle index 4df15fd..d2c349d 100644 --- a/week3/seminar3/build.gradle +++ b/week3/seminar3/build.gradle @@ -25,6 +25,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation group: 'org.postgresql', name: 'postgresql', version: '42.7.3' + implementation 'org.springframework.boot:spring-boot-starter-validation' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/common/GlobalExceptionHandler.java b/week3/seminar3/src/main/java/org/sopt/seminar3/common/GlobalExceptionHandler.java new file mode 100644 index 0000000..0807bff --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/common/GlobalExceptionHandler.java @@ -0,0 +1,28 @@ +package org.sopt.seminar3.common; + +import org.sopt.seminar3.common.dto.ErrorResponse; +import org.sopt.seminar3.exception.NotFoundException; +import org.sopt.seminar3.exception.message.ErrorMessage; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.Objects; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(NotFoundException.class) + protected ResponseEntity handleEntityNotFoundException(NotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ErrorResponse.of(ErrorMessage.MEMBER_NOT_FOUND_BY_ID_EXCEPTION)); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + protected ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ErrorResponse.of(HttpStatus.BAD_REQUEST.value(), Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage())); + } +} diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/common/dto/ErrorResponse.java b/week3/seminar3/src/main/java/org/sopt/seminar3/common/dto/ErrorResponse.java new file mode 100644 index 0000000..5b9160d --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/common/dto/ErrorResponse.java @@ -0,0 +1,16 @@ +package org.sopt.seminar3.common.dto; + +import org.sopt.seminar3.exception.message.ErrorMessage; + +public record ErrorResponse( + int status, + String message +) { + public static ErrorResponse of(int status, String message) { + return new ErrorResponse(status, message); + } + + public static ErrorResponse of(ErrorMessage errorMessage) { + return new ErrorResponse(errorMessage.getStatus(), errorMessage.getMessage()); + } +} diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/config/JpaAuditingConfig.java b/week3/seminar3/src/main/java/org/sopt/seminar3/config/JpaAuditingConfig.java new file mode 100644 index 0000000..993374b --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/config/JpaAuditingConfig.java @@ -0,0 +1,9 @@ +package org.sopt.seminar3.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@Configuration // 모든 어플리케이션 환경에서 적용되는 객체 +@EnableJpaAuditing // JPA가 엔티티를 감시할 수 있다 +public class JpaAuditingConfig { +} diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/controller/BlogController.java b/week3/seminar3/src/main/java/org/sopt/seminar3/controller/BlogController.java new file mode 100644 index 0000000..7c581d7 --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/controller/BlogController.java @@ -0,0 +1,41 @@ +package org.sopt.seminar3.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.sopt.seminar3.domain.SuccessMessage; +import org.sopt.seminar3.dto.BlogCreateRequest; +import org.sopt.seminar3.dto.BlogTitleUpdateRequest; +import org.sopt.seminar3.dto.SuccessStatusResponse; +import org.sopt.seminar3.service.BlogService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + + +@RestController +@RequestMapping("/api/v1") +@RequiredArgsConstructor +public class BlogController { + + private final BlogService blogService; + + @PostMapping("/blog") + public ResponseEntity createBlog( + @RequestHeader(name = "memberId") Long memberId, // 멤버 식별자는 중요한 정보이므로 헤더를 통해 받아옴 + @RequestBody BlogCreateRequest blogCreateRequest + ){ + return ResponseEntity.status(HttpStatus.CREATED).header( + "Location", + blogService.create(memberId, blogCreateRequest)) + .body(SuccessStatusResponse.of(SuccessMessage.BLOG_CREATE_SUCCESS)); + } + + @PatchMapping("/blog/{blogId}/title") + public ResponseEntity updateBlogTitle( + @PathVariable Long blogId, + @Valid @RequestBody BlogTitleUpdateRequest blogTitleUpdateRequest + ){ + blogService.updateTitle(blogId, blogTitleUpdateRequest); + return ResponseEntity.noContent().build(); + } +} diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/controller/MemberController.java b/week3/seminar3/src/main/java/org/sopt/seminar3/controller/MemberController.java new file mode 100644 index 0000000..43589d4 --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/controller/MemberController.java @@ -0,0 +1,40 @@ +package org.sopt.seminar3.controller; + +import lombok.RequiredArgsConstructor; +import org.sopt.seminar3.dto.MemberCreateDto; +import org.sopt.seminar3.dto.MemberFindDto; +import org.sopt.seminar3.service.MemberService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/member") +public class MemberController { + + private final MemberService memberService; + + @PostMapping + public ResponseEntity createMember( + @RequestBody MemberCreateDto memberCreate + ) { + return ResponseEntity.created(URI.create(memberService.createMember(memberCreate))).build(); + } + + @GetMapping("/{memberId}") + public ResponseEntity findMemberById( + @PathVariable Long memberId + ) { + return ResponseEntity.ok(memberService.findMemberById(memberId)); + } + + @DeleteMapping("/{memberId}") + public ResponseEntity deleteMemberById( + @PathVariable Long memberId + ) { + memberService.deleteMemberById(memberId); + return ResponseEntity.noContent().build(); + } +} \ No newline at end of file diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/domain/BaseTimeEntity.java b/week3/seminar3/src/main/java/org/sopt/seminar3/domain/BaseTimeEntity.java new file mode 100644 index 0000000..8cfae52 --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/domain/BaseTimeEntity.java @@ -0,0 +1,22 @@ +package org.sopt.seminar3.domain; + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Getter +@MappedSuperclass // 공통 정보가 필요할 때 부모 클래스에 선언된 필드를 상속받는 클래스에서 그대로 사용 +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseTimeEntity { + + @CreatedDate + private LocalDateTime createdAt; + + @LastModifiedDate + private LocalDateTime updatedAt; +} diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/domain/Blog.java b/week3/seminar3/src/main/java/org/sopt/seminar3/domain/Blog.java new file mode 100644 index 0000000..420f88e --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/domain/Blog.java @@ -0,0 +1,50 @@ +package org.sopt.seminar3.domain; + +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +public class Blog extends BaseTimeEntity{ + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // Member와 Blog는 1:1 관계 + @OneToOne(fetch = FetchType.LAZY) + private Member member; + + @Column(length = 200) + private String title; + + private String description; + + public static Blog create( + Member member, + String title, + String description + ) { + return Blog.builder() + .member(member) + .title(title) + .description(description) + .build(); + } + + @Builder + public Blog(Member member, String title, String description) { + this.member = member; + this.title = title; + this.description = description; + } + + public void updateTitle( + String title + ){ + this.title = title; + } +} diff --git a/practice2/src/main/java/com/sopt/practice2/domain/Member.java b/week3/seminar3/src/main/java/org/sopt/seminar3/domain/Member.java similarity index 50% rename from practice2/src/main/java/com/sopt/practice2/domain/Member.java rename to week3/seminar3/src/main/java/org/sopt/seminar3/domain/Member.java index b288fdb..b3a0ed4 100644 --- a/practice2/src/main/java/com/sopt/practice2/domain/Member.java +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/domain/Member.java @@ -1,27 +1,37 @@ -package com.sopt.practice2.domain; +package org.sopt.seminar3.domain; import jakarta.persistence.*; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -@Entity // 객체와 테이블을 매핑하는 어노테이션(JPA가 관리하는 Entity) -@Getter // 전체 필드에 대한 getter 함수를 lombok이 제공 -@NoArgsConstructor // 기본 생성자를 생성하도록 lombok이 제공 +@Entity +@Getter +@NoArgsConstructor public class Member { - @Id // 영속성 컨텍스트는 엔티티를 식별자 값으로 구분 + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + private String name; @Enumerated(EnumType.STRING) private Part part; + private int age; + public static Member create(String name, Part part, int age) { + return Member.builder() + .name(name) + .part(part) + .age(age) + .build(); + } + @Builder - public Member(String name, Part part, int age){ + public Member(String name, Part part, int age) { this.name = name; this.part = part; this.age = age; } -} +} \ No newline at end of file diff --git a/practice2/src/main/java/com/sopt/practice2/domain/Part.java b/week3/seminar3/src/main/java/org/sopt/seminar3/domain/Part.java similarity index 88% rename from practice2/src/main/java/com/sopt/practice2/domain/Part.java rename to week3/seminar3/src/main/java/org/sopt/seminar3/domain/Part.java index 3ddf54f..048dd93 100644 --- a/practice2/src/main/java/com/sopt/practice2/domain/Part.java +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/domain/Part.java @@ -1,6 +1,7 @@ -package com.sopt.practice2.domain; +package org.sopt.seminar3.domain; public enum Part { + SERVER("SERVER"), WEB("WEB"), ANDROID("ANDROID"), @@ -17,4 +18,4 @@ public enum Part { public String getPart() { return this.part; } -} +} \ No newline at end of file diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/domain/Post.java b/week3/seminar3/src/main/java/org/sopt/seminar3/domain/Post.java new file mode 100644 index 0000000..197134c --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/domain/Post.java @@ -0,0 +1,23 @@ +package org.sopt.seminar3.domain; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +public class Post extends BaseTimeEntity{ + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String title; + + private String content; + + // Blog 와 Post는 N:1 관계 + @ManyToOne(fetch = FetchType.LAZY) + private Blog blog; +} diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/domain/SuccessMessage.java b/week3/seminar3/src/main/java/org/sopt/seminar3/domain/SuccessMessage.java new file mode 100644 index 0000000..54f5a82 --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/domain/SuccessMessage.java @@ -0,0 +1,15 @@ +package org.sopt.seminar3.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum SuccessMessage { + + BLOG_CREATE_SUCCESS(HttpStatus.CREATED.value(),"블로그 생성이 완료되었습니다.") + ; + private final int status; + private final String message; +} diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/dto/BlogCreateRequest.java b/week3/seminar3/src/main/java/org/sopt/seminar3/dto/BlogCreateRequest.java new file mode 100644 index 0000000..d9aef29 --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/dto/BlogCreateRequest.java @@ -0,0 +1,7 @@ +package org.sopt.seminar3.dto; + +public record BlogCreateRequest( + String title, + String description +) { +} diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/dto/BlogTitleUpdateRequest.java b/week3/seminar3/src/main/java/org/sopt/seminar3/dto/BlogTitleUpdateRequest.java new file mode 100644 index 0000000..180efe0 --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/dto/BlogTitleUpdateRequest.java @@ -0,0 +1,9 @@ +package org.sopt.seminar3.dto; + +import jakarta.validation.constraints.Size; + +public record BlogTitleUpdateRequest( + @Size(max=5, message = "블로그 제목이 최대 글자 수(5자)를 초과했습니다.") + String title +) { +} diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/dto/MemberCreateDto.java b/week3/seminar3/src/main/java/org/sopt/seminar3/dto/MemberCreateDto.java new file mode 100644 index 0000000..c2ce138 --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/dto/MemberCreateDto.java @@ -0,0 +1,10 @@ +package org.sopt.seminar3.dto; + +import org.sopt.seminar3.domain.Part; + +public record MemberCreateDto( + String name, + Part part, + int age +) { +} diff --git a/practice2/src/main/java/com/sopt/practice2/Web/dto/MemberFindDto.java b/week3/seminar3/src/main/java/org/sopt/seminar3/dto/MemberFindDto.java similarity index 66% rename from practice2/src/main/java/com/sopt/practice2/Web/dto/MemberFindDto.java rename to week3/seminar3/src/main/java/org/sopt/seminar3/dto/MemberFindDto.java index bd25fbe..f0c6e57 100644 --- a/practice2/src/main/java/com/sopt/practice2/Web/dto/MemberFindDto.java +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/dto/MemberFindDto.java @@ -1,16 +1,17 @@ -package com.sopt.practice2.Web.dto; +package org.sopt.seminar3.dto; -import com.sopt.practice2.domain.Member; -import com.sopt.practice2.domain.Part; +import org.sopt.seminar3.domain.Member; +import org.sopt.seminar3.domain.Part; public record MemberFindDto( String name, Part part, int age ) { + public static MemberFindDto of( Member member - ){ + ) { return new MemberFindDto(member.getName(), member.getPart(), member.getAge()); } -} +} \ No newline at end of file diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/dto/SuccessStatusResponse.java b/week3/seminar3/src/main/java/org/sopt/seminar3/dto/SuccessStatusResponse.java new file mode 100644 index 0000000..9361a6e --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/dto/SuccessStatusResponse.java @@ -0,0 +1,12 @@ +package org.sopt.seminar3.dto; + +import org.sopt.seminar3.domain.SuccessMessage; + +public record SuccessStatusResponse( + int status, + String message +) { + public static SuccessStatusResponse of(SuccessMessage successMessage){ + return new SuccessStatusResponse(successMessage.getStatus(), successMessage.getMessage()); + } +} diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/exception/BusinessException.java b/week3/seminar3/src/main/java/org/sopt/seminar3/exception/BusinessException.java new file mode 100644 index 0000000..a2d2b8a --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/exception/BusinessException.java @@ -0,0 +1,12 @@ +package org.sopt.seminar3.exception; + +import org.postgresql.util.ServerErrorMessage; +import org.sopt.seminar3.exception.message.ErrorMessage; + +public class BusinessException extends RuntimeException{ + private ErrorMessage errorMessage; + public BusinessException(ErrorMessage errorMessage){ + super(errorMessage.getMessage()); + this.errorMessage = errorMessage; + } +} diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/exception/NotFoundException.java b/week3/seminar3/src/main/java/org/sopt/seminar3/exception/NotFoundException.java new file mode 100644 index 0000000..54da210 --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/exception/NotFoundException.java @@ -0,0 +1,9 @@ +package org.sopt.seminar3.exception; + +import org.sopt.seminar3.exception.message.ErrorMessage; + +public class NotFoundException extends BusinessException{ + public NotFoundException(ErrorMessage errorMessage){ + super(errorMessage); + } +} diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/exception/message/ErrorMessage.java b/week3/seminar3/src/main/java/org/sopt/seminar3/exception/message/ErrorMessage.java new file mode 100644 index 0000000..9adab73 --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/exception/message/ErrorMessage.java @@ -0,0 +1,15 @@ +package org.sopt.seminar3.exception.message; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@AllArgsConstructor +@Getter +public enum ErrorMessage { + MEMBER_NOT_FOUND_BY_ID_EXCEPTION(HttpStatus.NOT_FOUND.value(),"ID에 해당하는 사용자가 존재하지 않습니다."), + BLOG_NOT_FOUND_BY_ID_EXCEPTION(HttpStatus.NOT_FOUND.value(),"ID에 해당하는 블로그가 존재하지 않습니다."), + ; + private final int status; + private final String message; +} diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/repository/BlogRepository.java b/week3/seminar3/src/main/java/org/sopt/seminar3/repository/BlogRepository.java new file mode 100644 index 0000000..115257b --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/repository/BlogRepository.java @@ -0,0 +1,7 @@ +package org.sopt.seminar3.repository; + +import org.sopt.seminar3.domain.Blog; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BlogRepository extends JpaRepository { +} diff --git a/practice2/src/main/java/com/sopt/practice2/Repository/MemberRepository.java b/week3/seminar3/src/main/java/org/sopt/seminar3/repository/MemberRepository.java similarity index 62% rename from practice2/src/main/java/com/sopt/practice2/Repository/MemberRepository.java rename to week3/seminar3/src/main/java/org/sopt/seminar3/repository/MemberRepository.java index d13db7f..874ac0d 100644 --- a/practice2/src/main/java/com/sopt/practice2/Repository/MemberRepository.java +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/repository/MemberRepository.java @@ -1,7 +1,7 @@ -package com.sopt.practice2.Repository; +package org.sopt.seminar3.repository; -import com.sopt.practice2.domain.Member; +import org.sopt.seminar3.domain.Member; import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository { -} +} \ No newline at end of file diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/service/BlogService.java b/week3/seminar3/src/main/java/org/sopt/seminar3/service/BlogService.java new file mode 100644 index 0000000..39c6b6a --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/service/BlogService.java @@ -0,0 +1,33 @@ +package org.sopt.seminar3.service; + +import lombok.RequiredArgsConstructor; +import org.sopt.seminar3.domain.Blog; +import org.sopt.seminar3.domain.Member; +import org.sopt.seminar3.dto.BlogCreateRequest; +import org.sopt.seminar3.dto.BlogTitleUpdateRequest; +import org.sopt.seminar3.repository.BlogRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class BlogService { + + private final BlogRepository blogRepository; + private final MemberService memberService; + + public String create(Long memberId, BlogCreateRequest blogCreateRequest) { + //member찾기 + Member member = memberService.findById(memberId); + Blog blog = blogRepository.save(Blog.create(member, blogCreateRequest.title(), blogCreateRequest.description())); + return blog.getId().toString(); + } + + @Transactional + public void updateTitle(Long blogId, BlogTitleUpdateRequest blogTitleUpdateRequest){ + Blog blog = blogRepository.findById(blogId).orElseThrow( + () -> new RuntimeException("해당 글이 존재하지 않습니다.") + ); + blog.updateTitle(blogTitleUpdateRequest.title()); + } +} \ No newline at end of file diff --git a/week3/seminar3/src/main/java/org/sopt/seminar3/service/MemberService.java b/week3/seminar3/src/main/java/org/sopt/seminar3/service/MemberService.java new file mode 100644 index 0000000..fa95049 --- /dev/null +++ b/week3/seminar3/src/main/java/org/sopt/seminar3/service/MemberService.java @@ -0,0 +1,51 @@ +package org.sopt.seminar3.service; + +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.sopt.seminar3.domain.Member; +import org.sopt.seminar3.dto.MemberCreateDto; +import org.sopt.seminar3.dto.MemberFindDto; +import org.sopt.seminar3.exception.NotFoundException; +import org.sopt.seminar3.exception.message.ErrorMessage; +import org.sopt.seminar3.repository.MemberRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class MemberService { + + private final MemberRepository memberRepository; + + @Transactional + public String createMember( + MemberCreateDto memberCreateDto + ) { + Member member = Member.create(memberCreateDto.name(), memberCreateDto.part(), memberCreateDto.age()); + memberRepository.save(member); + return member.getId().toString(); + } + + public MemberFindDto findMemberById( + Long memberId + ) { + return MemberFindDto.of(memberRepository.findById(memberId).orElseThrow( + () -> new EntityNotFoundException("ID에 해당하는 사용자가 존재하지 않습니다.") + )); + } + + @Transactional + public void deleteMemberById( + Long memberId + ) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new EntityNotFoundException("ID에 해당하는 사용자가 존재하지 않습니다.")); + memberRepository.delete(member); + } + + public Member findById(Long memberId) { + return memberRepository.findById(memberId).orElseThrow( + () -> new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND_BY_ID_EXCEPTION) + ); + } +} \ No newline at end of file diff --git a/week3/seminar3/src/test/java/org/sopt/seminar3/controller/BlogControllerTest.java b/week3/seminar3/src/test/java/org/sopt/seminar3/controller/BlogControllerTest.java new file mode 100644 index 0000000..324cf06 --- /dev/null +++ b/week3/seminar3/src/test/java/org/sopt/seminar3/controller/BlogControllerTest.java @@ -0,0 +1,68 @@ +package org.sopt.seminar3.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.sopt.seminar3.dto.BlogCreateRequest; +import org.sopt.seminar3.repository.BlogRepository; +import org.sopt.seminar3.repository.MemberRepository; +import org.sopt.seminar3.service.BlogService; +import org.sopt.seminar3.service.MemberService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(BlogController.class) //SpringBootTest로 서버를 구동시키는 대신 Controller 계층만 테스트 +@AutoConfigureMockMvc //Spring Boot 테스트에서 MockMvc를 사용하기 위한 설정을 자동으로 제공하는 어노테이션 +public class BlogControllerTest { + + @Autowired + private MockMvc mockMvc; + /* + BlogRepository -> BlogService -> MemberService -> MemberRepository -> BlogRepository + */ + @SpyBean + private BlogService blogService; + + @SpyBean + private MemberService memberService; + + @MockBean + private MemberRepository memberRepository; + + @MockBean + private BlogRepository blogRepository; + + @Autowired + private ObjectMapper objectMapper; //생성하는 객체를 String JSON 배열로 바꾸기 위해 사용 + + @Nested + class createBlog { + @Test + @DisplayName("Blog 생성 실패 테스트") + public void createBlogSuccess() throws Exception { + + //given + String request = objectMapper.writeValueAsString(new BlogCreateRequest("소현이네 블로그", "블로그입니다.")); + + //when + mockMvc.perform( + post("/api/v1/blog") + .content(request).header("memberId", 2) + .contentType(MediaType.APPLICATION_JSON) + ).andExpect(status().isNotFound()) //생성 실패 시나리오로 NotFound가 돌아오는 상황을 테스트 + .andDo(print()); // 끝난 후 모든 결과를 출력 + + } + } +} +