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 @@
-
+
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