001package org.w3.ldp.testsuite.test; 002 003import com.hp.hpl.jena.rdf.model.Model; 004import com.hp.hpl.jena.rdf.model.Resource; 005import com.jayway.restassured.response.Header; 006import com.jayway.restassured.response.Response; 007import org.apache.http.HttpStatus; 008import org.testng.annotations.BeforeClass; 009import org.testng.annotations.Optional; 010import org.testng.annotations.Parameters; 011import org.testng.annotations.Test; 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.http.HttpMethod; 018import org.w3.ldp.testsuite.mapper.RdfObjectMapper; 019import org.w3.ldp.testsuite.matcher.HeaderMatchers; 020import org.w3.ldp.testsuite.vocab.LDP; 021 022import java.io.IOException; 023import java.net.URISyntaxException; 024import java.util.List; 025 026import static org.hamcrest.Matchers.notNullValue; 027import static org.testng.Assert.*; 028import static org.w3.ldp.testsuite.http.HttpHeaders.*; 029import static org.w3.ldp.testsuite.http.LdpPreferences.PREFER_MEMBERSHIP; 030import static org.w3.ldp.testsuite.http.LdpPreferences.PREFER_MINIMAL_CONTAINER; 031import static org.w3.ldp.testsuite.http.MediaTypes.TEXT_TURTLE; 032import static org.w3.ldp.testsuite.matcher.HttpStatusSuccessMatcher.isSuccessful; 033 034public class DirectContainerTest extends CommonContainerTest { 035 private String directContainer; 036 037 @Parameters({"directContainer", "auth"}) 038 public DirectContainerTest(@Optional String directContainer, @Optional String auth) throws IOException { 039 super(auth); 040 this.directContainer = directContainer; 041 } 042 043 @BeforeClass(alwaysRun = true) 044 public void hasDirectContainer() { 045 if (directContainer == null) { 046 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 047 "No directContainer parameter provided in testng.xml. Skipping ldp:DirectContainer tests.", 048 skipLog); 049 } 050 } 051 052 // MUST: 4.2.8.2 LDP servers must indicate their support for HTTP Methods by 053 // responding to a HTTP OPTIONS request on the LDPR's URL with the HTTP 054 // Method tokens in the HTTP response header Allow. 055 056 // MUST: 5.2.1.2 The representation of an LDPC may have an rdf:type of only 057 // one of ldp:Container for Linked Data Platform Container. 058 059 @Test( 060 groups = {MUST}, 061 description = "LDP servers exposing LDPCs MUST advertise their " 062 + "LDP support by exposing a HTTP Link header with a " 063 + "target URI matching the type of container (see below) " 064 + "the server supports, and a link relation type of type " 065 + "(that is, rel='type') in all responses to requests made " 066 + "to the LDPC's HTTP Request-URI.") 067 @SpecTest( 068 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-linktypehdr", 069 testMethod = METHOD.AUTOMATED, 070 approval = STATUS.WG_APPROVED, 071 comment = "Covers only part of the specification requirement. " 072 + "IndirectContainerTest.testContainerSupportsHttpLinkHeader and " 073 + "BasicContainerTest.testContainerSupportsHttpLinkHeader " 074 + "covers the rest.") 075 public void testHttpLinkHeader() { 076 Response response = buildBaseRequestSpecification().header(ACCEPT, TEXT_TURTLE) 077 .expect().statusCode(HttpStatus.SC_OK).when() 078 .get(directContainer); 079 assertTrue( 080 containsLinkHeader( 081 directContainer, 082 LINK_REL_TYPE, 083 LDP.DirectContainer.stringValue(), 084 directContainer, 085 response 086 ), 087 "LDP DirectContainers must advertise their LDP support by exposing a HTTP Link header with a URI matching <" + LDP.DirectContainer.stringValue() + "> and rel='type'"); 088 } 089 090 @Test( 091 groups = {SHOULD, "ldpMember"}, 092 description = "LDP Direct Containers SHOULD use the ldp:member predicate " 093 + "as an LDPC's membership predicate if there is no obvious " 094 + "predicate from an application vocabulary to use.") 095 @SpecTest( 096 specRefUri = LdpTestSuite.SPEC_URI + "#ldpdc-mbrpred", 097 testMethod = METHOD.AUTOMATED, 098 approval = STATUS.WG_APPROVED) 099 public void testUseMemberPredicate() throws URISyntaxException { 100 Model containerModel = getAsModel(directContainer); 101 Resource container = containerModel.getResource(directContainer); 102 if (container.hasProperty(containerModel.createProperty(LDP.isMemberOfRelation.stringValue()))) { 103 throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(), 104 "This test does not apply to containers using the ldp:isMemberOfRelation membership pattern.", 105 skipLog); 106 } 107 Resource hasMemberRelation = container.getPropertyResourceValue(containerModel.createProperty(LDP.hasMemberRelation.stringValue())); 108 assertEquals(LDP.member.stringValue(), hasMemberRelation.getURI(), "LDP Direct Containers should use the ldp:member predicate if " 109 + "there is no obvious predicate from the application vocabulary. You can disable this test using the 'testLdpMember' parameter in testng.xml."); 110 } 111 112 @Test( 113 groups = {MUST}, 114 description = "Each LDP Direct Container representation MUST contain exactly " 115 + "one triple whose subject is the LDPC URI, whose predicate is the " 116 + "ldp:membershipResource, and whose object is the LDPC's membership-" 117 + "constant-URI. Commonly the LDPC's URI is the membership-constant-URI," 118 + " but LDP does not require this.") 119 @SpecTest( 120 specRefUri = LdpTestSuite.SPEC_URI + "#ldpdc-containres", 121 testMethod = METHOD.AUTOMATED, 122 approval = STATUS.WG_APPROVED) 123 public void testMemberResourceTriple() throws URISyntaxException { 124 Model containerModel = getAsModel(directContainer); 125 Resource container = containerModel.getResource(directContainer); 126 Resource membershipResource = container.getPropertyResourceValue(containerModel.createProperty(LDP.membershipResource.stringValue())); 127 assertNotNull(membershipResource); 128 } 129 130 @Test( 131 groups = {MUST}, 132 description = "Each LDP Direct Container representation must contain exactly " 133 + "one triple whose subject is the LDPC URI, and whose predicate " 134 + "is either ldp:hasMemberRelation or ldp:isMemberOfRelation. " 135 + "The object of the triple is constrained by other sections, " 136 + "such as ldp:hasMemberRelation or ldp:isMemberOfRelation, " 137 + "based on the membership triple pattern used by the container.") 138 @SpecTest( 139 specRefUri = LdpTestSuite.SPEC_URI + "#ldpdc-containtriples", 140 testMethod = METHOD.AUTOMATED, 141 approval = STATUS.WG_APPROVED) 142 public void testMemberRelationOrIsMemberOfRelationTripleExists() throws URISyntaxException { 143 Model containerModel = getAsModel(directContainer); 144 Resource container = containerModel.getResource(directContainer); 145 Resource hasMemberRelation = container.getPropertyResourceValue(containerModel.createProperty(LDP.hasMemberRelation.stringValue())); 146 Resource isMemberOfRelation = container.getPropertyResourceValue(containerModel.createProperty(LDP.isMemberOfRelation.stringValue())); 147 if (hasMemberRelation == null) { 148 assertNotNull(isMemberOfRelation, "LDP DirectContainer must have either ldp:hasMemberRelation or ldp:isMemberOfRelation"); 149 } else { 150 assertNull(isMemberOfRelation, "LDP DirectContainer cannot have both ldp:hasMemberRelation and ldp:isMemberOfRelation"); 151 } 152 } 153 154 @Test( 155 groups = {MUST}, 156 description = "LDP Direct Containers MUST behave as if they have a " 157 + "(LDPC URI, ldp:insertedContentRelation , ldp:MemberSubject)" 158 + " triple, but LDP imposes no requirement to materialize such " 159 + "a triple in the LDP-DC representation.") 160 @SpecTest( 161 specRefUri = LdpTestSuite.SPEC_URI + "#ldpdc-indirectmbr-basic", 162 testMethod = METHOD.AUTOMATED, 163 approval = STATUS.WG_APPROVED) 164 public void testActAsIfInsertedContentRelationTripleExists() { 165 // TODO: This should probably just treat this as an indirect test, see issue #206 166 testPostResourceUpdatesTriples(); 167 } 168 169 @Test( 170 groups = {MUST}, 171 description = "When a successful HTTP POST request to an LDPC results " 172 + "in the creation of an LDPR, the LDPC MUST update its " 173 + "membership triples to reflect that addition, and the resulting " 174 + "membership triple MUST be consistent with any LDP-defined " 175 + "predicates it exposes.") 176 @SpecTest( 177 specRefUri = LdpTestSuite.SPEC_URI + "#ldpdc-post-createdmbr-member", 178 testMethod = METHOD.AUTOMATED, 179 approval = STATUS.WG_APPROVED) 180 public void testPostResourceUpdatesTriples() { 181 skipIfMethodNotAllowed(HttpMethod.POST); 182 183 Model model = postContent(); 184 Response postResponse = buildBaseRequestSpecification().contentType(TEXT_TURTLE).body(model, new RdfObjectMapper()) 185 .expect().statusCode(HttpStatus.SC_CREATED).header(LOCATION, notNullValue()) 186 .when().post(directContainer); 187 188 String location = postResponse.getHeader(LOCATION); 189 try { 190 Response getResponse = buildBaseRequestSpecification() 191 .header(ACCEPT, TEXT_TURTLE) 192 .header(PREFER, include(PREFER_MEMBERSHIP)) // request all membership triples 193 .expect() 194 .statusCode(isSuccessful()) 195 .when() 196 .get(directContainer); 197 Model containerModel = getResponse.as(Model.class, new RdfObjectMapper(directContainer)); 198 Resource container = containerModel.getResource(directContainer); 199 Resource membershipResource = container.getPropertyResourceValue(containerModel.createProperty(LDP.membershipResource.stringValue())); 200 Resource hasMemberRelation = container.getPropertyResourceValue(containerModel.createProperty(LDP.hasMemberRelation.stringValue())); 201 assertNotNull(membershipResource); 202 203 if (hasMemberRelation != null) { 204 // Make sure the resource is a member of the container. 205 assertTrue(membershipResource.hasProperty(containerModel.createProperty(hasMemberRelation.getURI()), containerModel.createResource(location))); 206 } 207 } finally { 208 // Delete the resource to clean up. 209 buildBaseRequestSpecification().delete(location); 210 } 211 } 212 213 @Test( 214 groups = {MUST}, 215 description = "When an LDPR identified by the object of a membership " 216 + "triple which was originally created by the LDP-DC is deleted, " 217 + "the LDPC server MUST also remove the corresponding membership triple.") 218 @SpecTest( 219 specRefUri = LdpTestSuite.SPEC_URI + "#ldpdc-del-contremovesmbrtriple", 220 testMethod = METHOD.AUTOMATED, 221 approval = STATUS.WG_APPROVED) 222 public void testDeleteResourceUpdatesTriples() { 223 skipIfMethodNotAllowed(HttpMethod.POST); 224 225 // Create a resource. 226 Model model = postContent(); 227 Response postResponse = buildBaseRequestSpecification() 228 .contentType(TEXT_TURTLE) 229 .body(model, new RdfObjectMapper()) 230 .expect() 231 .statusCode(HttpStatus.SC_CREATED) 232 .header(LOCATION, HeaderMatchers.headerPresent()) 233 .when() 234 .post(directContainer); 235 236 String location = postResponse.getHeader(LOCATION); 237 238 // Track whether we've deleted the resource, so we can clean up properly on test failures. 239 boolean deleted = false; 240 241 try { 242 // Test the membership triple 243 Response getResponse = buildBaseRequestSpecification() 244 .header(ACCEPT, TEXT_TURTLE) 245 .header(PREFER, include(PREFER_MEMBERSHIP)) // request all membership triple regardless of membership patterns 246 .expect() 247 .statusCode(isSuccessful()) 248 .when() 249 .get(directContainer); 250 Model containerModel = getResponse.as(Model.class, new RdfObjectMapper(directContainer)); 251 252 Resource container = containerModel.getResource(directContainer); 253 Resource membershipResource = container.getPropertyResourceValue(containerModel.createProperty(LDP.membershipResource.stringValue())); 254 Resource hasMemberRelation = container.getPropertyResourceValue(containerModel.createProperty(LDP.hasMemberRelation.stringValue())); 255 Resource isMemberOfRelation = null; 256 assertNotNull(membershipResource, MSG_MBRRES_NOTFOUND); 257 258 // First verify the membership triples exist 259 if (hasMemberRelation != null) { 260 assertTrue(membershipResource.hasProperty(containerModel.createProperty(hasMemberRelation.getURI()), containerModel.getResource(location)), 261 "The LDPC server must have a corresponding membership triple when an LDPR is added (hasMemberRelation)."); 262 } else { 263 // Not if membership triple is not of form: (container, membership predicate, member), it may be the inverse. 264 isMemberOfRelation = container.getPropertyResourceValue(containerModel.createProperty(LDP.isMemberOfRelation.stringValue())); 265 // Check the container for the triple. 266 if (!containerModel.contains(containerModel.getResource(location), containerModel.createProperty(isMemberOfRelation.getURI()), membershipResource)) { 267 List<Header> preferenceAppliedHeaders = getResponse.getHeaders().getList(PREFERNCE_APPLIED); 268 assertFalse( 269 !preferenceAppliedHeaders.isEmpty() && hasReturnRepresentation(preferenceAppliedHeaders), 270 "Server responded with Preference-Applied header for including membership triples, but membership triple is missing."); 271 } 272 273 // Check the resource has the triple as well. 274 Model memberResourceModel = getAsModel(location); 275 assertTrue(memberResourceModel.contains(memberResourceModel.getResource(location), memberResourceModel.createProperty(isMemberOfRelation.getURI()), membershipResource), 276 "The LDPC server must have a corresponding membership triple when an LDPR is added (isMemberOfRelation)."); 277 } 278 279 // Delete the resource 280 deleted = true; 281 buildBaseRequestSpecification().expect().statusCode(isSuccessful()).when().delete(location); 282 283 // Get the updated membership resource 284 getResponse = buildBaseRequestSpecification() 285 .header(ACCEPT, TEXT_TURTLE) 286 .header(PREFER, include(PREFER_MEMBERSHIP)) // request all membership triple regardless of membership patterns 287 .expect() 288 .statusCode(isSuccessful()) 289 .when() 290 .get(directContainer); 291 containerModel = getResponse.as(Model.class, new RdfObjectMapper(directContainer)); 292 membershipResource = containerModel.getResource(membershipResource.getURI()); 293 294 // Now verify the membership triples DON"T exist 295 if (hasMemberRelation != null) { 296 assertFalse(membershipResource.hasProperty(containerModel.createProperty(hasMemberRelation.getURI()), containerModel.getResource(location)), 297 "The LDPC server must remove the corresponding membership triple when an LDPR is deleted (hasMemberRelation)."); 298 } else { 299 // Not if membership triple is not of form: (container, membership predicate, member), it may be the inverse. 300 isMemberOfRelation = container.getPropertyResourceValue(containerModel.createProperty(LDP.isMemberOfRelation.stringValue())); 301 assertFalse(containerModel.contains(containerModel.getResource(location), containerModel.createProperty(isMemberOfRelation.getURI()), membershipResource), 302 "The LDPC server must remove the corresponding membership triple when an LDPR is deleted (isMemberOfRelation)."); 303 } 304 } finally { 305 // If an assertion failed before we could delete the resource, clean up now. 306 if (!deleted) { 307 buildBaseRequestSpecification().delete(location); 308 } 309 } 310 } 311 312 @Test( 313 groups = {MUST}, 314 description = "Each LDP Direct Container MUST also be a " 315 + "conforming LDP Container in section 5.2 " 316 + "Container along the following restrictions.") 317 @SpecTest( 318 specRefUri = LdpTestSuite.SPEC_URI + "#ldpdc-are-ldpcs", 319 testMethod = METHOD.INDIRECT, 320 approval = STATUS.WG_APPROVED, 321 coveredByTests = {CommonContainerTest.class}, 322 coveredByGroups = {MUST}) 323 public void testConformsDcLdpContainer() { 324 throw new org.testng.SkipException("Covered indirectly by the MUST tests defined in CommonContainerTest class"); 325 } 326 327 private boolean hasMembershipTriples(Model containerModel) { 328 Resource container = containerModel.getResource(directContainer); 329 Resource membershipResource = container.getPropertyResourceValue(containerModel.createProperty(LDP.membershipResource.stringValue())); 330 Resource hasMemberRelation = container.getPropertyResourceValue(containerModel.createProperty(LDP.hasMemberRelation.stringValue())); 331 assertNotNull(membershipResource, MSG_MBRRES_NOTFOUND); 332 333 // First verify the membership triples exist 334 if (hasMemberRelation != null) { 335 return membershipResource.hasProperty(containerModel.createProperty(hasMemberRelation.getURI())); 336 } 337 338 // Not if membership triple is not of form: (container, membership predicate, member), it may be the inverse. 339 Resource isMemberOfRelation = container.getPropertyResourceValue(containerModel.createProperty(LDP.isMemberOfRelation.stringValue())); 340 return containerModel.contains(null, containerModel.createProperty(isMemberOfRelation.getURI()), membershipResource); 341 } 342 343 @Test( 344 groups = {SHOULD}, 345 description = "LDP servers SHOULD respect all of a client's LDP-defined " 346 + "hints, for example which subsets of LDP-defined state the " 347 + "client is interested in processing, to influence the set of " 348 + "triples returned in representations of an LDPC, particularly for " 349 + "large LDPCs. See also [LDP-PAGING].") 350 @SpecTest( 351 specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-prefer", 352 testMethod = METHOD.AUTOMATED, 353 approval = STATUS.WG_APPROVED, 354 comment = "Covers only part of the specification requirement. ") 355 public void testPreferMembershipTriples() { 356 Response response; 357 Model model; 358 359 // Ask for membership triples. 360 response = buildBaseRequestSpecification() 361 .header(ACCEPT, TEXT_TURTLE) 362 .header(PREFER, include(PREFER_MEMBERSHIP)) // request all membership triples 363 .expect() 364 .statusCode(isSuccessful()) 365 .when() 366 .get(directContainer); 367 model = response.as(Model.class, new RdfObjectMapper(directContainer)); 368 369 // Assumes the container is not empty. 370 checkPreferenceAppliedHeader(response); 371 assertTrue(hasMembershipTriples(model), "Container does not have membership triples"); 372 373 // Ask for a minimal container. 374 response = buildBaseRequestSpecification() 375 .header(ACCEPT, TEXT_TURTLE) 376 .header(PREFER, include(PREFER_MINIMAL_CONTAINER)) // request no membership triples 377 .expect() 378 .statusCode(isSuccessful()) 379 .when() 380 .get(directContainer); 381 model = response.as(Model.class, new RdfObjectMapper(directContainer)); 382 383 checkPreferenceAppliedHeader(response); 384 assertFalse(hasMembershipTriples(model), "Container has membership triples when minimal container was requested"); 385 386 // Ask to omit membership. 387 response = buildBaseRequestSpecification() 388 .header(ACCEPT, TEXT_TURTLE) 389 .header(PREFER, omit(PREFER_MEMBERSHIP)) // request no membership triples 390 .expect() 391 .statusCode(isSuccessful()) 392 .when() 393 .get(directContainer); 394 model = response.as(Model.class, new RdfObjectMapper(directContainer)); 395 396 checkPreferenceAppliedHeader(response); 397 assertFalse(hasMembershipTriples(model), "Container has membership triples when client requested server omit them"); 398 399 // Ask for a minimal container, but include membership. (Example from spec.) 400 response = buildBaseRequestSpecification() 401 .header(ACCEPT, TEXT_TURTLE) 402 .header(PREFER, include(PREFER_MINIMAL_CONTAINER, PREFER_MEMBERSHIP)) 403 .expect() 404 .statusCode(isSuccessful()) 405 .when() 406 .get(directContainer); 407 model = response.as(Model.class, new RdfObjectMapper(directContainer)); 408 409 // Assumes the container is not empty. 410 checkPreferenceAppliedHeader(response); 411 assertTrue(hasMembershipTriples(model), "Container does not have membership triples"); 412 assertFalse(model.contains(model.getResource(directContainer), model.createProperty(LDP.contains.stringValue())), 413 "Container has containment triples when minimal container was requested"); 414 } 415 416 @Override 417 protected String getResourceUri() { 418 return directContainer; 419 } 420 421}