Skip to content

Commit

Permalink
[DEV-31] 과목 검색 API 응답 수정 (#265)
Browse files Browse the repository at this point in the history
* refactor: SearchLectureUseCase - return 값 수정(추가 여부 필드를 포함한 Dto)

* refactor: SearchLectureController - response spec 수정

* refactor: response 수정
  • Loading branch information
5uhwann authored May 29, 2024
1 parent 204ca95 commit 7fcc177
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.plzgraduate.myongjigraduatebe.lecture.api;

import java.util.List;

import javax.validation.constraints.Size;

import org.springframework.web.bind.annotation.RequestParam;

import com.plzgraduate.myongjigraduatebe.core.meta.LoginUser;
import com.plzgraduate.myongjigraduatebe.lecture.api.dto.response.SearchLectureResponse;

import io.swagger.v3.oas.annotations.Parameter;
Expand All @@ -12,7 +15,8 @@
@Tag(name = "SearchLecture", description = "type과 keyword를 통해 과목정보를 검색하는 API")
public interface SearchLectureApiPresentation {

SearchLectureResponse searchLecture(
List<SearchLectureResponse> searchLecture(
@LoginUser Long userId,
@Parameter(name = "type", description = "과목명 또는 과목코드") @RequestParam(defaultValue = "name") String type,
@Parameter(name = "keyword", description = "검색어 2자리 이상") @RequestParam @Size(min = 2, message = "검색어를 2자리 이상 입력해주세요.") String keyword
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package com.plzgraduate.myongjigraduatebe.lecture.api;

import java.util.List;
import java.util.stream.Collectors;

import javax.validation.constraints.Size;

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.plzgraduate.myongjigraduatebe.core.meta.LoginUser;
import com.plzgraduate.myongjigraduatebe.core.meta.WebAdapter;
import com.plzgraduate.myongjigraduatebe.lecture.api.dto.response.SearchLectureResponse;
import com.plzgraduate.myongjigraduatebe.lecture.application.usecase.SearchLectureUseCase;
import com.plzgraduate.myongjigraduatebe.lecture.application.usecase.dto.SearchedLectureDto;

import lombok.RequiredArgsConstructor;

Expand All @@ -22,10 +27,13 @@ public class SearchLectureController implements SearchLectureApiPresentation {
private final SearchLectureUseCase searchLectureUseCase;

@GetMapping
public SearchLectureResponse searchLecture(
public List<SearchLectureResponse> searchLecture(
@LoginUser Long userId,
@RequestParam(defaultValue = "name") String type,
@RequestParam @Size(min = 2, message = "검색어를 2자리 이상 입력해주세요.") String keyword
) {
return SearchLectureResponse.from(searchLectureUseCase.searchLectures(type, keyword));
return searchLectureUseCase.searchLectures(userId, type, keyword).stream()
.map(SearchLectureResponse::from)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,46 @@
package com.plzgraduate.myongjigraduatebe.lecture.api.dto.response;

import java.util.List;
import java.util.stream.Collectors;

import com.plzgraduate.myongjigraduatebe.lecture.domain.model.Lecture;
import com.plzgraduate.myongjigraduatebe.lecture.application.usecase.dto.SearchedLectureDto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class SearchLectureResponse {

private List<LectureResponse> lectures;
@Schema(name = "id", example = "18")
private final Long id;
@Schema(name = "lectureCode", example = "KMA02137")
private final String lectureCode;
@Schema(name = "name", example = "4차산업혁명시대의진로선택")
private final String name;
@Schema(name = "credit", example = "2")
private final int credit;
@Schema(name = "isRevoked", example = "false")
private final boolean revoked;
@Schema(name = "isAddable", example = "true")
private final boolean taken;

@Builder
private SearchLectureResponse(List<LectureResponse> lectures) {
this.lectures = lectures;
private SearchLectureResponse(Long id, String lectureCode, String name, int credit, boolean revoked,
boolean taken) {
this.id = id;
this.lectureCode = lectureCode;
this.name = name;
this.credit = credit;
this.revoked = revoked;
this.taken = taken;
}

public static SearchLectureResponse from(List<Lecture> lectures) {
public static SearchLectureResponse from(SearchedLectureDto searchedLectureDto) {
return SearchLectureResponse.builder()
.lectures(lectures.stream().map(LectureResponse::of).collect(Collectors.toList()))
.id(searchedLectureDto.getLecture().getId())
.lectureCode(searchedLectureDto.getLecture().getLectureCode())
.name(searchedLectureDto.getLecture().getName())
.credit(searchedLectureDto.getLecture().getCredit())
.revoked(searchedLectureDto.getLecture().getIsRevoked() != 0)
.taken(searchedLectureDto.isAddable())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
package com.plzgraduate.myongjigraduatebe.lecture.application.service;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.transaction.annotation.Transactional;

import com.plzgraduate.myongjigraduatebe.core.meta.UseCase;
import com.plzgraduate.myongjigraduatebe.lecture.application.port.SearchLecturePort;
import com.plzgraduate.myongjigraduatebe.lecture.application.usecase.SearchLectureUseCase;
import com.plzgraduate.myongjigraduatebe.lecture.application.usecase.dto.SearchedLectureDto;
import com.plzgraduate.myongjigraduatebe.lecture.domain.model.Lecture;
import com.plzgraduate.myongjigraduatebe.takenlecture.application.usecase.find.FindTakenLectureUseCase;
import com.plzgraduate.myongjigraduatebe.takenlecture.domain.model.TakenLecture;
import com.plzgraduate.myongjigraduatebe.takenlecture.domain.model.TakenLectureInventory;

import lombok.RequiredArgsConstructor;

@UseCase
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class SearchLectureService implements SearchLectureUseCase {

private final SearchLecturePort searchLecturePort;
private final FindTakenLectureUseCase findTakenLectureUseCase;

@Override
public List<Lecture> searchLectures(String type, String keyword) {
return searchLecturePort.searchLectureByNameOrCode(type, keyword);
public List<SearchedLectureDto> searchLectures(Long userId, String type, String keyword) {
List<Lecture> searchedLectures = searchLecturePort.searchLectureByNameOrCode(type, keyword);
TakenLectureInventory takenLectureInventory = findTakenLectureUseCase.findTakenLectures(userId);

List<Lecture> takenLectures = takenLectureInventory.getTakenLectures().stream()
.map(TakenLecture::getLecture)
.collect(Collectors.toList());

return searchedLectures.stream()
.map(searchedLecture -> SearchedLectureDto.of(takenLectures.contains(searchedLecture),
searchedLecture))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import java.util.List;

import com.plzgraduate.myongjigraduatebe.lecture.domain.model.Lecture;
import com.plzgraduate.myongjigraduatebe.lecture.application.usecase.dto.SearchedLectureDto;

public interface SearchLectureUseCase {
List<Lecture> searchLectures(String type, String keyword);
List<SearchedLectureDto> searchLectures(Long userId, String type, String keyword);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.plzgraduate.myongjigraduatebe.lecture.application.usecase.dto;

import com.plzgraduate.myongjigraduatebe.lecture.domain.model.Lecture;

import lombok.Builder;
import lombok.Getter;

@Getter
public class SearchedLectureDto {

private final boolean addable;
private final Lecture lecture;

@Builder
private SearchedLectureDto(boolean addable, Lecture lecture) {
this.addable = addable;
this.lecture = lecture;
}

public static SearchedLectureDto of(boolean addable, Lecture lecture) {
return SearchedLectureDto.builder()
.addable(addable)
.lecture(lecture).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.hamcrest.Matchers.hasSize;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.times;
Expand All @@ -15,6 +16,7 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import com.plzgraduate.myongjigraduatebe.lecture.application.usecase.dto.SearchedLectureDto;
import com.plzgraduate.myongjigraduatebe.lecture.domain.model.Lecture;
import com.plzgraduate.myongjigraduatebe.support.WebAdaptorTestSupport;
import com.plzgraduate.myongjigraduatebe.support.WithMockAuthenticationUser;
Expand All @@ -26,20 +28,19 @@ class SearchLectureControllerTest extends WebAdaptorTestSupport {
@Test
void searchLecture() throws Exception {
//given
List<Lecture> searchedLectures = List.of(
Lecture.builder().id(1L).build()
);
given(searchLectureUseCase.searchLectures(any(), any())).willReturn(searchedLectures);
List<SearchedLectureDto> searchedLectures = List.of(SearchedLectureDto.of(true, Lecture.from("code")));
given(searchLectureUseCase.searchLectures(anyLong(), any(), any())).willReturn(searchedLectures);

//when //then
mockMvc.perform(get("/api/v1/lectures")
.param("type", "name")
.param("keyword", "기초"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.lectures", hasSize(1)));
.andExpect(jsonPath("$", hasSize(searchedLectures.size())))
.andExpect(jsonPath("$.[0].taken").value(true));

then(searchLectureUseCase).should(times(1)).searchLectures(any(), any());
then(searchLectureUseCase).should(times(1)).searchLectures(anyLong(), any(), any());
}

@WithMockAuthenticationUser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.mockito.BDDMockito.given;

import java.util.List;
import java.util.Set;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -15,37 +16,50 @@

import com.plzgraduate.myongjigraduatebe.lecture.application.port.SearchLecturePort;
import com.plzgraduate.myongjigraduatebe.lecture.application.service.SearchLectureService;
import com.plzgraduate.myongjigraduatebe.lecture.application.usecase.dto.SearchedLectureDto;
import com.plzgraduate.myongjigraduatebe.lecture.domain.model.Lecture;
import com.plzgraduate.myongjigraduatebe.takenlecture.application.usecase.find.FindTakenLectureUseCase;
import com.plzgraduate.myongjigraduatebe.takenlecture.domain.model.TakenLecture;
import com.plzgraduate.myongjigraduatebe.takenlecture.domain.model.TakenLectureInventory;

@ExtendWith(MockitoExtension.class)
class SearchLectureServiceTest {
@Mock
private SearchLecturePort searchLecturePort;
@Mock
private FindTakenLectureUseCase findTakenLectureUseCase;
@InjectMocks
private SearchLectureService searchLectureService;

@DisplayName("과목을 검색한다.")
@Test
void searchLectures() {
Long userId = 1L;
String type = "name";
String keyword = "기초";
List<Lecture> lectures = List.of(
createLecture(1L, "code1", "기초웹프로그래밍", 3, 0),
createLecture(2L, "code2", "앱과웹기초", 2, 1)
Lecture takenLecture = createLecture(1L, "code1", "기초웹프로그래밍", 3, 0);
Lecture nonTakenLecture = createLecture(2L, "code2", "앱과웹기초", 2, 1);
List<Lecture> lectures = List.of(takenLecture, nonTakenLecture);
TakenLectureInventory takenLectureInventory = TakenLectureInventory.from(
Set.of(TakenLecture.builder()
.lecture(Lecture.from("code1"))
.build())
);
given(searchLecturePort.searchLectureByNameOrCode("name", "기초"))
.willReturn(lectures);
given(findTakenLectureUseCase.findTakenLectures(userId)).willReturn(takenLectureInventory);

//when
List<Lecture> searchedLectures = searchLectureService.searchLectures(type, keyword);
List<SearchedLectureDto> searchedLectureDtos = searchLectureService.searchLectures(userId, type,
keyword);

//then
assertThat(searchedLectures)
assertThat(searchedLectureDtos)
.hasSize(2)
.extracting("id", "lectureCode", "name", "credit", "isRevoked")
.extracting("addable", "lecture")
.containsExactlyInAnyOrder(
tuple(1L, "code1", "기초웹프로그래밍", 3, 0),
tuple(2L, "code2", "앱과웹기초", 2, 1)
tuple(true, takenLecture),
tuple(false, nonTakenLecture)
);
}

Expand Down

0 comments on commit 7fcc177

Please sign in to comment.