001package org.w3.ldp.testsuite.test; 002 003import com.github.jsonldjava.core.JsonLdError; 004import com.github.jsonldjava.core.JsonLdProcessor; 005import com.github.jsonldjava.utils.JsonUtils; 006import com.hp.hpl.jena.rdf.model.Model; 007import com.hp.hpl.jena.rdf.model.Property; 008import com.hp.hpl.jena.rdf.model.Resource; 009import com.hp.hpl.jena.rdf.model.StmtIterator; 010import com.hp.hpl.jena.vocabulary.DCTerms; 011import com.hp.hpl.jena.vocabulary.RDF; 012import com.jayway.restassured.response.Response; 013import org.apache.http.HttpStatus; 014import org.apache.marmotta.commons.vocabulary.LDP; 015import org.jboss.resteasy.spi.Failure; 016import org.testng.annotations.Optional; 017import org.testng.annotations.Parameters; 018import org.testng.annotations.Test; 019import org.w3.ldp.testsuite.LdpTestSuite; 020import org.w3.ldp.testsuite.annotations.SpecTest; 021import org.w3.ldp.testsuite.annotations.SpecTest.METHOD; 022import org.w3.ldp.testsuite.annotations.SpecTest.STATUS; 023import org.w3.ldp.testsuite.exception.SkipException; 024import org.w3.ldp.testsuite.exception.SkipNotTestableException; 025import org.w3.ldp.testsuite.http.HttpMethod; 026import org.w3.ldp.testsuite.mapper.RdfObjectMapper; 027import org.w3.ldp.testsuite.matcher.HeaderMatchers; 028 029import java.io.IOException; 030import java.net.URI; 031import java.net.URISyntaxException; 032 033import static org.hamcrest.MatcherAssert.assertThat; 034import static org.hamcrest.Matchers.not; 035import static org.hamcrest.text.IsEmptyString.isEmptyOrNullString; 036import static org.testng.Assert.*; 037import static org.w3.ldp.testsuite.http.HttpHeaders.*; 038import static org.w3.ldp.testsuite.http.MediaTypes.TEXT_TURTLE; 039import static org.w3.ldp.testsuite.matcher.HeaderMatchers.isValidEntityTag; 040import static org.w3.ldp.testsuite.matcher.HttpStatus4xxRangeMatcher.is4xxRange; 041import static org.w3.ldp.testsuite.matcher.HttpStatusSuccessMatcher.isSuccessful; 042 043/** 044 * Tests all RDF source LDP resources, including containers and member resources. 045 */ 046public abstract class RdfSourceTest extends CommonResourceTest { 047 048 private static final String MSG_NO_READ_ONLY_PROPERTY = "Skipping test because we have no read-only properties to PUT." 049 + " Server-managed properties are specified using the \"read-only-prop\" command-line parameter."; 050 private static final String MSG_PUT_RESTRICTIONS = "Skipping test because there are restrictions on PUT content for this resource. " 051 + "The requirement needs to be tested manually."; 052 053 private static final String UNKNOWN_PROPERTY = "http://example.com/ns#comment"; 054 055 /** A relative URI to use for testing. */ 056 protected static final String RELATIVE_URI = "relatedResource"; 057 058 @Parameters("auth") 059 public RdfSourceTest(@Optional String auth) throws IOException { 060 super(auth); 061 } 062 063 @Test( 064 groups = {MUST}, 065 description = "LDP servers MUST assign the default base-URI " 066 + "for [RFC3987] relative-URI resolution to be the HTTP " 067 + "Request-URI when the resource already exists, and to " 068 + "the URI of the created resource when the request results " 069 + "in the creation of a new resource.") 070 @Parameters("relativeUri") 071 @SpecTest( 072 specRefUri = LdpTestSuite.SPEC_URI + "#ldpr-gen-defbaseuri", 073 testMethod = METHOD.AUTOMATED, 074 approval = STATUS.WG_APPROVED) 075 public void testRelativeUriResolutionPut(@Optional String relativeUri) { 076 skipIfMethodNotAllowed(HttpMethod.PUT); 077 078 if (restrictionsOnTestResourceContent()) { 079 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 080 MSG_PUT_RESTRICTIONS, skipLog); 081 } 082 083 if (relativeUri == null) { 084 relativeUri = RELATIVE_URI; 085 } 086 087 String resourceUri = getResourceUri(); 088 Response response = buildBaseRequestSpecification() 089 .header(ACCEPT, TEXT_TURTLE) 090 .expect() 091 .statusCode(isSuccessful()) 092 .header(ETAG, isValidEntityTag()) 093 .when() 094 .get(resourceUri); 095 096 String eTag = response.getHeader(ETAG); 097 Model model = response.as(Model.class, new RdfObjectMapper(resourceUri)); 098 099 // Add a statement with a relative URI. 100 getPrimaryTopic(model, resourceUri).addProperty(DCTerms.relation, model.getResource(relativeUri)); 101 102 // Put the resource back using relative URIs. 103 Response put = buildBaseRequestSpecification() 104 .contentType(TEXT_TURTLE).header(IF_MATCH, eTag) 105 .body(model, new RdfObjectMapper("")) // keep URIs relative 106 .when().put(resourceUri); 107 if (!isSuccessful().matches(put.getStatusCode())) { 108 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 109 "Cannot verify relative URI resolution because the PUT request failed. Skipping test.", 110 skipLog); 111 } 112 113 // Get the resource again to verify its content. 114 model = buildBaseRequestSpecification() 115 .header(ACCEPT, TEXT_TURTLE) 116 .expect() 117 .statusCode(isSuccessful()) 118 .when() 119 .get(resourceUri).as(Model.class, new RdfObjectMapper(resourceUri)); 120 121 // Verify the change. 122 String relationAbsoluteUri = resolveIfRelative(resourceUri, relativeUri); 123 assertTrue( 124 model.contains( 125 getPrimaryTopic(model, resourceUri), 126 DCTerms.relation, 127 model.getResource(relationAbsoluteUri) 128 ), 129 "Response does not have expected triple: <" + resourceUri + "> dcterms:relation <" + relationAbsoluteUri + ">." 130 ); 131 } 132 133 @Test( 134 groups = {MUST}, 135 description = "If a HTTP PUT is accepted on an existing resource, " 136 + "LDP servers MUST replace the entire persistent state of " 137 + "the identified resource with the entity representation " 138 + "in the body of the request.") 139 @SpecTest( 140 specRefUri = LdpTestSuite.SPEC_URI + "#ldpr-put-replaceall", 141 testMethod = METHOD.AUTOMATED, 142 approval = STATUS.WG_APPROVED) 143 public void testPutReplacesResource() { 144 putReplaceResource(true); 145 } 146 147 @Test( 148 groups = {MUST}, 149 description = "LDP servers SHOULD allow clients to update resources " 150 + "without requiring detailed knowledge of server-specific " 151 + "constraints. This is a consequence of the requirement to " 152 + "enable simple creation and modification of LDPRs.") 153 @SpecTest( 154 specRefUri = LdpTestSuite.SPEC_URI + "#ldpr-put-simpleupdate", 155 testMethod = METHOD.AUTOMATED, 156 approval = STATUS.WG_APPROVED) 157 public void testPutSimpleUpdate() { 158 putReplaceResource(false); 159 } 160 161 @Override 162 @Test( 163 groups = {MUST}, 164 description = "LDP servers MUST provide an RDF representation " 165 + "for LDP-RSs. The HTTP Request-URI of the LDP-RS is " 166 + "typically the subject of most triples in the response.") 167 @SpecTest( 168 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-gen-rdf", 169 testMethod = METHOD.AUTOMATED, 170 approval = STATUS.WG_APPROVED) 171 public void testGetResource() { 172 // Make sure we can get the resource itself and the response is 173 // valid RDF. Turtle is a required media type, so this request 174 // should succeed for all LDP-RS. 175 buildBaseRequestSpecification() 176 .header(ACCEPT, TEXT_TURTLE) 177 .expect().statusCode(HttpStatus.SC_OK).contentType(HeaderMatchers.isTurtleCompatibleContentType()) 178 .when().get(getResourceUri()).as(Model.class, new RdfObjectMapper(getResourceUri())); 179 } 180 181 @Test( 182 groups = {SHOULD}, 183 description = "LDP-RSs representations SHOULD have at least one " 184 + "rdf:type set explicitly. This makes the representations " 185 + "much more useful to client applications that don’t " 186 + "support inferencing.") 187 @SpecTest( 188 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-gen-atleast1rdftype", 189 testMethod = METHOD.AUTOMATED, 190 approval = STATUS.WG_APPROVED) 191 public void testContainsRdfType() { 192 Model containerModel = getAsModel(getResourceUri()); 193 Resource r = getPrimaryTopic(containerModel, getResourceUri()); 194 assertTrue(r.hasProperty(RDF.type), "LDP-RS representation has no explicit rdf:type"); 195 } 196 197 @Test( 198 groups = {MAY}, 199 description = "The representation of a LDP-RS MAY have an rdf:type " 200 + "of ldp:RDFSource for Linked Data Platform RDF Source.") 201 @SpecTest( 202 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-rdftype", 203 testMethod = METHOD.AUTOMATED, 204 approval = STATUS.WG_APPROVED) 205 public void testTypeRdfSource() { 206 Model containerModel = getAsModel(getResourceUri()); 207 Resource r = containerModel.getResource(getResourceUri()); 208 assertTrue( 209 r.hasProperty( 210 RDF.type, 211 containerModel.createResource(LDP.RDFSource.stringValue()) 212 ), 213 "LDP-RS representation does not have rdf:type ldp:RDFSource"); 214 } 215 216 @Test( 217 groups = {SHOULD, MANUAL}, 218 description = "LDP-RSs SHOULD reuse existing vocabularies instead of " 219 + "creating their own duplicate vocabulary terms. In addition " 220 + "to this general rule, some specific cases are covered by " 221 + "other conformance rules.") 222 @SpecTest( 223 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-gen-reusevocab", 224 testMethod = METHOD.MANUAL, 225 approval = STATUS.WG_APPROVED, 226 steps = {"Given a URL for a RDF Source, perform a GET using an RDF content type", 227 "Inspect the content to ensure standard terms are used. For example, if things like " 228 + "ex:label or ex:title are used, instead of DCTERMS or RDFS, then the test " 229 + "should fail."}) 230 public void testReUseVocabularies() { 231 throw new SkipNotTestableException(Thread.currentThread().getStackTrace()[1].getMethodName(), skipLog); 232 } 233 234 @Test( 235 groups = {SHOULD, MANUAL}, 236 description = "LDP-RSs predicates SHOULD use standard vocabularies such " 237 + "as Dublin Core [DC-TERMS], RDF [rdf11-concepts] and RDF " 238 + "Schema [rdf-schema], whenever possible.") 239 @SpecTest( 240 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-gen-reusevocabsuchas", 241 testMethod = METHOD.MANUAL, 242 approval = STATUS.WG_APPROVED, 243 steps = {"Given a URL for a RDF Source, perform a GET using an RDF content type", 244 "Inspect the content to ensure standard terms are used. For example, if things like " 245 + "ex:label or ex:title are used, instead of DCTERMS or RDFS, then the test " 246 + "should fail."}) 247 public void testUseStandardVocabularies() throws URISyntaxException { 248 // TODO: Consider ideas for testUseStandardVocabularies (see comment) 249 /* Possible ideas: 250 fetch resource, look for known vocabulary term 251 URIs. Also can look at total number of terms and have a threshold 252 of differences, say 100 terms and < 5 standard predicates would be odd. 253 Also could look for similar/like short predicate short names or even 254 look for owl:sameAs. 255 */ 256 throw new SkipNotTestableException(Thread.currentThread().getStackTrace()[1].getMethodName(), skipLog); 257 } 258 259 @Test( 260 groups = {MUST, MANUAL}, 261 description = "LDP servers MUST NOT require LDP clients to implement inferencing " 262 + "in order to recognize the subset of content defined by LDP. Other " 263 + "specifications built on top of LDP may require clients to implement " 264 + "inferencing [rdf11-concepts]. The practical implication is that all " 265 + "content defined by LDP must be explicitly represented, unless noted " 266 + "otherwise within this document.") 267 @SpecTest( 268 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-gen-noinferencing", 269 testMethod = METHOD.MANUAL, 270 approval = STATUS.WG_APPROVED, 271 steps = {"Perform a GET on a URL for the reource requesting an RDF content type", 272 "Inspect the results (both headers and content) for missing terms. Additionally " 273 + "could run an inferencing tool and compare results, seeing if needed information" 274 + "should have been explicitly listed by the server."}) 275 public void testRestrictClientInference() { 276 throw new SkipNotTestableException(Thread.currentThread().getStackTrace()[1].getMethodName(), skipLog); 277 } 278 279 @Test( 280 groups = {MUST}, 281 description = "LDP servers must respond with a Turtle representation of the " 282 + "requested LDP-RS when the request includes an Accept header specifying " 283 + "text/turtle, unless HTTP content negotiation requires a different outcome [turtle].") 284 @SpecTest( 285 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-get-turtle", 286 testMethod = METHOD.AUTOMATED, 287 approval = STATUS.WG_APPROVED) 288 public void testGetResourceAcceptTurtle() { 289 // Accept: text/turtle 290 buildBaseRequestSpecification().header(ACCEPT, TEXT_TURTLE) 291 .expect().statusCode(isSuccessful()).contentType(HeaderMatchers.isTurtleCompatibleContentType()) 292 .when().get(getResourceUri()).as(Model.class, new RdfObjectMapper(getResourceUri())); 293 294 // More complicated Accept header 295 buildBaseRequestSpecification().header(ACCEPT, "text/turtle;q=0.9,application/json;q=0.8") 296 .expect().statusCode(isSuccessful()).contentType(HeaderMatchers.isTurtleCompatibleContentType()) 297 .when().get(getResourceUri()).as(Model.class, new RdfObjectMapper(getResourceUri())); 298 } 299 300 @Test( 301 groups = {SHOULD}, 302 description = "LDP servers should respond with a text/turtle representation of the " 303 + "requested LDP-RS whenever the Accept request header is absent [turtle].") 304 @SpecTest( 305 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-get-conneg", 306 testMethod = METHOD.AUTOMATED, 307 approval = STATUS.WG_APPROVED) 308 public void testGetResourceAsTurtleNoAccept() { 309 // No Accept header 310 buildBaseRequestSpecification() 311 .expect().statusCode(isSuccessful()).contentType(HeaderMatchers.isTurtleCompatibleContentType()) 312 .when().get(getResourceUri()).as(Model.class, new RdfObjectMapper(getResourceUri())); 313 } 314 315 @Test( 316 groups = {MUST}, 317 description = " LDP servers must respond with a application/ld+json " 318 + "representation of the requested LDP-RS when the request " 319 + "includes an Accept header, unless content negotiation or " 320 + "Turtle support requires a different outcome [JSON-LD].") 321 @SpecTest( 322 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-get-jsonld", 323 testMethod = METHOD.AUTOMATED, 324 approval = STATUS.WG_APPROVED) 325 public void testJsonLdRepresentation() throws IOException, JsonLdError { 326 Response response = buildBaseRequestSpecification() 327 .header(ACCEPT, "application/ld+json, application/json;q=0.5") 328 .expect() 329 .statusCode(isSuccessful()) 330 .contentType(HeaderMatchers.isJsonLdCompatibleContentType()) 331 .when() 332 .get(getResourceUri()); 333 334 // Make sure it parses as JSON-LD. 335 Object json = JsonUtils.fromInputStream(response.asInputStream()); 336 JsonLdProcessor.toRDF(json); // throws JsonLdError if not valid 337 } 338 339 @Test( 340 groups = {MUST}, 341 description = "LDP servers MUST publish any constraints on LDP clients’ " 342 + "ability to create or update LDPRs, by adding a Link header " 343 + "with rel='http://www.w3.org/ns/ldp#constrainedBy' [RFC5988] " 344 + "to all responses to requests which fail due to violation of " 345 + "those constraints.") 346 @Parameters({ "readOnlyProp" }) 347 @SpecTest( 348 specRefUri = LdpTestSuite.SPEC_URI + "#ldpr-gen-pubclireqs", 349 testMethod = METHOD.AUTOMATED, 350 approval = STATUS.WG_APPROVED, 351 comment = "Covers only part of the specification requirement. " 352 + "testPublishConstraintsUnknownProp covers the rest.") 353 public void testPublishConstraintsReadOnlyProp(@Optional String readOnlyProp) { 354 skipIfMethodNotAllowed(HttpMethod.PUT); 355 356 if (readOnlyProp == null) { 357 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 358 MSG_NO_READ_ONLY_PROPERTY, skipLog); 359 } 360 361 expectPut4xxConstrainedBy(readOnlyProp); 362 } 363 364 @Test( 365 groups = {MUST}, 366 description = "LDP servers MUST publish any constraints on LDP clients’ " 367 + "ability to create or update LDPRs, by adding a Link header " 368 + "with rel='http://www.w3.org/ns/ldp#constrainedBy' [RFC5988] " 369 + "to all responses to requests which fail due to violation of " 370 + "those constraints.") 371 @SpecTest( 372 specRefUri = LdpTestSuite.SPEC_URI + "#ldpr-gen-pubclireqs", 373 testMethod = METHOD.AUTOMATED, 374 approval = STATUS.WG_APPROVED, 375 comment = "Covers only part of the specification requirement. " 376 + "testPublishConstraintsReadOnlyProp covers the rest.") 377 public void testPublishConstraintsUnknownProp() { 378 skipIfMethodNotAllowed(HttpMethod.PUT); 379 expectPut4xxConstrainedBy(UNKNOWN_PROPERTY); 380 } 381 382 383 @Test( 384 groups = {MUST}, 385 description = "If an otherwise valid HTTP PUT request is received that " 386 + "attempts to change properties the server does not allow " 387 + "clients to modify, LDP servers MUST respond with a 4xx range " 388 + "status code (typically 409 Conflict)") 389 @Parameters("readOnlyProp") 390 @SpecTest( 391 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-put-servermanagedprops", 392 testMethod = METHOD.AUTOMATED, 393 approval = STATUS.WG_APPROVED, 394 comment = "Covers only part of the specification requirement. " 395 + "test4xxErrorHasResponseBody covers the rest.") 396 public void testPutReadOnlyProperties4xxStatus(@Optional String readOnlyProp) { 397 skipIfMethodNotAllowed(HttpMethod.PUT); 398 399 if (readOnlyProp == null) { 400 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 401 MSG_NO_READ_ONLY_PROPERTY, skipLog); 402 } 403 404 expectPut4xxStatus(readOnlyProp); 405 } 406 407 @Test( 408 groups = {SHOULD}, 409 description = "LDP servers SHOULD provide a corresponding response body containing " 410 + "information about which properties could not be persisted. The " 411 + "format of the 4xx response body is not constrained by LDP.") 412 @Parameters("readOnlyProp") 413 @SpecTest( 414 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-put-servermanagedprops", 415 testMethod = METHOD.AUTOMATED, 416 approval = STATUS.WG_APPROVED, 417 comment = "Covers only part of the specification requirement. " 418 + "testPutReadOnlyProperties4xxStatus covers the rest.") 419 public void test4xxErrorHasResponseBody(@Optional String readOnlyProp) { 420 skipIfMethodNotAllowed(HttpMethod.PUT); 421 422 if (readOnlyProp == null) { 423 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 424 MSG_NO_READ_ONLY_PROPERTY, skipLog); 425 } 426 427 expectPut4xxResponseBody(readOnlyProp); 428 } 429 430 @Test( 431 groups = {MUST}, 432 description = "If an otherwise valid HTTP PUT request is received that " 433 + "contains properties the server chooses not to persist, " 434 + "e.g. unknown content, LDP servers MUST respond with an " 435 + "appropriate 4xx range status code [HTTP11].") 436 @SpecTest( 437 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-put-failed", 438 testMethod = METHOD.AUTOMATED, 439 approval = STATUS.WG_APPROVED, 440 comment = "Covers only part of the specification requirement. " 441 + "testResponsePropertiesNotPersisted covers the rest.") 442 public void testPutPropertiesNotPersisted() { 443 skipIfMethodNotAllowed(HttpMethod.PUT); 444 expectPut4xxStatus(UNKNOWN_PROPERTY); 445 } 446 447 @Test( 448 groups = {SHOULD}, 449 description = "LDP servers SHOULD provide a corresponding response body containing " 450 + "information about which properties could not be persisted. The " 451 + "format of the 4xx response body is not constrained by LDP. LDP " 452 + "servers expose these application-specific constraints as described " 453 + "in section 4.2.1 General.") 454 @SpecTest( 455 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-put-failed", 456 testMethod = METHOD.AUTOMATED, 457 approval = STATUS.WG_APPROVED, 458 comment = "Covers only part of the specification requirement. " 459 + "testPutPropertiesNotPersisted covers the rest.") 460 public void testResponsePropertiesNotPersisted() { 461 skipIfMethodNotAllowed(HttpMethod.PUT); 462 expectPut4xxResponseBody(UNKNOWN_PROPERTY); 463 } 464 465 @Test( 466 groups = {MUST}, 467 description = "Each LDP RDF Source MUST also be a conforming LDP " 468 + "Resource as defined in section 4.2 Resource, along " 469 + "with the restrictions in this section.") 470 @SpecTest( 471 specRefUri = LdpTestSuite.SPEC_URI + "#ldprs-are-ldpr", 472 testMethod = METHOD.INDIRECT, 473 approval = STATUS.WG_APPROVED, 474 coveredByTests = {CommonResourceTest.class}, 475 coveredByGroups = {MUST}) 476 public void testConformsRdfSourceLdpResource() { 477 throw new org.testng.SkipException("Covered indirectly by the MUST tests defined in CommonResourceTest class"); 478 } 479 480 protected void modifyProperty(Model m, String resourceUri, String property) { 481 Resource r = getPrimaryTopic(m, resourceUri); 482 Property p = m.createProperty(property); 483 r.removeAll(p); 484 // Don't sweat the value or datatype since we expect the PUT to fail anyway. 485 r.addProperty(p, "modified"); 486 } 487 488 protected void expectPut4xxConstrainedBy(String invalidProp) { 489 Response putResponse = expectPut4xxStatus(invalidProp); 490 final String uri = getResourceUri(); 491 String constrainedBy = getFirstLinkForRelation(uri, LINK_REL_CONSTRAINEDBY, uri, putResponse); 492 assertNotNull(constrainedBy, "Response did not contain a Link header with rel=\"http://www.w3.org/ns/ldp#constrainedBy\""); 493 494 try { 495 final URI linkUri = new URI(constrainedBy); 496 497 if (linkUri.getScheme().startsWith("http")) { 498 // Make sure we can GET the constrainedBy link. 499 buildBaseRequestSpecification() 500 .expect() 501 .statusCode(isSuccessful()) 502 .when() 503 .get(constrainedBy); 504 } 505 } catch (URISyntaxException e) { 506 throw new IllegalArgumentException(e); 507 } 508 } 509 510 protected Response expectPut4xxStatus(String invalidProp) { 511 // Get the resource. 512 String resourceUri = getResourceUri(); 513 Response getResponse = buildBaseRequestSpecification() 514 .header(ACCEPT, TEXT_TURTLE) 515 .expect() 516 .statusCode(isSuccessful()) 517 .header(ETAG, isValidEntityTag()) 518 .when() 519 .get(resourceUri); 520 521 String eTag = getResponse.getHeader(ETAG); 522 Model m = getResponse.as(Model.class, new RdfObjectMapper(resourceUri)); 523 modifyProperty(m, resourceUri, invalidProp); 524 525 Response putResponse = buildBaseRequestSpecification() 526 .contentType(TEXT_TURTLE) 527 .header(IF_MATCH, eTag) 528 .body(m, new RdfObjectMapper(resourceUri)) 529 .when() 530 .put(resourceUri); 531 if (isSuccessful().matches(putResponse.getStatusCode())) { 532 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 533 "Skipping test because PUT request was successful.", skipLog); 534 } 535 536 assertThat(putResponse.statusCode(), is4xxRange()); 537 538 return putResponse; 539 } 540 541 protected void expectPut4xxResponseBody(String invalidProp) { 542 Response putResponse = expectPut4xxStatus(invalidProp); 543 assertThat(putResponse.body().asString(), not(isEmptyOrNullString())); 544 } 545 546 /** 547 * Are there any restrictions on the content of the resource being tested 548 * (i.e., the resource returned by {@link #getResourceUri()}). Should be 549 * true for LDP containers since PUT can't directly modify containment 550 * triples. 551 * 552 * <p> 553 * This method is used for {@link #testPutReplacesResource()} and 554 * {@link #testRelativeUriResolutionPut(String)}. 555 * </p> 556 * 557 * @return true if there are restrictions on what triples are allowed; false 558 * otherwise 559 560 * @see #restrictionsOnPostContent() 561 */ 562 protected boolean restrictionsOnTestResourceContent() { 563 // Should be the same as POST content unless this is a container. 564 // (Overridden by subclasses as necessary.) 565 return restrictionsOnPostContent(); 566 } 567 568 protected void putReplaceResource(boolean continueOnError) { 569 skipIfMethodNotAllowed(HttpMethod.PUT); 570 571 if (restrictionsOnTestResourceContent()) { 572 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 573 MSG_PUT_RESTRICTIONS, skipLog); 574 } 575 576 String resourceUri = getResourceUri(); 577 Response response = buildBaseRequestSpecification() 578 .header(ACCEPT, TEXT_TURTLE) 579 .expect() 580 .statusCode(isSuccessful()) 581 .header(ETAG, isValidEntityTag()) 582 .when() 583 .get(resourceUri); 584 585 String eTag = response.getHeader(ETAG); 586 Model originalModel = response.as(Model.class, new RdfObjectMapper(resourceUri)); 587 Resource resource = getPrimaryTopic(originalModel, resourceUri); 588 589 assertNotNull(resource, "Expected to location resource in response for "+resourceUri); 590 591 // Update the model with updated title 592 resource.removeAll(DCTerms.title); 593 // Make sure the title is unique 594 final String UPDATED_TITLE = "This resources content has been replaced (" + System.currentTimeMillis() + ")"; 595 originalModel.add(resource, DCTerms.title, UPDATED_TITLE); 596 597 response = buildBaseRequestSpecification() 598 .contentType(TEXT_TURTLE) 599 .header(IF_MATCH, eTag) 600 .body(originalModel, new RdfObjectMapper(resourceUri)) // relative URI 601 .when() 602 .put(resourceUri); 603 604 if (!isSuccessful().matches(response.getStatusCode())) { 605 if (continueOnError) { 606 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 607 "Skipping test because the PUT failed. The server may have restrictions on its content.", 608 skipLog); 609 } else { 610 throw new Failure("Unable to do simple update on resource, received code: "+response.getStatusLine()); 611 } 612 } 613 614 // Get the resource again to see what's there. 615 response = buildBaseRequestSpecification() 616 .header(ACCEPT, TEXT_TURTLE) 617 .expect() 618 .statusCode(isSuccessful()) 619 .header(ETAG, isValidEntityTag()) 620 .when() 621 .get(resourceUri); 622 eTag = response.getHeader(ETAG); 623 Model updatedModel = response.as(Model.class, new RdfObjectMapper(resourceUri)); 624 625 // Make sure it's the only title (we removed all before PUTting) 626 Resource updatedResource = getPrimaryTopic(updatedModel, resourceUri); 627 StmtIterator titleProps = updatedResource.listProperties(DCTerms.title); 628 int titlePropSize = titleProps.toSet().size(); 629 assertEquals(titlePropSize, 1, "Updated resource should only contain one dcterms:title changes but instead found "+titlePropSize+" changes"); 630 } 631}