Commit eab121c6 authored by Michel Schudel's avatar Michel Schudel

more unit tests.

parent 9a6d14bc
......@@ -5,9 +5,13 @@ buildscript {
}
repositories {
mavenCentral()
repositories {
maven { url 'http://repo.spring.io/plugins-release' }
}
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath 'io.spring.gradle:propdeps-plugin:0.0.9.RELEASE'
}
}
......@@ -20,6 +24,10 @@ plugins {
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'propdeps'
apply plugin: 'propdeps-maven'
apply plugin: 'propdeps-idea'
apply plugin: 'propdeps-eclipse'
group = 'nl.craftsmen.craftscoinnode'
version = '0.0.1-SNAPSHOT'
......@@ -69,8 +77,10 @@ bootRun {
args += ["--server.port=${project.port}"]
}
compileJava.dependsOn(processResources)
dependencies {
optional "org.springframework.boot:spring-boot-configuration-processor"
compile('org.springframework.boot:spring-boot-starter-web')
compile("org.springframework.boot:spring-boot-devtools")
compile 'commons-codec:commons-codec:1.10'
......
......@@ -2,11 +2,33 @@ package nl.craftsmen.blockchain.craftscoinnode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@SpringBootApplication
@EnableSwagger2
public class CraftsCoinApplication {
public static void main(String[] args) {
SpringApplication.run(CraftsCoinApplication.class, args);
}
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
@Bean
protected RestTemplate restTemplate() {
return new RestTemplate();
}
}
package nl.craftsmen.blockchain.craftscoinnode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
class CraftsCoinConfig {
@Bean
protected RestTemplate restTemplate() {
return new RestTemplate();
}
}
package nl.craftsmen.blockchain.craftscoinnode;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.validation.constraints.NotNull;
@Configuration
@ConfigurationProperties("node")
public class CraftsCoinConfigurationProperties {
@NotBlank
private String bootstrapPeerHost;
@NotNull
private int bootstrapPeerPort;
@NotBlank
private String miningWalletId;
private String ipAddress;
public void setBootstrapPeerHost(String bootstrapPeerHost) {
this.bootstrapPeerHost = bootstrapPeerHost;
}
public void setBootstrapPeerPort(int bootstrapPeerPort) {
this.bootstrapPeerPort = bootstrapPeerPort;
}
public void setMiningWalletId(String miningWalletId) {
this.miningWalletId = miningWalletId;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getBootstrapPeerHost() {
return bootstrapPeerHost;
}
public int getBootstrapPeerPort() {
return bootstrapPeerPort;
}
public String getMiningWalletId() {
return miningWalletId;
}
public String getIpAddress() {
return ipAddress;
}
}
package nl.craftsmen.blockchain.craftscoinnode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
......@@ -16,7 +16,7 @@ import java.util.stream.Collectors;
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class Blockchain {
private List<Block> chain = new ArrayList<>();
private final List<Block> chain = new ArrayList<>();
/**
* Factory method to create a new blockchain.
......@@ -83,7 +83,7 @@ public class Blockchain {
* @return the most recent block in the blockchain.
*/
private Block getLastBlock() {
return chain.get(chain.size() - 1);
return chain.size() > 0 ? chain.get(chain.size() - 1): null;
}
/**
......
......@@ -18,7 +18,7 @@ import java.io.IOException;
@Component
final class BlockchainRepository {
private InstanceInfo instanceInfo;
private final InstanceInfo instanceInfo;
private static final Logger LOGGER = LoggerFactory.getLogger(BlockchainRepository.class);
......
package nl.craftsmen.blockchain.craftscoinnode.blockchain;
import nl.craftsmen.blockchain.craftscoinnode.CraftsCoinConfigurationProperties;
import nl.craftsmen.blockchain.craftscoinnode.network.Network;
import nl.craftsmen.blockchain.craftscoinnode.transaction.Transaction;
import nl.craftsmen.blockchain.craftscoinnode.transaction.TransactionPool;
......@@ -7,7 +8,6 @@ import nl.craftsmen.blockchain.craftscoinnode.util.InstanceInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
......@@ -26,21 +26,22 @@ public class BlockchainService {
private static final BigDecimal CRAFTSCOIN_MINING_REWARD = BigDecimal.TEN;
private final Network network;
private final BlockchainRepository blockchainRepository;
private final TransactionPool transactionPool;
private final InstanceInfo instanceInfo;
private final String miningWalletId;
private Blockchain blockchain;
private Network network;
private BlockchainRepository blockchainRepository;
private TransactionPool transactionPool;
private InstanceInfo instanceInfo;
@Value("${miningWalletId}")
private String miningWalletId;
@Autowired
public BlockchainService(Network network, BlockchainRepository blockchainRepository, TransactionPool transactionPool, InstanceInfo instanceInfo) {
public BlockchainService(Network network, BlockchainRepository blockchainRepository, TransactionPool transactionPool, InstanceInfo instanceInfo, CraftsCoinConfigurationProperties configuration) {
this.network = network;
this.blockchainRepository = blockchainRepository;
this.transactionPool = transactionPool;
this.instanceInfo = instanceInfo;
this.miningWalletId = configuration.getMiningWalletId();
}
......
package nl.craftsmen.blockchain.craftscoinnode.network;
import nl.craftsmen.blockchain.craftscoinnode.CraftsCoinConfigurationProperties;
import nl.craftsmen.blockchain.craftscoinnode.blockchain.Block;
import nl.craftsmen.blockchain.craftscoinnode.blockchain.Blockchain;
import nl.craftsmen.blockchain.craftscoinnode.transaction.Transaction;
......@@ -7,7 +8,6 @@ import nl.craftsmen.blockchain.craftscoinnode.util.InstanceInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
......@@ -28,25 +28,25 @@ public class Network {
private static final Logger LOGGER = LoggerFactory.getLogger(Network.class);
private String bootstrapPeerHost;
private final String bootstrapPeerHost;
private int bootstrapPeerPort;
private final int bootstrapPeerPort;
private Set<String> peers = new HashSet<>();
private final Set<String> peers = new HashSet<>();
private RestTemplate restTemplate;
private final RestTemplate restTemplate;
private InstanceInfo instanceInfo;
private final InstanceInfo instanceInfo;
private PeersRepository peersRepository;
private final PeersRepository peersRepository;
@Autowired
public Network(InstanceInfo instanceInfo, PeersRepository peersRepository, RestTemplate restTemplate, @Value("${bootstrap.peer.host}") String bootstrapPeerHost, @Value("${bootstrap.peer.port}") int bootstrapPeerPort) {
public Network(InstanceInfo instanceInfo, PeersRepository peersRepository, RestTemplate restTemplate, CraftsCoinConfigurationProperties configuration) {
this.instanceInfo = instanceInfo;
this.peersRepository = peersRepository;
this.restTemplate = restTemplate;
this.bootstrapPeerHost = bootstrapPeerHost;
this.bootstrapPeerPort = bootstrapPeerPort;
this.bootstrapPeerHost = configuration.getBootstrapPeerHost();
this.bootstrapPeerPort = configuration.getBootstrapPeerPort();
}
......
......@@ -20,8 +20,8 @@ import java.util.Set;
@Component
class PeersRepository {
private InstanceInfo instanceInfo;
private ObjectMapper objectMapper = new ObjectMapper();
private final InstanceInfo instanceInfo;
private final ObjectMapper objectMapper = new ObjectMapper();
private static final Logger LOGGER = LoggerFactory.getLogger(PeersRepository.class);
......
......@@ -18,7 +18,7 @@ public class TransactionPool {
private static final Logger LOGGER = LoggerFactory.getLogger(BlockchainService.class);
private Set<Transaction> transactions = new HashSet<>();
private final Set<Transaction> transactions = new HashSet<>();
/**
* Add a new transaction to this pool.
......
package nl.craftsmen.blockchain.craftscoinnode.util;
import nl.craftsmen.blockchain.craftscoinnode.CraftsCoinConfigurationProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.stereotype.Component;
......@@ -16,15 +16,16 @@ public class InstanceInfo {
private static final Logger LOGGER = LoggerFactory.getLogger(InstanceInfo.class);
private ServerProperties serverProperties;
private final ServerProperties serverProperties;
private String thisIpAdress;
private int port;
private String node;
private String thisIpAdress;
@Autowired
public InstanceInfo(ServerProperties serverProperties, @Value("${node.ipAdress:#{null}}") String thisIpAdress) {
public InstanceInfo(ServerProperties serverProperties, CraftsCoinConfigurationProperties configuration) {
this.serverProperties = serverProperties;
this.thisIpAdress = thisIpAdress;
this.thisIpAdress = configuration.getIpAddress();
}
@PostConstruct
......
......@@ -28,14 +28,17 @@ public class Wallet {
this.unconfirmedTransactions = unconfirmedTransactions;
}
@SuppressWarnings("unused")
public BigDecimal getBalance() {
return balance;
}
@SuppressWarnings("unused")
public List<Transaction> getMinedTransactions() {
return minedTransactions;
}
@SuppressWarnings("unused")
public List<Transaction> getUnconfirmedTransactions() {
return unconfirmedTransactions;
}
......
......@@ -13,8 +13,8 @@ import java.util.List;
@Component
public class WalletService {
private BlockchainService blockchainService;
private TransactionPool transactionPool;
private final BlockchainService blockchainService;
private final TransactionPool transactionPool;
@Autowired
public WalletService(BlockchainService blockchainService, TransactionPool transactionPool) {
......
{
"properties": [
{
"name": "node.bootstrapPeerHost",
"type": "java.lang.String",
"description": "bootstrap peer ip address."
},
{
"name": "node.bootstrapPeerPort",
"type": "java.lang.String",
"description": "bootstrap peer ip port."
},
{
"name": "node.miningWalletId",
"type": "java.lang.String",
"description": "wallet id that will receive mined coins."
}
] }
\ No newline at end of file
......@@ -4,11 +4,11 @@ server.port=8080
#if no peers are known to this node, it will try to connect to the node below in an attempt
#to connect to the peer-to-peer network and retrieve known peers from that node.
#of course, when the current running node happens to be the bootstrap node, nothing will happen.
bootstrap.peer.host=localhost
bootstrap.peer.port=8080
node.bootstrapPeerHost=localhost
node.bootstrapPeerPort=8080
#walletId that is used to deposit the reward of succesfully mining a block.
miningWalletId=me
node.miningWalletId=me
#set the property below to the correct, reachable dns address if the application doesn't find out this node's ip address correctly.
#node.ipAdress=x.x.x.x
\ No newline at end of file
#node.ipAddress=x.x.x.x
\ No newline at end of file
......@@ -7,7 +7,7 @@ import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BlockchainServiceApplicationIT {
public class CraftsCoinApplicationIT {
@Test
......
package nl.craftsmen.blockchain.craftscoinnode.blockchain;
import nl.craftsmen.blockchain.craftscoinnode.util.InstanceInfo;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class BlockchainRepositoryTest {
private static final String THISNODE = "thistestnode:8080";
private static final String THISNODE_OTHER = "thistestnodeother:8080";
private InstanceInfo instanceInfo = mock(InstanceInfo.class);
private BlockchainRepository blockchainRepository;
@Before
public void setup() {
blockchainRepository = new BlockchainRepository(instanceInfo);
}
@Test
public void loadingNotExistantFileProducesNull() {
when(instanceInfo.getNode()).thenReturn(THISNODE_OTHER);
assertThat(blockchainRepository.loadBlockChain()).isNull();
}
@Test
public void blockchainCanBeSavedAndLoaded() {
when(instanceInfo.getNode()).thenReturn(THISNODE);
Blockchain blockchain = Blockchain.create();
blockchainRepository.saveBlockChain(blockchain);
Blockchain loadedBlockchain = blockchainRepository.loadBlockChain();
assertThat(loadedBlockchain).isNotNull();
}
@After
public void cleanup() throws IOException {
Path path = Paths.get(System.getProperty("user.dir"), "thistestnode8080-blockchain.json");
Files.deleteIfExists(path);
}
}
\ No newline at end of file
package nl.craftsmen.blockchain.craftscoinnode.network;
import nl.craftsmen.blockchain.craftscoinnode.CraftsCoinConfigurationProperties;
import nl.craftsmen.blockchain.craftscoinnode.blockchain.Block;
import nl.craftsmen.blockchain.craftscoinnode.blockchain.Blockchain;
import nl.craftsmen.blockchain.craftscoinnode.transaction.Transaction;
......@@ -56,11 +57,17 @@ public class NetworkTest {
public void setup() {
when(instanceInfo.getNode()).thenReturn("localhost:8080");
when(instanceInfo.getPort()).thenReturn(8080);
network = new Network(instanceInfo, peersRepository, restTemplate, "localhost", 8080);
CraftsCoinConfigurationProperties craftsCoinConfigurationProperties = new CraftsCoinConfigurationProperties();
craftsCoinConfigurationProperties.setBootstrapPeerHost("localhost");
craftsCoinConfigurationProperties.setBootstrapPeerPort(8080);
network = new Network(instanceInfo, peersRepository, restTemplate, craftsCoinConfigurationProperties);
}
@Test
public void connectingToNetworkWithNoKnownPeersShouldRegisterThroughTheBootstrapPeer() {
network = new Network(instanceInfo, peersRepository, restTemplate, "localhost", 9000);
CraftsCoinConfigurationProperties craftsCoinConfigurationProperties = new CraftsCoinConfigurationProperties();
craftsCoinConfigurationProperties.setBootstrapPeerHost("localhost");
craftsCoinConfigurationProperties.setBootstrapPeerPort(9000);
network = new Network(instanceInfo, peersRepository, restTemplate, craftsCoinConfigurationProperties);
when(peersRepository.loadPeers()).thenReturn(Collections.emptySet());
Set<String> remotePeers = Stream.of("newRemotePeer:8080").collect(Collectors.toSet());
ResponseEntity<Set<String>> response = new ResponseEntity<>(remotePeers, HttpStatus.OK);
......
package nl.craftsmen.blockchain.craftscoinnode.transaction;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
public class TransactionPoolTest {
private TransactionPool transactionPool = new TransactionPool();
private Transaction transaction;
private Transaction transaction2;
@Before
public void setup() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
transactionPool = new TransactionPool();
String transactionAsJson = "{\"id\":\"4c9e82bd-ef03-4b30-8b70-95c63aa5b188\", \"amount\":3,\"from\":\"michel\",\"to\":\"gerry\"}";
String transactionAsJson2 = "{\"id\":\"4c9e82bd-ef03-4b30-8b70-95c63aa5b199\", \"amount\":4,\"from\":\"gerry\",\"to\":\"michel\"}";
transaction = objectMapper.readValue(transactionAsJson, Transaction.class);
transaction2 = objectMapper.readValue(transactionAsJson2, Transaction.class);
transactionPool.addTransaction(transaction);
transactionPool.addTransaction(transaction2);
}
@Test
public void testGetTransactions() {
assertThat(transactionPool.getAllTransactions()).containsExactlyInAnyOrder(transaction, transaction2);
}
@Test
public void testRemoveSelectedTransactions() {
transactionPool.clearTransactions(Collections.singletonList(transaction2));
assertThat(transactionPool.getAllTransactions()).containsExactly(transaction);
}
@Test
public void testRemoveAllTransactions() {
transactionPool.clearTransactions();
assertThat(transactionPool.getAllTransactions()).isEmpty();
}
}
\ No newline at end of file
package nl.craftsmen.blockchain.craftscoinnode.util;
import nl.craftsmen.blockchain.craftscoinnode.CraftsCoinConfigurationProperties;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.autoconfigure.web.ServerProperties;
......@@ -21,14 +22,16 @@ public class InstanceInfoTest {
}
@Test
public void nonConfiguredIpShouldResultInAutomaticIpDiscovery() {
instanceInfo = new InstanceInfo(serverProperties, null);
instanceInfo = new InstanceInfo(serverProperties, new CraftsCoinConfigurationProperties());
instanceInfo.init();
assertThat(instanceInfo.getPort()).isEqualTo(8080);
}
@Test
public void configuredIpShouldProduceThatIpForThisNode() {
instanceInfo = new InstanceInfo(serverProperties, "192.168.0.170");
CraftsCoinConfigurationProperties craftsCoinConfigurationProperties = new CraftsCoinConfigurationProperties();
craftsCoinConfigurationProperties.setIpAddress("192.168.0.170");
instanceInfo = new InstanceInfo(serverProperties, craftsCoinConfigurationProperties);
instanceInfo.init();
assertThat(instanceInfo.getPort()).isEqualTo(8080);
assertThat(instanceInfo.getNode()).isEqualTo("192.168.0.170:8080");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment