RetryingEncryptionService { public EncryptedValue encrypt(final String value) throws Exception { LOGGER.info("Attempting encrypt"); return retryOnErrorWithRemappedKey(() -> keySet.getActive().encrypt(value)); } @Autowired  RetryingEncryptionService(final EncryptionKeySet keySet); EncryptedValue encrypt(final String value); String decrypt(final EncryptedValue encryptedValue);  }
@Test public void encrypt_shouldEncryptTheStringWithoutAttemptingToReconnect() throws Exception { when(keySet.getActive()) .thenReturn(firstActiveKey); final EncryptedValue expectedEncryption = mock(EncryptedValue.class); when(firstActiveKey.encrypt("fake-plaintext")) .thenReturn(expectedEncryption); final EncryptedValue encryptedValue = subject.encrypt("fake-plaintext"); assertThat(encryptedValue, equalTo(expectedEncryption)); verify(encryptionService, times(0)) .reconnect(any(IllegalBlockSizeException.class)); verify(keySet, times(0)).reload(); }
@Test public void encrypt_whenThrowsAnError_retriesEncryptionFailure() throws Exception { when(keySet.getActive()) .thenReturn(firstActiveKey); when(firstActiveKey.encrypt("a value")) .thenThrow(new ProviderException("function 'C_GenerateRandom' returns 0x30")); when(firstActiveKey.getProvider()).thenReturn(encryptionService); when(encryptionService.isLoggedIn()).thenReturn(true); try { subject.encrypt("a value"); fail("Expected exception"); } catch (final ProviderException e) { } final InOrder inOrder = inOrder(firstActiveKey, encryptionService); inOrder.verify(firstActiveKey).encrypt(anyString()); inOrder.verify(encryptionService).reconnect(any(ProviderException.class)); inOrder.verify(firstActiveKey).encrypt(anyString()); }
@Test public void encrypt_whenThrowsAnError_unlocksAfterExceptionAndLocksAgainBeforeEncrypting() throws Exception { when(keySet.getActive()) .thenReturn(firstActiveKey); when(firstActiveKey.encrypt("a value")) .thenThrow(new ProviderException("function 'C_GenerateRandom' returns 0x30")); reset(writeLock); try { subject.encrypt("a value"); } catch (final ProviderException e) { } verify(readLock, times(2)).lock(); verify(readLock, times(2)).unlock(); verify(writeLock, times(1)).unlock(); verify(writeLock, times(1)).lock(); }
@Test public void encryption_whenThrowsAnError_createsNewKeysForUUIDs() throws Exception { when(keySet.getActive()) .thenReturn(firstActiveKey); when(firstActiveKey.encrypt(anyString())) .thenThrow(new ProviderException("function 'C_GenerateRandom' returns 0x30")); when(firstActiveKey.getProvider()).thenReturn(encryptionService); when(encryptionService.isLoggedIn()).thenReturn(true); try { subject.encrypt("a value"); fail("Expected exception"); } catch (final ProviderException e) { } verify(keySet).reload(); }
@Test public void encryption_whenTheOperationSucceedsOnlyAfterReconnection_shouldReturnTheEncryptedString() throws Exception { final EncryptedValue expectedEncryption = mock(EncryptedValue.class); when(keySet.getActive()) .thenReturn(firstActiveKey) .thenReturn(secondActiveKey); when(firstActiveKey.encrypt("fake-plaintext")) .thenThrow(new IllegalBlockSizeException("test exception")); when(firstActiveKey.getProvider()).thenReturn(encryptionService); when(secondActiveKey.encrypt("fake-plaintext")) .thenReturn(expectedEncryption); when(secondActiveKey.getProvider()).thenReturn(encryptionService); when(encryptionService.isLoggedIn()).thenReturn(true); assertThat(subject.encrypt("fake-plaintext"), equalTo(expectedEncryption)); verify(encryptionService, times(1)) .reconnect(any(IllegalBlockSizeException.class)); verify(keySet, times(1)).reload(); }
@Test public void encryption_whenReconnectionFails_shouldNotReloadTheKeySet() throws Exception { final EncryptedValue expectedEncryption = mock(EncryptedValue.class); when(keySet.getActive()) .thenReturn(firstActiveKey) .thenReturn(secondActiveKey); when(firstActiveKey.encrypt("fake-plaintext")) .thenThrow(new IllegalBlockSizeException("test exception")); when(firstActiveKey.getProvider()).thenReturn(encryptionService); when(secondActiveKey.encrypt("fake-plaintext")) .thenReturn(expectedEncryption); when(secondActiveKey.getProvider()).thenReturn(encryptionService); when(encryptionService.isLoggedIn()).thenReturn(false); assertThatThrownBy(() -> { subject.encrypt("fake-plaintext"); }).isInstanceOf(IllegalBlockSizeException.class); verify(encryptionService, times(1)) .reconnect(any(IllegalBlockSizeException.class)); verify(keySet, times(0)).reload(); }
@Test public void encryption_encryptionLocks_acquiresALunaUsageReadLock() throws Exception { reset(writeLock); when(keySet.getActive()) .thenReturn(firstActiveKey); subject.encrypt("a value"); verify(readLock, times(1)).lock(); verify(readLock, times(1)).unlock(); verify(writeLock, times(0)).unlock(); verify(writeLock, times(0)).lock(); }
@Test public void whenUsingTwoThreads_wontRetryTwice() throws Exception { final Object lock = new Object(); final Thread firstThread = new Thread("first") { @Override public void run() { try { subject.encrypt("a value 1"); } catch (final Exception e) { } } }; final Thread secondThread = new Thread("second") { @Override public void run() { try { subject.encrypt("a value 2"); } catch (final Exception e) { } } }; subject = new RacingRetryingEncryptionServiceForTest(firstThread, secondThread, lock); when(keySet.getActive()) .thenReturn(firstActiveKey); when(firstActiveKey.encrypt(anyString())) .thenThrow(new ProviderException("function 'C_GenerateRandom' returns 0x30")); when(firstActiveKey.getProvider()).thenReturn(encryptionService); when(encryptionService.isLoggedIn()).thenReturn(true); firstThread.start(); firstThread.join(); secondThread.join(); verify(keySet, times(1)).reload(); }