From d1183d1e2b17134bf596fb50139d5f9cee1de0c5 Mon Sep 17 00:00:00 2001 From: Auri Munoz Date: Tue, 9 Apr 2024 17:52:47 +0200 Subject: [PATCH] First try to add tests for list based spring repositories --- .../rest/deployment/ResourceImplementor.java | 7 - .../deployment/SpringDataRestProcessor.java | 42 -- .../crud/DefaultListCrudResourceTest.java | 259 +++++++++++ .../crud/DefaultRecordsListRepository.java | 6 + .../paged/CrudAndPagedRecordsRepository.java | 7 + .../rest/paged/CrudAndPagedResourceTest.java | 436 ++++++++++++++++++ .../paged/DefaultPagedResourceBisTest.java | 53 +++ .../rest/paged/DefaultPagedResourceTest.java | 182 -------- 8 files changed, 761 insertions(+), 231 deletions(-) create mode 100644 extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/DefaultListCrudResourceTest.java create mode 100644 extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/DefaultRecordsListRepository.java create mode 100644 extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/CrudAndPagedRecordsRepository.java create mode 100644 extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/CrudAndPagedResourceTest.java create mode 100644 extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceBisTest.java diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourceImplementor.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourceImplementor.java index c2ac1e5b431cc..4f0f503378822 100644 --- a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourceImplementor.java +++ b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/ResourceImplementor.java @@ -1,7 +1,5 @@ package io.quarkus.spring.data.rest.deployment; -import static io.quarkus.spring.data.rest.deployment.RepositoryMethodsImplementor.LIST_PAGED; - import jakarta.enterprise.context.ApplicationScoped; import org.jboss.logging.Logger; @@ -47,11 +45,6 @@ public String implement(ClassOutput classOutput, String resourceType, String ent methodsImplementor.implementUpdate(classCreator, resourceType, entityType); methodsImplementor.implementDelete(classCreator, resourceType); - boolean contains = classCreator.getExistingMethods().contains(LIST_PAGED); - // String string = classCreator.getExistingMethods().stream() - // .filter(methodDescriptor -> methodDescriptor.getName().equals(LIST_PAGED.getName())).findAny().get() - // .getDescriptor().toString(); - // LOGGER.infof("Method descriptor '%s'", string); classCreator.close(); LOGGER.tracef("Completed generation of '%s'", className); diff --git a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/SpringDataRestProcessor.java b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/SpringDataRestProcessor.java index 79489baaf0e4d..280aa259e5250 100644 --- a/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/SpringDataRestProcessor.java +++ b/extensions/spring-data-rest/deployment/src/main/java/io/quarkus/spring/data/rest/deployment/SpringDataRestProcessor.java @@ -104,48 +104,6 @@ unremovableBeansProducer, new RepositoryMethodsImplementor(index, entityClassHel repositoriesToImplement); } - // @BuildStep - // void registerCrudRepositories(CombinedIndexBuildItem indexBuildItem, Capabilities capabilities, - // BuildProducer implementationsProducer, - // BuildProducer restDataResourceProducer, - // BuildProducer resourcePropertiesProducer, - // BuildProducer unremovableBeansProducer) { - // IndexView index = indexBuildItem.getIndex(); - // - // implementResources(capabilities, implementationsProducer, restDataResourceProducer, resourcePropertiesProducer, - // unremovableBeansProducer, new CrudMethodsImplementor(index), new CrudPropertiesProvider(index), - // getRepositoriesToImplement(index, CRUD_REPOSITORY_INTERFACE, LIST_CRUD_REPOSITORY_INTERFACE, - // JPA_REPOSITORY_INTERFACE)); - // } - // - // @BuildStep - // void registerJpaRepositories(CombinedIndexBuildItem indexBuildItem, Capabilities capabilities, - // BuildProducer implementationsProducer, - // BuildProducer restDataResourceProducer, - // BuildProducer resourcePropertiesProducer, - // BuildProducer unremovableBeansProducer) { - // IndexView index = indexBuildItem.getIndex(); - // - // implementResources(capabilities, implementationsProducer, restDataResourceProducer, resourcePropertiesProducer, - // unremovableBeansProducer, new RepositoryMethodsImplementor(index), new RepositoryPropertiesProvider(index), - // getRepositoriesToImplement(index, JPA_REPOSITORY_INTERFACE)); - // } - - // @BuildStep - // void registerPagingAndSortingRepositories(CombinedIndexBuildItem indexBuildItem, Capabilities capabilities, - // BuildProducer implementationsProducer, - // BuildProducer restDataResourceProducer, - // BuildProducer resourcePropertiesProducer, - // BuildProducer unremovableBeansProducer) { - // IndexView index = indexBuildItem.getIndex(); - // - // implementResources(capabilities, implementationsProducer, restDataResourceProducer, resourcePropertiesProducer, - // unremovableBeansProducer, new PagingAndSortingMethodsImplementor(index), - // new PagingAndSortingPropertiesProvider(index), - // getRepositoriesToImplement(index, PAGING_AND_SORTING_REPOSITORY_INTERFACE, - // LIST_PAGING_AND_SORTING_REPOSITORY_INTERFACE, JPA_REPOSITORY_INTERFACE)); - // } - /** * Implement the {@link io.quarkus.rest.data.panache.RestDataResource} interface for each given Spring Data * repository and register its metadata and properties to be later picked up by the `rest-data-panache` extension. diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/DefaultListCrudResourceTest.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/DefaultListCrudResourceTest.java new file mode 100644 index 0000000000000..6fa6b2d265496 --- /dev/null +++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/DefaultListCrudResourceTest.java @@ -0,0 +1,259 @@ +package io.quarkus.spring.data.rest.crud; + +import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.is; + +import java.util.Arrays; +import java.util.List; + +import javax.inject.Inject; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.path.json.JsonPath; +import io.restassured.response.Response; + +class DefaultListCrudResourceTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(AbstractEntity.class, Record.class, DefaultRecordsListRepository.class) + .addAsResource("application.properties") + .addAsResource("import.sql")); + + @Inject + DefaultRecordsListRepository recordsListRepository; + + @Test + void givenDbContainsBooks_whenFindBooksByAuthor_thenReturnBooksByAuthor() { + + Record record3 = new Record(); + record3.setName("Third"); + Record record4 = new Record(); + record4.setName("Four"); + Record record5 = new Record(); + record5.setName("Five"); + + List list = Arrays.asList(record3, record4, record5); + + recordsListRepository.saveAll(list); + + } + + @Test + void shouldGet() { + given().accept("application/json") + .when().get("/default-records-list/1") + .then().statusCode(200) + .and().body("id", is(equalTo(1))) + .and().body("name", is(equalTo("first"))); + } + + @Test + void shouldNotGetNonExistent() { + given().accept("application/json") + .when().get("/default-records-list/1000") + .then().statusCode(404); + } + + @Test + @Disabled + void shouldGetHal() { + given().accept("application/hal+json") + .when().get("/default-records-list/1") + .then().statusCode(200) + .and().body("id", is(equalTo(1))) + .and().body("name", is(equalTo("first"))) + .and().body("_links.add.href", endsWith("/default-records-list")) + .and().body("_links.list.href", endsWith("/default-records-list")) + .and().body("_links.self.href", endsWith("/default-records-list/1")) + .and().body("_links.update.href", endsWith("/default-records-list/1")) + .and().body("_links.remove.href", endsWith("/default-records-list/1")); + } + + @Test + @Disabled + void shouldNotGetNonExistentHal() { + given().accept("application/hal+json") + .when().get("/default-records-list/1000") + .then().statusCode(404); + } + + @Test + void shouldList() { + given().accept("application/json") + .when().get("/default-records-list") + .then().statusCode(200) + .and().body("id", hasItems(1, 2)) + .and().body("name", hasItems("first", "second")); + } + + @Test + @Disabled + void shouldListHal() { + given().accept("application/hal+json") + .when().get("/default-records-list") + .then().statusCode(200) + .and().body("_embedded.default-records.id", hasItems(1, 2)) + .and().body("_embedded.default-records.name", hasItems("first", "second")) + .and() + .body("_embedded.default-records._links.add.href", + hasItems(endsWith("/default-records-list"), endsWith("/default-records-list"))) + .and() + .body("_embedded.default-records._links.list.href", + hasItems(endsWith("/default-records-list"), endsWith("/default-records-list"))) + .and() + .body("_embedded.default-records._links.self.href", + hasItems(endsWith("/default-records-list/1"), endsWith("/default-records-list/2"))) + .and() + .body("_embedded.default-records._links.update.href", + hasItems(endsWith("/default-records-list/1"), endsWith("/default-records-list/2"))) + .and() + .body("_embedded.default-records._links.remove.href", + hasItems(endsWith("/default-records-list/1"), endsWith("/default-records-list/2"))) + .and().body("_links.add.href", endsWith("/default-records-list")) + .and().body("_links.list.href", endsWith("/default-records-list")); + } + + @Test + void shouldCreate() { + Response response = given().accept("application/json") + .and().contentType("application/json") + .and().body("{\"name\": \"test-create\"}") + .when().post("/default-records-list") + .thenReturn(); + assertThat(response.statusCode()).isEqualTo(201); + + String location = response.header("Location"); + int id = Integer.parseInt(location.substring(response.header("Location").lastIndexOf("/") + 1)); + JsonPath body = response.body().jsonPath(); + assertThat(body.getInt("id")).isEqualTo(id); + assertThat(body.getString("name")).isEqualTo("test-create"); + + given().accept("application/json") + .when().get(location) + .then().statusCode(200) + .and().body("id", is(equalTo(id))) + .and().body("name", is(equalTo("test-create"))); + } + + @Test + @Disabled + void shouldCreateHal() { + Response response = given().accept("application/hal+json") + .and().contentType("application/json") + .and().body("{\"name\": \"test-create-hal\"}") + .when().post("/default-records-list") + .thenReturn(); + assertThat(response.statusCode()).isEqualTo(201); + + String location = response.header("Location"); + int id = Integer.parseInt(location.substring(response.header("Location").lastIndexOf("/") + 1)); + JsonPath body = response.body().jsonPath(); + assertThat(body.getInt("id")).isEqualTo(id); + assertThat(body.getString("name")).isEqualTo("test-create-hal"); + assertThat(body.getString("_links.add.href")).endsWith("/default-records-list"); + assertThat(body.getString("_links.list.href")).endsWith("/default-records-list"); + assertThat(body.getString("_links.self.href")).endsWith("/default-records-list/" + id); + assertThat(body.getString("_links.update.href")).endsWith("/default-records-list/" + id); + assertThat(body.getString("_links.remove.href")).endsWith("/default-records-list/" + id); + + given().accept("application/json") + .when().get(location) + .then().statusCode(200) + .and().body("id", is(equalTo(id))) + .and().body("name", is(equalTo("test-create-hal"))); + } + + @Test + void shouldCreateAndUpdate() { + Response createResponse = given().accept("application/json") + .and().contentType("application/json") + .and().body("{\"id\": \"101\", \"name\": \"test-update-create\"}") + .when().put("/default-records-list/101") + .thenReturn(); + assertThat(createResponse.statusCode()).isEqualTo(201); + + String location = createResponse.header("Location"); + int id = Integer.parseInt(location.substring(createResponse.header("Location").lastIndexOf("/") + 1)); + JsonPath body = createResponse.body().jsonPath(); + assertThat(body.getInt("id")).isEqualTo(id); + assertThat(body.getString("name")).isEqualTo("test-update-create"); + + given().accept("application/json") + .and().contentType("application/json") + .and().body("{\"id\": \"" + id + "\", \"name\": \"test-update\"}") + .when().put(location) + .then() + .statusCode(204); + given().accept("application/json") + .when().get(location) + .then().statusCode(200) + .and().body("id", is(equalTo(id))) + .and().body("name", is(equalTo("test-update"))); + } + + @Test + @Disabled + void shouldCreateAndUpdateHal() { + Response createResponse = given().accept("application/hal+json") + .and().contentType("application/json") + .and().body("{\"id\": \"102\", \"name\": \"test-update-create-hal\"}") + .when().put("/default-records-list/102") + .thenReturn(); + assertThat(createResponse.statusCode()).isEqualTo(201); + + String location = createResponse.header("Location"); + int id = Integer.parseInt(location.substring(createResponse.header("Location").lastIndexOf("/") + 1)); + JsonPath body = createResponse.body().jsonPath(); + assertThat(body.getInt("id")).isEqualTo(id); + assertThat(body.getString("name")).isEqualTo("test-update-create-hal"); + assertThat(body.getString("_links.add.href")).endsWith("/default-records-list"); + assertThat(body.getString("_links.list.href")).endsWith("/default-records-list"); + assertThat(body.getString("_links.self.href")).endsWith("/default-records-list/" + id); + assertThat(body.getString("_links.update.href")).endsWith("/default-records-list/" + id); + assertThat(body.getString("_links.remove.href")).endsWith("/default-records-list/" + id); + + given().accept("application/json") + .and().contentType("application/json") + .and().body("{\"id\": \"" + id + "\", \"name\": \"test-update-hal\"}") + .when().put(location) + .then() + .statusCode(204); + given().accept("application/json") + .when().get(location) + .then().statusCode(200) + .and().body("id", is(equalTo(id))) + .and().body("name", is(equalTo("test-update-hal"))); + } + + @Test + void shouldCreateAndDelete() { + Response createResponse = given().accept("application/json") + .and().contentType("application/json") + .and().body("{\"name\": \"test-delete\"}") + .when().post("/default-records-list") + .thenReturn(); + assertThat(createResponse.statusCode()).isEqualTo(201); + + String location = createResponse.header("Location"); + when().delete(location) + .then().statusCode(204); + when().get(location) + .then().statusCode(404); + } + + @Test + void shouldNotDeleteNonExistent() { + when().delete("/default-records-list/1000") + .then().statusCode(404); + } +} diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/DefaultRecordsListRepository.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/DefaultRecordsListRepository.java new file mode 100644 index 0000000000000..018701ec12cde --- /dev/null +++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/crud/DefaultRecordsListRepository.java @@ -0,0 +1,6 @@ +package io.quarkus.spring.data.rest.crud; + +import org.springframework.data.repository.ListCrudRepository; + +public interface DefaultRecordsListRepository extends ListCrudRepository { +} diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/CrudAndPagedRecordsRepository.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/CrudAndPagedRecordsRepository.java new file mode 100644 index 0000000000000..4febb3e2ecc67 --- /dev/null +++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/CrudAndPagedRecordsRepository.java @@ -0,0 +1,7 @@ +package io.quarkus.spring.data.rest.paged; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; + +public interface CrudAndPagedRecordsRepository extends PagingAndSortingRepository, CrudRepository { +} diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/CrudAndPagedResourceTest.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/CrudAndPagedResourceTest.java new file mode 100644 index 0000000000000..0b2c54fa4f13d --- /dev/null +++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/CrudAndPagedResourceTest.java @@ -0,0 +1,436 @@ +package io.quarkus.spring.data.rest.paged; + +import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.is; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import jakarta.ws.rs.core.Link; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.http.Header; +import io.restassured.http.Headers; +import io.restassured.path.json.JsonPath; +import io.restassured.response.Response; + +class CrudAndPagedResourceTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(AbstractEntity.class, Record.class, CrudAndPagedRecordsRepository.class) + .addAsResource("application.properties") + .addAsResource("import.sql")); + + @Test + void shouldGet() { + given().accept("application/json") + .when().get("/crud-and-paged-records/1") + .then().statusCode(200) + .and().body("id", is(equalTo(1))) + .and().body("name", is(equalTo("first"))); + } + + @Test + void shouldNotGetNonExistent() { + given().accept("application/json") + .when().get("/crud-and-paged-records/1000") + .then().statusCode(404); + } + + @Test + void shouldGetHal() { + given().accept("application/hal+json") + .when().get("/crud-and-paged-records/1") + .then().statusCode(200) + .and().body("id", is(equalTo(1))) + .and().body("name", is(equalTo("first"))) + .and().body("_links.add.href", endsWith("/crud-and-paged-records")) + .and().body("_links.list.href", endsWith("/crud-and-paged-records")) + .and().body("_links.self.href", endsWith("/crud-and-paged-records/1")) + .and().body("_links.update.href", endsWith("/crud-and-paged-records/1")) + .and().body("_links.remove.href", endsWith("/crud-and-paged-records/1")); + } + + @Test + void shouldNotGetNonExistentHal() { + given().accept("application/hal+json") + .when().get("/crud-and-paged-records/1000") + .then().statusCode(404); + } + + @Test + void shouldList() { + Response response = given().accept("application/json") + .when().get("/crud-and-paged-records") + .thenReturn(); + + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.body().jsonPath().getList("id")).contains(1, 2); + assertThat(response.body().jsonPath().getList("name")).contains("first", "second"); + + Map expectedLinks = new HashMap<>(2); + expectedLinks.put("first", "/crud-and-paged-records?page=0&size=20"); + expectedLinks.put("last", "/crud-and-paged-records?page=0&size=20"); + assertLinks(response.headers(), expectedLinks); + } + + @Test + void shouldListHal() { + given().accept("application/hal+json") + .when().get("/crud-and-paged-records") + .then().statusCode(200) + .and().body("_embedded.default-records.id", hasItems(1, 2)) + .and().body("_embedded.default-records.name", hasItems("first", "second")) + .and() + .body("_embedded.default-records._links.add.href", + hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records"))) + .and() + .body("_embedded.default-records._links.list.href", + hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records"))) + .and() + .body("_embedded.default-records._links.self.href", + hasItems(endsWith("/crud-and-paged-records/1"), endsWith("/crud-and-paged-records/2"))) + .and() + .body("_embedded.default-records._links.update.href", + hasItems(endsWith("/crud-and-paged-records/1"), endsWith("/crud-and-paged-records/2"))) + .and() + .body("_embedded.default-records._links.remove.href", + hasItems(endsWith("/crud-and-paged-records/1"), endsWith("/crud-and-paged-records/2"))) + .and().body("_links.add.href", endsWith("/crud-and-paged-records")) + .and().body("_links.list.href", endsWith("/crud-and-paged-records")) + .and().body("_links.first.href", endsWith("/crud-and-paged-records?page=0&size=20")) + .and().body("_links.last.href", endsWith("/crud-and-paged-records?page=0&size=20")); + } + + @Test + void shouldListFirstPage() { + Response initResponse = given().accept("application/json") + .when().get("/crud-and-paged-records") + .thenReturn(); + List ids = initResponse.body().jsonPath().getList("id"); + List names = initResponse.body().jsonPath().getList("name"); + int lastPage = ids.size() - 1; + + Response response = given().accept("application/json") + .and().queryParam("page", 0) + .and().queryParam("size", 1) + .when().get("/crud-and-paged-records") + .thenReturn(); + + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.body().jsonPath().getList("id")).containsOnly(ids.get(0)); + assertThat(response.body().jsonPath().getList("name")).containsOnly(names.get(0)); + + Map expectedLinks = new HashMap<>(3); + expectedLinks.put("first", "/crud-and-paged-records?page=0&size=1"); + expectedLinks.put("last", "/crud-and-paged-records?page=" + lastPage + "&size=1"); + expectedLinks.put("next", "/crud-and-paged-records?page=1&size=1"); + assertLinks(response.headers(), expectedLinks); + } + + @Test + void shouldListFirstPageHal() { + Response initResponse = given().accept("application/json") + .when().get("/crud-and-paged-records") + .thenReturn(); + List ids = initResponse.body().jsonPath().getList("id"); + List names = initResponse.body().jsonPath().getList("name"); + int lastPage = ids.size() - 1; + + given().accept("application/hal+json") + .and().queryParam("page", 0) + .and().queryParam("size", 1) + .when().get("/crud-and-paged-records") + .then().statusCode(200) + .and().body("_embedded.default-records.id", contains(ids.get(0))) + .and().body("_embedded.default-records.name", contains(names.get(0))) + .and() + .body("_embedded.default-records._links.add.href", + hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records"))) + .and() + .body("_embedded.default-records._links.list.href", + hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records"))) + .and() + .body("_embedded.default-records._links.self.href", + contains(endsWith("/crud-and-paged-records/" + ids.get(0)))) + .and() + .body("_embedded.default-records._links.update.href", + contains(endsWith("/crud-and-paged-records/" + ids.get(0)))) + .and() + .body("_embedded.default-records._links.remove.href", + contains(endsWith("/crud-and-paged-records/" + ids.get(0)))) + .and().body("_links.add.href", endsWith("/crud-and-paged-records")) + .and().body("_links.list.href", endsWith("/crud-and-paged-records")) + .and().body("_links.first.href", endsWith("/crud-and-paged-records?page=0&size=1")) + .and().body("_links.last.href", endsWith("/crud-and-paged-records?page=" + lastPage + "&size=1")) + .and().body("_links.next.href", endsWith("/crud-and-paged-records?page=1&size=1")); + } + + @Test + void shouldListLastPage() { + Response initResponse = given().accept("application/json") + .when().get("/crud-and-paged-records") + .thenReturn(); + List ids = initResponse.body().jsonPath().getList("id"); + List names = initResponse.body().jsonPath().getList("name"); + int lastPage = ids.size() - 1; + + Response response = given().accept("application/json") + .and().queryParam("page", lastPage) + .and().queryParam("size", 1) + .when().get("/crud-and-paged-records") + .thenReturn(); + + assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.body().jsonPath().getList("id")).containsOnly(ids.get(lastPage)); + assertThat(response.body().jsonPath().getList("name")).containsOnly(names.get(lastPage)); + + Map expectedLinks = new HashMap<>(3); + expectedLinks.put("first", "/crud-and-paged-records?page=0&size=1"); + expectedLinks.put("last", "/crud-and-paged-records?page=" + lastPage + "&size=1"); + expectedLinks.put("previous", "/crud-and-paged-records?page=" + (lastPage - 1) + "&size=1"); + assertLinks(response.headers(), expectedLinks); + } + + @Test + void shouldListLastPageHal() { + Response initResponse = given().accept("application/json") + .when().get("/crud-and-paged-records") + .thenReturn(); + List ids = initResponse.body().jsonPath().getList("id"); + List names = initResponse.body().jsonPath().getList("name"); + int lastPage = ids.size() - 1; + + given().accept("application/hal+json") + .and().queryParam("page", lastPage) + .and().queryParam("size", 1) + .when().get("/crud-and-paged-records") + .then().statusCode(200) + .and().body("_embedded.default-records.id", contains(ids.get(lastPage))) + .and().body("_embedded.default-records.name", contains(names.get(lastPage))) + .and() + .body("_embedded.default-records._links.add.href", + hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records"))) + .and() + .body("_embedded.default-records._links.list.href", + hasItems(endsWith("/crud-and-paged-records"), endsWith("/crud-and-paged-records"))) + .and() + .body("_embedded.default-records._links.self.href", + contains(endsWith("/crud-and-paged-records/" + ids.get(lastPage)))) + .and() + .body("_embedded.default-records._links.update.href", + contains(endsWith("/crud-and-paged-records/" + ids.get(lastPage)))) + .and() + .body("_embedded.default-records._links.remove.href", + contains(endsWith("/crud-and-paged-records/" + ids.get(lastPage)))) + .and().body("_links.add.href", endsWith("/crud-and-paged-records")) + .and().body("_links.list.href", endsWith("/crud-and-paged-records")) + .and().body("_links.first.href", endsWith("/crud-and-paged-records?page=0&size=1")) + .and().body("_links.last.href", endsWith("/crud-and-paged-records?page=" + lastPage + "&size=1")) + .and().body("_links.previous.href", endsWith("/crud-and-paged-records?page=" + (lastPage - 1) + "&size=1")); + } + + @Test + void shouldNotGetNonExistentPage() { + given().accept("application/json") + .and().queryParam("page", 100) + .when().get("/crud-and-paged-records") + .then().statusCode(200) + .and().body("id", is(empty())); + } + + @Test + void shouldNotGetNegativePageOrSize() { + given().accept("application/json") + .and().queryParam("page", -1) + .and().queryParam("size", -1) + .when().get("/crud-and-paged-records") + .then().statusCode(200) + // Invalid page and size parameters are replaced with defaults + .and().body("id", hasItems(1, 2)); + } + + @Test + void shouldListAscending() { + Response response = given().accept("application/json") + .when().get("/crud-and-paged-records?sort=name,id") + .thenReturn(); + + List actualNames = response.body().jsonPath().getList("name"); + List expectedNames = new LinkedList<>(actualNames); + expectedNames.sort(Comparator.naturalOrder()); + assertThat(actualNames).isEqualTo(expectedNames); + } + + @Test + void shouldListDescending() { + Response response = given().accept("application/json") + .when().get("/crud-and-paged-records?sort=-name,id") + .thenReturn(); + + List actualNames = response.body().jsonPath().getList("name"); + List expectedNames = new LinkedList<>(actualNames); + expectedNames.sort(Comparator.reverseOrder()); + assertThat(actualNames).isEqualTo(expectedNames); + } + + @Test + void shouldCreate() { + Response response = given().accept("application/json") + .and().contentType("application/json") + .and().body("{\"name\": \"test-create\"}") + .when().post("/crud-and-paged-records") + .thenReturn(); + assertThat(response.statusCode()).isEqualTo(201); + + String location = response.header("Location"); + int id = Integer.parseInt(location.substring(response.header("Location").lastIndexOf("/") + 1)); + JsonPath body = response.body().jsonPath(); + assertThat(body.getInt("id")).isEqualTo(id); + assertThat(body.getString("name")).isEqualTo("test-create"); + + given().accept("application/json") + .when().get(location) + .then().statusCode(200) + .and().body("id", is(equalTo(id))) + .and().body("name", is(equalTo("test-create"))); + } + + @Test + void shouldCreateHal() { + Response response = given().accept("application/hal+json") + .and().contentType("application/json") + .and().body("{\"name\": \"test-create-hal\"}") + .when().post("/crud-and-paged-records") + .thenReturn(); + assertThat(response.statusCode()).isEqualTo(201); + + String location = response.header("Location"); + int id = Integer.parseInt(location.substring(response.header("Location").lastIndexOf("/") + 1)); + JsonPath body = response.body().jsonPath(); + assertThat(body.getInt("id")).isEqualTo(id); + assertThat(body.getString("name")).isEqualTo("test-create-hal"); + assertThat(body.getString("_links.add.href")).endsWith("/crud-and-paged-records"); + assertThat(body.getString("_links.list.href")).endsWith("/crud-and-paged-records"); + assertThat(body.getString("_links.self.href")).endsWith("/crud-and-paged-records/" + id); + assertThat(body.getString("_links.update.href")).endsWith("/crud-and-paged-records/" + id); + assertThat(body.getString("_links.remove.href")).endsWith("/crud-and-paged-records/" + id); + + given().accept("application/json") + .when().get(location) + .then().statusCode(200) + .and().body("id", is(equalTo(id))) + .and().body("name", is(equalTo("test-create-hal"))); + } + + @Test + void shouldCreateAndUpdate() { + Response createResponse = given().accept("application/json") + .and().contentType("application/json") + .and().body("{\"id\": \"101\", \"name\": \"test-update-create\"}") + .when().put("/crud-and-paged-records/101") + .thenReturn(); + assertThat(createResponse.statusCode()).isEqualTo(201); + + String location = createResponse.header("Location"); + int id = Integer.parseInt(location.substring(createResponse.header("Location").lastIndexOf("/") + 1)); + JsonPath body = createResponse.body().jsonPath(); + assertThat(body.getInt("id")).isEqualTo(id); + assertThat(body.getString("name")).isEqualTo("test-update-create"); + + given().accept("application/json") + .and().contentType("application/json") + .and().body("{\"id\": \"" + id + "\", \"name\": \"test-update\"}") + .when().put(location) + .then() + .statusCode(204); + given().accept("application/json") + .when().get(location) + .then().statusCode(200) + .and().body("id", is(equalTo(id))) + .and().body("name", is(equalTo("test-update"))); + } + + @Test + void shouldCreateAndUpdateHal() { + Response createResponse = given().accept("application/hal+json") + .and().contentType("application/json") + .and().body("{\"id\": \"102\", \"name\": \"test-update-create-hal\"}") + .when().put("/crud-and-paged-records/102") + .thenReturn(); + assertThat(createResponse.statusCode()).isEqualTo(201); + + String location = createResponse.header("Location"); + int id = Integer.parseInt(location.substring(createResponse.header("Location").lastIndexOf("/") + 1)); + JsonPath body = createResponse.body().jsonPath(); + assertThat(body.getInt("id")).isEqualTo(id); + assertThat(body.getString("name")).isEqualTo("test-update-create-hal"); + assertThat(body.getString("_links.add.href")).endsWith("/crud-and-paged-records"); + assertThat(body.getString("_links.list.href")).endsWith("/crud-and-paged-records"); + assertThat(body.getString("_links.self.href")).endsWith("/crud-and-paged-records/" + id); + assertThat(body.getString("_links.update.href")).endsWith("/crud-and-paged-records/" + id); + assertThat(body.getString("_links.remove.href")).endsWith("/crud-and-paged-records/" + id); + + given().accept("application/json") + .and().contentType("application/json") + .and().body("{\"id\": \"" + id + "\", \"name\": \"test-update-hal\"}") + .when().put(location) + .then() + .statusCode(204); + given().accept("application/json") + .when().get(location) + .then().statusCode(200) + .and().body("id", is(equalTo(id))) + .and().body("name", is(equalTo("test-update-hal"))); + } + + @Test + void shouldCreateAndDelete() { + Response createResponse = given().accept("application/json") + .and().contentType("application/json") + .and().body("{\"name\": \"test-delete\"}") + .when().post("/crud-and-paged-records") + .thenReturn(); + assertThat(createResponse.statusCode()).isEqualTo(201); + + String location = createResponse.header("Location"); + when().delete(location) + .then().statusCode(204); + when().get(location) + .then().statusCode(404); + } + + @Test + void shouldNotDeleteNonExistent() { + when().delete("/crud-and-paged-records/1000") + .then().statusCode(404); + } + + private void assertLinks(Headers headers, Map expectedLinks) { + List links = new LinkedList<>(); + for (Header header : headers.getList("Link")) { + links.add(Link.valueOf(header.getValue())); + } + assertThat(links).hasSize(expectedLinks.size()); + for (Map.Entry expectedLink : expectedLinks.entrySet()) { + assertThat(links).anySatisfy(link -> { + assertThat(link.getUri().toString()).endsWith(expectedLink.getValue()); + assertThat(link.getRel()).isEqualTo(expectedLink.getKey()); + }); + } + } +} diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceBisTest.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceBisTest.java new file mode 100644 index 0000000000000..2fcd9f4463d31 --- /dev/null +++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceBisTest.java @@ -0,0 +1,53 @@ +package io.quarkus.spring.data.rest.paged; + +import static io.restassured.RestAssured.given; +import static io.restassured.RestAssured.when; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.is; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import jakarta.ws.rs.core.Link; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.http.Header; +import io.restassured.http.Headers; + +class DefaultPagedResourceBisTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(AbstractEntity.class, Record.class, CrudAndPagedRecordsRepository.class) + .addAsResource("application.properties") + .addAsResource("import.sql")); + + @Test + void shouldNotGetNonExistentPage() { + given().accept("application/json") + .and().queryParam("page", 100) + .when().get("/crud-and-paged-records") + .then().statusCode(200) + .and().body("id", is(empty())); + } + + private void assertLinks(Headers headers, Map expectedLinks) { + List links = new LinkedList<>(); + for (Header header : headers.getList("Link")) { + links.add(Link.valueOf(header.getValue())); + } + assertThat(links).hasSize(expectedLinks.size()); + for (Map.Entry expectedLink : expectedLinks.entrySet()) { + assertThat(links).anySatisfy(link -> { + assertThat(link.getUri().toString()).endsWith(expectedLink.getValue()); + assertThat(link.getRel()).isEqualTo(expectedLink.getKey()); + }); + } + } +} diff --git a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceTest.java b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceTest.java index 315990fe619a4..856939ed885dd 100644 --- a/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceTest.java +++ b/extensions/spring-data-rest/deployment/src/test/java/io/quarkus/spring/data/rest/paged/DefaultPagedResourceTest.java @@ -6,7 +6,6 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.endsWith; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.is; @@ -18,14 +17,12 @@ import jakarta.ws.rs.core.Link; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.test.QuarkusUnitTest; import io.restassured.http.Header; import io.restassured.http.Headers; -import io.restassured.path.json.JsonPath; import io.restassured.response.Response; class DefaultPagedResourceTest { @@ -36,47 +33,6 @@ class DefaultPagedResourceTest { .addAsResource("application.properties") .addAsResource("import.sql")); - @Test - @Disabled - void shouldGet() { - given().accept("application/json") - .when().get("/default-records/1") - .then().statusCode(200) - .and().body("id", is(equalTo(1))) - .and().body("name", is(equalTo("first"))); - } - - @Test - @Disabled - void shouldNotGetNonExistent() { - given().accept("application/json") - .when().get("/default-records/1000") - .then().statusCode(404); - } - - @Test - @Disabled - void shouldGetHal() { - given().accept("application/hal+json") - .when().get("/default-records/1") - .then().statusCode(200) - .and().body("id", is(equalTo(1))) - .and().body("name", is(equalTo("first"))) - .and().body("_links.add.href", endsWith("/default-records")) - .and().body("_links.list.href", endsWith("/default-records")) - .and().body("_links.self.href", endsWith("/default-records/1")) - .and().body("_links.update.href", endsWith("/default-records/1")) - .and().body("_links.remove.href", endsWith("/default-records/1")); - } - - @Test - @Disabled - void shouldNotGetNonExistentHal() { - given().accept("application/hal+json") - .when().get("/default-records/1000") - .then().statusCode(404); - } - @Test void shouldList() { Response response = given().accept("application/json") @@ -293,144 +249,6 @@ void shouldListDescending() { assertThat(actualNames).isEqualTo(expectedNames); } - @Test - @Disabled - void shouldCreate() { - Response response = given().accept("application/json") - .and().contentType("application/json") - .and().body("{\"name\": \"test-create\"}") - .when().post("/default-records") - .thenReturn(); - assertThat(response.statusCode()).isEqualTo(201); - - String location = response.header("Location"); - int id = Integer.parseInt(location.substring(response.header("Location").lastIndexOf("/") + 1)); - JsonPath body = response.body().jsonPath(); - assertThat(body.getInt("id")).isEqualTo(id); - assertThat(body.getString("name")).isEqualTo("test-create"); - - given().accept("application/json") - .when().get(location) - .then().statusCode(200) - .and().body("id", is(equalTo(id))) - .and().body("name", is(equalTo("test-create"))); - } - - @Test - @Disabled - void shouldCreateHal() { - Response response = given().accept("application/hal+json") - .and().contentType("application/json") - .and().body("{\"name\": \"test-create-hal\"}") - .when().post("/default-records") - .thenReturn(); - assertThat(response.statusCode()).isEqualTo(201); - - String location = response.header("Location"); - int id = Integer.parseInt(location.substring(response.header("Location").lastIndexOf("/") + 1)); - JsonPath body = response.body().jsonPath(); - assertThat(body.getInt("id")).isEqualTo(id); - assertThat(body.getString("name")).isEqualTo("test-create-hal"); - assertThat(body.getString("_links.add.href")).endsWith("/default-records"); - assertThat(body.getString("_links.list.href")).endsWith("/default-records"); - assertThat(body.getString("_links.self.href")).endsWith("/default-records/" + id); - assertThat(body.getString("_links.update.href")).endsWith("/default-records/" + id); - assertThat(body.getString("_links.remove.href")).endsWith("/default-records/" + id); - - given().accept("application/json") - .when().get(location) - .then().statusCode(200) - .and().body("id", is(equalTo(id))) - .and().body("name", is(equalTo("test-create-hal"))); - } - - @Test - @Disabled - void shouldCreateAndUpdate() { - Response createResponse = given().accept("application/json") - .and().contentType("application/json") - .and().body("{\"id\": \"101\", \"name\": \"test-update-create\"}") - .when().put("/default-records/101") - .thenReturn(); - assertThat(createResponse.statusCode()).isEqualTo(201); - - String location = createResponse.header("Location"); - int id = Integer.parseInt(location.substring(createResponse.header("Location").lastIndexOf("/") + 1)); - JsonPath body = createResponse.body().jsonPath(); - assertThat(body.getInt("id")).isEqualTo(id); - assertThat(body.getString("name")).isEqualTo("test-update-create"); - - given().accept("application/json") - .and().contentType("application/json") - .and().body("{\"id\": \"" + id + "\", \"name\": \"test-update\"}") - .when().put(location) - .then() - .statusCode(204); - given().accept("application/json") - .when().get(location) - .then().statusCode(200) - .and().body("id", is(equalTo(id))) - .and().body("name", is(equalTo("test-update"))); - } - - @Test - @Disabled - void shouldCreateAndUpdateHal() { - Response createResponse = given().accept("application/hal+json") - .and().contentType("application/json") - .and().body("{\"id\": \"102\", \"name\": \"test-update-create-hal\"}") - .when().put("/default-records/102") - .thenReturn(); - assertThat(createResponse.statusCode()).isEqualTo(201); - - String location = createResponse.header("Location"); - int id = Integer.parseInt(location.substring(createResponse.header("Location").lastIndexOf("/") + 1)); - JsonPath body = createResponse.body().jsonPath(); - assertThat(body.getInt("id")).isEqualTo(id); - assertThat(body.getString("name")).isEqualTo("test-update-create-hal"); - assertThat(body.getString("_links.add.href")).endsWith("/default-records"); - assertThat(body.getString("_links.list.href")).endsWith("/default-records"); - assertThat(body.getString("_links.self.href")).endsWith("/default-records/" + id); - assertThat(body.getString("_links.update.href")).endsWith("/default-records/" + id); - assertThat(body.getString("_links.remove.href")).endsWith("/default-records/" + id); - - given().accept("application/json") - .and().contentType("application/json") - .and().body("{\"id\": \"" + id + "\", \"name\": \"test-update-hal\"}") - .when().put(location) - .then() - .statusCode(204); - given().accept("application/json") - .when().get(location) - .then().statusCode(200) - .and().body("id", is(equalTo(id))) - .and().body("name", is(equalTo("test-update-hal"))); - } - - @Test - @Disabled - void shouldCreateAndDelete() { - Response createResponse = given().accept("application/json") - .and().contentType("application/json") - .and().body("{\"name\": \"test-delete\"}") - .when().post("/default-records") - .thenReturn(); - assertThat(createResponse.statusCode()).isEqualTo(201); - - String location = createResponse.header("Location"); - when().delete(location) - .then().statusCode(204); - when().get(location) - .then().statusCode(404); - } - - @Test - @Disabled - void shouldNotDeleteNonExistent() { - when().delete("/default-records/1000") - .then().statusCode(404); - } - private void assertLinks(Headers headers, Map expectedLinks) { List links = new LinkedList<>(); for (Header header : headers.getList("Link")) {