Add tests to confirm that if a Hibernate error occurs the transaction will fail and no states will be persisted

Add tests to NodeVaultServiceTest
Add VaultFlowTest
Add UniqueDummyFungibleContract and UniqueLinearContract to be used in the constraint tests
This commit is contained in:
LankyDan 2018-08-26 15:36:50 +01:00 committed by Mike Hearn
parent 793a52c57a
commit 643ad31736
4 changed files with 242 additions and 2 deletions

View File

@ -34,7 +34,7 @@ import net.corda.testing.contracts.DummyState
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.internal.LogHelper import net.corda.testing.internal.LogHelper
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.vault.VaultFiller import net.corda.testing.internal.vault.*
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestIdentityService import net.corda.testing.node.makeTestIdentityService
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
@ -48,13 +48,15 @@ import java.math.BigDecimal
import java.util.* import java.util.*
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors import java.util.concurrent.Executors
import javax.persistence.*
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertTrue import kotlin.test.assertTrue
class NodeVaultServiceTest { class NodeVaultServiceTest {
private companion object { private companion object {
val cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName, "net.corda.testing.contracts") val cordappPackages = listOf("net.corda.finance.contracts.asset", CashSchemaV1::class.packageName, "net.corda.testing.contracts",
"net.corda.testing.internal.vault")
val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10) val dummyCashIssuer = TestIdentity(CordaX500Name("Snake Oil Issuer", "London", "GB"), 10)
val DUMMY_CASH_ISSUER = dummyCashIssuer.ref(1) val DUMMY_CASH_ISSUER = dummyCashIssuer.ref(1)
val bankOfCorda = TestIdentity(BOC_NAME) val bankOfCorda = TestIdentity(BOC_NAME)
@ -769,4 +771,65 @@ class NodeVaultServiceTest {
// We should never see 2 or 7. // We should never see 2 or 7.
} }
@Test
fun `Unique column constraint failing causes linear state to not persist to vault`() {
fun createTx(): SignedTransaction {
return services.signInitialTransaction(TransactionBuilder(DUMMY_NOTARY).apply {
addOutputState(UniqueDummyLinearContract.State(listOf(megaCorp.party), "Dummy linear id"), UNIQUE_DUMMY_LINEAR_CONTRACT_PROGRAM_ID)
addCommand(DummyCommandData, listOf(megaCorp.publicKey))
})
}
services.recordTransactions(StatesToRecord.ONLY_RELEVANT, listOf(createTx()))
assertThatExceptionOfType(PersistenceException::class.java).isThrownBy {
services.recordTransactions(StatesToRecord.ONLY_RELEVANT, listOf(createTx()))
}
assertEquals(1, database.transaction {
vaultService.queryBy<UniqueDummyLinearContract.State>().states.size
})
}
@Test
fun `Unique column constraint failing causes fungible state to not persist to vault`() {
fun createTx(): SignedTransaction {
return services.signInitialTransaction(TransactionBuilder(DUMMY_NOTARY).apply {
addOutputState(UniqueDummyFungibleContract.State(10.DOLLARS `issued by` DUMMY_CASH_ISSUER, megaCorp.party), UNIQUE_DUMMY_FUNGIBLE_CONTRACT_PROGRAM_ID)
addCommand(DummyCommandData, listOf(megaCorp.publicKey))
})
}
services.recordTransactions(StatesToRecord.ONLY_RELEVANT, listOf(createTx()))
assertThatExceptionOfType(PersistenceException::class.java).isThrownBy {
services.recordTransactions(StatesToRecord.ONLY_RELEVANT, listOf(createTx()))
}
assertEquals(1, database.transaction {
vaultService.queryBy<UniqueDummyFungibleContract.State>().states.size
})
assertEquals(10.DOLLARS.quantity, database.transaction {
vaultService.queryBy<UniqueDummyFungibleContract.State>().states.first().state.data.amount.quantity
})
}
@Test
fun `Unique column constraint failing causes all states in transaction to fail`() {
fun createTx(): SignedTransaction {
return services.signInitialTransaction(TransactionBuilder(DUMMY_NOTARY).apply {
addOutputState(UniqueDummyLinearContract.State(listOf(megaCorp.party), "Dummy linear id"), UNIQUE_DUMMY_LINEAR_CONTRACT_PROGRAM_ID)
addOutputState(DummyDealContract.State(listOf(megaCorp.party), "Dummy linear id"), DUMMY_DEAL_PROGRAM_ID)
addCommand(DummyCommandData, listOf(megaCorp.publicKey))
})
}
services.recordTransactions(StatesToRecord.ONLY_RELEVANT, listOf(createTx()))
assertThatExceptionOfType(PersistenceException::class.java).isThrownBy {
services.recordTransactions(StatesToRecord.ONLY_RELEVANT, listOf(createTx()))
}
assertEquals(1, database.transaction {
vaultService.queryBy<UniqueDummyLinearContract.State>().states.size
})
assertEquals(1, database.transaction {
vaultService.queryBy<DummyDealContract.State>().states.size
})
}
} }

