package jnpf.flowable.service; import com.baomidou.dynamic.datasource.tx.TransactionContext; import jnpf.flowable.mapper.DmsLifecycleNativeFormMapper; import jnpf.onlinedev.VisualdevOnlineApi; import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.ObjectProvider; import org.springframework.transaction.support.TransactionSynchronizationManager; import static org.junit.Assert.*; import static org.mockito.Mockito.*; public class DmsLifecycleNativeFormAdapterTest { private final DmsLifecycleNativeFormMapper mapper = mock(DmsLifecycleNativeFormMapper.class); @SuppressWarnings("unchecked") private final ObjectProvider provider = mock(ObjectProvider.class); private final VisualdevOnlineApi remote = mock(VisualdevOnlineApi.class); private final VisualdevOnlineApi local = mock(VisualdevOnlineApi.class); private final DmsLifecycleNativeFormAdapter adapter = new DmsLifecycleNativeFormAdapter(mapper, provider, true); private DmsLifecycleNativeFormAdapter.NativeForm protectedForm() { var form = new DmsLifecycleNativeFormAdapter.NativeForm(); form.setType(1); when(mapper.publishedLifecycleForm("form")).thenReturn(form); when(provider.getIfAvailable()).thenReturn(local); TransactionContext.bind("test"); return form; } @After public void cleanup() { TransactionContext.remove(); TransactionSynchronizationManager.clear(); } @Test public void disabledDoesNotReadConfigurationOrChangeRoute() { assertSame(remote, new DmsLifecycleNativeFormAdapter(mapper,provider,false).select("form",remote,true)); verifyNoInteractions(mapper,provider); } @Test public void unrelatedFormsRetainOriginalRoute() { assertSame(remote, adapter.select("usage-form",remote,true)); verifyNoInteractions(provider); } @Test public void saveAndImmediateReadbackUseSameNativeService() { protectedForm(); assertSame(local, adapter.select("form",remote,true)); assertSame(local, adapter.select("form",remote,false)); verifyNoInteractions(remote); } @Test public void nativeDefaultDatasourceSentinelUsesLocalSaveAndReadback() { var form = protectedForm(); // Published online forms use "0" for the default datasource (ServiceBaseUtil). form.setDbLinkId("0"); assertSame(local, adapter.select("form",remote,true)); assertSame(local, adapter.select("form",remote,false)); verifyNoInteractions(remote); } @Test public void ordinaryReadOutsideTransactionRetainsOriginalRoute() { protectedForm(); TransactionContext.remove(); assertSame(remote,adapter.select("form",remote,false)); verifyNoInteractions(local); } @Test public void protectedWriteWithoutTransactionCannotFallBackToFeign() { protectedForm(); TransactionContext.remove(); try {adapter.select("form",remote,true); fail();} catch (IllegalStateException expected) { } verifyNoInteractions(remote,local); } @Test public void missingLocalCapabilityCannotFallBackToFeign() { protectedForm(); when(provider.getIfAvailable()).thenReturn(null); try {adapter.select("form",remote,true); fail();} catch (IllegalStateException expected) { } verifyNoInteractions(remote); } @Test public void linkedDatasourceAndSystemFormsAreRejected() { var form=protectedForm(); form.setDbLinkId("separate-database"); try {adapter.select("form",remote,true); fail();} catch (IllegalStateException expected) { } form.setDbLinkId(null); form.setType(2); try {adapter.select("form",remote,true); fail();} catch (IllegalStateException expected) { } verifyNoInteractions(remote,local); } @Test public void nativeRuntimeFailureIsTaggedForEngineCompensation() { protectedForm(); RuntimeException failure=new IllegalStateException("native persistence failed"); try { adapter.invoke("form",remote,true,api->{throw failure;}); fail(); } catch (DmsLifecycleNativeFormAdapter.NativeFailure error) { assertSame(failure,error.getCause()); } } @Test public void missingNativeReadbackIsTaggedForCompensation() { protectedForm(); try { adapter.invoke("form",remote,false,api->null); fail(); } catch (DmsLifecycleNativeFormAdapter.NativeFailure expected) { } } @Test public void unrelatedRuntimeFailureRetainsOriginalType() { RuntimeException failure=new IllegalArgumentException("ordinary failure"); try { adapter.invoke("usage-form",remote,true,api->{throw failure;}); fail(); } catch (IllegalArgumentException error) { assertSame(failure,error); } } @Test public void mixedTransactionManagersAreRejected() { protectedForm(); TransactionSynchronizationManager.setActualTransactionActive(true); try {adapter.select("form",remote,true); fail();} catch (IllegalStateException expected) { } verifyNoInteractions(remote,local); } }