Skip to content

Commit

Permalink
Feat(Problem): Bookmark 생성,삭제 API 구현 (#9)
Browse files Browse the repository at this point in the history
* Refactor(Problem): 가독성 등을 위해 문제 조회에서 DB관련 로직과 응답 DTO 매핑을 분리

* Feat(Problem): Bookmark Entity 구현

- Entity, Repository와 매핑관계 구현

* Feat(Problem): Bookmark 생성 API 구현

* Test(Problem): Bookmark 생성 API 테스트 구현

* Feat(Problem): Bookmark 삭제 API 구현

* Test(Problem): Bookmark 생성 API 테스트 추가 구현

- 예외 상황 테스트

* Test(Problem): Bookmark 삭제 API 테스트 구현

Jira issue: SWM-36
  • Loading branch information
morenow98 authored Jun 29, 2024
1 parent 5dc4a6a commit 7054d7a
Show file tree
Hide file tree
Showing 12 changed files with 339 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public class BookmarkController {
public ResponseEntity<Void> createBookmark(
@RequestBody CreateBookmarkRequest request
) {
String bookmarkId = createBookmarkUseCase.execute(request);
String memberId = "1"; // TODO : 로그인 기능 구현 후 로그인한 사용자의 ID로 변경
String bookmarkId = createBookmarkUseCase.execute(memberId, request);

URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
Expand All @@ -39,7 +40,8 @@ public ResponseEntity<Void> createBookmark(
public ResponseEntity<Void> deleteBookmark(
@RequestBody DeleteBookmarkRequest request
) {
deleteBookmarkUseCase.execute(request);
String memberId = "1"; // TODO : 로그인 기능 구현 후 로그인한 사용자의 ID로 변경
deleteBookmarkUseCase.execute(memberId, request);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
package com.jabiseo.problem.usecase;

import com.jabiseo.member.domain.Member;
import com.jabiseo.member.domain.MemberRepository;
import com.jabiseo.problem.domain.Bookmark;
import com.jabiseo.problem.domain.BookmarkRepository;
import com.jabiseo.problem.domain.Problem;
import com.jabiseo.problem.domain.ProblemRepository;
import com.jabiseo.problem.dto.CreateBookmarkRequest;
import com.jabiseo.problem.exception.ProblemBusinessException;
import com.jabiseo.problem.exception.ProblemErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class CreateBookmarkUseCase {

public String execute(CreateBookmarkRequest request) {
return "bookmarkId";
private final MemberRepository memberRepository;

private final ProblemRepository problemRepository;

private final BookmarkRepository bookmarkRepository;

public String execute(String memberId, CreateBookmarkRequest request) {
if (bookmarkRepository.existsByMemberIdAndProblemId(memberId, request.problemId())) {
throw new ProblemBusinessException(ProblemErrorCode.BOOKMARK_ALREADY_EXISTS);
}

Member member = memberRepository.getReferenceById(memberId);
Problem problem = problemRepository.findById(request.problemId())
.orElseThrow(() -> new ProblemBusinessException(ProblemErrorCode.PROBLEM_NOT_FOUND));


Bookmark bookmark = Bookmark.of(member, problem);

return bookmarkRepository.save(bookmark).getId();
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
package com.jabiseo.problem.usecase;

import com.jabiseo.problem.domain.Bookmark;
import com.jabiseo.problem.domain.BookmarkRepository;
import com.jabiseo.problem.dto.DeleteBookmarkRequest;
import com.jabiseo.problem.exception.ProblemBusinessException;
import com.jabiseo.problem.exception.ProblemErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
public class DeleteBookmarkUseCase {

public void execute(DeleteBookmarkRequest request) {
return;
private final BookmarkRepository bookmarkRepository;

public void execute(String memberId, DeleteBookmarkRequest request) {
Bookmark bookmark = bookmarkRepository.findByMemberIdAndProblemId(memberId, request.problemId())
.orElseThrow(() -> new ProblemBusinessException(ProblemErrorCode.BOOKMARK_NOT_FOUND));

bookmarkRepository.delete(bookmark);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.jabiseo.certificate.dto.SubjectResponse;
import com.jabiseo.certificate.exception.CertificateBusinessException;
import com.jabiseo.certificate.exception.CertificateErrorCode;
import com.jabiseo.problem.domain.Problem;
import com.jabiseo.problem.domain.ProblemRepository;
import com.jabiseo.problem.dto.ChoiceResponse;
import com.jabiseo.problem.dto.FindProblemsRequest;
Expand Down Expand Up @@ -43,14 +44,17 @@ public List<FindProblemsResponse> execute(String certificateId, List<String> sub
validateProblemCount(count);


return subjectIds.stream()
List<Problem> problems = subjectIds.stream()
.map(subjectId -> {
if (examId.isPresent()) {
return problemRepository.findRandomByExamIdAndSubjectId(examId.get(), subjectId, count);
}
return problemRepository.findRandomBySubjectId(subjectId, count);
})
.flatMap(List::stream)
.toList();

return problems.stream()
.map(FindProblemsResponse::from)
.toList();
}
Expand Down
23 changes: 23 additions & 0 deletions jabiseo-api/src/test/java/com/jabiseo/fixture/ProblemFixture.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
import com.jabiseo.certificate.domain.Subject;
import com.jabiseo.problem.domain.Problem;

import static com.jabiseo.fixture.CertificateFixture.createCertificate;
import static com.jabiseo.fixture.ExamFixture.createExam;
import static com.jabiseo.fixture.SubjectFixture.createSubject;

public class ProblemFixture {
public static Problem createProblem(String id, Certificate certificate, Exam exam, Subject subject) {
return Problem.of(
Expand All @@ -23,4 +27,23 @@ public static Problem createProblem(String id, Certificate certificate, Exam exa
subject
);
}

public static Problem createProblem(String id) {
Certificate certificate = createCertificate("1234");
return Problem.of(
id,
"problem description",
"choice1",
"choice2",
"choice3",
"choice4",
"choice5",
1,
"problem theory",
"problem solution",
certificate,
createExam("5432", certificate),
createSubject("9876", certificate)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.jabiseo.problem.usecase;

import com.jabiseo.member.domain.Member;
import com.jabiseo.member.domain.MemberRepository;
import com.jabiseo.problem.domain.Bookmark;
import com.jabiseo.problem.domain.BookmarkRepository;
import com.jabiseo.problem.domain.Problem;
import com.jabiseo.problem.domain.ProblemRepository;
import com.jabiseo.problem.dto.CreateBookmarkRequest;
import com.jabiseo.problem.exception.ProblemBusinessException;
import com.jabiseo.problem.exception.ProblemErrorCode;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

import static com.jabiseo.fixture.MemberFixture.createMember;
import static com.jabiseo.fixture.ProblemFixture.createProblem;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;

@DisplayName("북마크 생성 테스트")
@ExtendWith(MockitoExtension.class)
class CreateBookmarkUseCaseTest {

@InjectMocks
CreateBookmarkUseCase sut;

@Mock
MemberRepository memberRepository;

@Mock
ProblemRepository problemRepository;

@Mock
BookmarkRepository bookmarkRepository;

@Test
@DisplayName("북마크 생성 테스트 성공 케이스")
void givenMemberIdAndProblemId_whenCreatingBookmark_thenCreateBookmark() {
//given
String memberId = "1";
String problemId = "2";
Member member = createMember(memberId);
Problem problem = createProblem(problemId);
given(memberRepository.getReferenceById(memberId)).willReturn(member);
given(problemRepository.findById(problemId)).willReturn(Optional.of(problem));
given(bookmarkRepository.existsByMemberIdAndProblemId(memberId, problemId)).willReturn(false);
given(bookmarkRepository.save(any())).willReturn(Bookmark.of(member, problem));


//when
sut.execute(memberId, new CreateBookmarkRequest(problemId));

//then
ArgumentCaptor<Bookmark> bookmarkCaptor = ArgumentCaptor.forClass(Bookmark.class);
verify(bookmarkRepository).save(bookmarkCaptor.capture());
Bookmark savedBookmark = bookmarkCaptor.getValue();

assertThat(savedBookmark).isNotNull();
assertThat(savedBookmark.getMember().getId()).isEqualTo(memberId);
assertThat(savedBookmark.getProblem().getId()).isEqualTo(problemId);
}

@Test
@DisplayName("이미 북마크한 문제를 북마크하는 경우 테스트")
void givenAlreadyExistedMemberIdAndProblemId_whenCreatingBookmark_thenReturnError() {
//given
String memberId = "1";
String problemId = "2";
given(bookmarkRepository.existsByMemberIdAndProblemId(memberId, problemId)).willReturn(true);


//when & then
assertThatThrownBy(() -> sut.execute(memberId, new CreateBookmarkRequest(problemId)))
.isInstanceOf(ProblemBusinessException.class)
.hasFieldOrPropertyWithValue("errorCode", ProblemErrorCode.BOOKMARK_ALREADY_EXISTS);

}

@Test
@DisplayName("존재하지 않는 문제의 북마크 생성 테스트")
void givenMemberIdAndNonExistedProblemId_whenCreatingBookmark_thenReturnError() {
//given
String memberId = "1";
String problemId = "2";
given(bookmarkRepository.existsByMemberIdAndProblemId(memberId, problemId)).willReturn(false);
given(problemRepository.findById(problemId)).willReturn(Optional.empty());

//when & then
assertThatThrownBy(() -> sut.execute(memberId, new CreateBookmarkRequest(problemId)))
.isInstanceOf(ProblemBusinessException.class)
.hasFieldOrPropertyWithValue("errorCode", ProblemErrorCode.PROBLEM_NOT_FOUND);

}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.jabiseo.problem.usecase;

import com.jabiseo.member.domain.Member;
import com.jabiseo.problem.domain.Bookmark;
import com.jabiseo.problem.domain.BookmarkRepository;
import com.jabiseo.problem.domain.Problem;
import com.jabiseo.problem.dto.DeleteBookmarkRequest;
import com.jabiseo.problem.exception.ProblemBusinessException;
import com.jabiseo.problem.exception.ProblemErrorCode;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

import static com.jabiseo.fixture.MemberFixture.createMember;
import static com.jabiseo.fixture.ProblemFixture.createProblem;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;

@DisplayName("북마크 삭제 테스트")
@ExtendWith(MockitoExtension.class)
class DeleteBookmarkUseCaseTest {

@InjectMocks
DeleteBookmarkUseCase sut;

@Mock
BookmarkRepository bookmarkRepository;

@Test
@DisplayName("북마크 삭제 테스트 성공 케이스")
void givenMemberIdAndProblemId_whenDeletingBookmark_thenDeleteBookmark() {
//given
String memberId = "1";
String problemId = "2";
Member member = createMember(memberId);
Problem problem = createProblem(problemId);
Bookmark bookmark = Bookmark.of(member, problem);
given(bookmarkRepository.findByMemberIdAndProblemId(memberId, problemId)).willReturn(Optional.of(bookmark));

//when
sut.execute(memberId, new DeleteBookmarkRequest(problemId));

//then
ArgumentCaptor<Bookmark> bookmarkCaptor = ArgumentCaptor.forClass(Bookmark.class);
verify(bookmarkRepository).delete(bookmarkCaptor.capture());
Bookmark deletedBookmark = bookmarkCaptor.getValue();
assertThat(deletedBookmark).isEqualTo(bookmark);
}

@Test
@DisplayName("존재하지 않는 북마크를 삭제하는 경우 테스트")
void givenNonExistBookmarkWithMemberIdAndProblemId_whenDeletingBookmark_thenReturnError() {
//given
String memberId = "1";
String problemId = "2";
given(bookmarkRepository.findByMemberIdAndProblemId(memberId, problemId)).willReturn(Optional.empty());

//when & then
assertThatThrownBy(() -> sut.execute(memberId, new DeleteBookmarkRequest(problemId)))
.isInstanceOf(ProblemBusinessException.class)
.hasFieldOrPropertyWithValue("errorCode", ProblemErrorCode.BOOKMARK_NOT_FOUND);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.jabiseo.member.domain;

import com.jabiseo.certificate.domain.Certificate;
import com.jabiseo.problem.domain.Bookmark;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
Expand All @@ -12,6 +13,8 @@
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
Expand Down Expand Up @@ -51,6 +54,9 @@ public class Member {
@JoinColumn(name = "certificate_state_id", foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT))
private Certificate certificateState;

@OneToMany(mappedBy = "member")
private List<Bookmark> bookmarks = new ArrayList<>();

private Member(String id, String email, String nickname, String oauthId, String oauthServer, String profileImage) {
this.id = id;
this.email = email;
Expand All @@ -70,4 +76,7 @@ public Member updateCertificateState(Certificate certificate) {
return this;
}

public void addBookmark(Bookmark bookmark) {
bookmarks.add(bookmark);
}
}
Loading

0 comments on commit 7054d7a

Please sign in to comment.