View File

@ -0,0 +1,91 @@
package net.corda.node.services.vault
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.services.queryBy
import net.corda.core.transactions.TransactionBuilder
import net.corda.testing.core.DummyCommandData
import net.corda.testing.core.singleIdentity
import net.corda.testing.internal.vault.DUMMY_DEAL_PROGRAM_ID
import net.corda.testing.internal.vault.DummyDealContract
import net.corda.testing.internal.vault.UNIQUE_DUMMY_LINEAR_CONTRACT_PROGRAM_ID
import net.corda.testing.internal.vault.UniqueDummyLinearContract
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNetworkNotarySpec
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.node.StartedMockNode
import org.assertj.core.api.Assertions
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.util.concurrent.ExecutionException
import kotlin.test.assertEquals
class VaultFlowTest {
private lateinit var mockNetwork: MockNetwork
private lateinit var partyA: StartedMockNode
private lateinit var partyB: StartedMockNode
private lateinit var notaryNode: MockNetworkNotarySpec
@Before
fun setup() {
notaryNode = MockNetworkNotarySpec(CordaX500Name("Notary", "London", "GB"))
mockNetwork = MockNetwork(
listOf(
"net.corda.node.services.vault", "net.corda.testing.internal.vault"
),
notarySpecs = listOf(notaryNode),
threadPerNode = true,
networkSendManuallyPumped = false
)
partyA =
mockNetwork.createNode(MockNodeParameters(legalName = CordaX500Name("PartyA", "Berlin", "DE")))
partyB =
mockNetwork.createNode(MockNodeParameters(legalName = CordaX500Name("PartyB", "Berlin", "DE")))
mockNetwork.startNodes()
}
@After
fun tearDown() {
mockNetwork.stopNodes()
}
@Test
fun `Unique column constraint failing causes states to not persist to vaults`() {
partyA.startFlow(Initiator(listOf(partyA.info.singleIdentity(), partyB.info.singleIdentity()))).get()
Assertions.assertThatExceptionOfType(ExecutionException::class.java).isThrownBy {
partyA.startFlow(Initiator(listOf(partyA.info.singleIdentity(), partyB.info.singleIdentity()))).get()
}
assertEquals(1, partyA.transaction {
partyA.services.vaultService.queryBy<UniqueDummyLinearContract.State>().states.size
})
assertEquals(1, partyB.transaction {
partyB.services.vaultService.queryBy<UniqueDummyLinearContract.State>().states.size
})
assertEquals(1, partyA.transaction {
partyA.services.vaultService.queryBy<DummyDealContract.State>().states.size
})
assertEquals(1, partyB.transaction {
partyB.services.vaultService.queryBy<DummyDealContract.State>().states.size
})
}
}
@InitiatingFlow
class Initiator(private val participants: List<Party>) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val stx = serviceHub.signInitialTransaction(TransactionBuilder(serviceHub.networkMapCache.notaryIdentities.first()).apply {
addOutputState(UniqueDummyLinearContract.State(participants, "Dummy linear id"), UNIQUE_DUMMY_LINEAR_CONTRACT_PROGRAM_ID)
addOutputState(DummyDealContract.State(participants, "linear id"), DUMMY_DEAL_PROGRAM_ID)
addCommand(DummyCommandData, listOf(ourIdentity.owningKey))
})
subFlow(FinalityFlow(stx))
}
}

