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}