I am trying to write a test of a service method. Most of the logic performed in their is covered by tests of those methods. I am mostly trying to see if the correct repository calls are being performed.The method returns the id of the receipt/bon it just created.
I can train the mocks of the repository with the calls being performed in the service. But my test keeps failing, because the service call is unable to return an id value (long). This is because the id is generated by performing the save method followed by the flush. I cannot seem to train my mocks to do this right.
This is the only error eccuring, that it cannot perform the service call, because it returns a null value instead of an id.
How can I mock the repository correct so testing my method will not fail on not having an id?
method:
#Override
#Transactional
public long addBonAndProducts(Boninfo boninfo) {
Consignatiebon bon = new Consignatiebon(boninfo.getGebruiker(), LocalDate.now(), boninfo.getOpmbon(), boninfo.getSolden());
LocalDate dateUit = this.createCorrectDateOut(bon.getDatumIn(), bon.getSolden());
bon.setDatumUit(dateUit);
//creates correct bonnr - logic tested elswhere
String currentLetterBon = this.findCurrentLetter();
Integer numberToInsert = this.findMostRecentBoncode(currentLetterBon);
bon.setBonCode(currentLetterBon, (numberToInsert + 1));
consignatiebonRepository.save(bon);
consignatiebonRepository.flush();
for(var product:boninfo.getLijstproducten()) {
product.setConsignatiebon(bon);
if(!(product.getStatus()==Status.STOCK)) {
product.setDatumUit(dateUit);
}
//creates correct articlenr - logi tested elswhere
String currentLetter = this.findCurrentLetter();
Integer numberToInsertBon = this.findMostRecentArtikelcode(currentLetter);
product.setArtikelCode(currentLetter, (numberToInsertBon + 1));
productRepository.save(product);
productRepository.flush();
long idProduct = product.getProductId();
bon.add(product);
}
consignatiebonRepository.save(bon);
consignatiebonRepository.flush();
bon.setTotalPieces();
bon.setTotalValueBon();
// these values are correct
System.out.println("inside function pieces: " + bon.getTotalPieces());
System.out.println("inside function pieces: " + bon.getTotalPrice());
consignatiebonRepository.save(bon);
// in the test, this is outputted as null, cause the flush method doesn't get performed
System.out.println("inside function pieces: " + bon.getIdConsignatiebon());
return bon.getIdConsignatiebon();
}
test:
#ExtendWith(MockitoExtension.class)
class ProductServiceTest {
private DefaultProductService productService;
private Kleur kleur;
private Maat maat;
private Merk merk;
private Eigenschap eigenschap;
private Product product;
private Product productTwo;
private Product productThree;
private Adres adres;
private Gebruiker gebruiker;
private Consignatiebon consignatiebon;
private Consignatiebon consignatiebonToFill;
#Mock
private ProductRepository productRepository;
#Mock
private GebruikerRepository gebruikerRepository;
#Mock
private ConsignatiebonRepository consignatiebonRepository;
#Mock
private WoocommerceApi woocommerceApi;
#Mock
private Mailing mailing;
#BeforeEach
void beforeEach() {
productService = new DefaultProductService(productRepository, consignatiebonRepository, gebruikerRepository,
woocommerceApi, mailing);
kleur = new Kleur("testkleur");
maat = new Maat("testmaat");
merk = new Merk("testmerk");
eigenschap = new Eigenschap("testclustereigenschap", "testsubeigenschap");
adres = new Adres("teststraat", "testhuisnummer", "testpostcode", "testgemeente", "testland");
gebruiker = new Gebruiker("testvoornaam","testnaam", "testriziv", "testmail#hotmail.be", true,
"testtelefoonnr", adres, Gebruikersrol.TESTLEVERANCIER, "testopmerking");
consignatiebon = new Consignatiebon(gebruiker, LocalDate.of(2020, 1, 1), "testopmerking", true);
product = new Product(gebruiker, consignatiebon, eigenschap, kleur, merk, maat, Soort.DAMES,
"testbeschrijving", BigDecimal.valueOf(10), BigDecimal.valueOf(25), Status.TEKOOP, false, true);
productTwo = new Product(gebruiker, consignatiebon, eigenschap, kleur, merk, maat, Soort.DAMES,
"testbeschrijvingTwo", BigDecimal.valueOf(10), BigDecimal.valueOf(25), Status.TEKOOP, false, true);
productThree = new Product(gebruiker, consignatiebon, eigenschap, kleur, merk, maat, Soort.DAMES,
"testbeschrijvingThree", BigDecimal.valueOf(10), BigDecimal.valueOf(25), Status.TEKOOP, false, true);
}
#Test
void addBonAndProducts() {
when(consignatiebonRepository.save(Mockito.any(Consignatiebon.class))).thenAnswer(i -> i.getArguments()[0]);
when(productRepository.save(Mockito.any(Product.class))).thenAnswer(i -> i.getArguments()[0]);
// when(productService.addBonAndProducts(Mockito.any(Boninfo.class))).thenReturn(1L);
List<Product> productlist = new ArrayList<>();
productlist.add(product);
productlist.add(productTwo);
productlist.add(productThree);
Boninfo testboninfo = new Boninfo(productlist, gebruiker, "testopmerkingbon", true);
productService.addBonAndProducts(testboninfo);
verify(consignatiebonRepository, times(3)).save(consignatiebon);
verify(productRepository, times(1)).save(product);
verify(productRepository, times(1)).save(productTwo);
verify(productRepository, times(1)).save(productThree);
}
}
Related
I'm trying to use spy mockito in my test. Here is my code:
#Spy
AddressServiceImpl service;
#InjectMocks
AddressController controller;
#Test
void loadAddresses(){
User user = new User();
Address address1 = new Address(user,"2345", "5678", "7890", "test#gmail.com", "street");
Address address2 = new Address(user,"9876", "7654", "6543", "test2#gmail.com", "avenue");
service.save(address1);
service.save(address2);
List<Address> searchAdress = new ArrayList<>();
given(service.findByEmailContaining(anyString())).willCallRealMethod();
searchAdress = controller.loadAddress(address1);
}
And in my controller:
private AddressService addressService;
public List<Address> loadAddress(Address address){
List<Address> theAddresses = new ArrayList<>();
theAddresses = addressService.findByEmailContaining("%"+ address.getEmail() + "%");
return theAddresses;
}
I expect that controller searches in my saved addresses(address1,address2) by address1.getEmail() and returns the address1 object. but it dosen't return any thing.
In fact service.save(address1) retuurn null object instead of adress1.
this method call save method from JPA repository.
I am using Corda's Account library and currently unable to transfer a state from one owner (Account) to another (Account) hosted on separate nodes. Getting different errors at different time. But I am pretty sure it has to do with collecting signatures.
Is it ok to collect signature from owner account (anonymous party) or does it have to be the nodes owning key. Also, is it mandatory to collect signatures from all parties involved in the participants list in states class. For instance in the below example, is it necessary to collect signature from issue during a transfer flow.
An Issuer issues a state to an owner using Registration Flow.
An owner can transfer to another owner using Transfer Flow.
I am currently having issues with Transfer Flow.
Transfer Flow
package com.template.flows;
import co.paralleluniverse.fibers.Suspendable;
import com.r3.corda.lib.accounts.contracts.states.AccountInfo;
import com.r3.corda.lib.accounts.workflows.UtilitiesKt;
import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount;
import com.template.accountutilities.NewKeyForAccount;
import com.template.contracts.CarContract;
import com.template.states.CarState;
import net.corda.core.contracts.Command;
import net.corda.core.contracts.StateAndRef;
import net.corda.core.contracts.UniqueIdentifier;
import net.corda.core.flows.*;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.AnonymousParty;
import net.corda.core.identity.Party;
import net.corda.core.node.services.Vault;
import net.corda.core.node.services.vault.QueryCriteria;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
import static com.template.contracts.CarContract.CID;
import java.lang.reflect.Array;
import java.security.PublicKey;
import java.util.*;
import java.util.stream.Collectors;
#InitiatingFlow
#StartableByRPC
public class CarTransferFlowInitiator extends FlowLogic<String> {
private final String carVin;
private final String oldCarOwner;
private final String newCarOwner;
private int input;
public CarTransferFlowInitiator(String carVin,String oldCarOwner, String newCarOwner){
this.carVin = carVin;
this.oldCarOwner = oldCarOwner;
this.newCarOwner = newCarOwner;
}
private final ProgressTracker.Step RETRIEVING_NOTARY = new ProgressTracker.Step("Retrieving Notary");
private final ProgressTracker.Step CREATE_TRANSACTION_INPUT= new ProgressTracker.Step("Creating Transaction Input");
private final ProgressTracker.Step CREATE_TRANSACTION_OUTPUT= new ProgressTracker.Step("Creating Transaction Output");
private final ProgressTracker.Step CREATE_TRANSACTION_BUILDER= new ProgressTracker.Step("Creating transaction Builder");
private final ProgressTracker.Step SIGN_TRANSACTION = new ProgressTracker.Step("Signing Transaction");
private final ProgressTracker.Step INITIATE_SESSION = new ProgressTracker.Step("Initiating session with counterparty");
private final ProgressTracker.Step FINALIZE_FLOW = new ProgressTracker.Step("Finalizing the flow");
private final ProgressTracker progressTracker = new ProgressTracker(
RETRIEVING_NOTARY,
CREATE_TRANSACTION_OUTPUT,
CREATE_TRANSACTION_BUILDER,
SIGN_TRANSACTION,
INITIATE_SESSION,
FINALIZE_FLOW
);
#Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
public StateAndRef<CarState> checkForCarStates(UUID accountId) throws FlowException {
// This returns the old owners unconsumed state
QueryCriteria.VaultQueryCriteria criteria = new QueryCriteria.VaultQueryCriteria().withExternalIds(Arrays.asList(accountId)).withStatus(Vault.StateStatus.UNCONSUMED);
//QueryCriteria generalCriteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED);
//QueryCriteria generalCriteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED);
List<StateAndRef<CarState>> CarStates = getServiceHub().getVaultService().queryBy(CarState.class, criteria).getStates();
boolean inputFound = false;
int t = CarStates.size();
input = 0;
for (int x = 0; x < t; x++) {
if (CarStates.get(x).getState().getData().getCarVIN().equals(carVin)) {
// if (CarStates.get(x).getState().getData().getLinearId().getExternalId().equals(linearId.getExternalId())) {
input = x;
inputFound = true;
}
}
if (inputFound) {
System.out.println("\n Input Found");
} else {
System.out.println("\n Input not found");
throw new FlowException();
}
return CarStates.get(input);
}
#Suspendable
public String call() throws FlowException {
//Retrieve the notary identity from the network map
progressTracker.setCurrentStep(RETRIEVING_NOTARY);
Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
//<-------------------------------------- NEW CODE ------------------------------>
// Get the oldOwnerAccount info here
AccountInfo oldOwnerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(oldCarOwner).get(0).getState().getData();
//Get the party here
AnonymousParty oldOwnerAccount = subFlow(new RequestKeyForAccount(oldOwnerAccountInfo));
// Get the newOwnerAccount info here
AccountInfo newOwnerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(newCarOwner).get(0).getState().getData();
//Get the party here
AnonymousParty newOwnerAccount = subFlow(new RequestKeyForAccount(oldOwnerAccountInfo));
//Create transaction components both input and output for this application
progressTracker.setCurrentStep(CREATE_TRANSACTION_OUTPUT);
StateAndRef<CarState> inputState = null;
inputState = checkForCarStates(oldOwnerAccountInfo.getIdentifier().getId());
System.out.println(inputState.getState().getData().getCarMake()+" "+inputState.getState().getData().getCarVIN());
//Issuer is Toyota
//Owner is AutoSmart
//AnonymousParty issuer = inputState.getState().getData().getIssuer();
PublicKey issuerKey = inputState.getState().getData().getIssuer().getOwningKey();
PublicKey newOwnerKey = newOwnerAccount.getOwningKey();
//Create transaction components both input and output for this application
progressTracker.setCurrentStep(CREATE_TRANSACTION_OUTPUT);
//CarState outputState = new CarState(carMake,carModel,carYear,carMileage,carVin,issuer,carOwner);
// String carMake = inputState.getState().getData().getCarMake();
// String carModel = inputState.getState().getData().getCarModel();
// int carYear = inputState.getState().getData().getCarYear();
// double carMile = inputState.getState().getData().getCarMileAge();
// String carVIN = inputState.getState().getData().getCarVIN();
// AnonymousParty carIssuer = inputState.getState().getData().getIssuer();
// This creating a new output ?
// CarState outputState = new CarState(carMake,carModel,carYear,carMile, carVin,carIssuer,newOwnerAccount);
// System.out.println(outputState.getParticipants());
// Thi swill collect signature from Issuer, Old owner and New Owner
// check what to provide here
//List<PublicKey> requiresSigners = Arrays.asList(getOurIdentity().getOwningKey());
// List<PublicKey> requiredSigners = inputState.getState().getData().getParticipants()
// .stream().map(AbstractParty::getOwningKey)
// .collect(Collectors.toList());
// requiredSigners.add(newOwnerAccount.getOwningKey());
List<PublicKey> requiredSigners = Arrays.asList(issuerKey,oldOwnerAccount.getOwningKey(),newOwnerKey);
System.out.println(issuerKey+","+oldOwnerAccount.getOwningKey()+","+newOwnerKey);
final Command<CarContract.Transfer> txCommand = new Command<>(
new CarContract.Transfer(),
requiredSigners
);
final TransactionBuilder txBuilder = new TransactionBuilder(notary)
.addInputState(inputState)
.addOutputState(inputState.getState().getData().withNewOwner(newOwnerAccount), CID)
.addCommand(txCommand);
// Create the transaction builder here and add compenents to it
progressTracker.setCurrentStep(CREATE_TRANSACTION_BUILDER);
// Sign the transaction
progressTracker.setCurrentStep(SIGN_TRANSACTION);
txBuilder.verify(getServiceHub());
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder, getOurIdentity().getOwningKey());
System.out.println("Current nodes identity: " +getOurIdentity().getName().getOrganisation());
System.out.println("Signed transaction by old owner"+": "+signedTx);
// <----------------------------------------------------------------------->
// Code stops here
List<FlowSession> sessions = new ArrayList<>();
AccountInfo issuerAccount = UtilitiesKt.getAccountService(this).accountInfo("account1").get(0).getState().getData();
sessions.add(initiateFlow(issuerAccount.getHost()));
sessions.add(initiateFlow(newOwnerAccountInfo.getHost()));
// Create session with counterparty
progressTracker.setCurrentStep(INITIATE_SESSION);
System.out.println("Owner Host name is: "+newOwnerAccountInfo.getHost().getName().getOrganisation()+" Account is: "+newOwnerAccountInfo.getName());
System.out.println("Started session with new owner");
SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
signedTx, sessions, Collections.singleton(getOurIdentity().getOwningKey())));
//
// final SignedTransaction fullySignedTx = subFlow(
// new CollectSignaturesFlow(signedTx, sessions, CollectSignaturesFlow.Companion.tracker()));
//
// SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
// signedTx, sessions, CollectSignaturesFlow.tracker()));
// SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
// signedTx, sessions));
System.out.println("Passed the fullySignedTx section of the code.");
//Finalizing the transaction
progressTracker.setCurrentStep(FINALIZE_FLOW);
SignedTransaction sTx = subFlow(new FinalityFlow(fullySignedTx,sessions));
System.out.println("Getting sign from initiator");
return "Transfer Completed";
}
}
State
package com.template.states;
import com.template.contracts.CarContract;
import net.corda.core.contracts.BelongsToContract;
import net.corda.core.contracts.ContractState;
import net.corda.core.contracts.LinearState;
import net.corda.core.contracts.UniqueIdentifier;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.AnonymousParty;
import net.corda.core.serialization.ConstructorForDeserialization;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
#BelongsToContract(CarContract.class)
public class CarState implements ContractState, LinearState {
private String carMake;
private String carModel;
private int carYear;
private double carMileAge;
private String carVIN;
private AnonymousParty issuer;
private AnonymousParty owner;
private UniqueIdentifier linearId;
private List<AbstractParty> participants;
public CarState(String carMake, String carModel, int carYear, double carMileAge, String carVIN, AnonymousParty issuer, AnonymousParty owner) {
this.carMake = carMake;
this.carModel = carModel;
this.carYear = carYear;
this.carMileAge = carMileAge;
this.carVIN = carVIN;
this.issuer = issuer;
this.owner = owner;
this.linearId = new UniqueIdentifier();
}
#ConstructorForDeserialization
public CarState(String carMake, String carModel, int carYear, double carMileAge, String carVIN, AnonymousParty issuer, AnonymousParty owner, UniqueIdentifier linearId) {
this.carMake = carMake;
this.carModel = carModel;
this.carYear = carYear;
this.carMileAge = carMileAge;
this.carVIN = carVIN;
this.issuer = issuer;
this.owner = owner;
this.linearId = linearId;
}
public String getCarMake() {
return carMake;
}
public String getCarModel() {
return carModel;
}
public int getCarYear() {
return carYear;
}
public double getCarMileAge() {
return carMileAge;
}
public String getCarVIN() {
return carVIN;
}
public AnonymousParty getIssuer() {
return issuer;
}
public AnonymousParty getOwner() {
return owner;
}
#NotNull
#Override
public List<AbstractParty> getParticipants() {
return Arrays.asList(issuer,owner);
}
#NotNull
#Override
public UniqueIdentifier getLinearId() {
return linearId;
}
public CarState withNewOwner(AnonymousParty newOwner){
return new CarState(carMake,carModel,carYear,carMileAge, carVIN,issuer,newOwner, linearId);
}
}
There are many things in your flow that need to be changed:
Your approach of searching for a car by the owner then looping through the list until you find the state with the VIN that you want is not good. What if that account had 100,000 cars? You're going to loop through 100,000 records until you find the one with the VIN that you want? Instead, create a custom schema for your car state so you can create a query criteria that filters by VIN. See an example of how to create a custom schema here, here, and here.
Your car state should implement LinearState instead of just ContractState. This will allow you to "update" your state. LinearState has a linearId attribute which is unique on the network level. So when you want to update a car, you consume as an input then you create the updated version as an output; the output car must have the exact same linearId, this way you can track the different version of that car as it changes its attributes values (you query the car by that linearId).
Following step #2, your car state should have 2 constructors; one that doesn't take linearId as an input parameter and inside the constructor you assign a random value to it (this constructor is used when you issue a car, the output state is created using that constructor); then you need a second constructor that takes linearId as an input parameter and is marked with the annotation #ConstructorForDeserialization; this constructor is used in transfer when creating the output car (i.e. the car with the new owner), you will pass to it the linearId of the input car; this way the output car is considered as the updated version of the input car (because they have the same linearId).
As for your question about the required signer; it's you who decides who's the required signer for each command inside the contract (which you didn't share the code of in your question). Logically speaking, when you issue a new car, only the issuer should be required; and when you transfer a car, only the current (old) owner is required to sign.
The assumption is that the initiator of the transfer flow is the current owner of the car (you don't want someone transferring your car without your consent). So in your case the initiator of the flow (i.e. the old owner) is the only required signer (if that's what your contract says); so signing the transaction locally is sufficient.
You still need to create a FlowSession with the node of the new owner; so that finality flow sends the finalized transaction/state and the responder flow (i.e. new owner) receives the finalized transaction/state.
I am trying to transfer a state from one owner to the other back and forth. So always end up having new state created with same values. But intend to pass the state even if it is consumed to another owner. Trying to achieve this with linear state. I have pasted the transfer flow that basically should use the same car state that was issued to transfer across owners. The same consumed car state should be possible to transfer back and forth with the same state being consumed agian and again. Is this possible in Corda. From atheory perspective I am trying to transfer the car back and forth between two or more partys.
State
#ConstructorForDeserialization
public CarState(String carMake, String carModel, int carYear, double carMileAge, String carVIN, Party issuer, Party owner,UniqueIdentifier linearId) {
this.carMake = carMake;
this.carModel = carModel;
this.carYear = carYear;
this.carMileAge = carMileAge;
this.carVIN = carVIN;
this.issuer = issuer;
this.owner = owner;
this.linearId = linearId;
}
Contract
if(!(inputState.getLinearId().getExternalId().equals(outputState.getLinearId().getExternalId()))){
throw new IllegalArgumentException("UUID of input state and output state must be same");
}
Flow
#InitiatingFlow
#StartableByRPC
public class CarTransferFlowInitiator extends FlowLogic<String> {
// private final String carMake;
// private final String carModel;
// private final int carYear;
private final String carVin;
// private final double carMileage;
private final Party carOwner;
private final UniqueIdentifier linearId;
private int input;
public CarTransferFlowInitiator(String carVin,Party carOwner,UniqueIdentifier linearId){
this.carVin = carVin;
this.carOwner = carOwner;
this.linearId = linearId;
}
private final ProgressTracker.Step RETRIEVING_NOTARY = new ProgressTracker.Step("Retrieving Notary");
private final ProgressTracker.Step CREATE_TRANSACTION_INPUT= new ProgressTracker.Step("Creating Transaction Input");
private final ProgressTracker.Step CREATE_TRANSACTION_OUTPUT= new ProgressTracker.Step("Creating Transaction Output");
private final ProgressTracker.Step CREATE_TRANSACTION_BUILDER= new ProgressTracker.Step("Creating transaction Builder");
private final ProgressTracker.Step SIGN_TRANSACTION = new ProgressTracker.Step("Signing Transaction");
private final ProgressTracker.Step INITIATE_SESSION = new ProgressTracker.Step("Initiating session with counterparty");
private final ProgressTracker.Step FINALIZE_FLOW = new ProgressTracker.Step("Finalizing the flow");
private final ProgressTracker progressTracker = new ProgressTracker(
RETRIEVING_NOTARY,
CREATE_TRANSACTION_OUTPUT,
CREATE_TRANSACTION_BUILDER,
SIGN_TRANSACTION,
INITIATE_SESSION,
FINALIZE_FLOW
);
#Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
public StateAndRef<CarState> checkForCarStates() throws FlowException {
//QueryCriteria generalCriteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED);
QueryCriteria generalCriteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL);
List<StateAndRef<CarState>> CarStates = getServiceHub().getVaultService().queryBy(CarState.class, generalCriteria).getStates();
boolean inputFound = false;
int t = CarStates.size();
input = 0;
for (int x = 0; x < t; x++) {
if (CarStates.get(x).getState().getData().getCarVIN().equals(carVin)) {
// if (CarStates.get(x).getState().getData().getLinearId().getExternalId().equals(linearId.getExternalId())) {
input = x;
inputFound = true;
}
}
if (inputFound) {
System.out.println("\n Input Found");
// System.out.println(CarStates.get(input).getState().getData().getCarMake());
// System.out.println(CarStates.get(input).getState().getData().getCarModel());
// System.out.println(CarStates.get(input).getState().getData().getCarYear());
// System.out.println(CarStates.get(input).getState().getData().getCarMileAge());
// System.out.println(CarStates.get(input).getState().getData().getCarVIN());
} else {
System.out.println("\n Input not found");
throw new FlowException();
}
return CarStates.get(input);
}
#Suspendable
public String call() throws FlowException {
//Retrieve the notary identity from the network map
progressTracker.setCurrentStep(RETRIEVING_NOTARY);
Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
//Create transaction components both input and output for this application
progressTracker.setCurrentStep(CREATE_TRANSACTION_OUTPUT);
StateAndRef<CarState> inputState = null;
inputState = checkForCarStates();
//Issuer is Toyota
//Owner is AutoSmart
Party issuer = inputState.getState().getData().getIssuer();
PublicKey issuerKey = issuer.getOwningKey();
//Create transaction components both input and output for this application
progressTracker.setCurrentStep(CREATE_TRANSACTION_OUTPUT);
//CarState outputState = new CarState(carMake,carModel,carYear,carMileage,carVin,issuer,carOwner);
String carMake = inputState.getState().getData().getCarMake();
String carModel = inputState.getState().getData().getCarModel();
int carYear = inputState.getState().getData().getCarYear();
double carMile = inputState.getState().getData().getCarMileAge();
String carVIN = inputState.getState().getData().getCarVIN();
Party carIssuer = inputState.getState().getData().getIssuer();
UniqueIdentifier carLinearId = inputState.getState().getData().getLinearId();
System.out.println(carLinearId);
CarState outputState = new CarState(carMake,carModel,carYear,carMile, carVin,carIssuer,carOwner,carLinearId);
List<PublicKey> requiresSigners = Arrays.asList(issuerKey,getOurIdentity().getOwningKey(),outputState.getOwner().getOwningKey());
// requiresSigners.add(outputState.getIssuer().getOwningKey());
// requiresSigners.add(outputState.getOwner().getOwningKey());
final Command<CarContract.Transfer> txCommand = new Command<>(
new CarContract.Transfer(),
requiresSigners
);
final TransactionBuilder txBuilder = new TransactionBuilder(notary)
.addInputState(inputState)
.addOutputState(outputState, CID)
.addCommand(txCommand);
// Create the transaction builder here and add compenents to it
progressTracker.setCurrentStep(CREATE_TRANSACTION_BUILDER);
//TransactionBuilder txB = new TransactionBuilder(notary);
// PublicKey issuerKey = getServiceHub().getMyInfo().getLegalIdentitiesAndCerts().get(0).getOwningKey();
// PublicKey ownerKey = carOwner.getOwningKey();
//List<PublicKey> requiredSigners = ImmutableList.of(issuerKey,ownerKey);
//ArrayList<PublicKey> requiredSigners = new ArrayList<PublicKey>();
//requiredSigners.add(issuerKey);
//requiredSigners.add(ownerKey);
// Command cmd = new Command(new CarContract.Register(), getOurIdentity().getOwningKey());
//txB.addOutputState(outputState, CID)
// .addCommand(cmd);
// Sign the transaction
progressTracker.setCurrentStep(SIGN_TRANSACTION);
final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder);
// Create session with counterparty
progressTracker.setCurrentStep(INITIATE_SESSION);
FlowSession issuePartySession = initiateFlow((issuer));
FlowSession otherPartySession = initiateFlow(carOwner);
ArrayList<FlowSession> sessions = new ArrayList<>();
sessions.add(otherPartySession);
sessions.add(issuePartySession);
final SignedTransaction fullySignedTx = subFlow(
new CollectSignaturesFlow(signedTx, sessions, CollectSignaturesFlow.Companion.tracker()));
// SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
// signedTx, Arrays.asList(otherPartySession), CollectSignaturesFlow.tracker()));
//Finalizing the transaction
progressTracker.setCurrentStep(FINALIZE_FLOW);
subFlow(new FinalityFlow(fullySignedTx,sessions));
return "Transfer Completed";
}
}
You need to create a second constructor for your linear state which accepts as input parameter a linearId; you should mark the constructor with the annotation #ConstructorForDeserialization.
Otherwise, when your flow suspends (for any reason) and it serializes your linear state, when the flow will attempt to resume and deserialize the state; it will use your current constructor which generates a random linearId, so the flow will end up with a new/different state than yours (because it has a different linearId).
But when you create that second constructor and mark it with the annotation, the flow will use it to deserialize and create an identical state.
You can use that constructor as well when you create the output state instead of using the setter like you do now.
Edit (after you added the flow code):
You cannot use a consumed state as an input; that's what's known as the double-spend problem. Imagine you had a US Dollar state and you tried to use the same dollar twice to buy things; that cannot happen. The notary will throw and exception if you try to use a consumed state as an input.
That's why when you query, you should query for UNCONSUMED instead of ALL.
Your query solution is not correct and not efficient, imagine if you had 100,000 cars; you're going to fetch all 100,000 then loop through them until you find the car with the VIN that you want? First of all you need pagination (Corda will throw an error if your result set returns more than 200 records and you're not using pagination), second of all this will probably drain your Java heap from creating those 100,000 objects and crash your CorDapp.
Instead, you should create a custom schema for your car state; then use a custom query criteria to query by the VIN number. Have a look at the IOU example how they created a custom schema for IOU state (see here and here). Then you can use the custom schema in a VaultCustomQueryCriteria (read here).
I'm currently new to the Spring Boot Java framework and I'm building a simple application. When my service starts, I want to be able to read a raw file from a URL, parse that data, and upload it into my mongodb database of atlas. So far this is what I have:
#Service
public class CoronaVirusDataService {
private List<LocationStats> allConfirmedStats = new ArrayList<>();
MongoOperations mongoOperations;
#PostConstruct // run this method as soon as the application runs
#Scheduled(cron = "* * 1 * * *") // execute this method every day
public void fetchVirusData() {
List<LocationStats> newStats = new ArrayList<>(); // to hold the stats of each state
HttpClient client = HttpClient.newHttpClient();
// creating a new http request
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(ConstantsUtil.VIRUS_CONFIRMED_DATA_URL))
.build();
// get a response by having the client send the request
try {
HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
// parse the body of the request from csv format to readable format
StringReader csvBodyReader = new StringReader(httpResponse.body());
Iterable<CSVRecord> records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(csvBodyReader);
for (CSVRecord record: records) {
// create a model with the parsed data
LocationStats stats = new LocationStats();
stats.setState(record.get("Province/State"));
stats.setCountry(record.get("Country/Region"));
// the latest day
int latestCases = Integer.parseInt(record.get(record.size() - 1));
int prevDayCases = Integer.parseInt(record.get(record.size() - 2));
stats.setLatestTotalCases(latestCases);
stats.setDiffFromPreviousDay(prevDayCases);
mongoOperations.save(LocationStats);
// add to new stats
newStats.add(stats);
}
// assign to class array -> we use this array to display the data
this.allConfirmedStats = newStats;
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
So the main issue with this is the data is not saving to the mongoDB once I call mongoOperations.save(). Also, I've learned that it is bad practice to maintain some type of state in a Service. What is the best practice for this? Will inserting the data into MongoDB take care of that since we are not managing state.
Here is my model class that I want to save to mongodb
#Document(collection = "LocationStats")
public class LocationStats {
/** Location model to show corona virus statistics in each state*/
#Id
private String state;
private String country;
private int latestTotalCases;
private int diffFromPreviousDay;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public int getLatestTotalCases() {
return latestTotalCases;
}
public void setLatestTotalCases(int latestTotalCases) {
this.latestTotalCases = latestTotalCases;
}
public int getDiffFromPreviousDay() {
return diffFromPreviousDay;
}
public void setDiffFromPreviousDay(int diffFromPreviousDay) {
this.diffFromPreviousDay = diffFromPreviousDay;
}
#Override
public String toString() {
return "LocationStats{" +
"state='" + state + '\'' +
", country='" + country + '\'' +
", latestTotalCases=" + latestTotalCases +
'}';
}
}
once I have my models saved into mongoDB, I want to read from the database and get all the data from each collection and display it on the webpage. I'm thinking I'd fetch that data within the controller class and pass it to the frontend, is this good practice? here is my controller class.
#Controller
public class HomeController {
/** Controller class to generate/render the html UI */
#Autowired
CoronaVirusDataService coronaVirusDataService;
#Autowired
MongoOperations mongoOperations;
#GetMapping("/") // map this to the root template
public String home(Model model) {
List<LocationStats> allStats = coronaVirusDataService.getAllConfirmedStats();
// instead of above getter method, have a method call that fetches all data from mongoDB and return it as a List<LocationStats>
// get the total confirmed cases
int totalConfirmedCases = allStats.stream().mapToInt(LocationStats::getLatestTotalCases).sum();
int totalNewCases = allStats.stream().mapToInt(LocationStats::getDiffFromPreviousDay).sum();
// send the models to the view
model.addAttribute("locationStats", allStats);
model.addAttribute("totalReportedCases", totalConfirmedCases);
model.addAttribute("totalNewCases", totalNewCases);
return "home";
}
}
When I am accessing my class to test the methods, I am getting exceptions every time I try to define a new object and use the newly defined object.
Tests in error:
UserInformationControllerTest.deleteUser:83 » NullPointer
UserInformationControllerTest.getUserInfo:27 » NullPointer
UserInformationControllerTest.updateUserInfo:68 » NullPointer
UserOrderControllerTest.createUserOrder:60 » NoSuchElement
UserOrderControllerTest.getUserOrder:47 » NullPointer
UserOrderControllerTest.updateUserOrder:85 » NullPointer
My assignment is to make 4 happy cases and 4 unhappy cases for each class
I am thoroughly confused.
My test class for UserInformation
private HashMap<Integer,UserInformation> userInformationHashMap;
#Test
public void getUserInfo(){
UserInformationController userInformationController = new UserInformationController();
this.userInformationHashMap = new HashMap<>();
int user0 = 0;
int user1 = 1;
UserInformation userInformation0 = new UserInformation("Doug","Jones", "djones#gmail.com","17073");
UserInformation userInformation1 = new UserInformation("Natalie","Peirce", "nataliepeirce12#yahoo.com","dynamicrabbit");
this.userInformationHashMap.put(user0,userInformation0);
this.userInformationHashMap.put(user1,userInformation1);
userInformationController.getUserInfo(user0);
userInformationController.getUserInfo(user1);
Assert.assertEquals(userInformationController.getUserInfo(user0),userInformationController.getUserInfo(user1)); //False
Assert.assertNotEquals(user0,user1); //True
}
#Test
public void createUser(){
UserInformationController userInformationController = new UserInformationController();
this.userInformationHashMap = new HashMap<>();
UserInformation userInformation0 = new UserInformation("Troy","Green","tjg217#verizon.com","2012hummingbirds");
UserInformation userInformation1 = new UserInformation("Sierra", "West","themostimportantwest#msn.com","shadeyglasses77");
int user0 = userInformationController.createUser(userInformation0);//Can you tell me why this does not work
int user1 = userInformationController.createUser(userInformation1);//Can you tell me why this does not work
this.userInformationHashMap.put(user0, userInformation0);
this.userInformationHashMap.put(user1, userInformation1);
Assert.assertNotEquals(this.userInformationHashMap.get(user0),this.userInformationHashMap.get(user1)); //True
Assert.assertNotNull(this.userInformationHashMap.get(user0)); //False
}
#Test
public void updateUserInfo(){
UserInformationController userInformationController = new UserInformationController();
this.userInformationHashMap = new HashMap<>();
int userId = 0;
UserInformation userInformation = new UserInformation("Nicole", "Rigby", "sexygirl69#babellon.com","throwmethemoney");
UserInformation newUserInformation = new UserInformation("Kitty", "Morgan", "ilovecats#cats.com","cats");
this.userInformationHashMap.put(userId,userInformation);
Assert.assertEquals(this.userInformationHashMap.get(userId),userInformation); //True
userInformationController.updateUserInfo(userId,newUserInformation); //Can you tell me why this does not work
Assert.assertNotEquals(this.userInformationHashMap.get(userId),newUserInformation); //False
}
#Test
public void deleteUser(){
UserInformationController userInformationController = new UserInformationController();
this.userInformationHashMap = new HashMap<>();
int user = 0;
UserInformation userInformation = new UserInformation("Camryn","Resele","smartcookie#email.com","28564088");
this.userInformationHashMap.put(user,userInformation);
userInformationController.deleteUser(user);
Assert.assertNull(this.userInformationHashMap.get(user)); //True
Assert.assertTrue(this.userInformationHashMap.containsKey(user)); //False
}
}
UserInformationController
private HashMap<Integer,UserInformation> userInformationHashMap;
/**
* Default json constructor`enter code here`
* #return new user object
*/
#GetMapping(path = "/defaultUserInformation")
public UserInformation test()
{
return new UserInformation("fname", "lname", "email", "pass");
}
/**
* Gets the users information
* #return users information
*/
#GetMapping (path = "/userInfo")
public UserInformation getUserInfo(#RequestParam ("id") int id){
return userInformationHashMap.get(id);
}
/**
* Sets the users information
* #param userInformation userInformation model
* #return users key
*/
#PostMapping (path = "/createUser")
public int createUser(#RequestBody UserInformation userInformation){
if(this.userInformationHashMap == null){
this.userInformationHashMap = new HashMap<>();
}
int maxKey = 0;
if(this.userInformationHashMap.size() != 0){
maxKey = Collections.max(this.userInformationHashMap.keySet()) + 1;
}
this.userInformationHashMap.put(maxKey,userInformation);
return maxKey;
}
#PutMapping (path = "/updateUserInfo")
public void updateUserInfo(#RequestParam ("id") int id, #RequestBody UserInformation userInformation) {
if (this.userInformationHashMap.containsKey(id)) {
this.userInformationHashMap.put(id, userInformation);
}
}
#DeleteMapping (path = "/deleteUser")
public void deleteUser(#RequestParam ("id") int id){
this.userInformationHashMap.remove(id);
}
}
userInformationHashMap within UserInformationController is never initialized, this is why you're getting the NullPointerExceptions.
You're initializing the HashMap in the createUser endpoint and it's never being called within the test.
The createUser endpoint I can't see where it's failing, but anyway this code should really be reorganized because it has many points of failure. The HashMapshould really be initialized when the Bean is created, and you should revisit the way you are calculating the Key.
Also for Controller testing purposes, you should be using MockMvc instead of calling controller methods directly.