/**
* Copyright (C) 2010-2017 Structr GmbH
*
* This file is part of Structr <http://structr.org>.
*
* Structr is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Structr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Structr. If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.rest.test;
import com.jayway.restassured.RestAssured;
import com.jayway.restassured.filter.log.ResponseLoggingFilter;
import java.util.LinkedList;
import java.util.List;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.structr.common.error.FrameworkException;
import org.structr.core.app.StructrApp;
import org.structr.core.graph.Tx;
import org.structr.rest.common.StructrRestTest;
import org.structr.rest.entity.TestOne;
import org.structr.rest.entity.TestUser;
/**
*
*
*/
public class AdvancedPagingTest extends StructrRestTest {
private static final Logger logger = LoggerFactory.getLogger(AdvancedPagingTest.class.getName());
@Test
public void test01Paging() {
// create a root object
String resource = "/test_twos";
String location = RestAssured.given().contentType("application/json; charset=UTF-8")
.body(" { 'name' : 'TestTwo-0', 'anInt' : 0, 'aLong' : 0, 'aDate' : '2012-09-18T00:33:12+0200' } ")
.expect().statusCode(201).when().post(resource).getHeader("Location");
String baseId = getUuidFromLocation(location);
resource = resource.concat("/").concat(baseId).concat("/test_ones");
String offsetId = null;
// create sub objects
for (int i=0; i<10; i++) {
location = RestAssured.given().contentType("application/json; charset=UTF-8")
.body(" { 'name' : 'TestOne-" + i + "', 'anInt' : " + i + ", 'aLong' : " + i + ", 'aDate' : '2012-09-18T0" + i + ":33:12+0200' } ")
.expect().statusCode(201).when().post(resource).getHeader("Location");
String id = getUuidFromLocation(location);
if (i == 3) {
offsetId = id;
}
System.out.println("Object created: " + id);
}
resource = "/test_ones";
for (int page=1; page<5; page++) {
String url = resource + "?sort=name&pageSize=2&page=" + page;
System.out.println("Testing page " + page + " with URL " + url);
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.expect()
.statusCode(200)
.body("result", hasSize(2))
.body("result_count", equalTo(10))
.body("result[0]", isEntity(TestOne.class))
.body("result[0].name ", equalTo("TestOne-" + ((2*page)-2)))
.body("result[1]", isEntity(TestOne.class))
.body("result[1].name ", equalTo("TestOne-" + ((2*page)-1)))
.when()
.get(url);
}
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.expect()
.statusCode(200)
.body("result", hasSize(2))
.body("result_count", equalTo(10))
.body("result[0]", isEntity(TestOne.class))
.body("result[0].name ", equalTo("TestOne-3"))
.body("result[1]", isEntity(TestOne.class))
.body("result[1].name ", equalTo("TestOne-4"))
.when()
.get(resource + "?sort=name&pageSize=2&page=1&pageStartId=" + offsetId);
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.expect()
.statusCode(200)
.body("result", hasSize(2))
.body("result_count", equalTo(10))
.body("result[0]", isEntity(TestOne.class))
.body("result[0].name ", equalTo("TestOne-1"))
.body("result[1]", isEntity(TestOne.class))
.body("result[1].name ", equalTo("TestOne-2"))
.when()
.get(resource + "?sort=name&pageSize=2&page=-1&pageStartId=" + offsetId);
// with empty pageSize: Should start at offset element (index 3) and return 0, 1, 2
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.expect()
.statusCode(200)
.body("result", hasSize(3))
.body("result_count", equalTo(10))
.body("result[0]", isEntity(TestOne.class))
.body("result[0].name ", equalTo("TestOne-0"))
.body("result[1]", isEntity(TestOne.class))
.body("result[1].name ", equalTo("TestOne-1"))
.body("result[2]", isEntity(TestOne.class))
.body("result[2].name ", equalTo("TestOne-2"))
.when()
.get(resource + "?sort=name&page=-1&pageStartId=" + offsetId);
}
/**
* Paging of subresources
*/
@Test
public void test02PagingOfSubresources() {
// create a root object
String resource = "/test_twos";
String location = RestAssured.given().contentType("application/json; charset=UTF-8")
.body(" { 'name' : 'TestTwo-0', 'anInt' : 0, 'aLong' : 0, 'aDate' : '2012-09-18T00:33:12+0200' } ")
.expect().statusCode(201).when().post(resource).getHeader("Location");
String baseId = getUuidFromLocation(location);
resource = resource.concat("/").concat(baseId).concat("/test_ones");
String offsetId = null;
// create sub objects
for (int i=0; i<10; i++) {
location = RestAssured.given().contentType("application/json; charset=UTF-8")
.body(" { 'name' : 'TestOne-" + i + "', 'anInt' : " + i + ", 'aLong' : " + i + ", 'aDate' : '2012-09-18T0" + i + ":33:12+0200' } ")
.expect().statusCode(201).when().post(resource).getHeader("Location");
String id = getUuidFromLocation(location);
if (i == 3) {
offsetId = id;
}
}
resource = "/test_twos/" + baseId + "/test_ones";
for (int page=1; page<5; page++) {
String url = resource + "?sort=name&pageSize=2&page=" + page;
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.expect()
.statusCode(200)
.body("result", hasSize(2))
.body("result_count", equalTo(10))
.body("result[0]", isEntity(TestOne.class))
.body("result[0].name ", equalTo("TestOne-" + ((2*page)-2)))
.body("result[1]", isEntity(TestOne.class))
.body("result[1].name ", equalTo("TestOne-" + ((2*page)-1)))
.when()
.get(url);
}
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.expect()
.statusCode(200)
.body("result", hasSize(2))
.body("result_count", equalTo(10))
.body("result[0]", isEntity(TestOne.class))
.body("result[0].name ", equalTo("TestOne-3"))
.body("result[1]", isEntity(TestOne.class))
.body("result[1].name ", equalTo("TestOne-4"))
.when()
.get(resource + "?sort=name&pageSize=2&page=1&pageStartId=" + offsetId);
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.expect()
.statusCode(200)
.body("result", hasSize(2))
.body("result_count", equalTo(10))
.body("result[0]", isEntity(TestOne.class))
.body("result[0].name ", equalTo("TestOne-1"))
.body("result[1]", isEntity(TestOne.class))
.body("result[1].name ", equalTo("TestOne-2"))
.when()
.get(resource + "?sort=name&pageSize=2&page=-1&pageStartId=" + offsetId);
// with empty pageSize: Should start at offset element (index 3) and return 0, 1, 2
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.expect()
.statusCode(200)
.body("result", hasSize(3))
.body("result_count", equalTo(10))
.body("result[0]", isEntity(TestOne.class))
.body("result[0].name ", equalTo("TestOne-0"))
.body("result[1]", isEntity(TestOne.class))
.body("result[1].name ", equalTo("TestOne-1"))
.body("result[2]", isEntity(TestOne.class))
.body("result[2].name ", equalTo("TestOne-2"))
.when()
.get(resource + "?sort=name&page=-1&pageStartId=" + offsetId);
}
@Test
public void test03RangeHeader() {
// create a root object
final List<String> testOneIDs = new LinkedList<>();
String resource = "/test_twos";
String location = RestAssured.given().contentType("application/json; charset=UTF-8")
.body(" { 'name' : 'TestTwo-0', 'anInt' : 0, 'aLong' : 0, 'aDate' : '2012-09-18T00:33:12+0200' } ")
.expect().statusCode(201).when().post(resource).getHeader("Location");
String baseId = getUuidFromLocation(location);
resource = resource.concat("/").concat(baseId).concat("/test_ones");
// create sub objects
for (int i=0; i<20; i++) {
final String subLocation = RestAssured.given().contentType("application/json; charset=UTF-8")
.body(" { 'name' : 'TestOne-" + i + "', 'anInt' : " + i + ", 'aLong' : " + i + ", 'aDate' : '2012-09-18T0" + i + ":33:12+0200' } ")
.expect().statusCode(201).when().post(resource).getHeader("Location");
testOneIDs.add(getUuidFromLocation(subLocation));
}
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.header("Range", "test_ones=0-3")
.filter(ResponseLoggingFilter.logResponseIfStatusCodeIs(200))
.expect()
.statusCode(200)
.body("result", hasSize(1))
.body("result_count", equalTo(1))
.body("result[0]", isEntity(TestOne.class))
.body("result[0].test_ones", hasSize(3))
.body("result[0].test_ones[0].id", equalTo(testOneIDs.get(0)))
.body("result[0].test_ones[1].id", equalTo(testOneIDs.get(1)))
.body("result[0].test_ones[2].id", equalTo(testOneIDs.get(2)))
.when()
.get("/test_twos");
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.header("Range", "test_ones=3-6")
.filter(ResponseLoggingFilter.logResponseIfStatusCodeIs(200))
.expect()
.statusCode(200)
.body("result", hasSize(1))
.body("result_count", equalTo(1))
.body("result[0]", isEntity(TestOne.class))
.body("result[0].test_ones", hasSize(3))
.body("result[0].test_ones[0].id", equalTo(testOneIDs.get(3)))
.body("result[0].test_ones[1].id", equalTo(testOneIDs.get(4)))
.body("result[0].test_ones[2].id", equalTo(testOneIDs.get(5)))
.when()
.get("/test_twos");
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.header("Range", "test_ones=10-20")
.filter(ResponseLoggingFilter.logResponseIfStatusCodeIs(200))
.expect()
.statusCode(200)
.body("result", hasSize(1))
.body("result_count", equalTo(1))
.body("result[0]", isEntity(TestOne.class))
.body("result[0].test_ones", hasSize(10))
.body("result[0].test_ones[0].id", equalTo(testOneIDs.get(10)))
.body("result[0].test_ones[1].id", equalTo(testOneIDs.get(11)))
.body("result[0].test_ones[2].id", equalTo(testOneIDs.get(12)))
.body("result[0].test_ones[3].id", equalTo(testOneIDs.get(13)))
.body("result[0].test_ones[4].id", equalTo(testOneIDs.get(14)))
.body("result[0].test_ones[5].id", equalTo(testOneIDs.get(15)))
.body("result[0].test_ones[6].id", equalTo(testOneIDs.get(16)))
.body("result[0].test_ones[7].id", equalTo(testOneIDs.get(17)))
.body("result[0].test_ones[8].id", equalTo(testOneIDs.get(18)))
.body("result[0].test_ones[9].id", equalTo(testOneIDs.get(19)))
.when()
.get("/test_twos");
}
/* moved from AccessControlTest to improve performance */
@Test
public void test01PagingWithDeletedNodes() {
List<TestOne> testOnes = new LinkedList<>();
// Create two User and ten TestOne nodes
try (final Tx tx = StructrApp.getInstance().tx()) {
createEntityAsSuperUser("/resource_access", "{'signature': 'TestOne', 'flags': 4095}");
List<TestUser> users = createTestNodes(TestUser.class, 2);
users.get(0).setProperty(TestUser.name, "user1");
users.get(0).setProperty(TestUser.password, "user1");
users.get(1).setProperty(TestUser.name, "user2");
users.get(1).setProperty(TestUser.password, "user2");
users.get(1).setProperty(TestUser.isAdmin, true);
testOnes = createTestNodes(TestOne.class, 3);
int i=0;
// First test user is owner
for (TestOne t: testOnes) {
i++;
t.setProperty(TestOne.name, "t-one-" + i);
t.setProperty(TestOne.owner, users.get(0));
t.setProperty(TestOne.visibleToAuthenticatedUsers, true);
}
tx.success();
} catch (FrameworkException ex) {
logger.warn("", ex);
fail(ex.getMessage());
}
// Check as user1 with pageSize=1
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.filter(ResponseLoggingFilter.logResponseIfStatusCodeIs(200))
.header("X-User", "user1")
.header("X-Password", "user1")
.expect()
.statusCode(200)
.body("result", hasSize(1))
.body("result_count", equalTo(3))
.when()
.get("/test_ones?pageSize=1&page=1");
// Check as user2 with pageSize=1
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.filter(ResponseLoggingFilter.logResponseIfStatusCodeIs(200))
.header("X-User", "user2")
.header("X-Password", "user2")
.expect()
.statusCode(200)
.body("result", hasSize(1))
.body("result_count", equalTo(3))
.when()
.get("/test_ones?pageSize=1&page=1");
try (final Tx tx = StructrApp.getInstance().tx()) {
// "soft delete" first node
testOnes.get(0).setProperty(TestOne.name, "deleted");
testOnes.get(0).setProperty(TestOne.deleted, true);
//testOnes.get(0).setProperty(TestOne.hidden, true);
tx.success();
} catch (FrameworkException ex) {
logger.warn("", ex);
fail(ex.getMessage());
}
// Check as user1 with pageSize=1
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.filter(ResponseLoggingFilter.logResponseIfStatusCodeIs(200))
.header("X-User", "user2")
.header("X-Password", "user2")
.expect()
.statusCode(200)
.body("result", hasSize(1))
.body("result_count", equalTo(3))
.when()
.get("/test_ones?sort=name&pageSize=1&page=1");
}
/**
* Paging with soft-deleted nodes
*/
@Test
public void test02PagingWithSoftDeletedNodes() {
List<TestOne> testOnes = null;
// Create two User and ten TestOne nodes
try (final Tx tx = StructrApp.getInstance().tx()) {
createEntityAsSuperUser("/resource_access", "{'signature': 'TestOne', 'flags': 4095}");
List<TestUser> users = createTestNodes(TestUser.class, 2);
users.get(0).setProperty(TestUser.name, "user1");
users.get(0).setProperty(TestUser.password, "user1");
users.get(1).setProperty(TestUser.name, "user2");
users.get(1).setProperty(TestUser.password, "user2");
users.get(1).setProperty(TestUser.isAdmin, true);
testOnes = createTestNodes(TestOne.class, 3);
int i=0;
// First test user is owner
for (TestOne t: testOnes) {
i++;
t.setProperty(TestOne.name, "t-one-" + i);
t.setProperty(TestOne.owner, users.get(0));
t.setProperty(TestOne.visibleToAuthenticatedUsers, true);
}
// "soft delete" first node
testOnes.get(0).setProperty(TestOne.name, "deleted");
testOnes.get(0).setProperty(TestOne.deleted, true);
//testOnes.get(0).setProperty(TestOne.hidden, true);
tx.success();
} catch (FrameworkException ex) {
logger.warn("", ex);
fail(ex.getMessage());
}
// Check as user1 with pageSize=1
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.filter(ResponseLoggingFilter.logResponseIfStatusCodeIs(200))
.header("X-User", "user2")
.header("X-Password", "user2")
.expect()
.statusCode(200)
.body("result", hasSize(1))
.body("result_count", equalTo(3))
.when()
.get("/test_ones?sort=name&pageSize=1&page=1");
// Check as user1 with pageSize=1
RestAssured
.given()
.contentType("application/json; charset=UTF-8")
.filter(ResponseLoggingFilter.logResponseIfStatusCodeIs(200))
.header("X-User", "user2")
.header("X-Password", "user2")
.expect()
.statusCode(200)
.body("result", hasSize(1))
.body("result_count", equalTo(2))
.when()
.get("/test_ones?deleted=false&sort=name&pageSize=1&page=1");
}
}