001/* 002 * The contents of this file are subject to the license and copyright 003 * detailed in the LICENSE and NOTICE files at the root of the source 004 * tree. 005 */ 006 007package org.fcrepo.kernel.impl.models; 008 009import static java.net.URI.create; 010import static org.apache.jena.rdf.model.ModelFactory.createDefaultModel; 011import static org.apache.jena.rdf.model.ResourceFactory.createResource; 012import static org.apache.jena.vocabulary.RDF.type; 013import static org.fcrepo.kernel.api.FedoraTypes.FCR_VERSIONS; 014import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_ID_PREFIX; 015import static org.fcrepo.kernel.api.RdfLexicon.BASIC_CONTAINER; 016import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_BINARY; 017import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_RESOURCE; 018import static org.fcrepo.kernel.api.RdfLexicon.NON_RDF_SOURCE; 019import static org.fcrepo.kernel.api.RdfLexicon.RESOURCE; 020import static org.fcrepo.kernel.api.RdfLexicon.VERSIONED_RESOURCE; 021import static org.fcrepo.kernel.api.RdfLexicon.VERSIONING_TIMEGATE_TYPE; 022import static org.fcrepo.kernel.api.rdf.DefaultRdfStream.fromModel; 023import static org.junit.Assert.assertEquals; 024import static org.junit.Assert.assertNull; 025import static org.mockito.ArgumentMatchers.any; 026import static org.mockito.ArgumentMatchers.eq; 027import static org.mockito.Mockito.doReturn; 028import static org.mockito.Mockito.spy; 029import static org.mockito.Mockito.when; 030 031import java.net.URI; 032import java.time.Instant; 033import java.util.ArrayList; 034import java.util.List; 035 036import org.fcrepo.kernel.api.Transaction; 037import org.fcrepo.kernel.api.identifiers.FedoraId; 038import org.fcrepo.kernel.api.models.FedoraResource; 039import org.fcrepo.kernel.api.models.ResourceFactory; 040import org.fcrepo.kernel.api.models.ResourceHeaders; 041import org.fcrepo.kernel.api.models.TimeMap; 042import org.fcrepo.kernel.api.services.VersionService; 043import org.fcrepo.persistence.api.PersistentStorageSession; 044import org.fcrepo.persistence.api.PersistentStorageSessionManager; 045 046import org.apache.jena.rdf.model.Model; 047import org.junit.Test; 048import org.junit.runner.RunWith; 049import org.mockito.Mock; 050import org.mockito.junit.MockitoJUnitRunner; 051 052/** 053 * @author pwinckles 054 */ 055@RunWith(MockitoJUnitRunner.Silent.class) 056public class FedoraResourceImplTest { 057 058 @Mock 059 private TimeMap timeMap; 060 061 @Mock 062 private PersistentStorageSessionManager sessionManager; 063 064 @Mock 065 private ResourceFactory resourceFactory; 066 067 @Mock 068 private PersistentStorageSession psSession; 069 070 @Mock 071 private ResourceHeaders headers; 072 073 @Mock 074 private Transaction transaction; 075 076 private static final String ID = "info:fedora/test"; 077 078 private static final FedoraId FEDORA_ID = FedoraId.create(ID); 079 080 @Test 081 public void findMementoWhenOnlyOneAndBeforeSearch() { 082 final var resource = resourceWithMockedTimeMap(); 083 expectMementos("20200309172117"); 084 final var match = resource.findMementoByDatetime(instant("20200309172118")); 085 assertEquals(FEDORA_ID_PREFIX + "/0", match.getId()); 086 } 087 088 @Test 089 public void findClosestMementoWhenMultiple() { 090 final var resource = resourceWithMockedTimeMap(); 091 expectMementos("20200309172117", "20200309172118", "20200309172119"); 092 final var match = resource.findMementoByDatetime(instant("20200309172118")); 093 assertEquals(FEDORA_ID_PREFIX + "/1", match.getId()); 094 } 095 096 @Test 097 public void findClosestMementoWhenMultipleNoneExact() { 098 final var resource = resourceWithMockedTimeMap(); 099 expectMementos("20200309172116", "20200309172117", "20200309172119"); 100 final var match = resource.findMementoByDatetime(instant("20200309172118")); 101 assertEquals(FEDORA_ID_PREFIX + "/1", match.getId()); 102 } 103 104 @Test 105 public void findClosestMementoMultipleSameSecond() { 106 final var resource = resourceWithMockedTimeMap(); 107 expectMementos("20200309172117", "20200309172117", "20200309172117"); 108 final var match = resource.findMementoByDatetime(instant("20200309172118")); 109 assertEquals(FEDORA_ID_PREFIX + "/2", match.getId()); 110 } 111 112 @Test 113 public void findMementoWhenNonBeforeSearch() { 114 final var resource = resourceWithMockedTimeMap(); 115 expectMementos("20200309172119", "20200309172120", "20200309172121"); 116 final var match = resource.findMementoByDatetime(instant("20200309172118")); 117 assertEquals(FEDORA_ID_PREFIX + "/0", match.getId()); 118 } 119 120 @Test 121 public void findNoMementoWhenThereAreNone() { 122 final var resource = resourceWithMockedTimeMap(); 123 expectMementos(); 124 final var match = resource.findMementoByDatetime(instant("20200309172118")); 125 assertNull("Should not find a memento", match); 126 } 127 128 @Test 129 public void testTypesRdfSource() throws Exception { 130 final var subject = createResource(ID); 131 final String exampleType = "http://example.org/customType"; 132 final Model userModel = createDefaultModel(); 133 userModel.add(subject, type, createResource(exampleType)); 134 final var userStream = fromModel(subject.asNode(), userModel); 135 when(sessionManager.getReadOnlySession()).thenReturn(psSession); 136 when(psSession.getTriples(eq(FEDORA_ID), any())).thenReturn(userStream); 137 138 final List<URI> expectedTypes = List.of( 139 create(exampleType), 140 create(BASIC_CONTAINER.toString()), 141 create(RESOURCE.toString()), 142 create(FEDORA_RESOURCE.toString()), 143 create(VERSIONED_RESOURCE.getURI()), 144 create(VERSIONING_TIMEGATE_TYPE) 145 ); 146 147 when(transaction.isShortLived()).thenReturn(true); 148 149 final var resource = new FedoraResourceImpl(FEDORA_ID, transaction, sessionManager, resourceFactory, null); 150 resource.setInteractionModel(BASIC_CONTAINER.toString()); 151 resource.setIsArchivalGroup(false); 152 final var resourceTypes = resource.getTypes(); 153 154 // Initial lengths are the same 155 assertEquals(expectedTypes.size(), resourceTypes.size()); 156 // Only keep the types in the expected list. 157 resourceTypes.retainAll(expectedTypes); 158 // Lengths are still the same. 159 assertEquals(expectedTypes.size(), resourceTypes.size()); 160 } 161 162 @Test 163 public void testTypesNonRdfSource() throws Exception { 164 final var descriptionFedoraId = FEDORA_ID.asDescription(); 165 final var subject = createResource(ID); 166 final String exampleType = "http://example.org/customType"; 167 final Model userModel = createDefaultModel(); 168 userModel.add(subject, type, createResource(exampleType)); 169 final var userStream = fromModel(subject.asNode(), userModel); 170 171 final var description = new NonRdfSourceDescriptionImpl(descriptionFedoraId, null, sessionManager, 172 resourceFactory, null); 173 174 when(resourceFactory.getResource(any(Transaction.class), eq(descriptionFedoraId))).thenReturn(description); 175 when(sessionManager.getReadOnlySession()).thenReturn(psSession); 176 when(psSession.getTriples(eq(descriptionFedoraId), any())).thenReturn(userStream); 177 178 final List<URI> expectedTypes = List.of( 179 create(exampleType), 180 create(NON_RDF_SOURCE.toString()), 181 create(RESOURCE.toString()), 182 create(FEDORA_RESOURCE.toString()), 183 create(FEDORA_BINARY.toString()), 184 create(VERSIONED_RESOURCE.getURI()), 185 create(VERSIONING_TIMEGATE_TYPE) 186 ); 187 188 when(transaction.isShortLived()).thenReturn(true); 189 190 final var resource = new BinaryImpl(FEDORA_ID, transaction, sessionManager, resourceFactory, null); 191 resource.setInteractionModel(NON_RDF_SOURCE.toString()); 192 resource.setIsArchivalGroup(false); 193 final var resourceTypes = resource.getTypes(); 194 195 // Initial lengths are the same 196 assertEquals(expectedTypes.size(), resourceTypes.size()); 197 // Only keep the types in the expected list. 198 resourceTypes.retainAll(expectedTypes); 199 // Lengths are still the same. 200 assertEquals(expectedTypes.size(), resourceTypes.size()); 201 } 202 203 @Test 204 public void testGetChildren() { 205 final var resource = new FedoraResourceImpl(FEDORA_ID, null, sessionManager, resourceFactory, null); 206 assertEquals(0, resource.getChildren().count()); 207 } 208 209 private void expectMementos(final String... instants) { 210 final var mementos = new ArrayList<FedoraResource>(instants.length); 211 for (int i = 0; i < instants.length; i++) { 212 mementos.add(memento(String.valueOf(i), instant(instants[i]))); 213 } 214 when(timeMap.getChildren()).thenReturn(mementos.stream()); 215 } 216 217 private FedoraResource resourceWithMockedTimeMap() { 218 final var resource = spy(new FedoraResourceImpl(FEDORA_ID, null, sessionManager, 219 resourceFactory, null)); 220 doReturn(timeMap).when(resource).getTimeMap(); 221 return resource; 222 } 223 224 private FedoraResource memento(final String id, final Instant instant) { 225 final String mementoTime = VersionService.MEMENTO_LABEL_FORMATTER.format(instant); 226 final FedoraId fedoraID = FedoraId.create(id, FCR_VERSIONS, mementoTime); 227 final var memento = new FedoraResourceImpl(fedoraID, null, sessionManager, resourceFactory, null); 228 memento.setIsMemento(true); 229 memento.setMementoDatetime(instant); 230 return memento; 231 } 232 233 private Instant instant(final String timestamp) { 234 return Instant.from(VersionService.MEMENTO_LABEL_FORMATTER.parse(timestamp)); 235 } 236 237}