From 5558a9e1e02f71d2d5caba3af159c1c817ebf756 Mon Sep 17 00:00:00 2001 From: Anton Baliasnikov Date: Wed, 25 Oct 2023 11:52:22 +0100 Subject: [PATCH] test: add anoncreds issuance happy path bdd scenario (#767) Signed-off-by: Anton Baliasnikov --- README.md | 2 +- infrastructure/shared/docker-compose-demo.yml | 1 + .../src/main/kotlin/models/AnoncredsSchema.kt | 17 +++ .../models/{Schema.kt => JsonSchema.kt} | 4 +- ...chemaProperty.kt => JsonSchemaProperty.kt} | 2 +- .../src/test/kotlin/common/TestConstants.kt | 14 +- .../src/test/kotlin/features/CommonSteps.kt | 4 +- .../credentials/IssueCredentialsSteps.kt | 125 ++++++++++++++++-- .../schemas/CredentialSchemasSteps.kt | 6 +- .../credentials/issue_credentials.feature | 23 +++- 10 files changed, 171 insertions(+), 27 deletions(-) create mode 100644 tests/integration-tests/src/main/kotlin/models/AnoncredsSchema.kt rename tests/integration-tests/src/main/kotlin/models/{Schema.kt => JsonSchema.kt} (77%) rename tests/integration-tests/src/main/kotlin/models/{SchemaProperty.kt => JsonSchemaProperty.kt} (79%) diff --git a/README.md b/README.md index 1867a7c59e..19a45729d3 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@
Coverage Status Unit tests - End-to-end tests + End-to-end tests Performance tests


diff --git a/infrastructure/shared/docker-compose-demo.yml b/infrastructure/shared/docker-compose-demo.yml index 14a42d9b33..0fc0461c3e 100644 --- a/infrastructure/shared/docker-compose-demo.yml +++ b/infrastructure/shared/docker-compose-demo.yml @@ -33,6 +33,7 @@ services: image: ghcr.io/input-output-hk/prism-agent:${PRISM_AGENT_VERSION} environment: DIDCOMM_SERVICE_URL: http://${DOCKERHOST}:${PORT}/didcomm + REST_SERVICE_URL: http://${DOCKERHOST}:${PORT}/prism-agent PRISM_NODE_HOST: prism-node PRISM_NODE_PORT: 50053 SECRET_STORAGE_BACKEND: postgres diff --git a/tests/integration-tests/src/main/kotlin/models/AnoncredsSchema.kt b/tests/integration-tests/src/main/kotlin/models/AnoncredsSchema.kt new file mode 100644 index 0000000000..ae79a5d118 --- /dev/null +++ b/tests/integration-tests/src/main/kotlin/models/AnoncredsSchema.kt @@ -0,0 +1,17 @@ +package models + +import com.google.gson.annotations.SerializedName + +class AnoncredsSchema( + @SerializedName("name") + val name: String, + + @SerializedName("version") + val version: String, + + @SerializedName("issuerId") + val issuerId: String, + + @SerializedName("attrNames") + val attrNames: List +) diff --git a/tests/integration-tests/src/main/kotlin/models/Schema.kt b/tests/integration-tests/src/main/kotlin/models/JsonSchema.kt similarity index 77% rename from tests/integration-tests/src/main/kotlin/models/Schema.kt rename to tests/integration-tests/src/main/kotlin/models/JsonSchema.kt index 59db1c85a7..dac0cef8a4 100644 --- a/tests/integration-tests/src/main/kotlin/models/Schema.kt +++ b/tests/integration-tests/src/main/kotlin/models/JsonSchema.kt @@ -2,7 +2,7 @@ package models import com.google.gson.annotations.SerializedName -data class Schema( +data class JsonSchema( @SerializedName("\$id") var id: String = "", @@ -16,5 +16,5 @@ data class Schema( var type: String = "", @SerializedName("properties") - val properties: MutableMap = mutableMapOf() + val properties: MutableMap = mutableMapOf() ) diff --git a/tests/integration-tests/src/main/kotlin/models/SchemaProperty.kt b/tests/integration-tests/src/main/kotlin/models/JsonSchemaProperty.kt similarity index 79% rename from tests/integration-tests/src/main/kotlin/models/SchemaProperty.kt rename to tests/integration-tests/src/main/kotlin/models/JsonSchemaProperty.kt index 676d47151b..dc118bf7b0 100644 --- a/tests/integration-tests/src/main/kotlin/models/SchemaProperty.kt +++ b/tests/integration-tests/src/main/kotlin/models/JsonSchemaProperty.kt @@ -2,7 +2,7 @@ package models import com.google.gson.annotations.SerializedName -data class SchemaProperty( +data class JsonSchemaProperty( @SerializedName("type") var type: String = "" ) diff --git a/tests/integration-tests/src/test/kotlin/common/TestConstants.kt b/tests/integration-tests/src/test/kotlin/common/TestConstants.kt index f3b10b8432..36258df158 100644 --- a/tests/integration-tests/src/test/kotlin/common/TestConstants.kt +++ b/tests/integration-tests/src/test/kotlin/common/TestConstants.kt @@ -1,8 +1,8 @@ package common import io.iohk.atala.prism.models.* -import models.Schema -import models.SchemaProperty +import models.JsonSchema +import models.JsonSchemaProperty import java.time.Duration import java.util.* @@ -22,16 +22,16 @@ object TestConstants { ) val CREDENTIAL_SCHEMA_TYPE = "https://w3c-ccg.github.io/vc-json-schemas/schema/2.0/schema.json" - val SCHEMA_TYPE = "https://json-schema.org/draft/2020-12/schema" + val SCHEMA_TYPE_JSON = "https://json-schema.org/draft/2020-12/schema" - val jsonSchema = Schema( + val jsonSchema = JsonSchema( id = "https://example.com/student-schema-1.0", - schema = SCHEMA_TYPE, + schema = SCHEMA_TYPE_JSON, description = "Student schema", type = "object", properties = mutableMapOf( - "name" to SchemaProperty(type = "string"), - "age" to SchemaProperty(type = "integer") + "name" to JsonSchemaProperty(type = "string"), + "age" to JsonSchemaProperty(type = "integer") ) ) diff --git a/tests/integration-tests/src/test/kotlin/features/CommonSteps.kt b/tests/integration-tests/src/test/kotlin/features/CommonSteps.kt index 1713f2ad20..95a9ab8762 100644 --- a/tests/integration-tests/src/test/kotlin/features/CommonSteps.kt +++ b/tests/integration-tests/src/test/kotlin/features/CommonSteps.kt @@ -150,6 +150,7 @@ class CommonSteps { ) val receivedCredential = SerenityRest.lastResponse().get().contents!!.findLast { credential -> credential.protocolState == IssueCredentialRecord.ProtocolState.CREDENTIAL_RECEIVED + && credential.credentialFormat == IssueCredentialRecord.CredentialFormat.JWT } if (receivedCredential != null) { @@ -162,7 +163,8 @@ class CommonSteps { publishDidSteps.createsUnpublishedDid(issuer) publishDidSteps.hePublishesDidToLedger(issuer) issueSteps.acmeOffersACredential(issuer, holder, "short") - issueSteps.bobRequestsTheCredential(holder) + issueSteps.holderReceivesCredentialOffer(holder) + issueSteps.holderAcceptsCredentialOfferForJwt(holder) issueSteps.acmeIssuesTheCredential(issuer) issueSteps.bobHasTheCredentialIssued(holder) } diff --git a/tests/integration-tests/src/test/kotlin/features/credentials/IssueCredentialsSteps.kt b/tests/integration-tests/src/test/kotlin/features/credentials/IssueCredentialsSteps.kt index a187fb0f5a..8238474631 100644 --- a/tests/integration-tests/src/test/kotlin/features/credentials/IssueCredentialsSteps.kt +++ b/tests/integration-tests/src/test/kotlin/features/credentials/IssueCredentialsSteps.kt @@ -7,15 +7,15 @@ import io.cucumber.java.en.Then import io.cucumber.java.en.When import io.iohk.atala.automation.extensions.get import io.iohk.atala.automation.serenity.ensure.Ensure -import io.iohk.atala.prism.models.AcceptCredentialOfferRequest -import io.iohk.atala.prism.models.Connection -import io.iohk.atala.prism.models.CreateIssueCredentialRecordRequest -import io.iohk.atala.prism.models.IssueCredentialRecord +import io.iohk.atala.prism.models.* +import models.AnoncredsSchema import models.CredentialEvent import net.serenitybdd.rest.SerenityRest import net.serenitybdd.screenplay.Actor +import net.serenitybdd.screenplay.rest.abilities.CallAnApi import org.apache.http.HttpStatus.SC_CREATED import org.apache.http.HttpStatus.SC_OK +import java.util.* class IssueCredentialsSteps { @@ -38,6 +38,7 @@ class IssueCredentialsSteps { issuingDID = did, connectionId = issuer.recall("connection-with-${holder.name}").connectionId, validityPeriod = 3600.0, + credentialFormat = "JWT", automaticIssuance = false ) @@ -58,15 +59,105 @@ class IssueCredentialsSteps { holder.remember("thid", credentialRecord.thid) } - @When("{actor} receives the credential offer and accepts") - fun bobRequestsTheCredential(holder: Actor) { + @When("{actor} creates anoncred schema") + fun acmeCreatesAnoncredSchema(issuer: Actor) { + issuer.attemptsTo( + Post.to("/schema-registry/schemas") + .with { + it.body( + CredentialSchemaInput( + author = issuer.recall("shortFormDid"), + name = UUID.randomUUID().toString(), + description = "Simple student credentials schema", + type = "AnoncredSchemaV1", + schema = AnoncredsSchema( + name = "StudentCredential", + version = "1.0", + issuerId = issuer.recall("shortFormDid"), + attrNames = listOf("name", "age") + ), + tags = listOf("school", "students"), + version = "1.0.0" + ) + ) + } + ) + issuer.attemptsTo( + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED) + ) + val schema = SerenityRest.lastResponse().get() + issuer.remember("anoncredsSchema", schema) + } + + @When("{actor} creates anoncred credential definition") + fun acmeCreatesAnoncredCredentialDefinition(issuer: Actor) { + val schemaRegistryUrl = issuer.usingAbilityTo(CallAnApi::class.java).resolve("/schema-registry/schemas") + .replace("localhost", "host.docker.internal") + issuer.attemptsTo( + Post.to("/credential-definition-registry/definitions") + .with { + it.body( + CredentialDefinitionInput( + name = "StudentCredential", + version = "1.0.0", + schemaId = "$schemaRegistryUrl/${issuer.recall("anoncredsSchema").guid}", + description = "Simple student credentials definition", + author = issuer.recall("shortFormDid"), + signatureType = "CL", + tag = "student", + supportRevocation = false, + ) + ) + } + ) + issuer.attemptsTo( + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED) + ) + val credentialDefinition = SerenityRest.lastResponse().get() + issuer.remember("anoncredsCredentialDefinition", credentialDefinition) + } + + @When("{actor} offers anoncred to {actor}") + fun acmeOffersAnoncredToBob(issuer: Actor, holder: Actor) { + val credentialOfferRequest = CreateIssueCredentialRecordRequest( + credentialDefinitionId = issuer.recall("anoncredsCredentialDefinition").guid, + claims = linkedMapOf( + "name" to "Bob", + "age" to "21" + ), + issuingDID = issuer.recall("shortFormDid"), + connectionId = issuer.recall("connection-with-${holder.name}").connectionId, + validityPeriod = 3600.0, + credentialFormat = "AnonCreds", + automaticIssuance = false + ) + + issuer.attemptsTo( + Post.to("/issue-credentials/credential-offers") + .with { + it.body(credentialOfferRequest) + } + ) + + val credentialRecord = SerenityRest.lastResponse().get() + + issuer.attemptsTo( + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED) + ) + + issuer.remember("thid", credentialRecord.thid) + holder.remember("thid", credentialRecord.thid) + } + + @When("{actor} receives the credential offer") + fun holderReceivesCredentialOffer(holder: Actor) { wait( { credentialEvent = ListenToEvents.`as`(holder).credentialEvents.lastOrNull { it.data.thid == holder.recall("thid") } credentialEvent != null && - credentialEvent!!.data.protocolState == IssueCredentialRecord.ProtocolState.OFFER_RECEIVED + credentialEvent!!.data.protocolState == IssueCredentialRecord.ProtocolState.OFFER_RECEIVED }, "Holder was unable to receive the credential offer from Issuer! " + "Protocol state did not achieve ${IssueCredentialRecord.ProtocolState.OFFER_RECEIVED} state." @@ -74,9 +165,12 @@ class IssueCredentialsSteps { val recordId = ListenToEvents.`as`(holder).credentialEvents.last().data.recordId holder.remember("recordId", recordId) + } + @When("{actor} accepts credential offer for JWT") + fun holderAcceptsCredentialOfferForJwt(holder: Actor) { holder.attemptsTo( - Post.to("/issue-credentials/records/$recordId/accept-offer") + Post.to("/issue-credentials/records/${holder.recall("recordId")}/accept-offer") .with { it.body( AcceptCredentialOfferRequest(holder.recall("longFormDid")) @@ -88,6 +182,21 @@ class IssueCredentialsSteps { ) } + @When("{actor} accepts credential offer for anoncred") + fun holderAcceptsCredentialOfferForAnoncred(holder: Actor) { + holder.attemptsTo( + Post.to("/issue-credentials/records/${holder.recall("recordId")}/accept-offer") + .with { + it.body( + "{}" + ) + } + ) + holder.attemptsTo( + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK) + ) + } + @When("{actor} issues the credential") fun acmeIssuesTheCredential(issuer: Actor) { wait( diff --git a/tests/integration-tests/src/test/kotlin/features/schemas/CredentialSchemasSteps.kt b/tests/integration-tests/src/test/kotlin/features/schemas/CredentialSchemasSteps.kt index c34af47855..b1aad0e237 100644 --- a/tests/integration-tests/src/test/kotlin/features/schemas/CredentialSchemasSteps.kt +++ b/tests/integration-tests/src/test/kotlin/features/schemas/CredentialSchemasSteps.kt @@ -8,7 +8,7 @@ import io.cucumber.java.en.When import io.iohk.atala.automation.extensions.get import io.iohk.atala.automation.serenity.ensure.Ensure import io.iohk.atala.prism.models.CredentialSchemaResponse -import models.Schema +import models.JsonSchema import net.serenitybdd.rest.SerenityRest import net.serenitybdd.screenplay.Actor import org.apache.http.HttpStatus.SC_CREATED @@ -30,7 +30,7 @@ class CredentialSchemasSteps { @Then("{actor} sees new credential schema is available") fun newCredentialSchemaIsAvailable(actor: Actor) { val credentialSchema = SerenityRest.lastResponse().get() - val schema = SerenityRest.lastResponse().get("schema") + val jsonSchema = SerenityRest.lastResponse().get("schema") actor.attemptsTo( Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), @@ -44,7 +44,7 @@ class CredentialSchemasSteps { Ensure.that(credentialSchema.version).contains(TestConstants.STUDENT_SCHEMA.version), Ensure.that(credentialSchema.type).isEqualTo(TestConstants.CREDENTIAL_SCHEMA_TYPE), Ensure.that(credentialSchema.tags!!).containsExactlyInAnyOrderElementsFrom(TestConstants.STUDENT_SCHEMA.tags!!), - Ensure.that(schema.toString()).isEqualTo(TestConstants.jsonSchema.toString()) + Ensure.that(jsonSchema.toString()).isEqualTo(TestConstants.jsonSchema.toString()) ) } diff --git a/tests/integration-tests/src/test/resources/features/credentials/issue_credentials.feature b/tests/integration-tests/src/test/resources/features/credentials/issue_credentials.feature index 94a1609753..8f85e9d332 100644 --- a/tests/integration-tests/src/test/resources/features/credentials/issue_credentials.feature +++ b/tests/integration-tests/src/test/resources/features/credentials/issue_credentials.feature @@ -1,21 +1,36 @@ @RFC0453 @AIP20 Feature: Issue Credentials Protocol -Scenario: Issuing credential with published PRISM DID to unpublished PRISM DID +Scenario: Issuing credential with published PRISM DID Given Acme and Bob have an existing connection When Acme creates unpublished DID And He publishes DID to ledger And Bob creates unpublished DID And Acme offers a credential to Bob with "short" form DID - And Bob receives the credential offer and accepts + And Bob receives the credential offer + And Bob accepts credential offer for JWT And Acme issues the credential Then Bob receives the issued credential -Scenario: Issuing credential with unpublished PRISM DID to unpublished PRISM DID +Scenario: Issuing credential with unpublished PRISM DID Given Acme and Bob have an existing connection When Acme creates unpublished DID And Bob creates unpublished DID And Acme offers a credential to Bob with "long" form DID - And Bob receives the credential offer and accepts + And Bob receives the credential offer + And Bob accepts credential offer for JWT + And Acme issues the credential + Then Bob receives the issued credential + +Scenario: Issuing anoncred with published PRISM DID + Given Acme and Bob have an existing connection + When Acme creates unpublished DID + And He publishes DID to ledger + And Bob creates unpublished DID + And Acme creates anoncred schema + And Acme creates anoncred credential definition + And Acme offers anoncred to Bob + And Bob receives the credential offer + And Bob accepts credential offer for anoncred And Acme issues the credential Then Bob receives the issued credential