001package org.w3.ldp.testsuite.test; 002 003import com.hp.hpl.jena.rdf.model.Model; 004import com.jayway.restassured.response.Response; 005import org.apache.commons.io.IOUtils; 006import org.apache.commons.lang3.StringUtils; 007import org.apache.http.HttpStatus; 008import org.apache.marmotta.commons.util.HashUtils; 009import org.apache.marmotta.commons.vocabulary.LDP; 010import org.testng.Assert; 011import org.testng.annotations.*; 012import org.w3.ldp.testsuite.LdpTestSuite; 013import org.w3.ldp.testsuite.annotations.SpecTest; 014import org.w3.ldp.testsuite.annotations.SpecTest.METHOD; 015import org.w3.ldp.testsuite.annotations.SpecTest.STATUS; 016import org.w3.ldp.testsuite.exception.SkipException; 017import org.w3.ldp.testsuite.mapper.RdfObjectMapper; 018import org.w3.ldp.testsuite.matcher.HeaderMatchers; 019 020import java.io.IOException; 021 022import static org.testng.Assert.assertEquals; 023import static org.testng.Assert.assertTrue; 024import static org.w3.ldp.testsuite.http.HttpHeaders.*; 025import static org.w3.ldp.testsuite.http.MediaTypes.TEXT_TURTLE; 026import static org.w3.ldp.testsuite.matcher.HttpStatusNotFoundOrGoneMatcher.isNotFoundOrGone; 027import static org.w3.ldp.testsuite.matcher.HttpStatusSuccessMatcher.isSuccessful; 028 029/** 030 * Tests Non-RDF Source LDP resources. 031 */ 032public class NonRDFSourceTest extends CommonResourceTest { 033 private final static String SETUP_ERROR = "ERROR: Could not create test resource for NonRDFSourceTest. Skipping tests."; 034 035 private String container; 036 /** Resource for CommonResourceTest */ 037 private String nonRdfSource; 038 039 @Parameters("auth") 040 public NonRDFSourceTest(@Optional String auth) throws IOException { 041 super(auth); 042 } 043 044 @Parameters({ "basicContainer", "directContainer", "indirectContainer" }) 045 @BeforeSuite(alwaysRun = true) 046 public void createTestResource(@Optional String basicContainer, @Optional String directContainer, @Optional String indirectContainer) { 047 if (StringUtils.isNotBlank(basicContainer)) { 048 container = basicContainer; 049 } else if (StringUtils.isNotBlank(directContainer)) { 050 container = directContainer; 051 } else if (StringUtils.isNotBlank(indirectContainer)) { 052 container = indirectContainer; 053 } else { 054 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 055 "No root container provided in testng.xml. Skipping LDP Non-RDF Source (LDP-NR) tests.", 056 skipLog); 057 } 058 059 final String slug = "non-rdf-source", 060 file = "test.png", 061 mimeType = "image/png"; 062 063 // Create a resource to use for CommonResourceTest. 064 try { 065 Response response = buildBaseRequestSpecification() 066 .header(SLUG, slug) 067 .body(IOUtils.toByteArray(getClass().getResourceAsStream("/" + file))) 068 .contentType(mimeType) 069 .post(container); 070 if (response.getStatusCode() != HttpStatus.SC_CREATED) { 071 System.err.println(SETUP_ERROR); 072 System.err.println("POST failed with status code: " + response.getStatusCode()); 073 System.err.println(); 074 return; 075 } 076 077 nonRdfSource = response.getHeader(LOCATION); 078 if (nonRdfSource == null) { 079 System.err.println(SETUP_ERROR); 080 System.err.println("Location response header missing"); 081 System.err.println(); 082 return; 083 } 084 } catch (Exception e) { 085 System.err.println(SETUP_ERROR); 086 e.printStackTrace(); 087 } 088 } 089 090 @AfterSuite(alwaysRun = true) 091 public void deleteTestResource() { 092 if (nonRdfSource != null) { 093 buildBaseRequestSpecification().delete(nonRdfSource); 094 } 095 } 096 097 @Override 098 protected String getResourceUri() { 099 if (nonRdfSource == null) { 100 throw new SkipException(Thread.currentThread().getStackTrace()[2].getMethodName(), 101 "Skipping test because test resource is null.", skipLog); 102 } 103 104 return nonRdfSource; 105 } 106 107 @Test( 108 groups = {MAY}, 109 description = "LDP servers may accept an HTTP POST of non-RDF " + 110 "representations (LDP-NRs) for creation of any kind of " + 111 "resource, for example binary resources.") 112 @SpecTest( 113 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-createbins", 114 testMethod = METHOD.AUTOMATED, 115 approval = STATUS.WG_APPROVED, 116 comment = "Covers only part of the specification requirement. " 117 + "testPostResourceAndGetFromContainer covers the rest.") 118 public void testPostNonRDFSource() throws IOException { 119 // Test constants 120 final String slug = "test", 121 file = slug + ".png", 122 mimeType = "image/png"; 123 124 // Make sure we can post binary resources 125 Response response = postNonRDFSource(slug, file, mimeType); 126 buildBaseRequestSpecification().delete(response.getHeader(LOCATION)); 127 } 128 129 @Test( 130 groups = {MAY}, 131 description = "LDP servers may accept an HTTP POST of non-RDF " + 132 "representations (LDP-NRs) for creation of any kind of " + 133 "resource, for example binary resources.") 134 @SpecTest( 135 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-createbins", 136 testMethod = METHOD.AUTOMATED, 137 approval = STATUS.WG_APPROVED, 138 comment = "Covers only part of the specification requirement. " 139 + "testPostNonRDFSource covers the rest.") 140 public void testPostResourceAndGetFromContainer() throws IOException { 141 // Test constants 142 final String slug = "test", 143 file = slug + ".png", 144 mimeType = "image/png"; 145 146 // Make sure we can post binary resources 147 Response response = postNonRDFSource(slug, file, mimeType); 148 try { 149 // Check the container contains the new resource 150 Model model = buildBaseRequestSpecification() 151 .header(ACCEPT, TEXT_TURTLE) 152 .expect() 153 .statusCode(HttpStatus.SC_OK) 154 .contentType(HeaderMatchers.isTurtleCompatibleContentType()) 155 .get(container) 156 .body().as(Model.class, new RdfObjectMapper(container)); 157 158 assertTrue(model.contains(model.createResource(container), model.createProperty(LDP.contains.stringValue()), model.createResource(response.getHeader(LOCATION)))); 159 } finally { 160 buildBaseRequestSpecification().delete(response.getHeader(LOCATION)); 161 } 162 } 163 164 @Test( 165 groups = {MAY}, 166 description = "LDP servers may host a mixture of LDP-RSs and LDP-NRs. " + 167 "For example, it is common for LDP servers to need to host binary " + 168 "or text resources that do not have useful RDF representations.") 169 @SpecTest( 170 specRefUri = LdpTestSuite.SPEC_URI + "#ldpr-gen-binary", 171 testMethod = METHOD.AUTOMATED, 172 approval = STATUS.WG_APPROVED) 173 public void testPostResourceGetBinary() throws IOException { 174 // Test constants 175 final String slug = "test", 176 file = slug + ".png", 177 mimeType = "image/png"; 178 179 // Make sure we can post binary resources 180 Response response = postNonRDFSource(slug, file, mimeType); 181 try { 182 // And then check we get the binary back 183 final String expectedMD5 = HashUtils.md5sum(NonRDFSourceTest.class.getResourceAsStream("/" + file)); 184 final byte[] binary = buildBaseRequestSpecification() 185 .header(ACCEPT, mimeType) 186 .expect() 187 .statusCode(HttpStatus.SC_OK) 188 .contentType(mimeType) 189 .header(ETAG, HeaderMatchers.isValidEntityTag()) 190 .when() 191 .get(response.getHeader(LOCATION)) 192 .body().asByteArray(); 193 assertEquals(expectedMD5, HashUtils.md5sum(binary), "md5sum"); 194 } finally { 195 buildBaseRequestSpecification().delete(response.getHeader(LOCATION)); 196 } 197 } 198 199 @Test( 200 groups = {MAY}, 201 description = "Each LDP Non-RDF Source must also be a conforming LDP Resource. " + 202 "LDP Non-RDF Sources may not be able to fully express their state using RDF.") 203 @SpecTest( 204 specRefUri = LdpTestSuite.SPEC_URI + "#ldpnr-are-ldpr", 205 testMethod = METHOD.AUTOMATED, 206 approval = STATUS.WG_APPROVED) 207 public void testPostResourceGetMetadataAndBinary() throws IOException { 208 // Test constants 209 final String slug = "test", 210 file = slug + ".png", 211 mimeType = "image/png"; 212 213 // Make sure we can post binary resources 214 Response response = postNonRDFSource(slug, file, mimeType); 215 String location = response.getHeader(LOCATION); 216 217 try { 218 String associatedRdfSource = getFirstLinkForRelation(location, LINK_REL_DESCRIBEDBY, container, response); 219 Assert.assertNotNull(associatedRdfSource, "No Link response header with relation \"describedby\" " + 220 "and anchor parameter matching the newly-created resource URI"); 221 222 // And then check we get the metadata of back 223 buildBaseRequestSpecification() 224 .header(ACCEPT, TEXT_TURTLE) 225 .expect() 226 .statusCode(HttpStatus.SC_OK) 227 .contentType(HeaderMatchers.isTurtleCompatibleContentType()) 228 .header(ETAG, HeaderMatchers.isValidEntityTag()) 229 .when() 230 .get(associatedRdfSource) 231 .as(Model.class, new RdfObjectMapper(associatedRdfSource)); 232 233 // And the binary too 234 final String expectedMD5 = HashUtils.md5sum(NonRDFSourceTest.class.getResourceAsStream("/" + file)); 235 final byte[] binary = buildBaseRequestSpecification() 236 .header(ACCEPT, mimeType) 237 .expect() 238 .statusCode(HttpStatus.SC_OK) 239 .contentType(mimeType) 240 .header(ETAG, HeaderMatchers.isValidEntityTag()) 241 .when() 242 .get(location) 243 .body().asByteArray(); 244 assertEquals(expectedMD5, HashUtils.md5sum(binary), "md5sum"); 245 } finally { 246 buildBaseRequestSpecification().delete(location); 247 } 248 } 249 250 @Test( 251 groups = {MAY}, 252 description = "LDP servers exposing an LDP Non-RDF Source may advertise this by exposing " + 253 "a HTTP Link header with a target URI of http://www.w3.org/ns/ldp#NonRDFSource, and " + 254 "a link relation type of type (that is, rel='type') in responses to requests made to " + 255 "the LDP-NR's HTTP Request-URI.") 256 @SpecTest( 257 specRefUri = LdpTestSuite.SPEC_URI + "#ldpnr-type", 258 testMethod = METHOD.AUTOMATED, 259 approval = STATUS.WG_APPROVED) 260 public void testPostResourceAndCheckLink() throws IOException { 261 // Test constants 262 final String slug = "test", 263 file = slug + ".png", 264 mimeType = "image/png"; 265 266 // Make sure we can post binary resources 267 Response postResponse = postNonRDFSource(slug, file, mimeType); 268 final String location = postResponse.getHeader(LOCATION); 269 270 try { 271 // And then check the link when requesting the LDP-NR 272 Response getResponse = buildBaseRequestSpecification() 273 .expect() 274 .statusCode(isSuccessful()) 275 .header(ETAG, HeaderMatchers.isValidEntityTag()) 276 .when() 277 .get(location); 278 Assert.assertTrue(containsLinkHeader( 279 location, 280 LINK_REL_TYPE, 281 LDP.NonRDFSource.stringValue(), 282 location, 283 getResponse 284 )); 285 } finally { 286 buildBaseRequestSpecification().delete(postResponse.header(LOCATION)); 287 } 288 } 289 290 @Test( 291 groups = {MAY}, 292 description = "Upon successful creation of an LDP-NR (HTTP status code of 201-Created and " + 293 "URI indicated by Location response header), LDP servers may create an associated " + 294 "LDP-RS to contain data about the newly created LDP-NR. If a LDP server creates " + 295 "this associated LDP-RS it must indicate its location on the HTTP response using " + 296 "the HTTP Link response header with link relation describedby and href to be the " + 297 "URI of the associated LDP-RS resource.") 298 @SpecTest( 299 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-createbinlinkmetahdr", 300 testMethod = METHOD.AUTOMATED, 301 approval = STATUS.WG_APPROVED) 302 public void testPostResourceAndCheckAssociatedResource() throws IOException { 303 // Test constants 304 final String slug = "test", 305 file = slug + ".png", 306 mimeType = "image/png"; 307 308 // Make sure we can post binary resources 309 Response postResponse = postNonRDFSource(slug, file, mimeType); 310 String location = postResponse.getHeader(LOCATION); 311 try { 312 String associatedRdfSource = getFirstLinkForRelation(location, LINK_REL_DESCRIBEDBY, location, postResponse); 313 Assert.assertNotNull(associatedRdfSource, "No Link response header with relation \"describedby\" " + 314 "and anchor parameter matching the newly-created resource URI"); 315 316 // Check the link when requesting the LDP-NS 317 Response getResponse = buildBaseRequestSpecification() 318 .expect() 319 .statusCode(isSuccessful()) 320 .header(ETAG, HeaderMatchers.headerPresent()) 321 .when() 322 .get(location); 323 Assert.assertTrue(containsLinkHeader( 324 location, 325 LINK_REL_DESCRIBEDBY, 326 associatedRdfSource, 327 location, 328 getResponse 329 )); 330 331 // And then check the associated LDP-RS is actually there 332 buildBaseRequestSpecification() 333 .header(ACCEPT, TEXT_TURTLE) 334 .expect() 335 .statusCode(isSuccessful()) 336 .contentType(HeaderMatchers.isTurtleCompatibleContentType()) 337 .header(ETAG, HeaderMatchers.isValidEntityTag()) 338 .when() 339 .get(associatedRdfSource); 340 } finally { 341 buildBaseRequestSpecification().delete(location); 342 } 343 } 344 345 @Test( 346 groups = {MUST}, 347 description = "When a contained LDPR is deleted, and the LDPC server created an"+ 348 "associated LDP-RS (see the LDPC POST section), the LDPC server must also"+ 349 "delete the associated LDP-RS it created.", 350 dependsOnMethods = "testPostResourceAndCheckAssociatedResource") 351 @SpecTest( 352 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-del-contremovescontres", 353 testMethod = METHOD.AUTOMATED, 354 approval = STATUS.WG_APPROVED) 355 public void testDeleteNonRDFSourceDeletesAssociatedResource() throws IOException { 356 // Test constants 357 final String slug = "test", 358 file = slug + ".png", 359 mimeType = "image/png"; 360 361 // Make sure we can post binary resources 362 Response postResponse = postNonRDFSource(slug, file, mimeType); 363 String location = postResponse.getHeader(LOCATION); 364 boolean deleted = false; 365 366 try { 367 String associatedRdfSource = getFirstLinkForRelation(location, LINK_REL_DESCRIBEDBY, container, postResponse); 368 Assert.assertNotNull(associatedRdfSource, "No Link response header with relation \"describedby\" " + 369 "and anchor parameter matching the newly-created resource URI"); 370 371 // And then check the associated LDP-RS is actually there 372 buildBaseRequestSpecification() 373 .header(ACCEPT, TEXT_TURTLE) 374 .expect() 375 .statusCode(isSuccessful()) 376 .contentType(HeaderMatchers.isTurtleCompatibleContentType()) 377 .when() 378 .get(associatedRdfSource); 379 380 // Delete the LDP-NR. 381 deleted = true; 382 buildBaseRequestSpecification() 383 .expect() 384 .statusCode(isSuccessful()) 385 .when() 386 .delete(location); 387 388 // Check that the associated LDP-RS is also deleted. 389 buildBaseRequestSpecification() 390 .header(ACCEPT, TEXT_TURTLE) 391 .expect() 392 .statusCode(isNotFoundOrGone()) 393 .when() 394 .get(associatedRdfSource); 395 } finally { 396 // Clean up if an assertion failed before we could delete the resource. 397 if (!deleted) { 398 buildBaseRequestSpecification().delete(location); 399 } 400 } 401 } 402 403 protected Response postNonRDFSource(String slug, String file, String mimeType) throws IOException { 404 // Make sure we can post binary resources 405 return buildBaseRequestSpecification() 406 .header(SLUG, slug) 407 .body(IOUtils.toByteArray(getClass().getResourceAsStream("/" + file))) 408 .contentType(mimeType) 409 .expect() 410 .statusCode(HttpStatus.SC_CREATED) 411 .header(LOCATION, HeaderMatchers.headerPresent()) 412 .when() 413 .post(container); 414 } 415 416 @Test( 417 groups = {MUST}, 418 description = "When responding to requests whose request-URI is a LDP-NR with an"+ 419 "associated LDP-RS, a LDPC server must provide the same HTTP Link response"+ 420 "header as is required in the create response", 421 dependsOnMethods = "testPostResourceAndCheckAssociatedResource") 422 @SpecTest( 423 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-options-linkmetahdr", 424 testMethod = METHOD.AUTOMATED, 425 approval = STATUS.WG_APPROVED) 426 public void testOptionsHasSameLinkHeader() throws IOException { 427 // Test constants 428 final String slug = "test", 429 file = slug + ".png", 430 mimeType = "image/png"; 431 432 // Make sure we can post binary resources 433 Response postResponse = postNonRDFSource(slug, file, mimeType); 434 String location = postResponse.getHeader(LOCATION); 435 436 try { 437 String associatedRdfSource = getFirstLinkForRelation(location, LINK_REL_DESCRIBEDBY, container, postResponse); 438 Assert.assertNotNull(associatedRdfSource, "No Link response header with relation \"describedby\" " + 439 "and anchor parameter matching the newly-created resource URI"); 440 441 // Check the Link headers on an HTTP OPTIONS for the LDP-NR 442 Response optionsResponse = buildBaseRequestSpecification() 443 .expect() 444 .statusCode(isSuccessful()) 445 .when() 446 .options(location); 447 Assert.assertTrue(containsLinkHeader( 448 location, 449 LINK_REL_DESCRIBEDBY, 450 associatedRdfSource, 451 location, 452 optionsResponse 453 ), 454 "No Link response header with relation \"describedby\" and URI <" 455 + associatedRdfSource + "> for LDP-NR OPTIONS request"); 456 } finally { 457 buildBaseRequestSpecification().delete(location); 458 } 459 } 460 461}