/** * PODD is an OWL ontology database used for scientific project management * * Copyright (C) 2009-2013 The University Of Queensland * * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see <http://www.gnu.org/licenses/>. */ package com.github.podd.resources.test; import java.io.InputStream; import java.io.StringReader; import java.util.Collection; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.io.IOUtils; import org.junit.Assert; import org.junit.Test; import org.openrdf.model.Model; import org.openrdf.model.Resource; import org.openrdf.model.vocabulary.RDF; import org.openrdf.model.vocabulary.RDFS; import org.openrdf.rio.RDFFormat; import org.restlet.data.MediaType; import org.restlet.data.Method; import org.restlet.data.Status; import org.restlet.ext.html.FormData; import org.restlet.ext.html.FormDataSet; import org.restlet.representation.Representation; import org.restlet.resource.ClientResource; import org.restlet.resource.ResourceException; import com.github.ansell.restletutils.RestletUtilMediaType; import com.github.podd.api.test.TestConstants; import com.github.podd.exception.OntologyNotInProfileException; import com.github.podd.utils.InferredOWLOntologyID; import com.github.podd.utils.OntologyUtils; import com.github.podd.utils.PODD; import com.github.podd.utils.PoddWebConstants; /** * @author kutila * */ public class UploadArtifactResourceImplTest extends AbstractResourceImplTest { /** * Test Upload attempt with an artifact that is inconsistent. Results in an HTTP 500 Internal * Server Error with detailed error causes in the RDF body. */ @Test public void testErrorUploadWithInconsistentArtifactRdf() throws Exception { final MediaType mediaType = MediaType.APPLICATION_RDF_XML; final RDFFormat responseFormat = RDFFormat.forMIMEType(mediaType.getName(), RDFFormat.RDFXML); final ClientResource uploadArtifactClientResource = new ClientResource(this.getUrl(PoddWebConstants.PATH_ARTIFACT_UPLOAD)); try { final Representation input = this.buildRepresentationFromResource(TestConstants.TEST_ARTIFACT_BAD_2_LEAD_INSTITUTES, MediaType.APPLICATION_RDF_XML); this.doTestAuthenticatedRequest(uploadArtifactClientResource, Method.POST, input, mediaType, Status.SERVER_ERROR_INTERNAL, AbstractResourceImplTest.WITH_ADMIN); } catch(final ResourceException e) { // verify: error details Assert.assertEquals("Not the expected HTTP status code", Status.SERVER_ERROR_INTERNAL, e.getStatus()); final Model model = this.assertRdf(uploadArtifactClientResource.getResponseEntity(), responseFormat, 16); final Set<Resource> errors = model.filter(null, RDF.TYPE, PODD.ERR_TYPE_TOP_ERROR).subjects(); Assert.assertEquals("Not the expected number of Errors", 1, errors.size()); final Resource topError = errors.iterator().next(); // Resource level error details Assert.assertEquals("Not the expected HTTP Status Code", "500", model.filter(topError, PODD.HTTP_STATUS_CODE_VALUE, null).objectString()); Assert.assertEquals("Not the expected Reason Phrase", "Internal Server Error", model.filter(topError, PODD.HTTP_REASON_PHRASE, null).objectString()); Assert.assertEquals("Not the expected RDFS:comment", "Error loading artifact to PODD", model.filter(topError, RDFS.COMMENT, null).objectString()); Assert.assertEquals("Expected 1 child error node", 1, model.filter(topError, PODD.ERR_CONTAINS, null) .size()); final Resource errorNode = model.filter(topError, PODD.ERR_CONTAINS, null).objectResource(); // Error cause details Assert.assertEquals("Not the expected Exception class", "com.github.podd.exception.InconsistentOntologyException", model.filter(errorNode, PODD.ERR_EXCEPTION_CLASS, null).objectString()); Assert.assertEquals("Not the expected error source", "urn:temp:inconsistentArtifact:1", model.filter(errorNode, PODD.ERR_SOURCE, null).objectString()); Assert.assertTrue( "Not the expected inconsistency explanation", model.filter(errorNode, RDFS.COMMENT, null) .objectString() .contains( "Individual urn:temp:object:1960 has more than 1 values for property http://purl.org/podd/ns/poddBase#hasLeadInstitution")); } finally { this.releaseClient(uploadArtifactClientResource); } } /** * Test Upload attempt with an artifact that is inconsistent. Results in an HTTP 500 Internal * Server Error with detailed error causes in the RDF body. */ @Test public void testErrorUploadWithNotInOwlDlProfileArtifactRdf() throws Exception { final Representation input = this.buildRepresentationFromResource(TestConstants.TEST_ARTIFACT_BAD_NOT_OWL_DL, MediaType.APPLICATION_RDF_XML); final MediaType mediaType = MediaType.APPLICATION_RDF_XML; final RDFFormat responseFormat = RDFFormat.forMIMEType(mediaType.getName(), RDFFormat.RDFXML); final ClientResource uploadArtifactClientResource = new ClientResource(this.getUrl(PoddWebConstants.PATH_ARTIFACT_UPLOAD)); try { this.doTestAuthenticatedRequest(uploadArtifactClientResource, Method.POST, input, mediaType, Status.SERVER_ERROR_INTERNAL, AbstractResourceImplTest.WITH_ADMIN); } catch(final ResourceException e) { // verify: error details Assert.assertEquals("Not the expected HTTP status code", Status.SERVER_ERROR_INTERNAL, e.getStatus()); final Model model = this.assertRdf(uploadArtifactClientResource.getResponseEntity(), responseFormat, 18); final Set<Resource> errors = model.filter(null, RDF.TYPE, PODD.ERR_TYPE_TOP_ERROR).subjects(); Assert.assertEquals("Not the expected number of Errors", 1, errors.size()); final Resource topError = errors.iterator().next(); // Resource level error details Assert.assertEquals("Not the expected HTTP Status Code", "500", model.filter(topError, PODD.HTTP_STATUS_CODE_VALUE, null).objectString()); Assert.assertEquals("Not the expected Reason Phrase", "Internal Server Error", model.filter(topError, PODD.HTTP_REASON_PHRASE, null).objectString()); Assert.assertEquals("Not the expected RDFS:comment", "Error loading artifact to PODD", model.filter(topError, RDFS.COMMENT, null).objectString()); // Error cause details Assert.assertEquals("Not the expected Exception class", OntologyNotInProfileException.class.getName(), model.filter(null, PODD.ERR_EXCEPTION_CLASS, null).objectString()); Assert.assertEquals( "Expected error sources not found", 2, model.filter(null, PODD.ERR_SOURCE, PODD.VF.createLiteral("ClassAssertion(owl:Individual <mailto:helen.daily@csiro.au>)")) .size()); } finally { this.releaseClient(uploadArtifactClientResource); } } /** * Test unauthenticated access to "upload artifact" leads to an UNAUTHORIZED error. */ @Test public void testErrorUploadWithoutAuthentication() throws Exception { final ClientResource uploadArtifactClientResource = new ClientResource(this.getUrl(PoddWebConstants.PATH_ARTIFACT_UPLOAD)); try { final Representation input = this.buildRepresentationFromResource("/test/artifacts/basicProject-1-internal-object.rdf", MediaType.APPLICATION_RDF_XML); final FormDataSet form = new FormDataSet(); form.setMultipart(true); form.getEntries().add(new FormData("file", input)); uploadArtifactClientResource.post(form, MediaType.TEXT_HTML); Assert.fail("Should have thrown a ResourceException with Status Code 401"); } catch(final ResourceException e) { Assert.assertEquals("Not the expected HTTP status code", Status.CLIENT_ERROR_UNAUTHORIZED, e.getStatus()); } finally { this.releaseClient(uploadArtifactClientResource); } } /** * Test upload attempt without actual file leads to a BAD_REQUEST error */ @Test public void testErrorUploadWithoutFile() throws Exception { final ClientResource uploadArtifactClientResource = new ClientResource(this.getUrl(PoddWebConstants.PATH_ARTIFACT_UPLOAD)); try { final FormDataSet form = new FormDataSet(); form.setMultipart(true); this.doTestAuthenticatedRequest(uploadArtifactClientResource, Method.POST, form, MediaType.TEXT_HTML, Status.CLIENT_ERROR_BAD_REQUEST, AbstractResourceImplTest.WITH_ADMIN); Assert.fail("Should have thrown a ResourceException with Status Code 400"); } catch(final ResourceException e) { Assert.assertEquals("Not the expected HTTP status code", Status.CLIENT_ERROR_BAD_REQUEST, e.getStatus()); } finally { this.releaseClient(uploadArtifactClientResource); } } /** * Test authenticated access to the upload Artifact page in HTML */ @Test public void testGetUploadArtifactPageBasicHtml() throws Exception { // prepare: add an artifact final ClientResource getArtifactClientResource = new ClientResource(this.getUrl(PoddWebConstants.PATH_ARTIFACT_UPLOAD)); try { final Representation results = this.doTestAuthenticatedRequest(getArtifactClientResource, Method.GET, null, MediaType.TEXT_HTML, Status.SUCCESS_OK, AbstractResourceImplTest.WITH_ADMIN); final String body = this.getText(results); Assert.assertTrue(body.contains("Upload new artifact")); Assert.assertTrue(body.contains("type=\"file\"")); this.assertFreemarker(body); } finally { this.releaseClient(getArtifactClientResource); } } // @Ignore("When this test is active, it seems to slow down other tests so far that they don't complete normally") @Test public final void testLoadArtifactConcurrency() throws Exception { // load test artifact final InputStream inputStream4Artifact = this.getClass().getResourceAsStream(TestConstants.TEST_ARTIFACT_IMPORT_PSCIENCEv1); Assert.assertNotNull("Could not find test resource: " + TestConstants.TEST_ARTIFACT_IMPORT_PSCIENCEv1, inputStream4Artifact); final String nextTestArtifact = IOUtils.toString(inputStream4Artifact); final AtomicInteger threadSuccessCount = new AtomicInteger(0); final AtomicInteger perThreadSuccessCount = new AtomicInteger(0); final AtomicInteger threadStartCount = new AtomicInteger(0); final AtomicInteger perThreadStartCount = new AtomicInteger(0); final CountDownLatch openLatch = new CountDownLatch(1); // Changing this from 8 to 9 on my machine may be triggering a restlet // bug final int threadCount = 9; final int perThreadCount = 2; final CountDownLatch closeLatch = new CountDownLatch(threadCount); for(int i = 0; i < threadCount; i++) { final int number = i; final Runnable runner = new Runnable() { @Override public void run() { try { openLatch.await(55000, TimeUnit.MILLISECONDS); threadStartCount.incrementAndGet(); for(int j = 0; j < perThreadCount; j++) { perThreadStartCount.incrementAndGet(); ClientResource uploadArtifactClientResource = null; try { uploadArtifactClientResource = new ClientResource( UploadArtifactResourceImplTest.this .getUrl(PoddWebConstants.PATH_ARTIFACT_UPLOAD)); AbstractResourceImplTest.setupThreading(uploadArtifactClientResource.getContext()); final Representation input = UploadArtifactResourceImplTest.this.buildRepresentationFromResource( TestConstants.TEST_ARTIFACT_IMPORT_PSCIENCEv1, MediaType.APPLICATION_RDF_XML); final Representation results = UploadArtifactResourceImplTest.this.doTestAuthenticatedRequest( uploadArtifactClientResource, Method.POST, input, MediaType.APPLICATION_RDF_XML, Status.SUCCESS_OK, AbstractResourceImplTest.WITH_ADMIN); // verify: results (expecting the added // artifact's ontology IRI) final String body = UploadArtifactResourceImplTest.this.getText(results); final Collection<InferredOWLOntologyID> ontologyIDs = OntologyUtils.stringToOntologyID(body, RDFFormat.RDFXML); Assert.assertNotNull("No ontology IDs in response", ontologyIDs); Assert.assertEquals("More than 1 ontology ID in response", 1, ontologyIDs.size()); Assert.assertTrue("Ontology ID not of expected format", ontologyIDs.iterator() .next().toString().contains("artifact:1:version:1")); perThreadSuccessCount.incrementAndGet(); } finally { UploadArtifactResourceImplTest.this.releaseClient(uploadArtifactClientResource); } } threadSuccessCount.incrementAndGet(); } catch(final Throwable e) { e.printStackTrace(); Assert.fail("Failed in test: " + number); } finally { closeLatch.countDown(); } } }; new Thread(runner, "TestThread" + number).start(); } // all threads are waiting on the latch. openLatch.countDown(); // release the latch // all threads are now running concurrently. closeLatch.await(50000, TimeUnit.MILLISECONDS); // closeLatch.await(); // Verify that there were no startup failures Assert.assertEquals("Some threads did not all start successfully", threadCount, threadStartCount.get()); Assert.assertEquals("Some thread loops did not start successfully", perThreadCount * threadCount, perThreadStartCount.get()); // Verify that there were no failures, as the count is only incremented // for successes, where // the closeLatch must always be called, even for failures Assert.assertEquals("Some thread loops did not complete successfully", perThreadCount * threadCount, perThreadSuccessCount.get()); Assert.assertEquals("Some threads did not complete successfully", threadCount, threadSuccessCount.get()); } /** * Test successful upload of a new artifact file while authenticated with the admin role. * Expects an HTML response. */ @Test public void testUploadArtifactBasicHtml() throws Exception { final ClientResource uploadArtifactClientResource = new ClientResource(this.getUrl(PoddWebConstants.PATH_ARTIFACT_UPLOAD)); try { final Representation input = this.buildRepresentationFromResource("/test/artifacts/basicProject-1-internal-object.rdf", MediaType.APPLICATION_RDF_XML); final FormDataSet form = new FormDataSet(); form.setMultipart(true); form.getEntries().add(new FormData("file", input)); final Representation results = this.doTestAuthenticatedRequest(uploadArtifactClientResource, Method.POST, form, MediaType.TEXT_HTML, Status.SUCCESS_OK, AbstractResourceImplTest.WITH_ADMIN); // TODO: verify results once a proper success page is incorporated. final String body = this.getText(results); Assert.assertTrue(body.contains("Project successfully uploaded")); this.assertFreemarker(body); } finally { this.releaseClient(uploadArtifactClientResource); } } /** * Test successful upload of a new artifact file while authenticated with the admin role. * Expects a plain text response. */ @Test public void testUploadArtifactBasicRdf() throws Exception { final ClientResource uploadArtifactClientResource = new ClientResource(this.getUrl(PoddWebConstants.PATH_ARTIFACT_UPLOAD)); try { final Representation input = this.buildRepresentationFromResource("/test/artifacts/basicProject-1-internal-object.rdf", MediaType.APPLICATION_RDF_XML); final Representation results = this.doTestAuthenticatedRequest(uploadArtifactClientResource, Method.POST, input, MediaType.TEXT_PLAIN, Status.SUCCESS_OK, AbstractResourceImplTest.WITH_ADMIN); // verify: results (expecting the added artifact's ontology IRI) final String body = this.getText(results); Assert.assertTrue(body.contains("http://")); Assert.assertFalse(body.contains("html")); Assert.assertFalse(body.contains("\n")); } finally { this.releaseClient(uploadArtifactClientResource); } } /** * Test successful upload of a new artifact file while authenticated with the admin role. * Expects a plain text response. */ @Test public void testUploadArtifactBasicRdfWithFormData() throws Exception { final ClientResource uploadArtifactClientResource = new ClientResource(this.getUrl(PoddWebConstants.PATH_ARTIFACT_UPLOAD)); try { final Representation input = this.buildRepresentationFromResource("/test/artifacts/basicProject-1-internal-object.rdf", MediaType.APPLICATION_RDF_XML); final FormDataSet form = new FormDataSet(); form.setMultipart(true); form.getEntries().add(new FormData("file", input)); final Representation results = this.doTestAuthenticatedRequest(uploadArtifactClientResource, Method.POST, form, MediaType.TEXT_PLAIN, Status.SUCCESS_OK, AbstractResourceImplTest.WITH_ADMIN); // verify: results (expecting the added artifact's ontology IRI) final String body = this.getText(results); Assert.assertTrue(body.contains("http://")); Assert.assertFalse(body.contains("html")); Assert.assertFalse(body.contains("\n")); } finally { this.releaseClient(uploadArtifactClientResource); } } /** * Test successful upload of a new artifact file while authenticated with the admin role. * Expects a plain text response. */ @Test public void testUploadArtifactBasicTurtle() throws Exception { final ClientResource uploadArtifactClientResource = new ClientResource(this.getUrl(PoddWebConstants.PATH_ARTIFACT_UPLOAD)); try { final Representation input = this.buildRepresentationFromResource(TestConstants.TEST_ARTIFACT_TTL_1_INTERNAL_OBJECT, MediaType.APPLICATION_RDF_TURTLE); final Representation results = this.doTestAuthenticatedRequest(uploadArtifactClientResource, Method.POST, input, MediaType.APPLICATION_RDF_TURTLE, Status.SUCCESS_OK, AbstractResourceImplTest.WITH_ADMIN); // verify: results (expecting the added artifact's ontology IRI) final String body = this.getText(results); final Collection<InferredOWLOntologyID> ontologyIDs = OntologyUtils.stringToOntologyID(body, RDFFormat.TURTLE); Assert.assertNotNull("No ontology IDs in response", ontologyIDs); Assert.assertEquals("More than 1 ontology ID in response", 1, ontologyIDs.size()); Assert.assertTrue("Ontology ID not of expected format", ontologyIDs.iterator().next().toString().contains("artifact:1:version:1")); } finally { this.releaseClient(uploadArtifactClientResource); } } @Test public void testUploadNewProjectTemplate() throws Exception { // Rio.write(Rio.parse(this.getClass().getResourceAsStream("/test/artifacts/new-project-template.rj"), // "", RDFFormat.RDFJSON), System.out, RDFFormat.RDFJSON); final ClientResource uploadArtifactClientResource = new ClientResource(this.getUrl(PoddWebConstants.PATH_ARTIFACT_UPLOAD)); try { final Representation input = this.buildRepresentationFromResource("/test/artifacts/new-project-template.rj", RestletUtilMediaType.APPLICATION_RDF_JSON); final Representation results = this.doTestAuthenticatedRequest(uploadArtifactClientResource, Method.POST, input, RestletUtilMediaType.APPLICATION_RDF_JSON, Status.SUCCESS_OK, AbstractResourceImplTest.WITH_ADMIN); // verify: results (expecting the added artifact's ontology IRI) final String body = this.getText(results); final Model model = this.assertRdf(new StringReader(body), RDFFormat.RDFJSON, 6); Assert.assertEquals(3, model.subjects().size()); Assert.assertEquals(3, model.predicates().size()); Assert.assertEquals(5, model.objects().size()); } finally { this.releaseClient(uploadArtifactClientResource); } } }