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.http.api;
019
020import static java.time.Instant.now;
021import static org.fcrepo.http.commons.session.TransactionConstants.ATOMIC_EXPIRES_HEADER;
022import static org.junit.Assert.assertEquals;
023import static org.junit.Assert.assertNotNull;
024import static org.mockito.ArgumentMatchers.anyString;
025import static org.mockito.Mockito.doThrow;
026import static org.mockito.Mockito.never;
027import static org.mockito.Mockito.verify;
028import static org.mockito.Mockito.when;
029import static org.springframework.test.util.ReflectionTestUtils.setField;
030
031import java.net.URI;
032import java.net.URISyntaxException;
033import java.security.Principal;
034
035import javax.servlet.http.HttpServletRequest;
036import javax.ws.rs.core.Response;
037import javax.ws.rs.core.SecurityContext;
038import javax.ws.rs.core.UriInfo;
039
040import org.fcrepo.config.FedoraPropsConfig;
041import org.fcrepo.http.commons.api.rdf.HttpIdentifierConverter;
042import org.fcrepo.kernel.api.Transaction;
043import org.fcrepo.kernel.api.TransactionManager;
044import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
045import org.fcrepo.kernel.api.exception.TransactionClosedException;
046import org.fcrepo.kernel.api.exception.TransactionNotFoundException;
047import org.junit.Before;
048import org.junit.Test;
049import org.junit.runner.RunWith;
050import org.mockito.AdditionalMatchers;
051import org.mockito.ArgumentMatchers;
052import org.mockito.Mock;
053import org.mockito.junit.MockitoJUnitRunner;
054
055/**
056 * <p>TransactionsTest class.</p>
057 *
058 * @author awoods
059 */
060@RunWith(MockitoJUnitRunner.Silent.class)
061public class TransactionsTest {
062
063    private static final String VALID_TX_ID = "123";
064    private static final String VALID_TX_URI = "http://example.com/fcr:tx/123";
065
066    private Transactions testObj;
067
068    @Mock
069    private Transaction mockTransaction;
070
071    @Mock
072    private TransactionManager mockTxManager;
073
074    @Mock
075    private UriInfo mockUriInfo;
076
077    @Mock
078    private HttpIdentifierConverter mockIdConverter;
079
080    @Mock
081    private HttpServletRequest mockRequest;
082
083    @Mock
084    private Principal mockPrincipal;
085
086    @Mock
087    private SecurityContext mockSecurityContext;
088
089    private FedoraPropsConfig propsConfig;
090
091    @Before
092    public void setUp() {
093        propsConfig = new FedoraPropsConfig();
094        testObj = new Transactions();
095        mockTransaction.setShortLived(false);
096        when(mockTxManager.create()).thenReturn(mockTransaction);
097        when(mockTxManager.get(VALID_TX_ID)).thenReturn(mockTransaction);
098        when(mockTxManager.get(AdditionalMatchers.not(ArgumentMatchers.eq(VALID_TX_ID))))
099            .thenThrow(new TransactionNotFoundException("No Transaction found with transactionId"));
100        when(mockTransaction.getId()).thenReturn("123");
101        when(mockTransaction.getExpires()).thenReturn(now().plusSeconds(100));
102
103        when(mockUriInfo.getBaseUri()).thenReturn(URI.create("http://localhost/rest"));
104
105        setField(testObj, "txManager", mockTxManager);
106        setField(testObj, "uriInfo", mockUriInfo);
107        setField(testObj, "fedoraPropsConfig", propsConfig);
108    }
109
110    @Test
111    public void shouldStartANewTransaction() throws URISyntaxException {
112        setField(testObj, "identifierConverter", mockIdConverter);
113        setField(testObj, "servletRequest", mockRequest);
114
115        when(mockIdConverter.toExternalId(anyString())).thenReturn(VALID_TX_URI);
116
117        final Response response = testObj.createTransaction();
118        assertEquals(201, response.getStatus());
119        assertEquals(VALID_TX_URI, response.getHeaderString("Location"));
120    }
121
122    @Test
123    public void shouldCommitATransaction() {
124        final Response response = testObj.commit(VALID_TX_ID);
125        assertEquals(204, response.getStatus());
126        verify(mockTransaction).commit();
127    }
128
129    @Test
130    public void shouldErrorIfCommitClosedTransaction() {
131        when(mockTxManager.get(VALID_TX_ID))
132                .thenThrow(new TransactionClosedException("Transaction closed"));
133        final Response rollback = testObj.commit(VALID_TX_ID);
134        assertEquals(410, rollback.getStatus());
135    }
136
137    @Test
138    public void shouldErrorIfCommitNonExistingTransactionId() {
139        final Response response = testObj.commit("tx:404");
140        assertEquals(404, response.getStatus());
141    }
142
143    @Test
144    public void shouldErrorIfCommitFails() {
145        doThrow(new RepositoryRuntimeException("Oops")).when(mockTransaction).commit();
146        final Response response = testObj.commit(VALID_TX_ID);
147        assertEquals(409, response.getStatus());
148    }
149
150    @Test
151    public void shouldRollBackATransaction() {
152        final Response response = testObj.rollback(VALID_TX_ID);
153        assertEquals(204, response.getStatus());
154        verify(mockTransaction).rollback();
155    }
156
157    @Test
158    public void shouldErrorIfRollbackNonExistingTransactionId() {
159        final Response rollback = testObj.rollback("tx:404");
160        assertEquals(404, rollback.getStatus());
161    }
162
163    @Test
164    public void shouldErrorIfRollbackClosedTransaction() {
165        when(mockTxManager.get(VALID_TX_ID))
166                .thenThrow(new TransactionClosedException("Transaction closed"));
167        final Response rollback = testObj.rollback(VALID_TX_ID);
168        assertEquals(410, rollback.getStatus());
169    }
170
171    @Test
172    public void shouldErrorIfRollbackFails() {
173        when(mockTxManager.get(VALID_TX_ID))
174                .thenThrow(new RepositoryRuntimeException("Rollback failed"));
175        final Response rollback = testObj.rollback(VALID_TX_ID);
176        assertEquals(409, rollback.getStatus());
177    }
178
179    @Test
180    public void shouldRefreshATransaction() {
181        final Response response = testObj.refreshTransaction(VALID_TX_ID);
182        assertEquals(204, response.getStatus());
183        assertNotNull(response.getHeaderString(ATOMIC_EXPIRES_HEADER));
184        verify(mockTransaction).refresh();
185    }
186
187    @Test
188    public void shouldErrorIfRefreshNonExistentTx() {
189        final Response response = testObj.refreshTransaction("tx:404");
190        assertEquals(404, response.getStatus());
191        verify(mockTransaction, never()).refresh();
192    }
193
194    @Test
195    public void shouldErrorIfRefreshExpiredTx() {
196        when(mockTxManager.get(VALID_TX_ID))
197                .thenThrow(new TransactionClosedException("Transaction expired"));
198        final Response response = testObj.refreshTransaction(VALID_TX_ID);
199        assertEquals(410, response.getStatus());
200        verify(mockTransaction, never()).refresh();
201    }
202
203    @Test
204    public void shouldGetTransactionStatus() {
205        final Response response = testObj.getTransactionStatus(VALID_TX_ID);
206        assertEquals(204, response.getStatus());
207        assertNotNull(response.getHeaderString(ATOMIC_EXPIRES_HEADER));
208        verify(mockTransaction, never()).refresh();
209    }
210
211    @Test
212    public void shouldErrorIfGetStatusNonExistentTx() {
213        final Response response = testObj.getTransactionStatus("tx:404");
214        assertEquals(404, response.getStatus());
215    }
216
217    @Test
218    public void shouldErrorIfGetStatusExpired() {
219        when(mockTxManager.get(VALID_TX_ID))
220                .thenThrow(new TransactionClosedException("Transaction expired"));
221        final Response response = testObj.getTransactionStatus(VALID_TX_ID);
222        assertEquals(410, response.getStatus());
223    }
224}