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.services;
019
020import static org.junit.Assert.assertEquals;
021import static org.mockito.ArgumentMatchers.any;
022import static org.mockito.Mockito.times;
023import static org.mockito.Mockito.verify;
024import static org.mockito.Mockito.when;
025import static org.springframework.test.util.ReflectionTestUtils.setField;
026
027import java.util.List;
028import java.util.Optional;
029import java.util.UUID;
030
031import javax.inject.Inject;
032
033import org.fcrepo.kernel.api.ContainmentIndex;
034import org.fcrepo.kernel.api.Transaction;
035import org.fcrepo.kernel.api.auth.ACLHandle;
036import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
037import org.fcrepo.kernel.api.identifiers.FedoraId;
038import org.fcrepo.kernel.api.models.Binary;
039import org.fcrepo.kernel.api.models.Container;
040import org.fcrepo.kernel.api.models.NonRdfSourceDescription;
041import org.fcrepo.kernel.api.models.ResourceFactory;
042import org.fcrepo.kernel.api.models.ResourceHeaders;
043import org.fcrepo.kernel.api.models.WebacAcl;
044import org.fcrepo.kernel.api.observer.EventAccumulator;
045import org.fcrepo.kernel.api.services.MembershipService;
046import org.fcrepo.kernel.api.services.ReferenceService;
047import org.fcrepo.kernel.impl.TestTransactionHelper;
048import org.fcrepo.kernel.impl.operations.DeleteResourceOperation;
049import org.fcrepo.kernel.impl.operations.DeleteResourceOperationFactoryImpl;
050import org.fcrepo.persistence.api.PersistentStorageSession;
051import org.fcrepo.persistence.api.PersistentStorageSessionManager;
052import org.fcrepo.search.api.SearchIndex;
053
054import org.junit.After;
055import org.junit.Before;
056import org.junit.Test;
057import org.junit.runner.RunWith;
058import org.mockito.ArgumentCaptor;
059import org.mockito.Captor;
060import org.mockito.InjectMocks;
061import org.mockito.Mock;
062import org.mockito.MockitoAnnotations;
063import org.springframework.test.context.ContextConfiguration;
064import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
065
066import com.github.benmanes.caffeine.cache.Cache;
067
068/**
069 * DeleteResourceServiceTest
070 *
071 * @author dbernstein
072 */
073@RunWith(SpringJUnit4ClassRunner.class)
074@ContextConfiguration("/containmentIndexTest.xml")
075public class DeleteResourceServiceImplTest {
076
077    private static final String USER = "fedoraAdmin";
078
079    private Transaction tx;
080
081    @Mock
082    private EventAccumulator eventAccumulator;
083
084    @Mock
085    private PersistentStorageSession pSession;
086
087    @Inject
088    private ContainmentIndex containmentIndex;
089
090    @Mock
091    private SearchIndex searchIndex;
092
093    @Mock
094    private PersistentStorageSessionManager psManager;
095
096    @Mock
097    private ResourceFactory resourceFactory;
098
099    @Mock
100    private Container container;
101
102    @Mock
103    private Container childContainer;
104
105    @Mock
106    private Binary binary;
107
108    @Mock
109    private WebacAcl acl;
110
111    @Mock
112    private NonRdfSourceDescription binaryDesc;
113
114    @Mock
115    private ReferenceService referenceService;
116
117    @Mock
118    private MembershipService membershipService;
119
120    @Mock
121    private ResourceHeaders resourceHeaders;
122    @Mock
123    private ResourceHeaders childHeaders;
124    @Mock
125    private ResourceHeaders descHeaders;
126    @Mock
127    private ResourceHeaders aclHeaders;
128
129    @Mock
130    private Cache<String, Optional<ACLHandle>> authHandleCache;
131
132    @Captor
133    private ArgumentCaptor<DeleteResourceOperation> operationCaptor;
134
135    @InjectMocks
136    private DeleteResourceServiceImpl service;
137
138    private static final FedoraId RESOURCE_ID = FedoraId.create("test-resource");
139    private static final FedoraId CHILD_RESOURCE_ID = FedoraId.create("test-resource-child");
140    private static final FedoraId RESOURCE_DESCRIPTION_ID =
141            FedoraId.create("test-resource-description");
142    private static final FedoraId RESOURCE_ACL_ID = FedoraId.create("test-resource-acl");
143
144    @Before
145    public void setup() {
146        MockitoAnnotations.openMocks(this);
147        final String txId = UUID.randomUUID().toString();
148        tx = TestTransactionHelper.mockTransaction(txId, false);
149        when(psManager.getSession(any(Transaction.class))).thenReturn(pSession);
150        final DeleteResourceOperationFactoryImpl factoryImpl = new DeleteResourceOperationFactoryImpl();
151        setField(service, "deleteResourceFactory", factoryImpl);
152        setField(service, "containmentIndex", containmentIndex);
153        setField(service, "eventAccumulator", eventAccumulator);
154        setField(service, "referenceService", referenceService);
155        setField(service, "membershipService", membershipService);
156        setField(service, "searchIndex", searchIndex);
157        when(container.getFedoraId()).thenReturn(RESOURCE_ID);
158
159        when(pSession.getHeaders(RESOURCE_ID, null)).thenReturn(resourceHeaders);
160        when(pSession.getHeaders(CHILD_RESOURCE_ID, null)).thenReturn(childHeaders);
161        when(pSession.getHeaders(RESOURCE_DESCRIPTION_ID, null)).thenReturn(descHeaders);
162        when(pSession.getHeaders(RESOURCE_ACL_ID, null)).thenReturn(aclHeaders);
163    }
164
165    @After
166    public void cleanUp() {
167        containmentIndex.reset();
168    }
169
170    @Test
171    public void testContainerDelete() throws Exception {
172        when(container.isAcl()).thenReturn(false);
173        when(container.getAcl()).thenReturn(null);
174
175        service.perform(tx, container, USER);
176        containmentIndex.commitTransaction(tx);
177        verifyResourceOperation(RESOURCE_ID, operationCaptor, pSession);
178    }
179
180    @Test
181    public void testRecursiveDelete() throws Exception {
182        when(container.isAcl()).thenReturn(false);
183        when(container.getAcl()).thenReturn(null);
184        when(childContainer.getFedoraId()).thenReturn(CHILD_RESOURCE_ID);
185        when(childContainer.isAcl()).thenReturn(false);
186        when(childContainer.getAcl()).thenReturn(null);
187
188        when(resourceFactory.getResource(tx, CHILD_RESOURCE_ID)).thenReturn(childContainer);
189        containmentIndex.addContainedBy(tx, container.getFedoraId(), childContainer.getFedoraId());
190
191        when(container.isAcl()).thenReturn(false);
192        when(container.getAcl()).thenReturn(null);
193
194        assertEquals(1, containmentIndex.getContains(tx, RESOURCE_ID).count());
195        service.perform(tx, container, USER);
196
197        verify(pSession, times(2)).persist(operationCaptor.capture());
198        final List<DeleteResourceOperation> operations = operationCaptor.getAllValues();
199        assertEquals(2, operations.size());
200
201        assertEquals(CHILD_RESOURCE_ID, operations.get(0).getResourceId());
202        assertEquals(RESOURCE_ID, operations.get(1).getResourceId());
203
204        assertEquals(0, containmentIndex.getContains(tx, RESOURCE_ID).count());
205
206        verify(tx).lockResource(RESOURCE_ID);
207        verify(tx).lockResource(CHILD_RESOURCE_ID);
208    }
209
210    private void verifyResourceOperation(final FedoraId fedoraID,
211                                         final ArgumentCaptor<DeleteResourceOperation> captor,
212                                         final PersistentStorageSession pSession) throws Exception {
213        verify(pSession).persist(captor.capture());
214        final DeleteResourceOperation containerOperation = captor.getValue();
215        assertEquals(fedoraID, containerOperation.getResourceId());
216    }
217
218    @Test
219    public void testAclDelete() throws Exception {
220        when(acl.getFedoraId()).thenReturn(RESOURCE_ACL_ID);
221        when(acl.isAcl()).thenReturn(true);
222        service.perform(tx, acl, USER);
223        verifyResourceOperation(RESOURCE_ACL_ID, operationCaptor, pSession);
224    }
225
226    @Test(expected = RepositoryRuntimeException.class)
227    public void testBinaryDescriptionDelete() throws Exception {
228        when(binaryDesc.getFedoraId()).thenReturn(RESOURCE_DESCRIPTION_ID);
229        service.perform(tx, binaryDesc, USER);
230    }
231
232    @Test
233    public void testBinaryDeleteWithAcl() throws Exception {
234        when(binary.getFedoraId()).thenReturn(RESOURCE_ID);
235        when(binary.isAcl()).thenReturn(false);
236        when(binary.getDescription()).thenReturn(binaryDesc);
237        when(binaryDesc.getFedoraId()).thenReturn(RESOURCE_DESCRIPTION_ID);
238        when(binary.getAcl()).thenReturn(acl);
239        when(acl.getFedoraId()).thenReturn(RESOURCE_ACL_ID);
240
241        service.perform(tx, binary, USER);
242
243        verify(pSession, times(3)).persist(operationCaptor.capture());
244        final List<DeleteResourceOperation> operations = operationCaptor.getAllValues();
245        assertEquals(3, operations.size());
246
247        assertEquals(RESOURCE_DESCRIPTION_ID, operations.get(0).getResourceId());
248        assertEquals(RESOURCE_ACL_ID, operations.get(1).getResourceId());
249        assertEquals(RESOURCE_ID, operations.get(2).getResourceId());
250
251        verify(tx).lockResource(RESOURCE_ID);
252        verify(tx).lockResource(RESOURCE_DESCRIPTION_ID);
253        verify(tx).lockResource(RESOURCE_ACL_ID);
254    }
255
256}