diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 8856d03094..5c6a9e504e 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -71,7 +71,7 @@ jobs: - name: Start services for issuer env: PORT: 8080 - NETWORK: prism + WEBHOOK_URL: http://host.docker.internal:9955 uses: isbang/compose-action@v1.4.1 with: compose-file: "./infrastructure/shared/docker-compose.yml" @@ -82,7 +82,7 @@ jobs: - name: Start services for holder env: PORT: 8090 - NETWORK: prism + WEBHOOK_URL: http://host.docker.internal:9956 uses: isbang/compose-action@v1.4.1 with: compose-file: "./infrastructure/shared/docker-compose.yml" @@ -93,6 +93,7 @@ jobs: - name: Start services for verifier env: PORT: 8070 + WEBHOOK_URL: http://host.docker.internal:9957 uses: isbang/compose-action@v1.4.1 with: compose-file: "./infrastructure/shared/docker-compose.yml" @@ -121,15 +122,6 @@ jobs: name: e2e-tests-result path: ${{ env.REPORTS_DIR }} - - name: Publish e2e test Results - if: always() - id: publish-unit-tests - uses: EnricoMi/publish-unit-test-result-action@v2 - with: - junit_files: "${{ env.REPORTS_DIR }}/SERENITY-JUNIT-*.xml" - comment_title: "E2E Test Results" - check_name: "E2E Test Results" - - name: Extract test results id: analyze_test_results if: github.ref_name == 'main' @@ -162,6 +154,15 @@ jobs: echo "ignored=${IGNORED_TESTS}"; } >> "$GITHUB_OUTPUT" + - name: Publish e2e test Results + if: github.ref_name == 'main' || steps.analyze_test_results.outputs.conclusion == 'failure' + id: publish-unit-tests + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + junit_files: "${{ env.REPORTS_DIR }}/SERENITY-JUNIT-*.xml" + comment_title: "E2E Test Results" + check_name: "E2E Test Results" + - name: Slack Notification if: github.ref_name == 'main' && steps.analyze_test_results.outputs.conclusion == 'failure' uses: rtCamp/action-slack-notify@v2 diff --git a/.github/workflows/unit-tests-common.yml b/.github/workflows/unit-tests-common.yml index 90bfb75746..86a2cdefdc 100644 --- a/.github/workflows/unit-tests-common.yml +++ b/.github/workflows/unit-tests-common.yml @@ -75,13 +75,3 @@ jobs: junit_files: "${{ inputs.component-dir }}/target/test-reports/**/TEST-*.xml" comment_title: "${{ inputs.component-name }} Test Results" check_name: "${{ inputs.component-name }} Test Results" - - - name: Code coverage report - if: github.event_name == 'pull_request' && inputs.measure-coverage - uses: 5monkeys/cobertura-action@master - with: - path: "${{ inputs.component-dir }}/target/coverage/coverage-report/cobertura.xml" - report_name: "${{ inputs.component-name }} Code Coverage" - minimum_coverage: 12 - fail_below_threshold: true - only_changed_files: true diff --git a/infrastructure/local/run.sh b/infrastructure/local/run.sh index 2c1b570e4d..19dcf4cd35 100755 --- a/infrastructure/local/run.sh +++ b/infrastructure/local/run.sh @@ -16,6 +16,8 @@ Help() { echo "-e/--env Provide your own .env file with versions." echo "-w/--wait Wait until all containers are healthy (only in the background)." echo "--network Specify a docker network to run containers on." + echo "--webhook Specify webhook URL for agent events" + echo "--webhook-api-key Specify api key to secure webhook if required" echo "--debug Run additional services for debug using docker-compose debug profile." echo "-h/--help Print this help text." echo @@ -53,6 +55,16 @@ while [[ $# -gt 0 ]]; do shift # past argument shift # past value ;; + --webhook) + WEBHOOK_URL="$2" + shift # past argument + shift # past value + ;; + --webhook-api-key) + WEBHOOK_API_KEY="$2" + shift # past argument + shift # past value + ;; --debug) DEBUG="--profile debug" shift # past argument @@ -89,12 +101,15 @@ echo "NAME = ${NAME}" echo "PORT = ${PORT}" echo "ENV_FILE = ${ENV_FILE}" echo "NETWORK = ${NETWORK}" +echo "WEBHOOK_URL = ${WEBHOOK_URL}" +echo "WEBHOOK_API_KEY = ${WEBHOOK_API_KEY}" + echo "--------------------------------------" echo "Starting stack using docker compose" echo "--------------------------------------" -PORT=${PORT} NETWORK=${NETWORK} docker compose \ +PORT=${PORT} NETWORK=${NETWORK} WEBHOOK_URL=${WEBHOOK_URL} WEBHOOK_API_KEY=${WEBHOOK_API_KEY} docker compose \ -p ${NAME} \ -f ${SCRIPT_DIR}/../shared/docker-compose.yml \ --env-file ${ENV_FILE} ${DEBUG} up ${BACKGROUND} ${WAIT} diff --git a/infrastructure/shared/docker-compose.yml b/infrastructure/shared/docker-compose.yml index f365a0271b..cf124b2a2b 100644 --- a/infrastructure/shared/docker-compose.yml +++ b/infrastructure/shared/docker-compose.yml @@ -100,8 +100,8 @@ services: SECRET_STORAGE_BACKEND: postgres DEV_MODE: true WALLET_SEED: - WEBHOOK_URL: - WEBHOOK_API_KEY: + WEBHOOK_URL: ${WEBHOOK_URL} + WEBHOOK_API_KEY: ${WEBHOOK_API_KEY} depends_on: db: condition: service_healthy @@ -116,11 +116,9 @@ services: retries: 5 extra_hosts: - "host.docker.internal:host-gateway" - # ports: - # - "8085:8085" swagger-ui: - image: swaggerapi/swagger-ui:v4.14.0 + image: swaggerapi/swagger-ui:v5.1.0 environment: - 'URLS=[ { name: "Prism Agent", url: "/docs/prism-agent/api/docs.yaml" }, diff --git a/tests/e2e-tests/build.gradle.kts b/tests/e2e-tests/build.gradle.kts index b9afd99397..a0ac7b5707 100644 --- a/tests/e2e-tests/build.gradle.kts +++ b/tests/e2e-tests/build.gradle.kts @@ -3,6 +3,7 @@ plugins { idea jacoco id("net.serenity-bdd.serenity-gradle-plugin") version "3.4.2" + kotlin("plugin.serialization") version "1.8.21" } repositories { @@ -23,6 +24,10 @@ dependencies { testImplementation("org.assertj:assertj-core:3.23.1") // Navigate through Json with xpath testImplementation("com.jayway.jsonpath:json-path:2.7.0") + // HTTP listener + implementation("io.ktor:ktor-server-netty:2.3.0") + implementation("io.ktor:ktor-client-apache:2.3.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") } buildscript { diff --git a/tests/e2e-tests/src/main/kotlin/api_models/Connection.kt b/tests/e2e-tests/src/main/kotlin/api_models/Connection.kt index cefaf484b6..7a1c6fd6e1 100644 --- a/tests/e2e-tests/src/main/kotlin/api_models/Connection.kt +++ b/tests/e2e-tests/src/main/kotlin/api_models/Connection.kt @@ -1,4 +1,8 @@ package api_models + +import kotlinx.serialization.Serializable + +@Serializable data class Connection( var connectionId: String = "", var thid: String = "", @@ -12,7 +16,7 @@ data class Connection( var myDid: String = "", var theirDid: String = "", var role: String = "", -) +): JsonEncoded object ConnectionState { const val INVITATION_GENERATED = "InvitationGenerated" diff --git a/tests/e2e-tests/src/main/kotlin/api_models/Credential.kt b/tests/e2e-tests/src/main/kotlin/api_models/Credential.kt index 499a3b7eff..b8ce7da55d 100644 --- a/tests/e2e-tests/src/main/kotlin/api_models/Credential.kt +++ b/tests/e2e-tests/src/main/kotlin/api_models/Credential.kt @@ -1,5 +1,8 @@ package api_models +import kotlinx.serialization.Serializable + +@Serializable data class Credential( var automaticIssuance: Boolean = false, var awaitConfirmation: Boolean = false, @@ -11,9 +14,16 @@ data class Credential( var schemaId: String? = "", var subjectId: String = "", var updatedAt: String = "", - var validityPeriod: Int = 0, + var validityPeriod: Double = 0.0, var claims: LinkedHashMap = LinkedHashMap(), var jwtCredential: String = "", var issuingDID: String = "", var connectionId: String = "", -) +): JsonEncoded + +object CredentialState { + const val OFFER_RECEIVED = "OfferReceived" + const val REQUEST_RECEIVED = "RequestReceived" + const val CREDENTIAL_SENT = "CredentialSent" + const val CREDENTIAL_RECEIVED = "CredentialReceived" +} diff --git a/tests/e2e-tests/src/main/kotlin/api_models/DidResolutionResult.kt b/tests/e2e-tests/src/main/kotlin/api_models/DidResolutionResult.kt index becd45dfc5..0c5a8c7a7e 100644 --- a/tests/e2e-tests/src/main/kotlin/api_models/DidResolutionResult.kt +++ b/tests/e2e-tests/src/main/kotlin/api_models/DidResolutionResult.kt @@ -1,12 +1,16 @@ package api_models +import kotlinx.serialization.Serializable + +@Serializable data class DidResolutionResult( var `@context`: String? = null, var didDocument: DidDocument? = null, var didDocumentMetadata: DidDocumentMetadata? = null, var didResolutionMetadata: DidResolutionMetadata? = null, -) +): JsonEncoded +@Serializable data class DidDocument( var `@context`: List? = null, var assertionMethod: List? = null, @@ -18,35 +22,40 @@ data class DidDocument( var keyAgreement: List? = null, var service: List? = null, var verificationMethod: List? = null, -) +): JsonEncoded +@Serializable data class VerificationMethod( var controller: String? = null, var id: String? = null, var publicKeyJwk: PublicKeyJwk? = null, var type: String? = null, -) +): JsonEncoded typealias VerificationMethodRef = String +@Serializable data class PublicKeyJwk( var crv: String? = null, var kty: String? = null, var x: String? = null, var y: String? = null, -) +): JsonEncoded +@Serializable data class DidDocumentMetadata( var canonicalId: String? = null, var deactivated: Boolean? = null, -) +): JsonEncoded +@Serializable data class DidDocumentService( var id: String? = null, var serviceEndpoint: List? = null, var type: String? = null, -) +): JsonEncoded +@Serializable data class DidResolutionMetadata( var contentType: String? = null, -) +): JsonEncoded diff --git a/tests/e2e-tests/src/main/kotlin/api_models/Events.kt b/tests/e2e-tests/src/main/kotlin/api_models/Events.kt new file mode 100644 index 0000000000..5830f94ad2 --- /dev/null +++ b/tests/e2e-tests/src/main/kotlin/api_models/Events.kt @@ -0,0 +1,44 @@ +package api_models + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement + +@Serializable +data class Event ( + var type: String, + var id: String, + var ts: String, + var data: JsonElement, +): JsonEncoded + +@Serializable +data class ConnectionEvent ( + var type: String, + var id: String, + var ts: String, + var data: Connection, +): JsonEncoded + +@Serializable +data class CredentialEvent ( + var type: String, + var id: String, + var ts: String, + var data: Credential, +): JsonEncoded + +@Serializable +data class PresentationEvent ( + var type: String, + var id: String, + var ts: String, + var data: PresentationProof, +): JsonEncoded + +@Serializable +data class DidEvent ( + var type: String, + var id: String, + var ts: String, + var data: ManagedDid, +): JsonEncoded diff --git a/tests/e2e-tests/src/main/kotlin/api_models/Invitation.kt b/tests/e2e-tests/src/main/kotlin/api_models/Invitation.kt index 5c123fac81..93cb6f4fe8 100644 --- a/tests/e2e-tests/src/main/kotlin/api_models/Invitation.kt +++ b/tests/e2e-tests/src/main/kotlin/api_models/Invitation.kt @@ -1,8 +1,11 @@ package api_models +import kotlinx.serialization.Serializable + +@Serializable data class Invitation( var id: String = "", var from: String = "", var invitationUrl: String = "", var type: String = "", -) +): JsonEncoded diff --git a/tests/e2e-tests/src/main/kotlin/api_models/JsonEncoded.kt b/tests/e2e-tests/src/main/kotlin/api_models/JsonEncoded.kt new file mode 100644 index 0000000000..560d6132f1 --- /dev/null +++ b/tests/e2e-tests/src/main/kotlin/api_models/JsonEncoded.kt @@ -0,0 +1,12 @@ +package api_models + +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@Serializable +sealed interface JsonEncoded { + fun toJsonString(): String { + return Json.encodeToString(this) + } +} diff --git a/tests/e2e-tests/src/main/kotlin/api_models/ManagedDid.kt b/tests/e2e-tests/src/main/kotlin/api_models/ManagedDid.kt index 4ed915ea39..e5063ffc30 100644 --- a/tests/e2e-tests/src/main/kotlin/api_models/ManagedDid.kt +++ b/tests/e2e-tests/src/main/kotlin/api_models/ManagedDid.kt @@ -1,10 +1,13 @@ package api_models +import kotlinx.serialization.Serializable + +@Serializable data class ManagedDid( var did: String = "", var longFormDid: String = "", var status: String = "", -) +): JsonEncoded object ManagedDidStatuses { val PUBLISHED = "PUBLISHED" diff --git a/tests/e2e-tests/src/main/kotlin/api_models/PresentationProof.kt b/tests/e2e-tests/src/main/kotlin/api_models/PresentationProof.kt index 60c906f158..d1ef9f6716 100644 --- a/tests/e2e-tests/src/main/kotlin/api_models/PresentationProof.kt +++ b/tests/e2e-tests/src/main/kotlin/api_models/PresentationProof.kt @@ -1,5 +1,8 @@ package api_models +import kotlinx.serialization.Serializable + +@Serializable data class PresentationProof( var presentationId: String? = null, var thid: String? = null, @@ -7,4 +10,10 @@ data class PresentationProof( var connectionId: String? = null, var proofs: List? = null, var data: List? = null, -) +): JsonEncoded + +object PresentationProofStatus { + const val REQUEST_RECEIVED = "RequestReceived" + const val REQUEST_REJECTED = "RequestRejected" + const val PRESENTATION_VERIFIED = "PresentationVerified" +} diff --git a/tests/e2e-tests/src/test/kotlin/common/Agents.kt b/tests/e2e-tests/src/test/kotlin/common/Agents.kt deleted file mode 100644 index c01dca7a04..0000000000 --- a/tests/e2e-tests/src/test/kotlin/common/Agents.kt +++ /dev/null @@ -1,30 +0,0 @@ -package common - -import io.restassured.builder.RequestSpecBuilder -import net.serenitybdd.rest.SerenityRest -import net.serenitybdd.screenplay.Actor -import net.serenitybdd.screenplay.rest.abilities.CallAnApi - -object Agents { - lateinit var Acme: Actor - private set - lateinit var Bob: Actor - private set - lateinit var Mallory: Actor - private set - lateinit var Faber: Actor - private set - - fun createAgents() { - Acme = Actor.named("Acme").whoCan(CallAnApi.at(Environments.ACME_AGENT_URL)) - Bob = Actor.named("Bob").whoCan(CallAnApi.at(Environments.BOB_AGENT_URL)) - Mallory = Actor.named("Mallory").whoCan(CallAnApi.at(Environments.MALLORY_AGENT_URL)) - Faber = Actor.named("Faber").whoCan(CallAnApi.at(Environments.FABER_AGENT_URL)) - if (Environments.AGENT_AUTH_REQUIRED) { - Acme.remember("AUTH_KEY", Environments.ACME_AUTH_KEY) - Bob.remember("AUTH_KEY", Environments.BOB_AUTH_KEY) - Mallory.remember("AUTH_KEY", Environments.MALLORY_AUTH_KEY) - Faber.remember("AUTH_KEY", Environments.FABER_AUTH_KEY) - } - } -} diff --git a/tests/e2e-tests/src/test/kotlin/common/Environments.kt b/tests/e2e-tests/src/test/kotlin/common/Environments.kt index 5804346985..206ec5cc78 100644 --- a/tests/e2e-tests/src/test/kotlin/common/Environments.kt +++ b/tests/e2e-tests/src/test/kotlin/common/Environments.kt @@ -5,10 +5,14 @@ object Environments { val AGENT_AUTH_HEADER = System.getenv("AGENT_AUTH_HEADER") ?: "apikey" val ACME_AUTH_KEY = System.getenv("ACME_AUTH_KEY") ?: "" val ACME_AGENT_URL = System.getenv("ACME_AGENT_URL") ?: "http://localhost:8080/prism-agent" + val ACME_AGENT_WEBHOOK_HOST = System.getenv("ACME_AGENT_WEBHOOK_HOST") ?: "0.0.0.0" + val ACME_AGENT_WEBHOOK_PORT = (System.getenv("ACME_AGENT_WEBHOOK_PORT") ?: "9955").toInt() val BOB_AGENT_URL = System.getenv("BOB_AGENT_URL") ?: "http://localhost:8090/prism-agent" val BOB_AUTH_KEY = System.getenv("BOB_AUTH_KEY") ?: "" - val MALLORY_AGENT_URL = System.getenv("MALLORY_AGENT_URL") ?: "http://localhost:8070/prism-agent" - val MALLORY_AUTH_KEY = System.getenv("MALLORY_AUTH_KEY") ?: "" + val BOB_AGENT_WEBHOOK_HOST = System.getenv("BOB_AGENT_WEBHOOK_HOST") ?: "0.0.0.0" + val BOB_AGENT_WEBHOOK_PORT = (System.getenv("BOB_AGENT_WEBHOOK_PORT") ?: "9956").toInt() val FABER_AGENT_URL = System.getenv("FABER_AGENT_URL") ?: "http://localhost:8070/prism-agent" val FABER_AUTH_KEY = System.getenv("FABER_AUTH_KEY") ?: "" + val FABER_AGENT_WEBHOOK_HOST = System.getenv("FABER_AGENT_WEBHOOK_HOST") ?: "0.0.0.0" + val FABER_AGENT_WEBHOOK_PORT = (System.getenv("FABER_AGENT_WEBHOOK_PORT") ?: "9957").toInt() } diff --git a/tests/e2e-tests/src/test/kotlin/common/ListenToEvents.kt b/tests/e2e-tests/src/test/kotlin/common/ListenToEvents.kt new file mode 100644 index 0000000000..b970624005 --- /dev/null +++ b/tests/e2e-tests/src/test/kotlin/common/ListenToEvents.kt @@ -0,0 +1,78 @@ +package common + +import api_models.* +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.engine.* +import io.ktor.server.netty.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import net.serenitybdd.screenplay.Ability +import net.serenitybdd.screenplay.Actor +import net.serenitybdd.screenplay.HasTeardown +import java.lang.IllegalArgumentException + +open class ListenToEvents( + private val host: String, + private val port: Int, +): Ability, HasTeardown { + + private val server: ApplicationEngine + + var connectionEvents: MutableList = mutableListOf() + var credentialEvents: MutableList = mutableListOf() + var presentationEvents: MutableList = mutableListOf() + var didEvents: MutableList = mutableListOf() + + fun route(application: Application) { + application.routing { + post("/") { + val eventString = call.receiveText() + val event = Json.decodeFromString(eventString) + when (event.type) { + TestConstants.EVENT_TYPE_CONNECTION_UPDATED -> connectionEvents.add(Json.decodeFromString(eventString)) + TestConstants.EVENT_TYPE_ISSUE_CREDENTIAL_RECORD_UPDATED -> credentialEvents.add(Json.decodeFromString(eventString)) + TestConstants.EVENT_TYPE_PRESENTATION_UPDATED -> presentationEvents.add(Json.decodeFromString(eventString)) + TestConstants.EVENT_TYPE_DID_STATUS_UPDATED -> { + println("Updating did events") + didEvents.add(Json.decodeFromString(eventString)) + } + else -> { + throw IllegalArgumentException("ERROR: unknown event type ${event.type}") + } + } + call.respond(HttpStatusCode.OK) + } + } + } + + companion object { + fun at(host: String, port: Int): ListenToEvents { + return ListenToEvents(host, port) + } + + fun `as`(actor: Actor): ListenToEvents { + return actor.abilityTo(ListenToEvents::class.java) + } + } + + init { + server = embeddedServer( + Netty, + port = port, + host = host, + module = {route(this)}) + .start(wait = false) + } + + override fun toString(): String { + return "Listen HTTP port at ${host}:${port}" + } + + override fun tearDown() { + server.stop() + } +} diff --git a/tests/e2e-tests/src/test/kotlin/common/TestConstants.kt b/tests/e2e-tests/src/test/kotlin/common/TestConstants.kt index 25c3e4cc59..e9a53decb3 100644 --- a/tests/e2e-tests/src/test/kotlin/common/TestConstants.kt +++ b/tests/e2e-tests/src/test/kotlin/common/TestConstants.kt @@ -35,4 +35,8 @@ object TestConstants { listOf("https://new.service.com/"), "LinkedDomains", ) + val EVENT_TYPE_CONNECTION_UPDATED = "ConnectionUpdated" + val EVENT_TYPE_ISSUE_CREDENTIAL_RECORD_UPDATED = "IssueCredentialRecordUpdated" + val EVENT_TYPE_PRESENTATION_UPDATED = "PresentationUpdated" + val EVENT_TYPE_DID_STATUS_UPDATED = "DIDStatusUpdated" } diff --git a/tests/e2e-tests/src/test/kotlin/common/Utils.kt b/tests/e2e-tests/src/test/kotlin/common/Utils.kt index c911adb0af..50c5d08b11 100644 --- a/tests/e2e-tests/src/test/kotlin/common/Utils.kt +++ b/tests/e2e-tests/src/test/kotlin/common/Utils.kt @@ -33,7 +33,7 @@ object Utils { fun wait( blockToWait: () -> Boolean, errorMessage: String, - poolInterval: FixedPollInterval = FixedPollInterval(Duration.ofSeconds(7L)), + poolInterval: FixedPollInterval = FixedPollInterval(Duration.ofMillis(100L)), timeout: Duration = Duration.ofSeconds(120L), ) { try { diff --git a/tests/e2e-tests/src/test/kotlin/features/CommonSteps.kt b/tests/e2e-tests/src/test/kotlin/features/CommonSteps.kt index 7d23a2c5cd..a9076b8c13 100644 --- a/tests/e2e-tests/src/test/kotlin/features/CommonSteps.kt +++ b/tests/e2e-tests/src/test/kotlin/features/CommonSteps.kt @@ -3,21 +3,20 @@ package features import api_models.Connection import api_models.ConnectionState import api_models.Credential -import common.Agents.Acme -import common.Agents.Bob -import common.Agents.Faber -import common.Agents.Mallory -import common.Agents.createAgents +import common.Environments +import common.ListenToEvents import common.Utils.lastResponseList import features.connection.ConnectionSteps import features.did.PublishDidSteps import features.issue_credentials.IssueCredentialsSteps +import io.cucumber.java.After import io.cucumber.java.Before import io.cucumber.java.ParameterType import io.cucumber.java.en.Given import net.serenitybdd.screenplay.Actor import net.serenitybdd.screenplay.actors.Cast import net.serenitybdd.screenplay.actors.OnStage +import net.serenitybdd.screenplay.rest.abilities.CallAnApi import net.serenitybdd.screenplay.rest.interactions.Get import net.serenitybdd.screenplay.rest.questions.ResponseConsequence import org.apache.http.HttpStatus.SC_OK @@ -26,15 +25,31 @@ class CommonSteps { @Before fun setStage() { - createAgents() - val cast = object : Cast() { - override fun getActors(): MutableList { - return mutableListOf(Acme, Bob, Mallory, Faber) + val cast = Cast() + cast.actorNamed("Acme", CallAnApi.at(Environments.ACME_AGENT_URL), ListenToEvents.at(Environments.ACME_AGENT_WEBHOOK_HOST, Environments.ACME_AGENT_WEBHOOK_PORT)) + cast.actorNamed("Bob", CallAnApi.at(Environments.BOB_AGENT_URL), ListenToEvents.at(Environments.BOB_AGENT_WEBHOOK_HOST, Environments.BOB_AGENT_WEBHOOK_PORT)) + cast.actorNamed("Faber", CallAnApi.at(Environments.FABER_AGENT_URL), ListenToEvents.at(Environments.FABER_AGENT_WEBHOOK_HOST, Environments.FABER_AGENT_WEBHOOK_PORT)) + cast.actors.forEach { actor -> + when(actor.name) { + "Acme" -> { + actor.remember("AUTH_KEY", Environments.ACME_AUTH_KEY) + } + "Bob" -> { + actor.remember("AUTH_KEY", Environments.BOB_AUTH_KEY) + } + "Faber" -> { + actor.remember("AUTH_KEY", Environments.FABER_AUTH_KEY) + } } } OnStage.setTheStage(cast) } + @After + fun clearStage() { + OnStage.drawTheCurtain() + } + @ParameterType(".*") fun actor(actorName: String): Actor { return OnStage.theActorCalled(actorName) diff --git a/tests/e2e-tests/src/test/kotlin/features/connection/ConnectionSteps.kt b/tests/e2e-tests/src/test/kotlin/features/connection/ConnectionSteps.kt index c66ec162a8..999a51da08 100644 --- a/tests/e2e-tests/src/test/kotlin/features/connection/ConnectionSteps.kt +++ b/tests/e2e-tests/src/test/kotlin/features/connection/ConnectionSteps.kt @@ -1,8 +1,7 @@ package features.connection -import api_models.Connection -import api_models.ConnectionState -import api_models.Invitation +import api_models.* +import common.ListenToEvents import common.Utils.lastResponseObject import common.Utils.wait import interactions.Get @@ -57,6 +56,7 @@ class ConnectionSteps { lastResponseObject("", Connection::class) .connectionId, ) + inviter.remember("thid", lastResponseObject("", Connection::class).thid) } @When("{actor} receives the connection invitation from {actor}") @@ -94,24 +94,18 @@ class ConnectionSteps { }, ) invitee.remember("connectionId", lastResponseObject("", Connection::class).connectionId) + invitee.remember("thid", lastResponseObject("", Connection::class).thid) } @When("{actor} receives the connection request and sends back the response") fun inviterReceivesTheConnectionRequest(inviter: Actor) { wait( { - inviter.attemptsTo( - Get.resource("/connections/${inviter.recall("connectionId")}"), - ) - inviter.should( - ResponseConsequence.seeThatResponse { - it.statusCode(SC_OK) - }, - ) - lastResponseObject("", Connection::class).state == ConnectionState.CONNECTION_RESPONSE_SENT + val lastEvent = ListenToEvents.`as`(inviter).connectionEvents.last() + lastEvent.data.thid == inviter.recall("thid") && + lastEvent.data.state == ConnectionState.CONNECTION_RESPONSE_SENT }, - "Inviter connection didn't reach ${ConnectionState.CONNECTION_RESPONSE_SENT} state: " + - "connection state = ${lastResponseObject("", Connection::class).state}", + "Inviter connection didn't reach ${ConnectionState.CONNECTION_RESPONSE_SENT} state", ) } @@ -120,18 +114,11 @@ class ConnectionSteps { // Bob (Holder) receives final connection response wait( { - invitee.attemptsTo( - Get.resource("/connections/${invitee.recall("connectionId")}"), - ) - invitee.should( - ResponseConsequence.seeThatResponse { - it.statusCode(SC_OK) - }, - ) - lastResponseObject("", Connection::class).state == ConnectionState.CONNECTION_RESPONSE_RECEIVED + val lastEvent = ListenToEvents.`as`(invitee).connectionEvents.last() + lastEvent.data.thid == invitee.recall("thid") && + lastEvent.data.state == ConnectionState.CONNECTION_RESPONSE_RECEIVED }, - "Invitee connection didn't reach ${ConnectionState.CONNECTION_RESPONSE_RECEIVED} state: " + - "state is ${lastResponseObject("", Connection::class).state}", + "Invitee connection didn't reach ${ConnectionState.CONNECTION_RESPONSE_RECEIVED} state.", ) } diff --git a/tests/e2e-tests/src/test/kotlin/features/did/DeactivateDidSteps.kt b/tests/e2e-tests/src/test/kotlin/features/did/DeactivateDidSteps.kt index e7124a2b8e..d5c56a029f 100644 --- a/tests/e2e-tests/src/test/kotlin/features/did/DeactivateDidSteps.kt +++ b/tests/e2e-tests/src/test/kotlin/features/did/DeactivateDidSteps.kt @@ -1,8 +1,9 @@ package features.did +import common.ListenToEvents import common.TestConstants -import common.Utils import common.Utils.lastResponseObject +import common.Utils.wait import io.cucumber.java.en.Then import io.cucumber.java.en.When import net.serenitybdd.screenplay.Actor @@ -30,7 +31,7 @@ class DeactivateDidSteps { @Then("{actor} sees that PRISM DID is successfully deactivated") fun actorSeesThatPrismDidIsSuccessfullyDeactivated(actor: Actor) { - Utils.wait( + wait( { actor.attemptsTo( Get.resource("/dids/${actor.recall("shortFormDid")}"), diff --git a/tests/e2e-tests/src/test/kotlin/features/did/PublishDidSteps.kt b/tests/e2e-tests/src/test/kotlin/features/did/PublishDidSteps.kt index 9e2a67fb06..3e65483d5d 100644 --- a/tests/e2e-tests/src/test/kotlin/features/did/PublishDidSteps.kt +++ b/tests/e2e-tests/src/test/kotlin/features/did/PublishDidSteps.kt @@ -1,6 +1,7 @@ package features.did import api_models.* +import common.ListenToEvents import common.TestConstants import common.Utils.lastResponseList import common.Utils.lastResponseObject @@ -8,7 +9,6 @@ import common.Utils.wait import io.cucumber.java.en.Given import io.cucumber.java.en.Then import io.cucumber.java.en.When -import net.serenitybdd.rest.SerenityRest import net.serenitybdd.screenplay.Actor import interactions.Get import interactions.Post @@ -101,11 +101,8 @@ class PublishDidSteps { ) wait( { - actor.attemptsTo( - Get.resource("/did-registrar/dids/${actor.recall("longFormDid")}"), - ) - SerenityRest.lastResponse().statusCode == SC_OK && lastResponseObject("", ManagedDid::class) - .status == ManagedDidStatuses.PUBLISHED + val didEvent = ListenToEvents.`as`(actor).didEvents.lastOrNull() + didEvent != null && didEvent.data.status == ManagedDidStatuses.PUBLISHED }, "ERROR: DID was not published to ledger!", timeout = TestConstants.DID_UPDATE_PUBLISH_MAX_WAIT_5_MIN, @@ -143,7 +140,6 @@ class PublishDidSteps { assertThat(didDocument.verificationMethod!![0]) .hasFieldOrPropertyWithValue("controller", shortFormDid) - .hasFieldOrPropertyWithValue("id", "$shortFormDid#${TestConstants.PRISM_DID_ASSERTION_KEY.id}") .hasFieldOrProperty("publicKeyJwk") assertThat(lastResponseObject("", DidResolutionResult::class).didDocumentMetadata!!) diff --git a/tests/e2e-tests/src/test/kotlin/features/issue_credentials/IssueCredentialsSteps.kt b/tests/e2e-tests/src/test/kotlin/features/issue_credentials/IssueCredentialsSteps.kt index bdf431e9ec..70849258f3 100644 --- a/tests/e2e-tests/src/test/kotlin/features/issue_credentials/IssueCredentialsSteps.kt +++ b/tests/e2e-tests/src/test/kotlin/features/issue_credentials/IssueCredentialsSteps.kt @@ -1,20 +1,21 @@ package features.issue_credentials -import api_models.Connection -import api_models.Credential -import common.Utils.lastResponseList +import api_models.* +import common.ListenToEvents import common.Utils.lastResponseObject import common.Utils.wait import io.cucumber.java.en.Then import io.cucumber.java.en.When import net.serenitybdd.screenplay.Actor -import interactions.Get import interactions.Post import net.serenitybdd.screenplay.rest.questions.ResponseConsequence import org.apache.http.HttpStatus.SC_CREATED import org.apache.http.HttpStatus.SC_OK class IssueCredentialsSteps { + + var credentialEvent: CredentialEvent? = null + @When("{actor} offers a credential to {actor} with {string} form DID") fun acmeOffersACredential(issuer: Actor, holder: Actor, didForm: String) { @@ -23,7 +24,7 @@ class IssueCredentialsSteps { val newCredential = Credential( schemaId = null, - validityPeriod = 3600, + validityPeriod = 3600.0, automaticIssuance = false, awaitConfirmation = false, claims = linkedMapOf( @@ -44,27 +45,22 @@ class IssueCredentialsSteps { it.statusCode(SC_CREATED) }, ) + issuer.remember("thid", lastResponseObject("", Credential::class).thid) + holder.remember("thid", lastResponseObject("", Credential::class).thid) } @When("{actor} receives the credential offer and accepts") fun bobRequestsTheCredential(holder: Actor) { wait( { - holder.attemptsTo( - Get.resource("/issue-credentials/records"), - ) - holder.should( - ResponseConsequence.seeThatResponse { - it.statusCode(SC_OK) - }, - ) - lastResponseList("contents", Credential::class).findLast { it.protocolState == "OfferReceived" } != null + credentialEvent = ListenToEvents.`as`(holder).credentialEvents.lastOrNull() + credentialEvent != null && credentialEvent!!.data.thid == holder.recall("thid") && + credentialEvent!!.data.protocolState == CredentialState.OFFER_RECEIVED }, "Holder was unable to receive the credential offer from Issuer! Protocol state did not achieve OfferReceived state.", ) - val recordId = lastResponseList("contents", Credential::class) - .findLast { it.protocolState == "OfferReceived" }!!.recordId + val recordId = ListenToEvents.`as`(holder).credentialEvents.last().data.recordId holder.remember("recordId", recordId) holder.attemptsTo( @@ -86,21 +82,13 @@ class IssueCredentialsSteps { fun acmeIssuesTheCredential(issuer: Actor) { wait( { - issuer.attemptsTo( - Get.resource("/issue-credentials/records"), - ) - issuer.should( - ResponseConsequence.seeThatResponse("Credential records") { - it.statusCode(SC_OK) - }, - ) - lastResponseList("contents", Credential::class) - .findLast { it.protocolState == "RequestReceived" } != null + credentialEvent = ListenToEvents.`as`(issuer).credentialEvents.lastOrNull() + credentialEvent != null && credentialEvent!!.data.thid == issuer.recall("thid") && + credentialEvent!!.data.protocolState == CredentialState.REQUEST_RECEIVED }, "Issuer was unable to receive the credential request from Holder! Protocol state did not achieve RequestReceived state.", ) - val recordId = lastResponseList("contents", Credential::class) - .findLast { it.protocolState == "RequestReceived" }!!.recordId + val recordId = credentialEvent!!.data.recordId issuer.attemptsTo( Post.to("/issue-credentials/records/$recordId/issue-credential"), ) @@ -112,17 +100,12 @@ class IssueCredentialsSteps { wait( { - issuer.attemptsTo( - Get.resource("/issue-credentials/records/$recordId"), - ) - issuer.should( - ResponseConsequence.seeThatResponse("Credential records") { - it.statusCode(SC_OK) - }, - ) - lastResponseObject("", Credential::class).protocolState == "CredentialSent" + credentialEvent = ListenToEvents.`as`(issuer).credentialEvents.lastOrNull() + credentialEvent!!.data.thid == issuer.recall("thid") && + credentialEvent!!.data.protocolState == CredentialState.CREDENTIAL_SENT }, - "Issuer was unable to issue the credential! Protocol state did not achieve CredentialSent state.", + "Issuer was unable to issue the credential! " + + "Protocol state did not achieve ${CredentialState.CREDENTIAL_SENT} state.", ) } @@ -130,18 +113,13 @@ class IssueCredentialsSteps { fun bobHasTheCredentialIssued(holder: Actor) { wait( { - holder.attemptsTo( - Get.resource("/issue-credentials/records/${holder.recall("recordId")}"), - ) - holder.should( - ResponseConsequence.seeThatResponse("Credential records") { - it.statusCode(SC_OK) - }, - ) - lastResponseObject("", Credential::class).protocolState == "CredentialReceived" + credentialEvent = ListenToEvents.`as`(holder).credentialEvents.lastOrNull() + credentialEvent!!.data.thid == holder.recall("thid") && + credentialEvent!!.data.protocolState == CredentialState.CREDENTIAL_RECEIVED }, - "Holder was unable to receive the credential from Issuer! Protocol state did not achieve CredentialReceived state.", + "Holder was unable to receive the credential from Issuer! " + + "Protocol state did not achieve ${CredentialState.CREDENTIAL_RECEIVED} state.", ) - holder.remember("issuedCredential", lastResponseObject("", Credential::class)) + holder.remember("issuedCredential", ListenToEvents.`as`(holder).credentialEvents.last().data) } } diff --git a/tests/e2e-tests/src/test/kotlin/features/present_proof/PresentProofSteps.kt b/tests/e2e-tests/src/test/kotlin/features/present_proof/PresentProofSteps.kt index 9fe3f28751..fd1cf9ee6d 100644 --- a/tests/e2e-tests/src/test/kotlin/features/present_proof/PresentProofSteps.kt +++ b/tests/e2e-tests/src/test/kotlin/features/present_proof/PresentProofSteps.kt @@ -1,15 +1,13 @@ package features.present_proof -import api_models.Connection -import api_models.Credential -import api_models.PresentationProof -import common.Utils.lastResponseList +import api_models.* +import common.ListenToEvents import common.Utils.lastResponseObject import common.Utils.wait +import interactions.Get import io.cucumber.java.en.Then import io.cucumber.java.en.When import net.serenitybdd.screenplay.Actor -import interactions.Get import interactions.Post import interactions.Patch import net.serenitybdd.screenplay.rest.questions.ResponseConsequence @@ -17,6 +15,9 @@ import org.apache.http.HttpStatus.SC_CREATED import org.apache.http.HttpStatus.SC_OK class PresentProofSteps { + + var proofEvent: PresentationEvent? = null + @When("{actor} sends a request for proof presentation to {actor}") fun faberSendsARequestForProofPresentationToBob(faber: Actor, bob: Actor) { faber.attemptsTo( @@ -49,32 +50,32 @@ class PresentProofSteps { it.statusCode(SC_CREATED) }, ) - faber.remember("presentationId", lastResponseObject("", PresentationProof::class).presentationId) + + val presentationId = lastResponseObject("", PresentationProof::class).presentationId + faber.remember("presentationId", presentationId) + faber.attemptsTo( + Get.resource("/present-proof/presentations/${presentationId}"), + ) + faber.should( + ResponseConsequence.seeThatResponse("Get presentations") { + it.statusCode(SC_OK) + }, + ) + faber.remember("thid", lastResponseObject("", PresentationProof::class).thid) + bob.remember("thid", lastResponseObject("", PresentationProof::class).thid) } @When("{actor} receives the request") fun bobReceivesTheRequest(bob: Actor) { wait( { - bob.attemptsTo( - Get.resource("/present-proof/presentations"), - ) - bob.should( - ResponseConsequence.seeThatResponse("Get presentations") { - it.statusCode(SC_OK) - }, - ) - lastResponseList("contents", PresentationProof::class).findLast { - it.status == "RequestReceived" - } != null + proofEvent = ListenToEvents.`as`(bob).presentationEvents.lastOrNull() + proofEvent != null && proofEvent!!.data.thid == bob.recall("thid") && + proofEvent!!.data.status == PresentationProofStatus.REQUEST_RECEIVED }, "ERROR: Bob did not achieve any presentation request!", ) - - val presentationId = lastResponseList("contents", PresentationProof::class).findLast { - it.status == "RequestReceived" - }!!.presentationId - bob.remember("presentationId", presentationId) + bob.remember("presentationId", proofEvent!!.data.presentationId) } @When("{actor} makes the presentation of the proof to {actor}") @@ -100,37 +101,12 @@ class PresentProofSteps { } @Then("{actor} sees the proof is rejected") - fun bobSeesProofIsRejected(actor: Actor) { + fun bobSeesProofIsRejected(bob: Actor) { wait( { - actor.attemptsTo( - Get.resource("/present-proof/presentations/${actor.recall("presentationId")}"), - ) - actor.should( - ResponseConsequence.seeThatResponse { - it.statusCode(SC_OK) - }, - ) - lastResponseObject("", PresentationProof::class).status == "RequestRejected" - }, - "ERROR: Faber did not receive presentation from Bob!", - ) - } - - @When("{actor} acknowledges the proof") - fun faberAcknowledgesTheProof(faber: Actor) { - wait( - { - faber.attemptsTo( - Get.resource("/present-proof/presentations/${faber.recall("presentationId")}"), - ) - faber.should( - ResponseConsequence.seeThatResponse { - it.statusCode(SC_OK) - }, - ) - lastResponseObject("", PresentationProof::class).status == "PresentationReceived" || - lastResponseObject("", PresentationProof::class).status == "PresentationVerified" + proofEvent = ListenToEvents.`as`(bob).presentationEvents.lastOrNull() + proofEvent != null && proofEvent!!.data.thid == bob.recall("thid") && + proofEvent!!.data.status == PresentationProofStatus.REQUEST_REJECTED }, "ERROR: Faber did not receive presentation from Bob!", ) @@ -140,15 +116,9 @@ class PresentProofSteps { fun faberHasTheProofVerified(faber: Actor) { wait( { - faber.attemptsTo( - Get.resource("/present-proof/presentations/${faber.recall("presentationId")}"), - ) - faber.should( - ResponseConsequence.seeThatResponse { - it.statusCode(SC_OK) - }, - ) - lastResponseObject("", PresentationProof::class).status == "PresentationVerified" + proofEvent = ListenToEvents.`as`(faber).presentationEvents.lastOrNull() + proofEvent != null && proofEvent!!.data.thid == faber.recall("thid") && + proofEvent!!.data.status == PresentationProofStatus.PRESENTATION_VERIFIED }, "ERROR: presentation did not achieve PresentationVerified state!", ) diff --git a/tests/e2e-tests/src/test/resources/features/did_registrar/create_did.feature b/tests/e2e-tests/src/test/resources/features/did_registrar/create_did.feature index d23c17e6ad..90d8b3b23a 100644 --- a/tests/e2e-tests/src/test/resources/features/did_registrar/create_did.feature +++ b/tests/e2e-tests/src/test/resources/features/did_registrar/create_did.feature @@ -1,4 +1,4 @@ -Feature: Create DID +Feature: Create and publish DID @TEST_ATL-3838 Scenario: Create PRISM DID @@ -31,3 +31,9 @@ Examples: | documentTemplate.services[0].id | # | 422 | | documentTemplate.services[0].type | pot@to | 422 | | documentTemplate.services[0].serviceEndpoint[0] | potato | 422 | + +@TEST_ATL-3842 +Scenario: Successfully publish DID to ledger + When Acme creates unpublished DID + And He publishes DID to ledger + Then He resolves DID document corresponds to W3C standard diff --git a/tests/e2e-tests/src/test/resources/features/did_registrar/publish_did.feature b/tests/e2e-tests/src/test/resources/features/did_registrar/publish_did.feature deleted file mode 100644 index 338247bf8f..0000000000 --- a/tests/e2e-tests/src/test/resources/features/did_registrar/publish_did.feature +++ /dev/null @@ -1,8 +0,0 @@ -@DLT -Feature: Publish DID - -@TEST_ATL-3842 -Scenario: Successfully publish DID to ledger - Given Acme creates unpublished DID - When He publishes DID to ledger - And He resolves DID document corresponds to W3C standard diff --git a/tests/e2e-tests/src/test/resources/features/present_proof/present_proof.feature b/tests/e2e-tests/src/test/resources/features/present_proof/present_proof.feature index a6c3b76b80..dcb0865754 100644 --- a/tests/e2e-tests/src/test/resources/features/present_proof/present_proof.feature +++ b/tests/e2e-tests/src/test/resources/features/present_proof/present_proof.feature @@ -7,7 +7,6 @@ Scenario: Holder presents credential proof to verifier When Faber sends a request for proof presentation to Bob And Bob receives the request And Bob makes the presentation of the proof to Faber - And Faber acknowledges the proof Then Faber has the proof verified @TEST_ATL-3881 @@ -26,5 +25,4 @@ Scenario: Holder presents proof to verifier which is the issuer itself When Acme sends a request for proof presentation to Bob And Bob receives the request And Bob makes the presentation of the proof to Acme - And Acme acknowledges the proof Then Acme has the proof verified