diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index 48424e4fff..813af6813e 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -28,6 +28,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ !contains(github.event.pull_request.title, '[skip ci]') }}
env:
+ LOGS_DIR: "tests/integration-tests/target/logs"
REPORTS_DIR: "tests/integration-tests/target/site/serenity"
steps:
- name: Checkout
@@ -131,12 +132,21 @@ jobs:
comment_title: "Integration Test Results"
check_name: "Integration Test Results"
- - name: Upload artifacts
+ - name: Upload serenity report
if: github.ref_name == 'main' || failure()
uses: actions/upload-artifact@v4
with:
name: integration-tests-result
path: ${{ env.REPORTS_DIR }}
+ compression-level: 9
+
+ - name: Upload logs
+ if: github.ref_name == 'main' || failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: docker-logs
+ path: ${{ env.LOGS_DIR }}
+ compression-level: 9
- name: Slack Notification
if: github.ref_name == 'main' && failure()
diff --git a/tests/integration-tests/README.md b/tests/integration-tests/README.md
index 91d175e678..2fef57bf71 100644
--- a/tests/integration-tests/README.md
+++ b/tests/integration-tests/README.md
@@ -30,8 +30,10 @@ The Screenplay pattern is used to write the tests. The pattern is described in d
* The Screenplay Pattern promotes the use of high-level, business-oriented language in test scripts, making them more understandable to non-technical stakeholders.
-
-Pic. 1. Screenplay pattern overview
+
+ Screenplay pattern overview
## Project structure
@@ -67,8 +69,10 @@ The main idea of the framework is to test the ICA as a black box.
The tests interact with the ICA through the API and webhook messages.
-
-Pic. 2. Overview of the system under test. Roles, Agents and Services communication.
+
+ Overview of the system under test. Roles, Agents and Services communication.
### ICA Roles in Tests
@@ -397,7 +401,7 @@ The following variables must be set before running the tests:
* `AGENT_VERSION`: version of the ICA docker image to use.
```shell
-TESTS_CONFIG=/configs/basic.conf PRISM_NODE_VERSION=2.3.0 AGENT_VERSION=1.30.1 ./gradlew test
+TESTS_CONFIG=/configs/basic.conf PRISM_NODE_VERSION=2.3.0 AGENT_VERSION=1.36.1 ./gradlew test
```
> Please note: there is no need to pass environment variables if you're using already running agents.
@@ -414,17 +418,37 @@ To simplify the execution, each configuration file creates a new `gradle` task.
It's possible to execute the configuration file as
```shell
-PRISM_NODE_VERSION=2.3.0 AGENT_VERSION=1.30.1 ./gradlew test_basic
+PRISM_NODE_VERSION=2.3.0 AGENT_VERSION=1.36.1 ./gradlew test_basic
```
-Also, it's possible to execute the integration tests to all configurations files. The task is named `regression`, it should take a lot of time to execute.
-
-Note: report is not working due constrains in Serenity BDD reporting system.
+Also, it's possible to execute the integration tests to all configurations files. The task is named `regression`, it should take a long time to execute.
```shell
-PRISM_NODE_VERSION=2.3.0 AGENT_VERSION=1.30.1 ./gradlew regression
+PRISM_NODE_VERSION=2.3.0 AGENT_VERSION=1.36.1 ./gradlew regression
+```
+
+#### Regression report
+
+Running the regression tasks implies running the same features multiple times.
+To enable a full report of the regression execution, `context` variable was introduced to the report.
+
+To run all scenarios, even if there's a failure, it's required to add `--continue` to the execution
+
+Example
+```bash
+AGENT_VERSION=v1.36.1 PRISM_NODE_VERSION=v2.3.0 ./gradlew regression --continue
```
+Each `context` is based on the configuration used for the current execution and will be displayed in the
+Serenity report:
+
+
+
+ Serenity Regression report with contexts
+
+
### Running scenarios in IntelliJ IDEA
To run the scenarios in IntelliJ IDEA, you need to create a new run configuration.
@@ -434,8 +458,10 @@ It is easy to do by executing `IntegrationTestsRunner` class and selecting the r
The required configuration will be created, but you have to edit it to set the environment variables.
-
-Pic. 3. Running tests through IntelliJ IDEA.
+
+ Running tests through IntelliJ IDEA.
You could edit `@CucumberOptions` annotation to specify the features to run, as well as specify tags to include or exclude:
@@ -444,8 +470,9 @@ For example, here is how you can run only connection scenarios:
```kotlin
@CucumberOptions(
features = ["src/test/resources/features/connection"],
- ...
+ // ...
)
+class IntegrationTestsRunner
```
If you would like to run only particular scenarios from the feature or combine multiple scenarios from different feature file,
@@ -454,8 +481,9 @@ you could use tags:
@CucumberOptions(
features = ["src/test/resources/features"],
tags = ["@connection and @credentials"],
- ...
+ // ...
)
+class IntegrationTestsRunner
```
> Please note: if you use custom tags, you need to specify them in the feature files as well.
@@ -489,20 +517,26 @@ You could start by opening `index.html` file in your browser.
On the main report page you could see the summary of the test run as well as the functional coverage table:
-
-Pic. 4. HTML-report summary example.
+
+ HTML-report summary example.
-
-Pic. 5. Functional coverage example.
+
+ Functional coverage example.
Then, you can go deeper to each scenario and open each step to see the details of the test execution:
-
-Pic. 6. REST requests analysis example.
+
+ REST requests analysis example.
### Summary reports
@@ -530,3 +564,24 @@ And summary reports themselves will be available in `./target/site/serenity` fol
JUnit XML reports are also generated under `./target/site/serenity` folder with names `SERENITY-JUNIT-*.xml`.
> For more information about the reports, please refer to [Serenity BDD reports documentation](https://serenity-bdd.github.io/docs/reporting/the_serenity_reports).
+
+### Docker logs
+
+Docker logs are now redirected to `target/logs` folder.
+
+If you're running the test using the custom config goals it will have the context added
+to the path as `target/logs/basic`.
+
+Example
+```bash
+AGENT_VERSION=v1.36.1 PRISM_NODE_VERSION=v2.3.0 ./gradlew test_basic
+```
+
+Will have the logs output as such:
+
+
+
+ Docker logs directory
+
diff --git a/tests/integration-tests/build.gradle.kts b/tests/integration-tests/build.gradle.kts
index 24d137624b..177a614b9c 100644
--- a/tests/integration-tests/build.gradle.kts
+++ b/tests/integration-tests/build.gradle.kts
@@ -11,8 +11,8 @@ version = "1.0-SNAPSHOT"
buildscript {
dependencies {
- classpath("net.serenity-bdd:serenity-single-page-report:4.1.4")
- classpath("net.serenity-bdd:serenity-json-summary-report:4.1.4")
+ classpath("net.serenity-bdd:serenity-single-page-report:4.0.46")
+ classpath("net.serenity-bdd:serenity-json-summary-report:4.0.46")
}
}
@@ -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:1.33.1")
+ testImplementation("org.hyperledger.identus:cloud-agent-client-kotlin:1.36.1")
// Test helpers library
testImplementation("io.iohk.atala:atala-automation:0.4.0")
// Hoplite for configuration
@@ -41,6 +41,10 @@ dependencies {
testImplementation("com.sksamuel.hoplite:hoplite-hocon:2.7.5")
// Kotlin compose
testImplementation("org.testcontainers:testcontainers:1.19.1")
+ // Crypto
+ testImplementation("com.nimbusds:nimbus-jose-jwt:9.40")
+ testImplementation("org.bouncycastle:bcprov-jdk18on:1.78.1")
+ testImplementation("com.google.crypto.tink:tink:1.13.0")
}
serenity {
@@ -54,7 +58,7 @@ tasks.register("cleanTarget") {
tasks.test {
dependsOn("cleanTarget")
- finalizedBy("reports")
+ finalizedBy("aggregate", "reports")
testLogging.showStandardStreams = true
systemProperty("cucumber.filter.tags", System.getProperty("cucumber.filter.tags"))
}
@@ -82,11 +86,13 @@ afterEvaluate {
tasks.register("test_$fileName") {
group = "verification"
testLogging.showStandardStreams = true
+ systemProperty("context", fileName)
systemProperty("TESTS_CONFIG", "/configs/$fileName.conf")
systemProperty("PRISM_NODE_VERSION", System.getenv("PRISM_NODE_VERSION") ?: "")
systemProperty("AGENT_VERSION", System.getenv("AGENT_VERSION") ?: "")
systemProperty("cucumber.filter.tags", System.getProperty("cucumber.filter.tags"))
finalizedBy("aggregate", "reports")
+ outputs.upToDateWhen { false }
}
}
@@ -94,7 +100,7 @@ afterEvaluate {
* Runs the integration suite for each config file present
* Restrictions: aggregation of all executions doesn't work because of serenity configuration
*/
- tasks.register("regression") {
+ tasks.register("regression") {
dependsOn("cleanTarget")
group = "verification"
configFiles.forEach {
diff --git a/tests/integration-tests/docs/static/logs.png b/tests/integration-tests/docs/static/logs.png
new file mode 100644
index 0000000000..776e0643d2
Binary files /dev/null and b/tests/integration-tests/docs/static/logs.png differ
diff --git a/tests/integration-tests/docs/static/serenity_context.png b/tests/integration-tests/docs/static/serenity_context.png
new file mode 100644
index 0000000000..1470f2e2af
Binary files /dev/null and b/tests/integration-tests/docs/static/serenity_context.png differ
diff --git a/tests/integration-tests/serenity.properties b/tests/integration-tests/serenity.properties
index c8682e00d5..7cde73aebd 100644
--- a/tests/integration-tests/serenity.properties
+++ b/tests/integration-tests/serenity.properties
@@ -1,8 +1,9 @@
-serenity.project.name=Open Enterprise Agent Integration tests
+serenity.project.name=Identus Integration tests
jira.url=https://input-output.atlassian.net
jira.project=ATL
serenity.reports.show.step.details=true
serenity.report.show.manual.tests=false
+serenity.verbose.steps=true
serenity.simplified.stack.traces=false
serenity.report.accessibility=true
json.pretty.printing=true
diff --git a/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt b/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt
index 972f806069..05a6bbf37c 100644
--- a/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt
+++ b/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt
@@ -30,10 +30,15 @@ enum class CredentialSchema {
tags = listOf("school", "students"),
version = "1.0.0",
)
+ override val claims: Map = linkedMapOf(
+ "name" to "Name",
+ "age" to 18,
+ )
}, ;
abstract val credentialSchema: CredentialSchemaInput
abstract val schema: JsonSchema
abstract val credentialSchemaType: String
abstract val schemaType: String
+ abstract val claims: Map
}
diff --git a/tests/integration-tests/src/test/kotlin/common/DidPurpose.kt b/tests/integration-tests/src/test/kotlin/common/DidPurpose.kt
new file mode 100644
index 0000000000..dee1243f98
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/common/DidPurpose.kt
@@ -0,0 +1,25 @@
+package common
+
+import org.hyperledger.identus.client.models.*
+
+enum class DidPurpose {
+ EMPTY {
+ override val publicKeys = emptyList()
+ override val services = emptyList()
+ },
+ JWT {
+ override val publicKeys = listOf(
+ ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.SECP256K1),
+ ManagedDIDKeyTemplate("auth-2", Purpose.AUTHENTICATION, Curve.ED25519),
+ ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.SECP256K1),
+ )
+ override val services = emptyList()
+ },
+ ANONCRED {
+ override val publicKeys = emptyList()
+ override val services = emptyList()
+ }, ;
+
+ abstract val publicKeys: List
+ abstract val services: List
+}
diff --git a/tests/integration-tests/src/test/kotlin/common/JwtCredentialProblem.kt b/tests/integration-tests/src/test/kotlin/common/JwtCredentialProblem.kt
new file mode 100644
index 0000000000..eb9435d881
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/common/JwtCredentialProblem.kt
@@ -0,0 +1,120 @@
+package common
+
+import com.nimbusds.jose.JWSAlgorithm
+import com.nimbusds.jose.jwk.Curve
+import models.JwtCredential
+import org.hyperledger.identus.client.models.VcVerification
+import java.time.OffsetDateTime
+
+enum class JwtCredentialProblem {
+ ALGORITHM_VERIFICATION {
+ override fun jwt(): String {
+ val jwt = VerifiableJwt.jwtVCv1()
+ return jwt.sign(JWSAlgorithm.HS256, null)
+ }
+ override val verification = VcVerification.ALGORITHM_VERIFICATION
+ },
+ AUDIENCE_CHECK {
+ override fun jwt(): String {
+ val jwt = VerifiableJwt.jwtVCv1()
+ jwt.audience("did:wrong")
+ return jwt.sign(DEFAULT_ALGORITHM, DEFAULT_CURVE)
+ }
+ override val verification = VcVerification.AUDIENCE_CHECK
+ },
+ COMPLIANCE_WITH_STANDARDS {
+ override fun jwt(): String {
+ TODO("Not supported yet")
+ }
+
+ override val verification = VcVerification.COMPLIANCE_WITH_STANDARDS
+ },
+ EXPIRATION_CHECK {
+ override fun jwt(): String {
+ val jwt = VerifiableJwt.jwtVCv1()
+ jwt.expirationTime(OffsetDateTime.now().plusYears(10))
+ return jwt.sign(DEFAULT_ALGORITHM, DEFAULT_CURVE)
+ }
+
+ override val verification = VcVerification.EXPIRATION_CHECK
+ },
+ INTEGRITY_OF_CLAIMS {
+ override fun jwt(): String {
+ TODO("Not supported yet")
+ }
+ override val verification = VcVerification.INTEGRITY_OF_CLAIMS
+ },
+ ISSUER_IDENTIFICATION {
+ override fun jwt(): String {
+ val jwt = VerifiableJwt.jwtVCv1()
+ jwt.issuer("did:wrong")
+ return jwt.sign(DEFAULT_ALGORITHM, DEFAULT_CURVE)
+ }
+ override val verification = VcVerification.ISSUER_IDENTIFICATION
+ },
+ NOT_BEFORE_CHECK {
+ override fun jwt(): String {
+ val jwt = VerifiableJwt.jwtVCv1()
+ jwt.notBefore(OffsetDateTime.now().minusYears(10))
+ return jwt.sign(DEFAULT_ALGORITHM, DEFAULT_CURVE)
+ }
+ override val verification = VcVerification.NOT_BEFORE_CHECK
+ },
+ REVOCATION_CHECK {
+ override fun jwt(): String {
+ TODO("Not supported yet")
+ }
+ override val verification = VcVerification.REVOCATION_CHECK
+ },
+ SCHEMA_CHECK {
+ override fun jwt(): String {
+ TODO("Not supported yet")
+ }
+ override val verification = VcVerification.SCHEMA_CHECK
+ },
+ SEMANTIC_CHECK_OF_CLAIMS {
+ override fun jwt(): String {
+ val jwt = VerifiableJwt.jwtVCv1()
+ val jwtCredential = JwtCredential()
+ val claims = mutableMapOf()
+ claims.putAll(jwt.claimSetBuilder.claims)
+ claims.remove("iss")
+ jwtCredential.claims(claims)
+ return jwt.sign(DEFAULT_ALGORITHM, DEFAULT_CURVE)
+ }
+ override val verification = VcVerification.SEMANTIC_CHECK_OF_CLAIMS
+ },
+ SIGNATURE_VERIFICATION {
+ override fun jwt(): String {
+ val jwt = VerifiableJwt.jwtVCv1()
+ return jwt.sign(DEFAULT_ALGORITHM, DEFAULT_CURVE)
+ }
+ override val verification = VcVerification.SIGNATURE_VERIFICATION
+ },
+ SUBJECT_VERIFICATION {
+ override fun jwt(): String {
+ TODO("Not yet implemented")
+ }
+ override val verification = VcVerification.SUBJECT_VERIFICATION
+ }, ;
+
+ companion object {
+ init {
+ // forcefully check if JwtCredentialProblems has all VcVerification
+ // cases since it's not possible to inherit final class
+ VcVerification.entries.forEach {
+ try {
+ JwtCredentialProblem.valueOf(it.name)
+ } catch (e: IllegalArgumentException) {
+ throw IllegalArgumentException("JwtCredentialProblem does not contain the new ${it.name} VcVerification case")
+ }
+ }
+ }
+ }
+
+ protected val DEFAULT_ALGORITHM = JWSAlgorithm.ES256K
+ protected val DEFAULT_CURVE = Curve.SECP256K1
+
+ abstract fun jwt(): String
+ abstract val verification: VcVerification
+}
diff --git a/tests/integration-tests/src/test/kotlin/common/SchemaErrorTemplate.kt b/tests/integration-tests/src/test/kotlin/common/SchemaErrorTemplate.kt
new file mode 100644
index 0000000000..c76c614113
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/common/SchemaErrorTemplate.kt
@@ -0,0 +1,78 @@
+package common
+
+import com.google.gson.Gson
+import com.google.gson.JsonObject
+import common.CredentialSchema.STUDENT_SCHEMA
+import net.serenitybdd.screenplay.Actor
+
+enum class SchemaErrorTemplate {
+ TYPE_AND_PROPERTIES_WITHOUT_SCHEMA_TYPE {
+ override fun inner_schema(): String {
+ return """
+ {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "age": {
+ "type": "integer"
+ }
+ },
+ "required": ["name"]
+ }
+ """.trimIndent()
+ }
+ },
+ CUSTOM_WORDS_NOT_DEFINED {
+ override fun inner_schema(): String {
+ return """
+ {
+ "${"$"}schema": "http://json-schema.org/draft-2020-12/schema#",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "age": {
+ "type": "integer"
+ }
+ },
+ "customKeyword": "value"
+ }
+ """.trimIndent()
+ }
+ },
+ MISSING_REQUIRED_FOR_MANDATORY_PROPERTY {
+ override fun inner_schema(): String {
+ return """
+ {
+ "${"$"}schema": "http://json-schema.org/draft-2020-12/schema#",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "age": {
+ "type": "integer"
+ }
+ }
+ }
+ """
+ }
+ }, ;
+
+ abstract fun inner_schema(): String
+
+ fun schema(actor: Actor): String {
+ val innerSchema = Gson().fromJson(inner_schema(), JsonObject::class.java)
+ val json = getJson(actor)
+ json.add("schema", innerSchema)
+ return json.toString()
+ }
+
+ private fun getJson(actor: Actor): JsonObject {
+ val jsonString = Gson().toJson(STUDENT_SCHEMA.credentialSchema.copy(author = actor.recall("shortFormDid")))
+ return Gson().fromJson(jsonString, JsonObject::class.java)
+ }
+}
diff --git a/tests/integration-tests/src/test/kotlin/common/TestConstants.kt b/tests/integration-tests/src/test/kotlin/common/TestConstants.kt
index 56a81a0a73..15fed21943 100644
--- a/tests/integration-tests/src/test/kotlin/common/TestConstants.kt
+++ b/tests/integration-tests/src/test/kotlin/common/TestConstants.kt
@@ -18,18 +18,6 @@ object TestConstants {
),
)
- val PRISM_DID_AUTH_KEY = ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION)
- val PRISM_DID_SERVICE_FOR_UPDATE = Service(
- "https://update.com",
- listOf("LinkedDomains"),
- Json("https://update.com/"),
- )
- val PRISM_DID_UPDATE_NEW_SERVICE_URL = "https://bar.foo.com/"
- val PRISM_DID_UPDATE_NEW_SERVICE = Service(
- "https://new.service.com",
- listOf("LinkedDomains"),
- Json("https://new.service.com/"),
- )
val EVENT_TYPE_CONNECTION_UPDATED = "ConnectionUpdated"
val EVENT_TYPE_ISSUE_CREDENTIAL_RECORD_UPDATED = "IssueCredentialRecordUpdated"
val EVENT_TYPE_PRESENTATION_UPDATED = "PresentationUpdated"
diff --git a/tests/integration-tests/src/test/kotlin/common/VerifiableJwt.kt b/tests/integration-tests/src/test/kotlin/common/VerifiableJwt.kt
new file mode 100644
index 0000000000..4aa5d2483e
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/common/VerifiableJwt.kt
@@ -0,0 +1,132 @@
+package common
+
+import com.google.gson.JsonArray
+import com.google.gson.JsonObject
+import com.google.gson.annotations.SerializedName
+import com.google.gson.reflect.TypeToken
+import io.iohk.atala.automation.restassured.CustomGsonObjectMapperFactory
+import models.JwtCredential
+import java.time.OffsetDateTime
+
+object VerifiableJwt {
+ fun schemaVCv1(): JwtCredential {
+ val favoriteColorEnum = JsonArray()
+ listOf("red", "orange", "green", "blue", "yellow", "purple").forEach {
+ favoriteColorEnum.add(it)
+ }
+
+ val favoriteColor = JsonObject()
+ favoriteColor.addProperty("type", "string")
+ favoriteColor.add("enum", favoriteColorEnum)
+
+ val jsonSchemaPropertiesCredentialSubjectProperties = JsonObject()
+ jsonSchemaPropertiesCredentialSubjectProperties.add("favoriteColor", favoriteColor)
+
+ val required = JsonArray()
+ required.add("favoriteColor")
+
+ val jsonSchemaPropertiesCredentialSubject = JsonObject()
+ jsonSchemaPropertiesCredentialSubject.addProperty("type", "object")
+ jsonSchemaPropertiesCredentialSubject.add("properties", jsonSchemaPropertiesCredentialSubjectProperties)
+ jsonSchemaPropertiesCredentialSubject.add("required", required)
+
+ val jsonSchemaProperties = JsonObject()
+ jsonSchemaProperties.add("credentialSubject", jsonSchemaPropertiesCredentialSubject)
+
+ val jsonSchema = JsonObject()
+ jsonSchema.addProperty("${"$"}id", "https://example.com/schemas/favorite-color-schema.json")
+ jsonSchema.addProperty("${"$"}schema", "https://json-schema.org/draft/2020-12/schema")
+ jsonSchema.addProperty("title", "Favorite Color Schema")
+ jsonSchema.addProperty("description", "Favorite Color using JsonSchemaCredential")
+ jsonSchema.addProperty("type", "object")
+ jsonSchema.add("properties", jsonSchemaProperties)
+
+ val credentialSubject = JsonObject()
+ credentialSubject.addProperty("id", "https://example.com/schemas/favorite-color-schema.json")
+ credentialSubject.addProperty("type", "JsonSchema")
+ credentialSubject.add("jsonSchema", jsonSchema)
+
+ val credentialSchema = JsonObject()
+ credentialSchema.addProperty(
+ "id",
+ "https://www.w3.org/2022/credentials/v2/json-schema-credential-schema.json",
+ )
+ credentialSchema.addProperty("type", "JsonSchema")
+ credentialSchema.addProperty(
+ "digestSRI",
+ "sha384-S57yQDg1MTzF56Oi9DbSQ14u7jBy0RDdx0YbeV7shwhCS88G8SCXeFq82PafhCrW",
+ )
+
+ val verifiableSchema = VerifiableSchemaV1(
+ credentialSubject = credentialSubject,
+ credentialSchema = credentialSchema,
+ type = listOf("VerifiableCredential", "JsonSchemaCredential"),
+ context = listOf("https://www.w3.org/ns/credentials/v2", "https://www.w3.org/ns/credentials/examples/v2"),
+ id = "https://example.com/credentials/3734",
+ issuer = "https://example.com/issuers/14",
+ issuanceDate = OffsetDateTime.now(),
+ )
+
+ val typeToken = object : TypeToken