001package org.w3.ldp.testsuite.test;
002
003import com.hp.hpl.jena.rdf.model.Model;
004import com.jayway.restassured.response.Response;
005import org.apache.commons.io.IOUtils;
006import org.apache.commons.lang3.StringUtils;
007import org.apache.http.HttpStatus;
008import org.apache.marmotta.commons.util.HashUtils;
009import org.apache.marmotta.commons.vocabulary.LDP;
010import org.testng.Assert;
011import org.testng.annotations.*;
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.mapper.RdfObjectMapper;
018import org.w3.ldp.testsuite.matcher.HeaderMatchers;
019
020import java.io.IOException;
021
022import static org.testng.Assert.assertEquals;
023import static org.testng.Assert.assertTrue;
024import static org.w3.ldp.testsuite.http.HttpHeaders.*;
025import static org.w3.ldp.testsuite.http.MediaTypes.TEXT_TURTLE;
026import static org.w3.ldp.testsuite.matcher.HttpStatusNotFoundOrGoneMatcher.isNotFoundOrGone;
027import static org.w3.ldp.testsuite.matcher.HttpStatusSuccessMatcher.isSuccessful;
028
029/**
030 * Tests Non-RDF Source LDP resources.
031 */
032public class NonRDFSourceTest extends CommonResourceTest {
033        private final static String SETUP_ERROR = "ERROR: Could not create test resource for NonRDFSourceTest. Skipping tests.";
034
035        private String container;
036        /** Resource for CommonResourceTest */
037        private String nonRdfSource;
038
039        @Parameters("auth")
040        public NonRDFSourceTest(@Optional String auth) throws IOException {
041                super(auth);
042        }
043
044        @Parameters({ "basicContainer", "directContainer", "indirectContainer" })
045        @BeforeSuite(alwaysRun = true)
046        public void createTestResource(@Optional String basicContainer, @Optional String directContainer, @Optional String indirectContainer) {
047                if (StringUtils.isNotBlank(basicContainer)) {
048                        container = basicContainer;
049                } else if (StringUtils.isNotBlank(directContainer)) {
050                        container = directContainer;
051                } else if (StringUtils.isNotBlank(indirectContainer)) {
052                        container = indirectContainer;
053                } else {
054                        throw new SkipException(Thread.currentThread().getStackTrace()[1].getMethodName(),
055                                        "No root container provided in testng.xml. Skipping LDP Non-RDF Source (LDP-NR) tests.",
056                                        skipLog);
057                }
058
059                final String slug = "non-rdf-source",
060                                file = "test.png",
061                                mimeType = "image/png";
062
063                // Create a resource to use for CommonResourceTest.
064                try {
065                        Response response = buildBaseRequestSpecification()
066                                        .header(SLUG, slug)
067                                        .body(IOUtils.toByteArray(getClass().getResourceAsStream("/" + file)))
068                                        .contentType(mimeType)
069                                        .post(container);
070                        if (response.getStatusCode() != HttpStatus.SC_CREATED) {
071                                System.err.println(SETUP_ERROR);
072                                System.err.println("POST failed with status code: " + response.getStatusCode());
073                                System.err.println();
074                                return;
075                        }
076
077                        nonRdfSource = response.getHeader(LOCATION);
078                        if (nonRdfSource == null) {
079                                System.err.println(SETUP_ERROR);
080                                System.err.println("Location response header missing");
081                                System.err.println();
082                                return;
083                        }
084                } catch (Exception e) {
085                        System.err.println(SETUP_ERROR);
086                        e.printStackTrace();
087                }
088        }
089
090        @AfterSuite(alwaysRun = true)
091        public void deleteTestResource() {
092                if (nonRdfSource != null) {
093                        buildBaseRequestSpecification().delete(nonRdfSource);
094                }
095        }
096
097        @Override
098        protected String getResourceUri() {
099                if (nonRdfSource == null) {
100                        throw new SkipException(Thread.currentThread().getStackTrace()[2].getMethodName(),
101                                        "Skipping test because test resource is null.", skipLog);
102                }
103
104                return nonRdfSource;
105        }
106
107        @Test(
108                        groups = {MAY},
109                        description = "LDP servers may accept an HTTP POST of non-RDF " +
110                                        "representations (LDP-NRs) for creation of any kind of " +
111                                        "resource, for example binary resources.")
112        @SpecTest(
113                        specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-createbins",
114                        testMethod = METHOD.AUTOMATED,
115                        approval = STATUS.WG_APPROVED,
116                        comment = "Covers only part of the specification requirement. "
117                                        + "testPostResourceAndGetFromContainer covers the rest.")
118        public void testPostNonRDFSource() throws IOException {
119                // Test constants
120                final String slug = "test",
121                                file = slug + ".png",
122                                mimeType = "image/png";
123
124                // Make sure we can post binary resources
125                Response response = postNonRDFSource(slug, file, mimeType);
126                buildBaseRequestSpecification().delete(response.getHeader(LOCATION));
127        }
128
129        @Test(
130                        groups = {MAY},
131                        description = "LDP servers may accept an HTTP POST of non-RDF " +
132                                        "representations (LDP-NRs) for creation of any kind of " +
133                                        "resource, for example binary resources.")
134        @SpecTest(
135                        specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-createbins",
136                        testMethod = METHOD.AUTOMATED,
137                        approval = STATUS.WG_APPROVED,
138                        comment = "Covers only part of the specification requirement. "
139                                        + "testPostNonRDFSource covers the rest.")
140        public void testPostResourceAndGetFromContainer() throws IOException {
141                // Test constants
142                final String slug = "test",
143                                file = slug + ".png",
144                                mimeType = "image/png";
145
146                // Make sure we can post binary resources
147                Response response = postNonRDFSource(slug, file, mimeType);
148                try {
149                        // Check the container contains the new resource
150                        Model model = buildBaseRequestSpecification()
151                                        .header(ACCEPT, TEXT_TURTLE)
152                                .expect()
153                                        .statusCode(HttpStatus.SC_OK)
154                                        .contentType(HeaderMatchers.isTurtleCompatibleContentType())
155                                .get(container)
156                                        .body().as(Model.class, new RdfObjectMapper(container));
157
158                        assertTrue(model.contains(model.createResource(container), model.createProperty(LDP.contains.stringValue()), model.createResource(response.getHeader(LOCATION))));
159                } finally {
160                        buildBaseRequestSpecification().delete(response.getHeader(LOCATION));
161                }
162        }
163
164        @Test(
165                        groups = {MAY},
166                        description = "LDP servers may host a mixture of LDP-RSs and LDP-NRs. " +
167                                        "For example, it is common for LDP servers to need to host binary " +
168                                        "or text resources that do not have useful RDF representations.")
169        @SpecTest(
170                        specRefUri = LdpTestSuite.SPEC_URI + "#ldpr-gen-binary",
171                        testMethod = METHOD.AUTOMATED,
172                        approval = STATUS.WG_APPROVED)
173        public void testPostResourceGetBinary() throws IOException {
174                // Test constants
175                final String slug = "test",
176                                file = slug + ".png",
177                                mimeType = "image/png";
178
179                // Make sure we can post binary resources
180                Response response = postNonRDFSource(slug, file, mimeType);
181                try {
182                        // And then check we get the binary back
183                        final String expectedMD5 = HashUtils.md5sum(NonRDFSourceTest.class.getResourceAsStream("/" + file));
184                        final byte[] binary = buildBaseRequestSpecification()
185                                        .header(ACCEPT, mimeType)
186                                .expect()
187                                        .statusCode(HttpStatus.SC_OK)
188                                        .contentType(mimeType)
189                                        .header(ETAG, HeaderMatchers.isValidEntityTag())
190                                .when()
191                                        .get(response.getHeader(LOCATION))
192                                        .body().asByteArray();
193                        assertEquals(expectedMD5, HashUtils.md5sum(binary), "md5sum");
194                } finally {
195                        buildBaseRequestSpecification().delete(response.getHeader(LOCATION));
196                }
197        }
198
199        @Test(
200                        groups = {MAY},
201                        description = "Each LDP Non-RDF Source must also be a conforming LDP Resource. " +
202                                        "LDP Non-RDF Sources may not be able to fully express their state using RDF.")
203        @SpecTest(
204                        specRefUri = LdpTestSuite.SPEC_URI + "#ldpnr-are-ldpr",
205                        testMethod = METHOD.AUTOMATED,
206                        approval = STATUS.WG_APPROVED)
207        public void testPostResourceGetMetadataAndBinary() throws IOException {
208                // Test constants
209                final String slug = "test",
210                                file = slug + ".png",
211                                mimeType = "image/png";
212
213                // Make sure we can post binary resources
214                Response response = postNonRDFSource(slug, file, mimeType);
215                String location = response.getHeader(LOCATION);
216
217                try {
218                        String associatedRdfSource = getFirstLinkForRelation(location, LINK_REL_DESCRIBEDBY, container, response);
219                        Assert.assertNotNull(associatedRdfSource, "No Link response header with relation \"describedby\" " +
220                                        "and anchor parameter matching the newly-created resource URI");
221
222                        // And then check we get the metadata of back
223                        buildBaseRequestSpecification()
224                                        .header(ACCEPT, TEXT_TURTLE)
225                                .expect()
226                                        .statusCode(HttpStatus.SC_OK)
227                                        .contentType(HeaderMatchers.isTurtleCompatibleContentType())
228                                        .header(ETAG, HeaderMatchers.isValidEntityTag())
229                                .when()
230                                        .get(associatedRdfSource)
231                                        .as(Model.class, new RdfObjectMapper(associatedRdfSource));
232
233                        // And the binary too
234                        final String expectedMD5 = HashUtils.md5sum(NonRDFSourceTest.class.getResourceAsStream("/" + file));
235                        final byte[] binary = buildBaseRequestSpecification()
236                                        .header(ACCEPT, mimeType)
237                                .expect()
238                                        .statusCode(HttpStatus.SC_OK)
239                                        .contentType(mimeType)
240                                        .header(ETAG, HeaderMatchers.isValidEntityTag())
241                                .when()
242                                        .get(location)
243                                        .body().asByteArray();
244                        assertEquals(expectedMD5, HashUtils.md5sum(binary), "md5sum");
245                } finally {
246                        buildBaseRequestSpecification().delete(location);
247                }
248        }
249
250        @Test(
251                        groups = {MAY},
252                        description = "LDP servers exposing an LDP Non-RDF Source may advertise this by exposing " +
253                                        "a HTTP Link header with a target URI of http://www.w3.org/ns/ldp#NonRDFSource, and " +
254                                        "a link relation type of type (that is, rel='type') in responses to requests made to " +
255                                        "the LDP-NR's HTTP Request-URI.")
256        @SpecTest(
257                        specRefUri = LdpTestSuite.SPEC_URI + "#ldpnr-type",
258                        testMethod = METHOD.AUTOMATED,
259                        approval = STATUS.WG_APPROVED)
260        public void testPostResourceAndCheckLink() throws IOException {
261                // Test constants
262                final String slug = "test",
263                                file = slug + ".png",
264                                mimeType = "image/png";
265
266                // Make sure we can post binary resources
267                Response postResponse = postNonRDFSource(slug, file, mimeType);
268                final String location = postResponse.getHeader(LOCATION);
269
270                try {
271                        // And then check the link when requesting the LDP-NR
272                        Response getResponse = buildBaseRequestSpecification()
273                                .expect()
274                                        .statusCode(isSuccessful())
275                                        .header(ETAG, HeaderMatchers.isValidEntityTag())
276                                .when()
277                                        .get(location);
278                        Assert.assertTrue(containsLinkHeader(
279                                        location,
280                                        LINK_REL_TYPE,
281                                        LDP.NonRDFSource.stringValue(),
282                                        location,
283                                        getResponse
284                        ));
285                } finally {
286                        buildBaseRequestSpecification().delete(postResponse.header(LOCATION));
287                }
288        }
289
290        @Test(
291                        groups = {MAY},
292                        description = "Upon successful creation of an LDP-NR (HTTP status code of 201-Created and " +
293                                        "URI indicated by Location response header), LDP servers may create an associated " +
294                                        "LDP-RS to contain data about the newly created LDP-NR. If a LDP server creates " +
295                                        "this associated LDP-RS it must indicate its location on the HTTP response using " +
296                                        "the HTTP Link response header with link relation describedby and href to be the " +
297                                        "URI of the associated LDP-RS resource.")
298        @SpecTest(
299                        specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-post-createbinlinkmetahdr",
300                        testMethod = METHOD.AUTOMATED,
301                        approval = STATUS.WG_APPROVED)
302        public void testPostResourceAndCheckAssociatedResource() throws IOException {
303                // Test constants
304                final String slug = "test",
305                                file = slug + ".png",
306                                mimeType = "image/png";
307
308                // Make sure we can post binary resources
309                Response postResponse = postNonRDFSource(slug, file, mimeType);
310                String location = postResponse.getHeader(LOCATION);
311                try {
312                        String associatedRdfSource = getFirstLinkForRelation(location, LINK_REL_DESCRIBEDBY, location, postResponse);
313                        Assert.assertNotNull(associatedRdfSource, "No Link response header with relation \"describedby\" " +
314                                        "and anchor parameter matching the newly-created resource URI");
315
316                        // Check the link when requesting the LDP-NS
317                        Response getResponse = buildBaseRequestSpecification()
318                                .expect()
319                                        .statusCode(isSuccessful())
320                                        .header(ETAG, HeaderMatchers.headerPresent())
321                                .when()
322                                        .get(location);
323                        Assert.assertTrue(containsLinkHeader(
324                                        location,
325                                        LINK_REL_DESCRIBEDBY,
326                                        associatedRdfSource,
327                                        location,
328                                        getResponse
329                        ));
330
331                        // And then check the associated LDP-RS is actually there
332                        buildBaseRequestSpecification()
333                                        .header(ACCEPT, TEXT_TURTLE)
334                                .expect()
335                                        .statusCode(isSuccessful())
336                                        .contentType(HeaderMatchers.isTurtleCompatibleContentType())
337                                        .header(ETAG, HeaderMatchers.isValidEntityTag())
338                                .when()
339                                        .get(associatedRdfSource);
340                } finally {
341                        buildBaseRequestSpecification().delete(location);
342                }
343        }
344
345        @Test(
346                        groups = {MUST},
347                        description = "When a contained LDPR is deleted, and the LDPC server created an"+
348                                        "associated LDP-RS (see the LDPC POST section), the LDPC server must also"+
349                                        "delete the associated LDP-RS it created.",
350                        dependsOnMethods = "testPostResourceAndCheckAssociatedResource")
351        @SpecTest(
352                        specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-del-contremovescontres",
353                        testMethod = METHOD.AUTOMATED,
354                        approval = STATUS.WG_APPROVED)
355        public void testDeleteNonRDFSourceDeletesAssociatedResource() throws IOException {
356                // Test constants
357                final String slug = "test",
358                                file = slug + ".png",
359                                mimeType = "image/png";
360
361                // Make sure we can post binary resources
362                Response postResponse = postNonRDFSource(slug, file, mimeType);
363                String location = postResponse.getHeader(LOCATION);
364                boolean deleted = false;
365
366                try {
367                        String associatedRdfSource = getFirstLinkForRelation(location, LINK_REL_DESCRIBEDBY, container, postResponse);
368                        Assert.assertNotNull(associatedRdfSource, "No Link response header with relation \"describedby\" " +
369                                        "and anchor parameter matching the newly-created resource URI");
370
371                        // And then check the associated LDP-RS is actually there
372                        buildBaseRequestSpecification()
373                                        .header(ACCEPT, TEXT_TURTLE)
374                                .expect()
375                                        .statusCode(isSuccessful())
376                                        .contentType(HeaderMatchers.isTurtleCompatibleContentType())
377                                .when()
378                                        .get(associatedRdfSource);
379
380                        // Delete the LDP-NR.
381                        deleted = true;
382                        buildBaseRequestSpecification()
383                                .expect()
384                                        .statusCode(isSuccessful())
385                                .when()
386                                        .delete(location);
387
388                        // Check that the associated LDP-RS is also deleted.
389                        buildBaseRequestSpecification()
390                                        .header(ACCEPT, TEXT_TURTLE)
391                                .expect()
392                                        .statusCode(isNotFoundOrGone())
393                                .when()
394                                        .get(associatedRdfSource);
395                } finally {
396                        // Clean up if an assertion failed before we could delete the resource.
397                        if (!deleted) {
398                                buildBaseRequestSpecification().delete(location);
399                        }
400                }
401        }
402
403        protected Response postNonRDFSource(String slug, String file, String mimeType) throws IOException {
404                // Make sure we can post binary resources
405                return buildBaseRequestSpecification()
406                                .header(SLUG, slug)
407                                .body(IOUtils.toByteArray(getClass().getResourceAsStream("/" + file)))
408                                .contentType(mimeType)
409                        .expect()
410                                .statusCode(HttpStatus.SC_CREATED)
411                                .header(LOCATION, HeaderMatchers.headerPresent())
412                        .when()
413                                .post(container);
414        }
415
416        @Test(
417                        groups = {MUST},
418                        description = "When responding to requests whose request-URI is a LDP-NR with an"+
419                                        "associated LDP-RS, a LDPC server must provide the same HTTP Link response"+
420                                        "header as is required in the create response",
421                        dependsOnMethods = "testPostResourceAndCheckAssociatedResource")
422        @SpecTest(
423                        specRefUri = LdpTestSuite.SPEC_URI + "#ldpc-options-linkmetahdr",
424                        testMethod = METHOD.AUTOMATED,
425                        approval = STATUS.WG_APPROVED)
426        public void testOptionsHasSameLinkHeader() throws IOException {
427                // Test constants
428                final String slug = "test",
429                                file = slug + ".png",
430                                mimeType = "image/png";
431
432                // Make sure we can post binary resources
433                Response postResponse = postNonRDFSource(slug, file, mimeType);
434                String location = postResponse.getHeader(LOCATION);
435
436                try {
437                        String associatedRdfSource = getFirstLinkForRelation(location, LINK_REL_DESCRIBEDBY, container, postResponse);
438                        Assert.assertNotNull(associatedRdfSource, "No Link response header with relation \"describedby\" " +
439                                        "and anchor parameter matching the newly-created resource URI");
440
441                        // Check the Link headers on an HTTP OPTIONS for the LDP-NR
442                        Response optionsResponse = buildBaseRequestSpecification()
443                                .expect()
444                                        .statusCode(isSuccessful())
445                                .when()
446                                        .options(location);
447                        Assert.assertTrue(containsLinkHeader(
448                                                location,
449                                                LINK_REL_DESCRIBEDBY,
450                                                associatedRdfSource,
451                                                location,
452                                                optionsResponse
453                                        ),
454                                        "No Link response header with relation \"describedby\" and URI <"
455                                                        + associatedRdfSource + "> for LDP-NR OPTIONS request");
456                } finally {
457                        buildBaseRequestSpecification().delete(location);
458                }
459        }
460
461}