mirror of
https://github.com/corda/corda.git
synced 2024-12-30 09:48:59 +00:00
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:
parent
793a52c57a
commit
643ad31736
@ -34,7 +34,7 @@ import net.corda.testing.contracts.DummyState
|
||||
import net.corda.testing.core.*
|
||||
import net.corda.testing.internal.LogHelper
|
||||
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.makeTestIdentityService
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
@ -48,13 +48,15 @@ import java.math.BigDecimal
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Executors
|
||||
import javax.persistence.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class NodeVaultServiceTest {
|
||||
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 DUMMY_CASH_ISSUER = dummyCashIssuer.ref(1)
|
||||
val bankOfCorda = TestIdentity(BOC_NAME)
|
||||
@ -769,4 +771,65 @@ class NodeVaultServiceTest {
|
||||
|
||||
// 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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
@ -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()
|
||||
}
|
Loading…
Reference in New Issue
Block a user