Skip to content

Commit

Permalink
test: add anoncreds issuance happy path bdd scenario (#767)
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Baliasnikov <[email protected]>
  • Loading branch information
Anton Baliasnikov authored Oct 25, 2023
1 parent 43083a3 commit 5558a9e
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 27 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<br>
<a href='https://coveralls.io/github/input-output-hk/atala-prism-building-blocks?branch=main'><img src='https://coveralls.io/repos/github/input-output-hk/atala-prism-building-blocks/badge.svg?branch=main&amp;t=91BUzX&kill_cache=1' alt='Coverage Status' /></a>
<a href="https://github.com/input-output-hk/atala-prism-building-blocks/actions/workflows/prism-unit-tests.yml"> <img src="https://github.com/input-output-hk/atala-prism-building-blocks/actions/workflows/prism-unit-tests.yml/badge.svg" alt="Unit tests" /> </a>
<a href="https://github.com/input-output-hk/atala-prism-building-blocks/actions/workflows/e2e-tests.yml"> <img src="https://github.com/input-output-hk/atala-prism-building-blocks/actions/workflows/e2e-tests.yml/badge.svg" alt="End-to-end tests" /> </a>
<a href="https://github.com/input-output-hk/atala-prism-building-blocks/actions/workflows/integration-tests.yml"> <img src="https://github.com/input-output-hk/atala-prism-building-blocks/actions/workflows/integration-tests.yml/badge.svg" alt="End-to-end tests" /> </a>
<a href="https://github.com/input-output-hk/atala-prism-building-blocks/actions/workflows/performance-tests.yml"> <img src="https://github.com/input-output-hk/atala-prism-building-blocks/actions/workflows/performance-tests.yml/badge.svg" alt="Performance tests" /> </a>
</p>
<hr>
Expand Down
1 change: 1 addition & 0 deletions infrastructure/shared/docker-compose-demo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions tests/integration-tests/src/main/kotlin/models/AnoncredsSchema.kt
Original file line number Diff line number Diff line change
@@ -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<String>
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package models

import com.google.gson.annotations.SerializedName

data class Schema(
data class JsonSchema(
@SerializedName("\$id")
var id: String = "",

Expand All @@ -16,5 +16,5 @@ data class Schema(
var type: String = "",

@SerializedName("properties")
val properties: MutableMap<String, SchemaProperty> = mutableMapOf()
val properties: MutableMap<String, JsonSchemaProperty> = mutableMapOf()
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package models

import com.google.gson.annotations.SerializedName

data class SchemaProperty(
data class JsonSchemaProperty(
@SerializedName("type")
var type: String = ""
)
14 changes: 7 additions & 7 deletions tests/integration-tests/src/test/kotlin/common/TestConstants.kt
Original file line number Diff line number Diff line change
@@ -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.*

Expand All @@ -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")
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class CommonSteps {
)
val receivedCredential = SerenityRest.lastResponse().get<IssueCredentialRecordPage>().contents!!.findLast { credential ->
credential.protocolState == IssueCredentialRecord.ProtocolState.CREDENTIAL_RECEIVED
&& credential.credentialFormat == IssueCredentialRecord.CredentialFormat.JWT
}

if (receivedCredential != null) {
Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -38,6 +38,7 @@ class IssueCredentialsSteps {
issuingDID = did,
connectionId = issuer.recall<Connection>("connection-with-${holder.name}").connectionId,
validityPeriod = 3600.0,
credentialFormat = "JWT",
automaticIssuance = false
)

Expand All @@ -58,25 +59,118 @@ 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<CredentialSchemaResponse>()
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<CredentialSchemaResponse>("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<CredentialDefinitionResponse>()
issuer.remember("anoncredsCredentialDefinition", credentialDefinition)
}

@When("{actor} offers anoncred to {actor}")
fun acmeOffersAnoncredToBob(issuer: Actor, holder: Actor) {
val credentialOfferRequest = CreateIssueCredentialRecordRequest(
credentialDefinitionId = issuer.recall<CredentialDefinitionResponse>("anoncredsCredentialDefinition").guid,
claims = linkedMapOf(
"name" to "Bob",
"age" to "21"
),
issuingDID = issuer.recall("shortFormDid"),
connectionId = issuer.recall<Connection>("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<IssueCredentialRecord>()

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<String>("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."
)

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<String>("recordId")}/accept-offer")
.with {
it.body(
AcceptCredentialOfferRequest(holder.recall("longFormDid"))
Expand All @@ -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<String>("recordId")}/accept-offer")
.with {
it.body(
"{}"
)
}
)
holder.attemptsTo(
Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK)
)
}

@When("{actor} issues the credential")
fun acmeIssuesTheCredential(issuer: Actor) {
wait(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,7 +30,7 @@ class CredentialSchemasSteps {
@Then("{actor} sees new credential schema is available")
fun newCredentialSchemaIsAvailable(actor: Actor) {
val credentialSchema = SerenityRest.lastResponse().get<CredentialSchemaResponse>()
val schema = SerenityRest.lastResponse().get<Schema>("schema")
val jsonSchema = SerenityRest.lastResponse().get<JsonSchema>("schema")

actor.attemptsTo(
Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED),
Expand All @@ -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())
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 5558a9e

Please sign in to comment.