From 96c843529b685d71cf1abb737525e9e6ee63ddd5 Mon Sep 17 00:00:00 2001 From: Allain Magyar Date: Thu, 26 Sep 2024 08:46:22 -0300 Subject: [PATCH] test: add negative scenarios for oid4vci integration tests Signed-off-by: Allain Magyar --- tests/integration-tests/build.gradle.kts | 2 +- .../test/kotlin/steps/did/UpdateDidSteps.kt | 2 +- .../oid4vci/ManageCredentialConfigSteps.kt | 116 +++++++++++++++--- .../kotlin/steps/oid4vci/ManageIssuerSteps.kt | 115 +++++++++++++---- .../features/oid4vci/issue_jwt.feature | 6 +- .../oid4vci/manage_credential_config.feature | 29 ++++- .../features/oid4vci/manage_issuer.feature | 26 ++-- 7 files changed, 239 insertions(+), 57 deletions(-) diff --git a/tests/integration-tests/build.gradle.kts b/tests/integration-tests/build.gradle.kts index a2c4962c8d..07fcfb53a3 100644 --- a/tests/integration-tests/build.gradle.kts +++ b/tests/integration-tests/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { testImplementation("io.ktor:ktor-server-netty:2.3.0") testImplementation("io.ktor:ktor-client-apache:2.3.0") // RestAPI client - testImplementation("org.hyperledger.identus:cloud-agent-client-kotlin:0.0.1-SNAPSHOT") + testImplementation("org.hyperledger.identus:cloud-agent-client-kotlin:1.39.0-e077cdd") // Test helpers library testImplementation("io.iohk.atala:atala-automation:0.4.0") // Hoplite for configuration 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 267a564aba..e9e590d319 100644 --- a/tests/integration-tests/src/test/kotlin/steps/did/UpdateDidSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/did/UpdateDidSteps.kt @@ -189,7 +189,7 @@ class UpdateDidSteps { Get.resource("/dids/${actor.recall("shortFormDid")}"), ) val service = SerenityRest.lastResponse().get().didDocument!!.service!! - service.any { it.serviceEndpoint.getString().contains(serviceUrl) } + service.any { it.serviceEndpoint.value.contains(serviceUrl) } }, equalTo(true), ), diff --git a/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageCredentialConfigSteps.kt b/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageCredentialConfigSteps.kt index 470babb703..e75f8f5bf4 100644 --- a/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageCredentialConfigSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageCredentialConfigSteps.kt @@ -1,14 +1,24 @@ package steps.oid4vci +import com.google.gson.JsonObject import common.CredentialSchema -import interactions.* -import io.cucumber.java.en.* +import interactions.Delete +import interactions.Get +import interactions.Post +import interactions.body +import io.cucumber.java.en.Given +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 net.serenitybdd.rest.SerenityRest import net.serenitybdd.screenplay.Actor import org.apache.http.HttpStatus -import org.hyperledger.identus.client.models.* +import org.hyperledger.identus.client.models.CreateCredentialConfigurationRequest +import org.hyperledger.identus.client.models.CredentialFormat +import org.hyperledger.identus.client.models.CredentialIssuer +import org.hyperledger.identus.client.models.IssuerMetadata +import java.util.UUID class ManageCredentialConfigSteps { @Given("{actor} has {string} credential configuration created from {}") @@ -22,19 +32,18 @@ class ManageCredentialConfigSteps { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") val schemaGuid = issuer.recall(schema.name) val baseUrl = issuer.recall("baseUrl") + issuer.attemptsTo( - Post.to("/oid4vci/issuers/${credentialIssuer.id}/credential-configurations") - .with { - it.body( - CreateCredentialConfigurationRequest( - configurationId = configurationId, - format = CredentialFormat.JWT_VC_JSON, - schemaId = "$baseUrl/schema-registry/schemas/$schemaGuid/schema", - ), - ) - }, - Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_CREATED), + Post.to("/oid4vci/issuers/${credentialIssuer.id}/credential-configurations").body( + CreateCredentialConfigurationRequest( + configurationId = configurationId, + format = CredentialFormat.JWT_VC_JSON, + schemaId = "$baseUrl/schema-registry/schemas/$schemaGuid/schema", + ) + ), +// Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_CREATED), ) + SerenityRest.lastResponse().body.prettyPrint() } @When("{actor} deletes {string} credential configuration") @@ -46,6 +55,75 @@ class ManageCredentialConfigSteps { ) } + @When("{actor} deletes a non existent {} credential configuration") + fun issuerDeletesANonExistentCredentialConfiguration(issuer: Actor, configurationId: String) { + val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") + issuer.attemptsTo( + Delete("/oid4vci/issuers/${credentialIssuer.id}/credential-configurations/$configurationId") + ) + } + + @When("{actor} creates a new credential configuration request") + fun issuerCreatesANewConfigurationRequest(issuer: Actor) { + val credentialConfiguration = JsonObject() + issuer.remember("credentialConfiguration", credentialConfiguration) + } + + @When("{actor} uses {} issuer id for credential configuration") + fun issuerUsesIssuerId(issuer: Actor, issuerId: String) { + if (issuerId == "existing") { + val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") + issuer.remember("credentialConfigurationId", credentialIssuer.id) + } else if (issuerId == "wrong") { + issuer.remember("credentialConfigurationId", UUID.randomUUID()) + } + } + + @When("{actor} adds '{}' configuration id for credential configuration request") + fun issuerAddsConfigurationIdToCredentialConfigurationRequest(issuer: Actor, configurationId: String) { + val credentialIssuer = issuer.recall("credentialConfiguration") + val configurationIdProperty = if (configurationId == "null") { + null + } else { + configurationId + } + credentialIssuer.addProperty("configurationId", configurationIdProperty) + } + + @When("{actor} adds '{}' format for credential configuration request") + fun issuerAddsFormatToCredentialConfigurationRequest(issuer: Actor, format: String) { + val credentialIssuer = issuer.recall("credentialConfiguration") + val formatProperty = if (format == "null") { + null + } else { + format + } + credentialIssuer.addProperty("format", formatProperty) + } + + @When("{actor} adds '{}' schemaId for credential configuration request") + fun issuerAddsSchemaIdToCredentialConfigurationRequest(issuer: Actor, schema: String) { + + val credentialIssuer = issuer.recall("credentialConfiguration") + val schemaIdProperty = if (schema == "null") { + null + } else { + val baseUrl = issuer.recall("baseUrl") + val schemaGuid = issuer.recall(schema) + "$baseUrl/schema-registry/schemas/$schemaGuid/schema" + } + credentialIssuer.addProperty("schemaId", schemaIdProperty) + } + + @When("{actor} sends the create a credential configuration request") + fun issuerSendsTheCredentialConfigurationRequest(issuer: Actor) { + val credentialConfiguration = issuer.recall("credentialConfiguration") + val credentialIssuerId = issuer.recall("credentialConfigurationId").toString() + issuer.attemptsTo( + Post.to("/oid4vci/issuers/${credentialIssuerId}/credential-configurations").body(credentialConfiguration) + ) + } + @Then("{actor} sees the {string} configuration on IssuerMetadata endpoint") fun issuerSeesCredentialConfiguration(issuer: Actor, configurationId: String) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") @@ -65,11 +143,19 @@ class ManageCredentialConfigSteps { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") issuer.attemptsTo( Get("/oid4vci/issuers/${credentialIssuer.id}/.well-known/openid-credential-issuer"), - Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_OK), ) val metadata = SerenityRest.lastResponse().get() issuer.attemptsTo( Ensure.that(metadata.credentialConfigurationsSupported.keys).doesNotContain(configurationId), ) } + + @Then("{actor} should see that create credential configuration has failed with '{}' status code and '{}' detail") + fun issuerShouldSeeCredentialConfigurationRequestHasFailed(issuer: Actor, statusCode: Int, errorDetail: String) { + SerenityRest.lastResponse().body.prettyPrint() + issuer.attemptsTo( + Ensure.thatTheLastResponse().statusCode().isEqualTo(statusCode), + Ensure.that(SerenityRest.lastResponse().body.asString()).contains(errorDetail) + ) + } } diff --git a/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageIssuerSteps.kt b/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageIssuerSteps.kt index 72900f4962..ac7f6caad8 100644 --- a/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageIssuerSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/oid4vci/ManageIssuerSteps.kt @@ -1,13 +1,20 @@ package steps.oid4vci +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive import interactions.Delete import interactions.Get import interactions.Patch import interactions.Post +import interactions.body import io.cucumber.java.en.Given import io.cucumber.java.en.Then import io.cucumber.java.en.When import io.iohk.atala.automation.extensions.get +import io.iohk.atala.automation.extensions.toJsonPath import io.iohk.atala.automation.serenity.ensure.Ensure import net.serenitybdd.rest.SerenityRest import net.serenitybdd.screenplay.Actor @@ -17,7 +24,6 @@ import org.apache.http.HttpStatus.SC_OK import org.hyperledger.identus.client.models.AuthorizationServer import org.hyperledger.identus.client.models.CreateCredentialIssuerRequest import org.hyperledger.identus.client.models.CredentialIssuer -import org.hyperledger.identus.client.models.CredentialIssuer1 import org.hyperledger.identus.client.models.CredentialIssuerPage import org.hyperledger.identus.client.models.IssuerMetadata import org.hyperledger.identus.client.models.PatchAuthorizationServer @@ -30,7 +36,9 @@ class ManageIssuerSteps { @Given("{actor} has an existing oid4vci issuer") fun issuerHasExistingCredentialIssuer(issuer: Actor) { - issuerCreateCredentialIssuer(issuer) + if (!issuer.recallAll().containsKey("oid4vciCredentialIssuer")) { + issuerCreateCredentialIssuer(issuer) + } } @When("{actor} creates an oid4vci issuer") @@ -56,7 +64,7 @@ class ManageIssuerSteps { @Then("{actor} sees the oid4vci issuer exists on the agent") fun issuerSeesCredentialIssuerExists(issuer: Actor) { - val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") + val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") issuer.attemptsTo( Get("/oid4vci/issuers"), Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK), @@ -81,19 +89,29 @@ class ManageIssuerSteps { fun issuerUpdateCredentialIssuer(issuer: Actor) { val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") issuer.attemptsTo( - Patch.to("/oid4vci/issuers/${credentialIssuer.id}") - .with { - it.body( - PatchCredentialIssuerRequest( - authorizationServer = PatchAuthorizationServer( - url = UPDATE_AUTH_SERVER_URL, - clientId = UPDATE_AUTH_SERVER_CLIENT_ID, - clientSecret = UPDATE_AUTH_SERVER_CLIENT_SECRET, - ), - ), - ) - }, - Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_OK), + Patch.to("/oid4vci/issuers/${credentialIssuer.id}").body( + PatchCredentialIssuerRequest( + authorizationServer = PatchAuthorizationServer( + url = UPDATE_AUTH_SERVER_URL, + clientId = UPDATE_AUTH_SERVER_CLIENT_ID, + clientSecret = UPDATE_AUTH_SERVER_CLIENT_SECRET, + ), + ), + ), + Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK) + ) + } + + @When("{actor} tries to update the oid4vci issuer '{}' property using '{}' value") + fun issuerTriesToUpdateTheOID4VCIIssuer(issuer: Actor, property: String, value: String) { + val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") + val body = JsonObject() + val propertyValue = if (value == "null") { null } else { value } + body.addProperty(property, propertyValue) + + val gson = GsonBuilder().serializeNulls().create() + issuer.attemptsTo( + Patch.to("/oid4vci/issuers/${credentialIssuer.id}").body(gson.toJson(body)) ) } @@ -109,23 +127,60 @@ class ManageIssuerSteps { @When("{actor} tries to create oid4vci issuer with '{}', '{}', '{}' and '{}'") fun issuerTriesToCreateOIDCIssuer( issuer: Actor, - id: String?, - url: String?, - clientId: String?, - clientSecret: String? + id: String, + url: String, + clientId: String, + clientSecret: String ) { - println("$issuer $id $url $clientId $clientSecret") - println(url == null) - } + val idProperty = if (id == "null") { + null + } else { + id + } + val urlProperty = if (url == "null") { + null + } else { + url + } + val clientIdProperty = if (clientId == "null") { + null + } else { + clientId + } + val clientSecretProperty = if (clientSecret == "null") { + null + } else { + clientSecret + } + + val body = JsonObject() + val authorizationServer = JsonObject() - @Then("{actor} should see the oid4vci error '{}'") - fun issuerShouldSeeTheOIDC4VCIError(issuer: Actor, error: String) { + body.addProperty("id", idProperty) + body.add("authorizationServer", authorizationServer) + authorizationServer.addProperty("url", urlProperty) + authorizationServer.addProperty("clientId", clientIdProperty) + authorizationServer.addProperty("clientSecret", clientSecretProperty) + + val gson = GsonBuilder().serializeNulls().create() + issuer.attemptsTo( + Post.to("/oid4vci/issuers").body(gson.toJson(body)) + ) + } + + @Then("{actor} should see the oid4vci '{}' http status response with '{}' detail") + fun issuerShouldSeeTheOIDC4VCIError(issuer: Actor, httpStatus: Int, errorDetail: String) { + SerenityRest.lastResponse().body.prettyPrint() + issuer.attemptsTo( + Ensure.that(SerenityRest.lastResponse().statusCode).isEqualTo(httpStatus), + Ensure.that(SerenityRest.lastResponse().body.asString()).contains(errorDetail) + ) } @Then("{actor} sees the oid4vci issuer updated with new values") fun issuerSeesUpdatedCredentialIssuer(issuer: Actor) { - val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") + val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") issuer.attemptsTo( Get("/oid4vci/issuers"), Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_OK), @@ -152,7 +207,7 @@ class ManageIssuerSteps { @Then("{actor} cannot see the oid4vci issuer on the agent") fun issuerCannotSeeCredentialIssuer(issuer: Actor) { - val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") + val credentialIssuer = issuer.recall("oid4vciCredentialIssuer") issuer.attemptsTo( Get("/oid4vci/issuers"), Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_OK), @@ -172,4 +227,10 @@ class ManageIssuerSteps { Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_NOT_FOUND), ) } + + @Then("{actor} should see the update oid4vci issuer returned '{}' http status") + fun issuerShouldSeeTheUpdateOID4VCIIssuerReturnedHttpStatus(issuer: Actor, httpStatus: Int) { + println("STATUS: ${SerenityRest.lastResponse().statusCode}") + SerenityRest.lastResponse().body.prettyPrint() + } } diff --git a/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature b/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature index 2f30658ad8..53d010c2c5 100644 --- a/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature +++ b/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature @@ -1,20 +1,20 @@ @oid4vci Feature: Issue JWT Credentials using OID4VCI authorization code flow -Background: + Background: Given Issuer has a published DID for JWT And Issuer has published STUDENT_SCHEMA schema And Issuer has an existing oid4vci issuer And Issuer has "StudentProfile" credential configuration created from STUDENT_SCHEMA -Scenario: Issuing credential with published PRISM DID + Scenario: Issuing credential with published PRISM DID When Issuer creates an offer using "StudentProfile" configuration with "short" form DID And Holder receives oid4vci offer from Issuer And Holder resolves oid4vci issuer metadata and login via front-end channel And Holder presents the access token with JWT proof on CredentialEndpoint Then Holder sees credential issued successfully from CredentialEndpoint -Scenario: Issuing credential with unpublished PRISM DID + Scenario: Issuing credential with unpublished PRISM DID When Issuer creates an offer using "StudentProfile" configuration with "long" form DID And Holder receives oid4vci offer from Issuer And Holder resolves oid4vci issuer metadata and login via front-end channel diff --git a/tests/integration-tests/src/test/resources/features/oid4vci/manage_credential_config.feature b/tests/integration-tests/src/test/resources/features/oid4vci/manage_credential_config.feature index 3253069abe..47c7405f71 100644 --- a/tests/integration-tests/src/test/resources/features/oid4vci/manage_credential_config.feature +++ b/tests/integration-tests/src/test/resources/features/oid4vci/manage_credential_config.feature @@ -1,16 +1,39 @@ @oid4vci Feature: Manage OID4VCI credential configuration -Background: + Background: Given Issuer has a published DID for JWT And Issuer has published STUDENT_SCHEMA schema And Issuer has an existing oid4vci issuer -Scenario: Successfully create credential configuration + Scenario: Successfully create credential configuration When Issuer uses STUDENT_SCHEMA to create a credential configuration "StudentProfile" Then Issuer sees the "StudentProfile" configuration on IssuerMetadata endpoint -Scenario: Successfully delete credential configuration + Scenario: Successfully delete credential configuration Given Issuer has "StudentProfile" credential configuration created from STUDENT_SCHEMA When Issuer deletes "StudentProfile" credential configuration Then Issuer cannot see the "StudentProfile" configuration on IssuerMetadata endpoint + + Scenario Outline: Create configuration with expect code + When Issuer creates a new credential configuration request + And Issuer uses issuer id for credential configuration + And Issuer adds '' configuration id for credential configuration request + And Issuer adds '' format for credential configuration request + And Issuer adds '' schemaId for credential configuration request + And Issuer sends the create a credential configuration request + Then Issuer should see that create credential configuration has failed with '' status code and '' detail + Examples: + | issuerId | configurationId | format | schemaId | httpStatus | errorDetail | description | + | wrong | StudentProfile | jwt_vc_json | STUDENT_SCHEMA | 500 | | wrong issuer id | + | existing | null | jwt_vc_json | STUDENT_SCHEMA | 400 | configurationId | null configuration id | + | existing | StudentProfile | null | STUDENT_SCHEMA | 400 | format | null format | + | existing | StudentProfile | wrong-format | STUDENT_SCHEMA | 400 | format | wrong format | + | existing | StudentProfile | jwt_vc_json | null | 400 | schemaId | null schema | + | existing | StudentProfile | jwt_vc_json | malformed-schema | 502 | | malformed schema | + | existing | StudentProfile | jwt_vc_json | STUDENT_SCHEMA | 201 | | wrong issuer id | + | existing | StudentProfile | jwt_vc_json | STUDENT_SCHEMA | 500 | | wrong issuer id | + + Scenario: Delete non existent credential configuration + When Issuer deletes a non existent "NonExistentProfile" credential configuration + Then Issuer should see that create credential configuration has failed with '404' status code and 'There is no credential configuration' detail diff --git a/tests/integration-tests/src/test/resources/features/oid4vci/manage_issuer.feature b/tests/integration-tests/src/test/resources/features/oid4vci/manage_issuer.feature index 58a52814ee..74a5dcf595 100644 --- a/tests/integration-tests/src/test/resources/features/oid4vci/manage_issuer.feature +++ b/tests/integration-tests/src/test/resources/features/oid4vci/manage_issuer.feature @@ -19,12 +19,24 @@ Feature: Manage OID4VCI credential issuer And Issuer cannot see the oid4vci IssuerMetadata endpoint @test - Scenario Outline: Create issuer with wrong data should not work + Scenario Outline: Create issuer with expect response When Issuer tries to create oid4vci issuer with '', '', '' and '' - Then Issuer should see the oid4vci error '' + Then Issuer should see the oid4vci '' http status response with '' detail Examples: - | id | url | clientId | clientSecret | error | - | null | null | null | null | | - | | | | | | - | empty | empty | empty | empty | | - | 1 | {} | null | null | | + | id | url | clientId | clientSecret | httpStatus | errorDetail | description | + | null | null | null | null | 400 | authorizationServer.url | null values | + | null | malformed | id | secret | 400 | Invalid URL | malformed url | + | null | http://example.com | id | null | 400 | authorizationServer.clientSecret | null client secret | + | null | http://example.com | null | secret | 400 | authorizationServer.clientId | null client id | + | null | null | id | secret | 400 | authorizationServer.url | null url | + | 4048ef76-749d-4296-8c6c-07c8a20733a0 | http://example.com | id | secret | 201 | | right values | + | 4048ef76-749d-4296-8c6c-07c8a20733a0 | http://example.com | id | secret | 500 | | duplicated id | + + @test + Scenario Outline: Update issuer with expect response + Given Issuer has an existing oid4vci issuer + When Issuer tries to update the oid4vci issuer '' property using '' value + Then Issuer should see the oid4vci '' http status response with '' detail + Examples: + | property | value | httpStatus | errorDetail | description | + | url | malformed | 200 | | Invalid URL |