View File

@ -0,0 +1,45 @@
package net.corda.testing.internal.vault
import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState
import net.corda.core.transactions.LedgerTransaction
import net.corda.testing.core.DummyCommandData
import java.util.*
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Table
const val UNIQUE_DUMMY_FUNGIBLE_CONTRACT_PROGRAM_ID = "net.corda.testing.internal.vault.UniqueDummyFungibleContract"
class UniqueDummyFungibleContract : Contract {
override fun verify(tx: LedgerTransaction) {}
data class State(override val amount: Amount<Issued<Currency>>,
override val owner: AbstractParty) : FungibleAsset<Currency>, QueryableState {
override val exitKeys = setOf(owner.owningKey, amount.token.issuer.party.owningKey)
override val participants = listOf(owner)
override fun withNewOwnerAndAmount(newAmount: Amount<Issued<Currency>>, newOwner: AbstractParty): FungibleAsset<Currency> = copy(amount = amount.copy(newAmount.quantity), owner = newOwner)
override fun withNewOwner(newOwner: AbstractParty) = CommandAndState(DummyCommandData, copy(owner = newOwner))
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(UniqueDummyFungibleStateSchema)
override fun generateMappedObject(schema: MappedSchema): PersistentState {
return UniqueDummyFungibleStateSchema.UniquePersistentDummyFungibleState(currency = amount.token.product.currencyCode)
}
}
}
object UniqueDummyFungibleStateSchema : MappedSchema(schemaFamily = UniqueDummyFungibleStateSchema::class.java, version = 1, mappedTypes = listOf(UniquePersistentDummyFungibleState::class.java)) {
@Entity
@Table(name = "unique_dummy_fungible_state")
class UniquePersistentDummyFungibleState(
@Column(unique = true)
val currency: String
) : PersistentState()
}

View File

@ -0,0 +1,41 @@
package net.corda.testing.internal.vault
import net.corda.core.contracts.Contract
import net.corda.core.contracts.LinearState
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.identity.AbstractParty
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState
import net.corda.core.transactions.LedgerTransaction
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Table
const val UNIQUE_DUMMY_LINEAR_CONTRACT_PROGRAM_ID = "net.corda.testing.internal.vault.UniqueDummyLinearContract"
class UniqueDummyLinearContract : Contract {
override fun verify(tx: LedgerTransaction) {}
data class State(
override val participants: List<AbstractParty>,
override val linearId: UniqueIdentifier) : LinearState, QueryableState {
constructor(participants: List<AbstractParty> = listOf(),
ref: String) : this(participants, UniqueIdentifier(ref))
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(UniqueDummyLinearStateSchema)
override fun generateMappedObject(schema: MappedSchema): PersistentState {
return UniqueDummyLinearStateSchema.UniquePersistentLinearDummyState(id = linearId.externalId!!)
}
}
}
object UniqueDummyLinearStateSchema : MappedSchema(schemaFamily = UniqueDummyLinearStateSchema::class.java, version = 1, mappedTypes = listOf(UniquePersistentLinearDummyState::class.java)) {
@Entity
@Table(name = "unique_dummy_linear_state")
class UniquePersistentLinearDummyState(
@Column(unique = true)
val id: String
) : PersistentState()
}