From f71671e04ad8c4c46e424878315e44c6ef4b5caa Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Mon, 15 Feb 2016 07:33:38 +0100 Subject: [PATCH] Added tests for DefaultMessageListener and ProofOfWorkService and some minor improvements --- .../dissem/bitmessage/ProofOfWorkService.java | 6 +- .../ch/dissem/bitmessage/factory/Factory.java | 2 +- .../ch/dissem/bitmessage/utils/Singleton.java | 6 +- .../bitmessage/BitmessageContextTest.java | 54 ++++++- .../DefaultMessageListenerTest.java | 150 ++++++++++++++++++ .../bitmessage/ProofOfWorkServiceTest.java | 113 +++++++++++++ .../ch/dissem/bitmessage/utils/TestBase.java | 6 +- 7 files changed, 321 insertions(+), 16 deletions(-) create mode 100644 core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java index ecd1099..ad62dde 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java +++ b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java @@ -5,10 +5,10 @@ import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.PlaintextHolder; import ch.dissem.bitmessage.entity.payload.Pubkey; +import ch.dissem.bitmessage.ports.Cryptography; import ch.dissem.bitmessage.ports.MessageRepository; import ch.dissem.bitmessage.ports.ProofOfWorkEngine; import ch.dissem.bitmessage.ports.ProofOfWorkRepository; -import ch.dissem.bitmessage.ports.Cryptography; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,16 +61,14 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC public void onNonceCalculated(byte[] initialHash, byte[] nonce) { ObjectMessage object = powRepo.getItem(initialHash).object; object.setNonce(nonce); -// messageCallback.proofOfWorkCompleted(payload); Plaintext plaintext = messageRepo.getMessage(initialHash); if (plaintext != null) { plaintext.setInventoryVector(object.getInventoryVector()); messageRepo.save(plaintext); } ctx.getInventory().storeObject(object); - ctx.getProofOfWorkRepository().removeObject(initialHash); + powRepo.removeObject(initialHash); ctx.getNetworkHandler().offer(object.getInventoryVector()); -// messageCallback.messageOffered(payload, object.getInventoryVector()); } @Override diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java index 11d4330..e0db3f4 100644 --- a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java +++ b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java @@ -196,7 +196,7 @@ public class Factory { } } - public static ObjectPayload getBroadcast(Plaintext plaintext) { + public static Broadcast getBroadcast(Plaintext plaintext) { BitmessageAddress sendingAddress = plaintext.getFrom(); if (sendingAddress.getVersion() < 4) { return new V4Broadcast(sendingAddress, plaintext); diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java b/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java index 0c7134b..a751c65 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java @@ -19,16 +19,14 @@ package ch.dissem.bitmessage.utils; import ch.dissem.bitmessage.ports.Cryptography; /** - * Created by chris on 20.07.15. + * @author Christian Basler */ public class Singleton { private static Cryptography cryptography; public static void initialize(Cryptography cryptography) { synchronized (Singleton.class) { - if (Singleton.cryptography == null) { - Singleton.cryptography = cryptography; - } + Singleton.cryptography = cryptography; } } diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java index 3eead03..828e794 100644 --- a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java @@ -26,10 +26,12 @@ import ch.dissem.bitmessage.ports.*; import ch.dissem.bitmessage.utils.MessageMatchers; import ch.dissem.bitmessage.utils.Singleton; import ch.dissem.bitmessage.utils.TestUtils; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; import org.junit.Before; import org.junit.Test; -import java.lang.reflect.Field; +import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -50,10 +52,7 @@ public class BitmessageContextTest { @Before public void setUp() throws Exception { - Field field = Singleton.class.getDeclaredField("cryptography"); - field.setAccessible(true); - field.set(null, null); - + Singleton.initialize(null); listener = mock(BitmessageContext.Listener.class); ctx = new BitmessageContext.Builder() .addressRepo(mock(AddressRepository.class)) @@ -93,6 +92,51 @@ public class BitmessageContextTest { .calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class)); } + @Test + public void ensureV2PubkeyIsNotRequestedIfItExistsInInventory() throws Exception { + BitmessageAddress contact = new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"); + when(ctx.internals().getInventory().getObjects(anyLong(), anyLong(), any(ObjectType.class))) + .thenReturn(Collections.singletonList( + TestUtils.loadObjectMessage(2, "V2Pubkey.payload") + )); + + ctx.addContact(contact); + + verify(ctx.addresses(), atLeastOnce()).save(contact); + verify(ctx.internals().getProofOfWorkEngine(), never()) + .calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class)); + } + + @Test + public void ensureV4PubkeyIsNotRequestedIfItExistsInInventory() throws Exception { + BitmessageAddress contact = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); + when(ctx.internals().getInventory().getObjects(anyLong(), anyLong(), any(ObjectType.class))) + .thenReturn(Collections.singletonList( + TestUtils.loadObjectMessage(2, "V4Pubkey.payload") + )); + final BitmessageAddress stored = new BitmessageAddress(contact.getAddress()); + stored.setAlias("Test"); + when(ctx.addresses().getAddress(contact.getAddress())).thenReturn(stored); + + ctx.addContact(contact); + + verify(ctx.addresses(), atLeastOnce()).save(argThat(new BaseMatcher() { + @Override + public boolean matches(Object item) { + return item instanceof BitmessageAddress + && ((BitmessageAddress) item).getPubkey() != null + && stored.getAlias().equals(((BitmessageAddress) item).getAlias()); + } + + @Override + public void describeTo(Description description) { + description.appendText("pubkey must not be null and alias must be ").appendValue(stored.getAlias()); + } + })); + verify(ctx.internals().getProofOfWorkEngine(), never()) + .calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class)); + } + @Test public void ensureSubscriptionIsAddedAndExistingBroadcastsRetrieved() throws Exception { BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"); diff --git a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java new file mode 100644 index 0000000..a6e6c53 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java @@ -0,0 +1,150 @@ +/* + * Copyright 2016 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage; + +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.payload.Broadcast; +import ch.dissem.bitmessage.entity.payload.GetPubkey; +import ch.dissem.bitmessage.entity.payload.Msg; +import ch.dissem.bitmessage.factory.Factory; +import ch.dissem.bitmessage.ports.AddressRepository; +import ch.dissem.bitmessage.ports.MessageRepository; +import ch.dissem.bitmessage.utils.Singleton; +import ch.dissem.bitmessage.utils.TestBase; +import ch.dissem.bitmessage.utils.TestUtils; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Collections; + +import static ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED; +import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST; +import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; +import static ch.dissem.bitmessage.utils.MessageMatchers.plaintext; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; + +/** + * @author Christian Basler + */ +public class DefaultMessageListenerTest extends TestBase { + @Mock + private AddressRepository addressRepo; + @Mock + private MessageRepository messageRepo; + + private InternalContext ctx; + private DefaultMessageListener listener; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + ctx = mock(InternalContext.class); + Singleton.initialize(new BouncyCryptography()); + when(ctx.getAddressRepository()).thenReturn(addressRepo); + when(ctx.getMessageRepository()).thenReturn(messageRepo); + + listener = new DefaultMessageListener(ctx, mock(BitmessageContext.Listener.class)); + } + + @Test + public void ensurePubkeyIsSentOnRequest() throws Exception { + BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); + when(addressRepo.findIdentity(any(byte[].class))) + .thenReturn(identity); + listener.receive(new ObjectMessage.Builder() + .stream(2) + .payload(new GetPubkey(new BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))) + .build()); + verify(ctx).sendPubkey(eq(identity), eq(2L)); + } + + @Test + public void ensureIncomingPubkeyIsAddedToContact() throws Exception { + BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); + BitmessageAddress contact = new BitmessageAddress(identity.getAddress()); + when(addressRepo.findContact(any(byte[].class))) + .thenReturn(contact); + when(messageRepo.findMessages(eq(PUBKEY_REQUESTED), eq(contact))) + .thenReturn(Collections.singletonList( + new Plaintext.Builder(MSG).from(identity).to(contact).message("S", "T").build() + )); + + ObjectMessage objectMessage = new ObjectMessage.Builder() + .stream(2) + .payload(identity.getPubkey()) + .build(); + objectMessage.sign(identity.getPrivateKey()); + objectMessage.encrypt(Singleton.security().createPublicKey(identity.getPublicDecryptionKey())); + listener.receive(objectMessage); + + verify(addressRepo).save(any(BitmessageAddress.class)); + } + + @Test + public void ensureIncomingMessageIsSaved() throws Exception { + BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); + BitmessageAddress contact = new BitmessageAddress(identity.getAddress()); + + when(addressRepo.getIdentities()).thenReturn(Collections.singletonList(identity)); + + ObjectMessage objectMessage = new ObjectMessage.Builder() + .stream(2) + .payload(new Msg(new Plaintext.Builder(MSG) + .from(identity) + .to(contact) + .message("S", "T") + .build())) + .nonce(new byte[8]) + .build(); + objectMessage.sign(identity.getPrivateKey()); + objectMessage.encrypt(identity.getPubkey()); + + listener.receive(objectMessage); + + verify(messageRepo, atLeastOnce()).save(plaintext(MSG)); + } + + @Test + public void ensureIncomingBroadcastIsSaved() throws Exception { + BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); + + when(addressRepo.getSubscriptions(anyLong())).thenReturn(Collections.singletonList(identity)); + + Broadcast broadcast = Factory.getBroadcast(new Plaintext.Builder(BROADCAST) + .from(identity) + .message("S", "T") + .build()); + ObjectMessage objectMessage = new ObjectMessage.Builder() + .stream(2) + .payload(broadcast) + .nonce(new byte[8]) + .build(); + objectMessage.sign(identity.getPrivateKey()); + broadcast.encrypt(); + + listener.receive(objectMessage); + + verify(messageRepo, atLeastOnce()).save(plaintext(BROADCAST)); + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java new file mode 100644 index 0000000..eb5d368 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2016 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage; + +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.payload.GetPubkey; +import ch.dissem.bitmessage.entity.payload.Msg; +import ch.dissem.bitmessage.ports.*; +import ch.dissem.bitmessage.utils.Singleton; +import ch.dissem.bitmessage.utils.TestUtils; +import org.hamcrest.CoreMatchers; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; + +import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.*; + +/** + * @author Christian Basler + */ +public class ProofOfWorkServiceTest { + private ProofOfWorkService proofOfWorkService; + + private Cryptography cryptography; + @Mock + private InternalContext ctx; + @Mock + private ProofOfWorkRepository proofOfWorkRepo; + @Mock + private Inventory inventory; + @Mock + private NetworkHandler networkHandler; + @Mock + private MessageRepository messageRepo; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + cryptography = spy(new BouncyCryptography()); + Singleton.initialize(cryptography); + + ctx = mock(InternalContext.class); + when(ctx.getProofOfWorkRepository()).thenReturn(proofOfWorkRepo); + when(ctx.getInventory()).thenReturn(inventory); + when(ctx.getNetworkHandler()).thenReturn(networkHandler); + when(ctx.getMessageRepository()).thenReturn(messageRepo); + + proofOfWorkService = new ProofOfWorkService(); + proofOfWorkService.setContext(ctx); + } + + @Test + public void ensureMissingProofOfWorkIsDone() { + when(proofOfWorkRepo.getItems()).thenReturn(Arrays.asList(new byte[64])); + when(proofOfWorkRepo.getItem(any(byte[].class))).thenReturn(new ProofOfWorkRepository.Item(null, 1001, 1002)); + doNothing().when(cryptography).doProofOfWork(any(ObjectMessage.class), anyLong(), anyLong(), any(ProofOfWorkEngine.Callback.class)); + + proofOfWorkService.doMissingProofOfWork(); + + verify(cryptography).doProofOfWork((ObjectMessage) isNull(), eq(1001L), eq(1002L), + any(ProofOfWorkEngine.Callback.class)); + } + + @Test + public void ensureCalculatedNonceIsStored() throws Exception { + BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); + BitmessageAddress address = TestUtils.loadContact(); + Plaintext plaintext = new Plaintext.Builder(MSG).from(identity).to(address).message("", "").build(); + ObjectMessage object = new ObjectMessage.Builder() + .payload(new Msg(plaintext)) + .build(); + object.sign(identity.getPrivateKey()); + object.encrypt(address.getPubkey()); + byte[] initialHash = new byte[64]; + byte[] nonce = new byte[]{1, 2, 3, 4, 5, 6, 7, 8}; + + when(proofOfWorkRepo.getItem(initialHash)).thenReturn(new ProofOfWorkRepository.Item(object, 1001, 1002)); + when(messageRepo.getMessage(initialHash)).thenReturn(plaintext); + + proofOfWorkService.onNonceCalculated(initialHash, nonce); + + verify(proofOfWorkRepo).removeObject(eq(initialHash)); + verify(inventory).storeObject(eq(object)); + verify(networkHandler).offer(eq(object.getInventoryVector())); + assertThat(plaintext.getInventoryVector(), equalTo(object.getInventoryVector())); + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java b/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java index e757d91..4c73fe3 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java +++ b/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java @@ -17,12 +17,14 @@ package ch.dissem.bitmessage.utils; import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; +import org.junit.BeforeClass; /** - * Created by chris on 20.07.15. + * @author Christian Basler */ public class TestBase { - static { + @BeforeClass + public static void setUpClass() { Singleton.initialize(new BouncyCryptography()); } }