diff --git a/core/common/util/src/main/java/org/eclipse/edc/util/reflection/ReflectionUtil.java b/core/common/util/src/main/java/org/eclipse/edc/util/reflection/ReflectionUtil.java index 68facfb6885..076f7425467 100644 --- a/core/common/util/src/main/java/org/eclipse/edc/util/reflection/ReflectionUtil.java +++ b/core/common/util/src/main/java/org/eclipse/edc/util/reflection/ReflectionUtil.java @@ -68,7 +68,7 @@ private static T getFieldValue(List path, Object object) { var closingBracketIx = first.toString().indexOf(CLOSING_BRACKET); var propName = first.toString().substring(0, openingBracketIx); var arrayIndex = Integer.parseInt(first.toString().substring(openingBracketIx + 1, closingBracketIx)); - var iterableObject = (List) getFieldValue(propName, object); + var iterableObject = (List) getFieldValue("'%s'".formatted(propName), object); return (T) iterableObject.get(arrayIndex); } else { if (object instanceof Map map) { diff --git a/core/common/util/src/test/java/org/eclipse/edc/util/reflection/ReflectionUtilTest.java b/core/common/util/src/test/java/org/eclipse/edc/util/reflection/ReflectionUtilTest.java index c054e26c14d..ea539225f42 100644 --- a/core/common/util/src/test/java/org/eclipse/edc/util/reflection/ReflectionUtilTest.java +++ b/core/common/util/src/test/java/org/eclipse/edc/util/reflection/ReflectionUtilTest.java @@ -135,6 +135,14 @@ void shouldGetNestedValue_whenKeyContainsDot() { assertThat(value).isInstanceOf(String.class).isEqualTo("value"); } + @Test + void shouldMapValueFromList() { + var object = Map.of("http://namespace.domain/property", List.of(Map.of("@value", "value"))); + + var value = ReflectionUtil.getFieldValue("'http://namespace.domain/property'[0].@value", object); + + assertThat(value).isInstanceOf(String.class).isEqualTo("value"); + } } @Nested diff --git a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/service/asset/AssetQueryValidator.java b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/service/asset/AssetQueryValidator.java index 6480e38f32c..df9f6aa4f6c 100644 --- a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/service/asset/AssetQueryValidator.java +++ b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/service/asset/AssetQueryValidator.java @@ -23,7 +23,7 @@ import static java.lang.String.format; class AssetQueryValidator extends QueryValidator { - private static final Pattern VALID_QUERY_PATH_REGEX = Pattern.compile("^[A-Za-z_]+.*$"); + private static final Pattern VALID_QUERY_PATH_REGEX = Pattern.compile("^[A-Za-z_']+.*$"); AssetQueryValidator() { super(Asset.class); diff --git a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/service/query/QueryValidator.java b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/service/query/QueryValidator.java index 894b7fab170..a6f17126bc4 100644 --- a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/service/query/QueryValidator.java +++ b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/service/query/QueryValidator.java @@ -84,7 +84,7 @@ protected Result isValid(String path) { // cannot query on extensible (=Map) types if (type == Map.class) { - var pattern = Pattern.compile("^[0-9A-Za-z.':/]*$"); + var pattern = Pattern.compile("^[0-9A-Za-z.':/@]*$"); var matcher = pattern.matcher(path); return matcher.find() ? Result.success() : Result.failure("Querying Map types is not yet supported"); @@ -93,7 +93,7 @@ protected Result isValid(String path) { if (field != null) { type = field.getType(); if (Collection.class.isAssignableFrom(type)) { - ParameterizedType genericType = (ParameterizedType) field.getGenericType(); + var genericType = (ParameterizedType) field.getGenericType(); type = (Class) genericType.getActualTypeArguments()[0]; } } else { diff --git a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/asset/AssetQueryValidatorTest.java b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/asset/AssetQueryValidatorTest.java index 4febfd093c4..8e04f73055b 100644 --- a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/asset/AssetQueryValidatorTest.java +++ b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/asset/AssetQueryValidatorTest.java @@ -38,10 +38,11 @@ class AssetQueryValidatorTest { Asset.PROPERTY_CONTENT_TYPE, "someCustomVal", "_anotherValidVal", - + "'http://some.url/property'.nestedvalue" }) void validate_validProperty(String key) { var query = QuerySpec.Builder.newInstance().filter(List.of(new Criterion(key, "=", "someval"))).build(); + assertThat(validator.validate(query).succeeded()).isTrue(); } diff --git a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/query/QueryValidatorTest.java b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/query/QueryValidatorTest.java index 8cc6226eb3a..7d27710976a 100644 --- a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/query/QueryValidatorTest.java +++ b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/service/query/QueryValidatorTest.java @@ -16,7 +16,6 @@ import org.eclipse.edc.spi.query.Criterion; import org.eclipse.edc.spi.query.QuerySpec; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; @@ -34,10 +33,6 @@ class QueryValidatorTest { - @BeforeEach - void setUp() { - } - @Test void validate_isValid() { var queryValidator = new QueryValidator(TestObject.class); @@ -89,6 +84,16 @@ void validate_isMapTypeTrue() { assertThat(result.succeeded()).isTrue(); } + @Test + void shouldPermitQueryToJsonLdTags() { + var queryValidator = new QueryValidator(TestObject.class); + var query = with(criterion("someMap.foo.@id", "=", "bar")); + + var result = queryValidator.validate(query); + + assertThat(result.succeeded()).isTrue(); + } + @Test void validate_isMapTypeFalse() { var queryValidator = new QueryValidator(TestObject.class); diff --git a/extensions/common/sql/sql-core/build.gradle.kts b/extensions/common/sql/sql-core/build.gradle.kts index cf8ba2ab73e..47d32cd8346 100644 --- a/extensions/common/sql/sql-core/build.gradle.kts +++ b/extensions/common/sql/sql-core/build.gradle.kts @@ -27,10 +27,11 @@ dependencies { testImplementation(project(":core:common:junit")) - testFixturesImplementation(libs.postgres) - testFixturesImplementation(libs.junit.jupiter.api) testFixturesImplementation(project(":spi:common:transaction-datasource-spi")) + testFixturesImplementation(project(":core:common:junit")) + testFixturesImplementation(libs.junit.jupiter.api) testFixturesImplementation(libs.mockito.core) + testFixturesImplementation(libs.postgres) testFixturesImplementation(libs.testcontainers.junit) testFixturesImplementation(libs.testcontainers.postgres) diff --git a/extensions/common/sql/sql-core/src/testFixtures/java/org/eclipse/edc/sql/testfixtures/PostgresqlEndToEndInstance.java b/extensions/common/sql/sql-core/src/testFixtures/java/org/eclipse/edc/sql/testfixtures/PostgresqlEndToEndInstance.java new file mode 100644 index 00000000000..c8e449dd387 --- /dev/null +++ b/extensions/common/sql/sql-core/src/testFixtures/java/org/eclipse/edc/sql/testfixtures/PostgresqlEndToEndInstance.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.sql.testfixtures; + +import org.eclipse.edc.junit.testfixtures.TestUtils; +import org.eclipse.edc.spi.persistence.EdcPersistenceException; + +import java.io.IOException; +import java.nio.file.Files; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +public interface PostgresqlEndToEndInstance { + + String USER = "postgres"; + String PASSWORD = "password"; + String JDBC_URL_PREFIX = "jdbc:postgresql://localhost:5432/"; + + static void createDatabase(String participantName) { + try { + Class.forName("org.postgresql.Driver"); + } catch (ClassNotFoundException e) { + throw new EdcPersistenceException(e); + } + + var postgres = new PostgresqlLocalInstance(USER, PASSWORD, JDBC_URL_PREFIX, participantName); + postgres.createDatabase(); + + var extensionsFolder = TestUtils.findBuildRoot().toPath().resolve("extensions"); + var scripts = Stream.of( + "control-plane/store/sql/asset-index-sql", + "control-plane/store/sql/contract-definition-store-sql", + "control-plane/store/sql/contract-negotiation-store-sql", + "control-plane/store/sql/policy-definition-store-sql", + "control-plane/store/sql/transfer-process-store-sql", + "data-plane/store/sql/data-plane-store-sql", + "policy-monitor/store/sql/policy-monitor-store-sql" + ) + .map(extensionsFolder::resolve) + .map(it -> it.resolve("docs")) + .map(it -> it.resolve("schema.sql")) + .toList(); + + try (var connection = postgres.getConnection(participantName)) { + for (var script : scripts) { + var sql = Files.readString(script); + + try (var statement = connection.createStatement()) { + statement.execute(sql); + } catch (Exception exception) { + throw new EdcPersistenceException(exception.getMessage(), exception); + } + } + } catch (SQLException | IOException e) { + throw new EdcPersistenceException(e); + } + } + + static Map defaultDatasourceConfiguration(String name) { + return new HashMap<>() { + { + put("edc.datasource.default.url", JDBC_URL_PREFIX + name); + put("edc.datasource.default.user", USER); + put("edc.datasource.default.password", PASSWORD); + } + }; + } + +} diff --git a/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/EndToEndTransferPostgresqlTest.java b/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/EndToEndTransferPostgresqlTest.java index e0145b880c5..7b3ca21de9a 100644 --- a/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/EndToEndTransferPostgresqlTest.java +++ b/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/EndToEndTransferPostgresqlTest.java @@ -22,15 +22,15 @@ import java.util.HashMap; -import static org.eclipse.edc.test.e2e.PostgresUtil.createDatabase; +import static org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndInstance.createDatabase; @PostgresqlDbIntegrationTest class EndToEndTransferPostgresqlTest extends AbstractEndToEndTransfer { @RegisterExtension static BeforeAllCallback createDatabase = context -> { - createDatabase(CONSUMER); - createDatabase(PROVIDER); + createDatabase(CONSUMER.getName()); + createDatabase(PROVIDER.getName()); }; static String[] controlPlanePostgresqlModules = new String[] { diff --git a/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/PostgresConstants.java b/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/PostgresConstants.java deleted file mode 100644 index e4de1505587..00000000000 --- a/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/PostgresConstants.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.test.e2e; - -public interface PostgresConstants { - String USER = "postgres"; - String PASSWORD = "password"; - String JDBC_URL_PREFIX = "jdbc:postgresql://localhost:5432/"; -} diff --git a/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/PostgresUtil.java b/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/PostgresUtil.java deleted file mode 100644 index 43123a427cf..00000000000 --- a/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/PostgresUtil.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.test.e2e; - -import org.eclipse.edc.spi.persistence.EdcPersistenceException; -import org.eclipse.edc.sql.testfixtures.PostgresqlLocalInstance; -import org.eclipse.edc.test.e2e.participant.EndToEndTransferParticipant; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.sql.SQLException; -import java.util.stream.Stream; - -import static org.eclipse.edc.test.e2e.PostgresConstants.JDBC_URL_PREFIX; -import static org.eclipse.edc.test.e2e.PostgresConstants.PASSWORD; -import static org.eclipse.edc.test.e2e.PostgresConstants.USER; - -public class PostgresUtil { - - public static void createDatabase(EndToEndTransferParticipant participant) throws ClassNotFoundException, SQLException, IOException { - Class.forName("org.postgresql.Driver"); - - var postgres = new PostgresqlLocalInstance(USER, PASSWORD, JDBC_URL_PREFIX, participant.getName()); - postgres.createDatabase(); - - var scripts = Stream.of( - "extensions/control-plane/store/sql/asset-index-sql", - "extensions/control-plane/store/sql/contract-definition-store-sql", - "extensions/control-plane/store/sql/contract-negotiation-store-sql", - "extensions/control-plane/store/sql/policy-definition-store-sql", - "extensions/control-plane/store/sql/transfer-process-store-sql", - "extensions/data-plane/store/sql/data-plane-store-sql", - "extensions/policy-monitor/store/sql/policy-monitor-store-sql" - ) - .map("../../../%s/docs/schema.sql"::formatted) - .map(Paths::get) - .toList(); - - try (var connection = postgres.getConnection(participant.getName())) { - for (var script : scripts) { - var sql = Files.readString(script); - - try (var statement = connection.createStatement()) { - statement.execute(sql); - } catch (Exception exception) { - throw new EdcPersistenceException(exception.getMessage(), exception); - } - } - } - } -} diff --git a/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/participant/EndToEndTransferParticipant.java b/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/participant/EndToEndTransferParticipant.java index bc542837e04..a9f6ba8ce85 100644 --- a/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/participant/EndToEndTransferParticipant.java +++ b/system-tests/e2e-transfer-test/runner/src/test/java/org/eclipse/edc/test/e2e/participant/EndToEndTransferParticipant.java @@ -19,7 +19,6 @@ import jakarta.json.Json; import jakarta.json.JsonObject; import org.eclipse.edc.spi.types.domain.edr.EndpointDataReference; -import org.eclipse.edc.test.e2e.PostgresConstants; import org.eclipse.edc.test.system.utils.Participant; import org.hamcrest.Matcher; import org.jetbrains.annotations.NotNull; @@ -45,6 +44,7 @@ import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; import static org.eclipse.edc.spi.CoreConstants.EDC_PREFIX; import static org.eclipse.edc.spi.system.ServiceExtensionContext.PARTICIPANT_ID; +import static org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndInstance.defaultDatasourceConfiguration; public class EndToEndTransferParticipant extends Participant { @@ -208,24 +208,10 @@ public Map controlPlaneConfiguration() { public Map controlPlanePostgresConfiguration() { var baseConfiguration = controlPlaneConfiguration(); - - var postgresConfiguration = new HashMap() { - { - put("edc.datasource.default.url", jdbcUrl()); - put("edc.datasource.default.user", PostgresConstants.USER); - put("edc.datasource.default.password", PostgresConstants.PASSWORD); - } - }; - baseConfiguration.putAll(postgresConfiguration); - + baseConfiguration.putAll(defaultDatasourceConfiguration(getName())); return baseConfiguration; } - @NotNull - public String jdbcUrl() { - return PostgresConstants.JDBC_URL_PREFIX + getName(); - } - public Map dataPlaneConfiguration() { return new HashMap<>() { { @@ -246,17 +232,7 @@ public Map dataPlaneConfiguration() { public Map dataPlanePostgresConfiguration() { var baseConfiguration = dataPlaneConfiguration(); - - var postgresConfiguration = new HashMap() { - { - put("edc.datasource.default.url", jdbcUrl()); - put("edc.datasource.default.user", PostgresConstants.USER); - put("edc.datasource.default.password", PostgresConstants.PASSWORD); - } - }; - - baseConfiguration.putAll(postgresConfiguration); - + baseConfiguration.putAll(defaultDatasourceConfiguration(getName())); return baseConfiguration; } diff --git a/system-tests/management-api/management-api-test-runner/build.gradle.kts b/system-tests/management-api/management-api-test-runner/build.gradle.kts index 5855ea63a4a..0974ad8af90 100644 --- a/system-tests/management-api/management-api-test-runner/build.gradle.kts +++ b/system-tests/management-api/management-api-test-runner/build.gradle.kts @@ -35,6 +35,9 @@ dependencies { testImplementation(libs.awaitility) testImplementation(libs.junit.jupiter.api) testImplementation(libs.jakartaJson) + testImplementation(testFixtures(project(":extensions:common:sql:sql-core"))) + testImplementation(libs.testcontainers.junit) + testImplementation(libs.testcontainers.postgres) testCompileOnly(project(":system-tests:management-api:management-api-test-runtime")) } diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/AssetApiEndToEndTest.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/AssetApiEndToEndTest.java index 3c9c8132a39..8da493c24b9 100644 --- a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/AssetApiEndToEndTest.java +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/AssetApiEndToEndTest.java @@ -15,286 +15,321 @@ package org.eclipse.edc.test.e2e.managementapi; import io.restassured.http.ContentType; -import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.junit.annotations.PostgresqlDbIntegrationTest; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; import org.eclipse.edc.spi.asset.AssetIndex; import org.eclipse.edc.spi.types.domain.DataAddress; import org.eclipse.edc.spi.types.domain.asset.Asset; +import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndInstance; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.util.List; -import java.util.Map; +import java.util.UUID; import static jakarta.json.Json.createArrayBuilder; import static jakarta.json.Json.createObjectBuilder; -import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; import static org.eclipse.edc.spi.CoreConstants.EDC_PREFIX; +import static org.eclipse.edc.spi.query.Criterion.criterion; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; /** * Asset V3 endpoints end-to-end tests */ -@EndToEndTest -public class AssetApiEndToEndTest extends BaseManagementApiEndToEndTest { - - private static final String TEST_ASSET_ID = "test-asset-id"; - private static final String TEST_ASSET_CONTENTTYPE = "application/json"; - private static final String TEST_ASSET_DESCRIPTION = "test description"; - private static final String TEST_ASSET_VERSION = "0.4.2"; - private static final String TEST_ASSET_NAME = "test-asset"; - - @Test - void getAssetById() { - getAssetIndex().create(createAsset().dataAddress(createDataAddress().type("addressType").build()).build()); - - var body = baseRequest() - .get("/v3/assets/" + TEST_ASSET_ID) - .then() - .statusCode(200) - .extract().body().jsonPath(); - - assertThat(body).isNotNull(); - assertThat(body.getString(ID)).isEqualTo(TEST_ASSET_ID); - assertThat(body.getMap("properties")) - .hasSize(5) - .containsEntry("name", TEST_ASSET_NAME) - .containsEntry("description", TEST_ASSET_DESCRIPTION) - .containsEntry("contenttype", TEST_ASSET_CONTENTTYPE) - .containsEntry("version", TEST_ASSET_VERSION); - assertThat(body.getMap("'dataAddress'")) - .containsEntry("type", "addressType"); - } - - @Test - void createAsset_shouldBeStored() { - var assetJson = createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, "Asset") - .add(ID, TEST_ASSET_ID) - .add("properties", createPropertiesBuilder().build()) - .add("dataAddress", createObjectBuilder() - .add(TYPE, "DataAddress") - .add("type", "test-type") - .build()) - .build(); - - baseRequest() - .contentType(ContentType.JSON) - .body(assetJson) - .post("/v3/assets") - .then() - .log().ifError() - .statusCode(200) - .body(ID, is("test-asset-id")); - - assertThat(getAssetIndex().countAssets(List.of())).isEqualTo(1); - assertThat(getAssetIndex().findById("test-asset-id")).isNotNull(); - } - - @Test - void createAsset_shouldFail_whenBodyIsNotValid() { - var assetJson = createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, "Asset") - .add(ID, " ") - .add("properties", createPropertiesBuilder().build()) - .build(); - - baseRequest() - .contentType(ContentType.JSON) - .body(assetJson) - .post("/v3/assets") - .then() - .log().ifError() - .statusCode(400); - - assertThat(getAssetIndex().countAssets(emptyList())).isEqualTo(0); - } - - @Test - void createAsset_withoutPrefix_shouldAddEdcNamespace() { - var assetJson = createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, "Asset") - .add(ID, TEST_ASSET_ID) - .add("properties", createPropertiesBuilder() - .add("unprefixed-key", "test-value").build()) - .add("dataAddress", createObjectBuilder() - .add(TYPE, "DataAddress") - .add("type", "test-type") - .add("unprefixed-key", "test-value") - .build()) - .build(); - - baseRequest() - .contentType(ContentType.JSON) - .body(assetJson) - .post("/v3/assets") - .then() - .log().ifError() - .statusCode(200) - .body(ID, is("test-asset-id")); - - assertThat(getAssetIndex().countAssets(List.of())).isEqualTo(1); - var asset = getAssetIndex().findById("test-asset-id"); - assertThat(asset).isNotNull(); - //make sure unprefixed keys are caught and prefixed with the EDC_NAMESPACE ns. - assertThat(asset.getProperties().keySet()) - .hasSize(6) - .allMatch(key -> key.startsWith(EDC_NAMESPACE)); - - var dataAddress = getAssetIndex().resolveForAsset(asset.getId()); - assertThat(dataAddress).isNotNull(); - assertThat(dataAddress.getProperties().keySet()) - .hasSize(2) - .allMatch(key -> key.startsWith(EDC_NAMESPACE)); - - } +public class AssetApiEndToEndTest { - @Test - void queryAsset_byContentType() { - //insert one asset into the index - var asset = Asset.Builder.newInstance().id("test-asset").contentType("application/octet-stream").dataAddress(createDataAddress().build()).build(); - getAssetIndex().create(asset); - - var query = createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add("filterExpression", createArrayBuilder() - .add(createObjectBuilder() - .add("operandLeft", EDC_NAMESPACE + "contenttype") - .add("operator", "=") - .add("operandRight", "application/octet-stream")) - ).build(); - - baseRequest() - .contentType(ContentType.JSON) - .body(query) - .post("/v3/assets/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(1)); - } - - @Test - void queryAsset_byCustomStringProperty() { - getAssetIndex().create(Asset.Builder.newInstance() - .id("test-asset") - .contentType("application/octet-stream") - .property("myProp", "myVal") - .dataAddress(createDataAddress().build()) - .build()); - - var query = createSingleFilterQuery("myProp", "=", "myVal"); - - baseRequest() - .contentType(ContentType.JSON) - .body(query) - .post("/v3/assets/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(1)); - } - - @Test - void queryAsset_byCustomComplexProperty() { - getAssetIndex().create(Asset.Builder.newInstance() - .id("test-asset") - .contentType("application/octet-stream") - .property("myProp", Map.of("description", "test desc", "number", 42)) - .dataAddress(createDataAddress().build()) - .build()); - - var query = createSingleFilterQuery("myProp.description", "=", "test desc"); - - baseRequest() - .contentType(ContentType.JSON) - .body(query) - .post("/v3/assets/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(1)); - } + @Nested + @EndToEndTest + class InMemory extends Tests implements InMemoryRuntime { - @Test - void updateAsset() { - var asset = createAsset(); - getAssetIndex().create(asset.build()); - - var assetJson = createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, "Asset") - .add(ID, TEST_ASSET_ID) - .add("properties", createPropertiesBuilder() - .add("some-new-property", "some-new-value").build()) - .add("dataAddress", createObjectBuilder() - .add("type", "addressType")) - .build(); - - baseRequest() - .contentType(ContentType.JSON) - .body(assetJson) - .put("/v3/assets") - .then() - .log().all() - .statusCode(204) - .body(notNullValue()); - - var dbAsset = getAssetIndex().findById(TEST_ASSET_ID); - assertThat(dbAsset).isNotNull(); - assertThat(dbAsset.getProperties()).containsEntry(EDC_NAMESPACE + "some-new-property", "some-new-value"); - assertThat(dbAsset.getDataAddress().getType()).isEqualTo("addressType"); + InMemory() { + super(RUNTIME); + } } - private AssetIndex getAssetIndex() { - return controlPlane.getContext().getService(AssetIndex.class); - } + @Nested + @PostgresqlDbIntegrationTest + class Postgres extends Tests implements PostgresRuntime { - private DataAddress.Builder createDataAddress() { - return DataAddress.Builder.newInstance().type("test-type"); - } + Postgres() { + super(RUNTIME); + } - private Asset.Builder createAsset() { - return Asset.Builder.newInstance() - .id(TEST_ASSET_ID) - .name(TEST_ASSET_NAME) - .description(TEST_ASSET_DESCRIPTION) - .contentType(TEST_ASSET_CONTENTTYPE) - .version(TEST_ASSET_VERSION) - .dataAddress(createDataAddress().build()); + @BeforeAll + static void beforeAll() { + PostgresqlEndToEndInstance.createDatabase("runtime"); + } } - private JsonObjectBuilder createPropertiesBuilder() { - return createObjectBuilder() - .add("name", TEST_ASSET_NAME) - .add("description", TEST_ASSET_DESCRIPTION) - .add("version", TEST_ASSET_VERSION) - .add("contentType", TEST_ASSET_CONTENTTYPE); - } + abstract static class Tests extends ManagementApiEndToEndTestBase { + + Tests(EdcRuntimeExtension runtime) { + super(runtime); + } + + @Test + void getAssetById() { + var id = UUID.randomUUID().toString(); + var asset = createAsset().id(id) + .dataAddress(createDataAddress().type("addressType").build()) + .build(); + getAssetIndex().create(asset); + + var body = baseRequest() + .get("/v3/assets/" + id) + .then() + .statusCode(200) + .extract().body().jsonPath(); + + assertThat(body).isNotNull(); + assertThat(body.getString(ID)).isEqualTo(id); + assertThat(body.getMap("properties")) + .hasSize(5) + .containsEntry("name", "test-asset") + .containsEntry("description", "test description") + .containsEntry("contenttype", "application/json") + .containsEntry("version", "0.4.2"); + assertThat(body.getMap("'dataAddress'")) + .containsEntry("type", "addressType"); + } + + @Test + void createAsset_shouldBeStored() { + var id = UUID.randomUUID().toString(); + var assetJson = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) + .add(TYPE, "Asset") + .add(ID, id) + .add("properties", createPropertiesBuilder().build()) + .add("dataAddress", createObjectBuilder() + .add(TYPE, "DataAddress") + .add("type", "test-type") + .build()) + .build(); + + baseRequest() + .contentType(ContentType.JSON) + .body(assetJson) + .post("/v3/assets") + .then() + .log().ifError() + .statusCode(200) + .body(ID, is(id)); + + assertThat(getAssetIndex().findById(id)).isNotNull(); + } + + @Test + void createAsset_shouldFail_whenBodyIsNotValid() { + var assetJson = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) + .add(TYPE, "Asset") + .add(ID, " ") + .add("properties", createPropertiesBuilder().build()) + .build(); + + baseRequest() + .contentType(ContentType.JSON) + .body(assetJson) + .post("/v3/assets") + .then() + .log().ifError() + .statusCode(400); + } + + @Test + void createAsset_withoutPrefix_shouldAddEdcNamespace() { + var id = UUID.randomUUID().toString(); + var assetJson = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) + .add(TYPE, "Asset") + .add(ID, id) + .add("properties", createPropertiesBuilder() + .add("unprefixed-key", "test-value").build()) + .add("dataAddress", createObjectBuilder() + .add(TYPE, "DataAddress") + .add("type", "test-type") + .add("unprefixed-key", "test-value") + .build()) + .build(); + + baseRequest() + .contentType(ContentType.JSON) + .body(assetJson) + .post("/v3/assets") + .then() + .log().ifError() + .statusCode(200) + .body(ID, is(id)); + + var asset = getAssetIndex().findById(id); + assertThat(asset).isNotNull(); + //make sure unprefixed keys are caught and prefixed with the EDC_NAMESPACE ns. + assertThat(asset.getProperties().keySet()) + .hasSize(6) + .allMatch(key -> key.startsWith(EDC_NAMESPACE)); + + var dataAddress = getAssetIndex().resolveForAsset(asset.getId()); + assertThat(dataAddress).isNotNull(); + assertThat(dataAddress.getProperties().keySet()) + .hasSize(2) + .allMatch(key -> key.startsWith(EDC_NAMESPACE)); + } + + @Test + void queryAsset_byContentType() { + //insert one asset into the index + var id = UUID.randomUUID().toString(); + var asset = Asset.Builder.newInstance().id(id).contentType("application/octet-stream").dataAddress(createDataAddress().build()).build(); + getAssetIndex().create(asset); + + var query = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) + .add("filterExpression", createArrayBuilder() + .add(createObjectBuilder() + .add("operandLeft", EDC_NAMESPACE + "id") + .add("operator", "=") + .add("operandRight", id)) + .add(createObjectBuilder() + .add("operandLeft", EDC_NAMESPACE + "contenttype") + .add("operator", "=") + .add("operandRight", "application/octet-stream")) + ).build(); + + baseRequest() + .contentType(ContentType.JSON) + .body(query) + .post("/v3/assets/request") + .then() + .log().ifError() + .statusCode(200) + .body("size()", is(1)); + } + + @Test + void queryAsset_byCustomStringProperty() { + getAssetIndex().create(Asset.Builder.newInstance() + .id("test-asset") + .contentType("application/octet-stream") + .property("myProp", "myVal") + .dataAddress(createDataAddress().build()) + .build()); + + baseRequest() + .contentType(ContentType.JSON) + .body(query(criterion("myProp", "=", "myVal"))) + .post("/v3/assets/request") + .then() + .log().ifError() + .statusCode(200) + .body("size()", is(1)); + } + + @Test + void queryAsset_byCustomComplexProperty() { + var id = UUID.randomUUID().toString(); + var assetJson = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) + .add(TYPE, "Asset") + .add(ID, id) + .add("properties", createPropertiesBuilder() + .add("nested", createPropertiesBuilder() + .add("@id", "test-nested-id"))) + .add("dataAddress", createObjectBuilder() + .add(TYPE, "DataAddress") + .add("type", "test-type") + .add("unprefixed-key", "test-value") + .build()) + .build(); + + baseRequest() + .contentType(ContentType.JSON) + .body(assetJson) + .post("/v3/assets") + .then() + .log().ifError() + .statusCode(200) + .body(ID, is(id)); + + var query = query( + criterion("'%sid".formatted(EDC_NAMESPACE), "=", id), + criterion("'%snested'.@id".formatted(EDC_NAMESPACE), "=", "test-nested-id") + ); + + baseRequest() + .contentType(ContentType.JSON) + .body(query) + .post("/v3/assets/request") + .then() + .log().ifError() + .statusCode(200) + .body("size()", is(1)); + } + + @Test + void updateAsset() { + var asset = createAsset().build(); + getAssetIndex().create(asset); + + var assetJson = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) + .add(TYPE, "Asset") + .add(ID, asset.getId()) + .add("properties", createPropertiesBuilder() + .add("some-new-property", "some-new-value").build()) + .add("dataAddress", createObjectBuilder() + .add("type", "addressType")) + .build(); + + baseRequest() + .contentType(ContentType.JSON) + .body(assetJson) + .put("/v3/assets") + .then() + .log().all() + .statusCode(204) + .body(notNullValue()); + + var dbAsset = getAssetIndex().findById(asset.getId()); + assertThat(dbAsset).isNotNull(); + assertThat(dbAsset.getProperties()).containsEntry(EDC_NAMESPACE + "some-new-property", "some-new-value"); + assertThat(dbAsset.getDataAddress().getType()).isEqualTo("addressType"); + } + + private AssetIndex getAssetIndex() { + return runtime.getContext().getService(AssetIndex.class); + } + + private DataAddress.Builder createDataAddress() { + return DataAddress.Builder.newInstance().type("test-type"); + } + + private Asset.Builder createAsset() { + return Asset.Builder.newInstance() + .id(UUID.randomUUID().toString()) + .name("test-asset") + .description("test description") + .contentType("application/json") + .version("0.4.2") + .dataAddress(createDataAddress().build()); + } + + private JsonObjectBuilder createPropertiesBuilder() { + return createObjectBuilder() + .add("name", "test-asset") + .add("description", "test description") + .add("version", "0.4.2") + .add("contentType", "application/json"); + } - private JsonObject createSingleFilterQuery(String leftOperand, String operator, String rightOperand) { - var criteria = createArrayBuilder() - .add(createObjectBuilder() - .add(TYPE, "Criterion") - .add("operandLeft", leftOperand) - .add("operator", operator) - .add("operandRight", rightOperand) - ); - - return createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, "QuerySpec") - .add("filterExpression", criteria) - .build(); } } diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/BaseManagementApiEndToEndTest.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/BaseManagementApiEndToEndTest.java deleted file mode 100644 index b63a4efe238..00000000000 --- a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/BaseManagementApiEndToEndTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.test.e2e.managementapi; - -import io.restassured.specification.RequestSpecification; -import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; -import org.junit.jupiter.api.extension.RegisterExtension; - -import java.util.HashMap; - -import static io.restassured.RestAssured.given; -import static org.eclipse.edc.junit.testfixtures.TestUtils.getFreePort; - -public abstract class BaseManagementApiEndToEndTest { - - public static final int PORT = getFreePort(); - public static final int PROTOCOL_PORT = getFreePort(); - public static final String BASE_MANAGEMENT_URL = "http://localhost:" + PORT + "/management"; - - @RegisterExtension - static EdcRuntimeExtension controlPlane = new EdcRuntimeExtension( - ":system-tests:management-api:management-api-test-runtime", - "control-plane", - new HashMap<>() { - { - put("web.http.path", "/"); - put("web.http.protocol.path", "/protocol"); - put("web.http.protocol.port", String.valueOf(PROTOCOL_PORT)); - put("edc.dsp.callback.address", "http://localhost:" + PROTOCOL_PORT + "/protocol"); - put("web.http.port", String.valueOf(getFreePort())); - put("web.http.management.path", "/management"); - put("web.http.management.port", String.valueOf(PORT)); - } - } - ); - - protected RequestSpecification baseRequest() { - return given() - .port(PORT) - .baseUri(BASE_MANAGEMENT_URL) - .when(); - } -} diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/CatalogApiEndToEndTest.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/CatalogApiEndToEndTest.java index 4736d7fd12d..54e6ce8f4d1 100644 --- a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/CatalogApiEndToEndTest.java +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/CatalogApiEndToEndTest.java @@ -19,10 +19,15 @@ import org.eclipse.edc.connector.policy.spi.PolicyDefinition; import org.eclipse.edc.connector.policy.spi.store.PolicyDefinitionStore; import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.junit.annotations.PostgresqlDbIntegrationTest; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.asset.AssetIndex; import org.eclipse.edc.spi.types.domain.DataAddress; import org.eclipse.edc.spi.types.domain.asset.Asset; +import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndInstance; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.util.UUID; @@ -37,116 +42,147 @@ import static org.eclipse.edc.spi.CoreConstants.EDC_PREFIX; import static org.hamcrest.Matchers.is; -@EndToEndTest -public class CatalogApiEndToEndTest extends BaseManagementApiEndToEndTest { - - // requests the catalog to itself, to save another connector. - private final String providerUrl = "http://localhost:" + PROTOCOL_PORT + "/protocol"; - - @Test - void requestCatalog_shouldReturnCatalog_withoutQuerySpec() { - var requestBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, "CatalogRequest") - .add("counterPartyAddress", providerUrl) - .add("protocol", "dataspace-protocol-http") - .build(); - - baseRequest() - .contentType(JSON) - .body(requestBody) - .post("/v2/catalog/request") - .then() - .log().ifError() - .statusCode(200) - .contentType(JSON) - .body(TYPE, is("dcat:Catalog")); - } +public class CatalogApiEndToEndTest { + + @Nested + @EndToEndTest + class InMemory extends Tests implements InMemoryRuntime { + + InMemory() { + super(RUNTIME); + } - @Test - void requestCatalog_shouldReturnCatalog_withQuerySpec() { - var assetIndex = controlPlane.getContext().getService(AssetIndex.class); - var policyDefinitionStore = controlPlane.getContext().getService(PolicyDefinitionStore.class); - var contractDefinitionStore = controlPlane.getContext().getService(ContractDefinitionStore.class); - - var policyId = UUID.randomUUID().toString(); - - var cd = ContractDefinition.Builder.newInstance() - .id(UUID.randomUUID().toString()) - .contractPolicyId(policyId) - .accessPolicyId(policyId) - .build(); - - var policy = Policy.Builder.newInstance() - .build(); - - policyDefinitionStore.create(PolicyDefinition.Builder.newInstance().id(policyId).policy(policy).build()); - contractDefinitionStore.save(cd); - - assetIndex.create(createAsset("id-1").build()); - assetIndex.create(createAsset("id-2").build()); - - var criteria = createArrayBuilder() - .add(createObjectBuilder() - .add(TYPE, "Criterion") - .add("operandLeft", EDC_NAMESPACE + "id") - .add("operator", "=") - .add("operandRight", "id-2") - .build() - ) - .build(); - - var querySpec = createObjectBuilder() - .add(TYPE, "QuerySpec") - .add("filterExpression", criteria) - .add("limit", 1); - - var requestBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, "CatalogRequest") - .add("counterPartyAddress", providerUrl) - .add("protocol", "dataspace-protocol-http") - .add("querySpec", querySpec) - .build(); - - baseRequest() - .contentType(JSON) - .body(requestBody) - .post("/v2/catalog/request") - .then() - .statusCode(200) - .contentType(JSON) - .body(TYPE, is("dcat:Catalog")) - .body("'dcat:dataset'.id", is("id-2")); } - @Test - void getDataset_shouldReturnDataset() { - var assetIndex = controlPlane.getContext().getService(AssetIndex.class); - assetIndex.create(createAsset("asset-id").build()); - var requestBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, "DatasetRequest") - .add(ID, "asset-id") - .add("counterPartyAddress", providerUrl) - .add("protocol", "dataspace-protocol-http") - .build(); - - baseRequest() - .contentType(JSON) - .body(requestBody) - .post("/v2/catalog/dataset/request") - .then() - .statusCode(200) - .contentType(JSON) - .body(ID, is("asset-id")) - .body(TYPE, is("dcat:Dataset")); + @Nested + @PostgresqlDbIntegrationTest + class Postgres extends Tests implements PostgresRuntime { + + Postgres() { + super(RUNTIME); + } + + @BeforeAll + static void beforeAll() { + PostgresqlEndToEndInstance.createDatabase("runtime"); + } + } - private Asset.Builder createAsset(String id) { - return Asset.Builder.newInstance() - .dataAddress(DataAddress.Builder.newInstance().type("test-type").build()) - .id(id); + abstract static class Tests extends ManagementApiEndToEndTestBase { + // requests the catalog to itself, to save another connector. + private final String providerUrl = "http://localhost:" + PROTOCOL_PORT + "/protocol"; + + Tests(EdcRuntimeExtension runtime) { + super(runtime); + } + + @Test + void requestCatalog_shouldReturnCatalog_withoutQuerySpec() { + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) + .add(TYPE, "CatalogRequest") + .add("counterPartyAddress", providerUrl) + .add("protocol", "dataspace-protocol-http") + .build(); + + baseRequest() + .contentType(JSON) + .body(requestBody) + .post("/v2/catalog/request") + .then() + .log().ifError() + .statusCode(200) + .contentType(JSON) + .body(TYPE, is("dcat:Catalog")); + } + + @Test + void requestCatalog_shouldReturnCatalog_withQuerySpec() { + var assetIndex = runtime.getContext().getService(AssetIndex.class); + var policyDefinitionStore = runtime.getContext().getService(PolicyDefinitionStore.class); + var contractDefinitionStore = runtime.getContext().getService(ContractDefinitionStore.class); + + var policyId = UUID.randomUUID().toString(); + + var cd = ContractDefinition.Builder.newInstance() + .id(UUID.randomUUID().toString()) + .contractPolicyId(policyId) + .accessPolicyId(policyId) + .build(); + + var policy = Policy.Builder.newInstance() + .build(); + + policyDefinitionStore.create(PolicyDefinition.Builder.newInstance().id(policyId).policy(policy).build()); + contractDefinitionStore.save(cd); + + assetIndex.create(createAsset("id-1").build()); + assetIndex.create(createAsset("id-2").build()); + + var criteria = createArrayBuilder() + .add(createObjectBuilder() + .add(TYPE, "Criterion") + .add("operandLeft", EDC_NAMESPACE + "id") + .add("operator", "=") + .add("operandRight", "id-2") + .build() + ) + .build(); + + var querySpec = createObjectBuilder() + .add(TYPE, "QuerySpec") + .add("filterExpression", criteria) + .add("limit", 1); + + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) + .add(TYPE, "CatalogRequest") + .add("counterPartyAddress", providerUrl) + .add("protocol", "dataspace-protocol-http") + .add("querySpec", querySpec) + .build(); + + baseRequest() + .contentType(JSON) + .body(requestBody) + .post("/v2/catalog/request") + .then() + .statusCode(200) + .contentType(JSON) + .body(TYPE, is("dcat:Catalog")) + .body("'dcat:dataset'.id", is("id-2")); + } + + @Test + void getDataset_shouldReturnDataset() { + var assetIndex = runtime.getContext().getService(AssetIndex.class); + assetIndex.create(createAsset("asset-id").build()); + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) + .add(TYPE, "DatasetRequest") + .add(ID, "asset-id") + .add("counterPartyAddress", providerUrl) + .add("protocol", "dataspace-protocol-http") + .build(); + + baseRequest() + .contentType(JSON) + .body(requestBody) + .post("/v2/catalog/dataset/request") + .then() + .statusCode(200) + .contentType(JSON) + .body(ID, is("asset-id")) + .body(TYPE, is("dcat:Dataset")); + } + + private Asset.Builder createAsset(String id) { + return Asset.Builder.newInstance() + .dataAddress(DataAddress.Builder.newInstance().type("test-type").build()) + .id(id); + } + } } diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractAgreementApiEndToEndTest.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractAgreementApiEndToEndTest.java index 62af35d27e9..53ce009a7e7 100644 --- a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractAgreementApiEndToEndTest.java +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractAgreementApiEndToEndTest.java @@ -17,10 +17,15 @@ import org.eclipse.edc.connector.contract.spi.negotiation.store.ContractNegotiationStore; import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.junit.annotations.PostgresqlDbIntegrationTest; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.types.domain.agreement.ContractAgreement; import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; import org.eclipse.edc.spi.types.domain.offer.ContractOffer; +import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndInstance; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.util.List; @@ -32,95 +37,125 @@ import static org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates.FINALIZED; import static org.hamcrest.Matchers.is; -@EndToEndTest -public class ContractAgreementApiEndToEndTest extends BaseManagementApiEndToEndTest { - - @Test - void getAll() { - var store = controlPlane.getContext().getService(ContractNegotiationStore.class); - store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); - store.save(createContractNegotiationBuilder("cn2").contractAgreement(createContractAgreement("cn2")).build()); - - var jsonPath = baseRequest() - .contentType(JSON) - .post("/v2/contractagreements/request") - .then() - .log().ifError() - .statusCode(200) - .contentType(JSON) - .body("size()", is(2)) - .extract().jsonPath(); - - assertThat(jsonPath.getString("[0].assetId")).isNotNull(); - assertThat(jsonPath.getString("[1].assetId")).isNotNull(); - assertThat(jsonPath.getString("[0].@id")).isIn("cn1", "cn2"); - assertThat(jsonPath.getString("[1].@id")).isIn("cn1", "cn2"); - } +public class ContractAgreementApiEndToEndTest { - @Test - void getById() { - var store = controlPlane.getContext().getService(ContractNegotiationStore.class); - store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); - - var json = baseRequest() - .contentType(JSON) - .get("/v2/contractagreements/cn1") - .then() - .statusCode(200) - .contentType(JSON) - .extract().jsonPath(); - - assertThat(json.getString("@id")).isEqualTo("cn1"); - assertThat(json.getString("assetId")).isNotNull(); - } + @Nested + @EndToEndTest + class InMemory extends Tests implements InMemoryRuntime { - @Test - void getNegotiationByAgreementId() { - var store = controlPlane.getContext().getService(ContractNegotiationStore.class); - store.save(createContractNegotiationBuilder("negotiation-id") - .contractAgreement(createContractAgreement("agreement-id")) - .build()); - - var json = baseRequest() - .contentType(JSON) - .get("/v2/contractagreements/agreement-id/negotiation") - .then() - .statusCode(200) - .contentType(JSON) - .extract().jsonPath(); - - assertThat(json.getString("@id")).isEqualTo("negotiation-id"); - } + InMemory() { + super(RUNTIME); + } - private ContractNegotiation.Builder createContractNegotiationBuilder(String negotiationId) { - return ContractNegotiation.Builder.newInstance() - .id(negotiationId) - .counterPartyId(UUID.randomUUID().toString()) - .counterPartyAddress("address") - .callbackAddresses(List.of(CallbackAddress.Builder.newInstance() - .uri("local://test") - .events(Set.of("test-event1", "test-event2")) - .build())) - .protocol("dataspace-protocol-http") - .contractOffer(contractOfferBuilder().build()) - .state(FINALIZED.code()); } - private ContractOffer.Builder contractOfferBuilder() { - return ContractOffer.Builder.newInstance() - .id("test-offer-id") - .assetId("test-asset-id") - .policy(Policy.Builder.newInstance().build()); - } + @Nested + @PostgresqlDbIntegrationTest + class Postgres extends Tests implements PostgresRuntime { + + Postgres() { + super(RUNTIME); + } + + @BeforeAll + static void beforeAll() { + PostgresqlEndToEndInstance.createDatabase("runtime"); + } - private ContractAgreement createContractAgreement(String negotiationId) { - return ContractAgreement.Builder.newInstance() - .id(negotiationId) - .assetId(UUID.randomUUID().toString()) - .consumerId(UUID.randomUUID() + "-consumer") - .providerId(UUID.randomUUID() + "-provider") - .policy(Policy.Builder.newInstance().build()) - .build(); } + abstract static class Tests extends ManagementApiEndToEndTestBase { + + Tests(EdcRuntimeExtension runtime) { + super(runtime); + } + + @Test + void getAll() { + var store = runtime.getContext().getService(ContractNegotiationStore.class); + store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); + store.save(createContractNegotiationBuilder("cn2").contractAgreement(createContractAgreement("cn2")).build()); + + var jsonPath = baseRequest() + .contentType(JSON) + .post("/v2/contractagreements/request") + .then() + .log().ifError() + .statusCode(200) + .contentType(JSON) + .body("size()", is(2)) + .extract().jsonPath(); + + assertThat(jsonPath.getString("[0].assetId")).isNotNull(); + assertThat(jsonPath.getString("[1].assetId")).isNotNull(); + assertThat(jsonPath.getString("[0].@id")).isIn("cn1", "cn2"); + assertThat(jsonPath.getString("[1].@id")).isIn("cn1", "cn2"); + } + + @Test + void getById() { + var store = runtime.getContext().getService(ContractNegotiationStore.class); + store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); + + var json = baseRequest() + .contentType(JSON) + .get("/v2/contractagreements/cn1") + .then() + .statusCode(200) + .contentType(JSON) + .extract().jsonPath(); + + assertThat(json.getString("@id")).isEqualTo("cn1"); + assertThat(json.getString("assetId")).isNotNull(); + } + + @Test + void getNegotiationByAgreementId() { + var store = runtime.getContext().getService(ContractNegotiationStore.class); + store.save(createContractNegotiationBuilder("negotiation-id") + .contractAgreement(createContractAgreement("agreement-id")) + .build()); + + var json = baseRequest() + .contentType(JSON) + .get("/v2/contractagreements/agreement-id/negotiation") + .then() + .statusCode(200) + .contentType(JSON) + .extract().jsonPath(); + + assertThat(json.getString("@id")).isEqualTo("negotiation-id"); + } + + private ContractNegotiation.Builder createContractNegotiationBuilder(String negotiationId) { + return ContractNegotiation.Builder.newInstance() + .id(negotiationId) + .counterPartyId(UUID.randomUUID().toString()) + .counterPartyAddress("address") + .callbackAddresses(List.of(CallbackAddress.Builder.newInstance() + .uri("local://test") + .events(Set.of("test-event1", "test-event2")) + .build())) + .protocol("dataspace-protocol-http") + .contractOffer(contractOfferBuilder().build()) + .state(FINALIZED.code()); + } + + private ContractOffer.Builder contractOfferBuilder() { + return ContractOffer.Builder.newInstance() + .id("test-offer-id") + .assetId("test-asset-id") + .policy(Policy.Builder.newInstance().build()); + } + + private ContractAgreement createContractAgreement(String negotiationId) { + return ContractAgreement.Builder.newInstance() + .id(negotiationId) + .assetId(UUID.randomUUID().toString()) + .consumerId(UUID.randomUUID() + "-consumer") + .providerId(UUID.randomUUID() + "-provider") + .policy(Policy.Builder.newInstance().build()) + .build(); + } + } } diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractDefinitionApiEndToEndTest.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractDefinitionApiEndToEndTest.java index 683a22e21b2..0da278668c2 100644 --- a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractDefinitionApiEndToEndTest.java +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractDefinitionApiEndToEndTest.java @@ -15,227 +15,241 @@ package org.eclipse.edc.test.e2e.managementapi; import jakarta.json.JsonArray; -import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonValue; import org.eclipse.edc.connector.contract.spi.offer.store.ContractDefinitionStore; import org.eclipse.edc.connector.contract.spi.types.offer.ContractDefinition; import org.eclipse.edc.junit.annotations.EndToEndTest; -import org.eclipse.edc.spi.query.QuerySpec; +import org.eclipse.edc.junit.annotations.PostgresqlDbIntegrationTest; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; +import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndInstance; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import java.util.UUID; + import static io.restassured.http.ContentType.JSON; import static jakarta.json.Json.createArrayBuilder; import static jakarta.json.Json.createObjectBuilder; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.LIST; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB; import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; -import static org.eclipse.edc.spi.CoreConstants.EDC_PREFIX; import static org.eclipse.edc.spi.query.Criterion.criterion; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; -@EndToEndTest -public class ContractDefinitionApiEndToEndTest extends BaseManagementApiEndToEndTest { - public static final String TEST_ID = "test-id"; - public static final String TEST_AP_ID = "ap1"; - public static final String TEST_CP_ID = "cp1"; - - @Test - void queryContractDefinitions_noQuerySpec() { - - var contractDefStore = controlPlane.getContext().getService(ContractDefinitionStore.class); - contractDefStore.save(createContractDefinition().build()); - - var body = baseRequest() - .contentType(JSON) - .post("/v2/contractdefinitions/request") - .then() - .statusCode(200) - .body("size()", greaterThan(0)) - .extract().body().as(JsonArray.class); - - var criteria = body.getJsonObject(0).getJsonArray("assetsSelector"); - assertThat(criteria).hasSize(2); - } - - @Test - void queryPolicyDefinitionWithSimplePrivateProperties() { - var requestJson = createDefinitionBuilderWithPrivateProperties() - .build(); - - baseRequest() - .contentType(JSON) - .body(requestJson) - .post("/v2/contractdefinitions") - .then() - .statusCode(200) - .body("@id", equalTo(TEST_ID)); - - - var query = createSingleFilterQuery( - "privateProperties.'https://w3id.org/edc/v0.0.1/ns/newKey'", - "=", - "newValue"); - - baseRequest() - .body(query) - .contentType(JSON) - .post("/v2/contractdefinitions/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(1)); - - - query = createSingleFilterQuery( - "privateProperties.'https://w3id.org/edc/v0.0.1/ns/newKey'", - "=", - "somethingElse"); - - baseRequest() - .body(query) - .contentType(JSON) - .post("/v2/contractdefinitions/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(0)); - } - - @Test - void createContractDef() { - var defStore = controlPlane.getContext().getService(ContractDefinitionStore.class); - var requestJson = createDefinitionBuilder() - .build(); - - baseRequest() - .contentType(JSON) - .body(requestJson) - .post("/v2/contractdefinitions") - .then() - .statusCode(200) - .body("@id", equalTo(TEST_ID)); - - assertThat(defStore.findAll(QuerySpec.none())).hasSize(1) - .allMatch(cd -> cd.getId().equals(TEST_ID)); - } - - @Test - void delete() { - var store = controlPlane.getContext().getService(ContractDefinitionStore.class); - var entity = createContractDefinition().build(); - store.save(entity); - - baseRequest() - .delete("/v2/contractdefinitions/" + entity.getId()) - .then() - .statusCode(204); - - assertThat(store.findAll(QuerySpec.none())).isEmpty(); - } - - @Test - void update_whenExists() { - var store = controlPlane.getContext().getService(ContractDefinitionStore.class); - var entity = createContractDefinition().build(); - store.save(entity); - - var updated = createDefinitionBuilder() - .add("accessPolicyId", "new-policy") - .build(); - - baseRequest() - .contentType(JSON) - .body(updated) - .put("/v2/contractdefinitions") - .then() - .statusCode(204); - - var all = store.findAll(QuerySpec.none()); - assertThat(all).hasSize(1) - .allSatisfy(cd -> assertThat(cd.getAccessPolicyId()).isEqualTo("new-policy")); - } - - @Test - void update_whenNotExists() { - var store = controlPlane.getContext().getService(ContractDefinitionStore.class); - // nothing is saved in the store, so the update will fail +public class ContractDefinitionApiEndToEndTest { - var updated = createDefinitionBuilder() - .add("accessPolicyId", "new-policy") - .build(); + @Nested + @EndToEndTest + class InMemory extends Tests implements InMemoryRuntime { - baseRequest() - .contentType(JSON) - .body(updated) - .put("/v2/contractdefinitions") - .then() - .statusCode(404); + InMemory() { + super(RUNTIME); + } - var all = store.findAll(QuerySpec.none()); - assertThat(all).isEmpty(); } - private JsonObjectBuilder createDefinitionBuilder() { - return createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, EDC_NAMESPACE + "ContractDefinition") - .add(ID, TEST_ID) - .add("accessPolicyId", TEST_AP_ID) - .add("contractPolicyId", TEST_CP_ID) - .add("assetsSelector", createArrayBuilder() - .add(createCriterionBuilder("foo", "=", "bar")) - .add(createCriterionBuilder("bar", "=", "baz")).build()); - } + @Nested + @PostgresqlDbIntegrationTest + class Postgres extends Tests implements PostgresRuntime { - private JsonObjectBuilder createDefinitionBuilderWithPrivateProperties() { - return createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, EDC_NAMESPACE + "ContractDefinition") - .add(ID, TEST_ID) - .add("accessPolicyId", TEST_AP_ID) - .add("contractPolicyId", TEST_CP_ID) - .add("assetsSelector", createArrayBuilder() - .add(createCriterionBuilder("foo", "=", "bar")) - .add(createCriterionBuilder("bar", "=", "baz")).build()) - .add("edc:privateProperties", createObjectBuilder() - .add("newKey", "newValue") - .build()); - } + Postgres() { + super(RUNTIME); + } - private static JsonObjectBuilder createCriterionBuilder(String left, String operator, String right) { - return createObjectBuilder() - .add(TYPE, "Criterion") - .add("operandLeft", left) - .add("operator", operator) - .add("operandRight", right); - } + @BeforeAll + static void beforeAll() { + PostgresqlEndToEndInstance.createDatabase("runtime"); + } - private ContractDefinition.Builder createContractDefinition() { - return ContractDefinition.Builder.newInstance() - .id(TEST_ID) - .accessPolicyId(TEST_AP_ID) - .contractPolicyId(TEST_CP_ID) - .assetsSelectorCriterion(criterion("foo", "=", "bar")) - .assetsSelectorCriterion(criterion("bar", "=", "baz")); } - private JsonObject createSingleFilterQuery(String leftOperand, String operator, String rightOperand) { - var criteria = - (createObjectBuilder() - .add("operandLeft", leftOperand) - .add("operator", operator) - .add("operandRight", rightOperand) - ); - - return createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, "QuerySpec") - .add("filterExpression", criteria) - .build(); + abstract static class Tests extends ManagementApiEndToEndTestBase { + + Tests(EdcRuntimeExtension runtime) { + super(runtime); + } + + @Test + void queryContractDefinitions_noQuerySpec() { + var contractDefStore = getContractDefinitionStore(); + var id = UUID.randomUUID().toString(); + contractDefStore.save(createContractDefinition(id).build()); + + var body = baseRequest() + .contentType(JSON) + .post("/v2/contractdefinitions/request") + .then() + .statusCode(200) + .body("size()", greaterThan(0)) + .extract().body().as(JsonArray.class); + + var assetsSelector = body.stream().map(JsonValue::asJsonObject) + .filter(it -> it.getString(ID).equals(id)) + .map(it -> it.getJsonArray("assetsSelector")) + .findAny(); + + assertThat(assetsSelector).isPresent().get().asInstanceOf(LIST).hasSize(2); + } + + @Test + void queryPolicyDefinitionWithSimplePrivateProperties() { + var id = UUID.randomUUID().toString(); + var requestJson = createDefinitionBuilder(id) + .add("privateProperties", createObjectBuilder() + .add("newKey", createObjectBuilder().add(ID, "newValue")) + .build()) + .build(); + + baseRequest() + .contentType(JSON) + .body(requestJson) + .post("/v2/contractdefinitions") + .then() + .statusCode(200) + .body("@id", equalTo(id)); + + var matchingQuery = query( + criterion("id", "=", id), + criterion("privateProperties.'%snewKey'.@id".formatted(EDC_NAMESPACE), "=", "newValue") + ); + + baseRequest() + .body(matchingQuery) + .contentType(JSON) + .post("/v2/contractdefinitions/request") + .then() + .log().ifError() + .statusCode(200) + .body("size()", is(1)); + + + var nonMatchingQuery = query( + criterion("id", "=", id), + criterion("privateProperties.'%snewKey'.@id".formatted(EDC_NAMESPACE), "=", "anything-else") + ); + + baseRequest() + .body(nonMatchingQuery) + .contentType(JSON) + .post("/v2/contractdefinitions/request") + .then() + .log().ifError() + .statusCode(200) + .body("size()", is(0)); + } + + @Test + void shouldCreateAndRetrieve() { + var id = UUID.randomUUID().toString(); + var requestJson = createDefinitionBuilder(id) + .build(); + + baseRequest() + .contentType(JSON) + .body(requestJson) + .post("/v2/contractdefinitions") + .then() + .statusCode(200) + .body("@id", equalTo(id)); + + var actual = getContractDefinitionStore().findById(id); + + assertThat(actual.getId()).matches(id); + } + + @Test + void delete() { + var id = UUID.randomUUID().toString(); + var entity = createContractDefinition(id).build(); + getContractDefinitionStore().save(entity); + + baseRequest() + .delete("/v2/contractdefinitions/" + id) + .then() + .statusCode(204); + + var actual = getContractDefinitionStore().findById(id); + + assertThat(actual).isNull(); + } + + @Test + void update_whenExists() { + var store = getContractDefinitionStore(); + var id = UUID.randomUUID().toString(); + var entity = createContractDefinition(id).build(); + store.save(entity); + + var updated = createDefinitionBuilder(id) + .add("accessPolicyId", "new-policy") + .build(); + + baseRequest() + .contentType(JSON) + .body(updated) + .put("/v2/contractdefinitions") + .then() + .statusCode(204); + + var actual = store.findById(id); + + assertThat(actual.getAccessPolicyId()).isEqualTo("new-policy"); + } + + @Test + void update_whenNotExists() { + var updated = createDefinitionBuilder(UUID.randomUUID().toString()) + .add("accessPolicyId", "new-policy") + .build(); + + baseRequest() + .contentType(JSON) + .body(updated) + .put("/v2/contractdefinitions") + .then() + .statusCode(404); + } + + private ContractDefinitionStore getContractDefinitionStore() { + return runtime.getContext().getService(ContractDefinitionStore.class); + } + + private JsonObjectBuilder createDefinitionBuilder(String id) { + return createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(VOCAB, EDC_NAMESPACE)) + .add(TYPE, EDC_NAMESPACE + "ContractDefinition") + .add(ID, id) + .add("accessPolicyId", UUID.randomUUID().toString()) + .add("contractPolicyId", UUID.randomUUID().toString()) + .add("assetsSelector", createArrayBuilder() + .add(createCriterionBuilder("foo", "=", "bar")) + .add(createCriterionBuilder("bar", "=", "baz")).build()); + } + + private JsonObjectBuilder createCriterionBuilder(String left, String operator, String right) { + return createObjectBuilder() + .add(TYPE, "Criterion") + .add("operandLeft", left) + .add("operator", operator) + .add("operandRight", right); + } + + private ContractDefinition.Builder createContractDefinition(String id) { + return ContractDefinition.Builder.newInstance() + .id(id) + .accessPolicyId(UUID.randomUUID().toString()) + .contractPolicyId(UUID.randomUUID().toString()) + .assetsSelectorCriterion(criterion("foo", "=", "bar")) + .assetsSelectorCriterion(criterion("bar", "=", "baz")); + } } } diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractNegotiationApiEndToEndTest.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractNegotiationApiEndToEndTest.java index d2e3d220386..96863b1606d 100644 --- a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractNegotiationApiEndToEndTest.java +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ContractNegotiationApiEndToEndTest.java @@ -21,17 +21,24 @@ import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiation; import org.eclipse.edc.connector.contract.spi.types.negotiation.ContractNegotiationStates; import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.junit.annotations.PostgresqlDbIntegrationTest; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.types.domain.agreement.ContractAgreement; import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; import org.eclipse.edc.spi.types.domain.offer.ContractOffer; +import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndInstance; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import static io.restassured.http.ContentType.JSON; +import static jakarta.json.Json.createArrayBuilder; import static jakarta.json.Json.createObjectBuilder; import static java.util.UUID.randomUUID; import static org.assertj.core.api.Assertions.assertThat; @@ -39,6 +46,7 @@ import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB; import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; import static org.eclipse.edc.spi.CoreConstants.EDC_PREFIX; import static org.eclipse.edc.spi.types.domain.callback.CallbackAddress.EVENTS; @@ -46,220 +54,268 @@ import static org.eclipse.edc.spi.types.domain.callback.CallbackAddress.URI; import static org.hamcrest.Matchers.is; -@EndToEndTest -public class ContractNegotiationApiEndToEndTest extends BaseManagementApiEndToEndTest { - - private final String protocolUrl = "http://localhost:" + PROTOCOL_PORT + "/protocol"; - - @Test - void getAll() { - var store = controlPlane.getContext().getService(ContractNegotiationStore.class); - store.save(createContractNegotiation("cn1")); - store.save(createContractNegotiation("cn2")); - - var jsonPath = baseRequest() - .contentType(JSON) - .post("/v2/contractnegotiations/request") - .then() - .statusCode(200) - .contentType(JSON) - .body("size()", is(2)) - .extract().jsonPath(); - - assertThat(jsonPath.getString("[0].counterPartyAddress")).isEqualTo(protocolUrl); - assertThat(jsonPath.getString("[0].@id")).isIn("cn1", "cn2"); - assertThat(jsonPath.getString("[1].@id")).isIn("cn1", "cn2"); - assertThat(jsonPath.getString("[0].protocol")).isEqualTo("dataspace-protocol-http"); - assertThat(jsonPath.getString("[1].protocol")).isEqualTo("dataspace-protocol-http"); - } - - @Test - void getById() { - var store = controlPlane.getContext().getService(ContractNegotiationStore.class); - store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); - - var json = baseRequest() - .contentType(JSON) - .get("/v2/contractnegotiations/cn1") - .then() - .statusCode(200) - .contentType(JSON) - .extract().jsonPath(); - - assertThat((String) json.get("@id")).isEqualTo("cn1"); - assertThat(json.getString("protocol")).isEqualTo("dataspace-protocol-http"); - } - - @Test - void getState() { - var store = controlPlane.getContext().getService(ContractNegotiationStore.class); - var state = ContractNegotiationStates.FINALIZED.code(); // all other states could be modified by the state machine - store.save(createContractNegotiationBuilder("cn1").state(state).build()); - - baseRequest() - .contentType(JSON) - .get("/v2/contractnegotiations/cn1/state") - .then() - .statusCode(200) - .contentType(JSON) - .body("state", is("FINALIZED")); - } - - @Test - void getAgreementForNegotiation() { - var store = controlPlane.getContext().getService(ContractNegotiationStore.class); - var agreement = createContractAgreement("cn1"); - store.save(createContractNegotiationBuilder("cn1").contractAgreement(agreement).build()); - - var json = baseRequest() - .contentType(JSON) - .get("/v2/contractnegotiations/cn1/agreement") - .then() - .statusCode(200) - .contentType(JSON) - .extract().jsonPath(); - - assertThat(json.getString("@id")).isEqualTo("cn1"); - assertThat((Object) json.get("policy")).isNotNull().isInstanceOf(Map.class); - assertThat(json.getString("assetId")).isEqualTo(agreement.getAssetId()); - } +public class ContractNegotiationApiEndToEndTest { - @Test - void initiateNegotiation() { - - var requestJson = createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, "ContractRequest") - .add("counterPartyAddress", "test-address") - .add("protocol", "test-protocol") - .add("providerId", "test-provider-id") - .add("callbackAddresses", createCallbackAddress()) - .add("policy", createPolicy()) - .build(); - - var id = baseRequest() - .contentType(JSON) - .body(requestJson) - .post("/v2/contractnegotiations") - .then() - .statusCode(200) - .contentType(JSON) - .extract().jsonPath().getString(ID); - - var store = controlPlane.getContext().getService(ContractNegotiationStore.class); - - assertThat(store.findById(id)).isNotNull(); - } + @Nested + @EndToEndTest + class InMemory extends Tests implements InMemoryRuntime { - @Deprecated(since = "0.3.2") - @Test - void deprecated_initiateNegotiation() { - - var requestJson = createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, "ContractRequest") - .add("counterPartyAddress", "test-address") - .add("protocol", "test-protocol") - .add("providerId", "test-provider-id") - .add("callbackAddresses", createCallbackAddress()) - .add("offer", createObjectBuilder() - .add("offerId", "offer-id") - .add("assetId", "assetId") - .add("policy", createPolicy())) - .build(); - - var id = baseRequest() - .contentType(JSON) - .body(requestJson) - .post("/v2/contractnegotiations") - .then() - .log().ifError() - .statusCode(200) - .contentType(JSON) - .extract().jsonPath().getString(ID); - - var store = controlPlane.getContext().getService(ContractNegotiationStore.class); - - assertThat(store.findById(id)).isNotNull(); - } + InMemory() { + super(RUNTIME); + } - @Test - void terminate() { - var store = controlPlane.getContext().getService(ContractNegotiationStore.class); - store.save(createContractNegotiationBuilder("cn1").build()); - var requestBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE).build()) - .add(ID, "cn1") - .add("reason", "any good reason") - .build(); - - baseRequest() - .body(requestBody) - .contentType(JSON) - .post("/v2/contractnegotiations/cn1/terminate") - .then() - .log().ifError() - .statusCode(204); } - private ContractNegotiation createContractNegotiation(String negotiationId) { - return createContractNegotiationBuilder(negotiationId) - .build(); - } + @Nested + @PostgresqlDbIntegrationTest + class Postgres extends Tests implements PostgresRuntime { - private ContractNegotiation.Builder createContractNegotiationBuilder(String negotiationId) { - return ContractNegotiation.Builder.newInstance() - .id(negotiationId) - .correlationId(negotiationId) - .counterPartyId(randomUUID().toString()) - .counterPartyAddress(protocolUrl) - .callbackAddresses(List.of(CallbackAddress.Builder.newInstance() - .uri("local://test") - .events(Set.of("test-event1", "test-event2")) - .build())) - .protocol("dataspace-protocol-http") - .state(REQUESTED.code()) - .contractOffer(contractOfferBuilder().build()); - } + Postgres() { + super(RUNTIME); + } - private ContractOffer.Builder contractOfferBuilder() { - return ContractOffer.Builder.newInstance() - .id("test-offer-id") - .assetId(randomUUID().toString()) - .policy(Policy.Builder.newInstance().build()); - } - - private ContractAgreement createContractAgreement(String negotiationId) { - return ContractAgreement.Builder.newInstance() - .id(negotiationId) - .assetId(randomUUID().toString()) - .consumerId(randomUUID() + "-consumer") - .providerId(randomUUID() + "-provider") - .policy(Policy.Builder.newInstance().build()) - .build(); - } + @BeforeAll + static void beforeAll() { + PostgresqlEndToEndInstance.createDatabase("runtime"); + } - private JsonArrayBuilder createCallbackAddress() { - var builder = Json.createArrayBuilder(); - return builder.add(createObjectBuilder() - .add(IS_TRANSACTIONAL, false) - .add(URI, "http://test.local/") - .add(EVENTS, Json.createArrayBuilder().build())); } - private JsonObject createPolicy() { - var permissionJson = createObjectBuilder().add(TYPE, "permission").build(); - var prohibitionJson = createObjectBuilder().add(TYPE, "prohibition").build(); - var dutyJson = createObjectBuilder().add(TYPE, "duty").build(); - return createObjectBuilder() - .add(CONTEXT, "http://www.w3.org/ns/odrl.jsonld") - .add(TYPE, "Offer") - .add(ID, "offer-id") - .add("permission", permissionJson) - .add("prohibition", prohibitionJson) - .add("obligation", dutyJson) - .add("target", "asset-id") - .build(); + abstract static class Tests extends ManagementApiEndToEndTestBase { + + private final String protocolUrl = "http://localhost:" + PROTOCOL_PORT + "/protocol"; + + Tests(EdcRuntimeExtension runtime) { + super(runtime); + } + + @Test + void getAll() { + var store = getContractNegotiationStore(); + var id1 = UUID.randomUUID().toString(); + var id2 = UUID.randomUUID().toString(); + store.save(createContractNegotiation(id1)); + store.save(createContractNegotiation(id2)); + + var jsonPath = baseRequest() + .contentType(JSON) + .body(createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(VOCAB, EDC_NAMESPACE)) + .add("filterExpression", createArrayBuilder() + .add(createObjectBuilder() + .add("operandLeft", "id") + .add("operator", "in") + .add("operandRight", createArrayBuilder().add(id1).add(id2)) + ) + ) + .build() + ) + .post("/v2/contractnegotiations/request") + .then() + .statusCode(200) + .contentType(JSON) + .body("size()", is(2)) + .extract().jsonPath(); + + assertThat(jsonPath.getString("[0].counterPartyAddress")).isEqualTo(protocolUrl); + assertThat(jsonPath.getString("[0].@id")).isIn(id1, id2); + assertThat(jsonPath.getString("[1].@id")).isIn(id1, id2); + assertThat(jsonPath.getString("[0].protocol")).isEqualTo("dataspace-protocol-http"); + assertThat(jsonPath.getString("[1].protocol")).isEqualTo("dataspace-protocol-http"); + } + + @Test + void getById() { + var store = getContractNegotiationStore(); + store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); + + var json = baseRequest() + .contentType(JSON) + .get("/v2/contractnegotiations/cn1") + .then() + .statusCode(200) + .contentType(JSON) + .extract().jsonPath(); + + assertThat((String) json.get("@id")).isEqualTo("cn1"); + assertThat(json.getString("protocol")).isEqualTo("dataspace-protocol-http"); + } + + @Test + void getState() { + var store = getContractNegotiationStore(); + var state = ContractNegotiationStates.FINALIZED.code(); // all other states could be modified by the state machine + store.save(createContractNegotiationBuilder("cn1").state(state).build()); + + baseRequest() + .contentType(JSON) + .get("/v2/contractnegotiations/cn1/state") + .then() + .statusCode(200) + .contentType(JSON) + .body("state", is("FINALIZED")); + } + + @Test + void getAgreementForNegotiation() { + var store = getContractNegotiationStore(); + var agreement = createContractAgreement("cn1"); + store.save(createContractNegotiationBuilder("cn1").contractAgreement(agreement).build()); + + var json = baseRequest() + .contentType(JSON) + .get("/v2/contractnegotiations/cn1/agreement") + .then() + .statusCode(200) + .contentType(JSON) + .extract().jsonPath(); + + assertThat(json.getString("@id")).isEqualTo("cn1"); + assertThat((Object) json.get("policy")).isNotNull().isInstanceOf(Map.class); + assertThat(json.getString("assetId")).isEqualTo(agreement.getAssetId()); + } + + @Test + void initiateNegotiation() { + + var requestJson = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) + .add(TYPE, "ContractRequest") + .add("counterPartyAddress", "test-address") + .add("protocol", "test-protocol") + .add("providerId", "test-provider-id") + .add("callbackAddresses", createCallbackAddress()) + .add("policy", createPolicy()) + .build(); + + var id = baseRequest() + .contentType(JSON) + .body(requestJson) + .post("/v2/contractnegotiations") + .then() + .statusCode(200) + .contentType(JSON) + .extract().jsonPath().getString(ID); + + var store = getContractNegotiationStore(); + + assertThat(store.findById(id)).isNotNull(); + } + + @Deprecated(since = "0.3.2") + @Test + void deprecated_initiateNegotiation() { + + var requestJson = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) + .add(TYPE, "ContractRequest") + .add("counterPartyAddress", "test-address") + .add("protocol", "test-protocol") + .add("providerId", "test-provider-id") + .add("callbackAddresses", createCallbackAddress()) + .add("offer", createObjectBuilder() + .add("offerId", "offer-id") + .add("assetId", "assetId") + .add("policy", createPolicy())) + .build(); + + var id = baseRequest() + .contentType(JSON) + .body(requestJson) + .post("/v2/contractnegotiations") + .then() + .log().ifError() + .statusCode(200) + .contentType(JSON) + .extract().jsonPath().getString(ID); + + var store = getContractNegotiationStore(); + + assertThat(store.findById(id)).isNotNull(); + } + + @Test + void terminate() { + var store = getContractNegotiationStore(); + store.save(createContractNegotiationBuilder("cn1").build()); + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE).build()) + .add(ID, "cn1") + .add("reason", "any good reason") + .build(); + + baseRequest() + .body(requestBody) + .contentType(JSON) + .post("/v2/contractnegotiations/cn1/terminate") + .then() + .log().ifError() + .statusCode(204); + } + + private ContractNegotiation createContractNegotiation(String negotiationId) { + return createContractNegotiationBuilder(negotiationId) + .build(); + } + + private ContractNegotiation.Builder createContractNegotiationBuilder(String negotiationId) { + return ContractNegotiation.Builder.newInstance() + .id(negotiationId) + .correlationId(negotiationId) + .counterPartyId(randomUUID().toString()) + .counterPartyAddress(protocolUrl) + .callbackAddresses(List.of(CallbackAddress.Builder.newInstance() + .uri("local://test") + .events(Set.of("test-event1", "test-event2")) + .build())) + .protocol("dataspace-protocol-http") + .state(REQUESTED.code()) + .contractOffer(contractOfferBuilder().build()); + } + + private ContractOffer.Builder contractOfferBuilder() { + return ContractOffer.Builder.newInstance() + .id("test-offer-id") + .assetId(randomUUID().toString()) + .policy(Policy.Builder.newInstance().build()); + } + + private ContractAgreement createContractAgreement(String negotiationId) { + return ContractAgreement.Builder.newInstance() + .id(negotiationId) + .assetId(randomUUID().toString()) + .consumerId(randomUUID() + "-consumer") + .providerId(randomUUID() + "-provider") + .policy(Policy.Builder.newInstance().build()) + .build(); + } + + private JsonArrayBuilder createCallbackAddress() { + var builder = Json.createArrayBuilder(); + return builder.add(createObjectBuilder() + .add(IS_TRANSACTIONAL, false) + .add(URI, "http://test.local/") + .add(EVENTS, Json.createArrayBuilder().build())); + } + + private JsonObject createPolicy() { + var permissionJson = createObjectBuilder().add(TYPE, "permission").build(); + var prohibitionJson = createObjectBuilder().add(TYPE, "prohibition").build(); + var dutyJson = createObjectBuilder().add(TYPE, "duty").build(); + return createObjectBuilder() + .add(CONTEXT, "http://www.w3.org/ns/odrl.jsonld") + .add(TYPE, "Offer") + .add(ID, "offer-id") + .add("permission", permissionJson) + .add("prohibition", prohibitionJson) + .add("obligation", dutyJson) + .add("target", "asset-id") + .build(); + } + + private ContractNegotiationStore getContractNegotiationStore() { + return runtime.getContext().getService(ContractNegotiationStore.class); + } } } diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/InMemoryRuntime.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/InMemoryRuntime.java new file mode 100644 index 00000000000..7946a9c3099 --- /dev/null +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/InMemoryRuntime.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.test.e2e.managementapi; + +import org.eclipse.edc.junit.extensions.EdcClassRuntimesExtension; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.util.HashMap; + +import static org.eclipse.edc.junit.testfixtures.TestUtils.getFreePort; + +public interface InMemoryRuntime { + + EdcRuntimeExtension RUNTIME = new EdcRuntimeExtension( + "control-plane", + inMemoryConfiguration(), + ":system-tests:management-api:management-api-test-runtime" + ); + + @RegisterExtension + EdcClassRuntimesExtension RUNTIMES = new EdcClassRuntimesExtension(RUNTIME); + + @NotNull + static HashMap inMemoryConfiguration() { + return new HashMap<>() { + { + put("web.http.path", "/"); + put("web.http.protocol.path", "/protocol"); + put("web.http.protocol.port", String.valueOf(ManagementApiEndToEndTestBase.PROTOCOL_PORT)); + put("edc.dsp.callback.address", "http://localhost:" + ManagementApiEndToEndTestBase.PROTOCOL_PORT + "/protocol"); + put("web.http.port", String.valueOf(getFreePort())); + put("web.http.management.path", "/management"); + put("web.http.management.port", String.valueOf(ManagementApiEndToEndTestBase.PORT)); + } + }; + } + +} diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ManagementApiEndToEndTestBase.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ManagementApiEndToEndTestBase.java new file mode 100644 index 00000000000..88ee188abb7 --- /dev/null +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/ManagementApiEndToEndTestBase.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.test.e2e.managementapi; + +import io.restassured.specification.RequestSpecification; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; +import org.eclipse.edc.spi.query.Criterion; + +import java.util.Arrays; +import java.util.Collection; + +import static io.restassured.RestAssured.given; +import static jakarta.json.Json.createObjectBuilder; +import static jakarta.json.stream.JsonCollectors.toJsonArray; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB; +import static org.eclipse.edc.junit.testfixtures.TestUtils.getFreePort; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; + +public abstract class ManagementApiEndToEndTestBase { + + public static final int PORT = getFreePort(); + public static final int PROTOCOL_PORT = getFreePort(); + + protected final EdcRuntimeExtension runtime; + + public ManagementApiEndToEndTestBase(EdcRuntimeExtension runtime) { + this.runtime = runtime; + } + + protected RequestSpecification baseRequest() { + return given() + .port(PORT) + .baseUri("http://localhost:%s/management".formatted(PORT)) + .when(); + } + + protected JsonObject query(Criterion... criteria) { + var criteriaJson = Arrays.stream(criteria) + .map(it -> { + JsonValue operandRight; + if (it.getOperandRight() instanceof Collection collection) { + operandRight = Json.createArrayBuilder(collection).build(); + } else { + operandRight = Json.createValue(it.getOperandRight().toString()); + } + return createObjectBuilder() + .add("operandLeft", it.getOperandLeft().toString()) + .add("operator", it.getOperator()) + .add("operandRight", operandRight) + .build(); + } + ).collect(toJsonArray()); + + return createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(VOCAB, EDC_NAMESPACE)) + .add(TYPE, "QuerySpec") + .add("filterExpression", criteriaJson) + .build(); + } +} diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/PolicyDefinitionApiEndToEndTest.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/PolicyDefinitionApiEndToEndTest.java index 4519b1711b9..cd34139261e 100644 --- a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/PolicyDefinitionApiEndToEndTest.java +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/PolicyDefinitionApiEndToEndTest.java @@ -14,13 +14,16 @@ package org.eclipse.edc.test.e2e.managementapi; -import jakarta.json.JsonArray; import jakarta.json.JsonObject; import org.eclipse.edc.connector.policy.spi.PolicyDefinition; import org.eclipse.edc.connector.policy.spi.store.PolicyDefinitionStore; import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.junit.annotations.PostgresqlDbIntegrationTest; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.entity.Entity; +import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndInstance; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.util.HashMap; @@ -30,345 +33,303 @@ import static jakarta.json.Json.createArrayBuilder; import static jakarta.json.Json.createObjectBuilder; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.MAP; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB; import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_PREFIX; import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA; import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; import static org.eclipse.edc.spi.CoreConstants.EDC_PREFIX; +import static org.eclipse.edc.spi.query.Criterion.criterion; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; -@EndToEndTest -public class PolicyDefinitionApiEndToEndTest extends BaseManagementApiEndToEndTest { - - @Test - void shouldStorePolicyDefinition() { - var requestBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder() - .add("edc", EDC_NAMESPACE) - .build()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .build(); - - var id = baseRequest() - .body(requestBody) - .contentType(JSON) - .post("/v2/policydefinitions") - .then() - .contentType(JSON) - .extract().jsonPath().getString(ID); - - assertThat(store().findById(id)).isNotNull() - .extracting(PolicyDefinition::getPolicy).isNotNull() - .extracting(Policy::getPermissions).asList().hasSize(1); - - baseRequest() - .get("/v2/policydefinitions/" + id) - .then() - .statusCode(200) - .contentType(JSON) - .body(ID, is(id)) - .body(CONTEXT, hasEntry(EDC_PREFIX, EDC_NAMESPACE)) - .body(CONTEXT, hasEntry(ODRL_PREFIX, ODRL_SCHEMA)) - .log().all() - .body("policy.'odrl:permission'.'odrl:constraint'.'odrl:operator'.@id", is("odrl:eq")); - } - - @Test - void shouldStorePolicyDefinitionWithPrivateProperties() { - var requestBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder() - .add("edc", EDC_NAMESPACE) - .build()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .add("privateProperties", createObjectBuilder() - .add("newKey", "newValue") - .build()) - .build(); - - var id = baseRequest() - .body(requestBody) - .contentType(JSON) - .post("/v2/policydefinitions") - .then() - .contentType(JSON) - .extract().jsonPath().getString(ID); - - PolicyDefinition result = store().findById(id); - assertThat(result).isNotNull() - .extracting(PolicyDefinition::getPolicy).isNotNull() - .extracting(Policy::getPermissions).asList().hasSize(1); - Map privateProp = new HashMap<>(); - privateProp.put("https://w3id.org/edc/v0.0.1/ns/newKey", "newValue"); - assertThat(result).isNotNull() - .extracting(PolicyDefinition::getPrivateProperties).isEqualTo(privateProp); - - baseRequest() - .get("/v2/policydefinitions/" + id) - .then() - .statusCode(200) - .contentType(JSON) - .body(ID, is(id)) - .body(CONTEXT, hasEntry(EDC_PREFIX, EDC_NAMESPACE)) - .body(CONTEXT, hasEntry(ODRL_PREFIX, ODRL_SCHEMA)) - .log().all() - .body("policy.'odrl:permission'.'odrl:constraint'.'odrl:operator'.@id", is("odrl:eq")); - } +public class PolicyDefinitionApiEndToEndTest { - @Test - void queryPolicyDefinitionWithSimplePrivateProperties() { - var requestBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder() - .add("edc", EDC_NAMESPACE) - .build()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .add("edc:privateProperties", createObjectBuilder() - .add("newKey", "newValue") - .build()) - .build(); - - baseRequest() - .body(requestBody) - .contentType(JSON) - .post("/v2/policydefinitions") - .then() - .contentType(JSON) - .extract().jsonPath().getString(ID); - - var query = createSingleFilterQuery( - "privateProperties.'https://w3id.org/edc/v0.0.1/ns/newKey'", - "=", - "newValue"); - - baseRequest() - .body(query) - .contentType(JSON) - .post("/v2/policydefinitions/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(1)); - - - query = createSingleFilterQuery( - "privateProperties.'https://w3id.org/edc/v0.0.1/ns/newKey'", - "=", - "somethingElse"); - - baseRequest() - .body(query) - .contentType(JSON) - .post("/v2/policydefinitions/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(0)); - } + @Nested + @EndToEndTest + class InMemory extends Tests implements InMemoryRuntime { - @Test - void shouldUpdate() { - var requestBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder() - .add("edc", EDC_NAMESPACE) - .build()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .build(); - - var id = baseRequest() - .body(requestBody) - .contentType(JSON) - .post("/v2/policydefinitions") - .then() - .statusCode(200) - .extract().jsonPath().getString(ID); - - var createdAt = baseRequest() - .contentType(JSON) - .post("/v2/policydefinitions/request") - .then() - .statusCode(200) - .extract().as(JsonArray.class) - .get(0).asJsonObject() - .getJsonNumber("createdAt").longValue(); - - baseRequest() - .contentType(JSON) - .body(createObjectBuilder(requestBody).add(ID, id).build()) - .put("/v2/policydefinitions/" + id) - .then() - .statusCode(204); - - assertThat(store().findById(id)) - .extracting(Entity::getCreatedAt) - .isNotEqualTo(createdAt); - } + InMemory() { + super(RUNTIME); + } - @Test - void shouldUpdateWithProperties() { - var requestBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder() - .add("edc", EDC_NAMESPACE) - .build()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .add("privateProperties", createObjectBuilder() - .add("newKey", "newValue") - .build()) - .build(); - - var updatedBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder() - .add("edc", EDC_NAMESPACE) - .build()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .add("privateProperties", createObjectBuilder() - .add("newKey", "updatedValue") - .build()) - .build(); - - var id = baseRequest() - .body(requestBody) - .contentType(JSON) - .post("/v2/policydefinitions") - .then() - .statusCode(200) - .extract().jsonPath().getString(ID); - - var createdAt = baseRequest() - .contentType(JSON) - .post("/v2/policydefinitions/request") - .then() - .statusCode(200) - .extract().as(JsonArray.class) - .get(0).asJsonObject() - .getJsonNumber("createdAt").longValue(); - - baseRequest() - .contentType(JSON) - .body(createObjectBuilder(updatedBody).add(ID, id).build()) - .put("/v2/policydefinitions/" + id) - .then() - .statusCode(204); - - var result = store().findById(id); - assertThat(result) - .extracting(Entity::getCreatedAt) - .isNotEqualTo(createdAt); - - - Map privateProp = new HashMap<>(); - privateProp.put("https://w3id.org/edc/v0.0.1/ns/newKey", "updatedValue"); - assertThat(result).isNotNull() - .extracting(PolicyDefinition::getPrivateProperties).isEqualTo(privateProp); - - assertThat(store().findById(id)) - .extracting(Entity::getCreatedAt) - .isNotEqualTo(createdAt); } - @Test - void shouldDelete() { - var requestBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder() - .add("edc", EDC_NAMESPACE) - .build()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .build(); - - var id = baseRequest() - .body(requestBody) - .contentType(JSON) - .post("/v2/policydefinitions") - .then() - .statusCode(200) - .extract().jsonPath().getString(ID); - - baseRequest() - .delete("/v2/policydefinitions/" + id) - .then() - .statusCode(204); - - baseRequest() - .get("/v2/policydefinitions/" + id) - .then() - .statusCode(404); - } + @Nested + @PostgresqlDbIntegrationTest + class Postgres extends Tests implements PostgresRuntime { + Postgres() { + super(RUNTIME); + } - @Test - void shouldDeleteWithProperties() { - var requestBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder() - .add("edc", EDC_NAMESPACE) - .build()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .add("privateProperties", createObjectBuilder() - .add("newKey", "newValue") - .build()) - .build(); - - var id = baseRequest() - .body(requestBody) - .contentType(JSON) - .post("/v2/policydefinitions") - .then() - .statusCode(200) - .extract().jsonPath().getString(ID); - - baseRequest() - .delete("/v2/policydefinitions/" + id) - .then() - .statusCode(204); - - baseRequest() - .get("/v2/policydefinitions/" + id) - .then() - .statusCode(404); - } + @BeforeAll + static void beforeAll() { + PostgresqlEndToEndInstance.createDatabase("runtime"); + } - private JsonObject sampleOdrlPolicy() { - return createObjectBuilder() - .add(CONTEXT, "http://www.w3.org/ns/odrl.jsonld") - .add(TYPE, "Set") - .add("permission", createArrayBuilder() - .add(createObjectBuilder() - .add("target", "http://example.com/asset:9898.movie") - .add("action", "use") - .add("constraint", createObjectBuilder() - .add("leftOperand", "left") - .add("operator", "eq") - .add("rightOperand", "value")) - .build()) - .build()) - .build(); } - private PolicyDefinitionStore store() { - return controlPlane.getContext().getService(PolicyDefinitionStore.class); - } + abstract static class Tests extends ManagementApiEndToEndTestBase { + + Tests(EdcRuntimeExtension runtime) { + super(runtime); + } + + @Test + void shouldStorePolicyDefinition() { + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder() + .add(VOCAB, EDC_NAMESPACE) + .build()) + .add(TYPE, "PolicyDefinition") + .add("policy", sampleOdrlPolicy()) + .build(); + + var id = baseRequest() + .body(requestBody) + .contentType(JSON) + .post("/v2/policydefinitions") + .then() + .contentType(JSON) + .extract().jsonPath().getString(ID); + + assertThat(store().findById(id)).isNotNull() + .extracting(PolicyDefinition::getPolicy).isNotNull() + .extracting(Policy::getPermissions).asList().hasSize(1); + + baseRequest() + .get("/v2/policydefinitions/" + id) + .then() + .statusCode(200) + .contentType(JSON) + .body(ID, is(id)) + .body(CONTEXT, hasEntry(EDC_PREFIX, EDC_NAMESPACE)) + .body(CONTEXT, hasEntry(ODRL_PREFIX, ODRL_SCHEMA)) + .log().all() + .body("policy.'odrl:permission'.'odrl:constraint'.'odrl:operator'.@id", is("odrl:eq")); + } + + @Test + void shouldStorePolicyDefinitionWithPrivateProperties() { + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder() + .add(VOCAB, EDC_NAMESPACE) + .build()) + .add(TYPE, "PolicyDefinition") + .add("policy", sampleOdrlPolicy()) + .add("privateProperties", createObjectBuilder() + .add("newKey", "newValue") + .build()) + .build(); + + var id = baseRequest() + .body(requestBody) + .contentType(JSON) + .post("/v2/policydefinitions") + .then() + .contentType(JSON) + .extract().jsonPath().getString(ID); + + var result = store().findById(id); + + assertThat(result).isNotNull() + .extracting(PolicyDefinition::getPolicy).isNotNull() + .extracting(Policy::getPermissions).asList().hasSize(1); + Map privateProp = new HashMap<>(); + privateProp.put("https://w3id.org/edc/v0.0.1/ns/newKey", "newValue"); + assertThat(result).isNotNull() + .extracting(PolicyDefinition::getPrivateProperties).isEqualTo(privateProp); + + baseRequest() + .get("/v2/policydefinitions/" + id) + .then() + .statusCode(200) + .contentType(JSON) + .body(ID, is(id)) + .body(CONTEXT, hasEntry(EDC_PREFIX, EDC_NAMESPACE)) + .body(CONTEXT, hasEntry(ODRL_PREFIX, ODRL_SCHEMA)) + .log().all() + .body("policy.'odrl:permission'.'odrl:constraint'.'odrl:operator'.@id", is("odrl:eq")); + } + + @Test + void queryPolicyDefinitionWithSimplePrivateProperties() { + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder() + .add(VOCAB, EDC_NAMESPACE) + .build()) + .add(TYPE, "PolicyDefinition") + .add("policy", sampleOdrlPolicy()) + .add("privateProperties", createObjectBuilder() + .add("newKey", createObjectBuilder().add(ID, "newValue")) + .build()) + .build(); + + var id = baseRequest() + .body(requestBody) + .contentType(JSON) + .post("/v2/policydefinitions") + .then() + .contentType(JSON) + .extract().jsonPath().getString(ID); + + var matchingQuery = query( + criterion("id", "=", id), + criterion("privateProperties.'https://w3id.org/edc/v0.0.1/ns/newKey'.@id", "=", "newValue") + ); + + baseRequest() + .body(matchingQuery) + .contentType(JSON) + .post("/v2/policydefinitions/request") + .then() + .log().ifError() + .statusCode(200) + .body("size()", is(1)); + + + var nonMatchingQuery = query( + criterion("id", "=", id), + criterion("privateProperties.'https://w3id.org/edc/v0.0.1/ns/newKey'.@id", "=", "somethingElse") + ); + + baseRequest() + .body(nonMatchingQuery) + .contentType(JSON) + .post("/v2/policydefinitions/request") + .then() + .log().ifError() + .statusCode(200) + .body("size()", is(0)); + } + + @Test + void shouldUpdate() { + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder() + .add(VOCAB, EDC_NAMESPACE) + .build()) + .add(TYPE, "PolicyDefinition") + .add("policy", sampleOdrlPolicy()) + .build(); + + var id = baseRequest() + .body(requestBody) + .contentType(JSON) + .post("/v2/policydefinitions") + .then() + .statusCode(200) + .extract().jsonPath().getString(ID); + + baseRequest() + .contentType(JSON) + .get("/v2/policydefinitions/" + id) + .then() + .statusCode(200); + + baseRequest() + .contentType(JSON) + .body(createObjectBuilder(requestBody) + .add(ID, id) + .add("privateProperties", createObjectBuilder().add("privateProperty", "value")) + .build()) + .put("/v2/policydefinitions/" + id) + .then() + .statusCode(204); + + assertThat(store().findById(id)) + .extracting(PolicyDefinition::getPrivateProperties) + .asInstanceOf(MAP) + .isNotEmpty(); + } + + @Test + void shouldDelete() { + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder() + .add(VOCAB, EDC_NAMESPACE) + .build()) + .add(TYPE, "PolicyDefinition") + .add("policy", sampleOdrlPolicy()) + .build(); + + var id = baseRequest() + .body(requestBody) + .contentType(JSON) + .post("/v2/policydefinitions") + .then() + .statusCode(200) + .extract().jsonPath().getString(ID); + + baseRequest() + .delete("/v2/policydefinitions/" + id) + .then() + .statusCode(204); + + baseRequest() + .get("/v2/policydefinitions/" + id) + .then() + .statusCode(404); + } + + + @Test + void shouldDeleteWithProperties() { + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder() + .add(VOCAB, EDC_NAMESPACE) + .build()) + .add(TYPE, "PolicyDefinition") + .add("policy", sampleOdrlPolicy()) + .add("privateProperties", createObjectBuilder() + .add("newKey", "newValue") + .build()) + .build(); + + var id = baseRequest() + .body(requestBody) + .contentType(JSON) + .post("/v2/policydefinitions") + .then() + .statusCode(200) + .extract().jsonPath().getString(ID); + + baseRequest() + .delete("/v2/policydefinitions/" + id) + .then() + .statusCode(204); + + baseRequest() + .get("/v2/policydefinitions/" + id) + .then() + .statusCode(404); + } + + private JsonObject sampleOdrlPolicy() { + return createObjectBuilder() + .add(CONTEXT, "http://www.w3.org/ns/odrl.jsonld") + .add(TYPE, "Set") + .add("permission", createArrayBuilder() + .add(createObjectBuilder() + .add("target", "http://example.com/asset:9898.movie") + .add("action", "use") + .add("constraint", createObjectBuilder() + .add("leftOperand", "left") + .add("operator", "eq") + .add("rightOperand", "value")) + .build()) + .build()) + .build(); + } + + private PolicyDefinitionStore store() { + return runtime.getContext().getService(PolicyDefinitionStore.class); + } - private JsonObject createSingleFilterQuery(String leftOperand, String operator, String rightOperand) { - var criteria = - (createObjectBuilder() - .add("operandLeft", leftOperand) - .add("operator", operator) - .add("operandRight", rightOperand) - ); - - return createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, "QuerySpec") - .add("filterExpression", criteria) - .build(); } } diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/PostgresRuntime.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/PostgresRuntime.java new file mode 100644 index 00000000000..1dd6eba75c1 --- /dev/null +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/PostgresRuntime.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.test.e2e.managementapi; + +import org.eclipse.edc.junit.extensions.EdcClassRuntimesExtension; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; +import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndInstance; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.util.HashMap; + +import static org.eclipse.edc.test.e2e.managementapi.InMemoryRuntime.inMemoryConfiguration; + +public interface PostgresRuntime { + + EdcRuntimeExtension RUNTIME = new EdcRuntimeExtension( + "control-plane", + postgresqlConfiguration(), + ":system-tests:management-api:management-api-test-runtime", + ":extensions:control-plane:store:sql:control-plane-sql", + ":extensions:common:sql:sql-pool:sql-pool-apache-commons", + ":extensions:common:transaction:transaction-local" + ); + + @RegisterExtension + EdcClassRuntimesExtension RUNTIMES = new EdcClassRuntimesExtension(RUNTIME); + + @NotNull + static HashMap postgresqlConfiguration() { + var config = new HashMap() { + { + put("edc.datasource.default.url", PostgresqlEndToEndInstance.JDBC_URL_PREFIX + "runtime"); + put("edc.datasource.default.user", PostgresqlEndToEndInstance.USER); + put("edc.datasource.default.password", PostgresqlEndToEndInstance.PASSWORD); + } + }; + + config.putAll(inMemoryConfiguration()); + return config; + } + +} diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/TransferProcessApiEndToEndTest.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/TransferProcessApiEndToEndTest.java index c7f009c5ee2..bedf999c621 100644 --- a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/TransferProcessApiEndToEndTest.java +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/TransferProcessApiEndToEndTest.java @@ -24,8 +24,13 @@ import org.eclipse.edc.connector.transfer.spi.types.TransferProcess; import org.eclipse.edc.jsonld.util.JacksonJsonLd; import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.junit.annotations.PostgresqlDbIntegrationTest; +import org.eclipse.edc.junit.extensions.EdcRuntimeExtension; import org.eclipse.edc.spi.types.domain.DataAddress; import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; +import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndInstance; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.util.List; @@ -43,192 +48,227 @@ import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB; import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; -import static org.eclipse.edc.spi.CoreConstants.EDC_PREFIX; +import static org.eclipse.edc.spi.query.Criterion.criterion; import static org.eclipse.edc.spi.types.domain.callback.CallbackAddress.EVENTS; import static org.eclipse.edc.spi.types.domain.callback.CallbackAddress.IS_TRANSACTIONAL; import static org.eclipse.edc.spi.types.domain.callback.CallbackAddress.URI; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.Matchers.is; -@EndToEndTest -public class TransferProcessApiEndToEndTest extends BaseManagementApiEndToEndTest { - - @Test - void getAll() { - getStore().save(createTransferProcess("tp1")); - getStore().save(createTransferProcess("tp2")); - - baseRequest() - .contentType(JSON) - .post("/v2/transferprocesses/request") - .then() - .statusCode(200) - .body("size()", is(2)) - .body("[0].@id", anyOf(is("tp1"), is("tp2"))) - .body("[1].@id", anyOf(is("tp1"), is("tp2"))); - } +public class TransferProcessApiEndToEndTest { - @Test - void getById() { - getStore().save(createTransferProcess("tp1")); - getStore().save(createTransferProcess("tp2")); - - baseRequest() - .get("/v2/transferprocesses/tp2") - .then() - .statusCode(200) - .body("@id", is("tp2")) - .body(TYPE, is("TransferProcess")); - } + @Nested + @EndToEndTest + class InMemory extends Tests implements InMemoryRuntime { - @Test - void getState() { - getStore().save(createTransferProcessBuilder("tp2").state(COMPLETED.code()).build()); - - baseRequest() - .get("/v2/transferprocesses/tp2/state") - .then() - .statusCode(200) - .contentType(JSON) - .body(TYPE, is("TransferState")) - .body("'state'", is("COMPLETED")); - } + InMemory() { + super(RUNTIME); + } - @Test - void create() { - var requestBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(EDC_PREFIX, EDC_NAMESPACE)) - .add(TYPE, "TransferRequest") - .add("dataDestination", createObjectBuilder() - .add(TYPE, "DataAddress") - .add("type", "HttpData") - .add("properties", createObjectBuilder() - .add("baseUrl", "http://any") - .build()) - .build() - ) - .add("callbackAddresses", createCallbackAddress()) - .add("protocol", "dataspace-protocol-http") - .add("counterPartyAddress", "http://connector-address") - .add("contractId", "contractId") - .add("assetId", "assetId") - .build(); - - var id = baseRequest() - .contentType(JSON) - .body(requestBody) - .post("/v2/transferprocesses/") - .then() - .log().ifError() - .statusCode(200) - .extract().jsonPath().getString(ID); - - assertThat(getStore().findById(id)).isNotNull(); } - @Test - void deprovision() { - var id = UUID.randomUUID().toString(); - getStore().save(createTransferProcessBuilder(id).state(COMPLETED.code()).build()); + @Nested + @PostgresqlDbIntegrationTest + class Postgres extends Tests implements PostgresRuntime { - baseRequest() - .contentType(JSON) - .post("/v2/transferprocesses/" + id + "/deprovision") - .then() - .statusCode(204); - } + Postgres() { + super(RUNTIME); + } - @Test - void terminate() { - var id = UUID.randomUUID().toString(); - getStore().save(createTransferProcessBuilder(id).state(REQUESTED.code()).build()); - var requestBody = createObjectBuilder() - .add(CONTEXT, createObjectBuilder().add(VOCAB, EDC_NAMESPACE)) - .add("reason", "any") - .build(); - - baseRequest() - .contentType(JSON) - .body(requestBody) - .post("/v2/transferprocesses/" + id + "/terminate") - .then() - .log().ifError() - .statusCode(204); - } + @BeforeAll + static void beforeAll() { + PostgresqlEndToEndInstance.createDatabase("runtime"); + } - @Test - void request_byState() throws JsonProcessingException { - - var state = DEPROVISIONED; - var tp = createTransferProcessBuilder("test-tp") - .state(state.code()) - .build(); - getStore().save(tp); - - - var content = """ - { - "@context": { - "@vocab": "https://w3id.org/edc/v0.0.1/ns/" - }, - "@type": "QuerySpec", - "filterExpression": [ - { - "operandLeft": "state", - "operandRight": %d, - "operator": "=" - } - ], - "limit": 100, - "offset": 0 - } - """.formatted(state.code()); - var query = JacksonJsonLd.createObjectMapper() - .readValue(content, JsonObject.class); - - var result = baseRequest() - .contentType(JSON) - .body(query) - .post("/v2/transferprocesses/request") - .then() - .statusCode(200) - .extract().body().as(JsonArray.class); - - assertThat(result).isNotEmpty(); - assertThat(result).anySatisfy(it -> assertThat(it.asJsonObject().getString("state")).isEqualTo(state.toString())); } - private TransferProcessStore getStore() { - return controlPlane.getContext().getService(TransferProcessStore.class); - } + abstract static class Tests extends ManagementApiEndToEndTestBase { - private TransferProcess createTransferProcess(String id) { - return createTransferProcessBuilder(id).build(); - } + Tests(EdcRuntimeExtension runtime) { + super(runtime); + } - private TransferProcess.Builder createTransferProcessBuilder(String id) { - return TransferProcess.Builder.newInstance() - .id(id) - .callbackAddresses(List.of(CallbackAddress.Builder.newInstance().uri("http://any").events(emptySet()).build())) - .dataRequest(DataRequest.Builder.newInstance() - .id(UUID.randomUUID().toString()) - .dataDestination(DataAddress.Builder.newInstance() - .type("type") - .build()) - .protocol("dataspace-protocol-http") - .assetId("asset-id") - .contractId("contractId") - .connectorAddress("http://connector/address") - .processId(id) - .build()); - } + @Test + void getAll() { + var id1 = UUID.randomUUID().toString(); + var id2 = UUID.randomUUID().toString(); + getStore().save(createTransferProcess(id1)); + getStore().save(createTransferProcess(id2)); + + baseRequest() + .contentType(JSON) + .body(query(criterion("id", "in", List.of(id1, id2)))) + .post("/v2/transferprocesses/request") + .then() + .log().ifError() + .statusCode(200) + .body("size()", is(2)) + .body("[0].@id", anyOf(is(id1), is(id2))) + .body("[1].@id", anyOf(is(id1), is(id2))); + } + + @Test + void getById() { + getStore().save(createTransferProcess("tp1")); + getStore().save(createTransferProcess("tp2")); + + baseRequest() + .get("/v2/transferprocesses/tp2") + .then() + .statusCode(200) + .body("@id", is("tp2")) + .body(TYPE, is("TransferProcess")); + } + + @Test + void getState() { + getStore().save(createTransferProcessBuilder("tp2").state(COMPLETED.code()).build()); + + baseRequest() + .get("/v2/transferprocesses/tp2/state") + .then() + .statusCode(200) + .contentType(JSON) + .body(TYPE, is("TransferState")) + .body("'state'", is("COMPLETED")); + } + + @Test + void create() { + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(VOCAB, EDC_NAMESPACE)) + .add(TYPE, "TransferRequest") + .add("dataDestination", createObjectBuilder() + .add(TYPE, "DataAddress") + .add("type", "HttpData") + .add("properties", createObjectBuilder() + .add("baseUrl", "http://any") + .build()) + .build() + ) + .add("callbackAddresses", createCallbackAddress()) + .add("protocol", "dataspace-protocol-http") + .add("counterPartyAddress", "http://connector-address") + .add("contractId", "contractId") + .add("assetId", "assetId") + .build(); + + var id = baseRequest() + .contentType(JSON) + .body(requestBody) + .post("/v2/transferprocesses/") + .then() + .log().ifError() + .statusCode(200) + .extract().jsonPath().getString(ID); + + assertThat(getStore().findById(id)).isNotNull(); + } + + @Test + void deprovision() { + var id = UUID.randomUUID().toString(); + getStore().save(createTransferProcessBuilder(id).state(COMPLETED.code()).build()); + + baseRequest() + .contentType(JSON) + .post("/v2/transferprocesses/" + id + "/deprovision") + .then() + .statusCode(204); + } + + @Test + void terminate() { + var id = UUID.randomUUID().toString(); + getStore().save(createTransferProcessBuilder(id).state(REQUESTED.code()).build()); + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(VOCAB, EDC_NAMESPACE)) + .add("reason", "any") + .build(); + + baseRequest() + .contentType(JSON) + .body(requestBody) + .post("/v2/transferprocesses/" + id + "/terminate") + .then() + .log().ifError() + .statusCode(204); + } + + @Test + void request_byState() throws JsonProcessingException { + + var state = DEPROVISIONED; + var tp = createTransferProcessBuilder("test-tp") + .state(state.code()) + .build(); + getStore().save(tp); + + + var content = """ + { + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/" + }, + "@type": "QuerySpec", + "filterExpression": [ + { + "operandLeft": "state", + "operandRight": %d, + "operator": "=" + } + ], + "limit": 100, + "offset": 0 + } + """.formatted(state.code()); + var query = JacksonJsonLd.createObjectMapper() + .readValue(content, JsonObject.class); + + var result = baseRequest() + .contentType(JSON) + .body(query) + .post("/v2/transferprocesses/request") + .then() + .statusCode(200) + .extract().body().as(JsonArray.class); + + assertThat(result).isNotEmpty(); + assertThat(result).anySatisfy(it -> assertThat(it.asJsonObject().getString("state")).isEqualTo(state.toString())); + } + + private TransferProcessStore getStore() { + return runtime.getContext().getService(TransferProcessStore.class); + } + + private TransferProcess createTransferProcess(String id) { + return createTransferProcessBuilder(id).build(); + } + + private TransferProcess.Builder createTransferProcessBuilder(String id) { + return TransferProcess.Builder.newInstance() + .id(id) + .callbackAddresses(List.of(CallbackAddress.Builder.newInstance().uri("http://any").events(emptySet()).build())) + .dataRequest(DataRequest.Builder.newInstance() + .id(UUID.randomUUID().toString()) + .dataDestination(DataAddress.Builder.newInstance() + .type("type") + .build()) + .protocol("dataspace-protocol-http") + .assetId("asset-id") + .contractId("contractId") + .connectorAddress("http://connector/address") + .processId(id) + .build()); + } - private JsonArrayBuilder createCallbackAddress() { - var builder = Json.createArrayBuilder(); - return builder.add(createObjectBuilder() - .add(IS_TRANSACTIONAL, false) - .add(URI, "http://test.local/") - .add(EVENTS, Json.createArrayBuilder().build())); + private JsonArrayBuilder createCallbackAddress() { + var builder = Json.createArrayBuilder(); + return builder.add(createObjectBuilder() + .add(IS_TRANSACTIONAL, false) + .add(URI, "http://test.local/") + .add(EVENTS, Json.createArrayBuilder().build())); + } } }