Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] issue18: 스터디 제목으로 검색 #24

Merged
merged 3 commits into from
Jul 6, 2022
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
Expand Up @@ -6,9 +6,12 @@
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/studies")
public class StudyController {

private final StudyService studyService;
Expand All @@ -17,11 +20,20 @@ public StudyController(final StudyService studyService) {
this.studyService = studyService;
}

@GetMapping("/api/studies")
@GetMapping
public ResponseEntity<StudiesResponse> getStudies(
@PageableDefault(size = 5) final Pageable pageable
) {
final StudiesResponse studiesResponse = studyService.getStudies(pageable);
return ResponseEntity.ok().body(studiesResponse);
}

@GetMapping("/search")
public ResponseEntity<StudiesResponse> searchStudies(
@RequestParam(required = false, defaultValue = "") final String title,
@PageableDefault(size = 5) final Pageable pageable
) {
final StudiesResponse studiesResponse = studyService.searchBy(title, pageable);
return ResponseEntity.ok().body(studiesResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@
public interface StudyRepository {

Slice<Study> findAll(Pageable pageable);

Slice<Study> findByTitleContainingIgnoreCase(String title, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,12 @@ public StudiesResponse getStudies(final Pageable pageable) {
.getContent();
return new StudiesResponse(studies, slice.hasNext());
}

public StudiesResponse searchBy(final String title, final Pageable pageable) {
final Slice<Study> slice = studyRepository.findByTitleContainingIgnoreCase(title.trim(), pageable);
final List<StudyResponse> studies = slice
.map(StudyResponse::new)
.getContent();
return new StudiesResponse(studies, slice.hasNext());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.woowacourse.acceptance;

import com.woowacourse.moamoa.MoamoaApplication;
import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.jdbc.Sql;

@SpringBootTest(
webEnvironment = WebEnvironment.RANDOM_PORT,
classes = {MoamoaApplication.class}
)
@Sql("/init.sql")
public class AcceptanceTest {
@LocalServerPort
protected int port;

@BeforeEach
void setUp() {
RestAssured.port = port;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.woowacourse.acceptance;
package com.woowacourse.acceptance.study;

import static org.hamcrest.Matchers.blankOrNullString;
import static org.hamcrest.Matchers.contains;
Expand All @@ -7,34 +7,17 @@
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;

import com.woowacourse.moamoa.MoamoaApplication;
import com.woowacourse.acceptance.AcceptanceTest;
import io.restassured.RestAssured;
import io.restassured.response.ValidatableResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.test.context.jdbc.Sql;

@SpringBootTest(
webEnvironment = WebEnvironment.RANDOM_PORT,
classes = {MoamoaApplication.class}
)
@Sql("/init.sql")
public class StudyAcceptanceTest {

@LocalServerPort
private int port;

@BeforeEach
void setUp() {
RestAssured.port = port;
}
@DisplayName("스터디 목록 조회 인수 테스트")
public class GettingStudiesAcceptanceTest extends AcceptanceTest {

@DisplayName("첫번째 페이지의 스터디 목록을 조회 한다.")
@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.woowacourse.acceptance.study;

import static org.hamcrest.Matchers.blankOrNullString;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;

import com.woowacourse.acceptance.AcceptanceTest;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.http.HttpStatus;

@DisplayName("키워드 검색 인수 테스트")
public class SearchingStudiesAcceptanceTest extends AcceptanceTest {

@DisplayName("잘못된 페이징 정보로 목록을 검색시 400에러를 응답한다.")
@ParameterizedTest
@CsvSource({"-1,3", "1,0", "one,1", "1,one"})
public void response400WhenRequestByInvalidPagingInfo(String page, String size) {
RestAssured.given().log().all()
.param("title", "java")
.param("page", page)
.param("size", size)
.when().log().all()
.get("/api/studies/search")
.then().log().all()
.statusCode(HttpStatus.BAD_REQUEST.value())
.body("message", not(blankOrNullString()));
}

@DisplayName("페이지 정보 없이 목록 검색시 400에러를 응답한다.")
@Test
public void getStudiesByDefaultPage() {
RestAssured.given().log().all()
.param("title", "java")
.param("size", 5)
.when().log().all()
.get("/api/studies/search")
.then().log().all()
.statusCode(HttpStatus.BAD_REQUEST.value())
.body("message", not(blankOrNullString()));
}

@DisplayName("사이즈 정보 없이 목록 조회시 400에러를 응답한다.")
@Test
public void getStudiesByDefaultSize() {
RestAssured.given().log().all()
.param("title", "java")
.param("page", 0)
.when().log().all()
.get("/api/studies/search")
.then().log().all()
.statusCode(HttpStatus.BAD_REQUEST.value())
.body("message", not(blankOrNullString()));
}

@DisplayName("페이징 정보 및 키워드가 없는 경우에는 기본페이징 정보를 사용해 전체 스터디 목록에서 조회한다.")
@Test
public void getStudiesByDefaultPagingInfo() {
RestAssured.given().log().all()
.when().log().all()
.get("/api/studies/search")
.then().log().all()
.statusCode(HttpStatus.OK.value())
.body("hasNext", is(false))
.body("studies", hasSize(5))
.body("studies.id", contains(
notNullValue(), notNullValue(), notNullValue(), notNullValue(), notNullValue()))
.body("studies.title", contains(
"Java 스터디", "React 스터디", "javaScript 스터디", "HTTP 스터디", "알고리즘 스터디"))
.body("studies.description", contains(
"자바 설명", "리액트 설명", "자바스크립트 설명", "HTTP 설명", "알고리즘 설명"))
.body("studies.thumbnail", contains(
"java thumbnail", "react thumbnail", "javascript thumbnail", "http thumbnail",
"algorithm thumbnail"))
.body("studies.status", contains("OPEN", "OPEN", "OPEN", "CLOSE", "CLOSE"));
}

@DisplayName("앞뒤 공백을 제거한 키워드로 스터디 목록을 조회한다.")
@Test
void getStudiesByTrimKeyword() {
RestAssured.given().log().all()
.param("title", " java ")
.param("page", 0)
.param("size", 3)
.when().log().all()
.get("/api/studies/search")
.then().log().all()
.statusCode(HttpStatus.OK.value())
.body("hasNext", is(false))
.body("studies", hasSize(2))
.body("studies.id", contains(notNullValue(), notNullValue()))
.body("studies.title", contains("Java 스터디", "javaScript 스터디"))
.body("studies.description", contains("자바 설명", "자바스크립트 설명"))
.body("studies.thumbnail", contains("java thumbnail", "javascript thumbnail"))
.body("studies.status", contains("OPEN", "OPEN"));
}

@DisplayName("중간에 공백이 있는 키워드를 사용해 스터디 목록을 조회한다.")
@Test
void getStudiesByHasSpaceKeyword() {
RestAssured.given().log().all()
.param("title", "Java 스터디")
.param("page", 0)
.param("size", 3)
.when().log().all()
.get("/api/studies/search")
.then().log().all()
.statusCode(HttpStatus.OK.value())
.body("hasNext", is(false))
.body("studies", hasSize(1))
.body("studies.id", contains(notNullValue()))
.body("studies.title", contains("Java 스터디"))
.body("studies.description", contains("자바 설명"))
.body("studies.thumbnail", contains("java thumbnail"))
.body("studies.status", contains("OPEN"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ void setUp() {
), Pageable.unpaged(), true)
);

when(studyRepository.findByTitleContainingIgnoreCase("Java 스터디", PageRequest.of(0, 3)))
.thenReturn(
new SliceImpl<>(List.of(
new Study(1L, "Java 스터디", "자바 설명", "java thumbnail", "OPEN")
))
);

when(studyRepository.findByTitleContainingIgnoreCase("", PageRequest.of(0, 3)))
.thenReturn(
new SliceImpl<>(List.of(
new Study(1L, "Java 스터디", "자바 설명", "java thumbnail", "OPEN"),
new Study(2L, "React 스터디", "리액트 설명", "react thumbnail", "OPEN"),
new Study(3L, "javaScript 스터디", "자바스크립트 설명", "javascript thumbnail", "OPEN")
), Pageable.unpaged(), true)
);

studyController = new StudyController(new StudyService(studyRepository));
}

Expand All @@ -61,4 +77,56 @@ public void getStudies() {

verify(studyRepository).findAll(PageRequest.of(0, 3));
}

@DisplayName("빈 문자열로 검색시 전체 스터디 목록에서 조회")
@Test
void searchByBlankKeyword() {
ResponseEntity<StudiesResponse> response = studyController.searchStudies("", PageRequest.of(0, 3));

assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().isHasNext()).isTrue();
assertThat(response.getBody().getStudies())
.hasSize(3)
.extracting("id", "title", "description", "thumbnail", "status")
.containsExactlyElementsOf(List.of(
tuple(1L, "Java 스터디", "자바 설명", "java thumbnail", "OPEN"),
tuple(2L, "React 스터디", "리액트 설명", "react thumbnail", "OPEN"),
tuple(3L, "javaScript 스터디", "자바스크립트 설명", "javascript thumbnail", "OPEN"))
);

verify(studyRepository).findByTitleContainingIgnoreCase("", PageRequest.of(0, 3));
}

@DisplayName("문자열로 검색시 해당되는 스터디 목록에서 조회")
@Test
void searchByKeyword() {
ResponseEntity<StudiesResponse> response = studyController.searchStudies("Java 스터디", PageRequest.of(0, 3));

assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().isHasNext()).isFalse();
assertThat(response.getBody().getStudies())
.hasSize(1)
.extracting("id", "title", "description", "thumbnail", "status")
.contains(tuple(1L, "Java 스터디", "자바 설명", "java thumbnail", "OPEN"));

verify(studyRepository).findByTitleContainingIgnoreCase("Java 스터디", PageRequest.of(0, 3));
}

@DisplayName("앞뒤 공백을 제거한 문자열로 스터디 목록 조회")
@Test
void searchWithTrimKeyword() {
ResponseEntity<StudiesResponse> response = studyController.searchStudies(" Java 스터디 ", PageRequest.of(0, 3));

assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().isHasNext()).isFalse();
assertThat(response.getBody().getStudies())
.hasSize(1)
.extracting("id", "title", "description", "thumbnail", "status")
.contains(tuple(1L, "Java 스터디", "자바 설명", "java thumbnail", "OPEN"));

verify(studyRepository).findByTitleContainingIgnoreCase("Java 스터디", PageRequest.of(0, 3));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.stream.Stream;
import org.assertj.core.groups.Tuple;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
Expand Down Expand Up @@ -41,6 +42,39 @@ public void findAllByPageable(Pageable pageable, List<Tuple> expectedTuples,
.containsExactlyElementsOf(expectedTuples);
}

@DisplayName("키워드와 함께 페이징 정보를 사용해 스터디 목록 조회")
@Test
public void findByTitleContaining() {
final Slice<Study> slice = studyRepository.findByTitleContainingIgnoreCase("java", PageRequest.of(0, 3));

assertThat(slice.hasNext()).isFalse();
assertThat(slice.getContent())
.hasSize(2)
.filteredOn(study -> study.getId() != null)
.extracting("title", "description", "thumbnail", "status")
.containsExactly(
tuple("Java 스터디", "자바 설명", "java thumbnail", "OPEN"),
tuple("javaScript 스터디", "자바스크립트 설명", "javascript thumbnail", "OPEN"));
}

@DisplayName("빈 키워드와 함께 페이징 정보를 사용해 스터디 목록 조회")
@Test
public void findByBlankTitle() {
final Slice<Study> slice = studyRepository.findByTitleContainingIgnoreCase("", PageRequest.of(0, 5));

assertThat(slice.hasNext()).isFalse();
assertThat(slice.getContent())
.hasSize(5)
.filteredOn(study -> study.getId() != null)
.extracting("title", "description", "thumbnail", "status")
.containsExactly(
tuple("Java 스터디", "자바 설명", "java thumbnail", "OPEN"),
tuple("React 스터디", "리액트 설명", "react thumbnail", "OPEN"),
tuple("javaScript 스터디", "자바스크립트 설명", "javascript thumbnail", "OPEN"),
tuple("HTTP 스터디", "HTTP 설명", "http thumbnail", "CLOSE"),
tuple("알고리즘 스터디", "알고리즘 설명", "algorithm thumbnail", "CLOSE"));
}

private static Stream<Arguments> providePageableAndExpect() {
List<Tuple> tuples = List.of(
tuple("Java 스터디", "자바 설명", "java thumbnail", "OPEN"),
Expand Down