001package org.w3.ldp.testsuite.test; 002 003import com.hp.hpl.jena.rdf.model.Model; 004import com.hp.hpl.jena.rdf.model.ModelFactory; 005import com.hp.hpl.jena.rdf.model.Resource; 006import com.hp.hpl.jena.util.ResourceUtils; 007import com.hp.hpl.jena.vocabulary.DCTerms; 008import com.hp.hpl.jena.vocabulary.RDF; 009import com.jayway.restassured.response.Response; 010import com.jayway.restassured.specification.RequestSpecification; 011import org.apache.commons.lang3.RandomStringUtils; 012import org.apache.http.HttpStatus; 013import org.testng.annotations.Optional; 014import org.testng.annotations.Parameters; 015import org.testng.annotations.Test; 016import org.w3.ldp.testsuite.LdpTestSuite; 017import org.w3.ldp.testsuite.annotations.SpecTest; 018import org.w3.ldp.testsuite.annotations.SpecTest.METHOD; 019import org.w3.ldp.testsuite.annotations.SpecTest.STATUS; 020import org.w3.ldp.testsuite.exception.SkipException; 021import org.w3.ldp.testsuite.http.HttpMethod; 022import org.w3.ldp.testsuite.mapper.RdfObjectMapper; 023import org.w3.ldp.testsuite.matcher.HeaderMatchers; 024import org.w3.ldp.testsuite.vocab.LDP; 025 026import javax.ws.rs.core.UriBuilder; 027import java.io.ByteArrayOutputStream; 028import java.io.IOException; 029import java.net.URI; 030import java.net.URISyntaxException; 031import java.util.UUID; 032 033import static org.hamcrest.core.IsNot.not; 034import static org.testng.Assert.*; 035import static org.w3.ldp.testsuite.http.HttpHeaders.*; 036import static org.w3.ldp.testsuite.http.LdpPreferences.PREFER_CONTAINMENT; 037import static org.w3.ldp.testsuite.http.LdpPreferences.PREFER_MINIMAL_CONTAINER; 038import static org.w3.ldp.testsuite.http.MediaTypes.APPLICATION_LD_JSON; 039import static org.w3.ldp.testsuite.http.MediaTypes.TEXT_TURTLE; 040import static org.w3.ldp.testsuite.matcher.HttpStatusSuccessMatcher.isSuccessful; 041 042/** 043 * Common tests for all LDP container types. 044 */ 045public abstract class CommonContainerTest extends RdfSourceTest { 046 047 public static final String MSG_LOC_NOTFOUND = "Location header missing after POST create."; 048 public static final String MSG_MBRRES_NOTFOUND = "Unable to locate object in triple with predicate ldp:membershipResource."; 049 050 @Parameters("auth") 051 public CommonContainerTest(@Optional String auth) throws IOException { 052 super(auth); 053 } 054 055 @Test( 056 groups = {MAY}, 057 description = "LDP servers MAY choose to allow the creation of new " 058 + "resources using HTTP PUT.") 059 @SpecTest( 060 specRefUri = LdpTestSuite.SPEC_URI + "#ldpr-put-create", 061 testMethod = METHOD.AUTOMATED, 062 approval = STATUS.WG_APPROVED) 063 public void testPutToCreate() { 064 String location = putToCreate(); 065 buildBaseRequestSpecification().delete(location); 066 } 067 068 @Test( 069 groups = {MUST}, 070 description = "LDP servers MUST assign the default base-URI " 071 + "for [RFC3987] relative-URI resolution to be the HTTP " 072 + "Request-URI when the resource already exists, and to " 073 + "the URI of the created resource when the request results " 074 + "in the creation of a new resource.") 075 @Parameters("relativeUri") 076 @SpecTest( 077 specRefUri = LdpTestSuite.SPEC_URI + "#ldpr-gen-defbaseuri", 078 testMethod = METHOD.AUTOMATED, 079 approval = STATUS.WG_APPROVED, 080 comment = "Covers only part of the specification requirement. ") 081 public void testRelativeUriResolutionPost(@Optional String relativeUri) { 082 skipIfMethodNotAllowed(HttpMethod.POST); 083 084 if (restrictionsOnPostContent()) { 085 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 086 "Skipping test because there are restrictions on POST content. " 087 + "The requirement needs to be tested manually.", skipLog); 088 } 089 090 if (relativeUri == null) { 091 relativeUri = RELATIVE_URI; 092 } 093 094 // POST content with a relative URI (other than the null relative URI). 095 Model requestModel = postContent(); 096 Resource r = requestModel.getResource(""); 097 r.addProperty(DCTerms.relation, requestModel.createResource(relativeUri)); 098 099 String containerUri = getResourceUri(); 100 Response postResponse = buildBaseRequestSpecification() 101 .contentType(TEXT_TURTLE) 102 .body(requestModel, new RdfObjectMapper()) 103 .expect() 104 .statusCode(HttpStatus.SC_CREATED) 105 .header(LOCATION, HeaderMatchers.headerPresent()) 106 .when() 107 .post(containerUri); 108 109 String location = postResponse.getHeader(LOCATION); 110 111 try { 112 Response getResponse = buildBaseRequestSpecification() 113 .header(ACCEPT, TEXT_TURTLE) 114 .expect() 115 .statusCode(isSuccessful()) 116 .when() 117 .get(location); 118 119 // Check that the dcterms:relation URI was resolved relative to the 120 // URI assigned to the new resource (location). 121 Model responseModel = getResponse.as(Model.class, new RdfObjectMapper(location)); 122 String relationAbsoluteUri = resolveIfRelative(location, relativeUri); 123 assertTrue( 124 responseModel.contains( 125 getPrimaryTopic(responseModel, location), 126 DCTerms.relation, 127 responseModel.getResource(relationAbsoluteUri) 128 ), 129 "Response does not have expected triple: <" + location + "> dcterms:relation <" + relationAbsoluteUri + ">." 130 ); 131 } finally { 132 buildBaseRequestSpecification().delete(location); 133 } 134 } 135 136 @Test( 137 groups = {SHOULD}, 138 description = "LDPC representations SHOULD NOT use RDF container " 139 + "types rdf:Bag, rdf:Seq or rdf:List.") 140 @SpecTest( 141 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-nordfcontainertypes", 142 testMethod = METHOD.AUTOMATED, 143 approval = STATUS.WG_APPROVED) 144 public void testNoRdfBagSeqOrList() { 145 Model containerModel = getAsModel(getResourceUri()); 146 assertFalse(containerModel.listResourcesWithProperty(RDF.type, RDF.Bag) 147 .hasNext(), "LDPC representations should not use rdf:Bag"); 148 assertFalse(containerModel.listResourcesWithProperty(RDF.type, RDF.Seq) 149 .hasNext(), "LDPC representations should not use rdf:Seq"); 150 assertFalse(containerModel.listResourcesWithProperty(RDF.type, RDF.List).hasNext(), 151 "LDPC representations should not use rdf:List" 152 ); 153 } 154 155 @Test( 156 groups = {SHOULD}, 157 description = "LDP servers SHOULD respect all of a client's LDP-defined " 158 + "hints, for example which subsets of LDP-defined state the " 159 + "client is interested in processing, to influence the set of " 160 + "triples returned in representations of an LDPC, particularly for " 161 + "large LDPCs. See also [LDP-PAGING].") 162 @SpecTest( 163 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-prefer", 164 testMethod = METHOD.AUTOMATED, 165 approval = STATUS.WG_APPROVED) 166 public void testPreferContainmentTriples() { 167 Response response; 168 Model model; 169 String containerUri = getResourceUri(); 170 171 // Ask for containment triples. 172 response = buildBaseRequestSpecification() 173 .header(ACCEPT, TEXT_TURTLE) 174 .header(PREFER, include(PREFER_CONTAINMENT)) // request all containment triples 175 .expect() 176 .statusCode(isSuccessful()) 177 .when() 178 .get(containerUri); 179 model = response.as(Model.class, new RdfObjectMapper(containerUri)); 180 181 checkPreferenceAppliedHeader(response); 182 183 // Assumes the container is not empty. 184 assertTrue(model.contains(model.getResource(containerUri), model.createProperty(LDP.contains.stringValue())), 185 "Container does not have containment triples"); 186 187 // Ask for a minimal container. 188 response = buildBaseRequestSpecification() 189 .header(ACCEPT, TEXT_TURTLE) 190 .header(PREFER, include(PREFER_MINIMAL_CONTAINER)) // request no containment triples 191 .expect() 192 .statusCode(isSuccessful()) 193 .when() 194 .get(containerUri); 195 model = response.as(Model.class, new RdfObjectMapper(containerUri)); 196 197 checkPreferenceAppliedHeader(response); 198 assertFalse(model.contains(model.getResource(containerUri), model.createProperty(LDP.contains.stringValue())), 199 "Container has containment triples when minimal container was requested"); 200 201 // Ask to omit containment triples. 202 response = buildBaseRequestSpecification() 203 .header(ACCEPT, TEXT_TURTLE) 204 .header(PREFER, omit(PREFER_CONTAINMENT)) // request no containment triples 205 .expect() 206 .statusCode(isSuccessful()) 207 .when() 208 .get(containerUri); 209 model = response.as(Model.class, new RdfObjectMapper(containerUri)); 210 211 checkPreferenceAppliedHeader(response); 212 213 // Assumes the container is not empty. 214 assertFalse(model.contains(model.getResource(containerUri), model.createProperty(LDP.contains.stringValue())), 215 "Container has containment triples when client requested server omit them"); 216 } 217 218 @Test( 219 groups = {MUST}, 220 description = "If the resource was created successfully, LDP servers MUST " 221 + "respond with status code 201 (Created) and the Location " 222 + "header set to the new resource’s URL. Clients shall not " 223 + "expect any representation in the response entity body on " 224 + "a 201 (Created) response.") 225 @SpecTest( 226 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-created201", 227 testMethod = METHOD.AUTOMATED, 228 approval = STATUS.WG_APPROVED) 229 public void testPostResponseStatusAndLocation() throws URISyntaxException { 230 skipIfMethodNotAllowed(HttpMethod.POST); 231 232 String location = null; 233 234 try { 235 Model model = postContent(); 236 Response postResponse = buildBaseRequestSpecification().contentType(TEXT_TURTLE) 237 .body(model, new RdfObjectMapper()).expect() 238 .statusCode(HttpStatus.SC_CREATED).when() 239 .post(getResourceUri()); 240 241 location = postResponse.getHeader(LOCATION); 242 assertNotNull(location, MSG_LOC_NOTFOUND); 243 } finally { 244 buildBaseRequestSpecification().delete(location); 245 } 246 } 247 248 @Test( 249 groups = {MUST}, 250 description = "When a successful HTTP POST request to an LDPC results " 251 + "in the creation of an LDPR, a containment triple MUST be " 252 + "added to the state of LDPC.") 253 @SpecTest( 254 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-createdmbr-contains", 255 testMethod = METHOD.AUTOMATED, 256 approval = STATUS.WG_APPROVED) 257 public void testPostContainer() throws URISyntaxException { 258 skipIfMethodNotAllowed(HttpMethod.POST); 259 260 Model model = postContent(); 261 String containerUri = getResourceUri(); 262 Response postResponse = buildBaseRequestSpecification() 263 .contentType(TEXT_TURTLE) 264 .body(model, new RdfObjectMapper()) 265 .expect() 266 .statusCode(HttpStatus.SC_CREATED) 267 .header(LOCATION, HeaderMatchers.headerPresent()) 268 .when() 269 .post(containerUri); 270 271 String location = postResponse.getHeader(LOCATION); 272 273 try { 274 Response getResponse = buildBaseRequestSpecification() 275 .header(ACCEPT, TEXT_TURTLE) 276 .header(PREFER, include(PREFER_CONTAINMENT)) // hint to the server that we want containment triples 277 .expect() 278 .statusCode(isSuccessful()) 279 .when() 280 .get(containerUri); 281 Model containerModel = getResponse.as(Model.class, new RdfObjectMapper(containerUri)); 282 Resource container = containerModel.getResource(containerUri); 283 284 assertTrue( 285 container.hasProperty(containerModel 286 .createProperty(LDP.contains.stringValue()), 287 containerModel.getResource(location) 288 ), 289 "Container <" 290 + containerUri 291 + "> does not have a containment triple for newly created resource <" 292 + location + ">." 293 ); 294 } finally { 295 buildBaseRequestSpecification().delete(location); 296 } 297 } 298 299 @Test( 300 groups = {MUST}, 301 description = "LDP servers that successfully create a resource from a " 302 + "RDF representation in the request entity body MUST honor the " 303 + "client's requested interaction model(s). The created resource " 304 + "can be thought of as an RDF named graph [rdf11-concepts]. If any " 305 + "model cannot be honored, the server MUST fail the request.") 306 @Parameters("containerAsResource") 307 @SpecTest( 308 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-createrdf", 309 testMethod = METHOD.AUTOMATED, 310 approval = STATUS.WG_APPROVED, 311 comment = "Covers only part of the specification requirement. " 312 + "testRequestedInteractionModelHeaders covers the rest.") 313 public void testRequestedInteractionModelCreateNotAllowed(@Optional String containerAsResource) { 314 if (containerAsResource == null) 315 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 316 "containerAsResource is null", skipLog); 317 318 Model model = postContent(); 319 320 // If create is successful, then not acting like a plain ole resource 321 Response postResponse = buildBaseRequestSpecification().contentType(TEXT_TURTLE) 322 .body(model, new RdfObjectMapper()) 323 .post(containerAsResource); 324 325 // Cleanup if it actually created something 326 String location = postResponse.getHeader(LOCATION); 327 if (postResponse.statusCode() == HttpStatus.SC_CREATED && location !=null) 328 buildBaseRequestSpecification().delete(location); 329 330 assertNotEquals(postResponse.statusCode(), HttpStatus.SC_CREATED, "Resources with interaction model of only ldp:Resources shouldn't allow container POST-create behavior."); 331 332 // TODO: Possibly parse 'Allow' header to see if POST is wrongly listed 333 } 334 335 @Test( 336 groups = {MUST}, 337 description = "LDP servers that successfully create a resource from a " 338 + "RDF representation in the request entity body MUST honor the " 339 + "client's requested interaction model(s). The created resource " 340 + "can be thought of as an RDF named graph [rdf11-concepts]. If any " 341 + "model cannot be honored, the server MUST fail the request.") 342 @Parameters("containerAsResource") 343 @SpecTest( 344 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-createrdf", 345 testMethod = METHOD.AUTOMATED, 346 approval = STATUS.WG_APPROVED, 347 comment = "Covers only part of the specification requirement. " 348 + "testRequestedInteractionModelCreateNotAllowed covers the rest.") 349 public void testRequestedInteractionModelHeaders(@Optional String containerAsResource) { 350 if (containerAsResource == null) 351 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 352 "containerAsResource is null", skipLog); 353 354 // Ensure we don't get back any of the container types in the rel='type' Link header 355 Response response = buildBaseRequestSpecification() 356 .expect() 357 .statusCode(isSuccessful()) 358 .when() 359 .options(containerAsResource); 360 assertFalse( 361 containsLinkHeader( 362 containerAsResource, 363 LINK_REL_TYPE, 364 LDP.BasicContainer.stringValue(), 365 containerAsResource, 366 response) || 367 containsLinkHeader( 368 containerAsResource, 369 LINK_REL_TYPE, 370 LDP.DirectContainer.stringValue(), 371 containerAsResource, 372 response) || 373 containsLinkHeader( 374 containerAsResource, 375 LINK_REL_TYPE, 376 LDP.IndirectContainer.stringValue(), 377 containerAsResource, 378 response), 379 "Resource wrongly advertising itself as a rel='type' of one of the container types." 380 ); 381 } 382 383 @Test( 384 groups = {MUST}, 385 description = "LDP servers MUST accept a request entity body with a " 386 + "request header of Content-Type with value of text/turtle [turtle].") 387 @SpecTest( 388 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-turtle", 389 testMethod = METHOD.AUTOMATED, 390 approval = STATUS.WG_APPROVED) 391 public void testAcceptTurtle() { 392 skipIfMethodNotAllowed(HttpMethod.POST); 393 394 Model model = postContent(); 395 Response postResponse = buildBaseRequestSpecification().contentType(TEXT_TURTLE) 396 .body(model, new RdfObjectMapper()).expect() 397 .statusCode(HttpStatus.SC_CREATED).when() 398 .post(getResourceUri()); 399 400 // Delete the resource to clean up. 401 String location = postResponse.getHeader(LOCATION); 402 if (location != null) { 403 buildBaseRequestSpecification().delete(location); 404 } 405 } 406 407 @Test( 408 groups = {SHOULD}, 409 description = "LDP servers SHOULD use the Content-Type request header to " 410 + "determine the representation format when the request has an " 411 + "entity body.") 412 @SpecTest( 413 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-contenttype", 414 testMethod = METHOD.AUTOMATED, 415 approval = STATUS.WG_APPROVED) 416 public void testContentTypeHeader() throws URISyntaxException { 417 skipIfMethodNotAllowed(HttpMethod.POST); 418 419 // POST Turtle content with a bad Content-Type request header to see what happens. 420 Model toPost = postContent(); 421 ByteArrayOutputStream out = new ByteArrayOutputStream(); 422 toPost.write(out, "TURTLE"); 423 Response postResponse = buildBaseRequestSpecification() 424 .contentType("text/this-is-not-turtle") 425 .body(out.toByteArray()) 426 .when() 427 .post(getResourceUri()); 428 429 if (postResponse.getStatusCode() == HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE) { 430 // If we get an unsupported media type status, we're done. 431 return; 432 } 433 434 // Otherwise, we still might be OK if the server supports non-RDF source, 435 // in which case it might have treated the POST content as binary. Check 436 // the response Content-Type if we ask for the new resource. 437 assertTrue(postResponse.getStatusCode() == HttpStatus.SC_CREATED || 438 postResponse.getStatusCode() == HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, 439 "Expected either 415 Unsupported Media Type or 201 Created in response to POST"); 440 441 String location = postResponse.getHeader(LOCATION); 442 assertNotNull(location, "No Location response header on 201 Created response"); 443 444 try { 445 Response getResponse = buildBaseRequestSpecification() 446 .when() 447 .get(location) 448 .then().assertThat() 449 .statusCode(isSuccessful()) 450 .and().contentType(not(TEXT_TURTLE)) 451 .extract().response(); 452 453 // Also make sure there is no Link header indicating this is an RDF source. 454 assertFalse( 455 containsLinkHeader( 456 location, 457 LINK_REL_TYPE, 458 LDP.RDFSource.stringValue(), 459 location, 460 getResponse 461 ), 462 "Server should not respond with RDF source Link header when content was " + 463 "created with non-RDF Content-Type"); 464 } finally { 465 // Clean up. 466 buildBaseRequestSpecification().delete(location); 467 } 468 } 469 470 @Test( 471 groups = {MUST}, 472 description = "In RDF representations, LDP servers MUST interpret the null relative " 473 + "URI for the subject of triples in the LDPR representation in the request " 474 + "entity body as referring to the entity in the request body. Commonly, that " 475 + "entity is the model for the “to be created” LDPR, so triples whose subject " 476 + "is the null relative URI will usually result in triples in the created " 477 + "resource whose subject is the created resource.") 478 @SpecTest( 479 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-rdfnullrel", 480 testMethod = METHOD.AUTOMATED, 481 approval = STATUS.WG_APPROVED) 482 public void testNullRelativeUriPost() throws URISyntaxException { 483 skipIfMethodNotAllowed(HttpMethod.POST); 484 485 Model requestModel = postContent(); 486 487 // Do not pass a URI to RdfObjectMapper so that it stays as the null 488 // relative URI in the request body 489 Response postResponse = buildBaseRequestSpecification() 490 .contentType(TEXT_TURTLE) 491 .body(requestModel, new RdfObjectMapper()) // do not pass a URI 492 .expect() 493 .statusCode(HttpStatus.SC_CREATED) 494 .when() 495 .post(getResourceUri()); 496 497 String location = postResponse.getHeader(LOCATION); 498 assertNotNull(location, MSG_LOC_NOTFOUND); 499 500 try { 501 // Get the resource to check that the resource with a null relative URI 502 // was assigned the URI in the Location response header. 503 Model responseModel = getAsModel(location); 504 505 // Rename the subject of the request model so it matches the received location 506 // and intersect both models. 507 ResourceUtils.renameResource( 508 requestModel.getResource(""), getPrimaryTopic(responseModel, location).getURI()); 509 Model intersectionModel = requestModel.intersection(responseModel); 510 511 // OK if the result is not empty 512 assertTrue(intersectionModel.size() > 0, 513 "The resource created with URI <" 514 + location 515 + "> has nothing in common with the resource POSTed using the null relative URI." 516 ); 517 } finally { 518 // Delete the resource to clean up. 519 buildBaseRequestSpecification().delete(location); 520 } 521 } 522 523 @Test( 524 groups = {SHOULD}, 525 description = "LDP servers SHOULD assign the URI for the resource to be created " 526 + "using server application specific rules in the absence of a client hint.") 527 @SpecTest( 528 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-serverassignuri", 529 testMethod = METHOD.AUTOMATED, 530 approval = STATUS.WG_APPROVED) 531 public void testPostNoSlug() { 532 skipIfMethodNotAllowed(HttpMethod.POST); 533 534 // POST content with no Slug and see if the server assigns a URI. 535 Model model = postContent(); 536 Response postResponse = buildBaseRequestSpecification() 537 .contentType(TEXT_TURTLE) 538 .body(model, new RdfObjectMapper()) 539 .expect() 540 .statusCode(HttpStatus.SC_CREATED) 541 .header(LOCATION, HeaderMatchers.headerPresent()) 542 .when() 543 .post(getResourceUri()); 544 545 // Delete the resource to clean up. 546 buildBaseRequestSpecification().delete(postResponse.getHeader(LOCATION)); 547 } 548 549 @Test( 550 groups = {SHOULD}, 551 description = "LDP servers SHOULD allow clients to create new resources without " 552 + "requiring detailed knowledge of application-specific constraints. This " 553 + "is a consequence of the requirement to enable simple creation and " 554 + "modification of LDPRs. LDP servers expose these application-specific " 555 + "constraints as described in section 4.2.1 General.") 556 @SpecTest( 557 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-mincontraints", 558 testMethod = METHOD.AUTOMATED, 559 approval = STATUS.WG_APPROVED) 560 public void testCreateWithoutConstraints() throws URISyntaxException { 561 skipIfMethodNotAllowed(HttpMethod.POST); 562 563 // Create a resource with one statement (dcterms:title). 564 Model requestModel = ModelFactory.createDefaultModel(); 565 Resource resource = requestModel.createResource(""); 566 resource.addProperty(DCTerms.title, "Created by the LDP test suite"); 567 568 Response postResponse = buildBaseRequestSpecification() 569 .contentType(TEXT_TURTLE) 570 .body(requestModel, new RdfObjectMapper()) 571 .expect() 572 .statusCode(HttpStatus.SC_CREATED) 573 .when() 574 .post(getResourceUri()); 575 576 // Delete the resource to clean up. 577 String location = postResponse.getHeader(LOCATION); 578 if (location != null) { 579 buildBaseRequestSpecification().delete(location); 580 } 581 582 } 583 584 @Test( 585 groups = {SHOULD}, 586 description = "LDP servers that allow member creation via POST SHOULD NOT re-use URIs.") 587 @SpecTest( 588 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-dontreuseuris", 589 testMethod = METHOD.AUTOMATED, 590 approval = STATUS.WG_APPROVED, 591 comment = "Covers only part of the specification requirement. " 592 + "testRestrictUriReUseNoSlug covers the rest.") 593 public void testRestrictUriReUseSlug() throws URISyntaxException { 594 testRestrictUriReUse("uritest"); 595 } 596 597 @Test( 598 groups = {SHOULD}, 599 description = "LDP servers that allow member creation via POST SHOULD NOT re-use URIs.") 600 @SpecTest( 601 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-dontreuseuris", 602 testMethod = METHOD.AUTOMATED, 603 approval = STATUS.WG_APPROVED, 604 comment = "Covers only part of the specification requirement. " 605 + "testRestrictUriReUseSlug covers the rest.") 606 public void testRestrictUriReUseNoSlug() throws URISyntaxException { 607 testRestrictUriReUse(null); 608 } 609 610 @Test( 611 groups = {MUST}, 612 description = "LDP servers that support POST MUST include an Accept-Post " 613 + "response header on HTTP OPTIONS responses, listing post document " 614 + "media type(s) supported by the server.") 615 @SpecTest( 616 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-acceptposthdr", 617 testMethod = METHOD.AUTOMATED, 618 approval = STATUS.WG_APPROVED) 619 public void testAcceptPostResponseHeader() { 620 skipIfMethodNotAllowed(HttpMethod.POST); 621 622 Response optionsResponse = buildBaseRequestSpecification().expect() 623 .statusCode(isSuccessful()).when() 624 .options(getResourceUri()); 625 assertNotNull( 626 optionsResponse.getHeader(ACCEPT_POST), 627 "The HTTP OPTIONS response on container <" 628 + getResourceUri() 629 + "> did not include an Accept-Post response header, but it lists POST in its Allow response header." 630 ); 631 } 632 633 @Test( 634 groups = {SHOULD}, 635 description = "LDP servers SHOULD NOT allow HTTP PUT to update an LDPC’s " 636 + "containment triples; if the server receives such a request, it " 637 + "SHOULD respond with a 409 (Conflict) status code.") 638 @SpecTest( 639 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-put-mbrprops", 640 testMethod = METHOD.AUTOMATED, 641 approval = STATUS.WG_APPROVED) 642 public void testRejectPutModifyingContainmentTriples() { 643 String containerUri = getResourceUri(); 644 Response response = buildBaseRequestSpecification() 645 .header(ACCEPT, TEXT_TURTLE) 646 .expect().statusCode(isSuccessful()) 647 .when().get(containerUri); 648 String eTag = response.getHeader(ETAG); 649 Model model = response.as(Model.class, new RdfObjectMapper(containerUri)); 650 651 // Try to modify the ldp:contains triple. 652 Resource containerResource = model.getResource(containerUri); 653 containerResource.addProperty(model.createProperty(LDP.contains.stringValue()), 654 model.createResource("#" + System.currentTimeMillis())); 655 656 RequestSpecification putRequest = buildBaseRequestSpecification().contentType(TEXT_TURTLE); 657 if (eTag != null) { 658 putRequest.header(IF_MATCH, eTag); 659 } 660 putRequest.body(model, new RdfObjectMapper(containerUri)) 661 .expect().statusCode(not(isSuccessful())) 662 .when().put(containerUri); 663 } 664 665 @Test( 666 groups = {SHOULD}, 667 dependsOnMethods = {"testPutToCreate"}, 668 description = "LDP servers that allow LDPR creation via PUT SHOULD NOT " 669 + "re-use URIs. For RDF representations (LDP-RSs),the created " 670 + "resource can be thought of as an RDF named graph [rdf11-concepts].") 671 @SpecTest( 672 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-put-create", 673 testMethod = METHOD.AUTOMATED, 674 approval = STATUS.WG_APPROVED) 675 public void testRestrictPutReUseUri() { 676 String location = putToCreate(); 677 678 // Delete the resource. 679 buildBaseRequestSpecification() 680 .expect() 681 .statusCode(isSuccessful()) 682 .when() 683 .delete(location); 684 685 // Try to put to the same URI again. It should fail. 686 Model content = postContent(); 687 buildBaseRequestSpecification() 688 .given() 689 .contentType(TEXT_TURTLE) 690 .body(content, new RdfObjectMapper(location)) 691 .expect() 692 .statusCode(not(isSuccessful())) 693 .when() 694 .put(location); 695 } 696 697 @Test( 698 groups = {MUST}, 699 description = "When an LDPR identified by the object of a containment triple " 700 + "is deleted, the LDPC server MUST also remove the LDPR from the " 701 + "containing LDPC by removing the corresponding containment triple.") 702 @SpecTest( 703 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-del-contremovesconttriple", 704 testMethod = METHOD.AUTOMATED, 705 approval = STATUS.WG_APPROVED) 706 public void testDeleteRemovesContainmentTriple() throws URISyntaxException { 707 skipIfMethodNotAllowed(HttpMethod.POST); 708 709 Model model = postContent(); 710 String containerUri = getResourceUri(); 711 Response postResponse = buildBaseRequestSpecification() 712 .contentType(TEXT_TURTLE) 713 .body(model, new RdfObjectMapper()) 714 .expect() 715 .statusCode(HttpStatus.SC_CREATED) 716 .when() 717 .post(getResourceUri()); 718 719 // POST support is optional. Only test delete if the POST succeeded. 720 if (postResponse.getStatusCode() != HttpStatus.SC_CREATED) { 721 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 722 "HTTP POST failed with status " + postResponse.getStatusCode(), skipLog); 723 } 724 725 String location = postResponse.getHeader(LOCATION); 726 assertNotNull(location, MSG_LOC_NOTFOUND); 727 728 // TODO: Check if delete is supported on location..... 729 730 // Delete the resource 731 buildBaseRequestSpecification() 732 .expect() 733 .statusCode(isSuccessful()) 734 .when() 735 .delete(location); 736 737 // Test the membership triple 738 Response getResponse = buildBaseRequestSpecification() 739 .header(ACCEPT, TEXT_TURTLE) 740 .header(PREFER, include(PREFER_CONTAINMENT)) // hint to the server that we want containment triples 741 .expect() 742 .statusCode(isSuccessful()) 743 .when() 744 .get(containerUri); 745 Model containerModel = getResponse.as(Model.class, new RdfObjectMapper(containerUri)); 746 Resource container = containerModel.getResource(containerUri); 747 748 assertFalse( 749 container.hasProperty(containerModel 750 .createProperty(LDP.contains.stringValue()), 751 containerModel.getResource(location) 752 ), 753 "The LDPC server must remove the corresponding containment triple when an LDPR is deleted." 754 ); 755 } 756 757 @Test( 758 groups = {SHOULD}, 759 description = "LDP servers are RECOMMENDED to support HTTP PATCH as the " 760 + "preferred method for updating an LDPC's empty-container triples. ") 761 @SpecTest( 762 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-patch-req", 763 testMethod = METHOD.AUTOMATED, 764 approval = STATUS.WG_APPROVED) 765 public void testPatchMethod() { 766 assertTrue( 767 supports(HttpMethod.PATCH), 768 "Container <" 769 + getResourceUri() 770 + "> has not advertised PATCH support through its HTTP OPTIONS response." 771 ); 772 773 // TODO: Actually try to patch the containment triples. 774 } 775 776 @Test( 777 groups = {MAY}, 778 description = "The representation of a LDPC MAY have an rdf:type " 779 + "of ldp:Container for Linked Data Platform Container. Non-normative " 780 + "note: LDPCs might have additional types, like any LDP-RS. ") 781 @SpecTest( 782 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-typecontainer", 783 testMethod = METHOD.AUTOMATED, 784 approval = STATUS.WG_APPROVED) 785 public void testRdfTypeLdpContainer() { 786 String container = getResourceUri(); 787 Model m = buildBaseRequestSpecification() 788 .header(ACCEPT, TEXT_TURTLE) 789 .expect() 790 .statusCode(isSuccessful()) 791 .when() 792 .get(container).as(Model.class, new RdfObjectMapper(container)); 793 assertTrue(m.contains(m.getResource(container), RDF.type, m.getResource(LDP.Container.stringValue())), 794 "LDPC does not have an rdf:type of ldp:Container"); 795 } 796 797 /** 798 * @see <a href="http://tools.ietf.org/html/rfc5023#page-30">RFC 5023 - The Atom Publishing Protocol: 9.7. The Slug Header</a> 799 */ 800 @Test( 801 groups = {MAY}, 802 description = "LDP servers MAY allow clients to suggest " 803 + "the URI for a resource created through POST, " 804 + "using the HTTP Slug header as defined in [RFC5023]. " 805 + "LDP adds no new requirements to this usage, so its " 806 + "presence functions as a client hint to the server " 807 + "providing a desired string to be incorporated into the " 808 + "server's final choice of resource URI.") 809 @SpecTest( 810 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-slug", 811 testMethod = METHOD.AUTOMATED, 812 approval = STATUS.WG_APPROVED) 813 public void testServerHonorsSlug() { 814 skipIfMethodNotAllowed(HttpMethod.POST); 815 816 // Come up with a (probably) unique slug header. Try to find one that 817 // the server will accept unchanged, so avoid special characters or very 818 // long strings. 819 String slug = RandomStringUtils.randomAlphabetic(6); 820 Model content = postContent(); 821 String location = post(content, slug); 822 823 try { 824 // Per RFC 5023, the server may change the slug so this isn't a 825 // perfect test. We can at least ignore case when looking at the 826 // return location. 827 assertTrue(location.toLowerCase().contains(slug.toLowerCase()), "Slug is not part of the return Location"); 828 } finally { 829 // Clean up. 830 buildBaseRequestSpecification().delete(location); 831 } 832 } 833 834 @Test( 835 groups = {MUST}, 836 description = "LDP servers SHOULD accept a request entity " 837 + "body with a request header of Content-Type with " 838 + "value of application/ld+json [JSON-LD].") 839 @SpecTest( 840 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-jsonld", 841 testMethod = METHOD.AUTOMATED, 842 approval = STATUS.WG_APPROVED) 843 public void testPostJsonLd() { 844 skipIfMethodNotAllowed(HttpMethod.POST); 845 846 // POST content as JSON-LD. 847 Model model = postContent(); 848 Response postResponse = buildBaseRequestSpecification() 849 .contentType(APPLICATION_LD_JSON) 850 .body(model, new RdfObjectMapper()) 851 .expect() 852 .statusCode(HttpStatus.SC_CREATED) 853 .header(LOCATION, HeaderMatchers.headerPresent()) 854 .when() 855 .post(getResourceUri()); 856 857 // Get the resource back to make sure it wasn't simply treated as a 858 // binary resource. We should be able to get it as text/turtle. 859 final String location = postResponse.getHeader(LOCATION); 860 Response getResponse = buildBaseRequestSpecification() 861 .header(ACCEPT, TEXT_TURTLE) 862 .expect() 863 .statusCode(isSuccessful()) 864 .contentType(HeaderMatchers.isTurtleCompatibleContentType()) 865 .when() 866 .get(location); 867 assertFalse( 868 containsLinkHeader( 869 location, 870 LINK_REL_TYPE, 871 LDP.NonRDFSource.stringValue(), 872 location, 873 getResponse 874 ), 875 "Resources POSTed using JSON-LD should be treated as RDF source"); 876 } 877 878 @Test( 879 groups = {MUST}, 880 description = "Each Linked Data Platform Container MUST " 881 + "also be a conforming Linked Data Platform RDF Source.") 882 @SpecTest( 883 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-isldpr", 884 testMethod = METHOD.INDIRECT, 885 approval = STATUS.WG_APPROVED, 886 coveredByTests = {RdfSourceTest.class}, 887 coveredByGroups = {MUST}) 888 public void testConformsContainerRdfResource() { 889 throw new org.testng.SkipException("Covered indirectly by the MUST tests defined in RdfSourceTest class"); 890 } 891 892 @Override 893 protected boolean restrictionsOnTestResourceContent() { 894 // Always true for containers because their containment triples can't be 895 // modified via PUT. 896 return true; 897 } 898 899 /** 900 * Tests that LDP servers do not reuse URIs after deleting resources. 901 * 902 * @param slug the slug header for the request or null if no slug 903 * @throws URISyntaxException on bad URIs 904 * @see #testRestrictUriReUseSlug() 905 * @see #testRestrictUriReUseNoSlug() 906 */ 907 private void testRestrictUriReUse(String slug) throws URISyntaxException { 908 skipIfMethodNotAllowed(HttpMethod.POST); 909 910 // POST two resources with the same Slug header and content to make sure 911 // they have different URIs. 912 Model content = postContent(); 913 String loc1 = post(content, slug); 914 915 // TODO: Test if DELETE is supported before trying to delete the 916 // resource. 917 // Delete the resource to make sure the server doesn't reuse the URI 918 // below. 919 buildBaseRequestSpecification().expect().statusCode(isSuccessful()).when().delete(loc1); 920 921 String loc2 = post(content, slug); 922 try { 923 assertNotEquals(loc1, loc2, "Server reused URIs for POSTed resources."); 924 } finally { 925 buildBaseRequestSpecification().delete(loc2); 926 } 927 } 928 929 private String post(Model content, String slug) { 930 RequestSpecification spec = buildBaseRequestSpecification().contentType(TEXT_TURTLE); 931 if (slug != null) { 932 spec.header(SLUG, slug); 933 } 934 Response post = spec.body(content, new RdfObjectMapper()).expect() 935 .statusCode(HttpStatus.SC_CREATED).when() 936 .post(getResourceUri()); 937 String location = post.getHeader(LOCATION); 938 assertNotNull(location, MSG_LOC_NOTFOUND); 939 940 return location; 941 } 942 943 /** 944 * Attempts to create a new resource using PUT. 945 * 946 * @return the location of the created resource 947 * @see #testPutToCreate() 948 * @see #testRestrictPutReUseUri() 949 */ 950 protected String putToCreate() { 951 // Build a unique URI for the PUT request. 952 URI target = UriBuilder.fromUri(getResourceUri()) 953 .path(UUID.randomUUID().toString()).build(); 954 Model model = postContent(); 955 Response response = buildBaseRequestSpecification().contentType(TEXT_TURTLE) 956 .body(model, new RdfObjectMapper("")).expect() 957 .statusCode(HttpStatus.SC_CREATED).when().put(target); 958 959 String location = response.getHeader(LOCATION); 960 if (location == null) { 961 return target.toString(); 962 } 963 964 return location; 965 } 966 967}