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}