001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.fcrepo.kernel.impl.models;
019
020import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_ID_PREFIX;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023import static org.mockito.Mockito.when;
024import static org.springframework.test.util.ReflectionTestUtils.setField;
025
026import javax.inject.Inject;
027
028import java.util.UUID;
029
030import org.fcrepo.kernel.api.ContainmentIndex;
031import org.fcrepo.kernel.api.ReadOnlyTransaction;
032import org.fcrepo.kernel.api.Transaction;
033import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
034import org.fcrepo.kernel.api.identifiers.FedoraId;
035import org.fcrepo.kernel.impl.TestTransactionHelper;
036import org.fcrepo.persistence.api.PersistentStorageSession;
037import org.fcrepo.persistence.api.PersistentStorageSessionManager;
038import org.fcrepo.persistence.api.exceptions.PersistentSessionClosedException;
039import org.junit.Before;
040import org.junit.Test;
041import org.junit.runner.RunWith;
042import org.mockito.InjectMocks;
043import org.mockito.Mock;
044import org.mockito.MockitoAnnotations;
045import org.springframework.test.context.ContextConfiguration;
046import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
047
048/**
049 * Test for ResourceHelper
050 * @author whikloj
051 * @since 6.0.0
052 */
053@RunWith(SpringJUnit4ClassRunner.class)
054@ContextConfiguration("/containmentIndexTest.xml")
055public class ResourceHelperImplTest {
056
057    @Mock
058    private PersistentStorageSessionManager sessionManager;
059
060    @Mock
061    private PersistentStorageSession psSession;
062
063    private Transaction mockTx;
064
065    @Inject
066    private ContainmentIndex containmentIndex;
067
068    @InjectMocks
069    private ResourceHelperImpl resourceHelper;
070
071    private String fedoraIdStr;
072
073    private String sessionId;
074
075    private final FedoraId rootId = FedoraId.getRepositoryRootId();
076
077    private FedoraId fedoraId;
078
079    private String fedoraMementoIdStr;
080
081    private FedoraId fedoraMementoId;
082
083    private Transaction readOnlyTx;
084
085    @Before
086    public void setup() throws Exception {
087        MockitoAnnotations.openMocks(this);
088        fedoraIdStr = FEDORA_ID_PREFIX + "/" + UUID.randomUUID().toString();
089        fedoraId = FedoraId.create(fedoraIdStr);
090        fedoraMementoIdStr = fedoraIdStr + "/fcr:versions/20000102120000";
091        fedoraMementoId = FedoraId.create(fedoraMementoIdStr);
092
093        sessionId = UUID.randomUUID().toString();
094        mockTx = TestTransactionHelper.mockTransaction(sessionId, false);
095
096        resourceHelper = new ResourceHelperImpl();
097
098        setField(resourceHelper, "persistentStorageSessionManager", sessionManager);
099        setField(resourceHelper, "containmentIndex", containmentIndex);
100
101        when(sessionManager.getSession(mockTx)).thenReturn(psSession);
102        when(sessionManager.getReadOnlySession()).thenReturn(psSession);
103
104        readOnlyTx = ReadOnlyTransaction.INSTANCE;
105    }
106
107    @Test
108    public void doesResourceExist_Exists_WithSession() throws Exception {
109        containmentIndex.addContainedBy(mockTx, rootId, fedoraId);
110        final boolean answerIn = resourceHelper.doesResourceExist(mockTx, fedoraId, false);
111        assertTrue(answerIn);
112        final boolean answerOut = resourceHelper.doesResourceExist(readOnlyTx, fedoraId, false);
113        assertFalse(answerOut);
114    }
115
116    @Test
117    public void doesResourceExist_Exists_Description_WithSession() {
118        containmentIndex.addContainedBy(mockTx, rootId, fedoraId);
119        final FedoraId descId = fedoraId.asDescription();
120        final boolean answerIn = resourceHelper.doesResourceExist(mockTx, descId, false);
121        assertTrue(answerIn);
122        final boolean answerOut = resourceHelper.doesResourceExist(readOnlyTx, descId, false);
123        assertFalse(answerOut);
124    }
125
126    @Test
127    public void doesResourceExist_Exists_WithoutSession() throws Exception {
128        containmentIndex.addContainedBy(mockTx, rootId, fedoraId);
129        containmentIndex.commitTransaction(mockTx);
130        final boolean answer = resourceHelper.doesResourceExist(readOnlyTx, fedoraId, false);
131        assertTrue(answer);
132    }
133
134    @Test
135    public void doesResourceExist_Exists_Description_WithoutSession() {
136        containmentIndex.addContainedBy(mockTx, rootId, fedoraId);
137        containmentIndex.commitTransaction(mockTx);
138        final FedoraId descId = fedoraId.asDescription();
139        final boolean answer = resourceHelper.doesResourceExist(readOnlyTx, descId, false);
140        assertTrue(answer);
141    }
142
143    @Test
144    public void doesResourceExist_DoesntExist_WithSession() throws Exception {
145        final boolean answer = resourceHelper.doesResourceExist(mockTx, fedoraId, false);
146        assertFalse(answer);
147    }
148
149    @Test
150    public void doesResourceExist_DoesntExists_Description_WithSession() {
151        final FedoraId descId = fedoraId.asDescription();
152        final boolean answer = resourceHelper.doesResourceExist(mockTx, descId, false);
153        assertFalse(answer);
154    }
155
156    @Test
157    public void doesResourceExist_DoesntExist_WithoutSession() throws Exception {
158        final boolean answer = resourceHelper.doesResourceExist(readOnlyTx, fedoraId, false);
159        assertFalse(answer);
160    }
161
162    @Test
163    public void doesResourceExist_DoesntExists_Description_WithoutSession() {
164        final FedoraId descId = fedoraId.asDescription();
165        final boolean answer = resourceHelper.doesResourceExist(readOnlyTx, descId, false);
166        assertFalse(answer);
167    }
168
169    /**
170     * Only Mementos go to the persistence layer.
171     */
172    @Test(expected = RepositoryRuntimeException.class)
173    public void doesResourceExist_Exception_WithSession() throws Exception {
174        when(psSession.getHeaders(fedoraMementoId, fedoraMementoId.getMementoInstant()))
175                .thenThrow(PersistentSessionClosedException.class);
176        resourceHelper.doesResourceExist(mockTx, fedoraMementoId, false);
177    }
178
179    /**
180     * Only Mementos go to the persistence layer.
181     */
182    @Test(expected = RepositoryRuntimeException.class)
183    public void doesResourceExist_Exception_WithoutSession() throws Exception {
184        when(psSession.getHeaders(fedoraMementoId, fedoraMementoId.getMementoInstant()))
185                .thenThrow(PersistentSessionClosedException.class);
186        resourceHelper.doesResourceExist(readOnlyTx, fedoraMementoId, false);
187    }
188
189    /**
190     * Test an item is not a ghost node because it exists.
191     */
192    @Test
193    public void testGhostNodeFailure() {
194        containmentIndex.addContainedBy(mockTx, rootId, fedoraId);
195        // Inside the transaction the resource exists, so its not a ghost node.
196        assertTrue(resourceHelper.doesResourceExist(mockTx, fedoraId, false));
197        assertFalse(resourceHelper.isGhostNode(mockTx, fedoraId));
198        // Outside the transaction the resource does not exist.
199        assertFalse(resourceHelper.doesResourceExist(readOnlyTx, fedoraId, false));
200        // Because there are no other items it is not a ghost node.
201        assertFalse(resourceHelper.isGhostNode(readOnlyTx, fedoraId));
202
203        containmentIndex.commitTransaction(mockTx);
204
205        // Now it exists outside the transaction.
206        assertTrue(resourceHelper.doesResourceExist(readOnlyTx, fedoraId, false));
207        // So it can't be a ghost node.
208        assertFalse(resourceHelper.isGhostNode(readOnlyTx, fedoraId));
209    }
210
211    /**
212     * Test that when the resource that does exist shares the ID of a resource that does not, then we have a ghost node.
213     */
214    @Test
215    public void testGhostNodeSuccess() {
216        final var resourceId = fedoraId.resolve("the/child/path");
217        containmentIndex.addContainedBy(mockTx, rootId, resourceId);
218        assertTrue(resourceHelper.doesResourceExist(mockTx, resourceId, false));
219        assertFalse(resourceHelper.doesResourceExist(mockTx, fedoraId, false));
220        assertTrue(resourceHelper.isGhostNode(mockTx, fedoraId));
221        assertFalse(resourceHelper.doesResourceExist(readOnlyTx, resourceId, false));
222        assertFalse(resourceHelper.doesResourceExist(readOnlyTx, fedoraId, false));
223        assertFalse(resourceHelper.isGhostNode(readOnlyTx, fedoraId));
224
225        containmentIndex.commitTransaction(mockTx);
226
227        assertTrue(resourceHelper.doesResourceExist(readOnlyTx, resourceId, false));
228        assertFalse(resourceHelper.doesResourceExist(readOnlyTx, fedoraId,false));
229        assertTrue(resourceHelper.isGhostNode(readOnlyTx, fedoraId));
230    }
231}