From 21f5f4f294d95912b75d90355309873823466aba Mon Sep 17 00:00:00 2001 From: Allain Magyar Date: Thu, 23 May 2024 00:38:17 -0300 Subject: [PATCH 1/2] test: add jwt revocation test scenario (#951) Signed-off-by: Allain Magyar Signed-off-by: Hyperledger Bot Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Hyperledger Bot --- tests/integration-tests/README.md | 4 +- .../test/kotlin/abilities/ListenToEvents.kt | 6 +- .../src/test/kotlin/common/Utils.kt | 28 ---- .../src/test/kotlin/models/Events.kt | 65 +++++++- .../src/test/kotlin/models/JwtCredential.kt | 20 +++ .../test/kotlin/steps/common/CommonSteps.kt | 35 ++--- .../steps/connection/ConnectionSteps.kt | 47 +++--- .../credentials/IssueCredentialsSteps.kt | 146 +++++------------ .../credentials/RevokeCredentialSteps.kt | 78 ++++++++++ .../kotlin/steps/did/DeactivateDidSteps.kt | 22 ++- .../test/kotlin/steps/did/PublishDidSteps.kt | 72 +++------ .../test/kotlin/steps/did/UpdateDidSteps.kt | 147 ++++++++---------- .../proofs/AnoncredsPresentProofSteps.kt | 31 ++-- .../kotlin/steps/proofs/PresentProofSteps.kt | 86 +++++----- .../schemas/AnoncredCredentialSchemaSteps.kt | 85 ++++++++++ .../credentials/issue_published_did.feature | 5 +- .../features/did/deactivate_did.feature | 3 +- .../resources/features/did/update_did.feature | 2 +- .../features/proofs/present_proof.feature | 1 + .../proofs/present_proof_anoncred.feature | 6 +- .../revocation/revoke_jwt_credential.feature | 21 +++ 21 files changed, 504 insertions(+), 406 deletions(-) delete mode 100644 tests/integration-tests/src/test/kotlin/common/Utils.kt create mode 100644 tests/integration-tests/src/test/kotlin/models/JwtCredential.kt create mode 100644 tests/integration-tests/src/test/kotlin/steps/credentials/RevokeCredentialSteps.kt create mode 100644 tests/integration-tests/src/test/kotlin/steps/schemas/AnoncredCredentialSchemaSteps.kt create mode 100644 tests/integration-tests/src/test/resources/features/revocation/revoke_jwt_credential.feature diff --git a/tests/integration-tests/README.md b/tests/integration-tests/README.md index 2725a970bc..91d175e678 100644 --- a/tests/integration-tests/README.md +++ b/tests/integration-tests/README.md @@ -46,9 +46,9 @@ The project structure is represented below: │ ├── abilities -> contains the abilities of the actors │ ├── common -> contains the common classes (test constants and helper functions) │ ├── config -> contains the configuration classes (Hoplite) - │ ├── features -> contains the features implementation steps │ ├── interactions -> contains the interactions of the actors - │ └── runners -> contains the test runners to execute the tests + │ ├── models -> contains the models + │ ├── steps -> contains the features implementation steps └── resources -> contains the test resources ├── configs -> contains the test configuration files ├── containers -> contains the Docker Compose files to start the test environment diff --git a/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt b/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt index 2ea3b69724..43c92230ed 100644 --- a/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt +++ b/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt @@ -39,9 +39,7 @@ open class ListenToEvents( TestConstants.EVENT_TYPE_CONNECTION_UPDATED -> connectionEvents.add(gson.fromJson(eventString, ConnectionEvent::class.java)) TestConstants.EVENT_TYPE_ISSUE_CREDENTIAL_RECORD_UPDATED -> credentialEvents.add(gson.fromJson(eventString, CredentialEvent::class.java)) TestConstants.EVENT_TYPE_PRESENTATION_UPDATED -> presentationEvents.add(gson.fromJson(eventString, PresentationEvent::class.java)) - TestConstants.EVENT_TYPE_DID_STATUS_UPDATED -> { - didEvents.add(gson.fromJson(eventString, DidEvent::class.java)) - } + TestConstants.EVENT_TYPE_DID_STATUS_UPDATED -> didEvents.add(gson.fromJson(eventString, DidEvent::class.java)) else -> { throw IllegalArgumentException("ERROR: unknown event type ${event.type}") } @@ -56,7 +54,7 @@ open class ListenToEvents( return ListenToEvents(url, webhookPort) } - fun `as`(actor: Actor): ListenToEvents { + fun with(actor: Actor): ListenToEvents { return actor.abilityTo(ListenToEvents::class.java) } } diff --git a/tests/integration-tests/src/test/kotlin/common/Utils.kt b/tests/integration-tests/src/test/kotlin/common/Utils.kt deleted file mode 100644 index 627dab4357..0000000000 --- a/tests/integration-tests/src/test/kotlin/common/Utils.kt +++ /dev/null @@ -1,28 +0,0 @@ -package common - -import org.awaitility.Awaitility -import org.awaitility.core.ConditionTimeoutException -import org.awaitility.pollinterval.FixedPollInterval -import java.time.Duration - -object Utils { - fun wait( - blockToWait: () -> Boolean, - errorMessage: String, - poolInterval: FixedPollInterval = FixedPollInterval(Duration.ofMillis(500L)), - timeout: Duration = Duration.ofSeconds(120L), - ) { - try { - Awaitility.await().with().pollInterval(poolInterval) - .pollInSameThread() - .atMost(timeout) - .until { - blockToWait() - } - } catch (err: ConditionTimeoutException) { - throw ConditionTimeoutException( - errorMessage, - ) - } - } -} diff --git a/tests/integration-tests/src/test/kotlin/models/Events.kt b/tests/integration-tests/src/test/kotlin/models/Events.kt index dcf27c3609..f4d1d76475 100644 --- a/tests/integration-tests/src/test/kotlin/models/Events.kt +++ b/tests/integration-tests/src/test/kotlin/models/Events.kt @@ -2,10 +2,7 @@ package models import com.google.gson.JsonElement import com.google.gson.annotations.SerializedName -import org.hyperledger.identus.client.models.Connection -import org.hyperledger.identus.client.models.IssueCredentialRecord -import org.hyperledger.identus.client.models.ManagedDID -import org.hyperledger.identus.client.models.PresentationStatus +import org.hyperledger.identus.client.models.* data class Event( @SerializedName("type") var type: String, @@ -35,10 +32,68 @@ data class PresentationEvent( @SerializedName("type") var type: String, @SerializedName("id") var id: String, @SerializedName("ts") var ts: String, - @SerializedName("data") var data: PresentationStatus, + @SerializedName("data") var data: PresentationStatusAdapter, // FIXME: rollback to PresentationStatus when Status is fixed @SerializedName("walletId") var walletId: String, ) +data class PresentationStatusAdapter( // FIXME: delete this class when PresentationStatus.Status is fixed + @SerializedName("presentationId") val presentationId: String, + @SerializedName("thid") val thid: String, + @SerializedName("role") val role: PresentationStatus.Role, + @SerializedName("status") val status: Status, + @SerializedName("metaRetries") val metaRetries: Int, + @SerializedName("proofs") val proofs: List? = null, + @SerializedName("data") val `data`: List? = null, + @SerializedName("connectionId") val connectionId: String? = null, +) { + enum class Status(val value: String) { + @SerializedName(value = "RequestPending") + REQUEST_PENDING("RequestPending"), + + @SerializedName(value = "RequestSent") + REQUEST_SENT("RequestSent"), + + @SerializedName(value = "RequestReceived") + REQUEST_RECEIVED("RequestReceived"), + + @SerializedName(value = "RequestRejected") + REQUEST_REJECTED("RequestRejected"), + + @SerializedName(value = "PresentationPending") + PRESENTATION_PENDING("PresentationPending"), + + @SerializedName(value = "PresentationGenerated") + PRESENTATION_GENERATED("PresentationGenerated"), + + @SerializedName(value = "PresentationSent") + PRESENTATION_SENT("PresentationSent"), + + @SerializedName(value = "PresentationReceived") + PRESENTATION_RECEIVED("PresentationReceived"), + + @SerializedName(value = "PresentationVerified") + PRESENTATION_VERIFIED("PresentationVerified"), + + @SerializedName(value = "PresentationAccepted") + PRESENTATION_ACCEPTED("PresentationAccepted"), + + @SerializedName(value = "PresentationRejected") + PRESENTATION_REJECTED("PresentationRejected"), + + @SerializedName(value = "ProblemReportPending") + PROBLEM_REPORT_PENDING("ProblemReportPending"), + + @SerializedName(value = "ProblemReportSent") + PROBLEM_REPORT_SENT("ProblemReportSent"), + + @SerializedName(value = "ProblemReportReceived") + PROBLEM_REPORT_RECEIVED("ProblemReportReceived"), + + @SerializedName(value = "PresentationVerificationFailed") + PRESENTATION_VERIFICATION_FAILED("PresentationVerificationFailed"), + } +} + data class DidEvent( @SerializedName("type") var type: String, @SerializedName("id") var id: String, diff --git a/tests/integration-tests/src/test/kotlin/models/JwtCredential.kt b/tests/integration-tests/src/test/kotlin/models/JwtCredential.kt new file mode 100644 index 0000000000..741f3f4c5f --- /dev/null +++ b/tests/integration-tests/src/test/kotlin/models/JwtCredential.kt @@ -0,0 +1,20 @@ +package models + +import com.jayway.jsonpath.DocumentContext +import com.jayway.jsonpath.JsonPath +import java.util.Base64 + +class JwtCredential(base64: String) { + private val payload: DocumentContext + + init { + val jwt = String(Base64.getDecoder().decode(base64)) + val parts = jwt.split(".") + payload = JsonPath.parse(String(Base64.getUrlDecoder().decode(parts[1]))) + } + + fun statusListId(): String { + val listUrl = payload.read("$.vc.credentialStatus.statusListCredential") + return listUrl.split("/credential-status/")[1] + } +} diff --git a/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt b/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt index 1158745211..e9883293c9 100644 --- a/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt @@ -33,31 +33,18 @@ class CommonSteps { @Given("{actor} has an issued credential from {actor}") fun holderHasIssuedCredentialFromIssuer(holder: Actor, issuer: Actor) { - holder.attemptsTo( - Get.resource("/issue-credentials/records"), - ) - holder.attemptsTo( - Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_OK), - ) - val receivedCredential = SerenityRest.lastResponse().get().contents!!.findLast { credential -> - credential.protocolState == IssueCredentialRecord.ProtocolState.CREDENTIAL_RECEIVED && - credential.credentialFormat == IssueCredentialRecord.CredentialFormat.JWT - } + actorsHaveExistingConnection(issuer, holder) - if (receivedCredential != null) { - holder.remember("issuedCredential", receivedCredential) - } else { - val publishDidSteps = PublishDidSteps() - val issueSteps = IssueCredentialsSteps() - actorsHaveExistingConnection(issuer, holder) - publishDidSteps.agentHasAnUnpublishedDID(holder) - publishDidSteps.agentHasAPublishedDID(issuer) - issueSteps.issuerOffersACredential(issuer, holder, "short") - issueSteps.holderReceivesCredentialOffer(holder) - issueSteps.holderAcceptsCredentialOfferForJwt(holder) - issueSteps.acmeIssuesTheCredential(issuer) - issueSteps.bobHasTheCredentialIssued(holder) - } + val publishDidSteps = PublishDidSteps() + publishDidSteps.createsUnpublishedDid(holder) + publishDidSteps.agentHasAPublishedDID(issuer) + + val issueSteps = IssueCredentialsSteps() + issueSteps.issuerOffersACredential(issuer, holder, "short") + issueSteps.holderReceivesCredentialOffer(holder) + issueSteps.holderAcceptsCredentialOfferForJwt(holder) + issueSteps.acmeIssuesTheCredential(issuer) + issueSteps.bobHasTheCredentialIssued(holder) } @Given("{actor} and {actor} have an existing connection") diff --git a/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt b/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt index 38922a021c..99d0ed603e 100644 --- a/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt @@ -1,21 +1,19 @@ package steps.connection import abilities.ListenToEvents -import common.Utils.wait import interactions.Get import interactions.Post 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.automation.utils.Wait import net.serenitybdd.rest.SerenityRest import net.serenitybdd.screenplay.Actor import org.apache.http.HttpStatus.SC_CREATED import org.apache.http.HttpStatus.SC_OK import org.assertj.core.api.Assertions.assertThat -import org.hyperledger.identus.client.models.AcceptConnectionInvitationRequest -import org.hyperledger.identus.client.models.Connection -import org.hyperledger.identus.client.models.CreateConnectionRequest +import org.hyperledger.identus.client.models.* class ConnectionSteps { @@ -66,7 +64,8 @@ class ConnectionSteps { Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), Ensure.that(inviteeConnection.invitation.from).isEqualTo(inviterConnection.invitation.from), Ensure.that(inviteeConnection.invitation.id).isEqualTo(inviterConnection.invitation.id), - Ensure.that(inviteeConnection.invitation.invitationUrl).isEqualTo(inviterConnection.invitation.invitationUrl), + Ensure.that(inviteeConnection.invitation.invitationUrl) + .isEqualTo(inviterConnection.invitation.invitationUrl), Ensure.that(inviteeConnection.invitation.type).isEqualTo(inviterConnection.invitation.type), Ensure.that(inviteeConnection.state).isEqualTo(Connection.State.CONNECTION_REQUEST_PENDING), Ensure.that(inviteeConnection.role).isEqualTo(Connection.Role.INVITEE), @@ -77,31 +76,27 @@ class ConnectionSteps { @When("{actor} receives the connection request and sends back the response") fun inviterReceivesTheConnectionRequest(inviter: Actor) { - wait( - { - val lastEvent = ListenToEvents.`as`(inviter).connectionEvents.lastOrNull { - it.data.thid == inviter.recall("connection").thid - } - lastEvent != null && - lastEvent.data.state == Connection.State.CONNECTION_RESPONSE_SENT - }, - "Inviter connection didn't reach ${Connection.State.CONNECTION_RESPONSE_SENT} state", - ) + Wait.until( + errorMessage = "Inviter connection didn't reach ${Connection.State.CONNECTION_RESPONSE_SENT} state", + ) { + val lastEvent = ListenToEvents.with(inviter).connectionEvents.lastOrNull { + it.data.thid == inviter.recall("connection").thid + } + lastEvent != null && lastEvent.data.state == Connection.State.CONNECTION_RESPONSE_SENT + } } @When("{actor} receives the connection response") fun inviteeReceivesTheConnectionResponse(invitee: Actor) { - // Bob (Holder) receives final connection response - wait( - { - val lastEvent = ListenToEvents.`as`(invitee).connectionEvents.lastOrNull { - it.data.thid == invitee.recall("connection").thid - } - lastEvent != null && - lastEvent.data.state == Connection.State.CONNECTION_RESPONSE_RECEIVED - }, - "Invitee connection didn't reach ${Connection.State.CONNECTION_RESPONSE_RECEIVED} state.", - ) + Wait.until( + errorMessage = "Invitee connection didn't reach ${Connection.State.CONNECTION_RESPONSE_RECEIVED} state.", + ) { + val lastEvent = ListenToEvents.with(invitee).connectionEvents.lastOrNull { + it.data.thid == invitee.recall("connection").thid + } + lastEvent != null && + lastEvent.data.state == Connection.State.CONNECTION_RESPONSE_RECEIVED + } } @Then("{actor} and {actor} have a connection") diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/IssueCredentialsSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/IssueCredentialsSteps.kt index 578ce9419d..4c75524e0d 100644 --- a/tests/integration-tests/src/test/kotlin/steps/credentials/IssueCredentialsSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/credentials/IssueCredentialsSteps.kt @@ -2,20 +2,17 @@ package steps.credentials import abilities.ListenToEvents import common.CredentialSchema -import common.Utils.wait import interactions.Post 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 models.AnoncredsSchema +import io.iohk.atala.automation.utils.Wait 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.* import org.hyperledger.identus.client.models.* -import java.util.UUID class IssueCredentialsSteps { @@ -108,65 +105,7 @@ class IssueCredentialsSteps { "name" to "Name", "surname" to "Surname", ) - sendCredentialOffer(issuer, holder, "short", schemaGuid, claims) - } - - @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", "sex"), - ), - 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}/schema", - 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) + sendCredentialOffer(issuer, holder, format, schemaGuid, claims) } @When("{actor} offers anoncred to {actor}") @@ -204,19 +143,18 @@ class IssueCredentialsSteps { @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 - }, - "Holder was unable to receive the credential offer from Issuer! " + + Wait.until( + errorMessage = "Holder was unable to receive the credential offer from Issuer! " + "Protocol state did not achieve ${IssueCredentialRecord.ProtocolState.OFFER_RECEIVED} state.", - ) + ) { + credentialEvent = ListenToEvents.with(holder).credentialEvents.lastOrNull { + it.data.thid == holder.recall("thid") + } + credentialEvent != null && + credentialEvent!!.data.protocolState == IssueCredentialRecord.ProtocolState.OFFER_RECEIVED + } - val recordId = ListenToEvents.`as`(holder).credentialEvents.last().data.recordId + val recordId = ListenToEvents.with(holder).credentialEvents.last().data.recordId holder.remember("recordId", recordId) } @@ -252,16 +190,15 @@ class IssueCredentialsSteps { @When("{actor} issues the credential") fun acmeIssuesTheCredential(issuer: Actor) { - wait( - { - credentialEvent = ListenToEvents.`as`(issuer).credentialEvents.lastOrNull { - it.data.thid == issuer.recall("thid") - } - credentialEvent != null && - credentialEvent!!.data.protocolState == IssueCredentialRecord.ProtocolState.REQUEST_RECEIVED - }, - "Issuer was unable to receive the credential request from Holder! Protocol state did not achieve RequestReceived state.", - ) + Wait.until( + errorMessage = "Issuer was unable to receive the credential request from Holder! Protocol state did not achieve RequestReceived state.", + ) { + credentialEvent = ListenToEvents.with(issuer).credentialEvents.lastOrNull { + it.data.thid == issuer.recall("thid") + } + credentialEvent != null && + credentialEvent!!.data.protocolState == IssueCredentialRecord.ProtocolState.REQUEST_RECEIVED + } val recordId = credentialEvent!!.data.recordId issuer.attemptsTo( Post.to("/issue-credentials/records/$recordId/issue-credential"), @@ -270,33 +207,32 @@ class IssueCredentialsSteps { Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), ) - wait( - { - credentialEvent = ListenToEvents.`as`(issuer).credentialEvents.lastOrNull { - it.data.thid == issuer.recall("thid") - } - credentialEvent != null && - credentialEvent!!.data.protocolState == IssueCredentialRecord.ProtocolState.CREDENTIAL_SENT - }, - "Issuer was unable to issue the credential! " + + Wait.until( + errorMessage = "Issuer was unable to issue the credential! " + "Protocol state did not achieve ${IssueCredentialRecord.ProtocolState.CREDENTIAL_SENT} state.", - ) + ) { + credentialEvent = ListenToEvents.with(issuer).credentialEvents.lastOrNull { + it.data.thid == issuer.recall("thid") + } + issuer.remember("issuedCredential", credentialEvent!!.data) + credentialEvent != null && + credentialEvent!!.data.protocolState == IssueCredentialRecord.ProtocolState.CREDENTIAL_SENT + } } @Then("{actor} receives the issued credential") fun bobHasTheCredentialIssued(holder: Actor) { - wait( - { - credentialEvent = ListenToEvents.`as`(holder).credentialEvents.lastOrNull { - it.data.thid == holder.recall("thid") - } - credentialEvent != null && - credentialEvent!!.data.protocolState == IssueCredentialRecord.ProtocolState.CREDENTIAL_RECEIVED - }, - "Holder was unable to receive the credential from Issuer! " + + Wait.until( + errorMessage = "Holder was unable to receive the credential from Issuer! " + "Protocol state did not achieve ${IssueCredentialRecord.ProtocolState.CREDENTIAL_RECEIVED} state.", - ) - holder.remember("issuedCredential", ListenToEvents.`as`(holder).credentialEvents.last().data) + ) { + credentialEvent = ListenToEvents.with(holder).credentialEvents.lastOrNull { + it.data.thid == holder.recall("thid") + } + credentialEvent != null && + credentialEvent!!.data.protocolState == IssueCredentialRecord.ProtocolState.CREDENTIAL_RECEIVED + } + holder.remember("issuedCredential", ListenToEvents.with(holder).credentialEvents.last().data) } @Then("{actor} should see that credential issuance has failed") diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/RevokeCredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/RevokeCredentialSteps.kt new file mode 100644 index 0000000000..4033fd924f --- /dev/null +++ b/tests/integration-tests/src/test/kotlin/steps/credentials/RevokeCredentialSteps.kt @@ -0,0 +1,78 @@ +package steps.credentials + +import interactions.* +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.automation.utils.Wait +import models.JwtCredential +import net.serenitybdd.rest.SerenityRest +import net.serenitybdd.screenplay.Actor +import org.apache.http.HttpStatus +import org.hyperledger.identus.client.models.IssueCredentialRecord +import kotlin.time.Duration.Companion.seconds + +class RevokeCredentialSteps { + @When("{actor} revokes the credential issued to {actor}") + fun issuerRevokesCredentialsIssuedToHolder(issuer: Actor, holder: Actor) { + val issuedCredential = issuer.recall("issuedCredential") + val jwtCred = JwtCredential(issuedCredential.credential!!) + val statusListId = jwtCred.statusListId() + issuer.remember("statusListId", statusListId) + + issuer.attemptsTo( + Get.resource("/credential-status/$statusListId"), + ) + val encodedList = SerenityRest.lastResponse().get("credentialSubject.encodedList") + issuer.remember("encodedStatusList", encodedList) + + issuer.attemptsTo( + Patch.to("/credential-status/revoke-credential/${issuedCredential.recordId}"), + Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_OK), + ) + } + + @When("{actor} tries to revoke credential from {actor}") + fun holderTriesToRevokeCredentialFromIssuer(holder: Actor, issuer: Actor) { + val issuedCredential = issuer.recall("issuedCredential") + val receivedCredential = holder.recall("issuedCredential") + holder.attemptsTo( + Patch.to("/credential-status/revoke-credential/${issuedCredential.recordId}"), + Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_NOT_FOUND), + ) + holder.attemptsTo( + Patch.to("/credential-status/revoke-credential/${receivedCredential.recordId}"), + Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_NOT_FOUND), + ) + } + + @Then("{actor} should see the credential was revoked") + fun credentialShouldBeRevoked(issuer: Actor) { + Wait.until( + timeout = 60.seconds, + errorMessage = "Encoded Status List didn't change after revoking.", + ) { + val statusListId: String = issuer.recall("statusListId") + val encodedStatusList: String = issuer.recall("encodedStatusList") + issuer.attemptsTo( + Get.resource("/credential-status/$statusListId"), + ) + val actualEncodedList: String = SerenityRest.lastResponse().jsonPath().get("credentialSubject.encodedList") + println("actual encoded $actualEncodedList | before encoded $encodedStatusList") + actualEncodedList != encodedStatusList + } + } + + @Then("{actor} should see the credential is not revoked") + fun issuerShouldSeeTheCredentialIsNotRevoked(issuer: Actor) { + val issuedCredential = issuer.recall("issuedCredential") + val jwtCred = JwtCredential(issuedCredential.credential!!) + val statusListId = jwtCred.statusListId() + issuer.remember("statusListId", statusListId) + + issuer.attemptsTo( + Get.resource("/credential-status/$statusListId"), + ) + } +} diff --git a/tests/integration-tests/src/test/kotlin/steps/did/DeactivateDidSteps.kt b/tests/integration-tests/src/test/kotlin/steps/did/DeactivateDidSteps.kt index b95a9f3233..52a46869f5 100644 --- a/tests/integration-tests/src/test/kotlin/steps/did/DeactivateDidSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/did/DeactivateDidSteps.kt @@ -1,13 +1,12 @@ package steps.did -import common.TestConstants -import common.Utils.wait import interactions.Get import interactions.Post 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.automation.utils.Wait import net.serenitybdd.rest.SerenityRest import net.serenitybdd.screenplay.Actor import org.apache.http.HttpStatus @@ -29,19 +28,18 @@ class DeactivateDidSteps { Ensure.that(didOperationResponse.scheduledOperation.didRef).isNotEmpty(), Ensure.that(didOperationResponse.scheduledOperation.id).isNotEmpty(), ) + actor.forget("hasPublishedDid") + val deactivatedDid = actor.forget("shortFormDid") + actor.forget("longFormDid") + actor.remember("deactivatedDid", deactivatedDid) } @Then("{actor} sees that PRISM DID is successfully deactivated") fun actorSeesThatPrismDidIsSuccessfullyDeactivated(actor: Actor) { - wait( - { - actor.attemptsTo( - Get.resource("/dids/${actor.recall("shortFormDid")}"), - ) - SerenityRest.lastResponse().get().didDocumentMetadata.deactivated!! - }, - "ERROR: DID deactivate operation did not succeed on the ledger!", - timeout = TestConstants.DID_UPDATE_PUBLISH_MAX_WAIT_5_MIN, - ) + val deactivatedDid = actor.recall("deactivatedDid") + Wait.until(errorMessage = "ERROR: DID deactivate operation did not succeed on the ledger!") { + actor.attemptsTo(Get.resource("/dids/$deactivatedDid")) + SerenityRest.lastResponse().get().didDocumentMetadata.deactivated!! + } } } diff --git a/tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt b/tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt index 0793e8b2f0..0b766d65b4 100644 --- a/tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt @@ -2,49 +2,21 @@ package steps.did import abilities.ListenToEvents import common.TestConstants -import common.Utils.wait import interactions.Get import interactions.Post -import io.cucumber.java.en.Given -import io.cucumber.java.en.Then -import io.cucumber.java.en.When +import io.cucumber.java.en.* import io.iohk.atala.automation.extensions.get import io.iohk.atala.automation.serenity.ensure.Ensure +import io.iohk.atala.automation.utils.Wait import net.serenitybdd.rest.SerenityRest import net.serenitybdd.screenplay.Actor import org.apache.http.HttpStatus import org.apache.http.HttpStatus.SC_CREATED import org.apache.http.HttpStatus.SC_OK import org.hyperledger.identus.client.models.* +import kotlin.time.Duration.Companion.seconds class PublishDidSteps { - - @Given("{actor} have published PRISM DID") - fun actorHavePublishedPrismDid(actor: Actor) { - actor.attemptsTo( - Get.resource("/did-registrar/dids"), - ) - actor.attemptsTo( - Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), - ) - val publishedDids = SerenityRest.lastResponse().get().contents!!.filter { - // TODO: fix openapi spec to have statuses as enum - it.status == "PUBLISHED" - } - val did = publishedDids.firstOrNull { - actor.attemptsTo( - Get.resource("/dids/${it.did}"), - ) - !SerenityRest.lastResponse().get().didDocumentMetadata.deactivated!! - } - if (did == null) { - createsUnpublishedDid(actor) - hePublishesDidToLedger(actor) - } else { - actor.remember("shortFormDid", did.did) - } - } - @Given("{actor} creates unpublished DID") fun createsUnpublishedDid(actor: Actor) { val createDidRequest = CreateManagedDidRequest( @@ -67,27 +39,21 @@ class PublishDidSteps { .with { it.body(createDidRequest) }, - ) - - val managedDid = SerenityRest.lastResponse().get() - actor.attemptsTo( Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), - Ensure.that(managedDid.longFormDid!!).isNotEmpty(), ) - actor.remember("longFormDid", managedDid.longFormDid) + val managedDid = SerenityRest.lastResponse().get() actor.attemptsTo( + Ensure.that(managedDid.longFormDid!!).isNotEmpty(), Get.resource("/did-registrar/dids/${managedDid.longFormDid}"), - ) - actor.attemptsTo( Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), ) + val did = SerenityRest.lastResponse().get() - actor.remember( - "shortFormDid", - did.did, - ) + + actor.remember("longFormDid", managedDid.longFormDid) + actor.remember("shortFormDid", did.did) actor.forget("hasPublishedDid") } @@ -130,17 +96,15 @@ class PublishDidSteps { Ensure.that(didOperationResponse.scheduledOperation.id).isNotEmpty(), ) - wait( - { - val didEvent = - ListenToEvents.`as`(actor).didEvents.lastOrNull { - it.data.did == actor.recall("shortFormDid") - } - didEvent != null && didEvent.data.status == "PUBLISHED" - }, - "ERROR: DID was not published to ledger!", - timeout = TestConstants.DID_UPDATE_PUBLISH_MAX_WAIT_5_MIN, - ) + Wait.until( + timeout = 30.seconds, + errorMessage = "ERROR: DID was not published to ledger!", + ) { + val didEvent = ListenToEvents.with(actor).didEvents.lastOrNull { + it.data.did == actor.recall("shortFormDid") + } + didEvent != null && didEvent.data.status == "PUBLISHED" + } actor.attemptsTo( Get.resource("/dids/${actor.recall("shortFormDid")}"), ) diff --git a/tests/integration-tests/src/test/kotlin/steps/did/UpdateDidSteps.kt b/tests/integration-tests/src/test/kotlin/steps/did/UpdateDidSteps.kt index 9ff4b5e207..ea2c0ec435 100644 --- a/tests/integration-tests/src/test/kotlin/steps/did/UpdateDidSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/did/UpdateDidSteps.kt @@ -1,24 +1,17 @@ package steps.did import common.TestConstants -import common.Utils.wait import interactions.Get import interactions.Post 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.automation.utils.Wait import net.serenitybdd.rest.SerenityRest import net.serenitybdd.screenplay.Actor import org.apache.http.HttpStatus -import org.hyperledger.identus.client.models.ActionType -import org.hyperledger.identus.client.models.DIDOperationResponse -import org.hyperledger.identus.client.models.DIDResolutionResult -import org.hyperledger.identus.client.models.Json -import org.hyperledger.identus.client.models.RemoveEntryById -import org.hyperledger.identus.client.models.UpdateManagedDIDRequest -import org.hyperledger.identus.client.models.UpdateManagedDIDRequestAction -import org.hyperledger.identus.client.models.UpdateManagedDIDServiceAction +import org.hyperledger.identus.client.models.* class UpdateDidSteps { @@ -92,94 +85,84 @@ class UpdateDidSteps { @Then("{actor} sees PRISM DID was successfully updated with new keys") fun actorSeesDidSuccessfullyUpdatedWithNewKeys(actor: Actor) { - wait( - { - actor.attemptsTo( - Get.resource("/dids/${actor.recall("shortFormDid")}"), - ) - val authUris = SerenityRest.lastResponse().get().didDocument!!.authentication!! - val verificationMethods = SerenityRest.lastResponse() - .get().didDocument!!.verificationMethod!!.map { it.id } - authUris.any { - it == "${actor.recall("shortFormDid")}#${TestConstants.PRISM_DID_UPDATE_NEW_AUTH_KEY.id}" - } && verificationMethods.any { - it == "${actor.recall("shortFormDid")}#${TestConstants.PRISM_DID_AUTH_KEY.id}" - } - }, - "ERROR: DID UPDATE operation did not succeed on the ledger!", - timeout = TestConstants.DID_UPDATE_PUBLISH_MAX_WAIT_5_MIN, - ) + Wait.until( + errorMessage = "ERROR: DID UPDATE operation did not succeed on the ledger!", + ) { + actor.attemptsTo( + Get.resource("/dids/${actor.recall("shortFormDid")}"), + ) + val authUris = SerenityRest.lastResponse().get().didDocument!!.authentication!! + val verificationMethods = SerenityRest.lastResponse() + .get().didDocument!!.verificationMethod!!.map { it.id } + authUris.any { + it == "${actor.recall("shortFormDid")}#${TestConstants.PRISM_DID_UPDATE_NEW_AUTH_KEY.id}" + } && verificationMethods.any { + it == "${actor.recall("shortFormDid")}#${TestConstants.PRISM_DID_AUTH_KEY.id}" + } + } } @Then("{actor} sees PRISM DID was successfully updated and keys removed") fun actorSeesDidSuccessfullyUpdatedAndKeysRemoved(actor: Actor) { - wait( - { - actor.attemptsTo( - Get.resource("/dids/${actor.recall("shortFormDid")}"), - ) - val authUris = SerenityRest.lastResponse().get().didDocument!!.authentication!! - val verificationMethods = SerenityRest.lastResponse() - .get().didDocument!!.verificationMethod!!.map { it.id } - authUris.none { - it == "${actor.recall("shortFormDid")}#${TestConstants.PRISM_DID_AUTH_KEY.id}" - } && verificationMethods.none { - it == "${actor.recall("shortFormDid")}#${TestConstants.PRISM_DID_AUTH_KEY.id}" - } - }, - "ERROR: DID UPDATE operation did not succeed on the ledger!", - timeout = TestConstants.DID_UPDATE_PUBLISH_MAX_WAIT_5_MIN, - ) + Wait.until( + errorMessage = "ERROR: DID UPDATE operation did not succeed on the ledger!", + ) { + actor.attemptsTo( + Get.resource("/dids/${actor.recall("shortFormDid")}"), + ) + val authUris = SerenityRest.lastResponse().get().didDocument!!.authentication!! + val verificationMethods = SerenityRest.lastResponse() + .get().didDocument!!.verificationMethod!!.map { it.id } + authUris.none { + it == "${actor.recall("shortFormDid")}#${TestConstants.PRISM_DID_AUTH_KEY.id}" + } && verificationMethods.none { + it == "${actor.recall("shortFormDid")}#${TestConstants.PRISM_DID_AUTH_KEY.id}" + } + } } @Then("{actor} sees PRISM DID was successfully updated with new services") fun actorSeesDidSuccessfullyUpdatedWithNewServices(actor: Actor) { - wait( - { - actor.attemptsTo( - Get.resource("/dids/${actor.recall("shortFormDid")}"), - ) - val serviceIds = - SerenityRest.lastResponse().get().didDocument!!.service!!.map { it.id } - serviceIds.any { - it == "${actor.recall("shortFormDid")}#${TestConstants.PRISM_DID_UPDATE_NEW_SERVICE.id}" - } - }, - "ERROR: DID UPDATE operation did not succeed on the ledger!", - timeout = TestConstants.DID_UPDATE_PUBLISH_MAX_WAIT_5_MIN, - ) + Wait.until( + errorMessage = "ERROR: DID UPDATE operation did not succeed on the ledger!", + ) { + actor.attemptsTo( + Get.resource("/dids/${actor.recall("shortFormDid")}"), + ) + val serviceIds = + SerenityRest.lastResponse().get().didDocument!!.service!!.map { it.id } + serviceIds.any { + it == "${actor.recall("shortFormDid")}#${TestConstants.PRISM_DID_UPDATE_NEW_SERVICE.id}" + } + } } @Then("{actor} sees PRISM DID was successfully updated by removing services") fun actorSeesDidSuccessfullyUpdatedByRemovingServices(actor: Actor) { - wait( - { - actor.attemptsTo( - Get.resource("/dids/${actor.recall("shortFormDid")}"), - ) - val serviceIds = - SerenityRest.lastResponse().get().didDocument!!.service!!.map { it.id } - serviceIds.none { - it == "${actor.recall("shortFormDid")}#${TestConstants.PRISM_DID_UPDATE_NEW_SERVICE.id}" - } - }, - "ERROR: DID UPDATE operation did not succeed on the ledger!", - timeout = TestConstants.DID_UPDATE_PUBLISH_MAX_WAIT_5_MIN, - ) + Wait.until( + errorMessage = "ERROR: DID UPDATE operation did not succeed on the ledger!", + ) { + actor.attemptsTo( + Get.resource("/dids/${actor.recall("shortFormDid")}"), + ) + val serviceIds = + SerenityRest.lastResponse().get().didDocument!!.service!!.map { it.id } + serviceIds.none { + it == "${actor.recall("shortFormDid")}#${TestConstants.PRISM_DID_UPDATE_NEW_SERVICE.id}" + } + } } @Then("{actor} sees PRISM DID was successfully updated by updating services") fun actorSeesDidSuccessfullyUpdatedByUpdatingServices(actor: Actor) { - wait( - { - actor.attemptsTo( - Get.resource("/dids/${actor.recall("shortFormDid")}"), - ) - val service = SerenityRest.lastResponse().get().didDocument!!.service!! - service.any { it.serviceEndpoint.value.contains(TestConstants.PRISM_DID_UPDATE_NEW_SERVICE_URL) } - }, - "ERROR: DID UPDATE operation did not succeed on the ledger!", - timeout = TestConstants.DID_UPDATE_PUBLISH_MAX_WAIT_5_MIN, - ) + Wait.until( + errorMessage = "ERROR: DID UPDATE operation did not succeed on the ledger!", + ) { + actor.attemptsTo( + Get.resource("/dids/${actor.recall("shortFormDid")}"), + ) + val service = SerenityRest.lastResponse().get().didDocument!!.service!! + service.any { it.serviceEndpoint.value.contains(TestConstants.PRISM_DID_UPDATE_NEW_SERVICE_URL) } + } } } diff --git a/tests/integration-tests/src/test/kotlin/steps/proofs/AnoncredsPresentProofSteps.kt b/tests/integration-tests/src/test/kotlin/steps/proofs/AnoncredsPresentProofSteps.kt index 566d2f1864..ace6fc890e 100644 --- a/tests/integration-tests/src/test/kotlin/steps/proofs/AnoncredsPresentProofSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/proofs/AnoncredsPresentProofSteps.kt @@ -1,13 +1,14 @@ package steps.proofs import abilities.ListenToEvents -import common.Utils.wait import interactions.Patch import interactions.Post 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.automation.utils.Wait import models.PresentationEvent +import models.PresentationStatusAdapter import net.serenitybdd.rest.SerenityRest import net.serenitybdd.screenplay.Actor import net.serenitybdd.screenplay.rest.abilities.CallAnApi @@ -67,7 +68,7 @@ class AnoncredsPresentProofSteps { }, ) faber.attemptsTo( - Ensure.thatTheLastResponse().apply { println(this.contentType()) }.statusCode().isEqualTo(SC_CREATED), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED), ) val presentationStatus = SerenityRest.lastResponse().get() faber.remember("thid", presentationStatus.thid) @@ -76,21 +77,20 @@ class AnoncredsPresentProofSteps { @When("{actor} receives the anoncreds request") fun bobReceivesTheAnoncredsRequest(bob: Actor) { - wait( - { - proofEvent = ListenToEvents.`as`(bob).presentationEvents.lastOrNull { - it.data.thid == bob.recall("thid") - } - proofEvent != null && - proofEvent!!.data.status == PresentationStatus.Status.REQUEST_RECEIVED - }, - "ERROR: Bob did not achieve any presentation request!", - ) + Wait.until( + errorMessage = "ERROR: Bob did not achieve any presentation request!", + ) { + proofEvent = ListenToEvents.with(bob).presentationEvents.lastOrNull { + it.data.thid == bob.recall("thid") + } + proofEvent != null && + proofEvent!!.data.status == PresentationStatusAdapter.Status.REQUEST_RECEIVED + } bob.remember("presentationId", proofEvent!!.data.presentationId) } - @When("{actor} accepts the anoncreds presentation request from {actor}") - fun bobAcceptsTheAnoncredsPresentationWithProof(bob: Actor, faber: Actor) { + @When("{actor} accepts the anoncreds presentation request") + fun bobAcceptsTheAnoncredsPresentationWithProof(bob: Actor) { val requestPresentationAction = RequestPresentationAction( anoncredPresentationRequest = AnoncredCredentialProofsV1( @@ -105,8 +105,9 @@ class AnoncredsPresentProofSteps { action = RequestPresentationAction.Action.REQUEST_MINUS_ACCEPT, ) + val presentationId = bob.recall("presentationId") bob.attemptsTo( - Patch.to("/present-proof/presentations/${bob.recall("presentationId")}").with { + Patch.to("/present-proof/presentations/$presentationId").with { it.body( requestPresentationAction, ) diff --git a/tests/integration-tests/src/test/kotlin/steps/proofs/PresentProofSteps.kt b/tests/integration-tests/src/test/kotlin/steps/proofs/PresentProofSteps.kt index 741ef0e9b6..9d2d09665d 100644 --- a/tests/integration-tests/src/test/kotlin/steps/proofs/PresentProofSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/proofs/PresentProofSteps.kt @@ -1,23 +1,22 @@ package steps.proofs import abilities.ListenToEvents -import common.Utils.wait import interactions.Patch import interactions.Post 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 models.PresentationEvent +import io.iohk.atala.automation.utils.Wait +import models.PresentationStatusAdapter import net.serenitybdd.rest.SerenityRest import net.serenitybdd.screenplay.Actor import org.apache.http.HttpStatus.SC_CREATED import org.hyperledger.identus.client.models.* +import kotlin.time.Duration.Companion.seconds class PresentProofSteps { - private var proofEvent: PresentationEvent? = null - @When("{actor} sends a request for proof presentation to {actor}") fun faberSendsARequestForProofPresentationToBob(faber: Actor, bob: Actor) { val presentationRequest = RequestPresentationInput( @@ -51,17 +50,16 @@ class PresentProofSteps { @When("{actor} receives the request") fun bobReceivesTheRequest(bob: Actor) { - wait( - { - proofEvent = ListenToEvents.`as`(bob).presentationEvents.lastOrNull { - it.data.thid == bob.recall("thid") - } - proofEvent != null && - proofEvent!!.data.status == PresentationStatus.Status.REQUEST_RECEIVED - }, - "ERROR: Bob did not achieve any presentation request!", - ) - bob.remember("presentationId", proofEvent!!.data.presentationId) + Wait.until( + timeout = 30.seconds, + errorMessage = "ERROR: Bob did not achieve any presentation request!", + ) { + val proofEvent = ListenToEvents.with(bob).presentationEvents.lastOrNull { + it.data.thid == bob.recall("thid") + } + bob.remember("presentationId", proofEvent?.data?.presentationId) + proofEvent?.data?.status == PresentationStatusAdapter.Status.REQUEST_RECEIVED + } } @When("{actor} makes the presentation of the proof to {actor}") @@ -73,9 +71,7 @@ class PresentProofSteps { bob.attemptsTo( Patch.to("/present-proof/presentations/${bob.recall("presentationId")}").with { - it.body( - requestPresentationAction, - ) + it.body(requestPresentationAction) }, ) } @@ -85,9 +81,7 @@ class PresentProofSteps { bob.attemptsTo( Patch.to("/present-proof/presentations/${bob.recall("presentationId")}").with { it.body( - RequestPresentationAction( - action = RequestPresentationAction.Action.REQUEST_MINUS_REJECT, - ), + RequestPresentationAction(action = RequestPresentationAction.Action.REQUEST_MINUS_REJECT), ) }, ) @@ -95,30 +89,40 @@ class PresentProofSteps { @Then("{actor} sees the proof is rejected") fun bobSeesProofIsRejected(bob: Actor) { - wait( - { - proofEvent = ListenToEvents.`as`(bob).presentationEvents.lastOrNull { - it.data.thid == bob.recall("thid") - } - proofEvent != null && - proofEvent!!.data.status == PresentationStatus.Status.REQUEST_REJECTED - }, - "ERROR: Faber did not receive presentation from Bob!", - ) + Wait.until( + timeout = 30.seconds, + errorMessage = "ERROR: Faber did not receive presentation from Bob!", + ) { + val proofEvent = ListenToEvents.with(bob).presentationEvents.lastOrNull { + it.data.thid == bob.recall("thid") + } + proofEvent?.data?.status == PresentationStatusAdapter.Status.REQUEST_REJECTED + } } @Then("{actor} has the proof verified") fun faberHasTheProofVerified(faber: Actor) { - wait( - { - proofEvent = ListenToEvents.`as`(faber).presentationEvents.lastOrNull { - it.data.thid == faber.recall("thid") - } + Wait.until( + timeout = 30.seconds, + errorMessage = "Presentation did not achieve PresentationVerified state!", + ) { + val proofEvent = ListenToEvents.with(faber).presentationEvents.lastOrNull { + it.data.thid == faber.recall("thid") + } + proofEvent?.data?.status == PresentationStatusAdapter.Status.PRESENTATION_VERIFIED + } + } - proofEvent != null && - proofEvent!!.data.status == PresentationStatus.Status.PRESENTATION_VERIFIED - }, - "ERROR: presentation did not achieve PresentationVerified state!", - ) + @Then("{actor} sees the proof returned verification failed") + fun verifierSeesTheProofReturnedVerificationFailed(verifier: Actor) { + Wait.until( + timeout = 60.seconds, + errorMessage = "Presentation did not achieve PresentationVerificationFailed state!", + ) { + val proofEvent = ListenToEvents.with(verifier).presentationEvents.lastOrNull { + it.data.thid == verifier.recall("thid") + } + proofEvent?.data?.status == PresentationStatusAdapter.Status.PRESENTATION_VERIFICATION_FAILED + } } } diff --git a/tests/integration-tests/src/test/kotlin/steps/schemas/AnoncredCredentialSchemaSteps.kt b/tests/integration-tests/src/test/kotlin/steps/schemas/AnoncredCredentialSchemaSteps.kt new file mode 100644 index 0000000000..3279777b88 --- /dev/null +++ b/tests/integration-tests/src/test/kotlin/steps/schemas/AnoncredCredentialSchemaSteps.kt @@ -0,0 +1,85 @@ +package steps.schemas + +import interactions.Post +import io.cucumber.java.en.Given +import io.cucumber.java.en.When +import io.iohk.atala.automation.extensions.get +import io.iohk.atala.automation.serenity.ensure.Ensure +import models.AnoncredsSchema +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.hyperledger.identus.client.models.* +import java.util.UUID + +class AnoncredCredentialSchemaSteps { + @Given("{actor} has an anoncred schema definition") + fun issuerHasAnAnoncredSchemaDefinition(issuer: Actor) { + if (issuer.recallAll().containsKey("anoncredsCredentialDefinition")) { + return + } + if (!issuer.recallAll().containsKey("anoncredsSchema")) { + issuerCreatesAnoncredSchema(issuer) + } + issuerCreatesAnoncredCredentialDefinition(issuer) + } + + @When("{actor} creates anoncred schema") + fun issuerCreatesAnoncredSchema(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", "sex"), + ), + 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 issuerCreatesAnoncredCredentialDefinition(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}/schema", + 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) + } +} diff --git a/tests/integration-tests/src/test/resources/features/credentials/issue_published_did.feature b/tests/integration-tests/src/test/resources/features/credentials/issue_published_did.feature index d33c7a021d..c44fa3c948 100644 --- a/tests/integration-tests/src/test/resources/features/credentials/issue_published_did.feature +++ b/tests/integration-tests/src/test/resources/features/credentials/issue_published_did.feature @@ -15,9 +15,8 @@ Feature: Issue Credentials Protocol with published DID Then Holder receives the issued credential Scenario: Issuing anoncred with published PRISM DID - When Issuer creates anoncred schema - And Issuer creates anoncred credential definition - And Issuer offers anoncred to Holder + Given Issuer has an anoncred schema definition + When Issuer offers anoncred to Holder And Holder receives the credential offer And Holder accepts credential offer for anoncred And Issuer issues the credential diff --git a/tests/integration-tests/src/test/resources/features/did/deactivate_did.feature b/tests/integration-tests/src/test/resources/features/did/deactivate_did.feature index bed84078a0..89fc7358f5 100644 --- a/tests/integration-tests/src/test/resources/features/did/deactivate_did.feature +++ b/tests/integration-tests/src/test/resources/features/did/deactivate_did.feature @@ -2,6 +2,7 @@ Feature: Deactivate DID Scenario: Deactivate DID - Given Issuer have published PRISM DID + Given Issuer creates unpublished DID + And Issuer publishes DID to ledger When Issuer deactivates PRISM DID Then He sees that PRISM DID is successfully deactivated diff --git a/tests/integration-tests/src/test/resources/features/did/update_did.feature b/tests/integration-tests/src/test/resources/features/did/update_did.feature index ae474c65fa..ae2496ccb1 100644 --- a/tests/integration-tests/src/test/resources/features/did/update_did.feature +++ b/tests/integration-tests/src/test/resources/features/did/update_did.feature @@ -2,7 +2,7 @@ Feature: Update DID Background: Published DID is created - Given Issuer have published PRISM DID + Given Issuer has a published DID Scenario: Update PRISM DID by adding new services When Issuer updates PRISM DID with new services diff --git a/tests/integration-tests/src/test/resources/features/proofs/present_proof.feature b/tests/integration-tests/src/test/resources/features/proofs/present_proof.feature index cbf44843f6..f439ce3a09 100644 --- a/tests/integration-tests/src/test/resources/features/proofs/present_proof.feature +++ b/tests/integration-tests/src/test/resources/features/proofs/present_proof.feature @@ -1,3 +1,4 @@ +@proof @jwt Feature: Present Proof Protocol Scenario: Holder presents credential proof to verifier diff --git a/tests/integration-tests/src/test/resources/features/proofs/present_proof_anoncred.feature b/tests/integration-tests/src/test/resources/features/proofs/present_proof_anoncred.feature index 9161c46bb4..cee6e19472 100644 --- a/tests/integration-tests/src/test/resources/features/proofs/present_proof_anoncred.feature +++ b/tests/integration-tests/src/test/resources/features/proofs/present_proof_anoncred.feature @@ -1,3 +1,4 @@ +@proof @anoncreds Feature: Present Proof Protocol Scenario: Holder presents anoncreds credential proof to verifier @@ -5,8 +6,7 @@ Scenario: Holder presents anoncreds credential proof to verifier And Verifier and Holder have an existing connection And Issuer has a published DID And Holder has an unpublished DID - And Issuer creates anoncred schema - And Issuer creates anoncred credential definition + And Issuer has an anoncred schema definition And Issuer offers anoncred to Holder And Holder receives the credential offer And Holder accepts credential offer for anoncred @@ -14,5 +14,5 @@ Scenario: Holder presents anoncreds credential proof to verifier And Holder receives the issued credential When Verifier sends a anoncreds request for proof presentation to Holder using credential definition issued by Issuer And Holder receives the anoncreds request - And Holder accepts the anoncreds presentation request from Verifier + And Holder accepts the anoncreds presentation request Then Verifier has the proof verified diff --git a/tests/integration-tests/src/test/resources/features/revocation/revoke_jwt_credential.feature b/tests/integration-tests/src/test/resources/features/revocation/revoke_jwt_credential.feature new file mode 100644 index 0000000000..2ac02f447b --- /dev/null +++ b/tests/integration-tests/src/test/resources/features/revocation/revoke_jwt_credential.feature @@ -0,0 +1,21 @@ +@revocation @jwt +Feature: Credential revocation - JWT + + Background: + Given Holder has an issued credential from Issuer + + Scenario: Revoke issued credential + When Issuer revokes the credential issued to Holder + Then Issuer should see the credential was revoked + When Issuer sends a request for proof presentation to Holder + And Holder receives the request + And Holder makes the presentation of the proof to Issuer + Then Issuer sees the proof returned verification failed + + Scenario: Holder tries to revoke credential from issuer + When Holder tries to revoke credential from Issuer + And Issuer sends a request for proof presentation to Holder + And Holder receives the request + And Holder makes the presentation of the proof to Issuer + Then Issuer has the proof verified + And Issuer should see the credential is not revoked From cbd72a82f5105c0fbb30f0a07695474adad2668b Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev - IOHK Date: Thu, 23 May 2024 12:48:50 +0700 Subject: [PATCH 2/2] ci: fail megalinter check when there are autofixes (#1092) Signed-off-by: Yurii Shynbuiev --- .mega-linter.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.mega-linter.yml b/.mega-linter.yml index 9c6e9cd614..1011db191d 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -3,6 +3,7 @@ APPLY_FIXES: all DEFAULT_BRANCH: main +FAIL_IF_UPDATED_SOURCES: true DISABLE_LINTERS: - REPOSITORY_DEVSKIM - REPOSITORY_GITLEAKS