From 02f6f3fbb339a6661c40a5f2516139f61fe98851 Mon Sep 17 00:00:00 2001 From: Enrico Risa Date: Fri, 6 Dec 2024 16:29:28 +0100 Subject: [PATCH] feat: replace @type with type and roundTrip testing (#114) --- ...ery-message-w-presentation-definition.json | 34 +++++ .../context/fixtures/AbstractJsonLdTest.java | 133 ++++++++++++++++++ .../context/issuance/IssuanceContextTest.java | 67 +++++++++ .../presentation/PresentationContextTest.java | 37 +++++ .../schema/fixtures/AbstractSchemaTest.java | 2 +- .../verifiable.presentation.protocol.md | 14 +- 6 files changed, 275 insertions(+), 12 deletions(-) create mode 100644 artifacts/src/main/resources/presentation/example/presentation-query-message-w-presentation-definition.json create mode 100644 artifacts/src/test/java/org/eclipse/dcp/context/fixtures/AbstractJsonLdTest.java create mode 100644 artifacts/src/test/java/org/eclipse/dcp/context/issuance/IssuanceContextTest.java create mode 100644 artifacts/src/test/java/org/eclipse/dcp/context/presentation/PresentationContextTest.java diff --git a/artifacts/src/main/resources/presentation/example/presentation-query-message-w-presentation-definition.json b/artifacts/src/main/resources/presentation/example/presentation-query-message-w-presentation-definition.json new file mode 100644 index 0000000..fce9a02 --- /dev/null +++ b/artifacts/src/main/resources/presentation/example/presentation-query-message-w-presentation-definition.json @@ -0,0 +1,34 @@ +{ + "@context": [ + "https://w3id.org/dspace-dcp/v0.8" + ], + "type": "PresentationQueryMessage", + "presentationDefinition": { + "id": "presentation1", + "input_descriptors": [ + { + "id": "organization credential", + "format": { + "ldp_vc": { + "proof_type": [ + "Ed25519Signature2018" + ] + } + }, + "constraints": { + "fields": [ + { + "path": [ + "$.type" + ], + "filter": { + "type": "string", + "pattern": "OrganizationCredential" + } + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/artifacts/src/test/java/org/eclipse/dcp/context/fixtures/AbstractJsonLdTest.java b/artifacts/src/test/java/org/eclipse/dcp/context/fixtures/AbstractJsonLdTest.java new file mode 100644 index 0000000..dd6ce10 --- /dev/null +++ b/artifacts/src/test/java/org/eclipse/dcp/context/fixtures/AbstractJsonLdTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.dcp.context.fixtures; + +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.JsonLdOptions; +import com.apicatalog.jsonld.document.Document; +import com.apicatalog.jsonld.document.JsonDocument; +import com.apicatalog.jsonld.loader.DocumentLoader; +import com.apicatalog.jsonld.loader.DocumentLoaderOptions; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsonp.JSONPModule; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SchemaLocation; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonStructure; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import static com.apicatalog.jsonld.JsonLd.compact; +import static com.apicatalog.jsonld.JsonLd.expand; +import static com.apicatalog.jsonld.lang.Keywords.CONTEXT; +import static com.networknt.schema.SpecVersion.VersionFlag.V202012; +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.dcp.schema.SchemaConstants.DCP_CONTEXT; +import static org.eclipse.dcp.schema.SchemaConstants.DCP_PREFIX; +import static org.eclipse.dcp.schema.fixtures.AbstractSchemaTest.PRESENTATION_EXCHANGE_PREFIX; + +/** + * Base class for Json-Ld expansion and compaction tests. + */ +public abstract class AbstractJsonLdTest { + private static final String CLASSPATH_SCHEMA = "classpath:/"; + private static final String CONTEXT_REFERENCE = format("{\"@context\": [\"%s\"]}", DCP_CONTEXT); + private final Map contextMap = Map.of( + DCP_CONTEXT, "/context/dcp.jsonld", + "https://www.w3.org/ns/odrl.jsonld", "/context/odrl.jsonld", + "https://www.w3.org/2018/credentials/v1", "/context/credentials.jsonld", + "https://www.w3.org/2018/credentials/examples/v1", "/context/credentials-examples.jsonld" + ); + protected ObjectMapper mapper; + protected JsonStructure compactionContext; + protected JsonLdOptions options; + + @BeforeEach + void setUp() { + mapper = new ObjectMapper(); + mapper.registerModule(new JSONPModule()); + + Map cache = new HashMap<>(); + + contextMap.forEach((key, value) -> { + try (var stream = getClass().getResourceAsStream(value)) { + var context = mapper.readValue(stream, JsonObject.class); + cache.put(key, JsonDocument.of(context)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + try { + var documentLoader = new LocalDocumentLoader(cache); + compactionContext = mapper.readValue(CONTEXT_REFERENCE, JsonStructure.class); + options = new JsonLdOptions(); + options.setDocumentLoader(documentLoader); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + protected void verifyRoundTrip(String jsonFile, String schemaFile) { + try { + var stream = getClass().getResourceAsStream(jsonFile); + var message = mapper.readValue(stream, JsonObject.class); + verifyRoundTrip(message, schemaFile); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + protected void verifyRoundTrip(JsonObject message, String schemaFile) { + try { + + var context = Json.createObjectBuilder().add(CONTEXT, message.get(CONTEXT)).build(); + var expanded = expand(JsonDocument.of(message)).options(options).get(); + var compacted = compact(JsonDocument.of(expanded), JsonDocument.of(context)).options(options).get(); + + var schemaFactory = JsonSchemaFactory.getInstance(V202012, builder -> + builder.schemaMappers(schemaMappers -> schemaMappers.mapPrefix(DCP_PREFIX, CLASSPATH_SCHEMA) + .mapPrefix(PRESENTATION_EXCHANGE_PREFIX, CLASSPATH_SCHEMA)) + ); + + var schema = schemaFactory.getSchema(SchemaLocation.of(DCP_PREFIX + schemaFile)); + var result = schema.validate(mapper.convertValue(compacted, JsonNode.class)); + assertThat(result.isEmpty()).isTrue(); + assertThat(compacted).isEqualTo(message); + } catch (JsonLdError e) { + throw new RuntimeException(e); + } + } + + private static class LocalDocumentLoader implements DocumentLoader { + private final Map contexts = new HashMap<>(); + + LocalDocumentLoader(Map contexts) { + this.contexts.putAll(contexts); + } + + @Override + public Document loadDocument(URI url, DocumentLoaderOptions options) { + return contexts.get(url.toString()); + } + } +} \ No newline at end of file diff --git a/artifacts/src/test/java/org/eclipse/dcp/context/issuance/IssuanceContextTest.java b/artifacts/src/test/java/org/eclipse/dcp/context/issuance/IssuanceContextTest.java new file mode 100644 index 0000000..7ef604e --- /dev/null +++ b/artifacts/src/test/java/org/eclipse/dcp/context/issuance/IssuanceContextTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.dcp.context.issuance; + +import jakarta.json.JsonObject; +import org.eclipse.dcp.context.fixtures.AbstractJsonLdTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; + +public class IssuanceContextTest extends AbstractJsonLdTest { + + @Test + void verifyCredentialRequestMessage() { + verifyRoundTrip("/issuance/example/credential-request-message.json", "/issuance/credential-request-message-schema.json"); + } + + @Test + void verifyCredentialOfferMessage() { + verifyRoundTrip("/issuance/example/credential-offer-message.json", "/issuance/credential-offer-message-schema.json"); + } + + @Test + void verifyCredentialObject() { + verifyRoundTrip("/issuance/example/credential-object.json", "/issuance/credential-object-schema.json"); + } + + @Test + void verifyIssuerMetadata() { + verifyRoundTrip("/issuance/example/issuer-metadata.json", "/issuance/issuer-metadata-schema.json"); + } + + @Test + void verifyCredentialStatus() { + verifyRoundTrip("/issuance/example/credential-status.json", "/issuance/credential-status-schema.json"); + } + + @ParameterizedTest + @ValueSource(strings = { "RECEIVED", "REJECTED", "ISSUED" }) + void verifyCredentialStatus_withStatus(String status) throws IOException { + var msg = """ + { + "@context": ["https://w3id.org/dspace-dcp/v0.8"], + "type": "CredentialStatus", + "requestId": "requestId", + "status": "%s" + }""".formatted(status); + + var message = mapper.readValue(msg, JsonObject.class); + verifyRoundTrip(message, "/issuance/credential-status-schema.json"); + } + +} \ No newline at end of file diff --git a/artifacts/src/test/java/org/eclipse/dcp/context/presentation/PresentationContextTest.java b/artifacts/src/test/java/org/eclipse/dcp/context/presentation/PresentationContextTest.java new file mode 100644 index 0000000..d12a7f8 --- /dev/null +++ b/artifacts/src/test/java/org/eclipse/dcp/context/presentation/PresentationContextTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.dcp.context.presentation; + +import org.eclipse.dcp.context.fixtures.AbstractJsonLdTest; +import org.junit.jupiter.api.Test; + +public class PresentationContextTest extends AbstractJsonLdTest { + + @Test + void verifyPresentationQueryMessage() { + verifyRoundTrip("/presentation/example/presentation-query-message.json", "/presentation/presentation-query-message-schema.json"); + } + + @Test + void verifyPresentationQueryMessage_withPresentationDefinition() { + verifyRoundTrip("/presentation/example/presentation-query-message-w-presentation-definition.json", "/presentation/presentation-query-message-schema.json"); + } + + @Test + void verifyPresentationResponseMessage() { + verifyRoundTrip("/presentation/example/presentation-response-message.json", "/presentation/presentation-response-message-schema.json"); + } + +} \ No newline at end of file diff --git a/artifacts/src/test/java/org/eclipse/dcp/schema/fixtures/AbstractSchemaTest.java b/artifacts/src/test/java/org/eclipse/dcp/schema/fixtures/AbstractSchemaTest.java index 4d97ad4..f0c4b8a 100644 --- a/artifacts/src/test/java/org/eclipse/dcp/schema/fixtures/AbstractSchemaTest.java +++ b/artifacts/src/test/java/org/eclipse/dcp/schema/fixtures/AbstractSchemaTest.java @@ -26,12 +26,12 @@ * Base test class. */ public abstract class AbstractSchemaTest { + public static final String PRESENTATION_EXCHANGE_PREFIX = "https://identity.foundation/"; protected static final String MIN_CONTAINS = "minContains"; protected static final String REQUIRED = "required"; protected static final String ONE_OF = "oneOf"; protected static final String TYPE = "type"; protected static final String ENUM = "enum"; - protected static final String PRESENTATION_EXCHANGE_PREFIX = "https://identity.foundation/"; private static final String CLASSPATH_SCHEMA = "classpath:/"; protected JsonSchema schema; diff --git a/specifications/verifiable.presentation.protocol.md b/specifications/verifiable.presentation.protocol.md index a30cb31..7ae2f2e 100644 --- a/specifications/verifiable.presentation.protocol.md +++ b/specifications/verifiable.presentation.protocol.md @@ -101,7 +101,7 @@ exact error code is implementation-specific. |--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Schema** | [JSON Schema](./resources/v0.8/presentation/presentation-query-message-schema.json) | | **Required** | - `@context`: Specifies a valid Json-Ld context ([[json-ld11]], sect. 3.1) | -| | - `@type`: A string specifying the `PresentationQueryMessage` type. | +| | - `type`: A string specifying the `PresentationQueryMessage` type. | | **Optional** | - `scope`: An array of scopes corresponding to Section [[[#scopes]]]. | | | - `presentationDefinition`: A valid `Presentation Definition` according to the [Presentation Exchange Specification](https://identity.foundation/presentation-exchange/spec/v2.0.0/#presentation-definition). | @@ -116,15 +116,7 @@ The following are non-normative examples of the JSON body: @@ -174,7 +166,7 @@ Verifiable Credential. |--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Schema** | [JSON Schema](./resources/v0.8/presentation/presentation-response-message-schema.json) | | **Required** | - `@context`: Specifies a valid Json-Ld context ([[json-ld11]], sect. 3.1). | -| | - `@type`: A string specifying the `PresentationResponseMessage` type. | +| | - `type`: A string specifying the `PresentationResponseMessage` type. | | | - `presentation`: An array of [=Verifiable Presentations=]. The [=Verifiable Presentations=] may be strings, JSON objects, or a combination of both depending on the format.
| The following are non-normative examples of the JSON response body: