From 58d5aa536f4ac8d85e7f1d56bc2f5eb00cc80674 Mon Sep 17 00:00:00 2001 From: Robert Chaing Date: Fri, 12 Mar 2021 07:41:00 -0800 Subject: [PATCH] Envelope Service Unit tests --- p8e-api/src/test/kotlin/helper/TestUtils.kt | 276 ++++++++- .../kotlin/service/EnvelopeServiceTest.kt | 538 ++++++++++++++++++ 2 files changed, 811 insertions(+), 3 deletions(-) create mode 100644 p8e-api/src/test/kotlin/service/EnvelopeServiceTest.kt diff --git a/p8e-api/src/test/kotlin/helper/TestUtils.kt b/p8e-api/src/test/kotlin/helper/TestUtils.kt index 411cc59..af461db 100644 --- a/p8e-api/src/test/kotlin/helper/TestUtils.kt +++ b/p8e-api/src/test/kotlin/helper/TestUtils.kt @@ -2,10 +2,19 @@ package helper import io.p8e.grpc.Constant import io.p8e.proto.Authentication +import io.p8e.proto.Common +import io.p8e.proto.ContractScope +import io.p8e.proto.ContractSpecs +import io.p8e.proto.Contracts +import io.p8e.proto.PK import io.p8e.util.toByteString import io.p8e.util.toProtoTimestampProv +import io.p8e.util.toProtoUuidProv +import io.p8e.util.toPublicKeyProto import io.provenance.p8e.encryption.ecies.ProvenanceKeyGenerator +import io.provenance.p8e.shared.domain.ScopeRecord import org.jetbrains.exposed.sql.Database +import java.security.KeyPair import java.security.PrivateKey import java.security.Signature import java.time.OffsetDateTime @@ -14,7 +23,10 @@ import java.util.* class TestUtils { companion object { - fun DatabaseConnect(){ + private val scopeUuid = UUID.randomUUID().toProtoUuidProv() + private val groupUuid = UUID.randomUUID().toProtoUuidProv() + + fun DatabaseConnect() { Database.connect( url = listOf( "jdbc:h2:mem:test", @@ -22,7 +34,7 @@ class TestUtils { "LOCK_TIMEOUT=10000", "INIT=" + listOf( "create domain if not exists jsonb as other", - "create domain if not exists TIMESTAMPTZ as TIMESTAMP WITH TIME ZONE" + "create domain if not exists TIMESTAMPTZ as TIMESTAMP" ).joinToString("\\;") ).joinToString(";") + ";", driver = "org.h2.Driver" @@ -45,5 +57,263 @@ class TestUtils { it.sign() } + fun generateTestContract(keys: KeyPair, scopeData: ScopeRecord): Contracts.Contract = + Contracts.Contract.newBuilder() + .setDefinition( + Common.DefinitionSpec.newBuilder() + .setName("def-contract-name") + .setResourceLocation( + Common.Location.newBuilder() + .setRef( + Common.ProvenanceReference.newBuilder() + .setScopeUuid(scopeData.scopeUuid.toProtoUuidProv()) + .setGroupUuid(groupUuid) + .setHash("AyZYcO1gmfndNHU+v4ltISy+nZb5rdwNjfc5+Q66dbpC3tlfF5Nt79usqSZjIz8h3HoJEYcIz3LE7sM09uTUXg==") + .setName("some-name") + .build() + ) + .setClassname("HelloWorldContract") + ) + .setSignature( + Common.Signature.newBuilder() + .setAlgo("algo") + .setProvider("provider") + .setSignature(UUID.randomUUID().toString()) + .setSigner( + PK.SigningAndEncryptionPublicKeys.newBuilder() + .setEncryptionPublicKey(keys.public.toPublicKeyProto()) + .setSigningPublicKey(keys.public.toPublicKeyProto()) + .build() + ) + .build() + ) + ) + .setType(Contracts.ContractType.FACT_BASED) + .setSpec( + Contracts.Fact.newBuilder() + .setDataLocation( + Common.Location.newBuilder() + .setRef( + Common.ProvenanceReference.newBuilder() + .setScopeUuid(scopeData.scopeUuid.toProtoUuidProv()) + .setGroupUuid(groupUuid) + .setHash("AyZYcO1gmfndNHU+v4ltISy+nZb5rdwNjfc5+Q66dbpC3tlfF5Nt79usqSZjIz8h3HoJEYcIz3LE7sM09uTUXg==") + .setName("some-name") + .build() + ) + .setClassname("HelloWorldContract") + .build() + ) + .setName("name") + .build() + ) + .setInvoker( + PK.SigningAndEncryptionPublicKeys.newBuilder() + .setEncryptionPublicKey(keys.public.toPublicKeyProto()) + .setSigningPublicKey(keys.public.toPublicKeyProto()) + .build() + ) + .addAllInputs( + mutableListOf( + Contracts.Fact.newBuilder() + .setDataLocation( + Common.Location.newBuilder() + .setRef( + Common.ProvenanceReference.newBuilder() + .setScopeUuid(scopeData.scopeUuid.toProtoUuidProv()) + .setGroupUuid(groupUuid) + .setHash("AyZYcO1gmfndNHU+v4ltISy+nZb5rdwNjfc5+Q66dbpC3tlfF5Nt79usqSZjIz8h3HoJEYcIz3LE7sM09uTUXg==") + .setName("some-name") + .build() + ) + .setClassname("HelloWorldContract") + .build() + ) + .setName("name") + .build() + ) + ) + .addAllConditions( + mutableListOf( + Contracts.ConditionProto.newBuilder() + .setConditionName("some-condition-name") + .setResult( + Contracts.ExecutionResult.newBuilder() + .setOutput( + Contracts.ProposedFact.newBuilder() + .setName("name") + .setHash("AyZYcO1gmfndNHU+v4ltISy+nZb5rdwNjfc5+Q66dbpC3tlfF5Nt79usqSZjIz8h3HoJEYcIz3LE7sM09uTUXg==") + .setClassname("HelloWorldContract") + .setAncestor(Common.ProvenanceReference.getDefaultInstance()) + .build() + ) + .setResult(Contracts.ExecutionResult.Result.PASS) + .setRecordedAt(OffsetDateTime.now().toProtoTimestampProv()) + .build() + ) + .build() + ) + ) + .addAllConsiderations( + mutableListOf( + Contracts.ConsiderationProto.newBuilder() + .setConsiderationName("name") + .addAllInputs( + mutableListOf( + Contracts.ProposedFact.newBuilder() + .setName("name") + .setHash("AyZYcO1gmfndNHU+v4ltISy+nZb5rdwNjfc5+Q66dbpC3tlfF5Nt79usqSZjIz8h3HoJEYcIz3LE7sM09uTUXg==h") + .setClassname("HelloWorldContract") + .setAncestor(Common.ProvenanceReference.getDefaultInstance()) + .build() + ) + ) + .setResult( + Contracts.ExecutionResult.newBuilder() + .setOutput( + Contracts.ProposedFact.newBuilder() + .setName("name") + .setHash("AyZYcO1gmfndNHU+v4ltISy+nZb5rdwNjfc5+Q66dbpC3tlfF5Nt79usqSZjIz8h3HoJEYcIz3LE7sM09uTUXg==") + .setClassname("HelloWorldContract") + .setAncestor(Common.ProvenanceReference.getDefaultInstance()) + .build() + ) + .setResult(Contracts.ExecutionResult.Result.PASS) + .setRecordedAt(OffsetDateTime.now().toProtoTimestampProv()) + .build() + ) + .build() + ) + ) + .addAllRecitals( + mutableListOf( + Contracts.Recital.newBuilder() + .setSignerRole(ContractSpecs.PartyType.OWNER) + .setSigner( + PK.SigningAndEncryptionPublicKeys.newBuilder() + .setEncryptionPublicKey(keys.public.toPublicKeyProto()) + .setSigningPublicKey(keys.public.toPublicKeyProto()) + .build() + ) + .setAddress("some-address".toByteString()) + .build() + ) + ) + .build() + + fun generateTestEnvelope(keys: KeyPair, scopeData: ScopeRecord, scopeWithLastEvent: Boolean = true, executionUUID: UUID? = null, contract: Contracts.Contract? = null): ContractScope.Envelope { + val executionUuid = executionUUID?.toProtoUuidProv() ?: UUID.randomUUID().toProtoUuidProv() + val contract = contract ?: generateTestContract(keys, scopeData) + + val lastEvent = if(scopeWithLastEvent) { + ContractScope.Event.newBuilder() + .setExecutionUuid(executionUuid) + .setGroupUuid(groupUuid) + .build() + } else { + ContractScope.Event.getDefaultInstance() + } + + return ContractScope.Envelope.newBuilder() + .setRef( + Common.ProvenanceReference.newBuilder() + .setScopeUuid(scopeData.scopeUuid.toProtoUuidProv()) + .setGroupUuid(groupUuid) + .setHash("AyZYcO1gmfndNHU+v4ltISy+nZb5rdwNjfc5+Q66dbpC3tlfF5Nt79usqSZjIz8h3HoJEYcIz3LE7sM09uTUXg==") + .setName("some-name") + .build() // Build ProvenanceReference + ) + .setContract(contract) + .addAllSignatures( + mutableListOf( + Common.Signature.newBuilder() + .setAlgo("algo") + .setProvider("provider") + .setSignature(UUID.randomUUID().toString()) + .setSigner( + PK.SigningAndEncryptionPublicKeys.newBuilder() + .setEncryptionPublicKey(keys.public.toPublicKeyProto()) + .setSigningPublicKey(keys.public.toPublicKeyProto()) + .build() // Build Signer + ) + .build() // Build Signature + ) + ) + .setExecutionUuid(executionUuid) + .setScope( + ContractScope.Scope.newBuilder() + .setUuid(UUID.randomUUID().toProtoUuidProv()) + .addAllParties( + mutableListOf( + Contracts.Recital.newBuilder() + .setSignerRole(ContractSpecs.PartyType.OWNER) + .setSigner( + PK.SigningAndEncryptionPublicKeys.newBuilder() + .setEncryptionPublicKey(keys.public.toPublicKeyProto()) + .setSigningPublicKey(keys.public.toPublicKeyProto()) + .build() // Build Signer + ) + .setAddress("some-address".toByteString()) + .build() // Build Recital + ) + ) + .addAllRecordGroup( + mutableListOf( + ContractScope.RecordGroup.newBuilder() + .setSpecification("some-hash-specification") + .setGroupUuid(groupUuid) + .setExecutor( + PK.SigningAndEncryptionPublicKeys.newBuilder() + .setEncryptionPublicKey(keys.public.toPublicKeyProto()) + .setSigningPublicKey(keys.public.toPublicKeyProto()) + .build() // Build Executor + ) + .addAllParties( + mutableListOf( + Contracts.Recital.newBuilder() + .setSignerRole(ContractSpecs.PartyType.OWNER) + .setSigner( + PK.SigningAndEncryptionPublicKeys.newBuilder() + .setEncryptionPublicKey(keys.public.toPublicKeyProto()) + .setSigningPublicKey(keys.public.toPublicKeyProto()) + .build() // Build Signer + ) + .setAddress("some-address".toByteString()) + .build() // Build Recital + ) + ) + .addAllRecords( + mutableListOf( + ContractScope.Record.newBuilder() + .setName("some-name") + .setHash("AyZYcO1gmfndNHU+v4ltISy+nZb5rdwNjfc5+Q66dbpC3tlfF5Nt79usqSZjIz8h3HoJEYcIz3LE7sM09uTUXg==") + .setClassname("HelloWorldContract") + .addAllInputs( + mutableListOf( + ContractScope.RecordInput.newBuilder() + .setName("some-name") + .setHash("AyZYcO1gmfndNHU+v4ltISy+nZb5rdwNjfc5+Q66dbpC3tlfF5Nt79usqSZjIz8h3HoJEYcIz3LE7sM09uTUXg==") + .setClassname("HelloWorldContract") + .setType(ContractScope.RecordInput.Type.PROPOSED) + .build() // Build RecordInput + ) + ) + .setResult(Contracts.ExecutionResult.Result.PASS) + .setResultName("some-result-name") + .setResultHash("some-result-hash") + .build() // Build Record + ) + + ) + .setClassname("HelloWorldContract") + .build() // Build RecordGroup + ) + ) + .setLastEvent(lastEvent) + .build() // Build Scope + ) + .setStatus(ContractScope.Envelope.Status.CREATED) + .build() // Build Envelope + } } -} +} \ No newline at end of file diff --git a/p8e-api/src/test/kotlin/service/EnvelopeServiceTest.kt b/p8e-api/src/test/kotlin/service/EnvelopeServiceTest.kt new file mode 100644 index 0000000..99e8d60 --- /dev/null +++ b/p8e-api/src/test/kotlin/service/EnvelopeServiceTest.kt @@ -0,0 +1,538 @@ +package service + +import helper.TestUtils +import io.p8e.crypto.Pen +import io.p8e.engine.ContractEngine +import io.p8e.proto.ContractScope +import io.p8e.util.* +import io.provenance.engine.service.* +import io.provenance.os.client.OsClient +import io.provenance.os.domain.inputstream.DIMEInputStream +import io.provenance.p8e.shared.domain.* +import io.provenance.p8e.shared.service.AffiliateService +import io.provenance.p8e.shared.state.EnvelopeStateEngine +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.transactions.transaction +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito +import java.security.KeyPair +import java.time.OffsetDateTime +import java.util.* + +class EnvelopeServiceTest { + + lateinit var envelopeRecord: EnvelopeRecord + + lateinit var scopeRecord: ScopeRecord + + lateinit var envelopeService: EnvelopeService + + lateinit var affiliateService: AffiliateService + + lateinit var osClient: OsClient + + lateinit var mailboxService: MailboxService + + lateinit var envelopeStateEngine: EnvelopeStateEngine + + lateinit var eventService: EventService + + lateinit var metricService: MetricsService + + lateinit var contractEngine: ContractEngine + + lateinit var dimeInputStream: DIMEInputStream + + lateinit var pen: Pen + + val ecKeys: KeyPair = TestUtils.generateKeyPair() + + val signingKeys: KeyPair = TestUtils.generateKeyPair() + + @Before + fun setup(){ + TestUtils.DatabaseConnect() + + transaction { + SchemaUtils.create(EnvelopeTable) + SchemaUtils.create(ScopeTable) + SchemaUtils.create(AffiliateTable) + + scopeRecord = ScopeRecord.new { + uuid = EntityID(UUID.randomUUID(), ScopeTable) + publicKey = ecKeys.public.toHex() + scopeUuid = UUID.randomUUID() + data = ContractScope.Scope.getDefaultInstance() + lastExecutionUuid = UUID.randomUUID() + } + + AffiliateTable.insert { + it[alias] = "alias" + it[publicKey] = ecKeys.public.toHex() + it[privateKey] = ecKeys.private.toHex() + it[whitelistData] = null + it[encryptionPublicKey] = ecKeys.public.toHex() + it[encryptionPrivateKey] = ecKeys.private.toHex() + it[active] = true + it[indexName] = "scopes" + } + } + + //Mock the service + affiliateService = Mockito.mock(AffiliateService::class.java) + osClient = Mockito.mock(OsClient::class.java) + mailboxService = Mockito.mock(MailboxService::class.java) + envelopeStateEngine = Mockito.mock(EnvelopeStateEngine::class.java) + eventService = Mockito.mock(EventService::class.java) + metricService = Mockito.mock(MetricsService::class.java) + contractEngine = Mockito.mock(ContractEngine::class.java) + dimeInputStream = Mockito.mock(DIMEInputStream::class.java) + + envelopeStateEngine = EnvelopeStateEngine() + + pen = Pen(signingKeys.private, signingKeys.public) + + envelopeService = EnvelopeService( + affiliateService = affiliateService, + osClient = osClient, + mailboxService = mailboxService, + envelopeStateEngine = envelopeStateEngine, + eventService = eventService, + metricsService = metricService + ) + } + + + @Test + fun `Verify staged contract that does not exist`(){ + //Setup + val testEnvelope = TestUtils.generateTestEnvelope(ecKeys, scopeRecord) + + //Assumption that EC keys and Signing keys are the same. + Mockito.`when`(affiliateService.getSigningKeyPair(ecKeys.public)).thenReturn(ecKeys) + + Assert.assertEquals(ContractScope.Envelope.Status.CREATED, testEnvelope.status) + + //Execute + val envelopeResult = transaction { envelopeService.stage(ecKeys.public, testEnvelope) } + + //Validate + Assert.assertNotEquals(ContractScope.Envelope.Status.CREATED, envelopeResult.status) + Assert.assertEquals(ContractScope.Envelope.Status.INBOX, envelopeResult.status) + } + + @Test + fun `Verify staged contracts that already exist`() { + //Setup + val testEnvelope = TestUtils.generateTestEnvelope(ecKeys, scopeRecord) + val envelopeState = ContractScope.EnvelopeState.newBuilder() + .setInput(testEnvelope) + .setResult(testEnvelope) + .setIsInvoker(false) + .setContractClassname("HellowWorldContract") + .setExecutedTime(OffsetDateTime.now().toProtoTimestampProv()) + .setChaincodeTime(OffsetDateTime.now().plusSeconds(5).toProtoTimestampProv()) + .setInboxTime(OffsetDateTime.now().plusSeconds(10).toProtoTimestampProv()) + .build() + + transaction { + envelopeRecord = EnvelopeRecord.new { + uuid = EntityID(UUID.randomUUID(), EnvelopeTable) + groupUuid = testEnvelope.ref.groupUuid.toUuidProv() + executionUuid = testEnvelope.executionUuid.toUuidProv() + publicKey = ecKeys.public.toHex() + data = envelopeState + status = ContractScope.Envelope.Status.CREATED + scopeUuid = scopeRecord.uuid + } + + //Assumption that EC keys and Signing keys are the same. + Mockito.`when`(affiliateService.getSigningKeyPair(ecKeys.public)).thenReturn(ecKeys) + + //Execute + val envelopeResult = envelopeService.stage(ecKeys.public, testEnvelope) + + //Validate + Assert.assertEquals(envelopeRecord.scope.scopeUuid, envelopeResult.scope.scopeUuid) + Assert.assertEquals(envelopeRecord.executionUuid, envelopeResult.executionUuid) + Assert.assertEquals(envelopeRecord.uuid.value, envelopeResult.uuid.value) + } + } + + @Test + fun `Validate read envelope has been read with read timestamp`() { + //Setup + val testEnvelope = TestUtils.generateTestEnvelope(ecKeys, scopeRecord) + val envelopeState = ContractScope.EnvelopeState.newBuilder() + .setInput(testEnvelope) + .setResult(testEnvelope) + .setIsInvoker(false) + .setContractClassname("HellowWorldContract") + .setExecutedTime(OffsetDateTime.now().toProtoTimestampProv()) + .setChaincodeTime(OffsetDateTime.now().plusSeconds(5).toProtoTimestampProv()) + .setInboxTime(OffsetDateTime.now().plusSeconds(10).toProtoTimestampProv()) + .build() + + transaction { + envelopeRecord = EnvelopeRecord.new { + uuid = EntityID(UUID.randomUUID(), EnvelopeTable) + groupUuid = testEnvelope.ref.groupUuid.toUuidProv() + executionUuid = testEnvelope.executionUuid.toUuidProv() + publicKey = ecKeys.public.toHex() + data = envelopeState + status = ContractScope.Envelope.Status.CREATED + scopeUuid = scopeRecord.uuid + } + } + + //Execute + val envelopeResult = transaction { envelopeService.read(ecKeys.public, testEnvelope.executionUuid.toUuidProv()) } + + //Validate + Assert.assertNotNull(envelopeResult.readTime) + Assert.assertNotNull(envelopeResult.data.readTime) + Assert.assertNotEquals(envelopeResult.readTime, envelopeRecord.readTime) + Assert.assertNotEquals(envelopeResult.data.readTime, envelopeRecord.data.readTime) + } + + @Test + fun `Validate read envelopes that has been mark as errored`(){ + + //Setup + val testEnvelope = TestUtils.generateTestEnvelope(ecKeys, scopeRecord) + + val errorUuid = UUID.randomUUID() + val envelopeError = ContractScope.EnvelopeError.newBuilder() + .setUuid(errorUuid.toProtoUuidProv()) + .setGroupUuid(testEnvelope.ref.groupUuid) + .setExecutionUuid(testEnvelope.executionUuid) + .setType(ContractScope.EnvelopeError.Type.CONTRACT_INVOCATION) + .setMessage("some-error") + .setReadTime(OffsetDateTime.now().toProtoTimestampProv()) + .setScopeUuid(testEnvelope.scope.uuid) + .setEnvelope(testEnvelope) + .auditedProv() + .build() + + val envelopeState = ContractScope.EnvelopeState.newBuilder() + .setInput(testEnvelope) + .setResult(testEnvelope) + .setIsInvoker(true) + .setContractClassname("HellowWorldContract") + .setExecutedTime(OffsetDateTime.now().toProtoTimestampProv()) + .setChaincodeTime(OffsetDateTime.now().plusSeconds(5).toProtoTimestampProv()) + .setInboxTime(OffsetDateTime.now().plusSeconds(10).toProtoTimestampProv()) + .addAllErrors(mutableListOf(envelopeError)) + .build() + + transaction { + envelopeRecord = EnvelopeRecord.new { + uuid = EntityID(UUID.randomUUID(), EnvelopeTable) + groupUuid = testEnvelope.ref.groupUuid.toUuidProv() + executionUuid = testEnvelope.executionUuid.toUuidProv() + publicKey = ecKeys.public.toHex() + data = envelopeState + status = ContractScope.Envelope.Status.CREATED + scopeUuid = scopeRecord.uuid + } + + //Execute + val envelopeResult = envelopeService.read(ecKeys.public, testEnvelope.executionUuid.toUuidProv(), errorUuid) + + //Validate + Assert.assertEquals(1, envelopeResult.data.errorsCount) + Assert.assertEquals(ContractScope.EnvelopeError.Type.CONTRACT_INVOCATION, envelopeResult.data.errorsList[0].type) + Assert.assertNotNull(envelopeError.message, envelopeResult.data.errorsList[0].message) + } + } + + @Test + fun `Validate envelope complete`(){ + //Setup + val testEnvelope = TestUtils.generateTestEnvelope(ecKeys, scopeRecord) + val envelopeState = ContractScope.EnvelopeState.newBuilder() + .setInput(testEnvelope) + .setResult(testEnvelope) + .setIsInvoker(true) + .setContractClassname("HellowWorldContract") + .setExecutedTime(OffsetDateTime.now().toProtoTimestampProv()) + .setChaincodeTime(OffsetDateTime.now().plusSeconds(5).toProtoTimestampProv()) + .setInboxTime(OffsetDateTime.now().plusSeconds(10).toProtoTimestampProv()) + .build() + + transaction { + EnvelopeTable.insert { + it[groupUuid] = testEnvelope.ref.groupUuid.toUuidProv() + it[executionUuid] = testEnvelope.executionUuid.toUuidProv() + it[publicKey] = ecKeys.public.toHex() + it[scope] = scopeRecord.uuid + it[data] = envelopeState + it[status] = ContractScope.Envelope.Status.INDEX + it[indexTime] = OffsetDateTime.now() + } + + //Execute + envelopeService.complete(ecKeys.public, testEnvelope.executionUuid.toUuidProv()) + + //Validate + val envelopeRecord = EnvelopeTable.selectAll().last() + Assert.assertEquals(ContractScope.Envelope.Status.COMPLETE, envelopeRecord[EnvelopeTable.status]) + Assert.assertNotNull(envelopeRecord[EnvelopeTable.completeTime]) + } + } + + @Test(expected = NotFoundException::class) + fun `Validate NotFoundException is thrown for EnvelopeRecord not in DB`(){ + //Setup + val testEnvelope = TestUtils.generateTestEnvelope(ecKeys, scopeRecord) + + //Execute + transaction{ envelopeService.complete(TestUtils.generateKeyPair().public, testEnvelope.executionUuid.toUuidProv()) } + + //Validate - NotFoundException expected. + } + + @Test + fun `Validate envelope merge`(){ + //Setup + val contract = TestUtils.generateTestContract(ecKeys, scopeRecord) + val testEnvelope = TestUtils.generateTestEnvelope(ecKeys, scopeRecord, contract = contract) + val testEnvelope2 = TestUtils.generateTestEnvelope(signingKeys, scopeRecord, executionUUID = testEnvelope.executionUuid.toUuidProv(), contract = contract) + + val envelopeState = ContractScope.EnvelopeState.newBuilder() + .setInput(testEnvelope) + .setResult(testEnvelope) + .setIsInvoker(true) + .setContractClassname("HellowWorldContract") + .setExecutedTime(OffsetDateTime.now().toProtoTimestampProv()) + .setChaincodeTime(OffsetDateTime.now().minusSeconds(5).toProtoTimestampProv()) + .build() + + Mockito.`when`(affiliateService.getSigningKeyPair(ecKeys.public)).thenReturn(ecKeys) + + transaction { + // Mutable Envelope Record + EnvelopeTable.insert { + it[groupUuid] = testEnvelope.ref.groupUuid.toUuidProv() + it[executionUuid] = testEnvelope.executionUuid.toUuidProv() + it[publicKey] = ecKeys.public.toHex() + it[scope] = scopeRecord.uuid + it[data] = envelopeState + it[status] = ContractScope.Envelope.Status.FRAGMENT + it[fragmentTime] = OffsetDateTime.now() + } + + //Execute + envelopeService.merge(ecKeys.public, testEnvelope2) + + //Validate + val envelopeRecord = EnvelopeTable.selectAll().last() + Assert.assertEquals(ContractScope.Envelope.Status.SIGNED, envelopeRecord[EnvelopeTable.status]) + Assert.assertNotNull(envelopeRecord[EnvelopeTable.signedTime]) + Assert.assertEquals(2, envelopeRecord[EnvelopeTable.data].result.signaturesCount) + } + } + + @Test + fun `Validate envelope merge for signature that already exists`(){ + //Setup + val testEnvelope = TestUtils.generateTestEnvelope(ecKeys, scopeRecord) + val envelopeState = ContractScope.EnvelopeState.newBuilder() + .setInput(testEnvelope) + .setResult(testEnvelope) + .setIsInvoker(true) + .setContractClassname("HellowWorldContract") + .setExecutedTime(OffsetDateTime.now().toProtoTimestampProv()) + .setChaincodeTime(OffsetDateTime.now().minusSeconds(5).toProtoTimestampProv()) + .build() + + Mockito.`when`(affiliateService.getSigningKeyPair(ecKeys.public)).thenReturn(ecKeys) + + transaction { + envelopeRecord = EnvelopeRecord.new { + uuid = EntityID(UUID.randomUUID(), EnvelopeTable) + groupUuid = testEnvelope.ref.groupUuid.toUuidProv() + executionUuid = testEnvelope.executionUuid.toUuidProv() + publicKey = ecKeys.public.toHex() + data = envelopeState + status = ContractScope.Envelope.Status.CHAINCODE + scopeUuid = scopeRecord.uuid + } + + //Execute + val record = envelopeService.merge(ecKeys.public, testEnvelope) + + //Validate + Assert.assertEquals(envelopeRecord.data, record.data) // Nothing has changed. + } + } + + @Test + fun `Validate envelope index`(){ + //Setup + val testEnvelope = TestUtils.generateTestEnvelope(ecKeys, scopeRecord) + val envelopeState = ContractScope.EnvelopeState.newBuilder() + .setInput(testEnvelope) + .setResult(testEnvelope) + .setIsInvoker(true) + .setContractClassname("HellowWorldContract") + .setExecutedTime(OffsetDateTime.now().toProtoTimestampProv()) + .setChaincodeTime(OffsetDateTime.now().minusSeconds(5).toProtoTimestampProv()) + .build() + + transaction { + envelopeRecord = EnvelopeRecord.new { + uuid = EntityID(UUID.randomUUID(), EnvelopeTable) + groupUuid = testEnvelope.ref.groupUuid.toUuidProv() + executionUuid = testEnvelope.executionUuid.toUuidProv() + publicKey = ecKeys.public.toHex() + data = envelopeState + status = ContractScope.Envelope.Status.CHAINCODE + scopeUuid = scopeRecord.uuid + } + + //Execute + envelopeService.index(envelopeRecord, testEnvelope.scope, "abc-hash", 100L) + + //Validate + Assert.assertNotEquals(ContractScope.Envelope.Status.CHAINCODE, envelopeRecord.status) + Assert.assertEquals(ContractScope.Envelope.Status.INDEX, envelopeRecord.status) + Assert.assertNotNull(envelopeRecord.indexTime) + } + } + + @Test + fun `Validate indexing does not happen if indexTime is available`(){ + //Setup + val testIndexTime = OffsetDateTime.now().minusNanos(10) + + transaction { + scopeRecord = ScopeRecord.new { + uuid = EntityID(UUID.randomUUID(), ScopeTable) + publicKey = ecKeys.public.toHex() + scopeUuid = UUID.randomUUID() + data = ContractScope.Scope.getDefaultInstance() + lastExecutionUuid = UUID.randomUUID() + + } + + val testEnvelope = TestUtils.generateTestEnvelope(ecKeys, scopeRecord) + val envelopeState = ContractScope.EnvelopeState.newBuilder() + .setInput(testEnvelope) + .setResult(testEnvelope) + .setIsInvoker(true) + .setContractClassname("HellowWorldContract") + .setExecutedTime(OffsetDateTime.now().toProtoTimestampProv()) + .setChaincodeTime(OffsetDateTime.now().minusSeconds(5).toProtoTimestampProv()) + .setIndexTime(testIndexTime.toProtoTimestampProv()) + .build() + + envelopeRecord = EnvelopeRecord.new { + uuid = EntityID(UUID.randomUUID(), EnvelopeTable) + groupUuid = testEnvelope.ref.groupUuid.toUuidProv() + executionUuid = testEnvelope.executionUuid.toUuidProv() + publicKey = ecKeys.public.toHex() + data = envelopeState + status = ContractScope.Envelope.Status.CHAINCODE + scopeUuid = scopeRecord.uuid + indexTime = testIndexTime + } + + //Execute + envelopeService.index(envelopeRecord, testEnvelope.scope, "abc-hash", 100L) + + //Validate + Assert.assertEquals(ContractScope.Envelope.Status.CHAINCODE, envelopeRecord.status) + Assert.assertNotEquals(ContractScope.Envelope.Status.INDEX, envelopeRecord.status) + Assert.assertNotNull(envelopeRecord.indexTime) + } + } + + @Test + fun `Validate indexing does not happen if Scope has last events`(){ + //Setup + val testEnvelope = TestUtils.generateTestEnvelope(ecKeys, scopeRecord, false) + val envelopeState = ContractScope.EnvelopeState.newBuilder() + .setInput(testEnvelope) + .setResult(testEnvelope) + .setIsInvoker(true) + .setContractClassname("HellowWorldContract") + .setExecutedTime(OffsetDateTime.now().toProtoTimestampProv()) + .setChaincodeTime(OffsetDateTime.now().minusSeconds(5).toProtoTimestampProv()) + .build() + + transaction { + envelopeRecord = EnvelopeRecord.new { + uuid = EntityID(UUID.randomUUID(), EnvelopeTable) + groupUuid = testEnvelope.ref.groupUuid.toUuidProv() + executionUuid = testEnvelope.executionUuid.toUuidProv() + publicKey = ecKeys.public.toHex() + data = envelopeState + status = ContractScope.Envelope.Status.CHAINCODE + scopeUuid = scopeRecord.uuid + } + + //Execute + envelopeService.index(envelopeRecord, testEnvelope.scope, "abc-hash", 100L) + + //Validate + Assert.assertEquals(ContractScope.Envelope.Status.CHAINCODE, envelopeRecord.status) + Assert.assertNotEquals(ContractScope.Envelope.Status.INDEX, envelopeRecord.status) + Assert.assertNull(envelopeRecord.indexTime) + } + } + + @Test + fun `Validate errored envelope is recorded`() { + //Setup + val testEnvelope = TestUtils.generateTestEnvelope(ecKeys, scopeRecord) + + val errorUuid = UUID.randomUUID() + val envelopeError = ContractScope.EnvelopeError.newBuilder() + .setUuid(errorUuid.toProtoUuidProv()) + .setGroupUuid(testEnvelope.ref.groupUuid) + .setExecutionUuid(testEnvelope.executionUuid) + .setType(ContractScope.EnvelopeError.Type.CONTRACT_INVOCATION) + .setMessage("some-error") + .setReadTime(OffsetDateTime.now().toProtoTimestampProv()) + .setScopeUuid(testEnvelope.scope.uuid) + .setEnvelope(testEnvelope) + .auditedProv() + .build() + + val envelopeState = ContractScope.EnvelopeState.newBuilder() + .setInput(testEnvelope) + .setResult(testEnvelope) + .setIsInvoker(true) + .setContractClassname("HellowWorldContract") + .setExecutedTime(OffsetDateTime.now().toProtoTimestampProv()) + .setChaincodeTime(OffsetDateTime.now().plusSeconds(5).toProtoTimestampProv()) + .setInboxTime(OffsetDateTime.now().plusSeconds(10).toProtoTimestampProv()) + .build() + + transaction { + EnvelopeTable.insert { + it[groupUuid] = testEnvelope.ref.groupUuid.toUuidProv() + it[executionUuid] = testEnvelope.executionUuid.toUuidProv() + it[publicKey] = ecKeys.public.toHex() + it[scope] = scopeRecord.uuid + it[data] = envelopeState + it[status] = ContractScope.Envelope.Status.ERROR + it[errorTime] = OffsetDateTime.now() + } + + //Execute + envelopeService.error(ecKeys.public, envelopeError) + + //Validate + val record = EnvelopeTable.selectAll().last() + Assert.assertNotNull(record[EnvelopeTable.data].errorsList[0]) + Assert.assertEquals(ContractScope.EnvelopeError.Type.CONTRACT_INVOCATION, record[EnvelopeTable.data].errorsList[0].type) + } + } +} \ No newline at end of file