Skip to content

Commit

Permalink
test: add negative scenarios for oid4vci integration tests
Browse files Browse the repository at this point in the history
Signed-off-by: Allain Magyar <[email protected]>
  • Loading branch information
amagyar-iohk committed Sep 26, 2024
1 parent cc8a9bb commit 96c8435
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 57 deletions.
2 changes: 1 addition & 1 deletion tests/integration-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ class UpdateDidSteps {
Get.resource("/dids/${actor.recall<String>("shortFormDid")}"),
)
val service = SerenityRest.lastResponse().get<DIDResolutionResult>().didDocument!!.service!!
service.any { it.serviceEndpoint.getString().contains(serviceUrl) }
service.any { it.serviceEndpoint.value.contains(serviceUrl) }
},
equalTo(true),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {}")
Expand All @@ -22,19 +32,18 @@ class ManageCredentialConfigSteps {
val credentialIssuer = issuer.recall<CredentialIssuer>("oid4vciCredentialIssuer")
val schemaGuid = issuer.recall<String>(schema.name)
val baseUrl = issuer.recall<String>("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")
Expand All @@ -46,6 +55,75 @@ class ManageCredentialConfigSteps {
)
}

@When("{actor} deletes a non existent {} credential configuration")
fun issuerDeletesANonExistentCredentialConfiguration(issuer: Actor, configurationId: String) {
val credentialIssuer = issuer.recall<CredentialIssuer>("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<CredentialIssuer>("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<JsonObject>("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<JsonObject>("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<JsonObject>("credentialConfiguration")
val schemaIdProperty = if (schema == "null") {
null
} else {
val baseUrl = issuer.recall<String>("baseUrl")
val schemaGuid = issuer.recall<String>(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<JsonObject>("credentialConfiguration")
val credentialIssuerId = issuer.recall<UUID>("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<CredentialIssuer>("oid4vciCredentialIssuer")
Expand All @@ -65,11 +143,19 @@ class ManageCredentialConfigSteps {
val credentialIssuer = issuer.recall<CredentialIssuer>("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<IssuerMetadata>()
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)
)
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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")
Expand All @@ -56,7 +64,7 @@ class ManageIssuerSteps {

@Then("{actor} sees the oid4vci issuer exists on the agent")
fun issuerSeesCredentialIssuerExists(issuer: Actor) {
val credentialIssuer = issuer.recall<CredentialIssuer1>("oid4vciCredentialIssuer")
val credentialIssuer = issuer.recall<CredentialIssuer>("oid4vciCredentialIssuer")
issuer.attemptsTo(
Get("/oid4vci/issuers"),
Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK),
Expand All @@ -81,19 +89,29 @@ class ManageIssuerSteps {
fun issuerUpdateCredentialIssuer(issuer: Actor) {
val credentialIssuer = issuer.recall<CredentialIssuer>("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<CredentialIssuer>("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))
)
}

Expand All @@ -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<CredentialIssuer1>("oid4vciCredentialIssuer")
val credentialIssuer = issuer.recall<CredentialIssuer>("oid4vciCredentialIssuer")
issuer.attemptsTo(
Get("/oid4vci/issuers"),
Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_OK),
Expand All @@ -152,7 +207,7 @@ class ManageIssuerSteps {

@Then("{actor} cannot see the oid4vci issuer on the agent")
fun issuerCannotSeeCredentialIssuer(issuer: Actor) {
val credentialIssuer = issuer.recall<CredentialIssuer1>("oid4vciCredentialIssuer")
val credentialIssuer = issuer.recall<CredentialIssuer>("oid4vciCredentialIssuer")
issuer.attemptsTo(
Get("/oid4vci/issuers"),
Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_OK),
Expand All @@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading

0 comments on commit 96c8435

Please sign in to comment.