/** * Copyright 2008 The University of North Carolina at Chapel Hill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package edu.unc.lib.dl.services; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; import javax.annotation.Resource; import org.apache.commons.io.IOUtils; import org.jdom2.Attribute; import org.jdom2.Document; import org.jdom2.Namespace; import org.jdom2.filter.Filters; import org.jdom2.xpath.XPathExpression; import org.jdom2.xpath.XPathFactory; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import edu.unc.lib.dl.acl.util.AccessGroupSet; import edu.unc.lib.dl.acl.util.Permission; import edu.unc.lib.dl.fedora.AccessClient; import edu.unc.lib.dl.fedora.ClientUtils; import edu.unc.lib.dl.fedora.DatastreamDocument; import edu.unc.lib.dl.fedora.FedoraAccessControlService; import edu.unc.lib.dl.fedora.FedoraException; import edu.unc.lib.dl.fedora.ManagementClient; import edu.unc.lib.dl.fedora.PID; import edu.unc.lib.dl.fedora.ServiceException; import edu.unc.lib.dl.fedora.types.MIMETypedStream; import edu.unc.lib.dl.ingest.IngestException; import edu.unc.lib.dl.update.UpdateException; import edu.unc.lib.dl.util.ContentModelHelper; import edu.unc.lib.dl.util.ContentModelHelper.Model; import edu.unc.lib.dl.util.PremisEventLogger; import edu.unc.lib.dl.util.ResourceType; import edu.unc.lib.dl.util.TripleStoreQueryService; import edu.unc.lib.dl.xml.JDOMNamespaceUtil; /** * @author Gregory Jansen * */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "/service-context.xml" }) public class DigitalObjectManagerImplTest { @Resource private final DigitalObjectManagerImpl digitalObjectManagerImpl = null; @Resource ManagementClient forwardedManagementClient = null; @Resource ManagementClient managementClient = null; @Resource AccessClient accessClient = null; @Resource FedoraAccessControlService aclService = null; @Resource TripleStoreQueryService tripleStoreQueryService = null; private static final String MD_CONTENTS = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<m:structMap xmlns:m=\"http://www.loc.gov/METS/\">" + "<m:div TYPE=\"Container\">" + "<m:div ID=\"test:delete\" ORDER=\"0\"/>" + "</m:div>" + "</m:structMap>"; private static final String MD_EVENTS = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<premis xmlns=\"info:lc/xmlns/premis-v2\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" + "<object xsi:type=\"representation\">" + "<objectIdentifier>" + "<objectIdentifierType>PID</objectIdentifierType>" + "<objectIdentifierValue>test:container</objectIdentifierValue>" + "</objectIdentifier>" + "</object>" + "</premis>"; /** * @throws java.lang.Exception */ @BeforeClass public static void setUpBeforeClass() throws Exception { } /** * @throws java.lang.Exception */ @AfterClass public static void tearDownAfterClass() throws Exception { } /** * @throws java.lang.Exception */ @Before public void setUp() throws Exception { reset(forwardedManagementClient); reset(accessClient); reset(tripleStoreQueryService); this.getDigitalObjectManagerImpl().setAvailable(true, "available"); // setup default MD_CONTENTS stream MIMETypedStream mts = mock(MIMETypedStream.class); when(mts.getStream()).thenReturn(MD_CONTENTS.getBytes()); when(accessClient.getDatastreamDissemination(any(PID.class), eq("MD_CONTENTS"), anyString())).thenReturn(mts); // setup default MD_EVENTS stream MIMETypedStream mts2 = mock(MIMETypedStream.class); when(mts2.getStream()).thenReturn(MD_EVENTS.getBytes()); when(accessClient.getDatastreamDissemination(any(PID.class), eq("MD_EVENTS"), any(String.class))) .thenReturn(mts2); // management client upload responses Answer<String> upload = new Answer<String>() { @Override public String answer(InvocationOnMock invocation) throws Throwable { return "upload://" + UUID.randomUUID(); } }; when(this.forwardedManagementClient.upload(any(File.class))).thenAnswer(upload); when(this.forwardedManagementClient.upload(any(Document.class))).thenAnswer(upload); } /** * @return */ private DigitalObjectManagerImpl getDigitalObjectManagerImpl() { return this.digitalObjectManagerImpl; } /** * @throws java.lang.Exception */ @After public void tearDown() throws Exception { } /** * Test method for * {@link edu.unc.lib.dl.services.DigitalObjectManagerImpl#delete(edu.unc.lib.dl.fedora.PID, edu.unc.lib.dl.agents.Agent, java.lang.String)} * . */ @SuppressWarnings("unchecked") @Test public void testDelete() throws Exception { // verify works with references internal to delete contents // verify works with container reference // setup mocks when(tripleStoreQueryService.fetchAllContents(any(PID.class))).thenReturn(new ArrayList<PID>()); PID container = new PID("test:container"); ArrayList<PID> refs = new ArrayList<PID>(); refs.add(container); when(tripleStoreQueryService.fetchObjectReferences(any(PID.class))).thenReturn(refs); when(tripleStoreQueryService.fetchContainer(any(PID.class))).thenReturn(container, container, container); when(forwardedManagementClient.purgeObjectRelationship(any(PID.class), any(String.class), any(Namespace.class), any(PID.class))).thenReturn(true); ArrayList<URI> cms = new ArrayList<URI>(); cms.add(ContentModelHelper.Model.CONTAINER.getURI()); when(tripleStoreQueryService.lookupContentModels(any(PID.class))).thenReturn(cms); PID test = new PID("test:delete"); this.getDigitalObjectManagerImpl().delete(test, "tron", "testing delete"); verify(forwardedManagementClient, times(1)).modifyInlineXMLDatastream(any(PID.class), eq("MD_CONTENTS"), eq(false), any(String.class), (ArrayList<String>) any(), any(String.class), any(Document.class)); verify(forwardedManagementClient, times(1)).writePremisEventsToFedoraObject(any(PremisEventLogger.class), eq(container)); verify(forwardedManagementClient, times(1)).purgeObject(eq(test), any(String.class), eq(false)); verify(forwardedManagementClient, times(0)).purgeObject(any(PID.class), any(String.class), eq(true)); verify(forwardedManagementClient, times(1)).purgeObject(any(PID.class), any(String.class), anyBoolean()); } /** * Test method for * {@link edu.unc.lib.dl.services.DigitalObjectManagerImpl#delete(edu.unc.lib.dl.fedora.PID, edu.unc.lib.dl.agents.Agent, java.lang.String)} * . verify exception when referenced by PIDs not being deleted and not container, verify exception cites the * referencing PID. */ @Test(expected = IngestException.class) public void testDeleteReferencedPIDException() throws Exception { // setup mocks when(tripleStoreQueryService.fetchAllContents(any(PID.class))).thenReturn(new ArrayList<PID>()); PID container = new PID("test:container"); ArrayList<PID> refs = new ArrayList<PID>(); refs.add(container); refs.add(new PID("test:randomReference")); when(tripleStoreQueryService.fetchObjectReferences(any(PID.class))).thenReturn(refs); when(tripleStoreQueryService.fetchContainer(any(PID.class))).thenReturn(container); this.getDigitalObjectManagerImpl().delete(new PID("test:delete"), "tron", "testing delete"); } /** * Test method for * {@link edu.unc.lib.dl.services.DigitalObjectManagerImpl#delete(edu.unc.lib.dl.fedora.PID, edu.unc.lib.dl.agents.Agent, java.lang.String)} * . */ @SuppressWarnings("unchecked") @Test public void testDeleteForFedoraFault() throws Exception { // verify works with references internal to delete contents // verify works with container reference // setup mocks when(tripleStoreQueryService.fetchAllContents(any(PID.class))).thenReturn(new ArrayList<PID>()); PID container = new PID("test:container"); ArrayList<PID> refs = new ArrayList<PID>(); refs.add(container); when(tripleStoreQueryService.fetchObjectReferences(any(PID.class))).thenReturn(refs); when(tripleStoreQueryService.fetchContainer(any(PID.class))).thenReturn(container, container, container); when(forwardedManagementClient.purgeObjectRelationship(any(PID.class), any(String.class), any(Namespace.class), any(PID.class))).thenReturn(true); ArrayList<URI> cms = new ArrayList<URI>(); cms.add(ContentModelHelper.Model.CONTAINER.getURI()); when(tripleStoreQueryService.lookupContentModels(any(PID.class))).thenReturn(cms); PID test = new PID("test:delete"); FedoraException fe = mock(FedoraException.class); when(forwardedManagementClient.purgeObject(any(PID.class), any(String.class), eq(false))).thenThrow(fe); Throwable thrown = null; try { this.getDigitalObjectManagerImpl().delete(test, "tron", "testing delete"); } catch (IngestException e) { thrown = e; } assertNotNull("An exception must be thrown", thrown); assertTrue("Exception must be an IngestException", thrown instanceof IngestException); // delete failures always result in a log dump (probably not necessary // unless PID are uncontained) // verify container was updated verify(forwardedManagementClient, times(1)).modifyInlineXMLDatastream(any(PID.class), eq("MD_CONTENTS"), eq(false), any(String.class), any(new ArrayList<String>().getClass()), any(String.class), any(Document.class)); verify(forwardedManagementClient, times(1)).writePremisEventsToFedoraObject(any(PremisEventLogger.class), eq(container)); // purge call will fail resulting in a log dump of rollback info verify(forwardedManagementClient, times(1)).purgeObject(any(PID.class), any(String.class), anyBoolean()); // TODO also test failure of "remove from container" operation } /** * Test method for * {@link edu.unc.lib.dl.services.DigitalObjectManagerImpl#delete(edu.unc.lib.dl.fedora.PID, edu.unc.lib.dl.agents.Agent, java.lang.String)} * . */ @SuppressWarnings("unchecked") @Test public void testDeleteForFedoraGone() throws Exception { // verify works with references internal to delete contents // verify works with container reference // setup mocks when(tripleStoreQueryService.fetchAllContents(any(PID.class))).thenReturn(new ArrayList<PID>()); PID container = new PID("test:container"); ArrayList<PID> refs = new ArrayList<PID>(); refs.add(container); when(tripleStoreQueryService.fetchObjectReferences(any(PID.class))).thenReturn(refs); when(tripleStoreQueryService.fetchContainer(any(PID.class))).thenReturn(container, container, container); when(forwardedManagementClient.purgeObjectRelationship(any(PID.class), any(String.class), any(Namespace.class), any(PID.class))).thenReturn(true); ArrayList<URI> cms = new ArrayList<URI>(); cms.add(ContentModelHelper.Model.CONTAINER.getURI()); when(tripleStoreQueryService.lookupContentModels(any(PID.class))).thenReturn(cms); PID test = new PID("test:delete"); ServiceException fe = mock(ServiceException.class); when(forwardedManagementClient.purgeObject(any(PID.class), any(String.class), eq(false))).thenThrow(fe); Throwable thrown = null; try { this.getDigitalObjectManagerImpl().delete(test, "tron", "testing delete"); } catch (IngestException e) { thrown = e; } assertNotNull("An exception must be thrown", thrown); assertTrue("Exception must be an IngestException", thrown instanceof IngestException); // delete failures always result in a log dump (probably not necessary // unless PID are uncontained) // verify container was updated verify(forwardedManagementClient, times(1)).modifyInlineXMLDatastream(any(PID.class), eq("MD_CONTENTS"), eq(false), any(String.class), any(new ArrayList<String>().getClass()), any(String.class), any(Document.class)); verify(forwardedManagementClient, times(1)).writePremisEventsToFedoraObject(any(PremisEventLogger.class), eq(container)); // purge call will fail resulting in a log dump of rollback info verify(forwardedManagementClient, times(1)).purgeObject(any(PID.class), any(String.class), anyBoolean()); assertTrue("DOM must be made unavailable after a service exception", !this.getDigitalObjectManagerImpl() .isAvailable()); } // TODO test fedora fault and ccessful rollback of a failed move // TODO test fedora fault and successful rollback of a failed update // TODO test fedora gone and successful log dump of rollback info for a // failed move // TODO test fedora gone and successful log dump of rollback info for a // failed update // TODO wrap mock around JavaMailSender with debug, verify email always sent // TODO testMailSendFailureLogging /** * Test method for * {@link edu.unc.lib.dl.services.DigitalObjectManagerImpl#updateSourceData(edu.unc.lib.dl.fedora.PID, java.lang.String, java.io.File, java.lang.String, java.lang.String, java.lang.String, edu.unc.lib.dl.agents.Agent, java.lang.String)} * . */ // @Test public void testUpdateSourceData() { fail("Not yet implemented"); // TODO } /** * Test method for {@link edu.unc.lib.dl.services.DigitalObjectManagerImpl#checkAvailable()} . */ @Test(expected = IngestException.class) public void testAvailabilityException() throws Exception { this.getDigitalObjectManagerImpl().setAvailable(false, "The repository manager is unavailable for a test of the availability check."); this.getDigitalObjectManagerImpl().delete(new PID("foo"), "Delete", "user"); } @Test(expected = UpdateException.class) public void testChangeResourceTypeInvalidNewType() throws Exception { this.digitalObjectManagerImpl.editResourceType(Arrays.asList(new PID("pid")), null, "user"); } @Test public void testChangeResourceType() throws Exception { String relsName = ContentModelHelper.Datastream.RELS_EXT.toString(); DatastreamDocument dsDoc = mock(DatastreamDocument.class); when(dsDoc.getLastModified()).thenReturn("2015-06-03"); when(managementClient.getXMLDatastreamIfExists(any(PID.class), eq(relsName))) .thenReturn(dsDoc); when(dsDoc.getDocument()).thenReturn(ClientUtils.parseXML( IOUtils.toByteArray(getClass().getResourceAsStream("/datastream/collectionRels.xml")))); when(aclService.hasAccess(any(PID.class), any(AccessGroupSet.class), eq(Permission.editResourceType))) .thenReturn(true); this.digitalObjectManagerImpl.editResourceType(Arrays.asList(new PID("pid")), ResourceType.Aggregate, "user"); ArgumentCaptor<Document> newRelsCaptor = ArgumentCaptor.forClass(Document.class); verify(managementClient).modifyDatastream(any(PID.class), eq(relsName), anyString(), eq("2015-06-03"), newRelsCaptor.capture()); Document newRels = newRelsCaptor.getValue(); XPathFactory xFactory = XPathFactory.instance(); XPathExpression<Attribute> expr = xFactory.compile("//fedModel:hasModel/@rdf:resource", Filters.attribute(), null, JDOMNamespaceUtil.RDF_NS, JDOMNamespaceUtil.FEDORA_MODEL_NS); List<Attribute> modelAttrs = expr.evaluate(newRels); List<String> modelValues = new ArrayList<>(); for (Attribute attr : modelAttrs) { modelValues.add(attr.getValue()); } assertTrue(modelValues.contains(Model.CONTAINER.toString())); assertTrue(modelValues.contains(Model.AGGREGATE_WORK.toString())); assertFalse(modelValues.contains(Model.COLLECTION.toString())); assertTrue(modelValues.contains(Model.PRESERVEDOBJECT.toString())); } /** * Test method for * {@link edu.unc.lib.dl.services.DigitalObjectManagerImpl#updateDescription(edu.unc.lib.dl.fedora.PID, java.io.File, java.lang.String, edu.unc.lib.dl.agents.Agent, java.lang.String)} * . */ // @Test public void testUpdateDescription() { fail("Not yet implemented"); // TODO } /** * Test method for * {@link edu.unc.lib.dl.services.DigitalObjectManagerImpl#move(java.util.List, java.lang.String, edu.unc.lib.dl.agents.Agent, java.lang.String)} * . */ // @Test public void testMove() { fail("Not yet implemented"); // TODO } }