/* * ------------------------------------------------------------------------------------------------ * Copyright 2014 by Swiss Post, Information Technology Services * ------------------------------------------------------------------------------------------------ * $Id$ * ------------------------------------------------------------------------------------------------ */ package org.swisspush.reststorage; import com.jayway.restassured.RestAssured; import com.jayway.restassured.http.ContentType; import com.jayway.restassured.response.Response; import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.VertxUnitRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import static com.jayway.restassured.RestAssured.*; import static org.hamcrest.Matchers.*; @RunWith(VertxUnitRunner.class) public class StorageExpandTest extends AbstractTestCase { final String ETAG_HEADER = "Etag"; final String IF_NONE_MATCH_HEADER = "if-none-match"; final String POST_STORAGE_EXP = "/server/resources?storageExpand=true"; final int BAD_REQUEST = 400; final String BAD_REQUEST_PARSE_MSG = "Bad Request: Unable to parse body of storageExpand POST request"; final String COMPRESS_HEADER = "x-stored-compressed"; @Before public void setPath() { RestAssured.basePath = "/server/resources"; delete("/"); } @Test public void testPOSTWithoutExpandParam(TestContext context) { Async async = context.async(); given() .body("{ \"foo\": \"bar1\" }") .when() .post("/server/resources") .then() .assertThat().statusCode(405); async.complete(); } @Test public void testPOSTWithWrongBody(TestContext context) { Async async = context.async(); given() .body("{ \"foo\": \"bar1\" }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(BAD_REQUEST) .assertThat().body(equalTo("Bad Request: Expected array field 'subResources' with names of resources")); given() .body("{ \"foo\": bar1 }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(BAD_REQUEST) .assertThat().body(equalTo(BAD_REQUEST_PARSE_MSG)); given() .body("{ \"subResources\": [\"res1\", \"res2\", 123] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(BAD_REQUEST) .assertThat().body(equalTo(BAD_REQUEST_PARSE_MSG)); given() .body("\"foo\": \"bar1\"") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(BAD_REQUEST) .assertThat().body(equalTo(BAD_REQUEST_PARSE_MSG)); async.complete(); } @Test public void testWithInvalidResource(TestContext context) { Async async = context.async(); delete("/server/resources"); with().body("{ \"foo\": \"bar1\" }").put("/server/resources/res1"); // invalid with().body("{ \"foo\"}").put("/server/resources/res2"); with().body("{ \"foo\": \"bar3\" }").put("/server/resources/res3"); given() .body("{ \"subResources\": [\"res1\", \"res2\", \"res3\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(500) .body("", hasKey("error")) .body("error", equalTo("Error decoding invalid json resource 'res2'")); async.complete(); } @Test public void testWithMultipleInvalidResources(TestContext context) { Async async = context.async(); delete("/server/resources"); // invalid with().body("{ \"foo\": \"bar1\"").put("/server/resources/res1"); // invalid with().body("{ \"foo\"}").put("/server/resources/res2"); with().body("{ \"foo\": \"bar3\" }").put("/server/resources/res3"); given() .body("{ \"subResources\": [\"res1\", \"res2\", \"res3\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(500) .body("", hasKey("error")) .body("error", equalTo("Error decoding invalid json resource 'res1'")); async.complete(); } @Test public void testSimpleWith3Resources(TestContext context) { Async async = context.async(); delete("/server/resources"); with().body("{ \"foo\": \"bar1\" }").put("/server/resources/res1"); with().body("{ \"foo\": \"bar2\" }").put("/server/resources/res2"); with().body("{ \"foo\": \"bar3\" }").put("/server/resources/res3"); given() .body("{ \"subResources\": [\"res1\", \"res2\", \"res3\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(200).contentType(ContentType.JSON).header(ETAG_HEADER, not(empty())) .body("", allOf(hasKey("res1"), hasKey("res2"), hasKey("res3"))) .body("res1.foo", equalTo("bar1")) .body("res2.foo", equalTo("bar2")) .body("res3.foo", equalTo("bar3")); async.complete(); } @Test public void testDoubleSlashesHandlingForPOSTRequests(TestContext context) { Async async = context.async(); delete("/server/resources"); with().body("{ \"foo\": \"bar1\" }").put("/server/resources/res1"); with().body("{ \"foo\": \"bar2\" }").put("/server/resources/res2"); with().body("{ \"foo\": \"bar3\" }").put("/server/resources/res3"); given() .urlEncodingEnabled(false) .body("{ \"subResources\": [\"res1\", \"res2\", \"res3\"] }") .when() .post("/server//resources?storageExpand=true") .then() .assertThat().statusCode(200).contentType(ContentType.JSON).header(ETAG_HEADER, not(empty())) .body("", allOf(hasKey("res1"), hasKey("res2"), hasKey("res3"))) .body("res1.foo", equalTo("bar1")) .body("res2.foo", equalTo("bar2")) .body("res3.foo", equalTo("bar3")); async.complete(); } @Test public void testSimpleWithUnknownSubResources(TestContext context) { Async async = context.async(); delete("/server/resources"); with().body("{ \"foo\": \"bar1\" }").put("/server/resources/res1"); with().body("{ \"foo\": \"bar2\" }").put("/server/resources/res2"); with().body("{ \"foo\": \"bar3\" }").put("/server/resources/res3"); given() .body("{ \"subResources\": [\"res1\", \"res2XX\", \"res3\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(200).contentType(ContentType.JSON).header(ETAG_HEADER, not(empty())) .body("", allOf(hasKey("res1"), hasKey("res3"))) .body("", not(hasKey("res2"))) .body("res1.foo", equalTo("bar1")) .body("res3.foo", equalTo("bar3")); async.complete(); } @Test public void testSimpleWithMissingSubResources(TestContext context) { Async async = context.async(); delete("/server/resources"); with().body("{ \"foo\": \"bar1\" }").put("/server/resources/res1"); with().body("{ \"foo\": \"bar3\" }").put("/server/resources/res3"); given() .body("{ \"subResources\": [\"res1\", \"res2\", \"res3\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(200).contentType(ContentType.JSON).header(ETAG_HEADER, not(empty())) .body("", allOf(hasKey("res1"), hasKey("res3"))) .body("", not(hasKey("res2"))) .body("res1.foo", equalTo("bar1")) .body("res3.foo", equalTo("bar3")); async.complete(); } @Test public void testSimpleWithEmptyResult(TestContext context) { Async async = context.async(); delete("/server/resources"); given() .body("{ \"subResources\": [\"res1\", \"res2\", \"res3\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(404); async.complete(); } @Test public void testSimpleWithResourcesAndCollections(TestContext context) { Async async = context.async(); delete("/server/resources"); with().body("{ \"foo\": \"bar1\" }").put("/server/resources/res1"); with().body("{ \"foo\": \"bar2\" }").put("/server/resources/res2"); with().body("{ \"foo\": \"bar3\" }").put("/server/resources/res3"); with().body("{ \"foo\": \"sub1\" }").put("/server/resources/sub/sub1"); with().body("{ \"foo\": \"sub2\" }").put("/server/resources/sub/sub2"); given() .body("{ \"subResources\": [\"res1\", \"res2\", \"res3\", \"sub/\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(200).contentType(ContentType.JSON).header(ETAG_HEADER, not(empty())) .body("", allOf(hasKey("res1"), hasKey("res2"), hasKey("res3"), hasKey("sub"))) .body("res1.foo", equalTo("bar1")) .body("res2.foo", equalTo("bar2")) .body("res3.foo", equalTo("bar3")) .body("sub", hasItems("sub1", "sub2")); delete("/server/resources"); with().body("{ \"foo\": \"sub1\" }").put("/server/resources/sub/sub1"); with().body("{ \"foo\": \"sub2\" }").put("/server/resources/sub/sub2"); with().body("{ \"foo\": \"sub2\" }").put("/server/resources/anothersub/sub3"); with().body("{ \"foo\": \"sub2\" }").put("/server/resources/anothersub/sub4"); with().body("{ \"foo\": \"sub2\" }").put("/server/resources/anothersub/sub5/subsub1"); given() .body("{ \"subResources\": [\"anothersub/\", \"sub/\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(200).contentType(ContentType.JSON).header(ETAG_HEADER, not(empty())) .body("", allOf(hasKey("anothersub"), hasKey("sub"))) .body("sub.get(0)", equalTo("sub1")) .body("sub.get(1)", equalTo("sub2")) .body("anothersub.get(0)", equalTo("sub5/")) .body("anothersub.get(1)", equalTo("sub3")) .body("anothersub.get(2)", equalTo("sub4")); with().body("{ \"foo\": \"sub1\" }").put("/server/resources/level1/sub/sub1"); with().body("{ \"foo\": \"sub2\" }").put("/server/resources/level1/sub/sub2"); with().body("{ \"foo\": \"sub2\" }").put("/server/resources/level1/anothersub/sub3"); with().body("{ \"foo\": \"sub2\" }").put("/server/resources/level1/anothersub/sub4"); given() .body("{ \"subResources\": [\"level1/\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(200).contentType(ContentType.JSON).header(ETAG_HEADER, not(empty())) .body("level1", hasItems("anothersub/", "sub/")); async.complete(); } @Test public void testSameEtagForSameResult(TestContext context) { Async async = context.async(); delete("/server/resources"); with().body("{ \"foo\": \"bar1\" }").put("/server/resources/res1"); with().body("{ \"foo\": \"bar2\" }").put("/server/resources/res2"); with().body("{ \"foo\": \"bar3\" }").put("/server/resources/res3"); Response post1 = given() .body("{ \"subResources\": [\"res1\", \"res2\", \"res3\"] }") .when() .post(POST_STORAGE_EXP); String etagPost1 = post1.getHeader(ETAG_HEADER); Response post2 = given() .body("{ \"subResources\": [\"res1\", \"res2\", \"res3\"] }") .when() .post(POST_STORAGE_EXP); String etagPost2 = post2.getHeader(ETAG_HEADER); Response post3 = given() .body("{ \"subResources\": [\"res1\", \"res2\"] }") .when() .post(POST_STORAGE_EXP); String etagPost3 = post3.getHeader(ETAG_HEADER); context.assertEquals(etagPost1, etagPost2); context.assertNotEquals(etagPost2, etagPost3); async.complete(); } @Test public void testIfNoneMatchHeaderProvided(TestContext context) { Async async = context.async(); delete("/server/resources"); with().body("{ \"foo\": \"bar1\" }").put("/server/resources/res1"); with().body("{ \"foo\": \"bar2\" }").put("/server/resources/res2"); with().body("{ \"foo\": \"bar3\" }").put("/server/resources/res3"); String etag = "SomeEtagValue"; Response post1 = given().header(IF_NONE_MATCH_HEADER, etag) .body("{ \"subResources\": [\"res1\", \"res2\", \"res3\"] }") .when() .post(POST_STORAGE_EXP); String etagPost1 = post1.getHeader(ETAG_HEADER); context.assertNotNull(etagPost1); context.assertNotEquals(etagPost1, etag); given().header(IF_NONE_MATCH_HEADER, etagPost1) .body("{ \"subResources\": [\"res1\", \"res2\", \"res3\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(304); async.complete(); } @Test public void testWithResourcesHavingDoubleQuotesInTheName(TestContext context) { Async async = context.async(); delete("/server/resources"); with().urlEncodingEnabled(false).body("{ \"foo\": \"bar1\" }").put("/server/resources/res:1"); with().urlEncodingEnabled(false).body("{ \"foo\": \"bar2\" }").put("/server/resources/res2"); with().urlEncodingEnabled(false).body("{ \"foo\": \"bar3\" }").put("/server/resources/999;_hello-:@$&()*+,=-._~!'"); given() .urlEncodingEnabled(false) .body("{ \"subResources\": [\"res:1\", \"res2\", \"999;_hello-:@$&()*+,=-._~!'\"] }") .when() .post(POST_STORAGE_EXP).prettyPrint(); given() .urlEncodingEnabled(false) .body("{ \"subResources\": [\"res:1\", \"res2\", \"999;_hello-:@$&()*+,=-._~!'\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(200).contentType(ContentType.JSON).header(ETAG_HEADER, not(empty())) .body("", allOf(hasKey("res:1"), hasKey("res2"), hasKey("999;_hello-:@$&()*+,=-._~!'"))) .body("res2.foo", equalTo("bar2")); async.complete(); } @Test public void testCompressedAndUncompressedDataInCollection(TestContext context) { Async async = context.async(); delete("/server/resources"); with().body("{ \"foo\": \"bar1\" }").put("/server/resources/res1"); with().header(COMPRESS_HEADER, "true").body("{ \"foo\": \"bar2\" }").put("/server/resources/res2"); with().body("{ \"foo\": \"bar3\" }").put("/server/resources/res3"); given() .body("{ \"subResources\": [\"res1\", \"res2\", \"res3\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat() .statusCode(409) .body(containsString("Collections having compressed resources are not supported in storage expand")); // make storage expand again without the compressed resource given() .body("{ \"subResources\": [\"res1\", \"res3\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(200).contentType(ContentType.JSON).header(ETAG_HEADER, not(empty())) .body("", allOf(hasKey("res1"), hasKey("res3"))) .body("res1.foo", equalTo("bar1")) .body("res3.foo", equalTo("bar3")); async.complete(); } @Test public void testCompressedResourcesInSubResources(TestContext context) { Async async = context.async(); delete("/server/resources"); with().body("{ \"foo\": \"bar1\" }").put("/server/resources/res1"); with().body("{ \"foo\": \"bar2\" }").put("/server/resources/res2"); with().body("{ \"foo\": \"bar3\" }").put("/server/resources/res3"); with().body("{ \"foo\": \"sub1\" }").put("/server/resources/sub/sub1"); with().header(COMPRESS_HEADER, "true").body("{ \"foo\": \"sub2\" }").put("/server/resources/sub/sub2"); given() .body("{ \"subResources\": [\"res1\", \"res2\", \"res3\", \"sub/\"] }") .when() .post(POST_STORAGE_EXP) .then() .assertThat().statusCode(200).contentType(ContentType.JSON).header(ETAG_HEADER, not(empty())) .body("", allOf(hasKey("res1"), hasKey("res2"), hasKey("res3"), hasKey("sub"))) .body("res1.foo", equalTo("bar1")) .body("res2.foo", equalTo("bar2")) .body("res3.foo", equalTo("bar3")) .body("sub", hasItems("sub1", "sub2")); given() .body("{ \"subResources\": [\"sub1\", \"sub2\"] }") .when() .post("/server/resources/sub?storageExpand=true") .then() .assertThat() .statusCode(409) .body(containsString("Collections having compressed resources are not supported in storage expand"));; async.complete(); } }