Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add anoncreds issuance happy path bdd scenario #767

Merged
merged 1 commit into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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 = ""
)
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
Loading