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 */ 006package org.fcrepo.kernel.impl; 007 008import static org.junit.Assert.assertEquals; 009import static org.junit.Assert.assertNotNull; 010import static org.junit.Assert.fail; 011import static org.mockito.ArgumentMatchers.any; 012import static org.mockito.Mockito.never; 013import static org.mockito.Mockito.verify; 014import static org.mockito.Mockito.when; 015import static org.springframework.test.util.ReflectionTestUtils.setField; 016 017import java.time.Duration; 018 019import org.fcrepo.common.db.DbTransactionExecutor; 020import org.fcrepo.config.FedoraPropsConfig; 021import org.fcrepo.kernel.api.ContainmentIndex; 022import org.fcrepo.kernel.api.cache.UserTypesCache; 023import org.fcrepo.kernel.api.exception.TransactionClosedException; 024import org.fcrepo.kernel.api.exception.TransactionNotFoundException; 025import org.fcrepo.kernel.api.lock.ResourceLockManager; 026import org.fcrepo.kernel.api.observer.EventAccumulator; 027import org.fcrepo.kernel.api.services.MembershipService; 028import org.fcrepo.kernel.api.services.ReferenceService; 029import org.fcrepo.persistence.api.PersistentStorageSession; 030import org.fcrepo.persistence.api.PersistentStorageSessionManager; 031 032import org.fcrepo.search.api.SearchIndex; 033import org.junit.Before; 034import org.junit.Test; 035import org.junit.runner.RunWith; 036import org.mockito.Mock; 037import org.mockito.junit.MockitoJUnitRunner; 038import org.springframework.transaction.PlatformTransactionManager; 039 040/** 041 * <p>TransactionTest class.</p> 042 * 043 * @author mohideen 044 */ 045@RunWith(MockitoJUnitRunner.Silent.class) 046public class TransactionManagerImplTest { 047 048 private TransactionImpl testTx; 049 050 private TransactionManagerImpl testTxManager; 051 052 @Mock 053 private PersistentStorageSessionManager pssManager; 054 055 @Mock 056 private PersistentStorageSession psSession; 057 058 @Mock 059 private ContainmentIndex containmentIndex; 060 061 @Mock 062 private EventAccumulator eventAccumulator; 063 064 @Mock 065 private ReferenceService referenceService; 066 067 @Mock 068 private MembershipService membershipService; 069 070 @Mock 071 private SearchIndex searchIndex; 072 073 @Mock 074 private PlatformTransactionManager platformTransactionManager; 075 076 @Mock 077 private ResourceLockManager resourceLockManager; 078 079 @Mock 080 private UserTypesCache userTypesCache; 081 082 private FedoraPropsConfig fedoraPropsConfig; 083 084 @Before 085 public void setUp() { 086 fedoraPropsConfig = new FedoraPropsConfig(); 087 fedoraPropsConfig.setSessionTimeout(Duration.ofMillis(180000)); 088 testTxManager = new TransactionManagerImpl(); 089 when(pssManager.getSession(any())).thenReturn(psSession); 090 setField(testTxManager, "pSessionManager", pssManager); 091 setField(testTxManager, "containmentIndex", containmentIndex); 092 setField(testTxManager, "searchIndex", searchIndex); 093 setField(testTxManager, "eventAccumulator", eventAccumulator); 094 setField(testTxManager, "referenceService", referenceService); 095 setField(testTxManager, "membershipService", membershipService); 096 setField(testTxManager, "dbTransactionExecutor", new DbTransactionExecutor()); 097 setField(testTxManager, "fedoraPropsConfig", fedoraPropsConfig); 098 setField(testTxManager, "resourceLockManager", resourceLockManager); 099 setField(testTxManager, "userTypesCache", userTypesCache); 100 testTx = (TransactionImpl) testTxManager.create(); 101 } 102 103 @Test 104 public void testCreateTransaction() { 105 testTx = (TransactionImpl) testTxManager.create(); 106 assertNotNull(testTx); 107 } 108 109 @Test 110 public void testGetTransaction() { 111 final TransactionImpl tx = (TransactionImpl) testTxManager.get(testTx.getId()); 112 assertNotNull(tx); 113 assertEquals(testTx.getId(), tx.getId()); 114 } 115 116 @Test(expected = TransactionNotFoundException.class) 117 public void testGetTransactionWithInvalidID() { 118 testTxManager.get("invalid-id"); 119 } 120 121 @Test(expected = TransactionClosedException.class) 122 public void testGetExpiredTransaction() throws Exception { 123 testTx.expire(); 124 try { 125 testTxManager.get(testTx.getId()); 126 } finally { 127 // Make sure rollback is triggered 128 verify(psSession).rollback(); 129 } 130 } 131 132 @Test 133 public void testCleanupClosedTransactions() { 134 fedoraPropsConfig.setSessionTimeout(Duration.ofMillis(10000)); 135 136 final var commitTx = testTxManager.create(); 137 commitTx.commit(); 138 final var continuingTx = testTxManager.create(); 139 final var rollbackTx = testTxManager.create(); 140 rollbackTx.rollback(); 141 142 // verify that transactions retrievable before cleanup 143 try { 144 testTxManager.get(commitTx.getId()); 145 fail("Transaction must be committed"); 146 } catch (final TransactionClosedException e) { 147 //expected 148 } 149 try { 150 testTxManager.get(rollbackTx.getId()); 151 fail("Transaction must be rolled back"); 152 } catch (final TransactionClosedException e) { 153 //expected 154 } 155 156 assertNotNull("Continuing transaction must be present", 157 testTxManager.get(continuingTx.getId())); 158 159 testTxManager.cleanupClosedTransactions(); 160 161 // Verify that the closed transactions are stick around since they haven't expired yet 162 try { 163 testTxManager.get(commitTx.getId()); 164 fail("Transaction must be present but committed"); 165 } catch (final TransactionClosedException e) { 166 //expected 167 } 168 try { 169 testTxManager.get(rollbackTx.getId()); 170 fail("Transaction must be present but rolled back"); 171 } catch (final TransactionClosedException e) { 172 //expected 173 } 174 175 // Force expiration of the closed transactions, rather than waiting for it 176 commitTx.expire(); 177 rollbackTx.expire(); 178 testTxManager.cleanupClosedTransactions(); 179 180 // verify that closed transactions cleanedup 181 182 verify(pssManager).removeSession(commitTx.getId()); 183 verify(pssManager).removeSession(rollbackTx.getId()); 184 verify(pssManager, never()).removeSession(continuingTx.getId()); 185 186 try { 187 testTxManager.get(commitTx.getId()); 188 fail("Committed transaction was not cleaned up"); 189 } catch (final TransactionNotFoundException e) { 190 //expected 191 } 192 try { 193 testTxManager.get(rollbackTx.getId()); 194 fail("Rolled back transaction was not cleaned up"); 195 } catch (final TransactionNotFoundException e) { 196 //expected 197 } 198 199 assertNotNull("Continuing transaction must be present", 200 testTxManager.get(continuingTx.getId())); 201 } 202 203 // Check that the scheduled cleanup process rolls back expired transactions, but leaves 204 // them around until the next cleanup call so that they can be queried. 205 @Test 206 public void testCleanupExpiringTransaction() throws Exception { 207 fedoraPropsConfig.setSessionTimeout(Duration.ofMillis(0)); 208 209 final var expiringTx = testTxManager.create(); 210 211 Thread.sleep(100); 212 213 testTxManager.cleanupClosedTransactions(); 214 215 try { 216 testTxManager.get(expiringTx.getId()); 217 fail("Transaction must be expired"); 218 } catch (final TransactionClosedException e) { 219 //expected 220 } 221 222 verify(psSession).rollback(); 223 verify(pssManager).removeSession(expiringTx.getId()); 224 225 testTxManager.cleanupClosedTransactions(); 226 227 try { 228 testTxManager.get(expiringTx.getId()); 229 fail("Expired transaction was not cleaned up"); 230 } catch (final TransactionNotFoundException e) { 231 //expected 232 } 233 } 234}