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}