diff --git a/extensions/control-plane/api/management-api/build.gradle.kts b/extensions/control-plane/api/management-api/build.gradle.kts index f40362c8..67d20b78 100644 --- a/extensions/control-plane/api/management-api/build.gradle.kts +++ b/extensions/control-plane/api/management-api/build.gradle.kts @@ -19,12 +19,6 @@ plugins { dependencies { api(libs.edc.mgmtapi.v5) api(project(":extensions:control-plane:api:management-api:catalog-api")) - api(project(":extensions:control-plane:api:management-api:contract-definition-api")) - api(project(":extensions:control-plane:api:management-api:policy-definition-api")) - api(project(":extensions:control-plane:api:management-api:contract-negotiation-api")) - api(project(":extensions:control-plane:api:management-api:contract-agreement-api")) - api(project(":extensions:control-plane:api:management-api:transfer-process-api")) - api(project(":extensions:control-plane:api:management-api:cel-api")) api(libs.edc.core.mgmtapi.jsonschema) api(libs.edc.mgmtapi.authn.oauth2) api(libs.edc.mgmtapi.authz) diff --git a/extensions/control-plane/api/management-api/cel-api/build.gradle.kts b/extensions/control-plane/api/management-api/cel-api/build.gradle.kts deleted file mode 100644 index f175d59b..00000000 --- a/extensions/control-plane/api/management-api/cel-api/build.gradle.kts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - - - -plugins { - `java-library` - id(libs.plugins.swagger.get().pluginId) - -} - -dependencies { - api(libs.edc.spi.auth) - - implementation(libs.edc.spi.core) - implementation(libs.edc.spi.contract) - implementation(libs.edc.spi.controlplane) - implementation(libs.edc.spi.participantcontext.config) - implementation(libs.edc.spi.asset) - implementation(libs.edc.spi.validator) - implementation(libs.edc.spi.web) - implementation(libs.edc.spi.transform) - implementation(libs.edc.spi.cel) - implementation(libs.edc.lib.controlplane.transform) - implementation(libs.jakarta.annotation) - - implementation(libs.edc.lib.api) - implementation(libs.edc.lib.jersey.providers) - implementation(libs.edc.lib.mgmtapi) - implementation(libs.edc.lib.validator) - - testImplementation(testFixtures(libs.edc.core.jersey)) - testImplementation(libs.restAssured) - testImplementation(libs.awaitility) -} - -edcBuild { - swagger { - apiGroup.set("management-api") - } -} - - diff --git a/extensions/control-plane/api/management-api/cel-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/CelExpressionManagementApiExtension.java b/extensions/control-plane/api/management-api/cel-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/CelExpressionManagementApiExtension.java deleted file mode 100644 index 0acbfa80..00000000 --- a/extensions/control-plane/api/management-api/cel-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/CelExpressionManagementApiExtension.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.cel; - -import jakarta.json.Json; -import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema; -import org.eclipse.edc.connector.controlplane.transform.edc.cel.from.JsonObjectFromCelExpressionTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.cel.to.JsonObjectToCelExpressionTransformer; -import org.eclipse.edc.jsonld.spi.JsonLd; -import org.eclipse.edc.policy.cel.service.CelPolicyExpressionService; -import org.eclipse.edc.runtime.metamodel.annotation.Extension; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.spi.types.TypeManager; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; -import org.eclipse.edc.virtual.connector.controlplane.api.management.cel.v5.CelExpressionApiV5Controller; -import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; -import org.eclipse.edc.web.spi.WebService; -import org.eclipse.edc.web.spi.configuration.ApiContext; - -import java.util.Map; - -import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_SCOPE_V4; -import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; -import static org.eclipse.edc.virtual.connector.controlplane.api.management.cel.CelExpressionManagementApiExtension.NAME; - -@Extension(value = NAME) -public class CelExpressionManagementApiExtension implements ServiceExtension { - - public static final String NAME = "CEL management API Extension"; - - @Inject - private WebService webService; - - @Inject - private TypeTransformerRegistry transformerRegistry; - - @Inject - private JsonObjectValidatorRegistry validatorRegistry; - - @Inject - private JsonLd jsonLd; - - @Inject - private TypeManager typeManager; - - @Inject - private CelPolicyExpressionService service; - - @Override - public void initialize(ServiceExtensionContext context) { - var factory = Json.createBuilderFactory(Map.of()); - - var managementApiTransformerRegistry = transformerRegistry.forContext("management-api"); - - managementApiTransformerRegistry.register(new JsonObjectFromCelExpressionTransformer(factory)); - managementApiTransformerRegistry.register(new JsonObjectToCelExpressionTransformer()); - - webService.registerResource(ApiContext.MANAGEMENT, new CelExpressionApiV5Controller(service, managementApiTransformerRegistry)); - webService.registerDynamicResource(ApiContext.MANAGEMENT, CelExpressionApiV5Controller.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, MANAGEMENT_SCOPE_V4, validatorRegistry, ManagementApiJsonSchema.V4.version())); - - } -} diff --git a/extensions/control-plane/api/management-api/cel-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/v5/CelExpressionApiV5.java b/extensions/control-plane/api/management-api/cel-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/v5/CelExpressionApiV5.java deleted file mode 100644 index a806ce96..00000000 --- a/extensions/control-plane/api/management-api/cel-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/v5/CelExpressionApiV5.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2026 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.cel.v5; - -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.info.Info; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema; - -@OpenAPIDefinition(info = @Info(title = "Cel Expressions Management API", version = "v5alpha")) -@Tag(name = "Cel Expressions v5alpha") -public interface CelExpressionApiV5 { - - @Operation(description = "Create a Cel Expression.", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.PARTICIPANT_CONTEXT_CONFIG), mediaType = "application/json")), - responses = { - @ApiResponse(responseCode = "200", description = "The Cel Expression was created successfully"), - @ApiResponse(responseCode = "400", description = "Request body was malformed, or the request could not be processed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)), mediaType = "application/json")), - @ApiResponse(responseCode = "401", description = "The request could not be completed, because either the authentication was missing or was not valid.", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)), mediaType = "application/json")), - @ApiResponse(responseCode = "409", description = "Can't create the Cel expression, because a object with the same ID already exists", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)), mediaType = "application/json")) - } - ) - JsonObject createExpressionV5(JsonObject expression); - - @Operation(description = "Gets an Expression by ID.", - responses = { - @ApiResponse(responseCode = "200", description = "The Cel Expression.", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CEL_EXPRESSION))), - @ApiResponse(responseCode = "400", description = "Request body was malformed, or the request could not be processed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)), mediaType = "application/json")), - @ApiResponse(responseCode = "401", description = "The request could not be completed, because either the authentication was missing or was not valid.", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)), mediaType = "application/json")), - @ApiResponse(responseCode = "404", description = "A Cel Expression with the given ID does not exist.", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)), mediaType = "application/json")) - } - ) - JsonObject getExpressionV5(String id); - - @Operation(description = "Update an Expression.", - responses = { - @ApiResponse(responseCode = "204", description = "The Cel Expression was updated successfully.", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CEL_EXPRESSION))), - @ApiResponse(responseCode = "400", description = "Request body was malformed, or the request could not be processed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)), mediaType = "application/json")), - @ApiResponse(responseCode = "401", description = "The request could not be completed, because either the authentication was missing or was not valid.", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)), mediaType = "application/json")), - @ApiResponse(responseCode = "404", description = "A Cel Expression with the given ID does not exist.", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)), mediaType = "application/json")) - } - ) - void updateExpressionV5(String id, JsonObject expression); - - @Operation(description = "Returns all cel expressions according to a query", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.QUERY_SPEC))), - responses = { - @ApiResponse(responseCode = "200", description = "The cel expressions matching the query", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.CEL_EXPRESSION)))), - @ApiResponse(responseCode = "400", description = "Request was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR))))} - ) - JsonArray queryExpressionV5(JsonObject querySpecJson); - - @Operation(description = "Delete an Expression by ID.", - responses = { - @ApiResponse(responseCode = "204", description = "The Cel Expression was deleted.", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CEL_EXPRESSION))), - @ApiResponse(responseCode = "400", description = "Request body was malformed, or the request could not be processed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)), mediaType = "application/json")), - @ApiResponse(responseCode = "401", description = "The request could not be completed, because either the authentication was missing or was not valid.", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)), mediaType = "application/json")), - @ApiResponse(responseCode = "404", description = "A Cel Expression with the given ID does not exist.", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)), mediaType = "application/json")) - } - ) - void deleteExpressionV5(String id); - -} diff --git a/extensions/control-plane/api/management-api/cel-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/v5/CelExpressionApiV5Controller.java b/extensions/control-plane/api/management-api/cel-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/v5/CelExpressionApiV5Controller.java deleted file mode 100644 index 16e6d4f8..00000000 --- a/extensions/control-plane/api/management-api/cel-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/v5/CelExpressionApiV5Controller.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (c) 2026 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.cel.v5; - - -import jakarta.annotation.security.RolesAllowed; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import org.eclipse.edc.api.auth.spi.ParticipantPrincipal; -import org.eclipse.edc.api.auth.spi.RequiredScope; -import org.eclipse.edc.api.model.IdResponse; -import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; -import org.eclipse.edc.policy.cel.model.CelExpression; -import org.eclipse.edc.policy.cel.service.CelPolicyExpressionService; -import org.eclipse.edc.spi.EdcException; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.edc.web.spi.validation.SchemaType; - -import static jakarta.json.stream.JsonCollectors.toJsonArray; -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; -import static org.eclipse.edc.policy.cel.model.CelExpression.CEL_EXPRESSION_TYPE_TERM; -import static org.eclipse.edc.spi.query.QuerySpec.EDC_QUERY_SPEC_TYPE_TERM; -import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper; - -@Consumes(APPLICATION_JSON) -@Produces(APPLICATION_JSON) -@Path("/v5alpha/celexpressions") -public class CelExpressionApiV5Controller implements CelExpressionApiV5 { - - private final CelPolicyExpressionService service; - private final TypeTransformerRegistry transformerRegistry; - - public CelExpressionApiV5Controller(CelPolicyExpressionService service, - TypeTransformerRegistry transformerRegistry) { - this.service = service; - this.transformerRegistry = transformerRegistry; - } - - @POST - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PROVISIONER}) - @RequiredScope("management-api:write") - @Override - public JsonObject createExpressionV5(@SchemaType(CEL_EXPRESSION_TYPE_TERM) JsonObject expression) { - - var expr = transformerRegistry.transform(expression, CelExpression.class) - .orElseThrow(InvalidRequestException::new); - - service.create(expr) - .orElseThrow(exceptionMapper(CelExpression.class, expr.getId())); - - var responseDto = IdResponse.Builder.newInstance() - .id(expr.getId()) - .createdAt(expr.getCreatedAt()) - .build(); - - return transformerRegistry.transform(responseDto, JsonObject.class) - .orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail())); - } - - @GET - @Path("{id}") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PROVISIONER}) - @RequiredScope("management-api:read") - @Override - public JsonObject getExpressionV5(@PathParam("id") String id) { - - var expr = service.findById(id) - .orElseThrow(exceptionMapper(ParticipantContext.class, id)); - - return transformerRegistry.transform(expr, JsonObject.class) - .orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail())); - } - - @PUT - @Path("{id}") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PROVISIONER}) - @RequiredScope("management-api:write") - @Override - public void updateExpressionV5(@PathParam("id") String id, @SchemaType(CEL_EXPRESSION_TYPE_TERM) JsonObject expression) { - - var expr = transformerRegistry.transform(expression, CelExpression.class) - .orElseThrow(InvalidRequestException::new); - - service.update(expr) - .orElseThrow(exceptionMapper(CelExpression.class, id)); - - - } - - @POST - @Path("request") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PROVISIONER}) - @RequiredScope("management-api:read") - @Override - public JsonArray queryExpressionV5(@SchemaType(EDC_QUERY_SPEC_TYPE_TERM) JsonObject querySpecJson) { - QuerySpec querySpec; - if (querySpecJson == null) { - querySpec = QuerySpec.Builder.newInstance().build(); - } else { - querySpec = transformerRegistry.transform(querySpecJson, QuerySpec.class) - .orElseThrow(InvalidRequestException::new); - } - - return service.query(querySpec).orElseThrow(exceptionMapper(CelExpression.class)).stream() - .map(expression -> transformerRegistry.transform(expression, JsonObject.class)) - .filter(Result::succeeded) - .map(Result::getContent) - .collect(toJsonArray()); - } - - @DELETE - @Path("{id}") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PROVISIONER}) - @RequiredScope("management-api:write") - @Override - public void deleteExpressionV5(@PathParam("id") String id) { - service.delete(id) - .orElseThrow(exceptionMapper(ParticipantContext.class, id)); - } -} diff --git a/extensions/control-plane/api/management-api/cel-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/control-plane/api/management-api/cel-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension deleted file mode 100644 index e83b9aaf..00000000 --- a/extensions/control-plane/api/management-api/cel-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ /dev/null @@ -1,15 +0,0 @@ -# -# Copyright (c) 2025 Metaform Systems, Inc. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# -# Contributors: -# Metaform Systems, Inc. - initial API and implementation -# -# - -org.eclipse.edc.virtual.connector.controlplane.api.management.cel.CelExpressionManagementApiExtension \ No newline at end of file diff --git a/extensions/control-plane/api/management-api/cel-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/CelExpressionApiControllerTestBase.java b/extensions/control-plane/api/management-api/cel-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/CelExpressionApiControllerTestBase.java deleted file mode 100644 index 890dc8e3..00000000 --- a/extensions/control-plane/api/management-api/cel-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/CelExpressionApiControllerTestBase.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.cel; - -import io.restassured.specification.RequestSpecification; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import org.eclipse.edc.api.model.IdResponse; -import org.eclipse.edc.participantcontext.spi.config.model.ParticipantContextConfiguration; -import org.eclipse.edc.policy.cel.model.CelExpression; -import org.eclipse.edc.policy.cel.service.CelPolicyExpressionService; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.spi.result.ServiceResult; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.UUID; - -import static io.restassured.RestAssured.given; -import static io.restassured.http.ContentType.JSON; -import static jakarta.json.Json.createObjectBuilder; -import static org.eclipse.edc.api.model.IdResponse.ID_RESPONSE_CREATED_AT; -import static org.eclipse.edc.api.model.IdResponse.ID_RESPONSE_TYPE; -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.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -public abstract class CelExpressionApiControllerTestBase extends RestControllerTestBase { - - protected final TypeTransformerRegistry transformerRegistry = mock(); - protected final CelPolicyExpressionService service = mock(); - - private RequestSpecification baseRequest() { - return given() - .baseUri("http://localhost:" + port + "/" + versionPath()) - .when(); - } - - protected abstract String versionPath(); - - private CelExpression celExpression() { - return CelExpression.Builder.newInstance() - .id(UUID.randomUUID().toString()) - .leftOperand("leftOperand") - .expression("expr") - .description("description") - .build(); - } - - private ParticipantContextConfiguration.Builder createParticipantContextBuilder() { - return ParticipantContextConfiguration.Builder.newInstance(); - } - - @BeforeEach - void setup() { - when(transformerRegistry.transform(isA(IdResponse.class), eq(JsonObject.class))).thenAnswer(a -> { - var idResponse = (IdResponse) a.getArgument(0); - return Result.success(createObjectBuilder() - .add(TYPE, ID_RESPONSE_TYPE) - .add(ID, idResponse.getId()) - .add(ID_RESPONSE_CREATED_AT, idResponse.getCreatedAt()) - .build() - ); - }); - } - - @Nested - class Create { - @Test - void create() { - var expr = celExpression(); - when(transformerRegistry.transform(any(), eq(CelExpression.class))).thenReturn(Result.success(expr)); - - when(service.create(any())).thenReturn(ServiceResult.success()); - var requestBody = Json.createObjectBuilder() - .add("policy", Json.createObjectBuilder() - .add(CONTEXT, "context") - .add(TYPE, "Set") - .build()) - .build(); - - baseRequest() - .body(requestBody) - .contentType(JSON) - .post("/celexpressions") - .then() - .statusCode(200); - verify(transformerRegistry).transform(isA(JsonObject.class), eq(CelExpression.class)); - verify(service).create(expr); - } - - @Test - void create_shouldReturnBadRequest_whenTransformationFails() { - when(transformerRegistry.transform(any(), any())).thenReturn(Result.failure("error")); - var requestBody = Json.createObjectBuilder() - .add("policy", Json.createObjectBuilder() - .add(CONTEXT, "context") - .add(TYPE, "Set") - .build()) - .build(); - - baseRequest() - .body(requestBody) - .contentType(JSON) - .post("/celexpressions") - .then() - .statusCode(400); - verifyNoInteractions(service); - } - } - - @Nested - class FindById { - @Test - void findById() { - var expr = celExpression(); - var expandedBody = Json.createObjectBuilder().add("id", "id").add("createdAt", 1234).build(); - when(service.findById(any())).thenReturn(ServiceResult.success(expr)); - when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.success(expandedBody)); - - baseRequest() - .get("/celexpressions/" + expr.getId()) - .then() - .statusCode(200) - .contentType(JSON) - .body("id", is("id")) - .body("createdAt", is(1234)); - verify(service).findById(expr.getId()); - verify(transformerRegistry).transform(expr, JsonObject.class); - } - - @Test - void get_shouldReturnNotFound_whenNotFound() { - when(service.findById(any())).thenReturn(ServiceResult.notFound("not found")); - - baseRequest() - .get("/celexpressions/id") - .then() - .statusCode(404) - .contentType(JSON); - verifyNoInteractions(transformerRegistry); - } - - } - - @Nested - class Update { - - @Test - void update() { - var expr = celExpression(); - when(transformerRegistry.transform(any(), eq(CelExpression.class))).thenReturn(Result.success(expr)); - - when(service.update(any())).thenReturn(ServiceResult.success()); - - baseRequest() - .body("{}") - .contentType(JSON) - .put("/celexpressions/" + expr.getId()) - .then() - .statusCode(204); - verify(transformerRegistry).transform(isA(JsonObject.class), eq(CelExpression.class)); - verify(service).update(expr); - } - - @Test - void update_shouldReturnBadRequest_whenTransformationFails() { - when(transformerRegistry.transform(any(), any())).thenReturn(Result.failure("error")); - var requestBody = Json.createObjectBuilder() - .add("policy", Json.createObjectBuilder() - .add(CONTEXT, "context") - .add(TYPE, "Set") - .build()) - .build(); - - baseRequest() - .body(requestBody) - .contentType(JSON) - .put("/celexpressions/1") - .then() - .statusCode(400); - verifyNoInteractions(service); - } - } - - @Nested - class Query { - @Test - void query() { - var querySpec = QuerySpec.none(); - var expr = celExpression(); - var expandedBody = Json.createObjectBuilder().add("id", "id").add("createdAt", 1234).build(); - when(transformerRegistry.transform(any(), eq(QuerySpec.class))).thenReturn(Result.success(querySpec)); - when(service.query(any())).thenReturn(ServiceResult.success(List.of(expr))); - when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.success(expandedBody)); - - baseRequest() - .contentType(JSON) - .body("{}") - .post("/celexpressions/request") - .then() - .statusCode(200) - .contentType(JSON) - .body("size()", is(1)) - .body("[0].id", is("id")) - .body("[0].createdAt", is(1234)); - - verify(service).query(querySpec); - verify(transformerRegistry).transform(expr, JsonObject.class); - } - - @Test - void query_whenTransformFails() { - when(service.findById(any())).thenReturn(ServiceResult.notFound("not found")); - when(transformerRegistry.transform(any(JsonObject.class), eq(QuerySpec.class))).thenReturn(Result.failure("failure")); - - baseRequest() - .body("{}") - .contentType(JSON) - .post("/celexpressions/request") - .then() - .statusCode(400) - .contentType(JSON); - } - - } - - @Nested - class Delete { - - @Test - void delete() { - var expr = celExpression(); - when(service.delete(any())).thenReturn(ServiceResult.success()); - - baseRequest() - .delete("/celexpressions/" + expr.getId()) - .then() - .statusCode(204); - - verify(service).delete(expr.getId()); - } - - @Test - void delete_shouldReturnNotFound_whenNotFound() { - when(service.delete(any())).thenReturn(ServiceResult.notFound("not found")); - - baseRequest() - .delete("/celexpressions/id") - .then() - .statusCode(404); - - } - - } - - -} diff --git a/extensions/control-plane/api/management-api/cel-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/v5/CelExpressionApiV5ControllerTest.java b/extensions/control-plane/api/management-api/cel-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/v5/CelExpressionApiV5ControllerTest.java deleted file mode 100644 index aea504fa..00000000 --- a/extensions/control-plane/api/management-api/cel-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/cel/v5/CelExpressionApiV5ControllerTest.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.cel.v5; - - -import org.eclipse.edc.virtual.connector.controlplane.api.management.cel.CelExpressionApiControllerTestBase; - -public class CelExpressionApiV5ControllerTest extends CelExpressionApiControllerTestBase { - @Override - protected String versionPath() { - return "v5alpha"; - } - - @Override - protected Object controller() { - return new CelExpressionApiV5Controller(service, transformerRegistry); - } -} diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/build.gradle.kts b/extensions/control-plane/api/management-api/contract-agreement-api/build.gradle.kts deleted file mode 100644 index 98d17f67..00000000 --- a/extensions/control-plane/api/management-api/contract-agreement-api/build.gradle.kts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - - -plugins { - `java-library` - id(libs.plugins.swagger.get().pluginId) -} - -dependencies { - api(libs.edc.spi.auth) - - implementation(libs.edc.spi.core) - implementation(libs.edc.spi.controlplane) - implementation(libs.edc.spi.asset) - implementation(libs.edc.spi.validator) - implementation(libs.edc.spi.web) - implementation(libs.edc.spi.transform) - implementation(libs.edc.lib.controlplane.transform) - implementation(libs.edc.lib.transform) - implementation(libs.jakarta.annotation) - - implementation(libs.edc.lib.api) - implementation(libs.edc.lib.jersey.providers) - implementation(libs.edc.lib.mgmtapi) - implementation(libs.edc.lib.validator) - - testImplementation(testFixtures(libs.edc.core.jersey)) - testImplementation(libs.restAssured) - testImplementation(libs.awaitility) - -} - -edcBuild { - swagger { - apiGroup.set("management-api") - } -} - - diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/BaseContractAgreementApiController.java b/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/BaseContractAgreementApiController.java deleted file mode 100644 index c017a93a..00000000 --- a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/BaseContractAgreementApiController.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractagreement; - -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.ws.rs.core.SecurityContext; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; -import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService; -import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; -import org.eclipse.edc.spi.EdcException; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; -import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.edc.web.spi.exception.ObjectNotFoundException; -import org.eclipse.edc.web.spi.exception.ValidationFailureException; - -import java.util.Optional; - -import static jakarta.json.stream.JsonCollectors.toJsonArray; -import static org.eclipse.edc.participantcontext.spi.types.ParticipantResource.filterByParticipantContextId; -import static org.eclipse.edc.spi.query.QuerySpec.EDC_QUERY_SPEC_TYPE; -import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper; - -public abstract class BaseContractAgreementApiController { - protected final Monitor monitor; - private final ContractAgreementService service; - private final AuthorizationService authorizationService; - private final TypeTransformerRegistry transformerRegistry; - private final JsonObjectValidatorRegistry validatorRegistry; - - public BaseContractAgreementApiController(ContractAgreementService service, AuthorizationService authorizationService, TypeTransformerRegistry transformerRegistry, - Monitor monitor, JsonObjectValidatorRegistry validatorRegistry) { - this.service = service; - this.authorizationService = authorizationService; - this.transformerRegistry = transformerRegistry; - this.monitor = monitor; - this.validatorRegistry = validatorRegistry; - } - - public JsonArray queryAgreements(String participantContextId, JsonObject querySpecJson, SecurityContext securityContext) { - authorizationService.authorize(securityContext, participantContextId, participantContextId, ParticipantContext.class) - .orElseThrow(exceptionMapper(ParticipantContext.class, participantContextId)); - - QuerySpec querySpec; - if (querySpecJson == null) { - querySpec = QuerySpec.Builder.newInstance().build(); - } else { - validatorRegistry.validate(EDC_QUERY_SPEC_TYPE, querySpecJson).orElseThrow(ValidationFailureException::new); - - querySpec = transformerRegistry.transform(querySpecJson, QuerySpec.class) - .orElseThrow(InvalidRequestException::new); - } - - var query = querySpec.toBuilder() - .filter(filterByParticipantContextId(participantContextId)) - .build(); - - return service.search(query).orElseThrow(exceptionMapper(ContractDefinition.class, null)).stream() - .map(it -> transformerRegistry.transform(it, JsonObject.class)) - .peek(r -> r.onFailure(f -> monitor.warning(f.getFailureDetail()))) - .filter(Result::succeeded) - .map(Result::getContent) - .collect(toJsonArray()); - } - - public JsonObject getAgreementById(String participantContextId, String id, SecurityContext securityContext) { - authorizationService.authorize(securityContext, participantContextId, id, ContractAgreement.class) - .orElseThrow(exceptionMapper(ContractAgreement.class, id)); - - return Optional.of(id) - .map(service::findById) - .map(it -> transformerRegistry.transform(it, JsonObject.class) - .orElseThrow(failure -> new EdcException(failure.getFailureDetail()))) - .orElseThrow(() -> new ObjectNotFoundException(ContractAgreement.class, id)); - } - - public JsonObject getNegotiationByAgreementId(String participantContextId, String id, SecurityContext securityContext) { - authorizationService.authorize(securityContext, participantContextId, id, ContractAgreement.class) - .orElseThrow(exceptionMapper(ContractAgreement.class, id)); - - return Optional.of(id) - .map(service::findNegotiation) - .map(it -> transformerRegistry.transform(it, JsonObject.class) - .orElseThrow(failure -> new EdcException(failure.getFailureDetail()))) - .orElseThrow(() -> new ObjectNotFoundException(ContractAgreement.class, id)); - } - -} diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtension.java b/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtension.java deleted file mode 100644 index c74820db..00000000 --- a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtension.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractagreement; - -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema; -import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService; -import org.eclipse.edc.jsonld.spi.JsonLd; -import org.eclipse.edc.participantcontext.spi.types.ParticipantResource; -import org.eclipse.edc.runtime.metamodel.annotation.Extension; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.spi.query.Criterion; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.spi.types.TypeManager; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; -import org.eclipse.edc.virtual.connector.controlplane.api.management.contractagreement.v5.ContractAgreementApiV5Controller; -import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; -import org.eclipse.edc.web.spi.WebService; -import org.eclipse.edc.web.spi.configuration.ApiContext; - -import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_API_CONTEXT; -import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_API_V_4; -import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_SCOPE_V4; -import static org.eclipse.edc.participantcontext.spi.types.ParticipantResource.filterByParticipantContextId; -import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; - -@Extension(value = ContractAgreementApiExtension.NAME) -public class ContractAgreementApiExtension implements ServiceExtension { - - public static final String NAME = "Management API: Contract Agreement"; - @Inject - private WebService webService; - - @Inject - private TypeTransformerRegistry transformerRegistry; - - @Inject - private ContractAgreementService service; - - @Inject - private JsonObjectValidatorRegistry validatorRegistry; - - @Inject - private JsonLd jsonLd; - - @Inject - private TypeManager typeManager; - - @Inject - private AuthorizationService authorizationService; - - @Override - public String name() { - return NAME; - } - - @Override - public void initialize(ServiceExtensionContext context) { - var monitor = context.getMonitor(); - - var managementApiTransformerRegistry = transformerRegistry.forContext(MANAGEMENT_API_CONTEXT); - var managementApiTransformerRegistryV4 = managementApiTransformerRegistry.forContext(MANAGEMENT_API_V_4); - - authorizationService.addLookupFunction(ContractAgreement.class, this::findContractAgreement); - - webService.registerResource(ApiContext.MANAGEMENT, new ContractAgreementApiV5Controller(service, authorizationService, managementApiTransformerRegistryV4, monitor, validatorRegistry)); - webService.registerDynamicResource(ApiContext.MANAGEMENT, ContractAgreementApiV5Controller.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, MANAGEMENT_SCOPE_V4, validatorRegistry, ManagementApiJsonSchema.V4.version())); - - } - - private ParticipantResource findContractAgreement(String ownerId, String agreementId) { - return service - .search(QuerySpec.Builder.newInstance() - .filter(filterByParticipantContextId(ownerId)) - .filter(new Criterion("id", "=", agreementId)) - .build() - ) - .map(it -> it.stream().findFirst().orElse(null)) - .orElse(f -> null); - } -} diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/v5/ContractAgreementApiV5.java b/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/v5/ContractAgreementApiV5.java deleted file mode 100644 index 0b858abb..00000000 --- a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/v5/ContractAgreementApiV5.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2026 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractagreement.v5; - -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.info.Info; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.ws.rs.core.SecurityContext; -import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema; - -@OpenAPIDefinition(info = @Info(version = "v5alpha")) -@Tag(name = "Contract Agreement v5alpha") -public interface ContractAgreementApiV5 { - - @Operation(description = "Gets all contract agreements according to a particular query", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.QUERY_SPEC))), - responses = { - @ApiResponse(responseCode = "200", description = "The contract agreements matching the query", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.CONTRACT_AGREEMENT)))), - @ApiResponse(responseCode = "400", description = "Request body was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - JsonArray queryAgreementsV5(String participantContextId, JsonObject querySpecJson, SecurityContext securityContext); - - @Operation(description = "Gets an contract agreement with the given ID", - responses = { - @ApiResponse(responseCode = "200", description = "The contract agreement", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CONTRACT_AGREEMENT))), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "An contract agreement with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - JsonObject getAgreementByIdV5(String participantContextId, String id, SecurityContext securityContext); - - - @Operation(description = "Gets a contract negotiation with the given contract agreement ID", - responses = { - @ApiResponse(responseCode = "200", description = "The contract negotiation", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CONTRACT_AGREEMENT))), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "An contract agreement with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - JsonObject getNegotiationByAgreementIdV5(String participantContextId, String id, SecurityContext securityContext); - -} diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/v5/ContractAgreementApiV5Controller.java b/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/v5/ContractAgreementApiV5Controller.java deleted file mode 100644 index bfb72a83..00000000 --- a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/v5/ContractAgreementApiV5Controller.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2026 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractagreement.v5; - -import jakarta.annotation.security.RolesAllowed; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.SecurityContext; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.auth.spi.ParticipantPrincipal; -import org.eclipse.edc.api.auth.spi.RequiredScope; -import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; -import org.eclipse.edc.virtual.connector.controlplane.api.management.contractagreement.BaseContractAgreementApiController; -import org.eclipse.edc.web.spi.validation.SchemaType; - -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; -import static org.eclipse.edc.spi.query.QuerySpec.EDC_QUERY_SPEC_TYPE_TERM; - -@Consumes(APPLICATION_JSON) -@Produces(APPLICATION_JSON) -@Path("/v5alpha/participants/{participantContextId}/contractagreements") -public class ContractAgreementApiV5Controller extends BaseContractAgreementApiController implements ContractAgreementApiV5 { - public ContractAgreementApiV5Controller(ContractAgreementService service, AuthorizationService authorizationService, TypeTransformerRegistry transformerRegistry, Monitor monitor, JsonObjectValidatorRegistry validatorRegistry) { - super(service, authorizationService, transformerRegistry, monitor, validatorRegistry); - } - - @POST - @Path("/request") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonArray queryAgreementsV5(@PathParam("participantContextId") String participantContextId, - @SchemaType(EDC_QUERY_SPEC_TYPE_TERM) JsonObject querySpecJson, - @Context SecurityContext securityContext) { - return queryAgreements(participantContextId, querySpecJson, securityContext); - } - - @GET - @Path("{id}") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonObject getAgreementByIdV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @Context SecurityContext securityContext) { - return getAgreementById(participantContextId, id, securityContext); - } - - @GET - @Path("{id}/negotiation") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonObject getNegotiationByAgreementIdV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @Context SecurityContext securityContext) { - return getNegotiationByAgreementId(participantContextId, id, securityContext); - } -} diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/control-plane/api/management-api/contract-agreement-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension deleted file mode 100644 index 5d20d74e..00000000 --- a/extensions/control-plane/api/management-api/contract-agreement-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ /dev/null @@ -1,15 +0,0 @@ -# -# Copyright (c) 2025 Metaform Systems, Inc. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# -# Contributors: -# Metaform Systems, Inc. - initial API and implementation -# -# - -org.eclipse.edc.virtual.connector.controlplane.api.management.contractagreement.ContractAgreementApiExtension diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/BaseContractAgreementApiControllerTest.java b/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/BaseContractAgreementApiControllerTest.java deleted file mode 100644 index 1dafc9bc..00000000 --- a/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/BaseContractAgreementApiControllerTest.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractagreement; - -import io.restassured.specification.RequestSpecification; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService; -import org.eclipse.edc.junit.annotations.ApiTest; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.spi.result.ServiceResult; -import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; -import org.eclipse.edc.validator.spi.ValidationResult; -import org.eclipse.edc.validator.spi.Violation; -import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.UUID; - -import static io.restassured.RestAssured.given; -import static io.restassured.http.ContentType.JSON; -import static java.util.Collections.emptyList; -import static java.util.UUID.randomUUID; -import static org.eclipse.edc.spi.query.QuerySpec.EDC_QUERY_SPEC_TYPE; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.notNullValue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -@ApiTest -public abstract class BaseContractAgreementApiControllerTest extends RestControllerTestBase { - - protected final ContractAgreementService service = mock(); - protected final TypeTransformerRegistry transformerRegistry = mock(); - protected final JsonObjectValidatorRegistry validatorRegistry = mock(); - protected final AuthorizationService authorizationService = mock(); - private final String participantContextId = "test-participant-context-id"; - - protected abstract String versionPath(); - - @BeforeEach - void setup() { - when(authorizationService.authorize(any(), any(), any(), any())).thenReturn(ServiceResult.success()); - } - - @Test - void queryAllAgreements_whenExists() { - var expanded = Json.createObjectBuilder().build(); - when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.success()); - when(transformerRegistry.transform(any(JsonObject.class), eq(QuerySpec.class))).thenReturn(Result.success(QuerySpec.none())); - when(service.search(any(QuerySpec.class))).thenReturn(ServiceResult.success(List.of(createContractAgreement("id1"), createContractAgreement("id2")))); - when(transformerRegistry.transform(any(ContractAgreement.class), eq(JsonObject.class))).thenReturn(Result.success(expanded)); - - baseRequest(participantContextId) - .contentType(JSON) - .body("{}") - .post("/request") - .then() - .statusCode(200) - .body("size()", equalTo(2)); - - verify(validatorRegistry).validate(eq(EDC_QUERY_SPEC_TYPE), any()); - verify(transformerRegistry).transform(any(JsonObject.class), eq(QuerySpec.class)); - verify(service).search(any(QuerySpec.class)); - verify(transformerRegistry, times(2)).transform(any(ContractAgreement.class), eq(JsonObject.class)); - verifyNoMoreInteractions(service, transformerRegistry); - } - - @Test - void queryAllAgreements_whenNoneExists() { - when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.success()); - when(transformerRegistry.transform(any(JsonObject.class), eq(QuerySpec.class))).thenReturn(Result.success(QuerySpec.none())); - when(service.search(any(QuerySpec.class))).thenReturn(ServiceResult.success(emptyList())); - - baseRequest(participantContextId) - .contentType(JSON) - .body("{}") - .post("/request") - .then() - .statusCode(200) - .body("size()", equalTo(0)); - - verify(transformerRegistry).transform(any(JsonObject.class), eq(QuerySpec.class)); - verify(service).search(any(QuerySpec.class)); - verify(transformerRegistry, never()).transform(any(ContractAgreement.class), eq(JsonObject.class)); - verifyNoMoreInteractions(service, transformerRegistry); - } - - @Test - void queryAllAgreements_shouldReturnBadRequest_whenValidationFails() { - when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.failure(Violation.violation("failure", "failing path"))); - - baseRequest(participantContextId) - .contentType(JSON) - .body("{}") - .post("/request") - .then() - .statusCode(400); - - verifyNoInteractions(service, transformerRegistry); - } - - @Test - void queryAllAgreements_whenTransformationFails() { - when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.success()); - when(transformerRegistry.transform(any(JsonObject.class), eq(QuerySpec.class))).thenReturn(Result.success(QuerySpec.none())); - when(service.search(any(QuerySpec.class))).thenReturn(ServiceResult.success(List.of(createContractAgreement("id1"), createContractAgreement("id2")))); - when(transformerRegistry.transform(any(ContractAgreement.class), eq(JsonObject.class))).thenReturn(Result.failure("test-failure")); - - baseRequest(participantContextId) - .contentType(JSON) - .body("{}") - .post("/request") - .then() - .statusCode(200) - .body("size()", equalTo(0)); - - verify(transformerRegistry).transform(any(JsonObject.class), eq(QuerySpec.class)); - verify(service).search(any(QuerySpec.class)); - verify(transformerRegistry, times(2)).transform(any(ContractAgreement.class), eq(JsonObject.class)); - verify(monitor, times(2)).warning(eq("test-failure")); - verifyNoMoreInteractions(service, transformerRegistry); - } - - @Test - void getContractAgreement() { - when(service.findById(eq("id1"))).thenReturn(createContractAgreement("id1")); - when(transformerRegistry.transform(any(ContractAgreement.class), eq(JsonObject.class))).thenReturn(Result.success(Json.createObjectBuilder().build())); - - baseRequest(participantContextId) - .contentType(JSON) - .get("/id1") - .then() - .statusCode(200) - .body(notNullValue()); - - verify(service).findById(eq("id1")); - verify(transformerRegistry).transform(any(ContractAgreement.class), eq(JsonObject.class)); - verifyNoMoreInteractions(service, transformerRegistry); - } - - @Test - void getContractAgreement_notFound() { - when(service.findById(eq("id1"))).thenReturn(null); - - baseRequest(participantContextId) - .contentType(JSON) - .get("/id1") - .then() - .statusCode(404) - .body(notNullValue()); - - verify(service).findById(eq("id1")); - verifyNoMoreInteractions(service, transformerRegistry); - } - - @Test - void getContractAgreement_transformationFails() { - when(service.findById(eq("id1"))).thenReturn(createContractAgreement("id1")); - when(transformerRegistry.transform(any(ContractAgreement.class), eq(JsonObject.class))).thenReturn(Result.failure("test-failure")); - - baseRequest(participantContextId) - .contentType(JSON) - .get("/id1") - .then() - .statusCode(500); - - verify(service).findById(eq("id1")); - verify(transformerRegistry).transform(any(ContractAgreement.class), eq(JsonObject.class)); - verifyNoMoreInteractions(service, transformerRegistry); - } - - - @Test - void getContractNegotiation() { - when(service.findNegotiation(any())).thenReturn(createContractNegotiationBuilder("negotiation-id").build()); - when(transformerRegistry.transform(any(ContractNegotiation.class), eq(JsonObject.class))).thenReturn(Result.success(Json.createObjectBuilder().build())); - - baseRequest(participantContextId) - .contentType(JSON) - .get("/agreement-id/negotiation") - .then() - .statusCode(200) - .body(notNullValue()); - - verify(service).findNegotiation(eq("agreement-id")); - verify(transformerRegistry).transform(any(ContractNegotiation.class), eq(JsonObject.class)); - verifyNoMoreInteractions(service, transformerRegistry); - } - - @Test - void getContractNegotiation_notFound() { - when(service.findNegotiation(any())).thenReturn(null); - - baseRequest(participantContextId) - .contentType(JSON) - .get("/agreement-id/negotiation") - .then() - .statusCode(404) - .body(notNullValue()); - - verify(service).findNegotiation("agreement-id"); - verifyNoMoreInteractions(service, transformerRegistry); - } - - @Test - void getContractNegotiation_transformationFails() { - when(service.findNegotiation(any())).thenReturn(createContractNegotiationBuilder("agreement-id").build()); - when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.failure("test-failure")); - - baseRequest(participantContextId) - .contentType(JSON) - .get("/agreement-id/negotiation") - .then() - .statusCode(500); - - verify(service).findNegotiation("agreement-id"); - verify(transformerRegistry).transform(isA(ContractNegotiation.class), eq(JsonObject.class)); - verifyNoMoreInteractions(service, transformerRegistry); - } - - private RequestSpecification baseRequest(String participantContextId) { - return given() - .baseUri("http://localhost:" + port + "/" + versionPath() + "/participants/" + participantContextId + "/contractagreements") - .when(); - } - - private ContractAgreement createContractAgreement(String negotiationId) { - return ContractAgreement.Builder.newInstance() - .id(negotiationId) - .consumerId("test-consumer") - .providerId("test-provider") - .assetId(UUID.randomUUID().toString()) - .policy(Policy.Builder.newInstance().build()) - .build(); - } - - - private ContractNegotiation.Builder createContractNegotiationBuilder(String negotiationId) { - return ContractNegotiation.Builder.newInstance() - .id(negotiationId) - .counterPartyId(randomUUID().toString()) - .counterPartyAddress("address") - .callbackAddresses(List.of(CallbackAddress.Builder.newInstance() - .uri("local://test") - .build())) - .protocol("protocol"); - } - -} diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtensionTest.java b/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtensionTest.java deleted file mode 100644 index 45d94c73..00000000 --- a/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/ContractAgreementApiExtensionTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractagreement; - -import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.virtual.connector.controlplane.api.management.contractagreement.v5.ContractAgreementApiV5Controller; -import org.eclipse.edc.web.spi.WebService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(DependencyInjectionExtension.class) -class ContractAgreementApiExtensionTest { - private final WebService webService = mock(WebService.class); - - private final TypeTransformerRegistry transformerRegistry = mock(); - - @BeforeEach - void setUp(ServiceExtensionContext context) { - context.registerService(WebService.class, webService); - context.registerService(TypeTransformerRegistry.class, transformerRegistry); - - when(transformerRegistry.forContext(any())).thenReturn(mock()); - } - - @Test - void initiate_shouldRegisterControllers(ContractAgreementApiExtension extension, ServiceExtensionContext context) { - extension.initialize(context); - - verify(webService).registerResource(any(), isA(ContractAgreementApiV5Controller.class)); - } -} diff --git a/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/v5/ContractAgreementApiV5ControllerTest.java b/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/v5/ContractAgreementApiV5ControllerTest.java deleted file mode 100644 index 4fa01608..00000000 --- a/extensions/control-plane/api/management-api/contract-agreement-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractagreement/v5/ContractAgreementApiV5ControllerTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractagreement.v5; - -import org.eclipse.edc.virtual.connector.controlplane.api.management.contractagreement.BaseContractAgreementApiControllerTest; - -class ContractAgreementApiV5ControllerTest extends BaseContractAgreementApiControllerTest { - @Override - protected Object controller() { - return new ContractAgreementApiV5Controller(service, authorizationService, transformerRegistry, monitor, validatorRegistry); - } - - @Override - protected String versionPath() { - return "v5alpha"; - } -} \ No newline at end of file diff --git a/extensions/control-plane/api/management-api/contract-definition-api/build.gradle.kts b/extensions/control-plane/api/management-api/contract-definition-api/build.gradle.kts deleted file mode 100644 index 7e99956a..00000000 --- a/extensions/control-plane/api/management-api/contract-definition-api/build.gradle.kts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - - - -plugins { - `java-library` - id(libs.plugins.swagger.get().pluginId) - -} - -dependencies { - api(libs.edc.spi.auth) - - implementation(libs.edc.spi.core) - implementation(libs.edc.spi.contract) - implementation(libs.edc.spi.controlplane) - implementation(libs.edc.spi.asset) - implementation(libs.edc.spi.validator) - implementation(libs.edc.spi.web) - implementation(libs.edc.spi.transform) - implementation(libs.jakarta.annotation) - - implementation(libs.edc.lib.api) - implementation(libs.edc.lib.jersey.providers) - implementation(libs.edc.lib.mgmtapi) - implementation(libs.edc.lib.validator) - implementation(libs.edc.lib.controlplane.transform) - - testImplementation(testFixtures(libs.edc.core.jersey)) - testImplementation(libs.restAssured) - testImplementation(libs.awaitility) -} - -edcBuild { - swagger { - apiGroup.set("management-api") - } -} - - diff --git a/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/ContractDefinitionApiExtension.java b/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/ContractDefinitionApiExtension.java deleted file mode 100644 index d7728648..00000000 --- a/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/ContractDefinitionApiExtension.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractdefinition; - -import jakarta.json.Json; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema; -import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; -import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService; -import org.eclipse.edc.connector.controlplane.transform.edc.contractdefinition.from.JsonObjectFromContractDefinitionTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.contractdefinition.to.JsonObjectToContractDefinitionTransformer; -import org.eclipse.edc.jsonld.spi.JsonLd; -import org.eclipse.edc.participantcontext.spi.types.ParticipantResource; -import org.eclipse.edc.runtime.metamodel.annotation.Extension; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.spi.query.Criterion; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.spi.types.TypeManager; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; -import org.eclipse.edc.virtual.connector.controlplane.api.management.contractdefinition.v5.ContractDefinitionApiV5Controller; -import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; -import org.eclipse.edc.web.spi.WebService; -import org.eclipse.edc.web.spi.configuration.ApiContext; - -import java.util.Map; - -import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_SCOPE_V4; -import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; - -@Extension(value = ContractDefinitionApiExtension.NAME) -public class ContractDefinitionApiExtension implements ServiceExtension { - - public static final String NAME = "Management API: Contract Definition"; - - @Inject - private WebService webService; - - @Inject - private TypeTransformerRegistry transformerRegistry; - - @Inject - private ContractDefinitionService contractDefinitionService; - - @Inject - private JsonObjectValidatorRegistry validatorRegistry; - - @Inject - private JsonLd jsonLd; - - @Inject - private TypeManager typeManager; - @Inject - private AuthorizationService authorizationService; - - @Override - public String name() { - return NAME; - } - - @Override - public void initialize(ServiceExtensionContext context) { - var jsonFactory = Json.createBuilderFactory(Map.of()); - transformerRegistry.register(new JsonObjectFromContractDefinitionTransformer(jsonFactory, typeManager, JSON_LD)); - transformerRegistry.register(new JsonObjectToContractDefinitionTransformer()); - - var managementApiTransformerRegistry = transformerRegistry.forContext("management-api"); - authorizationService.addLookupFunction(ContractDefinition.class, this::findContractDef); - webService.registerResource(ApiContext.MANAGEMENT, new ContractDefinitionApiV5Controller(managementApiTransformerRegistry, contractDefinitionService, context.getMonitor(), authorizationService)); - webService.registerDynamicResource(ApiContext.MANAGEMENT, ContractDefinitionApiV5Controller.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, MANAGEMENT_SCOPE_V4, validatorRegistry, ManagementApiJsonSchema.V4.version())); - - } - - private ParticipantResource findContractDef(String ownerId, String assetId) { - return contractDefinitionService - .search(QuerySpec.Builder.newInstance() - .filter(new Criterion("participantContextId", "=", ownerId)) - .filter(new Criterion("id", "=", assetId)) - .build() - ) - .map(it -> it.stream().findFirst().orElse(null)) - .orElse(f -> null); - } - -} diff --git a/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/v5/ContractDefinitionApiV5.java b/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/v5/ContractDefinitionApiV5.java deleted file mode 100644 index 4b6b613f..00000000 --- a/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/v5/ContractDefinitionApiV5.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2026 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractdefinition.v5; - -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.info.Info; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.ws.rs.core.SecurityContext; -import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema; - -@OpenAPIDefinition(info = @Info(version = "v5alpha")) -@Tag(name = "Contract Definition v5alpha") -public interface ContractDefinitionApiV5 { - - @Operation(description = "Returns all contract definitions according to a query", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.QUERY_SPEC))), - responses = { - @ApiResponse(responseCode = "200", description = "The contract definitions matching the query", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.CONTRACT_DEFINITION)))), - @ApiResponse(responseCode = "400", description = "Request was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - JsonArray queryContractDefinitionsV5(String participantContextId, JsonObject querySpecJson, SecurityContext securityContext); - - @Operation(description = "Gets an contract definition with the given ID", - responses = { - @ApiResponse(responseCode = "200", description = "The contract definition", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CONTRACT_DEFINITION))), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "An contract agreement with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - JsonObject getContractDefinitionV5(String participantContextId, String id, SecurityContext securityContext); - - @Operation(description = "Creates a new contract definition", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CONTRACT_DEFINITION))), - responses = { - @ApiResponse(responseCode = "200", description = "contract definition was created successfully. Returns the Contract Definition Id and created timestamp", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.ID_RESPONSE))), - @ApiResponse(responseCode = "400", description = "Request body was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "409", description = "Could not create contract definition, because a contract definition with that ID already exists", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR))))} - ) - JsonObject createContractDefinitionV5(String participantContextId, JsonObject createObject, SecurityContext securityContext); - - @Operation(description = "Removes a contract definition with the given ID if possible. " + - "DANGER ZONE: Note that deleting contract definitions can have unexpected results, especially for contract offers that have been sent out or ongoing or contract negotiations.", - responses = { - @ApiResponse(responseCode = "204", description = "Contract definition was deleted successfully"), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "A contract definition with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - void deleteContractDefinitionV5(String participantContextId, String id, SecurityContext securityContext); - - @Operation(description = "Updated a contract definition with the given ID. The supplied JSON structure must be a valid JSON-LD object", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CONTRACT_DEFINITION))), - responses = { - @ApiResponse(responseCode = "204", description = "Contract definition was updated successfully"), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "A contract definition with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - void updateContractDefinitionV5(String participantContextId, JsonObject updateObject, SecurityContext securityContext); - -} diff --git a/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/v5/ContractDefinitionApiV5Controller.java b/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/v5/ContractDefinitionApiV5Controller.java deleted file mode 100644 index 908e396d..00000000 --- a/extensions/control-plane/api/management-api/contract-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/v5/ContractDefinitionApiV5Controller.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) 2026 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractdefinition.v5; - -import jakarta.annotation.security.RolesAllowed; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.SecurityContext; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.auth.spi.ParticipantPrincipal; -import org.eclipse.edc.api.auth.spi.RequiredScope; -import org.eclipse.edc.api.model.IdResponse; -import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; -import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService; -import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; -import org.eclipse.edc.spi.EdcException; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.query.Criterion; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.edc.web.spi.exception.ObjectNotFoundException; -import org.eclipse.edc.web.spi.validation.SchemaType; - -import static jakarta.json.stream.JsonCollectors.toJsonArray; -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; -import static java.util.Optional.ofNullable; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_TYPE_TERM; -import static org.eclipse.edc.spi.query.QuerySpec.EDC_QUERY_SPEC_TYPE_TERM; -import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper; - -@Consumes(APPLICATION_JSON) -@Produces(APPLICATION_JSON) -@Path("/v5alpha/participants/{participantContextId}/contractdefinitions") -public class ContractDefinitionApiV5Controller implements ContractDefinitionApiV5 { - - private final TypeTransformerRegistry typeTransformerRegistry; - private final ContractDefinitionService contractDefinitionService; - private final Monitor monitor; - private final AuthorizationService authorizationService; - - public ContractDefinitionApiV5Controller(TypeTransformerRegistry typeTransformerRegistry, ContractDefinitionService contractDefinitionService, Monitor monitor, AuthorizationService authorizationService) { - this.typeTransformerRegistry = typeTransformerRegistry; - this.contractDefinitionService = contractDefinitionService; - this.monitor = monitor; - this.authorizationService = authorizationService; - } - - @POST - @Path("/request") - @Override - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - public JsonArray queryContractDefinitionsV5(@PathParam("participantContextId") String participantContextId, - @SchemaType(EDC_QUERY_SPEC_TYPE_TERM) JsonObject querySpecJson, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, participantContextId, ParticipantContext.class) - .orElseThrow(exceptionMapper(ParticipantContext.class, participantContextId)); - - QuerySpec querySpec; - if (querySpecJson == null) { - querySpec = QuerySpec.Builder.newInstance().build(); - } else { - querySpec = typeTransformerRegistry.transform(querySpecJson, QuerySpec.class) - .orElseThrow(InvalidRequestException::new); - } - - var query = querySpec.toBuilder() - .filter(new Criterion("participantContextId", "=", participantContextId)) - .build(); - - return contractDefinitionService.search(query).orElseThrow(exceptionMapper(ContractDefinition.class)).stream() - .map(contractDefinition -> typeTransformerRegistry.transform(contractDefinition, JsonObject.class)) - .peek(r -> r.onFailure(f -> monitor.warning(f.getFailureDetail()))) - .filter(Result::succeeded) - .map(Result::getContent) - .collect(toJsonArray()); - } - - @GET - @Path("/{id}") - @Override - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - public JsonObject getContractDefinitionV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @Context SecurityContext securityContext) { - authorizationService.authorize(securityContext, participantContextId, id, ContractDefinition.class) - .orElseThrow(exceptionMapper(ContractDefinition.class, id)); - - return ofNullable(contractDefinitionService.findById(id)) - .map(cd -> typeTransformerRegistry.transform(cd, JsonObject.class)) - .map(Result::getContent) - .orElseThrow(() -> new ObjectNotFoundException(ContractDefinition.class, id)); - } - - @POST - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:write") - @Override - public JsonObject createContractDefinitionV5(@PathParam("participantContextId") String participantContextId, - @SchemaType(CONTRACT_DEFINITION_TYPE_TERM) JsonObject createObject, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, participantContextId, ParticipantContext.class) - .orElseThrow(exceptionMapper(ParticipantContext.class, participantContextId)); - - var contractDef = typeTransformerRegistry.transform(createObject, ContractDefinition.class) - .orElseThrow(InvalidRequestException::new) - .toBuilder() - .participantContextId(participantContextId) - .build(); - - var id = contractDefinitionService.create(contractDef) - .map(cd -> IdResponse.Builder.newInstance() - .id(cd.getId()) - .createdAt(cd.getCreatedAt()) - .build()) - .orElseThrow(exceptionMapper(ContractDefinition.class)); - - return typeTransformerRegistry.transform(id, JsonObject.class) - .orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail())); - } - - @DELETE - @Path("{id}") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:write") - @Override - public void deleteContractDefinitionV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @Context SecurityContext securityContext) { - authorizationService.authorize(securityContext, participantContextId, id, ContractDefinition.class) - .orElseThrow(exceptionMapper(ContractDefinition.class, id)); - - contractDefinitionService.delete(id) - .orElseThrow(exceptionMapper(ContractDefinition.class, id)); - } - - @PUT - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:write") - @Override - public void updateContractDefinitionV5(@PathParam("participantContextId") String participantContextId, - @SchemaType(CONTRACT_DEFINITION_TYPE_TERM) JsonObject updateObject, - @Context SecurityContext securityContext) { - - var updateObj = typeTransformerRegistry.transform(updateObject, ContractDefinition.class) - .orElseThrow(InvalidRequestException::new) - .toBuilder() - .participantContextId(participantContextId) - .build(); - - var id = updateObj.getId(); - - authorizationService.authorize(securityContext, participantContextId, id, ContractDefinition.class) - .orElseThrow(exceptionMapper(ContractDefinition.class, id)); - - contractDefinitionService.update(updateObj) - .orElseThrow(exceptionMapper(ContractDefinition.class, id)); - } -} diff --git a/extensions/control-plane/api/management-api/contract-definition-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/control-plane/api/management-api/contract-definition-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension deleted file mode 100644 index c8054450..00000000 --- a/extensions/control-plane/api/management-api/contract-definition-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ /dev/null @@ -1,15 +0,0 @@ -# -# Copyright (c) 2025 Metaform Systems, Inc. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# -# Contributors: -# Metaform Systems, Inc. - initial API and implementation -# -# - -org.eclipse.edc.virtual.connector.controlplane.api.management.contractdefinition.ContractDefinitionApiExtension \ No newline at end of file diff --git a/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/ContractDefinitionApiControllerTest.java b/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/ContractDefinitionApiControllerTest.java deleted file mode 100644 index 2896243d..00000000 --- a/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/ContractDefinitionApiControllerTest.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractdefinition; - -import io.restassured.common.mapper.TypeRef; -import io.restassured.specification.RequestSpecification; -import jakarta.json.Json; -import jakarta.json.JsonArrayBuilder; -import jakarta.json.JsonObject; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.model.IdResponse; -import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; -import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.spi.result.ServiceResult; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; -import org.eclipse.edc.web.spi.ApiErrorDetail; -import org.hamcrest.Matchers; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import java.util.List; - -import static io.restassured.http.ContentType.JSON; -import static jakarta.json.Json.createObjectBuilder; -import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_ACCESSPOLICY_ID; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_ASSETS_SELECTOR; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_CONTRACTPOLICY_ID; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_TYPE; -import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; -import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; -import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; -import static org.hamcrest.Matchers.greaterThan; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -public abstract class ContractDefinitionApiControllerTest extends RestControllerTestBase { - - protected final ContractDefinitionService service = mock(); - protected final TypeTransformerRegistry transformerRegistry = mock(); - protected final AuthorizationService authService = mock(); - protected final String participantContextId = "test-participant-context-id"; - - @BeforeEach - void setup() { - when(authService.authorize(any(), eq(participantContextId), any(), any())).thenReturn(ServiceResult.success()); - } - - @ParameterizedTest - @ValueSource(strings = {"", "{}"}) - void queryAllContractDefinitions(String body) { - when(service.search(any())).thenReturn(ServiceResult.success(List.of(createContractDefinition().build()))); - when(transformerRegistry.transform(any(JsonObject.class), eq(QuerySpec.class))).thenReturn(Result.success(QuerySpec.Builder.newInstance().build())); - when(transformerRegistry.transform(any(ContractDefinition.class), eq(JsonObject.class))).thenReturn(Result.success(createExpandedJsonObject())); - - baseRequest(participantContextId) - .contentType(JSON) - .body(body) - .post("/request") - .then() - .statusCode(200) - .body("size()", greaterThan(0)); - - verify(service).search(argThat(q -> q.getFilterExpression().size() == 1)); - } - - @Test - void query_authFailure() { - when(authService.authorize(any(), eq(participantContextId), any(), any())).thenReturn(ServiceResult.unauthorized("unauthorized")); - baseRequest(participantContextId) - .contentType(JSON) - .body("{}") - .post("/request") - .then() - .statusCode(403); - verifyNoInteractions(service, transformerRegistry); - } - - @Test - void queryAll_queryTransformationFails() { - when(transformerRegistry.transform(any(JsonObject.class), eq(QuerySpec.class))).thenReturn(Result.failure("test-failure")); - when(service.search(any())).thenReturn(ServiceResult.success(List.of(createContractDefinition().build()))); - - baseRequest(participantContextId) - .contentType(JSON) - .body("{}") - .post("/request") - .then() - .statusCode(400); - - verify(transformerRegistry).transform(any(JsonObject.class), eq(QuerySpec.class)); - verifyNoInteractions(service); - } - - @Test - void queryAll_serviceBadRequest() { - when(transformerRegistry.transform(any(JsonObject.class), eq(QuerySpec.class))).thenReturn(Result.success(QuerySpec.Builder.newInstance().build())); - when(service.search(any())).thenReturn(ServiceResult.badRequest("test-message")); - - var error = baseRequest(participantContextId) - .contentType(JSON) - .body("{}") - .post("/request") - .then() - .statusCode(400) - .extract().body().as(new TypeRef>() { - }) - .get(0); - - assertThat(error.getMessage()).contains("test-message"); - - verify(transformerRegistry).transform(any(JsonObject.class), eq(QuerySpec.class)); - verify(service).search(isA(QuerySpec.class)); - verifyNoMoreInteractions(transformerRegistry); - } - - @Test - void getContractDefById_exists() { - var entity = createContractDefinition().id("test-id").build(); - - when(service.findById(any())).thenReturn(entity); - when(transformerRegistry.transform(any(ContractDefinition.class), eq(JsonObject.class))).thenReturn(Result.success(createExpandedJsonObject())); - baseRequest(participantContextId) - .get("/test-id") - .then() - .statusCode(200) - .body("size()", greaterThan(0)); - - verify(service).findById(eq(entity.getId())); - } - - @Test - void getContractDefById_notExists() { - when(service.findById(any())).thenReturn(null); - - baseRequest(participantContextId) - .get("/test-id") - .then() - .statusCode(404); - - verify(service).findById("test-id"); - verify(transformerRegistry, never()).transform(any(ContractDefinition.class), eq(JsonObject.class)); - } - - @Test - void getById_authFailure() { - when(authService.authorize(any(), eq(participantContextId), any(), any())).thenReturn(ServiceResult.unauthorized("unauthorized")); - baseRequest(participantContextId) - .get("/test-id") - .then() - .statusCode(403); - verifyNoInteractions(service, transformerRegistry); - } - - @Test - void create() { - var entity = createContractDefinition().build(); - var requestJson = createExpandedJsonObject(); - var responseBody = createObjectBuilder().add(TYPE, IdResponse.ID_RESPONSE_TYPE).add(ID, entity.getId()).build(); - when(transformerRegistry.transform(any(JsonObject.class), eq(ContractDefinition.class))).thenReturn(Result.success(entity)); - when(service.create(any(ContractDefinition.class))).thenReturn(ServiceResult.success(entity)); - when(transformerRegistry.transform(any(IdResponse.class), eq(JsonObject.class))).thenReturn(Result.success(responseBody)); - - baseRequest(participantContextId) - .contentType(JSON) - .body(requestJson) - .post() - .then() - .statusCode(200) - .body(ID, Matchers.equalTo(entity.getId())); - - verify(service).create(any(ContractDefinition.class)); - } - - @Test - void create_exists() { - var entity = createContractDefinition().build(); - var requestJson = createExpandedJsonObject(); - when(transformerRegistry.transform(any(JsonObject.class), eq(ContractDefinition.class))).thenReturn(Result.success(entity)); - when(service.create(any(ContractDefinition.class))).thenReturn(ServiceResult.conflict("test-message")); - - baseRequest(participantContextId) - .contentType(JSON) - .body(requestJson) - .post() - .then() - .statusCode(409); - - verify(service).create(any(ContractDefinition.class)); - } - - @Test - void create_transformationFails() { - var requestJson = createObjectBuilder() - .add(TYPE, CONTRACT_DEFINITION_TYPE) - .add(CONTRACT_DEFINITION_ACCESSPOLICY_ID, "ap1") - .add(CONTRACT_DEFINITION_CONTRACTPOLICY_ID, "cp1") - .add(CONTRACT_DEFINITION_ASSETS_SELECTOR, createCriterionBuilder().build()) - .build(); - when(transformerRegistry.transform(any(JsonObject.class), eq(ContractDefinition.class))).thenReturn(Result.failure("test-failure")); - - baseRequest(participantContextId) - .contentType(JSON) - .body(requestJson) - .post() - .then() - .statusCode(400); - - verify(service, never()).create(any(ContractDefinition.class)); - } - - @Test - void create_authFailure() { - when(authService.authorize(any(), eq(participantContextId), any(), any())).thenReturn(ServiceResult.unauthorized("unauthorized")); - baseRequest(participantContextId) - .contentType(JSON) - .body("{}") - .post() - .then() - .statusCode(403); - verifyNoInteractions(service, transformerRegistry); - } - - @Test - void delete_exists() { - var contractDefinition = createContractDefinition().build(); - when(service.delete(eq(contractDefinition.getId()))).thenReturn(ServiceResult.success(contractDefinition)); - - baseRequest(participantContextId) - .delete(contractDefinition.getId()) - .then() - .statusCode(204); - - verify(service).delete(contractDefinition.getId()); - } - - @Test - void delete_notExists() { - var contractDefinition = createContractDefinition().build(); - when(service.delete(eq(contractDefinition.getId()))).thenReturn(ServiceResult.notFound("test-message")); - - baseRequest(participantContextId) - .delete(contractDefinition.getId()) - .then() - .statusCode(404); - - verify(service).delete(contractDefinition.getId()); - } - - @Test - void delete_notPossible() { - var contractDefinition = createContractDefinition().build(); - when(service.delete(eq(contractDefinition.getId()))).thenReturn(ServiceResult.conflict("test-message")); - - baseRequest(participantContextId) - .delete(contractDefinition.getId()) - .then() - .statusCode(409); - - verify(service).delete(contractDefinition.getId()); - } - - @Test - void delete_authFailure() { - when(authService.authorize(any(), eq(participantContextId), any(), any())).thenReturn(ServiceResult.unauthorized("unauthorized")); - baseRequest(participantContextId) - .delete("some-id") - .then() - .statusCode(403); - - verifyNoInteractions(service, transformerRegistry); - } - - @Test - void update_whenExists() { - var entity = createContractDefinition().build(); - var requestJson = createExpandedJsonObject(); - when(transformerRegistry.transform(any(JsonObject.class), eq(ContractDefinition.class))).thenReturn(Result.success(entity)); - when(service.update(any(ContractDefinition.class))).thenReturn(ServiceResult.success()); - - baseRequest(participantContextId) - .contentType(JSON) - .body(requestJson) - .put() - .then() - .statusCode(204); - - verify(service).update(eq(entity)); - } - - @Test - void update_whenNotExists_shouldThrowException() { - var entity = createContractDefinition().build(); - var requestJson = createExpandedJsonObject(); - when(transformerRegistry.transform(any(JsonObject.class), eq(ContractDefinition.class))).thenReturn(Result.success(entity)); - when(service.update(any(ContractDefinition.class))).thenReturn(ServiceResult.notFound("test-message")); - - baseRequest(participantContextId) - .contentType(JSON) - .body(requestJson) - .put() - .then() - .statusCode(404); - - verify(service).update(eq(entity)); - } - - @Test - void update_whenTransformationFails_shouldThrowException() { - var entity = createContractDefinition().build(); - when(transformerRegistry.transform(any(JsonObject.class), eq(ContractDefinition.class))).thenReturn(Result.failure("test-failure")); - when(service.update(any(ContractDefinition.class))).thenReturn(ServiceResult.success()); - - var requestJson = createExpandedJsonObject(); - - baseRequest(participantContextId) - .contentType(JSON) - .body(requestJson) - .put() - .then() - .statusCode(400); - - verify(service, never()).update(eq(entity)); - } - - @Test - void update_authFailure() { - when(authService.authorize(any(), eq(participantContextId), eq("id"), any())).thenReturn(ServiceResult.unauthorized("unauthorized")); - var entity = createContractDefinition().id("id").build(); - when(transformerRegistry.transform(any(JsonObject.class), eq(ContractDefinition.class))).thenReturn(Result.success(entity)); - - baseRequest(participantContextId) - .contentType(JSON) - .body("{}") - .put() - .then() - .statusCode(403); - verify(transformerRegistry).transform(any(JsonObject.class), eq(ContractDefinition.class)); - verifyNoMoreInteractions(service, transformerRegistry); - } - - protected abstract RequestSpecification baseRequest(String participantContextId); - - private JsonArrayBuilder createCriterionBuilder() { - return Json.createArrayBuilder() - .add(createObjectBuilder() - .add(TYPE, EDC_NAMESPACE + "Criterion") - .add(EDC_NAMESPACE + "operandLeft", "foo") - .add(EDC_NAMESPACE + "operator", "=") - .add(EDC_NAMESPACE + "operandRight", "bar") - ); - } - - private JsonObject createExpandedJsonObject() { - return createObjectBuilder() - .add(TYPE, CONTRACT_DEFINITION_TYPE) - .add(ID, "test-id") - .add(CONTRACT_DEFINITION_ACCESSPOLICY_ID, "ap1") - .add(CONTRACT_DEFINITION_CONTRACTPOLICY_ID, "cp1") - .add(CONTRACT_DEFINITION_ASSETS_SELECTOR, createCriterionBuilder().build()) - .build(); - } - - private ContractDefinition.Builder createContractDefinition() { - return ContractDefinition.Builder.newInstance() - .id("1") - .accessPolicyId("ap-id") - .contractPolicyId("cp-id"); - } -} \ No newline at end of file diff --git a/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/v5/ContractDefinitionApiV5ControllerTest.java b/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/v5/ContractDefinitionApiV5ControllerTest.java deleted file mode 100644 index 9a44c905..00000000 --- a/extensions/control-plane/api/management-api/contract-definition-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractdefinition/v5/ContractDefinitionApiV5ControllerTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractdefinition.v5; - -import io.restassured.specification.RequestSpecification; -import org.eclipse.edc.junit.annotations.ApiTest; -import org.eclipse.edc.virtual.connector.controlplane.api.management.contractdefinition.ContractDefinitionApiControllerTest; - -import static io.restassured.RestAssured.given; - -@ApiTest -class ContractDefinitionApiV5ControllerTest extends ContractDefinitionApiControllerTest { - @Override - protected RequestSpecification baseRequest(String participantContextId) { - return given() - .baseUri("http://localhost:" + port + "/v5alpha/participants/%s/contractdefinitions".formatted(participantContextId)) - .when(); - - } - - @Override - protected Object controller() { - return new ContractDefinitionApiV5Controller(transformerRegistry, service, monitor, authService); - } -} \ No newline at end of file diff --git a/extensions/control-plane/api/management-api/contract-negotiation-api/build.gradle.kts b/extensions/control-plane/api/management-api/contract-negotiation-api/build.gradle.kts deleted file mode 100644 index b3a5ebe5..00000000 --- a/extensions/control-plane/api/management-api/contract-negotiation-api/build.gradle.kts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ -plugins { - `java-library` - id(libs.plugins.swagger.get().pluginId) -} - -dependencies { - api(libs.edc.spi.auth) - - implementation(libs.edc.spi.core) - implementation(libs.edc.spi.controlplane) - implementation(libs.edc.spi.asset) - implementation(libs.edc.spi.validator) - implementation(libs.edc.spi.web) - implementation(libs.edc.spi.transform) - implementation(libs.edc.lib.controlplane.transform) - implementation(libs.edc.lib.transform) - implementation(libs.jakarta.annotation) - - implementation(libs.edc.lib.api) - implementation(libs.edc.lib.jersey.providers) - implementation(libs.edc.lib.mgmtapi) - implementation(libs.edc.lib.validator) - - testImplementation(testFixtures(libs.edc.core.jersey)) - testImplementation(libs.restAssured) - testImplementation(libs.awaitility) -} - -edcBuild { - swagger { - apiGroup.set("management-api") - } -} - - diff --git a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiExtension.java b/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiExtension.java deleted file mode 100644 index 2c72b9e5..00000000 --- a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiExtension.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractnegotiation; - -import jakarta.json.Json; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; -import org.eclipse.edc.connector.controlplane.transform.edc.contractagreement.from.JsonObjectFromContractAgreementTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.contractnegotiation.from.JsonObjectFromContractNegotiationTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.contractnegotiation.from.JsonObjectFromNegotiationStateTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.contractnegotiation.to.JsonObjectToContractOfferTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.contractnegotiation.to.JsonObjectToContractRequestTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.contractnegotiation.to.JsonObjectToTerminateNegotiationCommandTransformer; -import org.eclipse.edc.jsonld.spi.JsonLd; -import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService; -import org.eclipse.edc.participantcontext.spi.types.ParticipantResource; -import org.eclipse.edc.runtime.metamodel.annotation.Extension; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.spi.query.Criterion; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.spi.types.TypeManager; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; -import org.eclipse.edc.virtual.connector.controlplane.api.management.contractnegotiation.v5.ContractNegotiationApiV5Controller; -import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; -import org.eclipse.edc.web.spi.WebService; -import org.eclipse.edc.web.spi.configuration.ApiContext; - -import java.util.Map; - -import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_SCOPE_V4; -import static org.eclipse.edc.participantcontext.spi.types.ParticipantResource.filterByParticipantContextId; -import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; - -@Extension(value = ContractNegotiationApiExtension.NAME) -public class ContractNegotiationApiExtension implements ServiceExtension { - - public static final String NAME = "Management API: Contract Negotiation"; - - @Inject - private WebService webService; - - @Inject - private TypeTransformerRegistry transformerRegistry; - - @Inject - private ContractNegotiationService service; - - @Inject - private JsonObjectValidatorRegistry validatorRegistry; - - @Inject - private JsonLd jsonLd; - - @Inject - private TypeManager typeManager; - - @Inject - private AuthorizationService authorizationService; - - @Inject - private ParticipantContextService participantContextService; - - @Override - public String name() { - return NAME; - } - - @Override - public void initialize(ServiceExtensionContext context) { - var factory = Json.createBuilderFactory(Map.of()); - var monitor = context.getMonitor(); - - var managementApiTransformerRegistry = transformerRegistry.forContext("management-api"); - - managementApiTransformerRegistry.register(new JsonObjectToContractRequestTransformer()); - managementApiTransformerRegistry.register(new JsonObjectToContractOfferTransformer()); - managementApiTransformerRegistry.register(new JsonObjectToTerminateNegotiationCommandTransformer()); - managementApiTransformerRegistry.register(new JsonObjectFromContractNegotiationTransformer(factory)); - managementApiTransformerRegistry.register(new JsonObjectFromNegotiationStateTransformer(factory)); - managementApiTransformerRegistry.register(new JsonObjectFromContractAgreementTransformer(factory)); - - authorizationService.addLookupFunction(ContractNegotiation.class, this::findContractNegotiation); - - webService.registerResource(ApiContext.MANAGEMENT, new ContractNegotiationApiV5Controller(service, participantContextService, authorizationService, managementApiTransformerRegistry, monitor)); - webService.registerDynamicResource(ApiContext.MANAGEMENT, ContractNegotiationApiV5Controller.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, MANAGEMENT_SCOPE_V4, validatorRegistry, ManagementApiJsonSchema.V4.version())); - } - - private ParticipantResource findContractNegotiation(String ownerId, String assetId) { - return service - .search(QuerySpec.Builder.newInstance() - .filter(filterByParticipantContextId(ownerId)) - .filter(new Criterion("id", "=", assetId)) - .build() - ) - .map(it -> it.stream().findFirst().orElse(null)) - .orElse(f -> null); - } -} diff --git a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/v5/ContractNegotiationApiV5.java b/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/v5/ContractNegotiationApiV5.java deleted file mode 100644 index 012cf253..00000000 --- a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/v5/ContractNegotiationApiV5.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2026 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractnegotiation.v5; - -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.info.Info; -import io.swagger.v3.oas.annotations.links.Link; -import io.swagger.v3.oas.annotations.links.LinkParameter; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.ws.rs.core.SecurityContext; -import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema; - -@OpenAPIDefinition(info = @Info(version = "v5alpha")) -@Tag(name = "Contract Negotiation v5alpha") -public interface ContractNegotiationApiV5 { - - @Operation(description = "Returns all contract negotiations according to a query", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.QUERY_SPEC))), - responses = { - @ApiResponse(responseCode = "200", description = "The contract negotiations that match the query", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.CONTRACT_NEGOTIATION)))), - @ApiResponse(responseCode = "400", description = "Request was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR))))} - ) - JsonArray queryNegotiationsV5(String participantContextId, JsonObject querySpecJson, SecurityContext securityContext); - - @Operation(description = "Gets a contract negotiation with the given ID", - responses = { - @ApiResponse(responseCode = "200", description = "The contract negotiation", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CONTRACT_NEGOTIATION))), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "An contract negotiation with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - JsonObject getNegotiationV5(String participantContextId, String id, SecurityContext securityContext); - - @Operation(description = "Gets the state of a contract negotiation with the given ID", - responses = { - @ApiResponse(responseCode = "200", description = "The contract negotiation's state", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CONTRACT_NEGOTIATION_STATE))), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "An contract negotiation with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - JsonObject getNegotiationStateV5(String participantContextId, String id, SecurityContext securityContext); - - @Operation(description = "Gets a contract agreement for a contract negotiation with the given ID", - responses = { - @ApiResponse(responseCode = "200", description = "The contract agreement that is attached to the negotiation, or null", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CONTRACT_AGREEMENT))), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "An contract negotiation with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - JsonObject getAgreementForNegotiationV5(String participantContextId, String negotiationId, SecurityContext securityContext); - - @Operation(description = "Initiates a contract negotiation for a given offer and with the given counter part. Please note that successfully invoking this endpoint " + - "only means that the negotiation was initiated. Clients must poll the /{id}/state endpoint to track the state", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.CONTRACT_REQUEST))), - responses = { - @ApiResponse(responseCode = "200", description = "The negotiation was successfully initiated. Returns the contract negotiation ID and created timestamp", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.ID_RESPONSE)), - links = @Link(name = "poll-state", operationId = "getNegotiationStateV3", parameters = { - @LinkParameter(name = "id", expression = "$response.body#/id") - }) - ), - @ApiResponse(responseCode = "400", description = "Request body was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - }) - JsonObject initiateContractNegotiationV5(String participantContextId, JsonObject requestDto, SecurityContext securityContext); - - @Operation(description = "Terminates the contract negotiation.", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.TERMINATE_NEGOTIATION))), - responses = { - @ApiResponse(responseCode = "200", description = "ContractNegotiation is terminating", - links = @Link(name = "poll-state", operationId = "getNegotiationStateV3")), - @ApiResponse(responseCode = "400", description = "Request was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "A contract negotiation with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - void terminateNegotiationV5(String participantContextId, String id, JsonObject terminateNegotiation, SecurityContext securityContext); - - @Operation(description = "Deletes the contract negotiation with the given ID. Only terminated negotiations without agreement will be deleted", - responses = { - @ApiResponse(responseCode = "204", description = "ContractNegotiation is deleted", - links = @Link(name = "poll-state", operationId = "getNegotiationStateV3")), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "A contract negotiation with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "409", description = "The given contract negotiation cannot be deleted due to a wrong state or has existing contract agreement", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - void deleteNegotiationV5(String participantContextId, String id, SecurityContext securityContext); - -} diff --git a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/v5/ContractNegotiationApiV5Controller.java b/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/v5/ContractNegotiationApiV5Controller.java deleted file mode 100644 index f652d812..00000000 --- a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/v5/ContractNegotiationApiV5Controller.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (c) 2026 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractnegotiation.v5; - -import jakarta.annotation.security.RolesAllowed; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.SecurityContext; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.auth.spi.ParticipantPrincipal; -import org.eclipse.edc.api.auth.spi.RequiredScope; -import org.eclipse.edc.api.model.IdResponse; -import org.eclipse.edc.connector.controlplane.contract.spi.types.command.TerminateNegotiationCommand; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractRequest; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.NegotiationState; -import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; -import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService; -import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; -import org.eclipse.edc.spi.EdcException; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.spi.result.ServiceResult; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.edc.web.spi.exception.ObjectNotFoundException; -import org.eclipse.edc.web.spi.validation.SchemaType; - -import java.util.Optional; - -import static jakarta.json.stream.JsonCollectors.toJsonArray; -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.command.TerminateNegotiationCommand.TERMINATE_NEGOTIATION_TYPE_TERM; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractRequest.CONTRACT_REQUEST_TYPE_TERM; -import static org.eclipse.edc.participantcontext.spi.types.ParticipantResource.filterByParticipantContextId; -import static org.eclipse.edc.spi.query.QuerySpec.EDC_QUERY_SPEC_TYPE_TERM; -import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper; - -@Consumes(APPLICATION_JSON) -@Produces(APPLICATION_JSON) -@Path("/v5alpha/participants/{participantContextId}/contractnegotiations") -public class ContractNegotiationApiV5Controller implements ContractNegotiationApiV5 { - private final ContractNegotiationService service; - private final ParticipantContextService participantContextService; - private final AuthorizationService authorizationService; - private final TypeTransformerRegistry transformerRegistry; - private final Monitor monitor; - - public ContractNegotiationApiV5Controller(ContractNegotiationService service, ParticipantContextService participantContextService, AuthorizationService authorizationService, TypeTransformerRegistry transformerRegistry, Monitor monitor) { - this.service = service; - this.participantContextService = participantContextService; - this.authorizationService = authorizationService; - this.transformerRegistry = transformerRegistry; - this.monitor = monitor; - } - - @POST - @Path("/request") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonArray queryNegotiationsV5(@PathParam("participantContextId") String participantContextId, - @SchemaType(EDC_QUERY_SPEC_TYPE_TERM) JsonObject querySpecJson, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, participantContextId, ParticipantContext.class) - .orElseThrow(exceptionMapper(ParticipantContext.class, participantContextId)); - - QuerySpec querySpec; - if (querySpecJson == null) { - querySpec = QuerySpec.Builder.newInstance().build(); - } else { - querySpec = transformerRegistry.transform(querySpecJson, QuerySpec.class) - .orElseThrow(InvalidRequestException::new); - } - - var query = querySpec.toBuilder() - .filter(filterByParticipantContextId(participantContextId)) - .build(); - - return service.search(query).orElseThrow(exceptionMapper(ContractNegotiation.class, null)).stream() - .map(it -> transformerRegistry.transform(it, JsonObject.class)) - .peek(this::logIfError) - .filter(Result::succeeded) - .map(Result::getContent) - .collect(toJsonArray()); - } - - @GET - @Path("/{id}") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonObject getNegotiationV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, id, ContractNegotiation.class) - .orElseThrow(exceptionMapper(ContractNegotiation.class, id)); - - return Optional.of(id) - .map(service::findbyId) - .map(it -> transformerRegistry.transform(it, JsonObject.class)) - .map(Result::getContent) - .orElseThrow(() -> new ObjectNotFoundException(ContractNegotiation.class, id)); - } - - @GET - @Path("/{id}/state") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonObject getNegotiationStateV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, id, ContractNegotiation.class) - .orElseThrow(exceptionMapper(ContractNegotiation.class, id)); - - return Optional.of(id) - .map(service::getState) - .map(NegotiationState::new) - .map(state -> transformerRegistry.transform(state, JsonObject.class)) - .orElseThrow(() -> new ObjectNotFoundException(ContractNegotiation.class, id)) - .orElseThrow(failure -> new EdcException(failure.getFailureDetail())); - } - - @GET - @Path("/{id}/agreement") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonObject getAgreementForNegotiationV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String negotiationId, - @Context SecurityContext securityContext) { - authorizationService.authorize(securityContext, participantContextId, negotiationId, ContractNegotiation.class) - .orElseThrow(exceptionMapper(ContractNegotiation.class, negotiationId)); - - return Optional.of(negotiationId) - .map(service::getForNegotiation) - .map(it -> transformerRegistry.transform(it, JsonObject.class) - .orElseThrow(failure -> new EdcException(failure.getFailureDetail()))) - .orElseThrow(() -> new ObjectNotFoundException(ContractNegotiation.class, negotiationId)); - } - - @POST - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:write") - @Override - public JsonObject initiateContractNegotiationV5(@PathParam("participantContextId") String participantContextId, - @SchemaType(CONTRACT_REQUEST_TYPE_TERM) JsonObject requestObject, - @Context SecurityContext securityContext) { - - - authorizationService.authorize(securityContext, participantContextId, participantContextId, ParticipantContext.class) - .orElseThrow(exceptionMapper(ParticipantContext.class, participantContextId)); - - var contractRequest = transformerRegistry.transform(requestObject, ContractRequest.class) - .orElseThrow(InvalidRequestException::new); - - var participantContext = participantContextService.getParticipantContext(participantContextId) - .orElseThrow(exceptionMapper(ParticipantContext.class, participantContextId)); - - return service.initiateNegotiation(participantContext, contractRequest) - .map(cn -> IdResponse.Builder.newInstance().id(cn.getId()).createdAt(cn.getCreatedAt()).build()) - .compose(idResponse -> ServiceResult.from(transformerRegistry.transform(idResponse, JsonObject.class))) - .orElseThrow(exceptionMapper(ContractNegotiation.class, null)); - - } - - @POST - @Path("/{id}/terminate") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:write") - @Override - public void terminateNegotiationV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @SchemaType(TERMINATE_NEGOTIATION_TYPE_TERM) JsonObject terminateNegotiation, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, id, ContractNegotiation.class) - .orElseThrow(exceptionMapper(ContractNegotiation.class, id)); - - var command = transformerRegistry.transform(terminateNegotiation, TerminateNegotiationCommand.class) - .orElseThrow(InvalidRequestException::new); - - service.terminate(command).orElseThrow(exceptionMapper(ContractNegotiation.class, id)); - } - - @DELETE - @Path("/{id}") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:write") - @Override - public void deleteNegotiationV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, id, ContractNegotiation.class) - .orElseThrow(exceptionMapper(ContractNegotiation.class, id)); - - service.delete(id).orElseThrow(exceptionMapper(ContractNegotiation.class, id)); - } - - private void logIfError(Result result) { - result.onFailure(f -> monitor.warning(f.getFailureDetail())); - } -} diff --git a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension deleted file mode 100644 index f5ce5344..00000000 --- a/extensions/control-plane/api/management-api/contract-negotiation-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ /dev/null @@ -1,15 +0,0 @@ -# -# Copyright (c) 2025 Metaform Systems, Inc. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# -# Contributors: -# Metaform Systems, Inc. - initial API and implementation -# -# - -org.eclipse.edc.virtual.connector.controlplane.api.management.contractnegotiation.ContractNegotiationApiExtension diff --git a/extensions/control-plane/api/management-api/contract-negotiation-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiControllerTestBase.java b/extensions/control-plane/api/management-api/contract-negotiation-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiControllerTestBase.java deleted file mode 100644 index 47ea180c..00000000 --- a/extensions/control-plane/api/management-api/contract-negotiation-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiControllerTestBase.java +++ /dev/null @@ -1,507 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractnegotiation; - -import io.restassured.specification.RequestSpecification; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.model.IdResponse; -import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.controlplane.contract.spi.types.command.TerminateNegotiationCommand; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractRequest; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.NegotiationState; -import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractOffer; -import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; -import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService; -import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.spi.result.ServiceResult; -import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static io.restassured.RestAssured.given; -import static io.restassured.http.ContentType.JSON; -import static jakarta.json.Json.createObjectBuilder; -import static java.lang.String.format; -import static java.util.UUID.randomUUID; -import static org.eclipse.edc.api.model.IdResponse.ID_RESPONSE_TYPE; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.NegotiationState.NEGOTIATION_STATE_TYPE; -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.constants.CoreConstants.EDC_NAMESPACE; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.contains; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -public abstract class ContractNegotiationApiControllerTestBase extends RestControllerTestBase { - protected final ContractNegotiationService service = mock(); - protected final TypeTransformerRegistry transformerRegistry = mock(); - protected final AuthorizationService authorizationService = mock(); - protected final ParticipantContextService participantContextService = mock(); - private final String participantContextId = "test-participant-context-id"; - - @BeforeEach - void setup() { - when(authorizationService.authorize(any(), any(), any(), any())).thenReturn(ServiceResult.success()); - } - - private RequestSpecification baseRequest(String participantContextId) { - return given() - .baseUri("http://localhost:" + port + "/" + versionPath() + "/participants/" + participantContextId) - .when(); - } - - protected abstract String versionPath(); - - private ContractNegotiation createContractNegotiation(String negotiationId) { - return createContractNegotiationBuilder(negotiationId) - .build(); - } - - private ContractAgreement createContractAgreement(String negotiationId) { - return ContractAgreement.Builder.newInstance() - .id(negotiationId) - .consumerId("test-consumer") - .providerId("test-provider") - .assetId(randomUUID().toString()) - .policy(Policy.Builder.newInstance().build()) - .build(); - } - - private ContractNegotiation.Builder createContractNegotiationBuilder(String negotiationId) { - return ContractNegotiation.Builder.newInstance() - .id(negotiationId) - .counterPartyId(randomUUID().toString()) - .counterPartyAddress("address") - .callbackAddresses(List.of(CallbackAddress.Builder.newInstance() - .uri("local://test") - .build())) - .protocol("protocol"); - } - - @Nested - class Delete { - - @Test - void delete_shouldCallService() { - when(service.delete(any())).thenReturn(ServiceResult.success()); - - baseRequest(participantContextId) - .contentType(JSON) - .delete("/contractnegotiations/cn1") - .then() - .statusCode(204); - - verify(service).delete("cn1"); - } - - @Test - void delete_shouldFailDueToWrongState() { - when(service.delete(any())).thenReturn(ServiceResult.conflict(format("Cannot delete negotiation in state: %s".formatted(ContractNegotiationStates.AGREED.name())))); - - baseRequest(participantContextId) - .contentType(JSON) - .delete("/contractnegotiations/cn1") - .then() - .statusCode(409); - - verify(service).delete("cn1"); - } - - @Test - void delete_shouldFailDueToNegotiationNotFound() { - when(service.delete(any())).thenReturn(ServiceResult.notFound("ContractNegotiation negotiationId not found")); - - baseRequest(participantContextId) - .contentType(JSON) - .delete("/contractnegotiations/cn1") - .then() - .statusCode(404); - - verify(service).delete("cn1"); - } - } - - @Nested - class Terminate { - - @Test - void terminate_shouldCallService() { - var command = new TerminateNegotiationCommand("id", "reason"); - when(transformerRegistry.transform(any(JsonObject.class), eq(TerminateNegotiationCommand.class))).thenReturn(Result.success(command)); - when(service.terminate(any())).thenReturn(ServiceResult.success()); - - baseRequest(participantContextId) - .body(Json.createObjectBuilder().add(ID, "id").build()) - .contentType(JSON) - .post("/contractnegotiations/cn1/terminate") - .then() - .statusCode(204); - - verify(service).terminate(command); - } - - @Test - void terminate_shouldReturnBadRequest_whenTransformerFails() { - when(transformerRegistry.transform(any(JsonObject.class), eq(TerminateNegotiationCommand.class))).thenReturn(Result.failure("error")); - - baseRequest(participantContextId) - .body(Json.createObjectBuilder().add(ID, "id").build()) - .contentType(JSON) - .post("/contractnegotiations/cn1/terminate") - .then() - .statusCode(400); - - verifyNoInteractions(service); - } - - @Test - void terminate_shouldReturnError_whenServiceFails() { - var command = new TerminateNegotiationCommand("id", "reason"); - when(transformerRegistry.transform(any(JsonObject.class), eq(TerminateNegotiationCommand.class))).thenReturn(Result.success(command)); - when(service.terminate(any())).thenReturn(ServiceResult.conflict("conflict")); - - baseRequest(participantContextId) - .body(Json.createObjectBuilder().add(ID, "id").build()) - .contentType(JSON) - .post("/contractnegotiations/cn1/terminate") - .then() - .statusCode(409); - } - - } - - @Nested - class Initiate { - - @Test - void initiate() { - var contractNegotiation = createContractNegotiation("cn1"); - var responseBody = createObjectBuilder().add(TYPE, ID_RESPONSE_TYPE).add(ID, contractNegotiation.getId()).build(); - - when(transformerRegistry.transform(any(JsonObject.class), eq(ContractRequest.class))).thenReturn(Result.success( - ContractRequest.Builder.newInstance() - .protocol("test-protocol") - .counterPartyAddress("test-cb") - .contractOffer(ContractOffer.Builder.newInstance() - .id("test-offer-id") - .assetId(randomUUID().toString()) - .policy(Policy.Builder.newInstance().build()) - .build()) - .build())); - - when(participantContextService.getParticipantContext(any())) - .thenReturn(ServiceResult.success(ParticipantContext.Builder.newInstance().participantContextId(participantContextId).identity(participantContextId).build())); - when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.success(responseBody)); - when(service.initiateNegotiation(any(ParticipantContext.class), any(ContractRequest.class))).thenReturn(ServiceResult.success(contractNegotiation)); - - when(transformerRegistry.transform(any(IdResponse.class), eq(JsonObject.class))).thenReturn(Result.success(responseBody)); - - baseRequest(participantContextId) - .contentType(JSON) - .body(createObjectBuilder().build()) - .post("/contractnegotiations") - .then() - .statusCode(200) - .body(ID, is(contractNegotiation.getId())); - - verify(service).initiateNegotiation(any(), any()); - verify(transformerRegistry).transform(any(JsonObject.class), eq(ContractRequest.class)); - verify(transformerRegistry).transform(any(IdResponse.class), eq(JsonObject.class)); - verifyNoMoreInteractions(transformerRegistry, service); - } - - @Test - void initiate_invalidRequest() { - when(transformerRegistry.transform(any(JsonObject.class), any())).thenReturn(Result.failure("test-failure")); - - baseRequest(participantContextId) - .contentType(JSON) - .body(createObjectBuilder().build()) - .post("/contractnegotiations") - .then() - .statusCode(400); - verifyNoMoreInteractions(service); - } - } - - @Nested - class FindAgreement { - - @Test - void getAgreement() { - when(service.getForNegotiation(eq("cn1"))).thenReturn(createContractAgreement("cn1")); - when(transformerRegistry.transform(any(ContractAgreement.class), eq(JsonObject.class))) - .thenReturn(Result.success(createObjectBuilder().build())); - - baseRequest(participantContextId) - .get("/contractnegotiations/cn1/agreement") - .then() - .statusCode(200) - .contentType(JSON) - .body(notNullValue()); - - verify(service).getForNegotiation(eq("cn1")); - verify(transformerRegistry).transform(any(ContractAgreement.class), eq(JsonObject.class)); - verifyNoMoreInteractions(transformerRegistry, service); - } - - @Test - void getAgreement_transformationFails() { - when(service.getForNegotiation(eq("cn1"))).thenReturn(createContractAgreement("cn1")); - when(transformerRegistry.transform(any(ContractAgreement.class), eq(JsonObject.class))) - .thenReturn(Result.failure("test-failure")); - - baseRequest(participantContextId) - .get("/contractnegotiations/cn1/agreement") - .then() - .statusCode(500); - - verify(service).getForNegotiation(eq("cn1")); - verify(transformerRegistry).transform(any(ContractAgreement.class), eq(JsonObject.class)); - verifyNoMoreInteractions(transformerRegistry, service); - } - - @Test - void getAgreement_whenNoneFound() { - when(service.getForNegotiation(eq("cn1"))).thenReturn(null); - - baseRequest(participantContextId) - .get("/contractnegotiations/cn1/agreement") - .then() - .statusCode(404) - .contentType(JSON) - .body(notNullValue()); - - verify(service).getForNegotiation(eq("cn1")); - verifyNoMoreInteractions(transformerRegistry, service); - } - - } - - @Nested - class FindById { - - @Test - void getById() { - when(service.findbyId(anyString())).thenReturn(createContractNegotiation("cn1")); - when(transformerRegistry.transform(any(ContractNegotiation.class), eq(JsonObject.class))) - .thenReturn(Result.success(createObjectBuilder().build())); - - baseRequest(participantContextId) - .get("/contractnegotiations/cn1") - .then() - .statusCode(200) - .contentType(JSON) - .body(notNullValue()); - - verify(service).findbyId(anyString()); - verify(transformerRegistry).transform(any(ContractNegotiation.class), eq(JsonObject.class)); - } - - @Test - void getById_notFound() { - when(service.findbyId(anyString())).thenReturn(null); - - baseRequest(participantContextId) - .get("/contractnegotiations/cn1") - .then() - .statusCode(404) - .contentType(JSON) - .body(notNullValue()); - - verify(service).findbyId(anyString()); - verifyNoInteractions(transformerRegistry); - } - - @Test - void getById_transformationFails() { - when(service.findbyId(eq("cn1"))).thenReturn(createContractNegotiation("cn1")); - when(transformerRegistry.transform(any(ContractNegotiation.class), eq(JsonObject.class))) - .thenReturn(Result.failure("test-failure")); - - baseRequest(participantContextId) - .get("/contractnegotiations/cn1") - .then() - .statusCode(404) - .contentType(JSON) - .body(notNullValue()); - - verify(service).findbyId(anyString()); - verify(transformerRegistry).transform(any(ContractNegotiation.class), eq(JsonObject.class)); - } - - @Test - void getById_onlyState() { - var compacted = createObjectBuilder() - .add(VOCAB, EDC_NAMESPACE) - .add(TYPE, NEGOTIATION_STATE_TYPE) - .add("state", "REQUESTED") - .build(); - - when(service.getState(eq("cn1"))).thenReturn("REQUESTED"); - when(transformerRegistry.transform(any(NegotiationState.class), eq(JsonObject.class))) - .thenReturn(Result.success(compacted)); - - baseRequest(participantContextId) - .get("/contractnegotiations/cn1/state") - .then() - .statusCode(200) - .contentType(JSON) - .body("state", is("REQUESTED")); - verify(service).getState(eq("cn1")); - verify(transformerRegistry).transform(any(NegotiationState.class), eq(JsonObject.class)); - verifyNoMoreInteractions(service, transformerRegistry); - } - } - - @Nested - class Query { - - @Test - void query() { - when(service.search(any(QuerySpec.class))).thenReturn(ServiceResult.success(List.of( - createContractNegotiation("cn1"), - createContractNegotiation("cn2") - ))); - var responseBody = createObjectBuilder().add(ID, "cn").build(); - - when(transformerRegistry.transform(any(ContractNegotiation.class), eq(JsonObject.class))) - .thenReturn(Result.success(responseBody)); - - baseRequest(participantContextId) - .contentType(JSON) - .post("/contractnegotiations/request") - .then() - .statusCode(200) - .contentType(JSON) - .body("size()", is(2)); - - verify(service).search(any(QuerySpec.class)); - verify(transformerRegistry, times(2)).transform(any(ContractNegotiation.class), eq(JsonObject.class)); - } - - - @Test - void query_queryTransformationFails() { - when(service.search(any(QuerySpec.class))).thenReturn(ServiceResult.success(List.of( - createContractNegotiation("cn1"), - createContractNegotiation("cn2") - ))); - when(transformerRegistry.transform(any(JsonObject.class), eq(QuerySpec.class))).thenReturn(Result.failure("test-failure")); - - var requestBody = createObjectBuilder().build(); - baseRequest(participantContextId) - .contentType(JSON) - .body(requestBody) - .post("/contractnegotiations/request") - .then() - .statusCode(400); - - verify(transformerRegistry).transform(any(JsonObject.class), eq(QuerySpec.class)); - verifyNoInteractions(service); - verifyNoMoreInteractions(transformerRegistry); - } - - @Test - void query_dtoTransformationFails() { - when(service.search(any(QuerySpec.class))).thenReturn(ServiceResult.success(List.of( - createContractNegotiation("cn1"), - createContractNegotiation("cn2") - ))); - when(transformerRegistry.transform(any(ContractNegotiation.class), any())) - .thenReturn(Result.failure("test-failure")); - - baseRequest(participantContextId) - .contentType(JSON) - .post("/contractnegotiations/request") - .then() - .statusCode(200) - .contentType(JSON) - .body("size()", is(0)); - - verify(service).search(any(QuerySpec.class)); - } - - @Test - void query_singleFailure_shouldLogError() { - when(service.search(any(QuerySpec.class))).thenReturn(ServiceResult.success(List.of( - createContractNegotiation("cn1"), - createContractNegotiation("cn2") - ))); - when(transformerRegistry.transform(any(ContractNegotiation.class), eq(JsonObject.class))) - .thenReturn(Result.success(createObjectBuilder().build())) - .thenReturn(Result.failure("test-failure")); - - baseRequest(participantContextId) - .contentType(JSON) - .post("/contractnegotiations/request") - .then() - .statusCode(200) - .contentType(JSON) - .body("size()", is(1)); - - verify(service).search(any(QuerySpec.class)); - verify(transformerRegistry, times(2)).transform(any(ContractNegotiation.class), eq(JsonObject.class)); - verify(monitor).warning(contains("test-failure")); - } - - @Test - void query_jsonObjectTransformationFails() { - when(service.search(any(QuerySpec.class))).thenReturn(ServiceResult.success(List.of( - createContractNegotiation("cn1"), - createContractNegotiation("cn2") - ))); - when(transformerRegistry.transform(any(JsonObject.class), eq(QuerySpec.class))).thenReturn(Result.success(QuerySpec.none())); - when(transformerRegistry.transform(any(ContractNegotiation.class), eq(JsonObject.class))) - .thenReturn(Result.failure("test-failure")); - - var requestBody = createObjectBuilder().build(); - baseRequest(participantContextId) - .contentType(JSON) - .body(requestBody) - .post("/contractnegotiations/request") - .then() - .statusCode(200) - .contentType(JSON) - .body("size()", is(0)); - - verify(service).search(any(QuerySpec.class)); - verify(transformerRegistry).transform(any(JsonObject.class), eq(QuerySpec.class)); - verify(transformerRegistry, times(2)).transform(any(ContractNegotiation.class), eq(JsonObject.class)); - } - } -} diff --git a/extensions/control-plane/api/management-api/contract-negotiation-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiExtensionTest.java b/extensions/control-plane/api/management-api/contract-negotiation-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiExtensionTest.java deleted file mode 100644 index f61d9a7f..00000000 --- a/extensions/control-plane/api/management-api/contract-negotiation-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/ContractNegotiationApiExtensionTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractnegotiation; - -import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; -import org.eclipse.edc.virtual.connector.controlplane.api.management.contractnegotiation.v5.ContractNegotiationApiV5Controller; -import org.eclipse.edc.web.spi.WebService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(DependencyInjectionExtension.class) -class ContractNegotiationApiExtensionTest { - - private final JsonObjectValidatorRegistry validatorRegistry = mock(JsonObjectValidatorRegistry.class); - private final WebService webService = mock(); - private final TypeTransformerRegistry typeTransformerRegistry = mock(); - - @BeforeEach - void setUp(ServiceExtensionContext context) { - when(typeTransformerRegistry.forContext(any())).thenReturn(mock()); - context.registerService(TypeTransformerRegistry.class, typeTransformerRegistry); - context.registerService(JsonObjectValidatorRegistry.class, validatorRegistry); - context.registerService(WebService.class, webService); - } - - @Test - void initiate_shouldRegisterControllers(ServiceExtensionContext context, ContractNegotiationApiExtension extension) { - extension.initialize(context); - - verify(webService).registerResource(any(), isA(ContractNegotiationApiV5Controller.class)); - } - -} diff --git a/extensions/control-plane/api/management-api/contract-negotiation-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/v5/ContractNegotiationApiV5ControllerTest.java b/extensions/control-plane/api/management-api/contract-negotiation-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/v5/ContractNegotiationApiV5ControllerTest.java deleted file mode 100644 index 4b6716c7..00000000 --- a/extensions/control-plane/api/management-api/contract-negotiation-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/contractnegotiation/v5/ContractNegotiationApiV5ControllerTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.contractnegotiation.v5; - -import org.eclipse.edc.junit.annotations.ApiTest; -import org.eclipse.edc.virtual.connector.controlplane.api.management.contractnegotiation.ContractNegotiationApiControllerTestBase; - -@ApiTest -class ContractNegotiationApiV5ControllerTest extends ContractNegotiationApiControllerTestBase { - - @Override - protected Object controller() { - return new ContractNegotiationApiV5Controller(service, participantContextService, authorizationService, transformerRegistry, monitor); - } - - @Override - protected String versionPath() { - return "v5alpha"; - } -} diff --git a/extensions/control-plane/api/management-api/policy-definition-api/build.gradle.kts b/extensions/control-plane/api/management-api/policy-definition-api/build.gradle.kts deleted file mode 100644 index 1fa8b62a..00000000 --- a/extensions/control-plane/api/management-api/policy-definition-api/build.gradle.kts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - - - -plugins { - `java-library` - id(libs.plugins.swagger.get().pluginId) - -} - -dependencies { - api(libs.edc.spi.auth) - - implementation(libs.edc.spi.core) - implementation(libs.edc.spi.controlplane) - implementation(libs.edc.spi.web) - implementation(libs.edc.spi.transform) - implementation(libs.edc.lib.controlplane.transform) - implementation(libs.jakarta.annotation) - - implementation(libs.edc.lib.api) - implementation(libs.edc.lib.jersey.providers) - implementation(libs.edc.lib.mgmtapi) - implementation(libs.edc.lib.validator) - - testImplementation(testFixtures(libs.edc.core.jersey)) - testImplementation(libs.restAssured) - testImplementation(libs.awaitility) -} - -edcBuild { - swagger { - apiGroup.set("management-api") - } -} - - diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/PolicyDefinitionApiExtension.java b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/PolicyDefinitionApiExtension.java deleted file mode 100644 index fe72203b..00000000 --- a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/PolicyDefinitionApiExtension.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.policy; - -import jakarta.json.Json; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema; -import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; -import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; -import org.eclipse.edc.connector.controlplane.transform.edc.policy.from.JsonObjectFromPolicyDefinitionTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.policy.from.JsonObjectFromPolicyEvaluationPlanTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.policy.from.JsonObjectFromPolicyValidationResultTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.policy.to.JsonObjectToPolicyDefinitionTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.policy.to.JsonObjectToPolicyEvaluationPlanRequestTransformer; -import org.eclipse.edc.jsonld.spi.JsonLd; -import org.eclipse.edc.participantcontext.spi.types.ParticipantResource; -import org.eclipse.edc.runtime.metamodel.annotation.Extension; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.spi.query.Criterion; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.spi.types.TypeManager; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; -import org.eclipse.edc.virtual.connector.controlplane.api.management.policy.v5.PolicyDefinitionApiV5Controller; -import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; -import org.eclipse.edc.web.spi.WebService; -import org.eclipse.edc.web.spi.configuration.ApiContext; - -import java.util.Map; - -import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_SCOPE_V4; -import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; - -@Extension(value = PolicyDefinitionApiExtension.NAME) -public class PolicyDefinitionApiExtension implements ServiceExtension { - - public static final String NAME = "Management API: Policy Definition"; - - @Inject - private WebService webService; - - @Inject - private TypeTransformerRegistry transformerRegistry; - - @Inject - private PolicyDefinitionService policyDefinitionService; - - @Inject - private JsonObjectValidatorRegistry validatorRegistry; - - @Inject - private JsonLd jsonLd; - - @Inject - private TypeManager typeManager; - @Inject - private AuthorizationService authorizationService; - - @Override - public String name() { - return NAME; - } - - @Override - public void initialize(ServiceExtensionContext context) { - var monitor = context.getMonitor(); - var jsonBuilderFactory = Json.createBuilderFactory(Map.of()); - - var managementApiTransformerRegistry = transformerRegistry.forContext("management-api"); - managementApiTransformerRegistry.register(new JsonObjectToPolicyEvaluationPlanRequestTransformer()); - managementApiTransformerRegistry.register(new JsonObjectToPolicyDefinitionTransformer()); - managementApiTransformerRegistry.register(new JsonObjectFromPolicyDefinitionTransformer(jsonBuilderFactory, typeManager, JSON_LD)); - managementApiTransformerRegistry.register(new JsonObjectFromPolicyValidationResultTransformer(jsonBuilderFactory)); - managementApiTransformerRegistry.register(new JsonObjectFromPolicyEvaluationPlanTransformer(jsonBuilderFactory)); - - authorizationService.addLookupFunction(PolicyDefinition.class, this::findPolicyDefinition); - webService.registerResource(ApiContext.MANAGEMENT, new PolicyDefinitionApiV5Controller(policyDefinitionService, managementApiTransformerRegistry, monitor, authorizationService)); - webService.registerDynamicResource(ApiContext.MANAGEMENT, PolicyDefinitionApiV5Controller.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, MANAGEMENT_SCOPE_V4, validatorRegistry, ManagementApiJsonSchema.V4.version())); - - } - - private ParticipantResource findPolicyDefinition(String ownerId, String policyDefId) { - return policyDefinitionService - .search(QuerySpec.Builder.newInstance() - .filter(new Criterion("participantContextId", "=", ownerId)) - .filter(new Criterion("id", "=", policyDefId)) - .build() - ) - .map(it -> it.stream().findFirst().orElse(null)) - .orElse(f -> null); - } - -} diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/v5/PolicyDefinitionApiV5.java b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/v5/PolicyDefinitionApiV5.java deleted file mode 100644 index 8123ecb4..00000000 --- a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/v5/PolicyDefinitionApiV5.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2026 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.policy.v5; - -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.info.Info; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.ws.rs.core.SecurityContext; -import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema; - -@OpenAPIDefinition(info = @Info(version = "v5alpha")) -@Tag(name = "Policy Definition v5alpha") -public interface PolicyDefinitionApiV5 { - - @Operation(description = "Returns all policy definitions according to a query", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.QUERY_SPEC))), - responses = { - @ApiResponse(responseCode = "200", description = "The policy definitions matching the query", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.POLICY_DEFINITION)))), - @ApiResponse(responseCode = "400", description = "Request was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR))))} - ) - JsonArray queryPolicyDefinitionsV5(String participantContextId, JsonObject querySpecJson, SecurityContext securityContext); - - @Operation(description = "Gets a policy definition with the given ID", - responses = { - @ApiResponse(responseCode = "200", description = "The policy definition", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.POLICY_DEFINITION))), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "An policy definition with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - JsonObject getPolicyDefinitionV5(String participantContextId, String id, SecurityContext securityContext); - - @Operation(description = "Creates a new policy definition", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.POLICY_DEFINITION))), - responses = { - @ApiResponse(responseCode = "200", description = "policy definition was created successfully. Returns the Policy Definition Id and created timestamp", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.ID_RESPONSE))), - @ApiResponse(responseCode = "400", description = "Request body was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "409", description = "Could not create policy definition, because a contract definition with that ID already exists", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR))))} - ) - JsonObject createPolicyDefinitionV5(String participantContextId, JsonObject policyDefinition, SecurityContext securityContext); - - @Operation(description = "Removes a policy definition with the given ID if possible. Deleting a policy definition is " + - "only possible if that policy definition is not yet referenced by a contract definition, in which case an error is returned. " + - "DANGER ZONE: Note that deleting policy definitions can have unexpected results, do this at your own risk!", - responses = { - @ApiResponse(responseCode = "204", description = "Policy definition was deleted successfully"), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "An policy definition with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "409", description = "The policy definition cannot be deleted, because it is referenced by a contract definition", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - void deletePolicyDefinitionV5(String participantContextId, String id, SecurityContext securityContext); - - @Operation(description = "Updates an existing Policy, If the Policy is not found, an error is reported", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.POLICY_DEFINITION))), - responses = { - @ApiResponse(responseCode = "204", description = "policy definition was updated successfully."), - @ApiResponse(responseCode = "400", description = "Request body was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "policy definition could not be updated, because it does not exists", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR))) - } - ) - void updatePolicyDefinitionV5(String participantContextId, String id, JsonObject policyDefinition, SecurityContext securityContext); - - @Operation(description = "Validates an existing Policy, If the Policy is not found, an error is reported", - responses = { - @ApiResponse(responseCode = "200", description = "Returns the validation result", content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.POLICY_VALIDATION_RESULT))), - @ApiResponse(responseCode = "404", description = "policy definition could not be validated, because it does not exists", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR))) - } - ) - JsonObject validatePolicyDefinitionV5(String participantContextId, String id, SecurityContext securityContext); - - - @Operation(description = "Creates an execution plane for an existing Policy, If the Policy is not found, an error is reported", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.POLICY_EVALUATION_REQUEST))), - responses = { - @ApiResponse(responseCode = "200", description = "Returns the evaluation plan", content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.POLICY_EVALUATION_PLAN))), - @ApiResponse(responseCode = "404", description = "An evaluation plan could not be created, because the policy definition does not exists", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR))) - } - ) - JsonObject createExecutionPlanV5(String participantContextId, String id, JsonObject input, SecurityContext securityContext); - -} diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/v5/PolicyDefinitionApiV5Controller.java b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/v5/PolicyDefinitionApiV5Controller.java deleted file mode 100644 index 2c2bed1e..00000000 --- a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/v5/PolicyDefinitionApiV5Controller.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (c) 2026 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.policy.v5; - -import jakarta.annotation.security.RolesAllowed; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.SecurityContext; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.auth.spi.ParticipantPrincipal; -import org.eclipse.edc.api.auth.spi.RequiredScope; -import org.eclipse.edc.api.model.IdResponse; -import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; -import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; -import org.eclipse.edc.connector.controlplane.policy.spi.PolicyEvaluationPlanRequest; -import org.eclipse.edc.connector.controlplane.policy.spi.PolicyValidationResult; -import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; -import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; -import org.eclipse.edc.spi.EdcException; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.query.Criterion; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.edc.web.spi.exception.ObjectNotFoundException; -import org.eclipse.edc.web.spi.validation.SchemaType; - -import java.util.ArrayList; - -import static jakarta.json.stream.JsonCollectors.toJsonArray; -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; -import static java.lang.String.format; -import static org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition.EDC_POLICY_DEFINITION_TYPE_TERM; -import static org.eclipse.edc.spi.query.QuerySpec.EDC_QUERY_SPEC_TYPE_TERM; -import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper; - -@Consumes(APPLICATION_JSON) -@Produces(APPLICATION_JSON) -@Path("/v5alpha/participants/{participantContextId}/policydefinitions") -public class PolicyDefinitionApiV5Controller implements PolicyDefinitionApiV5 { - private final TypeTransformerRegistry typeTransformerRegistry; - private final Monitor monitor; - private final AuthorizationService authorizationService; - private final PolicyDefinitionService policyDefinitionService; - - public PolicyDefinitionApiV5Controller(PolicyDefinitionService policyDefinitionService, TypeTransformerRegistry transformerRegistry, Monitor monitor, AuthorizationService authorizationService) { - this.policyDefinitionService = policyDefinitionService; - this.typeTransformerRegistry = transformerRegistry; - this.monitor = monitor; - this.authorizationService = authorizationService; - } - - - @POST - @Path("request") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonArray queryPolicyDefinitionsV5(@PathParam("participantContextId") String participantContextId, - @SchemaType(EDC_QUERY_SPEC_TYPE_TERM) JsonObject querySpecJson, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, participantContextId, ParticipantContext.class) - .orElseThrow(exceptionMapper(ParticipantContext.class, participantContextId)); - QuerySpec querySpec; - if (querySpecJson == null) { - querySpec = QuerySpec.Builder.newInstance().build(); - } else { - querySpec = typeTransformerRegistry.transform(querySpecJson, QuerySpec.class) - .orElseThrow(InvalidRequestException::new); - } - - var query = querySpec.toBuilder() - .filter(new Criterion("participantContextId", "=", participantContextId)) - .build(); - - return policyDefinitionService.search(query).orElseThrow(exceptionMapper(QuerySpec.class, null)) - .stream() - .map(it -> typeTransformerRegistry.transform(it, JsonObject.class)) - .peek(r -> r.onFailure(f -> monitor.warning(f.getFailureDetail()))) - .filter(Result::succeeded) - .map(Result::getContent) - .collect(toJsonArray()); - } - - @GET - @Path("{id}") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonObject getPolicyDefinitionV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, id, PolicyDefinition.class) - .orElseThrow(exceptionMapper(PolicyDefinition.class, id)); - - var definition = policyDefinitionService.findById(id); - if (definition == null) { - throw new ObjectNotFoundException(PolicyDefinition.class, id); - } - - return typeTransformerRegistry.transform(definition, JsonObject.class) - .orElseThrow(failure -> new ObjectNotFoundException(PolicyDefinition.class, id)); - } - - @POST - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:write") - @Override - public JsonObject createPolicyDefinitionV5(@PathParam("participantContextId") String participantContextId, - @SchemaType(EDC_POLICY_DEFINITION_TYPE_TERM) JsonObject policyDefinition, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, participantContextId, ParticipantContext.class) - .orElseThrow(exceptionMapper(ParticipantContext.class, participantContextId)); - - var definition = typeTransformerRegistry.transform(policyDefinition, PolicyDefinition.class) - .orElseThrow(InvalidRequestException::new) - .toBuilder() - .participantContextId(participantContextId) - .build(); - - var createdDefinition = policyDefinitionService.create(definition) - .onSuccess(d -> monitor.debug(format("Policy Definition created %s", d.getId()))) - .orElseThrow(exceptionMapper(PolicyDefinition.class, definition.getId())); - - var responseDto = IdResponse.Builder.newInstance() - .id(createdDefinition.getId()) - .createdAt(createdDefinition.getCreatedAt()) - .build(); - - return typeTransformerRegistry.transform(responseDto, JsonObject.class) - .orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail())); - } - - @DELETE - @Path("{id}") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:write") - @Override - public void deletePolicyDefinitionV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, id, PolicyDefinition.class) - .orElseThrow(exceptionMapper(Asset.class, id)); - - policyDefinitionService.deleteById(id) - .onSuccess(d -> monitor.debug(format("Policy Definition deleted %s", d.getId()))) - .orElseThrow(exceptionMapper(PolicyDefinition.class, id)); - } - - @PUT - @Path("{id}") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:write") - @Override - public void updatePolicyDefinitionV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @SchemaType(EDC_POLICY_DEFINITION_TYPE_TERM) JsonObject input, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, id, PolicyDefinition.class) - .orElseThrow(exceptionMapper(PolicyDefinition.class, id)); - - var policyDefinition = typeTransformerRegistry.transform(input, PolicyDefinition.class) - .orElseThrow(InvalidRequestException::new) - .toBuilder() - .participantContextId(participantContextId) - .build(); - - policyDefinitionService.update(policyDefinition) - .onSuccess(d -> monitor.debug(format("Policy Definition updated %s", d.getId()))) - .orElseThrow(exceptionMapper(PolicyDefinition.class, id)); - } - - @POST - @Path("{id}/validate") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonObject validatePolicyDefinitionV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, id, PolicyDefinition.class) - .orElseThrow(exceptionMapper(PolicyDefinition.class, id)); - - var definition = policyDefinitionService.findById(id); - if (definition == null) { - throw new ObjectNotFoundException(PolicyDefinition.class, id); - } - - var messages = new ArrayList(); - - var result = policyDefinitionService.validate(definition.getPolicy()) - .onFailure(failure -> messages.addAll(failure.getMessages())); - - var validationResult = new PolicyValidationResult(result.succeeded(), messages); - - return typeTransformerRegistry.transform(validationResult, JsonObject.class) - .orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail())); - } - - @POST - @Path("{id}/evaluationplan") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonObject createExecutionPlanV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @SchemaType("PolicyEvaluationPlanRequest") JsonObject input, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, id, PolicyDefinition.class) - .orElseThrow(exceptionMapper(PolicyDefinition.class, id)); - - var planeRequest = typeTransformerRegistry.transform(input, PolicyEvaluationPlanRequest.class) - .orElseThrow(InvalidRequestException::new); - - var definition = policyDefinitionService.findById(id); - if (definition == null) { - throw new ObjectNotFoundException(PolicyDefinition.class, id); - } - - var plan = policyDefinitionService.createEvaluationPlan(planeRequest.policyScope(), definition.getPolicy()) - .orElseThrow(exceptionMapper(PolicyDefinition.class, definition.getId())); - - return typeTransformerRegistry.transform(plan, JsonObject.class) - .orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail())); - } -} diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/control-plane/api/management-api/policy-definition-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension deleted file mode 100644 index 0619404b..00000000 --- a/extensions/control-plane/api/management-api/policy-definition-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ /dev/null @@ -1,15 +0,0 @@ -# -# Copyright (c) 2025 Metaform Systems, Inc. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# -# Contributors: -# Metaform Systems, Inc. - initial API and implementation -# -# - -org.eclipse.edc.virtual.connector.controlplane.api.management.policy.PolicyDefinitionApiExtension \ No newline at end of file diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/PolicyDefinitionApiControllerTestBase.java b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/PolicyDefinitionApiControllerTestBase.java deleted file mode 100644 index 2c0036bf..00000000 --- a/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/PolicyDefinitionApiControllerTestBase.java +++ /dev/null @@ -1,623 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.policy; - -import io.restassured.specification.RequestSpecification; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.model.IdResponse; -import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; -import org.eclipse.edc.connector.controlplane.policy.spi.PolicyEvaluationPlanRequest; -import org.eclipse.edc.connector.controlplane.policy.spi.PolicyValidationResult; -import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; -import org.eclipse.edc.junit.annotations.ApiTest; -import org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.spi.result.ServiceResult; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static io.restassured.RestAssured.given; -import static io.restassured.http.ContentType.JSON; -import static jakarta.json.Json.createObjectBuilder; -import static org.eclipse.edc.api.model.IdResponse.ID_RESPONSE_CREATED_AT; -import static org.eclipse.edc.api.model.IdResponse.ID_RESPONSE_TYPE; -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.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -@ApiTest -public abstract class PolicyDefinitionApiControllerTestBase extends RestControllerTestBase { - - protected final TypeTransformerRegistry transformerRegistry = mock(); - protected final PolicyDefinitionService service = mock(); - protected final AuthorizationService authorizationService = mock(); - private final String participantContextId = "test-participant-context-id"; - - @BeforeEach - void setup() { - when(transformerRegistry.transform(isA(IdResponse.class), eq(JsonObject.class))).thenAnswer(a -> { - var idResponse = (IdResponse) a.getArgument(0); - return Result.success(createObjectBuilder() - .add(TYPE, ID_RESPONSE_TYPE) - .add(ID, idResponse.getId()) - .add(ID_RESPONSE_CREATED_AT, idResponse.getCreatedAt()) - .build() - ); - }); - when(authorizationService.authorize(any(), any(), any(), any())).thenReturn(ServiceResult.success()); - } - - private RequestSpecification baseRequest(String participantContextId) { - return given() - .baseUri("http://localhost:" + port + "/" + versionPath() + "/participants/" + participantContextId) - .when(); - } - - protected abstract String versionPath(); - - @NotNull - private PolicyDefinition.Builder createPolicyDefinition() { - var policy = Policy.Builder.newInstance().build(); - - return PolicyDefinition.Builder.newInstance() - .id("policyDefinitionId") - .createdAt(1234) - .policy(policy); - } - - @Nested - class EvaluationPlane { - @Test - void createEvaluationPlan() { - - var policyScope = "scope"; - var policyDefinition = PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()).build(); - var plan = PolicyEvaluationPlan.Builder.newInstance().build(); - var response = Json.createObjectBuilder().build(); - var body = Json.createObjectBuilder().add("policyScope", policyScope).build(); - - when(service.findById(any())).thenReturn(policyDefinition); - when(service.createEvaluationPlan(policyScope, policyDefinition.getPolicy())).thenReturn(ServiceResult.success(plan)); - - when(transformerRegistry.transform(any(JsonObject.class), eq(PolicyEvaluationPlanRequest.class))) - .thenReturn(Result.success(new PolicyEvaluationPlanRequest(policyScope))); - - when(transformerRegistry.transform(any(PolicyEvaluationPlan.class), eq(JsonObject.class))).thenReturn(Result.success(response)); - - baseRequest(participantContextId) - .contentType(JSON) - .body(body) - .post("/policydefinitions/id/evaluationplan") - .then() - .statusCode(200) - .contentType(JSON); - } - - @Test - void createEvaluationPlan_fails_whenPolicyNotFound() { - - var policyScope = "scope"; - var body = Json.createObjectBuilder().add("policyScope", policyScope).build(); - - when(service.findById(any())).thenReturn(null); - when(transformerRegistry.transform(any(JsonObject.class), eq(PolicyEvaluationPlanRequest.class))) - .thenReturn(Result.success(new PolicyEvaluationPlanRequest(policyScope))); - - baseRequest(participantContextId) - .contentType(JSON) - .body(body) - .post("/policydefinitions/id/evaluationplan") - .then() - .statusCode(404); - } - - @Test - void createEvaluationPlan_authorizationFailed() { - - when(authorizationService.authorize(any(), any(), any(), any())) - .thenReturn(ServiceResult.unauthorized("unauthorized")); - var policyScope = "scope"; - var body = Json.createObjectBuilder().add("policyScope", policyScope).build(); - - baseRequest(participantContextId) - .contentType(JSON) - .body(body.toString()) - .post("/policydefinitions/id/evaluationplan") - .then() - .statusCode(403); - } - } - - @Nested - class Validate { - @Test - void validate_shouldReturnNotFound_whenNotFound() { - when(service.findById(any())).thenReturn(null); - - baseRequest(participantContextId) - .contentType(JSON) - .post("/policydefinitions/id/validate") - .then() - .statusCode(404); - } - - @Test - void validate_shouldReturnValid_whenValidationSucceed() { - - var policyDefinition = PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()).build(); - - when(service.findById(any())).thenReturn(policyDefinition); - when(service.validate(policyDefinition.getPolicy())).thenReturn(ServiceResult.success()); - when(transformerRegistry.transform(any(PolicyValidationResult.class), eq(JsonObject.class))).then(answer -> { - PolicyValidationResult result = answer.getArgument(0); - var response = Json.createObjectBuilder() - .add("isValid", result.isValid()) - .add("errors", Json.createArrayBuilder(result.errors())) - .build(); - return Result.success(response); - }); - - baseRequest(participantContextId) - .contentType(JSON) - .post("/policydefinitions/id/validate") - .then() - .statusCode(200) - .contentType(JSON) - .body("isValid", is(true)) - .body("errors.size()", is(0)); - } - - @Test - void validate_shouldReturnInvalidValid_whenValidationFails() { - - var policyDefinition = PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()).build(); - - - when(service.findById(any())).thenReturn(policyDefinition); - when(service.validate(policyDefinition.getPolicy())).thenReturn(ServiceResult.badRequest(List.of("error1", "error2"))); - when(transformerRegistry.transform(any(PolicyValidationResult.class), eq(JsonObject.class))).then(answer -> { - PolicyValidationResult result = answer.getArgument(0); - var response = Json.createObjectBuilder() - .add("isValid", result.isValid()) - .add("errors", Json.createArrayBuilder(result.errors())) - .build(); - return Result.success(response); - }); - - baseRequest(participantContextId) - .contentType(JSON) - .post("/policydefinitions/id/validate") - .then() - .statusCode(200) - .contentType(JSON) - .body("isValid", is(false)) - .body("errors.size()", is(2)); - } - - @Test - void validate_authorizationFailed() { - when(authorizationService.authorize(any(), any(), any(), any())) - .thenReturn(ServiceResult.unauthorized("unauthorized")); - - baseRequest(participantContextId) - .contentType(JSON) - .post("/policydefinitions/id/validate") - .then() - .statusCode(403); - } - } - - @Nested - class FindById { - @Test - void get_shouldReturnPolicyDefinition() { - var policyDefinition = createPolicyDefinition().build(); - var expandedBody = Json.createObjectBuilder().add("id", "id").add("createdAt", 1234).build(); - when(service.findById(any())).thenReturn(policyDefinition); - when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.success(expandedBody)); - - baseRequest(participantContextId) - .get("/policydefinitions/id") - .then() - .statusCode(200) - .contentType(JSON) - .body("id", is("id")) - .body("createdAt", is(1234)); - verify(service).findById("id"); - verify(transformerRegistry).transform(policyDefinition, JsonObject.class); - } - - @Test - void get_shouldReturnNotFound_whenNotFound() { - when(service.findById(any())).thenReturn(null); - - baseRequest(participantContextId) - .get("/policydefinitions/id") - .then() - .statusCode(404) - .contentType(JSON); - verifyNoInteractions(transformerRegistry); - } - - @Test - void get_shouldReturnNotFound_whenTransformFails() { - when(service.findById(any())).thenReturn(createPolicyDefinition().build()); - when(transformerRegistry.transform(any(), any())).thenReturn(Result.failure("error")); - - baseRequest(participantContextId) - .get("/policydefinitions/id") - .then() - .statusCode(404) - .contentType(JSON); - } - - @Test - void get_authorizationFailed() { - when(authorizationService.authorize(any(), any(), any(), any())) - .thenReturn(ServiceResult.unauthorized("unauthorized")); - - baseRequest(participantContextId) - .contentType(JSON) - .get("/policydefinitions/id") - .then() - .statusCode(403); - } - } - - @Nested - class Search { - @Test - void search_shouldReturnQueriedPolicyDefinitions() { - var querySpec = QuerySpec.none(); - var policyDefinition = createPolicyDefinition().id("id").build(); - var expandedResponseBody = Json.createObjectBuilder().add("id", "id").add("createdAt", 1234).build(); - when(transformerRegistry.transform(any(), eq(QuerySpec.class))).thenReturn(Result.success(querySpec)); - when(service.search(any())).thenReturn(ServiceResult.success(List.of(policyDefinition))); - when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.success(expandedResponseBody)); - var requestBody = Json.createObjectBuilder().build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/policydefinitions/request") - .then() - .statusCode(200) - .contentType(JSON) - .body("size()", is(1)) - .body("[0].id", is("id")) - .body("[0].createdAt", is(1234)); - - verify(transformerRegistry).transform(isA(JsonObject.class), eq(QuerySpec.class)); - verify(service).search(argThat(s -> s.getOffset() == querySpec.getOffset() && - s.getFilterExpression().stream().anyMatch(c -> c.getOperandLeft().equals("participantContextId") && - c.getOperator().equals("=") && - c.getOperandRight().equals(participantContextId)))); - verify(transformerRegistry).transform(policyDefinition, JsonObject.class); - } - - @Test - void search_shouldReturn400_whenInvalidQuery() { - var requestBody = Json.createObjectBuilder() - .add("offset", -1) - .build(); - when(transformerRegistry.transform(any(JsonObject.class), eq(QuerySpec.class))).thenReturn(Result.failure("failure")); - - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/policydefinitions/request") - .then() - .statusCode(400); - - verify(transformerRegistry).transform(any(JsonObject.class), eq(QuerySpec.class)); - verifyNoInteractions(service); - verifyNoMoreInteractions(transformerRegistry); - } - - @Test - void search_shouldReturnBadRequest_whenQuerySpecTransformFails() { - var requestBody = Json.createObjectBuilder().build(); - when(transformerRegistry.transform(any(), eq(QuerySpec.class))).thenReturn(Result.failure("error")); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/policydefinitions/request") - .then() - .statusCode(400) - .contentType(JSON); - verifyNoInteractions(service); - } - - @Test - void search_shouldReturnBadRequest_whenServiceReturnsBadRequest() { - var querySpec = QuerySpec.none(); - when(transformerRegistry.transform(any(), eq(QuerySpec.class))).thenReturn(Result.success(querySpec)); - when(service.search(any())).thenReturn(ServiceResult.badRequest("error")); - var requestBody = Json.createObjectBuilder().build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/policydefinitions/request") - .then() - .statusCode(400) - .contentType(JSON); - } - - @Test - void search_shouldFilterOutResults_whenTransformFails() { - var querySpec = QuerySpec.none(); - var policyDefinition = createPolicyDefinition().id("id").build(); - when(transformerRegistry.transform(any(), eq(QuerySpec.class))).thenReturn(Result.success(querySpec)); - when(service.search(any())).thenReturn(ServiceResult.success(List.of(policyDefinition))); - when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.failure("error")); - var requestBody = Json.createObjectBuilder().build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/policydefinitions/request") - .then() - .statusCode(200) - .contentType(JSON) - .body("size()", is(0)); - } - - @Test - void search_authorizationFailed() { - when(authorizationService.authorize(any(), any(), any(), any())) - .thenReturn(ServiceResult.unauthorized("unauthorized")); - - baseRequest(participantContextId) - .body("{}") - .contentType(JSON) - .post("/policydefinitions/request") - .then() - .statusCode(403); - } - } - - @Nested - class Delete { - @Test - void delete_shouldCallService() { - var policyDefinition = createPolicyDefinition().build(); - when(service.deleteById(any())).thenReturn(ServiceResult.success(policyDefinition)); - - baseRequest(participantContextId) - .delete("/policydefinitions/id") - .then() - .statusCode(204); - - verify(service).deleteById("id"); - } - - @Test - void delete_shouldReturnNotFound_whenNotFound() { - when(service.deleteById(any())).thenReturn(ServiceResult.notFound("not found")); - - baseRequest(participantContextId) - .delete("/policydefinitions/id") - .then() - .statusCode(404); - } - - @Test - void delete_authorizationFailed() { - when(authorizationService.authorize(any(), any(), any(), any())) - .thenReturn(ServiceResult.unauthorized("unauthorized")); - - baseRequest(participantContextId) - .contentType(JSON) - .delete("/policydefinitions/id") - .then() - .statusCode(403); - } - } - - @Nested - class Update { - @Test - void update_shouldCallService() { - var policyDefinition = createPolicyDefinition().build(); - when(transformerRegistry.transform(any(), eq(PolicyDefinition.class))).thenReturn(Result.success(policyDefinition)); - when(service.update(any())).thenReturn(ServiceResult.success(policyDefinition)); - var requestBody = Json.createObjectBuilder() - .add("policy", Json.createObjectBuilder() - .add(CONTEXT, "context") - .add(TYPE, "Set") - .build()) - .build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .put("/policydefinitions/id") - .then() - .statusCode(204); - verify(transformerRegistry).transform(isA(JsonObject.class), eq(PolicyDefinition.class)); - verify(service).update(policyDefinition); - } - - @Test - void update_shouldReturnBadRequest_whenTransformationFails() { - when(transformerRegistry.transform(any(), any())).thenReturn(Result.failure("error")); - var requestBody = Json.createObjectBuilder() - .add("policy", Json.createObjectBuilder() - .add(CONTEXT, "context") - .add(TYPE, "Set") - .build()) - .build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .put("/policydefinitions/id") - .then() - .statusCode(400); - verifyNoInteractions(service); - } - - @Test - void update_shouldReturnNotFound_whenNotFound() { - var policyDefinition = createPolicyDefinition().build(); - when(transformerRegistry.transform(any(), eq(PolicyDefinition.class))).thenReturn(Result.success(policyDefinition)); - when(service.update(any())).thenReturn(ServiceResult.notFound("not found")); - var requestBody = Json.createObjectBuilder() - .add("policy", Json.createObjectBuilder() - .add(CONTEXT, "context") - .add(TYPE, "Set") - .build()) - .build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .put("/policydefinitions/id") - .then() - .statusCode(404); - } - - @Test - void update_authorizationFailed() { - when(authorizationService.authorize(any(), any(), any(), any())) - .thenReturn(ServiceResult.unauthorized("unauthorized")); - - baseRequest(participantContextId) - .contentType(JSON) - .put("/policydefinitions/id") - .then() - .statusCode(403); - } - } - - @Nested - class Create { - @Test - void create_shouldReturnDefinitionId() { - var policyDefinition = createPolicyDefinition().id("policyDefinitionId").createdAt(1234).build(); - var response = Json.createObjectBuilder() - .add("id", policyDefinition.getId()) - .add("createdAt", policyDefinition.getCreatedAt()) - .build(); - - when(transformerRegistry.transform(any(), eq(PolicyDefinition.class))).thenReturn(Result.success(policyDefinition)); - when(service.create(any())).thenReturn(ServiceResult.success(policyDefinition)); - when(transformerRegistry.transform(any(IdResponse.class), eq(JsonObject.class))).thenReturn(Result.success(response)); - - var requestBody = Json.createObjectBuilder() - .add("policy", Json.createObjectBuilder() - .add(CONTEXT, "context") - .add(TYPE, "Set") - .build()) - .build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/policydefinitions") - .then() - .statusCode(200) - .contentType(JSON) - .body("id", is("policyDefinitionId")) - .body("createdAt", is(1234)); - - verify(transformerRegistry).transform(isA(JsonObject.class), eq(PolicyDefinition.class)); - verify(service).create(policyDefinition); - } - - @Test - void create_shouldReturnBadRequest_whenTransformationFails() { - when(transformerRegistry.transform(any(), any())).thenReturn(Result.failure("error")); - var requestBody = Json.createObjectBuilder() - .add("policy", Json.createObjectBuilder() - .add(CONTEXT, "context") - .add(TYPE, "Set") - .build()) - .build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/policydefinitions") - .then() - .statusCode(400) - .contentType(JSON); - verifyNoInteractions(service); - } - - @Test - void create_shouldReturnConflict_whenItAlreadyExists() { - var policyDefinition = createPolicyDefinition().id("policyDefinitionId").createdAt(1234).build(); - when(transformerRegistry.transform(any(), eq(PolicyDefinition.class))).thenReturn(Result.success(policyDefinition)); - when(service.create(any())).thenReturn(ServiceResult.conflict("already exists")); - var requestBody = Json.createObjectBuilder() - .add("policy", Json.createObjectBuilder() - .add(CONTEXT, "context") - .add(TYPE, "Set") - .build()) - .build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/policydefinitions") - .then() - .statusCode(409) - .contentType(JSON); - } - - @Test - void create_authorizationFailed() { - var requestBody = Json.createObjectBuilder() - .add("policy", Json.createObjectBuilder() - .add(CONTEXT, "context") - .add(TYPE, "Set") - .build()) - .build(); - when(authorizationService.authorize(any(), any(), any(), any())) - .thenReturn(ServiceResult.unauthorized("unauthorized")); - - baseRequest(participantContextId) - .contentType(JSON) - .body(requestBody.toString()) - .post("/policydefinitions") - .then() - .statusCode(403); - } - } -} diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/v5/PolicyDefinitionApiV5ControllerTest.java b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/v5/PolicyDefinitionApiV5ControllerTest.java deleted file mode 100644 index 49d4df2f..00000000 --- a/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/policy/v5/PolicyDefinitionApiV5ControllerTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.policy.v5; - -import org.eclipse.edc.virtual.connector.controlplane.api.management.policy.PolicyDefinitionApiControllerTestBase; - -public class PolicyDefinitionApiV5ControllerTest extends PolicyDefinitionApiControllerTestBase { - @Override - protected String versionPath() { - return "v5alpha"; - } - - @Override - protected Object controller() { - return new PolicyDefinitionApiV5Controller(service, transformerRegistry, monitor, authorizationService); - } -} diff --git a/extensions/control-plane/api/management-api/transfer-process-api/build.gradle.kts b/extensions/control-plane/api/management-api/transfer-process-api/build.gradle.kts deleted file mode 100644 index a58d4343..00000000 --- a/extensions/control-plane/api/management-api/transfer-process-api/build.gradle.kts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - - - -plugins { - `java-library` - id(libs.plugins.swagger.get().pluginId) - -} - -dependencies { - - api(libs.edc.spi.auth) - implementation(libs.edc.spi.core) - implementation(libs.edc.spi.controlplane) - implementation(libs.edc.spi.web) - implementation(libs.edc.spi.transform) - implementation(libs.edc.lib.controlplane.transform) - implementation(libs.jakarta.annotation) - - implementation(libs.edc.lib.api) - implementation(libs.edc.lib.jersey.providers) - implementation(libs.edc.lib.mgmtapi) - implementation(libs.edc.lib.validator) - - testImplementation(testFixtures(libs.edc.core.jersey)) - testImplementation(libs.edc.lib.transform) - testImplementation(libs.restAssured) - testImplementation(libs.awaitility) -} - -edcBuild { - swagger { - apiGroup.set("management-api") - } -} \ No newline at end of file diff --git a/extensions/control-plane/api/management-api/transfer-process-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/TransferProcessApiExtension.java b/extensions/control-plane/api/management-api/transfer-process-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/TransferProcessApiExtension.java deleted file mode 100644 index 43d3b6e2..00000000 --- a/extensions/control-plane/api/management-api/transfer-process-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/TransferProcessApiExtension.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.transferprocess; - -import jakarta.json.Json; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema; -import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; -import org.eclipse.edc.connector.controlplane.transform.edc.transferprocess.from.JsonObjectFromTransferProcessTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.transferprocess.from.JsonObjectFromTransferStateTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.transferprocess.to.JsonObjectToSuspendTransferTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.transferprocess.to.JsonObjectToTerminateTransferTransformer; -import org.eclipse.edc.connector.controlplane.transform.edc.transferprocess.to.JsonObjectToTransferRequestTransformer; -import org.eclipse.edc.jsonld.spi.JsonLd; -import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService; -import org.eclipse.edc.participantcontext.spi.types.ParticipantResource; -import org.eclipse.edc.runtime.metamodel.annotation.Extension; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.spi.query.Criterion; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.spi.types.TypeManager; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; -import org.eclipse.edc.virtual.connector.controlplane.api.management.transferprocess.v5.TransferProcessApiV5Controller; -import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; -import org.eclipse.edc.web.spi.WebService; -import org.eclipse.edc.web.spi.configuration.ApiContext; - -import static java.util.Collections.emptyMap; -import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_SCOPE_V4; -import static org.eclipse.edc.participantcontext.spi.types.ParticipantResource.filterByParticipantContextId; -import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; - -@Extension(value = TransferProcessApiExtension.NAME) -public class TransferProcessApiExtension implements ServiceExtension { - - public static final String NAME = "Management API: Transfer Process"; - - @Inject - private WebService webService; - - @Inject - private TypeTransformerRegistry transformerRegistry; - - @Inject - private TransferProcessService service; - - @Inject - private JsonObjectValidatorRegistry validatorRegistry; - - @Inject - private JsonLd jsonLd; - - @Inject - private TypeManager typeManager; - - @Inject - private AuthorizationService authorizationService; - - @Inject - private ParticipantContextService participantContextService; - - @Override - public String name() { - return NAME; - } - - @Override - public void initialize(ServiceExtensionContext context) { - var builderFactory = Json.createBuilderFactory(emptyMap()); - - var managementApiTransformerRegistry = transformerRegistry.forContext("management-api"); - managementApiTransformerRegistry.register(new JsonObjectFromTransferProcessTransformer(builderFactory)); - managementApiTransformerRegistry.register(new JsonObjectFromTransferStateTransformer(builderFactory)); - - managementApiTransformerRegistry.register(new JsonObjectToTerminateTransferTransformer()); - managementApiTransformerRegistry.register(new JsonObjectToSuspendTransferTransformer()); - managementApiTransformerRegistry.register(new JsonObjectToTransferRequestTransformer()); - - authorizationService.addLookupFunction(TransferProcess.class, this::findTransferProcess); - - webService.registerResource(ApiContext.MANAGEMENT, new TransferProcessApiV5Controller(context.getMonitor(), authorizationService, participantContextService, service, managementApiTransformerRegistry)); - webService.registerDynamicResource(ApiContext.MANAGEMENT, TransferProcessApiV5Controller.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, MANAGEMENT_SCOPE_V4, validatorRegistry, ManagementApiJsonSchema.V4.version())); - } - - private ParticipantResource findTransferProcess(String ownerId, String assetId) { - return service - .search(QuerySpec.Builder.newInstance() - .filter(filterByParticipantContextId(ownerId)) - .filter(new Criterion("id", "=", assetId)) - .build() - ) - .map(it -> it.stream().findFirst().orElse(null)) - .orElse(f -> null); - } - -} diff --git a/extensions/control-plane/api/management-api/transfer-process-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/v5/TransferProcessApiV5.java b/extensions/control-plane/api/management-api/transfer-process-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/v5/TransferProcessApiV5.java deleted file mode 100644 index f7b8db0f..00000000 --- a/extensions/control-plane/api/management-api/transfer-process-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/v5/TransferProcessApiV5.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2026 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.transferprocess.v5; - -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.info.Info; -import io.swagger.v3.oas.annotations.links.Link; -import io.swagger.v3.oas.annotations.links.LinkParameter; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.ws.rs.core.SecurityContext; -import org.eclipse.edc.api.management.schema.ManagementApiJsonSchema; - -@OpenAPIDefinition(info = @Info(version = "v5alpha")) -@Tag(name = "Transfer Process v5alpha") -public interface TransferProcessApiV5 { - - String ASYNC_WARNING = "Due to the asynchronous nature of transfers, a successful response only indicates that the " + - "request was successfully received. This may take a long time, so clients must poll the /{id}/state " + - "endpoint to track the state."; - - @Operation(description = "Returns all transfer process according to a query", - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.QUERY_SPEC))), - responses = { - @ApiResponse(responseCode = "200", description = "The transfer processes matching the query", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.TRANSFER_PROCESS)))), - @ApiResponse(responseCode = "400", description = "Request was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR))))} - ) - JsonArray queryTransferProcessesV5(String participantContextId, JsonObject querySpecJson, SecurityContext securityContext); - - @Operation(description = "Gets an transfer process with the given ID", - responses = { - @ApiResponse(responseCode = "200", description = "The transfer process", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.TRANSFER_PROCESS))), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "A transfer process with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - JsonObject getTransferProcessV5(String participantContextId, String id, SecurityContext securityContext); - - @Operation(description = "Gets the state of a transfer process with the given ID", - responses = { - @ApiResponse(responseCode = "200", description = "The transfer process's state", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.TRANSFER_PROCESS_STATE))), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "An transfer process with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - } - ) - JsonObject getTransferProcessStateV5(String participantContextId, String id, SecurityContext securityContext); - - @Operation(description = "Initiates a data transfer with the given parameters. " + ASYNC_WARNING, - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.TRANSFER_REQUEST))), - responses = { - @ApiResponse(responseCode = "200", description = "The transfer was successfully initiated. Returns the transfer process ID and created timestamp", - content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.ID_RESPONSE)), - links = @Link(name = "poll-state", operationId = "getTransferProcessStateV3", parameters = { - @LinkParameter(name = "id", expression = "$response.body#/id") - }) - ), - @ApiResponse(responseCode = "400", description = "Request body was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - }) - JsonObject initiateTransferProcessV5(String participantContextId, JsonObject transferRequest, SecurityContext securityContext); - - @Operation(description = "Requests the termination of a transfer process. " + ASYNC_WARNING, - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.TERMINATE_TRANSFER))), - responses = { - @ApiResponse(responseCode = "204", description = "Request to terminate the transfer process was successfully received", - links = @Link(name = "poll-state", operationId = "terminateTransferProcessV3")), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "A transfer process with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "409", description = "Could not terminate transfer process, because it is already completed or terminated.", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - }) - void terminateTransferProcessV5(String participantContextId, String id, JsonObject terminateTransfer, SecurityContext securityContext); - - @Operation(description = "Requests the suspension of a transfer process. " + ASYNC_WARNING, - requestBody = @RequestBody(content = @Content(schema = @Schema(ref = ManagementApiJsonSchema.V4.SUSPEND_TRANSFER))), - responses = { - @ApiResponse(responseCode = "204", description = "Request to suspend the transfer process was successfully received", - links = @Link(name = "poll-state", operationId = "suspendTransferProcessV3")), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "A transfer process with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "409", description = "Could not suspend the transfer process, because it is already completed or terminated.", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - }) - void suspendTransferProcessV5(String participantContextId, String id, JsonObject suspendTransfer, SecurityContext securityContext); - - @Operation(description = "Requests the resumption of a suspended transfer process. " + ASYNC_WARNING, - responses = { - @ApiResponse(responseCode = "204", description = "Request to resume the transfer process was successfully received", - links = @Link(name = "poll-state", operationId = "resumeTransferProcessV3")), - @ApiResponse(responseCode = "400", description = "Request was malformed, e.g. id was null", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))), - @ApiResponse(responseCode = "404", description = "A transfer process with the given ID does not exist", - content = @Content(array = @ArraySchema(schema = @Schema(ref = ManagementApiJsonSchema.V4.API_ERROR)))) - }) - void resumeTransferProcessV5(String participantContextId, String id, SecurityContext securityContext); -} diff --git a/extensions/control-plane/api/management-api/transfer-process-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/v5/TransferProcessApiV5Controller.java b/extensions/control-plane/api/management-api/transfer-process-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/v5/TransferProcessApiV5Controller.java deleted file mode 100644 index cf3e8a99..00000000 --- a/extensions/control-plane/api/management-api/transfer-process-api/src/main/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/v5/TransferProcessApiV5Controller.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (c) 2026 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.transferprocess.v5; - -import jakarta.annotation.security.RolesAllowed; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.SecurityContext; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.auth.spi.ParticipantPrincipal; -import org.eclipse.edc.api.auth.spi.RequiredScope; -import org.eclipse.edc.api.model.IdResponse; -import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.SuspendTransfer; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.TerminateTransfer; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferState; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.ResumeTransferCommand; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.SuspendTransferCommand; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.TerminateTransferCommand; -import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService; -import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; -import org.eclipse.edc.spi.EdcException; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.edc.web.spi.exception.ObjectNotFoundException; -import org.eclipse.edc.web.spi.validation.SchemaType; - -import java.util.Optional; - -import static jakarta.json.stream.JsonCollectors.toJsonArray; -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; -import static java.lang.String.format; -import static org.eclipse.edc.connector.controlplane.transfer.spi.types.SuspendTransfer.SUSPEND_TRANSFER_TYPE_TERM; -import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TerminateTransfer.TERMINATE_TRANSFER_TYPE_TERM; -import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest.TRANSFER_REQUEST_TYPE_TERM; -import static org.eclipse.edc.participantcontext.spi.types.ParticipantResource.filterByParticipantContextId; -import static org.eclipse.edc.spi.query.QuerySpec.EDC_QUERY_SPEC_TYPE_TERM; -import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper; -import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.mapToException; - -@Consumes(APPLICATION_JSON) -@Produces(APPLICATION_JSON) -@Path("/v5alpha/participants/{participantContextId}/transferprocesses") -public class TransferProcessApiV5Controller implements TransferProcessApiV5 { - - private final Monitor monitor; - private final AuthorizationService authorizationService; - private final ParticipantContextService participantContextService; - private final TransferProcessService service; - private final TypeTransformerRegistry transformerRegistry; - - public TransferProcessApiV5Controller(Monitor monitor, AuthorizationService authorizationService, ParticipantContextService participantContextService, TransferProcessService service, TypeTransformerRegistry transformerRegistry) { - this.monitor = monitor; - this.authorizationService = authorizationService; - this.participantContextService = participantContextService; - this.service = service; - this.transformerRegistry = transformerRegistry; - } - - @POST - @Path("request") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonArray queryTransferProcessesV5(@PathParam("participantContextId") String participantContextId, - @SchemaType(EDC_QUERY_SPEC_TYPE_TERM) JsonObject querySpecJson, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, participantContextId, ParticipantContext.class) - .orElseThrow(exceptionMapper(ParticipantContext.class, participantContextId)); - - QuerySpec querySpec; - if (querySpecJson == null) { - querySpec = QuerySpec.none(); - } else { - querySpec = transformerRegistry.transform(querySpecJson, QuerySpec.class) - .orElseThrow(InvalidRequestException::new); - } - - var query = querySpec.toBuilder() - .filter(filterByParticipantContextId(participantContextId)) - .build(); - - return service.search(query).orElseThrow(exceptionMapper(TransferProcess.class)).stream() - .map(transferProcess -> transformerRegistry.transform(transferProcess, JsonObject.class) - .onFailure(f -> monitor.warning(f.getFailureDetail()))) - .filter(Result::succeeded) - .map(Result::getContent) - .collect(toJsonArray()); - } - - @GET - @Path("{id}") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonObject getTransferProcessV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, id, TransferProcess.class) - .orElseThrow(exceptionMapper(TransferProcess.class, id)); - - var transferProcess = service.findById(id); - if (transferProcess == null) { - throw new ObjectNotFoundException(TransferProcess.class, id); - } - - return transformerRegistry.transform(transferProcess, JsonObject.class) - .onFailure(f -> monitor.warning(f.getFailureDetail())) - .orElseThrow(failure -> new ObjectNotFoundException(TransferProcess.class, id)); - } - - @GET - @Path("/{id}/state") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:read") - @Override - public JsonObject getTransferProcessStateV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, id, TransferProcess.class) - .orElseThrow(exceptionMapper(TransferProcess.class, id)); - - return Optional.of(id) - .map(service::getState) - .map(TransferState::new) - .map(state -> transformerRegistry.transform(state, JsonObject.class) - .onFailure(f -> monitor.warning(f.getFailureDetail())) - .orElseThrow(failure -> new ObjectNotFoundException(TransferProcess.class, id))) - .orElseThrow(() -> new ObjectNotFoundException(TransferProcess.class, id)); - } - - @POST - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:write") - @Override - public JsonObject initiateTransferProcessV5(@PathParam("participantContextId") String participantContextId, - @SchemaType(TRANSFER_REQUEST_TYPE_TERM) JsonObject request, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, participantContextId, ParticipantContext.class) - .orElseThrow(exceptionMapper(ParticipantContext.class, participantContextId)); - - var transferRequest = transformerRegistry.transform(request, TransferRequest.class) - .orElseThrow(InvalidRequestException::new); - - var participantContext = participantContextService.getParticipantContext(participantContextId) - .orElseThrow(exceptionMapper(ParticipantContext.class, participantContextId)); - - var createdTransfer = service.initiateTransfer(participantContext, transferRequest) - .onSuccess(d -> monitor.debug(format("Transfer Process created %s", d.getId()))) - .orElseThrow(it -> mapToException(it, TransferProcess.class)); - - var responseDto = IdResponse.Builder.newInstance() - .id(createdTransfer.getId()) - .createdAt(createdTransfer.getCreatedAt()) - .build(); - - return transformerRegistry.transform(responseDto, JsonObject.class) - .orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail())); - } - - @POST - @Path("/{id}/terminate") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:write") - @Override - public void terminateTransferProcessV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @SchemaType(TERMINATE_TRANSFER_TYPE_TERM) JsonObject requestBody, - @Context SecurityContext securityContext) { - - - authorizationService.authorize(securityContext, participantContextId, id, TransferProcess.class) - .orElseThrow(exceptionMapper(TransferProcess.class, id)); - - var terminateTransfer = transformerRegistry.transform(requestBody, TerminateTransfer.class) - .orElseThrow(InvalidRequestException::new); - - service.terminate(new TerminateTransferCommand(id, terminateTransfer.reason())) - .onSuccess(tp -> monitor.debug(format("Termination requested for TransferProcess with ID %s", id))) - .orElseThrow(exceptionMapper(TransferProcess.class, id)); - } - - @POST - @Path("/{id}/suspend") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:write") - @Override - public void suspendTransferProcessV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @SchemaType(SUSPEND_TRANSFER_TYPE_TERM) JsonObject requestBody, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, id, TransferProcess.class) - .orElseThrow(exceptionMapper(TransferProcess.class, id)); - - var suspendTransfer = transformerRegistry.transform(requestBody, SuspendTransfer.class) - .orElseThrow(InvalidRequestException::new); - - service.suspend(new SuspendTransferCommand(id, suspendTransfer.reason())) - .onSuccess(tp -> monitor.debug(format("Suspension requested for TransferProcess with ID %s", id))) - .orElseThrow(exceptionMapper(TransferProcess.class, id)); - } - - @POST - @Path("/{id}/resume") - @RolesAllowed({ParticipantPrincipal.ROLE_ADMIN, ParticipantPrincipal.ROLE_PARTICIPANT}) - @RequiredScope("management-api:write") - @Override - public void resumeTransferProcessV5(@PathParam("participantContextId") String participantContextId, - @PathParam("id") String id, - @Context SecurityContext securityContext) { - - authorizationService.authorize(securityContext, participantContextId, id, TransferProcess.class) - .orElseThrow(exceptionMapper(TransferProcess.class, id)); - - service.resume(new ResumeTransferCommand(id)) - .onSuccess(tp -> monitor.debug(format("Resumption requested for TransferProcess with ID %s", id))) - .orElseThrow(exceptionMapper(TransferProcess.class, id)); - } -} diff --git a/extensions/control-plane/api/management-api/transfer-process-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/control-plane/api/management-api/transfer-process-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension deleted file mode 100644 index 2415b5f5..00000000 --- a/extensions/control-plane/api/management-api/transfer-process-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ /dev/null @@ -1,15 +0,0 @@ -# -# Copyright (c) 2025 Metaform Systems, Inc. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License, Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# -# Contributors: -# Metaform Systems, Inc. - initial API and implementation -# -# - -org.eclipse.edc.virtual.connector.controlplane.api.management.transferprocess.TransferProcessApiExtension diff --git a/extensions/control-plane/api/management-api/transfer-process-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/BaseTransferProcessApiControllerTest.java b/extensions/control-plane/api/management-api/transfer-process-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/BaseTransferProcessApiControllerTest.java deleted file mode 100644 index 166298b6..00000000 --- a/extensions/control-plane/api/management-api/transfer-process-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/BaseTransferProcessApiControllerTest.java +++ /dev/null @@ -1,461 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.transferprocess; - -import io.restassured.specification.RequestSpecification; -import jakarta.json.Json; -import jakarta.json.JsonObject; -import org.eclipse.edc.api.auth.spi.AuthorizationService; -import org.eclipse.edc.api.model.IdResponse; -import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.SuspendTransfer; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.TerminateTransfer; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.ResumeTransferCommand; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.SuspendTransferCommand; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.TerminateTransferCommand; -import org.eclipse.edc.junit.annotations.ApiTest; -import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService; -import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.spi.result.ServiceResult; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.UUID; - -import static io.restassured.RestAssured.given; -import static io.restassured.http.ContentType.JSON; -import static java.util.Collections.emptyList; -import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; -import static org.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -@ApiTest -public abstract class BaseTransferProcessApiControllerTest extends RestControllerTestBase { - - protected final TypeTransformerRegistry transformerRegistry = mock(); - protected final AuthorizationService authorizationService = mock(); - protected final TransferProcessService service = mock(); - protected final ParticipantContextService participantContextService = mock(); - private final String participantContextId = "test-participant-context-id"; - - protected abstract String versionPath(); - - private RequestSpecification baseRequest(String participantContextId) { - return given() - .baseUri("http://localhost:" + port + "/" + versionPath() + "/participants/" + participantContextId) - .when(); - } - - @BeforeEach - void setup() { - when(authorizationService.authorize(any(), any(), any(), any())).thenReturn(ServiceResult.success()); - } - - @NotNull - private TransferProcess.Builder createTransferProcess() { - return TransferProcess.Builder.newInstance().id(UUID.randomUUID().toString()); - } - - @Nested - class Get { - @Test - void shouldReturnTransferProcess() { - var transferProcess = createTransferProcess().id("id").build(); - var responseBody = Json.createObjectBuilder().add("id", "id").add("createdAt", 1234).build(); - when(service.findById(any())).thenReturn(transferProcess); - when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.success(responseBody)); - - baseRequest(participantContextId) - .get("/transferprocesses/id") - .then() - .statusCode(200) - .contentType(JSON) - .body("id", is("id")) - .body("createdAt", is(1234)); - verify(service).findById("id"); - verify(transformerRegistry).transform(transferProcess, JsonObject.class); - } - - @Test - void shouldReturnNotFound_whenNotFound() { - when(service.findById(any())).thenReturn(null); - - baseRequest(participantContextId) - .get("/transferprocesses/id") - .then() - .statusCode(404) - .contentType(JSON); - verifyNoInteractions(transformerRegistry); - } - - @Test - void shouldReturnNotFound_whenTransformFails() { - when(service.findById(any())).thenReturn(createTransferProcess().build()); - when(transformerRegistry.transform(any(), any())).thenReturn(Result.failure("error")); - - baseRequest(participantContextId) - .get("/transferprocesses/id") - .then() - .statusCode(404) - .contentType(JSON); - } - } - - @Nested - class GetState { - - @Test - void shouldReturnTheState() { - when(service.getState(any())).thenReturn("INITIAL"); - var result = Json.createObjectBuilder().add("state", "INITIAL").build(); - when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.success(result)); - - baseRequest(participantContextId) - .get("/transferprocesses/id/state") - .then() - .statusCode(200) - .contentType(JSON) - .body("state", is("INITIAL")); - verify(service).getState("id"); - } - - @Test - void shouldReturnNotFound_whenTransferProcessIsNotFound() { - when(service.getState(any())).thenReturn(null); - - baseRequest(participantContextId) - .get("/transferprocesses/id/state") - .then() - .statusCode(404); - verify(service).getState("id"); - } - } - - @Nested - class Request { - - @Test - void shouldReturnQueriedTransferProcesses() { - var querySpec = QuerySpec.none(); - var expandedRequestBody = Json.createObjectBuilder().build(); - var transferProcess = createTransferProcess().id("id").build(); - var expandedResponseBody = Json.createObjectBuilder().add("id", "id").add("createdAt", 1234).build(); - when(transformerRegistry.transform(any(), eq(QuerySpec.class))).thenReturn(Result.success(querySpec)); - when(service.search(any())).thenReturn(ServiceResult.success(List.of(transferProcess))); - when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.success(expandedResponseBody)); - var requestBody = Json.createObjectBuilder().build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/transferprocesses/request") - .then() - .statusCode(200) - .contentType(JSON) - .body("size()", is(1)) - .body("[0].id", is("id")) - .body("[0].createdAt", is(1234)); - verify(transformerRegistry).transform(expandedRequestBody, QuerySpec.class); - verify(service).search(argThat(s -> s.getOffset() == querySpec.getOffset() && - s.getFilterExpression().stream().anyMatch(c -> c.getOperandLeft().equals("participantContextId") && - c.getOperator().equals("=") && - c.getOperandRight().equals(participantContextId)))); - verify(transformerRegistry).transform(transferProcess, JsonObject.class); - } - - @Test - void shouldNotReturnError_whenEmptyBody() { - var querySpec = QuerySpec.none(); - when(service.search(any())).thenReturn(ServiceResult.success(emptyList())); - - baseRequest(participantContextId) - .contentType(JSON) - .post("/transferprocesses/request") - .then() - .statusCode(200) - .contentType(JSON) - .body("size()", is(0)); - - verify(service).search(argThat(s -> s.getOffset() == querySpec.getOffset() && - s.getFilterExpression().stream().anyMatch(c -> c.getOperandLeft().equals("participantContextId") && - c.getOperator().equals("=") && - c.getOperandRight().equals(participantContextId)))); - - } - - @Test - void shouldReturn400_whenQuerySpecTransformFails() { - when(transformerRegistry.transform(any(), eq(QuerySpec.class))).thenReturn(Result.failure("error")); - var requestBody = Json.createObjectBuilder().build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/transferprocesses/request") - .then() - .statusCode(400); - verifyNoInteractions(service); - } - - @Test - void shouldReturnBadRequest_whenServiceReturnsBadRequest() { - var querySpec = QuerySpec.none(); - when(transformerRegistry.transform(any(), eq(QuerySpec.class))).thenReturn(Result.success(querySpec)); - when(service.search(any())).thenReturn(ServiceResult.badRequest("error")); - var requestBody = Json.createObjectBuilder().build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/transferprocesses/request") - .then() - .statusCode(400) - .contentType(JSON); - } - - @Test - void shouldFilterOutResults_whenTransformFails() { - var querySpec = QuerySpec.none(); - var transferProcess = createTransferProcess().id("id").build(); - when(transformerRegistry.transform(any(), eq(QuerySpec.class))).thenReturn(Result.success(querySpec)); - when(service.search(any())).thenReturn(ServiceResult.success(List.of(transferProcess))); - when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.failure("error")); - var requestBody = Json.createObjectBuilder().build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/transferprocesses/request") - .then() - .statusCode(200) - .contentType(JSON) - .body("size()", is(0)); - } - } - - @Nested - class Initiate { - - @Test - void shouldReturnId() { - var transferRequest = TransferRequest.Builder.newInstance().build(); - var transferProcess = createTransferProcess().id("id").build(); - var responseBody = Json.createObjectBuilder().add(ID, "transferProcessId").build(); - when(participantContextService.getParticipantContext(any())) - .thenReturn(ServiceResult.success(ParticipantContext.Builder.newInstance().participantContextId(participantContextId).identity(participantContextId).build())); - when(transformerRegistry.transform(any(), eq(TransferRequest.class))).thenReturn(Result.success(transferRequest)); - when(service.initiateTransfer(isA(ParticipantContext.class), any())).thenReturn(ServiceResult.success(transferProcess)); - when(transformerRegistry.transform(any(), eq(JsonObject.class))).thenReturn(Result.success(responseBody)); - var requestBody = Json.createObjectBuilder().build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/transferprocesses") - .then() - .statusCode(200) - .contentType(JSON) - .body(ID, is("transferProcessId")); - verify(transformerRegistry).transform(isA(JsonObject.class), eq(TransferRequest.class)); - verify(service).initiateTransfer(any(), eq(transferRequest)); - verify(transformerRegistry).transform(isA(IdResponse.class), eq(JsonObject.class)); - } - - @Test - void shouldReturnBadRequest_whenTransformationFails() { - when(transformerRegistry.transform(any(), any())).thenReturn(Result.failure("error")); - var requestBody = Json.createObjectBuilder().build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/transferprocesses") - .then() - .statusCode(400) - .contentType(JSON); - verifyNoInteractions(service); - } - - @Test - void shouldReturnConflict_whenItAlreadyExists() { - var transferRequest = TransferRequest.Builder.newInstance().build(); - when(participantContextService.getParticipantContext(any())) - .thenReturn(ServiceResult.success(ParticipantContext.Builder.newInstance().participantContextId(participantContextId).identity(participantContextId).build())); - when(transformerRegistry.transform(any(), eq(TransferRequest.class))).thenReturn(Result.success(transferRequest)); - when(service.initiateTransfer(any(), any())).thenReturn(ServiceResult.conflict("already exists")); - var requestBody = Json.createObjectBuilder().build(); - - baseRequest(participantContextId) - .body(requestBody) - .contentType(JSON) - .post("/transferprocesses") - .then() - .statusCode(409) - .contentType(JSON); - } - } - - @Nested - class Terminate { - - @Test - void shouldTerminate() { - var expanded = Json.createObjectBuilder().build(); - var terminateTransfer = new TerminateTransfer("anyReason"); - when(transformerRegistry.transform(any(), eq(TerminateTransfer.class))).thenReturn(Result.success(terminateTransfer)); - when(service.terminate(any())).thenReturn(ServiceResult.success()); - - baseRequest(participantContextId) - .contentType(JSON) - .body(Json.createObjectBuilder().build()) - .post("/transferprocesses/id/terminate") - .then() - .statusCode(204); - verify(transformerRegistry).transform(expanded, TerminateTransfer.class); - verify(service).terminate(isA(TerminateTransferCommand.class)); - } - - @Test - void shouldReturnBadRequest_whenTransformFail() { - when(transformerRegistry.transform(any(), eq(TerminateTransfer.class))).thenReturn(Result.failure("failure")); - - baseRequest(participantContextId) - .contentType(JSON) - .body(Json.createObjectBuilder().build()) - .post("/transferprocesses/id/terminate") - .then() - .statusCode(400); - verifyNoInteractions(service); - } - - @Test - void shouldReturnConflict_whenServiceReturnsConflict() { - var terminateTransfer = new TerminateTransfer("anyReason"); - when(transformerRegistry.transform(any(), eq(TerminateTransfer.class))).thenReturn(Result.success(terminateTransfer)); - when(service.terminate(any())).thenReturn(ServiceResult.conflict("conflict")); - - baseRequest(participantContextId) - .contentType(JSON) - .post("/transferprocesses/id/terminate") - .then() - .statusCode(409); - } - - } - - @Nested - class Suspend { - - @Test - void shouldSuspend() { - var expanded = Json.createObjectBuilder().build(); - var suspendTransfer = new SuspendTransfer("anyReason"); - when(transformerRegistry.transform(any(), eq(SuspendTransfer.class))).thenReturn(Result.success(suspendTransfer)); - when(service.suspend(any())).thenReturn(ServiceResult.success()); - - baseRequest(participantContextId) - .contentType(JSON) - .body(Json.createObjectBuilder().build()) - .post("/transferprocesses/id/suspend") - .then() - .statusCode(204); - verify(transformerRegistry).transform(expanded, SuspendTransfer.class); - verify(service).suspend(isA(SuspendTransferCommand.class)); - } - - @Test - void shouldReturnBadRequest_whenTransformFail() { - when(transformerRegistry.transform(any(), eq(SuspendTransfer.class))).thenReturn(Result.failure("failure")); - - baseRequest(participantContextId) - .contentType(JSON) - .body(Json.createObjectBuilder().build()) - .post("/transferprocesses/id/suspend") - .then() - .statusCode(400); - verifyNoInteractions(service); - } - - @Test - void shouldReturnConflict_whenServiceReturnsConflict() { - var suspendTransfer = new SuspendTransfer("anyReason"); - when(transformerRegistry.transform(any(), eq(SuspendTransfer.class))).thenReturn(Result.success(suspendTransfer)); - when(service.suspend(any())).thenReturn(ServiceResult.conflict("conflict")); - - baseRequest(participantContextId) - .contentType(JSON) - .post("/transferprocesses/id/suspend") - .then() - .statusCode(409); - } - - } - - @Nested - class Resume { - - @Test - void shouldResumeTransfer() { - when(service.resume(any())).thenReturn(ServiceResult.success()); - - baseRequest(participantContextId) - .contentType(JSON) - .post("/transferprocesses/id/resume") - .then() - .statusCode(204); - verify(service).resume(isA(ResumeTransferCommand.class)); - } - - @Test - void shouldReturnConflict_whenServiceReturnsConflict() { - when(service.resume(any())).thenReturn(ServiceResult.conflict("conflict")); - - baseRequest(participantContextId) - .contentType(JSON) - .post("/transferprocesses/id/resume") - .then() - .statusCode(409); - } - - @Test - void shouldReturnNotFound_whenServiceReturnsNotFound() { - when(service.resume(any())).thenReturn(ServiceResult.notFound("not found")); - - baseRequest(participantContextId) - .contentType(JSON) - .post("/transferprocesses/id/resume") - .then() - .statusCode(404); - } - - } - -} diff --git a/extensions/control-plane/api/management-api/transfer-process-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/TransferProcessApiExtensionTest.java b/extensions/control-plane/api/management-api/transfer-process-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/TransferProcessApiExtensionTest.java deleted file mode 100644 index e03ec895..00000000 --- a/extensions/control-plane/api/management-api/transfer-process-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/TransferProcessApiExtensionTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.transferprocess; - -import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; -import org.eclipse.edc.virtual.connector.controlplane.api.management.transferprocess.v5.TransferProcessApiV5Controller; -import org.eclipse.edc.web.spi.WebService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@ExtendWith(DependencyInjectionExtension.class) -class TransferProcessApiExtensionTest { - - private final JsonObjectValidatorRegistry validatorRegistry = mock(); - private final WebService webService = mock(); - - @BeforeEach - void setUp(ServiceExtensionContext context) { - TypeTransformerRegistry typeTransformerRegistry = mock(); - when(typeTransformerRegistry.forContext(any())).thenReturn(mock()); - context.registerService(TypeTransformerRegistry.class, typeTransformerRegistry); - context.registerService(JsonObjectValidatorRegistry.class, validatorRegistry); - context.registerService(WebService.class, webService); - } - - @Test - void initialize_shouldRegisterControllers(ServiceExtensionContext context, TransferProcessApiExtension extension) { - extension.initialize(context); - - verify(webService).registerResource(any(), isA(TransferProcessApiV5Controller.class)); - } -} diff --git a/extensions/control-plane/api/management-api/transfer-process-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/v5/TransferProcessApiV5ControllerTest.java b/extensions/control-plane/api/management-api/transfer-process-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/v5/TransferProcessApiV5ControllerTest.java deleted file mode 100644 index 7b00cb4c..00000000 --- a/extensions/control-plane/api/management-api/transfer-process-api/src/test/java/org/eclipse/edc/virtual/connector/controlplane/api/management/transferprocess/v5/TransferProcessApiV5ControllerTest.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.virtual.connector.controlplane.api.management.transferprocess.v5; - -import org.eclipse.edc.virtual.connector.controlplane.api.management.transferprocess.BaseTransferProcessApiControllerTest; - -public class TransferProcessApiV5ControllerTest extends BaseTransferProcessApiControllerTest { - @Override - protected Object controller() { - return new TransferProcessApiV5Controller(monitor, authorizationService, participantContextService, service, transformerRegistry); - } - - @Override - protected String versionPath() { - return "v5alpha"; - } - -} diff --git a/resources/openapi/management-api.version b/resources/openapi/management-api.version deleted file mode 100644 index 10fcaca4..00000000 --- a/resources/openapi/management-api.version +++ /dev/null @@ -1 +0,0 @@ -extensions/control-plane/api/management-api/management-api-configuration/src/main/resources/management-api-version.json diff --git a/settings.gradle.kts b/settings.gradle.kts index 5676c6f4..a3b20f25 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -59,12 +59,6 @@ include(":extensions:common:banner-extension") // APIs include(":extensions:control-plane:api:management-api:catalog-api") -include(":extensions:control-plane:api:management-api:contract-definition-api") -include(":extensions:control-plane:api:management-api:policy-definition-api") -include(":extensions:control-plane:api:management-api:contract-negotiation-api") -include(":extensions:control-plane:api:management-api:contract-agreement-api") -include(":extensions:control-plane:api:management-api:transfer-process-api") -include(":extensions:control-plane:api:management-api:cel-api") // lib diff --git a/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/CelExpressionApiV5EndToEndTest.java b/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/CelExpressionApiV5EndToEndTest.java deleted file mode 100644 index 0fdd1fd5..00000000 --- a/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/CelExpressionApiV5EndToEndTest.java +++ /dev/null @@ -1,414 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.test.e2e.managementapi.v5; - -import com.nimbusds.jose.JOSEException; -import org.assertj.core.api.Assertions; -import org.eclipse.edc.api.authentication.OauthServer; -import org.eclipse.edc.api.authentication.OauthServerEndToEndExtension; -import org.eclipse.edc.connector.controlplane.policy.spi.store.PolicyDefinitionStore; -import org.eclipse.edc.junit.annotations.EndToEndTest; -import org.eclipse.edc.junit.annotations.PostgresqlIntegrationTest; -import org.eclipse.edc.junit.extensions.ComponentRuntimeExtension; -import org.eclipse.edc.junit.extensions.RuntimeExtension; -import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService; -import org.eclipse.edc.policy.cel.model.CelExpression; -import org.eclipse.edc.policy.cel.service.CelPolicyExpressionService; -import org.eclipse.edc.policy.cel.store.CelExpressionStore; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndExtension; -import org.eclipse.edc.test.e2e.managementapi.ManagementEndToEndTestContext; -import org.eclipse.edc.test.e2e.managementapi.Runtimes; -import org.eclipse.edc.virtual.nats.testfixtures.NatsEndToEndExtension; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.RegisterExtension; - -import java.util.ArrayList; -import java.util.Map; -import java.util.UUID; - -import static io.restassured.http.ContentType.JSON; -import static jakarta.json.Json.createObjectBuilder; -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.junit.assertions.AbstractResultAssert.assertThat; -import static org.eclipse.edc.spi.constants.CoreConstants.EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2; -import static org.eclipse.edc.spi.query.Criterion.criterion; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.createParticipant; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.jsonLdContext; -import static org.eclipse.edc.virtual.test.system.fixtures.DockerImages.createPgContainer; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; - - -public class CelExpressionApiV5EndToEndTest { - - - @SuppressWarnings("JUnitMalformedDeclaration") - abstract static class Tests { - - private static final String PARTICIPANT_CONTEXT_ID = "test-participant"; - private String adminToken; - - private CelExpression expression(String leftOperand, String expr) { - return CelExpression.Builder.newInstance().id(UUID.randomUUID().toString()) - .leftOperand(leftOperand) - .expression(expr) - .description("description") - .build(); - } - - @BeforeEach - void setup(OauthServer authServer, ParticipantContextService participantContextService) throws JOSEException { - createParticipant(participantContextService, PARTICIPANT_CONTEXT_ID); - - adminToken = authServer.createAdminToken(); - } - - @AfterEach - void teardown(ParticipantContextService participantContextService, PolicyDefinitionStore store) { - participantContextService.deleteParticipantContext(PARTICIPANT_CONTEXT_ID) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - store.findAll(QuerySpec.max()).forEach(pd -> store.delete(pd.getId())); - } - - @Test - void create(ManagementEndToEndTestContext context, CelPolicyExpressionService service) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "CelExpression") - .add("leftOperand", "leftOperand") - .add("expression", "ctx.agent.id == 'agent-123'") - .add("description", "desc") - .build(); - - var id = context.baseRequest(adminToken) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/celexpressions") - .then() - .log().ifValidationFails() - .contentType(JSON) - .statusCode(200) - .extract().jsonPath().getString(ID); - - assertThat(service.findById(id)).isSucceeded() - .satisfies(c -> { - Assertions.assertThat(c.getId()).isEqualTo(id); - }); - - } - - @Test - void create_validationFails(ManagementEndToEndTestContext context) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "CelExpression") - .build(); - - context.baseRequest(adminToken) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/celexpressions") - .then() - .log().ifValidationFails() - .contentType(JSON) - .statusCode(400); - } - - @Test - void create_NotAdmin(ManagementEndToEndTestContext context, OauthServer authServer) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "CelExpression") - .add("leftOperand", "leftOperand") - .add("expression", "ctx.agent.id == 'agent-123'") - .build(); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:write")); - - context.baseRequest(token) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/celexpressions") - .then() - .log().ifError() - .statusCode(403) - .body(containsString("Required user role not satisfied.")); - } - - @Test - void get(ManagementEndToEndTestContext context, CelExpressionStore store) { - var expr = expression("leftOperand", "ctx.agent.id == 'agent-123'"); - store.create(expr) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - context.baseRequest(adminToken) - .contentType(JSON) - .get("/v5alpha/celexpressions/" + expr.getId()) - .then() - .log().ifError() - .statusCode(200) - .body(ID, is(expr.getId())) - .body(CONTEXT, contains(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .body("leftOperand", is(expr.getLeftOperand())) - .body("expression", is(expr.getExpression())) - .body("description", is(expr.getDescription())) - .body("scopes", is(new ArrayList<>(expr.getScopes()))); - } - - - @Test - void get_NotAdmin(ManagementEndToEndTestContext context, CelExpressionStore store, OauthServer authServer) { - - var expr = expression("leftOperand", "ctx.agent.id == 'agent-123'"); - store.create(expr) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:read")); - - context.baseRequest(token) - .contentType(JSON) - .get("/v5alpha/celexpressions/" + expr.getId()) - .then() - .log().ifError() - .statusCode(403) - .body(containsString("Required user role not satisfied.")); - - } - - @Test - void query(ManagementEndToEndTestContext context, CelExpressionStore store) { - var expr = expression("leftOperand", "ctx.agent.id == 'agent-123'"); - store.create(expr) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - var matchingQuery = context.query( - criterion("id", "=", expr.getId()) - ); - - context.baseRequest(adminToken) - .body(matchingQuery.toString()) - .contentType(JSON) - .post("/v5alpha/celexpressions/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(1)); - - - var nonMatchingQuery = context.query( - criterion("id", "=", "notFound") - ); - - context.baseRequest(adminToken) - .body(nonMatchingQuery.toString()) - .contentType(JSON) - .post("/v5alpha/celexpressions/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(0)); - } - - @Test - void query_NotAdmin(ManagementEndToEndTestContext context, CelExpressionStore store, OauthServer authServer) { - var expr = expression("leftOperand", "ctx.agent.id == 'agent-123'"); - store.create(expr) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - var matchingQuery = context.query( - criterion("id", "=", expr.getId()) - ); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:read")); - - context.baseRequest(token) - .body(matchingQuery.toString()) - .contentType(JSON) - .post("/v5alpha/celexpressions/request") - .then() - .log().ifError() - .statusCode(403) - .body(containsString("Required user role not satisfied.")); - - } - - @Test - void update(ManagementEndToEndTestContext context, CelPolicyExpressionService service) { - var expr = expression("leftOperand", "ctx.agent.id == 'agent-123'"); - service.create(expr) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "CelExpression") - .add(ID, expr.getId()) - .add("leftOperand", "leftOperand") - .add("expression", "ctx.agent.id == 'agent-125'") - .add("description", "desc") - .build(); - - context.baseRequest(adminToken) - .body(requestBody.toString()) - .contentType(JSON) - .put("/v5alpha/celexpressions/" + expr.getId()) - .then() - .statusCode(204); - - assertThat(service.findById(expr.getId())).isSucceeded() - .extracting(CelExpression::getExpression) - .isEqualTo("ctx.agent.id == 'agent-125'"); - } - - - @Test - void update_whenSchemaValidationFails(ManagementEndToEndTestContext context) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "CelExpression") - .build(); - - context.baseRequest(adminToken) - .body(requestBody.toString()) - .contentType(JSON) - .put("/v5alpha/celexpressions/id") - .then() - .log().ifValidationFails() - .contentType(JSON) - .statusCode(400); - } - - @Test - void update_NotAdmin(ManagementEndToEndTestContext context, OauthServer authServer) { - - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "CelExpression") - .build(); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:write")); - - context.baseRequest(token) - .contentType(JSON) - .body(requestBody.toString()) - .put("/v5alpha/celexpressions/id") - .then() - .log().ifValidationFails() - .statusCode(403) - .body(containsString("Required user role not satisfied.")); - - } - - - @Test - void delete(ManagementEndToEndTestContext context, CelPolicyExpressionService service) { - var expr = expression("leftOperand", "ctx.agent.id == 'agent-123'"); - service.create(expr) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - - context.baseRequest(adminToken) - .delete("/v5alpha/celexpressions/" + expr.getId()) - .then() - .statusCode(204); - - context.baseRequest(adminToken) - .delete("/v5alpha/celexpressions/" + expr.getId()) - .then() - .statusCode(404); - } - - - @Test - void delete_NotAdmin(ManagementEndToEndTestContext context, OauthServer authServer) { - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:write")); - - context.baseRequest(token) - .delete("/v5alpha/celexpressions/id") - .then() - .statusCode(403) - .body(containsString("Required user role not satisfied.")); - } - } - - @Nested - @EndToEndTest - class InMemory extends Tests { - - @Order(0) - @RegisterExtension - static final OauthServerEndToEndExtension AUTH_SERVER_EXTENSION = OauthServerEndToEndExtension.Builder.newInstance().build(); - - @Order(1) - @RegisterExtension - static RuntimeExtension runtime = ComponentRuntimeExtension.Builder.newInstance() - .name(Runtimes.ControlPlane.NAME) - .modules(Runtimes.ControlPlane.MODULES) - .endpoints(Runtimes.ControlPlane.ENDPOINTS.build()) - .configurationProvider(Runtimes.ControlPlane::config) - .paramProvider(ManagementEndToEndTestContext.class, ManagementEndToEndTestContext::forContext) - .configurationProvider(AUTH_SERVER_EXTENSION::getConfig) - .build(); - } - - @Nested - @PostgresqlIntegrationTest - class Postgres extends Tests { - - @Order(0) - @RegisterExtension - static final OauthServerEndToEndExtension AUTH_SERVER_EXTENSION = OauthServerEndToEndExtension.Builder.newInstance().build(); - - @RegisterExtension - @Order(0) - static final PostgresqlEndToEndExtension POSTGRES_EXTENSION = new PostgresqlEndToEndExtension(createPgContainer()); - - @Order(0) - @RegisterExtension - static final NatsEndToEndExtension NATS_EXTENSION = new NatsEndToEndExtension(); - - @Order(1) - @RegisterExtension - static final BeforeAllCallback SETUP = context -> { - POSTGRES_EXTENSION.createDatabase(Runtimes.ControlPlane.NAME.toLowerCase()); - }; - - @Order(2) - @RegisterExtension - static RuntimeExtension runtime = ComponentRuntimeExtension.Builder.newInstance() - .name(Runtimes.ControlPlane.NAME) - .modules(Runtimes.ControlPlane.MODULES) - .modules(Runtimes.ControlPlane.PG_MODULES) - .endpoints(Runtimes.ControlPlane.ENDPOINTS.build()) - .configurationProvider(Runtimes.ControlPlane::config) - .configurationProvider(() -> POSTGRES_EXTENSION.configFor(Runtimes.ControlPlane.NAME.toLowerCase())) - .configurationProvider(NATS_EXTENSION::configFor) - .configurationProvider(AUTH_SERVER_EXTENSION::getConfig) - .paramProvider(ManagementEndToEndTestContext.class, ManagementEndToEndTestContext::forContext) - .build(); - - } - -} diff --git a/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/ContractAgreementApiV5EndToEndTest.java b/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/ContractAgreementApiV5EndToEndTest.java deleted file mode 100644 index e631885b..00000000 --- a/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/ContractAgreementApiV5EndToEndTest.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.test.e2e.managementapi.v5; - -import com.nimbusds.jose.JOSEException; -import org.eclipse.edc.api.authentication.OauthServer; -import org.eclipse.edc.api.authentication.OauthServerEndToEndExtension; -import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.store.ContractNegotiationStore; -import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractOffer; -import org.eclipse.edc.junit.annotations.EndToEndTest; -import org.eclipse.edc.junit.annotations.PostgresqlIntegrationTest; -import org.eclipse.edc.junit.extensions.ComponentRuntimeExtension; -import org.eclipse.edc.junit.extensions.RuntimeExtension; -import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; -import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndExtension; -import org.eclipse.edc.test.e2e.managementapi.ManagementEndToEndTestContext; -import org.eclipse.edc.test.e2e.managementapi.Runtimes; -import org.eclipse.edc.virtual.nats.testfixtures.NatsEndToEndExtension; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.RegisterExtension; - -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 org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.FINALIZED; -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.constants.CoreConstants.EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.PARTICIPANT_CONTEXT_ID; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.createParticipant; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.jsonLdContextArray; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.participantContext; -import static org.eclipse.edc.virtual.test.system.fixtures.DockerImages.createPgContainer; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; - -public class ContractAgreementApiV5EndToEndTest { - - @SuppressWarnings("JUnitMalformedDeclaration") - abstract static class Tests { - - private String participantTokenJwt; - - @BeforeEach - void setup(OauthServer authServer, ParticipantContextService participantContextService) throws JOSEException { - createParticipant(participantContextService, PARTICIPANT_CONTEXT_ID); - - participantTokenJwt = authServer.createToken(PARTICIPANT_CONTEXT_ID); - } - - @AfterEach - void teardown(ParticipantContextService participantContextService, ContractNegotiationStore store) { - participantContextService.deleteParticipantContext(PARTICIPANT_CONTEXT_ID) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - store.queryNegotiations(QuerySpec.max()).forEach(cn -> store.deleteById(cn.getId())); - } - - - @Test - void getById(ManagementEndToEndTestContext context, ContractNegotiationStore store) { - var agreement = createContractAgreement("agreement-id"); - store.save(createContractNegotiationBuilder("cn1").contractAgreement(agreement).build()); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractagreements/agreement-id") - .then() - .statusCode(200) - .contentType(JSON) - .body(CONTEXT, contains(jsonLdContextArray())) - .body(TYPE, equalTo("ContractAgreement")) - .body(ID, is(agreement.getId())) - .body("agreementId", is(agreement.getAgreementId())) - .body("assetId", notNullValue()) - .body("policy.assignee", is(agreement.getPolicy().getAssignee())) - .body("policy.assigner", is(agreement.getPolicy().getAssigner())); - - } - - @Test - void getById_tokenBearerDoesNotOwnResource(ManagementEndToEndTestContext context, - OauthServer authServer, - ContractNegotiationStore store, ParticipantContextService srv) { - var agreement = createContractAgreement("agreement-id"); - store.save(createContractNegotiationBuilder("cn1").contractAgreement(agreement).build()); - - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractagreements/agreement-id") - .then() - .statusCode(403); - } - - @Test - void getById_tokenLacksRequiredScope(ManagementEndToEndTestContext context, OauthServer authServer, ContractNegotiationStore store) { - var agreement = createContractAgreement("agreement-id"); - store.save(createContractNegotiationBuilder("cn1").contractAgreement(agreement).build()); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:missing")); - - context.baseRequest(token) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractagreements/agreement-id") - .then() - .statusCode(403); - } - - - @Test - void getNegotiationByAgreementId(ManagementEndToEndTestContext context, ContractNegotiationStore store) { - store.save(createContractNegotiationBuilder("negotiation-id") - .contractAgreement(createContractAgreement("agreement-id")) - .build()); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractagreements/agreement-id/negotiation") - .then() - .statusCode(200) - .contentType(JSON) - .body(CONTEXT, contains(jsonLdContextArray())) - .body(TYPE, equalTo("ContractNegotiation")) - .body(ID, is("negotiation-id")); - - } - - @Test - void getNegotiationByAgreementId_tokenBearerDoesNotOwnResource(ManagementEndToEndTestContext context, - OauthServer authServer, - ContractNegotiationStore store, ParticipantContextService srv) { - var agreement = createContractAgreement("agreement-id"); - store.save(createContractNegotiationBuilder("cn1").contractAgreement(agreement).build()); - - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractagreements/agreement-id/negotiation") - .then() - .statusCode(403); - } - - @Test - void getNegotiationByAgreementId_tokenLacksRequiredScope(ManagementEndToEndTestContext context, - OauthServer authServer, - ContractNegotiationStore store) { - var agreement = createContractAgreement("agreement-id"); - store.save(createContractNegotiationBuilder("cn1").contractAgreement(agreement).build()); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:missing")); - - context.baseRequest(token) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractagreements/agreement-id/negotiation") - .then() - .statusCode(403); - } - - @Test - void query(ManagementEndToEndTestContext context, ContractNegotiationStore store) { - store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); - store.save(createContractNegotiationBuilder("cn2").contractAgreement(createContractAgreement("cn2")).build()); - - var jsonPath = context.baseRequest(participantTokenJwt) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/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"); - assertThat(jsonPath.getList("[0].@context")).contains(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2); - assertThat(jsonPath.getList("[1].@context")).contains(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2); - } - - @Test - void query_tokenBearerIsAdmin_shouldReturnAll(ManagementEndToEndTestContext context, OauthServer authServer, ContractNegotiationStore store) { - store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); - store.save(createContractNegotiationBuilder("cn2").contractAgreement(createContractAgreement("cn2")).build()); - - var token = authServer.createAdminToken(); - - var jsonPath = context.baseRequest(token) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/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"); - assertThat(jsonPath.getList("[0].@context")).contains(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2); - assertThat(jsonPath.getList("[1].@context")).contains(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2); - } - - @Test - void query_shouldLimitToResourceOwner(ManagementEndToEndTestContext context, OauthServer authServer, ContractNegotiationStore store) { - var otherParticipantId = UUID.randomUUID().toString(); - - store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); - store.save(createContractNegotiationBuilder("cn2") - .participantContextId(otherParticipantId) - .contractAgreement(createContractAgreementBuilder("cn2").participantContextId(otherParticipantId).build()).build()); - - var token = authServer.createAdminToken(); - - var jsonPath = context.baseRequest(token) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractagreements/request") - .then() - .log().ifError() - .statusCode(200) - .contentType(JSON) - .body("size()", is(1)) - .extract().jsonPath(); - - assertThat(jsonPath.getString("[0].assetId")).isNotNull(); - assertThat(jsonPath.getString("[0].@id")).isEqualTo("cn1"); - assertThat(jsonPath.getList("[0].@context")).contains(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2); - } - - @Test - void query_tokenBearerNotEqualResourceOwner(ManagementEndToEndTestContext context, - OauthServer authServer, - ContractNegotiationStore store, ParticipantContextService srv) { - var otherParticipantId = UUID.randomUUID().toString(); - srv.createParticipantContext(participantContext(otherParticipantId)) - .orElseThrow(f -> new AssertionError("ParticipantContext " + otherParticipantId + " not created.")); - - store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractagreements/request") - .then() - .log().ifValidationFails() - .statusCode(403) - .body(containsString("User '%s' is not authorized to access this resource.".formatted(otherParticipantId))); - } - - 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()) - .participantContextId(PARTICIPANT_CONTEXT_ID) - .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 id) { - return createContractAgreementBuilder(id) - .build(); - } - - private ContractAgreement.Builder createContractAgreementBuilder(String id) { - return ContractAgreement.Builder.newInstance() - .id(id) - .assetId(UUID.randomUUID().toString()) - .consumerId(UUID.randomUUID() + "-consumer") - .providerId(UUID.randomUUID() + "-provider") - .policy(Policy.Builder.newInstance().assignee("assignee").assigner("assigner").build()) - .participantContextId(PARTICIPANT_CONTEXT_ID); - } - } - - @Nested - @EndToEndTest - class InMemory extends Tests { - - @Order(0) - @RegisterExtension - static final OauthServerEndToEndExtension AUTH_SERVER_EXTENSION = OauthServerEndToEndExtension.Builder.newInstance().build(); - - @Order(1) - @RegisterExtension - static RuntimeExtension runtime = ComponentRuntimeExtension.Builder.newInstance() - .name(Runtimes.ControlPlane.NAME) - .modules(Runtimes.ControlPlane.MODULES) - .endpoints(Runtimes.ControlPlane.ENDPOINTS.build()) - .configurationProvider(Runtimes.ControlPlane::config) - .paramProvider(ManagementEndToEndTestContext.class, ManagementEndToEndTestContext::forContext) - .configurationProvider(AUTH_SERVER_EXTENSION::getConfig) - .build(); - } - - @Nested - @PostgresqlIntegrationTest - class Postgres extends Tests { - - @Order(0) - @RegisterExtension - static final OauthServerEndToEndExtension AUTH_SERVER_EXTENSION = OauthServerEndToEndExtension.Builder.newInstance().build(); - - @RegisterExtension - @Order(0) - static final PostgresqlEndToEndExtension POSTGRES_EXTENSION = new PostgresqlEndToEndExtension(createPgContainer()); - - @Order(0) - @RegisterExtension - static final NatsEndToEndExtension NATS_EXTENSION = new NatsEndToEndExtension(); - - @Order(1) - @RegisterExtension - static final BeforeAllCallback SETUP = context -> { - POSTGRES_EXTENSION.createDatabase(Runtimes.ControlPlane.NAME.toLowerCase()); - }; - - @Order(2) - @RegisterExtension - static RuntimeExtension runtime = ComponentRuntimeExtension.Builder.newInstance() - .name(Runtimes.ControlPlane.NAME) - .modules(Runtimes.ControlPlane.MODULES) - .modules(Runtimes.ControlPlane.PG_MODULES) - .endpoints(Runtimes.ControlPlane.ENDPOINTS.build()) - .configurationProvider(Runtimes.ControlPlane::config) - .configurationProvider(() -> POSTGRES_EXTENSION.configFor(Runtimes.ControlPlane.NAME.toLowerCase())) - .configurationProvider(NATS_EXTENSION::configFor) - .configurationProvider(AUTH_SERVER_EXTENSION::getConfig) - .paramProvider(ManagementEndToEndTestContext.class, ManagementEndToEndTestContext::forContext) - .build(); - - - @AfterEach - void cleanup() { - POSTGRES_EXTENSION.execute(Runtimes.ControlPlane.NAME.toLowerCase(), "DELETE FROM edc_contract_negotiation;"); - POSTGRES_EXTENSION.execute(Runtimes.ControlPlane.NAME.toLowerCase(), "DELETE FROM edc_contract_agreement;"); - } - } -} diff --git a/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/ContractDefinitionApiV5EndToEndTest.java b/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/ContractDefinitionApiV5EndToEndTest.java deleted file mode 100644 index 47eace45..00000000 --- a/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/ContractDefinitionApiV5EndToEndTest.java +++ /dev/null @@ -1,753 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.test.e2e.managementapi.v5; - -import com.nimbusds.jose.JOSEException; -import jakarta.json.JsonObjectBuilder; -import org.eclipse.edc.api.authentication.OauthServer; -import org.eclipse.edc.api.authentication.OauthServerEndToEndExtension; -import org.eclipse.edc.connector.controlplane.contract.spi.offer.store.ContractDefinitionStore; -import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; -import org.eclipse.edc.junit.annotations.ApiTest; -import org.eclipse.edc.junit.annotations.EndToEndTest; -import org.eclipse.edc.junit.annotations.PostgresqlIntegrationTest; -import org.eclipse.edc.junit.extensions.ComponentRuntimeExtension; -import org.eclipse.edc.junit.extensions.RuntimeExtension; -import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService; -import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndExtension; -import org.eclipse.edc.test.e2e.managementapi.ManagementEndToEndTestContext; -import org.eclipse.edc.test.e2e.managementapi.Runtimes; -import org.eclipse.edc.virtual.nats.testfixtures.NatsEndToEndExtension; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.RegisterExtension; - -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Stream; - -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.spi.constants.CoreConstants.EDC_NAMESPACE; -import static org.eclipse.edc.spi.query.Criterion.criterion; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.createParticipant; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.jsonLdContext; -import static org.eclipse.edc.virtual.test.system.fixtures.DockerImages.createPgContainer; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.matchesRegex; - -@ApiTest -public class ContractDefinitionApiV5EndToEndTest { - - @SuppressWarnings("JUnitMalformedDeclaration") - abstract static class Tests { - private static final String PARTICIPANT_CONTEXT_ID = "test-participant"; - - private String participantTokenJwt; - - @BeforeEach - void setup(OauthServer authServer, ParticipantContextService participantContextService) - throws JOSEException { - createParticipant(participantContextService, PARTICIPANT_CONTEXT_ID); - - participantTokenJwt = authServer.createToken(PARTICIPANT_CONTEXT_ID); - - } - - @AfterEach - void teardown(ParticipantContextService participantContextService) { - participantContextService.deleteParticipantContext(PARTICIPANT_CONTEXT_ID) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - } - - - @Test - void create(ManagementEndToEndTestContext context, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - var requestJson = createDefinitionBuilder(id) - .build() - .toString(); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(requestJson) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions") - .then() - .statusCode(200) - .body("@id", equalTo(id)); - - var actual = store.findById(id); - - assertThat(actual.getId()).matches(id); - } - - @Test - void create_tokenBearerNotOwner(ManagementEndToEndTestContext context, OauthServer authServer, ParticipantContextService srv) { - var id = UUID.randomUUID().toString(); - var requestJson = createDefinitionBuilder(id) - .build() - .toString(); - - // create a second participant who will make the request - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .body(requestJson) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions") - .then() - .statusCode(403) - .body(containsString("User '%s' is not authorized to access this resource".formatted(otherParticipantId))); - } - - @Test - void create_resourceNotOwnedByTokenBearer(ManagementEndToEndTestContext context, ParticipantContextService srv) { - var id = UUID.randomUUID().toString(); - var requestJson = createDefinitionBuilder(id) - .build() - .toString(); - - // create a second participant who will make the request - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(requestJson) - .post("/v5alpha/participants/" + otherParticipantId + "/contractdefinitions") - .then() - .statusCode(403); - } - - @Test - void create_tokenBearerIsAdmin(ManagementEndToEndTestContext context, OauthServer authServer, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - var requestJson = createDefinitionBuilder(id) - .build() - .toString(); - - context.baseRequest(authServer.createAdminToken()) - .contentType(JSON) - .body(requestJson) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions") - .then() - .statusCode(200) - .body("@id", equalTo(id)); - - var actual = store.findById(id); - - assertThat(actual.getId()).matches(id); - } - - @Test - void create_tokenLacksRequiredScope(ManagementEndToEndTestContext context, OauthServer authServer) { - - var id = UUID.randomUUID().toString(); - var requestJson = createDefinitionBuilder(id) - .build() - .toString(); - - var offendingToken = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:read")); - context.baseRequest(offendingToken) - .contentType(JSON) - .body(requestJson) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions") - .then() - .statusCode(403) - .body(matchesRegex("(?s).*Required scope.*management-api:write.*missing.*")); - } - - @Test - void create_tokenHasWrongRole(ManagementEndToEndTestContext context, OauthServer authServer) { - - var id = UUID.randomUUID().toString(); - var requestJson = createDefinitionBuilder(id) - .build() - .toString(); - - var offendingToken = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("role", "barbaz")); - context.baseRequest(offendingToken) - .contentType(JSON) - .body(requestJson) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions") - .then() - .statusCode(403) - .body(matchesRegex("Required user role not satisfied.")); - } - - @Test - void queryContractDefinitions_noQuerySpec(ManagementEndToEndTestContext context, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - store.save(createContractDefinition(id).build()); - - var body = context.baseRequest(participantTokenJwt) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/request") - .then() - .statusCode(200) - .body("size()", greaterThan(0)) - .extract().body().as(Map[].class); - - var assetsSelector = Arrays.stream(body) - .filter(it -> it.get(ID).equals(id)) - .map(it -> it.get("assetsSelector")) - .findAny(); - - assertThat(assetsSelector).isPresent().get().asInstanceOf(LIST).hasSize(2); - } - - @Test - void queryContractDefinitionWithSimplePrivateProperties(ManagementEndToEndTestContext context) { - var id = UUID.randomUUID().toString(); - var requestJson = createDefinitionBuilder(id) - .add("privateProperties", createObjectBuilder() - .add("newKey", createObjectBuilder().add(ID, "newValue")) - .build()) - .build() - .toString(); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(requestJson) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions") - .then() - .statusCode(200) - .body("@id", equalTo(id)); - - var matchingQuery = context.query( - criterion("id", "=", id), - criterion("privateProperties.'%snewKey'.@id".formatted(EDC_NAMESPACE), "=", - "newValue")).toString(); - - context.baseRequest(participantTokenJwt) - .body(matchingQuery) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/request") - .then() - .log().ifValidationFails() - .statusCode(200) - .body("size()", is(1)); - - var nonMatchingQuery = context.query( - criterion("id", "=", id), - criterion("privateProperties.'%snewKey'.@id".formatted(EDC_NAMESPACE), "=", - "anything-else")).toString(); - - context.baseRequest(participantTokenJwt) - .body(nonMatchingQuery) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(0)); - } - - @Test - void queryContractDefinitions_sortByCreatedDate(ManagementEndToEndTestContext context, ContractDefinitionStore store) { - var id1 = UUID.randomUUID().toString(); - var id2 = UUID.randomUUID().toString(); - var id3 = UUID.randomUUID().toString(); - var createdAtTime = new AtomicLong(1000L); - Stream.of(id1, id2, id3).forEach(id -> store.save(createContractDefinition(id) - .createdAt(createdAtTime.getAndIncrement()).build())); - - var query = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "QuerySpec") - .add("sortField", "createdAt") - .add("sortOrder", "DESC") - .add("limit", 100) - .add("offset", 0) - .build() - .toString(); - - var result = context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(query) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(3)) - .extract() - .as(List.class); - - assertThat(result) - .extracting(cd -> ((LinkedHashMap) cd).get(ID)) - .containsExactlyElementsOf(List.of(id3, id2, id1)); - } - - - @Test - void query_tokenBearerIsAdmin(ManagementEndToEndTestContext context, OauthServer authServer, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - store.save(createContractDefinition(id).build()); - - var body = context.baseRequest(authServer.createAdminToken()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/request") - .then() - .statusCode(200) - .body("size()", greaterThan(0)) - .extract().body().as(Map[].class); - - var assetsSelector = Arrays.stream(body) - .filter(it -> it.get(ID).equals(id)) - .map(it -> it.get("assetsSelector")) - .findAny(); - - assertThat(assetsSelector).isPresent().get().asInstanceOf(LIST).hasSize(2); - } - - @Test - void query_shouldLimitToResourceOwner(ManagementEndToEndTestContext context, ContractDefinitionStore store) { - store.save(createContractDefinition("cd-1").build()); - store.save(createContractDefinition("cd-2").build()); - store.save(createContractDefinition("other-cd-1").participantContextId("another-participant").build()); - store.save(createContractDefinition("other-cd-2").participantContextId("another-participant").build()); - - var body = context.baseRequest(participantTokenJwt) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/request") - .then() - .statusCode(200) - .body("size()", greaterThanOrEqualTo(2)) - .extract().body().as(Map[].class); - - - assertThat(Arrays.stream(body)).anyMatch(e -> e.get(ID).equals("cd-1")); - assertThat(Arrays.stream(body)).anyMatch(e -> e.get(ID).equals("cd-2")); - assertThat(Arrays.stream(body)).noneMatch(e -> e.get(ID).equals("another-cd-1")); - assertThat(Arrays.stream(body)).noneMatch(e -> e.get(ID).equals("another-cd-2")); - } - - @Test - void query_tokenBearerNotResourceOwner(ManagementEndToEndTestContext context, OauthServer authServer, - ContractDefinitionStore store, ParticipantContextService srv) { - var id = UUID.randomUUID().toString(); - store.save(createContractDefinition(id).build()); - - createParticipant(srv, "another-participant"); - var token = authServer.createToken("another-participant"); - - context.baseRequest(token) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/request") - .then() - .statusCode(403) - .body(containsString("User '%s' is not authorized to access this resource".formatted("another-participant"))); - } - - @Test - void query_tokenLacksScope(ManagementEndToEndTestContext context, OauthServer authServer, - ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - store.save(createContractDefinition(id).build()); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:bizzbuzz")); - - context.baseRequest(token) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/request") - .then() - .statusCode(403) - .body(matchesRegex("(?s).*Required scope.*management-api:read.*missing.*")); - } - - @Test - void query_tokenHasWrongRole(ManagementEndToEndTestContext context, OauthServer authServer, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - store.save(createContractDefinition(id).build()); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("role", "some-role")); - - context.baseRequest(token) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/request") - .then() - .statusCode(403) - .body(containsString("Required user role not satisfied.")); - } - - @Test - void delete(ManagementEndToEndTestContext context, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - var entity = createContractDefinition(id).build(); - store.save(entity); - - context.baseRequest(participantTokenJwt) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/" + id) - .then() - .statusCode(204); - - var actual = store.findById(id); - - assertThat(actual).isNull(); - } - - @Test - void delete_tokenBearerNotOwner(ManagementEndToEndTestContext context, OauthServer authServer, - ContractDefinitionStore store, ParticipantContextService srv) { - var id = UUID.randomUUID().toString(); - var entity = createContractDefinition(id) - .build(); - store.save(entity).orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - // create a second participant who will make the request - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/" + id) - .then() - .statusCode(403) - .body(containsString("User '%s' is not authorized to access this resource".formatted(otherParticipantId))); - - } - - @Test - void delete_resourceNotOwnedByTokenBearer(ManagementEndToEndTestContext context, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - var otherParticipantId = "other-participant"; - var entity = createContractDefinition(id) - .participantContextId(otherParticipantId) - .build(); - store.save(entity).orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - //url path != actual owner - context.baseRequest(participantTokenJwt) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/" + id) - .then() - .statusCode(404); - - // url path == actual owner, but token is not authorized to access it - context.baseRequest(participantTokenJwt) - .delete("/v5alpha/participants/" + otherParticipantId + "/contractdefinitions/" + id) - .then() - .statusCode(403); - } - - @Test - void delete_tokenBearerIsAdmin(ManagementEndToEndTestContext context, OauthServer authServer, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - var entity = createContractDefinition(id).build(); - store.save(entity); - - var adminToken = authServer.createAdminToken(); - context.baseRequest(adminToken) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/" + id) - .then() - .statusCode(204); - - var actual = store.findById(id); - - assertThat(actual).isNull(); - } - - @Test - void delete_tokenLacksRequiredScopes(ManagementEndToEndTestContext context, OauthServer authServer, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - var entity = createContractDefinition(id).build(); - store.save(entity); - - var offendingToken = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:foobar")); - - context.baseRequest(offendingToken) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/" + id) - .then() - .statusCode(403); - } - - @Test - void delete_tokenHasWrongRole(ManagementEndToEndTestContext context, OauthServer authServer, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - var entity = createContractDefinition(id).build(); - store.save(entity); - - var offendingToken = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("role", "barbaz")); - - context.baseRequest(offendingToken) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions/" + id) - .then() - .statusCode(403); - } - - @Test - void update_whenExists(ManagementEndToEndTestContext context, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - var entity = createContractDefinition(id).build(); - store.save(entity); - - var updated = createDefinitionBuilder(id) - .add("accessPolicyId", "new-policy") - .build() - .toString(); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(updated) - .put("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions") - .then() - .statusCode(204); - - var actual = store.findById(id); - - assertThat(actual.getAccessPolicyId()).isEqualTo("new-policy"); - } - - @Test - void update_whenNotExists(ManagementEndToEndTestContext context) { - var updated = createDefinitionBuilder(UUID.randomUUID().toString()) - .add("accessPolicyId", "new-policy") - .build() - .toString(); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(updated) - .put("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions") - .then() - .statusCode(404); - } - - @Test - void update_tokenBearerNotOwner(ManagementEndToEndTestContext context, OauthServer authServer, ContractDefinitionStore store, ParticipantContextService srv) { - - var id = UUID.randomUUID().toString(); - var entity = createContractDefinition(id).build(); - store.save(entity); - - // create a second participant who will make the request - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - var offendingToken = authServer.createToken(otherParticipantId); - - - var updated = createDefinitionBuilder(id) - .add("accessPolicyId", "new-policy") - .build() - .toString(); - - context.baseRequest(offendingToken) - .contentType(JSON) - .body(updated) - .put("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions") - .then() - .statusCode(403) - .body(containsString("User '%s' is not authorized to access this resource".formatted(otherParticipantId))); - } - - @Test - void update_resourceNotOwnedByTokenBearer(ManagementEndToEndTestContext context, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - var otherParticipantId = "other-participant"; - var entity = createContractDefinition(id) - .participantContextId(otherParticipantId) - .build(); - store.save(entity); - - var updated = createDefinitionBuilder(id) - .add("accessPolicyId", "new-policy") - .build() - .toString(); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(updated) - .put("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions") - .then() - .statusCode(404); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(updated) - .put("/v5alpha/participants/" + otherParticipantId + "/contractdefinitions") - .then() - .statusCode(403); - } - - @Test - void update_tokenBearerIsAdmin(ManagementEndToEndTestContext context, OauthServer authServer, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - var entity = createContractDefinition(id).build(); - store.save(entity); - - var updated = createDefinitionBuilder(id) - .add("accessPolicyId", "new-policy") - .build() - .toString(); - - context.baseRequest(authServer.createAdminToken()) - .contentType(JSON) - .body(updated) - .put("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions") - .then() - .statusCode(204); - } - - @Test - void update_tokenHasWrongRole(ManagementEndToEndTestContext context, OauthServer authServer, ContractDefinitionStore store) { - - var id = UUID.randomUUID().toString(); - var entity = createContractDefinition(id).build(); - store.save(entity); - - var updated = createDefinitionBuilder(id) - .add("accessPolicyId", "new-policy") - .build() - .toString(); - - var offendingToken = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("role", "barbaz")); - context.baseRequest(offendingToken) - .contentType(JSON) - .body(updated) - .put("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions") - .then() - .statusCode(403); - } - - @Test - void update_tokenLacksRequiredScopes(ManagementEndToEndTestContext context, OauthServer authServer, ContractDefinitionStore store) { - var id = UUID.randomUUID().toString(); - var entity = createContractDefinition(id).build(); - store.save(entity); - - var updated = createDefinitionBuilder(id) - .add("accessPolicyId", "new-policy") - .build() - .toString(); - - var offendingToken = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:read")); - context.baseRequest(offendingToken) - .contentType(JSON) - .body(updated) - .put("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractdefinitions") - .then() - .statusCode(403); - } - - private JsonObjectBuilder createDefinitionBuilder(String id) { - return createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "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()) - .participantContextId(PARTICIPANT_CONTEXT_ID) - .assetsSelectorCriterion(criterion("foo", "=", "bar")) - .assetsSelectorCriterion(criterion("bar", "=", "baz")); - } - } - - @Nested - @EndToEndTest - class InMemory extends Tests { - - @Order(0) - @RegisterExtension - static final OauthServerEndToEndExtension AUTH_SERVER_EXTENSION = OauthServerEndToEndExtension.Builder.newInstance().build(); - - @Order(1) - @RegisterExtension - static RuntimeExtension runtime = ComponentRuntimeExtension.Builder.newInstance() - .name(Runtimes.ControlPlane.NAME) - .modules(Runtimes.ControlPlane.MODULES) - .endpoints(Runtimes.ControlPlane.ENDPOINTS.build()) - .configurationProvider(Runtimes.ControlPlane::config) - .configurationProvider(AUTH_SERVER_EXTENSION::getConfig) - .paramProvider(ManagementEndToEndTestContext.class, ManagementEndToEndTestContext::forContext) - .build(); - } - - @Nested - @PostgresqlIntegrationTest - class Postgres extends Tests { - - @Order(0) - @RegisterExtension - static final OauthServerEndToEndExtension AUTH_SERVER_EXTENSION = OauthServerEndToEndExtension.Builder.newInstance().build(); - - @RegisterExtension - @Order(0) - static final PostgresqlEndToEndExtension POSTGRES_EXTENSION = new PostgresqlEndToEndExtension( - createPgContainer()); - - @Order(0) - @RegisterExtension - static final NatsEndToEndExtension NATS_EXTENSION = new NatsEndToEndExtension(); - - @Order(1) - @RegisterExtension - static final BeforeAllCallback SETUP = context -> { - POSTGRES_EXTENSION.createDatabase(Runtimes.ControlPlane.NAME.toLowerCase()); - }; - - @Order(2) - @RegisterExtension - static RuntimeExtension runtime = ComponentRuntimeExtension.Builder.newInstance() - .name(Runtimes.ControlPlane.NAME) - .modules(Runtimes.ControlPlane.MODULES) - .modules(Runtimes.ControlPlane.PG_MODULES) - .endpoints(Runtimes.ControlPlane.ENDPOINTS.build()) - .configurationProvider(Runtimes.ControlPlane::config) - .configurationProvider(() -> POSTGRES_EXTENSION.configFor(Runtimes.ControlPlane.NAME.toLowerCase())) - .configurationProvider(NATS_EXTENSION::configFor) - .configurationProvider(AUTH_SERVER_EXTENSION::getConfig) - .paramProvider(ManagementEndToEndTestContext.class, ManagementEndToEndTestContext::forContext) - .build(); - - } - -} \ No newline at end of file diff --git a/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/ContractNegotiationApiV5EndToEndTest.java b/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/ContractNegotiationApiV5EndToEndTest.java deleted file mode 100644 index 227f5eee..00000000 --- a/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/ContractNegotiationApiV5EndToEndTest.java +++ /dev/null @@ -1,786 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.test.e2e.managementapi.v5; - -import com.nimbusds.jose.JOSEException; -import jakarta.json.Json; -import jakarta.json.JsonArrayBuilder; -import jakarta.json.JsonObject; -import org.eclipse.edc.api.authentication.OauthServer; -import org.eclipse.edc.api.authentication.OauthServerEndToEndExtension; -import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.store.ContractNegotiationStore; -import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates; -import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractOffer; -import org.eclipse.edc.junit.annotations.EndToEndTest; -import org.eclipse.edc.junit.annotations.PostgresqlIntegrationTest; -import org.eclipse.edc.junit.extensions.ComponentRuntimeExtension; -import org.eclipse.edc.junit.extensions.RuntimeExtension; -import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.policy.model.PolicyType; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; -import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndExtension; -import org.eclipse.edc.test.e2e.managementapi.ManagementEndToEndTestContext; -import org.eclipse.edc.test.e2e.managementapi.Runtimes; -import org.eclipse.edc.virtual.nats.testfixtures.NatsEndToEndExtension; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.RegisterExtension; - -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; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.AGREED; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.REQUESTED; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.TERMINATED; -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.constants.CoreConstants.EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.PARTICIPANT_CONTEXT_ID; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.createParticipant; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.jsonLdContext; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.jsonLdContextArray; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.participantContext; -import static org.eclipse.edc.virtual.test.system.fixtures.DockerImages.createPgContainer; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.matchesRegex; - -public class ContractNegotiationApiV5EndToEndTest { - - @SuppressWarnings("JUnitMalformedDeclaration") - abstract static class Tests { - - private static final String COUNTER_PARTY_ID = "counter-party-id"; - - private String participantTokenJwt; - - @BeforeEach - void setup(OauthServer authServer, ParticipantContextService participantContextService) throws JOSEException { - createParticipant(participantContextService, PARTICIPANT_CONTEXT_ID); - - participantTokenJwt = authServer.createToken(PARTICIPANT_CONTEXT_ID); - } - - @AfterEach - void teardown(ParticipantContextService participantContextService, ContractNegotiationStore store) { - participantContextService.deleteParticipantContext(PARTICIPANT_CONTEXT_ID) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - store.queryNegotiations(QuerySpec.max()).forEach(cn -> store.deleteById(cn.getId())); - } - - @Test - void initiate(ManagementEndToEndTestContext context, ContractNegotiationStore store) { - - var requestJson = contractRequestJson(); - - var id = context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(requestJson.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations") - .then() - .log().ifValidationFails() - .statusCode(200) - .contentType(JSON) - .extract().jsonPath().getString(ID); - - assertThat(store.findById(id)).isNotNull(); - } - - @Test - void initiate_whenValidationFails(ManagementEndToEndTestContext context) { - - var requestJson = createObjectBuilder() - .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .add(TYPE, "ContractRequest") - .add("counterPartyAddress", "test-address") - .build(); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(requestJson.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations") - .then() - .log().ifValidationFails() - .statusCode(400); - } - - @Test - void initiate_tokenBearerWrong(ManagementEndToEndTestContext context, OauthServer authServer, ParticipantContextService service) { - var requestJson = contractRequestJson(); - - var otherParticipantId = UUID.randomUUID().toString(); - - service.createParticipantContext(participantContext(otherParticipantId)) - .orElseThrow(f -> new AssertionError("ParticipantContext " + otherParticipantId + " not created.")); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .body(requestJson.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations") - .then() - .log().ifValidationFails() - .statusCode(403) - .body(containsString("User '%s' is not authorized to access this resource.".formatted(otherParticipantId))); - } - - @Test - void initiate_tokenLacksWriteScope(ManagementEndToEndTestContext context, OauthServer authServer, ParticipantContextService service) { - var requestJson = contractRequestJson(); - - var otherParticipantId = UUID.randomUUID().toString(); - - service.createParticipantContext(participantContext(otherParticipantId)) - .orElseThrow(f -> new AssertionError("ParticipantContext " + otherParticipantId + " not created.")); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:read")); - - context.baseRequest(token) - .contentType(JSON) - .body(requestJson.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations") - .then() - .log().ifValidationFails() - .statusCode(403) - .body(matchesRegex("(?s).*Required scope.*missing.*")); - } - - @Test - void initiate_tokenBearerIsAdmin(ManagementEndToEndTestContext context, OauthServer authServer, ContractNegotiationStore store) { - - var requestJson = contractRequestJson(); - - var token = authServer.createAdminToken(); - - var id = context.baseRequest(token) - .contentType(JSON) - .body(requestJson.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations") - .then() - .log().ifValidationFails() - .statusCode(200) - .contentType(JSON) - .extract().jsonPath().getString(ID); - - assertThat(store.findById(id)).isNotNull(); - } - - @Test - void getById(ManagementEndToEndTestContext context, ContractNegotiationStore store) { - store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1") - .then() - .statusCode(200) - .contentType(JSON) - .body(CONTEXT, contains(jsonLdContextArray())) - .body(TYPE, equalTo("ContractNegotiation")) - .body(ID, is("cn1")) - .body("protocol", equalTo("dataspace-protocol-http")); - } - - @Test - void getById_tokenBearerDoesNotOwnResource(ManagementEndToEndTestContext context, OauthServer authServer, - ContractNegotiationStore store, ParticipantContextService srv) { - store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); - - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1") - .then() - .statusCode(403); - } - - @Test - void getById_tokenLacksRequiredScope(ManagementEndToEndTestContext context, OauthServer authServer, ContractNegotiationStore store) { - store.save(createContractNegotiationBuilder("cn1").contractAgreement(createContractAgreement("cn1")).build()); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:missing")); - - context.baseRequest(token) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1") - .then() - .statusCode(403); - } - - @Test - void getState(ManagementEndToEndTestContext context, ContractNegotiationStore store) { - var state = ContractNegotiationStates.FINALIZED.code(); // all other states could be modified by the state machine - store.save(createContractNegotiationBuilder("cn1").state(state).build()); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1/state") - .then() - .statusCode(200) - .contentType(JSON) - .body("state", is("FINALIZED")); - } - - @Test - void getState_tokenBearerDoesNotOwnResource(ManagementEndToEndTestContext context, OauthServer authServer, - ContractNegotiationStore store, ParticipantContextService srv) { - var state = ContractNegotiationStates.FINALIZED.code(); - store.save(createContractNegotiationBuilder("cn1").state(state).build()); - - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1/state") - .then() - .statusCode(403); - - } - - @Test - void getState_tokenLacksRequiredScope(ManagementEndToEndTestContext context, OauthServer authServer, ContractNegotiationStore store) { - var state = ContractNegotiationStates.FINALIZED.code(); - store.save(createContractNegotiationBuilder("cn1").state(state).build()); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:missing")); - - context.baseRequest(token) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1/state") - .then() - .statusCode(403); - - } - - @Test - void getAgreement(ManagementEndToEndTestContext context, ContractNegotiationStore store) { - var agreement = createContractAgreement("cn1"); - store.save(createContractNegotiationBuilder("cn1").contractAgreement(agreement).build()); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1/agreement") - .then() - .statusCode(200) - .contentType(JSON) - .body(CONTEXT, contains(jsonLdContextArray())) - .body(TYPE, equalTo("ContractAgreement")) - .body(ID, is("cn1")) - .body("assetId", equalTo(agreement.getAssetId())) - .body("policy.'@type'", equalTo("Agreement")); - - } - - @Test - void getAgreement_tokenBearerDoesNotOwnResource(ManagementEndToEndTestContext context, OauthServer authServer, - ContractNegotiationStore store, ParticipantContextService srv) { - var agreement = createContractAgreement("cn1"); - store.save(createContractNegotiationBuilder("cn1").contractAgreement(agreement).build()); - - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1/agreement") - .then() - .statusCode(403); - - } - - @Test - void getAgreement_tokenLacksRequiredScope(ManagementEndToEndTestContext context, OauthServer authServer, - ContractNegotiationStore store) { - var agreement = createContractAgreement("cn1"); - store.save(createContractNegotiationBuilder("cn1").contractAgreement(agreement).build()); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:missing")); - - context.baseRequest(token) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1/agreement") - .then() - .statusCode(403); - - } - - @Test - void query(ManagementEndToEndTestContext context, ContractNegotiationStore store) { - var id1 = UUID.randomUUID().toString(); - var id2 = UUID.randomUUID().toString(); - store.save(createContractNegotiationBuilder(id1).counterPartyAddress(context.providerProtocolUrl(COUNTER_PARTY_ID)).build()); - store.save(createContractNegotiationBuilder(id2).counterPartyAddress(context.providerProtocolUrl(COUNTER_PARTY_ID)).build()); - - var query = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "QuerySpec") - .add("filterExpression", createArrayBuilder() - .add(createObjectBuilder() - .add(TYPE, "Criterion") - .add("operandLeft", "id") - .add("operator", "in") - .add("operandRight", createArrayBuilder().add(id1).add(id2)) - ) - .add(createObjectBuilder() - .add(TYPE, "Criterion") - .add("operandLeft", "pending") - .add("operator", "=") - .add("operandRight", false) - ) - ) - .build(); - - var jsonPath = context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(query.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/request") - .then() - .log().ifValidationFails() - .statusCode(200) - .contentType(JSON) - .body("size()", is(2)) - .extract().jsonPath(); - - assertThat(jsonPath.getString("[0].counterPartyAddress")).isEqualTo(context.providerProtocolUrl(COUNTER_PARTY_ID)); - 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"); - assertThat(jsonPath.getString("[0].@type")).isEqualTo("ContractNegotiation"); - assertThat(jsonPath.getString("[1].@type")).isEqualTo("ContractNegotiation"); - assertThat(jsonPath.getList("[0].@context")).contains(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2); - assertThat(jsonPath.getList("[1].@context")).contains(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2); - - } - - @Test - void query_tokenBearerIsAdmin_shouldReturnAll(ManagementEndToEndTestContext context, OauthServer authServer, ContractNegotiationStore store) { - - var id1 = UUID.randomUUID().toString(); - var id2 = UUID.randomUUID().toString(); - store.save(createContractNegotiationBuilder(id1).counterPartyAddress(context.providerProtocolUrl(COUNTER_PARTY_ID)).build()); - store.save(createContractNegotiationBuilder(id2).counterPartyAddress(context.providerProtocolUrl(COUNTER_PARTY_ID)).build()); - - var query = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "QuerySpec") - .add("filterExpression", createArrayBuilder() - .add(createObjectBuilder() - .add(TYPE, "Criterion") - .add("operandLeft", "id") - .add("operator", "in") - .add("operandRight", createArrayBuilder().add(id1).add(id2)) - ) - .add(createObjectBuilder() - .add(TYPE, "Criterion") - .add("operandLeft", "pending") - .add("operator", "=") - .add("operandRight", false) - ) - ) - .build(); - - var token = authServer.createAdminToken(); - - context.baseRequest(token) - .contentType(JSON) - .body(query.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/request") - .then() - .log().ifValidationFails() - .statusCode(200) - .contentType(JSON) - .body("size()", is(2)); - - } - - @Test - void query_shouldLimitToResourceOwner(ManagementEndToEndTestContext context, ContractNegotiationStore store) { - var otherParticipantId = UUID.randomUUID().toString(); - - var id1 = UUID.randomUUID().toString(); - var id2 = UUID.randomUUID().toString(); - store.save(createContractNegotiationBuilder(id1).counterPartyAddress(context.providerProtocolUrl(COUNTER_PARTY_ID)).build()); - store.save(createContractNegotiationBuilder(id2).participantContextId(otherParticipantId).counterPartyAddress(context.providerProtocolUrl(COUNTER_PARTY_ID)).build()); - - var query = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "QuerySpec") - .add("filterExpression", createArrayBuilder() - .add(createObjectBuilder() - .add(TYPE, "Criterion") - .add("operandLeft", "id") - .add("operator", "in") - .add("operandRight", createArrayBuilder().add(id1).add(id2)) - ) - .add(createObjectBuilder() - .add(TYPE, "Criterion") - .add("operandLeft", "pending") - .add("operator", "=") - .add("operandRight", false) - ) - ) - .build(); - - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(query.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/request") - .then() - .log().ifValidationFails() - .statusCode(200) - .contentType(JSON) - .body("size()", is(1)) - .body("[0].@id", is(id1)); - - } - - @Test - void query_tokenBearerNotEqualResourceOwner(ManagementEndToEndTestContext context, OauthServer authServer, ContractNegotiationStore store, ParticipantContextService srv) { - var otherParticipantId = UUID.randomUUID().toString(); - srv.createParticipantContext(participantContext(otherParticipantId)) - .orElseThrow(f -> new AssertionError("ParticipantContext " + otherParticipantId + " not created.")); - var id = UUID.randomUUID().toString(); - store.save(createContractNegotiationBuilder(id).counterPartyAddress(context.providerProtocolUrl(COUNTER_PARTY_ID)).build()); - - var query = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "QuerySpec") - .add("filterExpression", createArrayBuilder() - .add(createObjectBuilder() - .add(TYPE, "Criterion") - .add("operandLeft", "id") - .add("operator", "in") - .add("operandRight", createArrayBuilder().add(id)) - ) - .add(createObjectBuilder() - .add(TYPE, "Criterion") - .add("operandLeft", "pending") - .add("operator", "=") - .add("operandRight", false) - ) - ) - .build(); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .body(query.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/request") - .then() - .log().ifValidationFails() - .statusCode(403) - .body(containsString("User '%s' is not authorized to access this resource.".formatted(otherParticipantId))); - - } - - @Test - void terminate(ManagementEndToEndTestContext context, ContractNegotiationStore store) { - store.save(createContractNegotiationBuilder("cn1").build()); - var requestBody = createObjectBuilder() - .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .add(ID, "cn1") - .add(TYPE, "TerminateNegotiation") - .add("reason", "any good reason") - .build(); - - context.baseRequest(participantTokenJwt) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1/terminate") - .then() - .log().ifError() - .statusCode(204); - } - - @Test - void terminate_tokenBearerDoesNotOwnResource(ManagementEndToEndTestContext context, OauthServer authServer, - ContractNegotiationStore store, - ParticipantContextService srv) { - store.save(createContractNegotiationBuilder("cn1").build()); - var requestBody = createObjectBuilder() - .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .add(ID, "cn1") - .add(TYPE, "TerminateNegotiation") - .add("reason", "any good reason") - .build(); - - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1/terminate") - .then() - .log().ifError() - .statusCode(403); - } - - @Test - void terminate_tokenLacksRequiredScope(ManagementEndToEndTestContext context, OauthServer authServer, - ContractNegotiationStore store) { - store.save(createContractNegotiationBuilder("cn1").build()); - var requestBody = createObjectBuilder() - .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .add(ID, "cn1") - .add(TYPE, "TerminateNegotiation") - .add("reason", "any good reason") - .build(); - - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:missing")); - - context.baseRequest(token) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1/terminate") - .then() - .log().ifError() - .statusCode(403); - } - - @Test - void delete(ManagementEndToEndTestContext context, ContractNegotiationStore store) { - store.save(createContractNegotiationBuilder("cn1") - .state(TERMINATED.code()).build()); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1") - .then() - .statusCode(204); - - assertThat(store.findById("cn1")).isNull(); - } - - @Test - void delete_shouldFailDueToWrongState(ManagementEndToEndTestContext context, ContractNegotiationStore store) { - store.save(createContractNegotiationBuilder("cn1") - .state(AGREED.code()).build()); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1") - .then() - .statusCode(409); - - assertThat(store.findById("cn1")).isNotNull(); - } - - @Test - void delete_tokenBearerDoesNotOwnResource(ManagementEndToEndTestContext context, OauthServer authServer, - ContractNegotiationStore store, - ParticipantContextService srv) { - store.save(createContractNegotiationBuilder("cn1").build()); - - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1") - .then() - .statusCode(403); - } - - @Test - void delete_tokenLacksRequiredScope(ManagementEndToEndTestContext context, OauthServer authServer, - ContractNegotiationStore store) { - store.save(createContractNegotiationBuilder("cn1").build()); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:missing")); - - context.baseRequest(token) - .contentType(JSON) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/contractnegotiations/cn1") - .then() - .statusCode(403); - } - - private JsonObject contractRequestJson() { - return createObjectBuilder() - .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .add(TYPE, "ContractRequest") - .add("counterPartyAddress", "test-address") - .add("protocol", "test-protocol") - .add("providerId", "test-provider-id") - .add("callbackAddresses", createCallbackAddress()) - .add("policy", createPolicy()) - .build(); - } - - private ContractNegotiation.Builder createContractNegotiationBuilder(String negotiationId) { - return ContractNegotiation.Builder.newInstance() - .id(negotiationId) - .correlationId(negotiationId) - .counterPartyId(randomUUID().toString()) - .counterPartyAddress("http://counter-party/address") - .callbackAddresses(List.of(CallbackAddress.Builder.newInstance() - .uri("local://test") - .events(Set.of("test-event1", "test-event2")) - .build())) - .protocol("dataspace-protocol-http") - .state(REQUESTED.code()) - .participantContextId(PARTICIPANT_CONTEXT_ID) - .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().type(PolicyType.CONTRACT).build()) - .participantContextId(PARTICIPANT_CONTEXT_ID) - .build(); - } - - private JsonArrayBuilder createCallbackAddress() { - var builder = Json.createArrayBuilder(); - return builder.add(createObjectBuilder() - .add(TYPE, "CallbackAddress") - .add("transactional", false) - .add("uri", "http://test.local/") - .add("events", Json.createArrayBuilder().build())); - } - - private JsonObject createPolicy() { - var permissionJson = createObjectBuilder().add(TYPE, "permission") - .add("action", "use") - .build(); - var prohibitionJson = createObjectBuilder().add(TYPE, "prohibition") - .add("action", "use") - .build(); - var dutyJson = createObjectBuilder().add(TYPE, "duty") - .add("action", "use") - .build(); - - return createObjectBuilder() - .add(CONTEXT, "http://www.w3.org/ns/odrl.jsonld") - .add(TYPE, "Offer") - .add(ID, "offer-id") - .add("permission", createArrayBuilder().add(permissionJson)) - .add("prohibition", createArrayBuilder().add(prohibitionJson)) - .add("obligation", createArrayBuilder().add(dutyJson)) - .add("assigner", "provider-id") - .add("target", "asset-id") - .build(); - } - - } - - @Nested - @EndToEndTest - class InMemory extends Tests { - - @Order(0) - @RegisterExtension - static final OauthServerEndToEndExtension AUTH_SERVER_EXTENSION = OauthServerEndToEndExtension.Builder.newInstance().build(); - - @Order(1) - @RegisterExtension - static RuntimeExtension runtime = ComponentRuntimeExtension.Builder.newInstance() - .name(Runtimes.ControlPlane.NAME) - .modules(Runtimes.ControlPlane.MODULES) - .endpoints(Runtimes.ControlPlane.ENDPOINTS.build()) - .configurationProvider(Runtimes.ControlPlane::config) - .paramProvider(ManagementEndToEndTestContext.class, ManagementEndToEndTestContext::forContext) - .configurationProvider(AUTH_SERVER_EXTENSION::getConfig) - .build(); - } - - @Nested - @PostgresqlIntegrationTest - class Postgres extends Tests { - - @Order(0) - @RegisterExtension - static final OauthServerEndToEndExtension AUTH_SERVER_EXTENSION = OauthServerEndToEndExtension.Builder.newInstance().build(); - - @RegisterExtension - @Order(0) - static final PostgresqlEndToEndExtension POSTGRES_EXTENSION = new PostgresqlEndToEndExtension(createPgContainer()); - - @Order(0) - @RegisterExtension - static final NatsEndToEndExtension NATS_EXTENSION = new NatsEndToEndExtension(); - - @Order(1) - @RegisterExtension - static final BeforeAllCallback SETUP = context -> { - POSTGRES_EXTENSION.createDatabase(Runtimes.ControlPlane.NAME.toLowerCase()); - }; - - @Order(2) - @RegisterExtension - static RuntimeExtension runtime = ComponentRuntimeExtension.Builder.newInstance() - .name(Runtimes.ControlPlane.NAME) - .modules(Runtimes.ControlPlane.MODULES) - .modules(Runtimes.ControlPlane.PG_MODULES) - .endpoints(Runtimes.ControlPlane.ENDPOINTS.build()) - .configurationProvider(Runtimes.ControlPlane::config) - .configurationProvider(() -> POSTGRES_EXTENSION.configFor(Runtimes.ControlPlane.NAME.toLowerCase())) - .configurationProvider(NATS_EXTENSION::configFor) - .configurationProvider(AUTH_SERVER_EXTENSION::getConfig) - .paramProvider(ManagementEndToEndTestContext.class, ManagementEndToEndTestContext::forContext) - .build(); - - } - -} diff --git a/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/PolicyDefinitionApiV5EndToEndTest.java b/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/PolicyDefinitionApiV5EndToEndTest.java deleted file mode 100644 index 09873758..00000000 --- a/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/PolicyDefinitionApiV5EndToEndTest.java +++ /dev/null @@ -1,960 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.test.e2e.managementapi.v5; - -import com.nimbusds.jose.JOSEException; -import io.restassured.http.ContentType; -import jakarta.json.JsonObject; -import org.eclipse.edc.api.authentication.OauthServer; -import org.eclipse.edc.api.authentication.OauthServerEndToEndExtension; -import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; -import org.eclipse.edc.connector.controlplane.policy.spi.store.PolicyDefinitionStore; -import org.eclipse.edc.junit.annotations.EndToEndTest; -import org.eclipse.edc.junit.annotations.PostgresqlIntegrationTest; -import org.eclipse.edc.junit.extensions.ComponentRuntimeExtension; -import org.eclipse.edc.junit.extensions.RuntimeExtension; -import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndExtension; -import org.eclipse.edc.test.e2e.managementapi.ManagementEndToEndTestContext; -import org.eclipse.edc.test.e2e.managementapi.Runtimes; -import org.eclipse.edc.virtual.nats.testfixtures.NatsEndToEndExtension; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.RegisterExtension; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.stream.IntStream; - -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.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.spi.query.Criterion.criterion; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.createParticipant; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.jsonLdContext; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.jsonLdContextArray; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.participantContext; -import static org.eclipse.edc.virtual.test.system.fixtures.DockerImages.createPgContainer; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.matchesRegex; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.Matchers.startsWith; - -public class PolicyDefinitionApiV5EndToEndTest { - - @SuppressWarnings("JUnitMalformedDeclaration") - abstract static class Tests { - - private static final String PARTICIPANT_CONTEXT_ID = "test-participant"; - - private String participantTokenJwt; - - @BeforeEach - void setup(OauthServer authServer, ParticipantContextService participantContextService) throws JOSEException { - createParticipant(participantContextService, PARTICIPANT_CONTEXT_ID); - - participantTokenJwt = authServer.createToken(PARTICIPANT_CONTEXT_ID); - } - - @AfterEach - void teardown(ParticipantContextService participantContextService, PolicyDefinitionStore store) { - participantContextService.deleteParticipantContext(PARTICIPANT_CONTEXT_ID) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - store.findAll(QuerySpec.max()).forEach(pd -> store.delete(pd.getId())); - } - - @Test - void create(ManagementEndToEndTestContext context) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .build(); - - var id = context.baseRequest(participantTokenJwt) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions") - .then() - .log().ifValidationFails() - .contentType(JSON) - .statusCode(200) - .extract().jsonPath().getString(ID); - - context.baseRequest(participantTokenJwt) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + id) - .then() - .log().ifValidationFails() - .statusCode(200) - .contentType(JSON) - .body(ID, is(id)) - .body(CONTEXT, contains(jsonLdContextArray())) - .body("policy.permission[0].constraint[0].leftOperand", is("inForceDate")) - .body("policy.permission[0].constraint[0].operator", is("gteq")) - .body("policy.permission[0].constraint[0].rightOperand", is("contractAgreement+0s")) - .body("policy.prohibition[0].action", is("use")) - .body("policy.obligation[0].action", is("use")); - } - - @Test - void create_WithPrivateProperties(ManagementEndToEndTestContext context, PolicyDefinitionStore store) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .add("privateProperties", createObjectBuilder() - .add("newKey", "newValue") - .build()) - .build(); - - var id = context.baseRequest(participantTokenJwt) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/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); - - context.baseRequest(participantTokenJwt) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + id) - .then() - .statusCode(200) - .contentType(JSON) - .body(ID, is(id)) - .body(CONTEXT, contains(jsonLdContextArray())) - .log().all() - .body("policy.permission[0].constraint[0].operator", is("gteq")); - } - - @Test - void create_validationFails(ManagementEndToEndTestContext context) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .build(); - - context.baseRequest(participantTokenJwt) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions") - .then() - .log().ifValidationFails() - .contentType(JSON) - .statusCode(400); - } - - @Test - void create_tokenBearerWrong(ManagementEndToEndTestContext context, OauthServer authServer, ParticipantContextService service) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .add("privateProperties", createObjectBuilder() - .add("newKey", "newValue") - .build()) - .build(); - var id = "other-participant"; - - service.createParticipantContext(participantContext(id)) - .orElseThrow(f -> new AssertionError("ParticipantContext " + id + " not created.")); - - var token = authServer.createToken(id); - - context.baseRequest(token) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions") - .then() - .log().ifError() - .contentType(JSON) - .statusCode(403) - .body(containsString("User '%s' is not authorized to access this resource.".formatted(id))); - } - - @Test - void create_tokenLacksWriteScope(ManagementEndToEndTestContext context, OauthServer authServer) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .add("privateProperties", createObjectBuilder() - .add("newKey", "newValue") - .build()) - .build(); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:read")); - - context.baseRequest(token) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions") - .then() - .log().ifError() - .statusCode(403) - .body(matchesRegex("(?s).*Required scope.*missing.*")); - } - - @Test - void create_tokenBearerIsAdmin(ManagementEndToEndTestContext context, OauthServer authServer) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .add("privateProperties", createObjectBuilder() - .add("newKey", "newValue") - .build()) - .build(); - var token = authServer.createAdminToken(); - - context.baseRequest(token) - .contentType(ContentType.JSON) - .body(requestBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions") - .then() - .log().ifValidationFails() - .statusCode(200); - } - - @Test - void create_tokenBearerIsAdmin_participantNotFound(ManagementEndToEndTestContext context, OauthServer authServer) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .add("privateProperties", createObjectBuilder() - .add("newKey", "newValue") - .build()) - .build(); - var token = authServer.createAdminToken(); - - context.baseRequest(token) - .contentType(ContentType.JSON) - .body(requestBody.toString()) - .post("/v5alpha/participants/not-valid/policydefinitions") - .then() - .log().ifValidationFails() - .statusCode(404); - } - - @Test - void get(ManagementEndToEndTestContext context, PolicyDefinitionStore store) { - - var policy = PolicyDefinition.Builder.newInstance() - .participantContextId(PARTICIPANT_CONTEXT_ID) - .policy(Policy.Builder.newInstance().build()) - .build(); - var stored = store.create(policy) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + stored.getId()) - .then() - .log().ifError() - .statusCode(200) - .body(ID, is(stored.getId())) - .body(CONTEXT, contains(jsonLdContextArray())); - } - - @Test - void get_tokenBearerDoesNotOwnResource(ManagementEndToEndTestContext context, OauthServer authServer, - PolicyDefinitionStore store, ParticipantContextService srv) { - - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - - var policy = PolicyDefinition.Builder.newInstance() - .participantContextId(PARTICIPANT_CONTEXT_ID) - .policy(Policy.Builder.newInstance().build()) - .build(); - var stored = store.create(policy) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + stored.getId()) - .then() - .log().ifError() - .statusCode(403); - } - - @Test - void get_tokenLacksRequiredScope(ManagementEndToEndTestContext context, OauthServer authServer, - PolicyDefinitionStore store, ParticipantContextService srv) { - - var policy = PolicyDefinition.Builder.newInstance() - .participantContextId(PARTICIPANT_CONTEXT_ID) - .policy(Policy.Builder.newInstance().build()) - .build(); - var stored = store.create(policy) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:missing")); - - context.baseRequest(token) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + stored.getId()) - .then() - .log().ifError() - .statusCode(403); - - } - - @Test - void query_WithSimplePrivateProperties(ManagementEndToEndTestContext context) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .add("privateProperties", createObjectBuilder() - .add("newKey", createObjectBuilder().add(ID, "newValue")) - .build()) - .build(); - - var id = context.baseRequest(participantTokenJwt) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions") - .then() - .contentType(JSON) - .extract().jsonPath().getString(ID); - - var matchingQuery = context.query( - criterion("id", "=", id), - criterion("privateProperties.'https://w3id.org/edc/v0.0.1/ns/newKey'.@id", "=", "newValue") - ); - - context.baseRequest(participantTokenJwt) - .body(matchingQuery.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(1)); - - - var nonMatchingQuery = context.query( - criterion("id", "=", id), - criterion("privateProperties.'https://w3id.org/edc/v0.0.1/ns/newKey'.@id", "=", "somethingElse") - ); - - context.baseRequest(participantTokenJwt) - .body(nonMatchingQuery.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(0)); - } - - @Test - void query_tokenBearerIsAdmin_shouldReturnAll(ManagementEndToEndTestContext context, OauthServer authServer, PolicyDefinitionStore store) { - IntStream.range(0, 10) - .forEach(i -> { - store.create(PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()) - .participantContextId(PARTICIPANT_CONTEXT_ID).build()) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - }); - - var token = authServer.createAdminToken(); - - var result = context.baseRequest(token) - .contentType(ContentType.JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/request") - .then() - .log().ifError() - .statusCode(200) - .extract().body().as(Map[].class); - - assertThat(result).isNotNull().hasSize(10); - - } - - @Test - void query_shouldLimitToResourceOwner(ManagementEndToEndTestContext context, OauthServer authServer, PolicyDefinitionStore store) { - var otherParticipantId = UUID.randomUUID().toString(); - - var ownPolicy = store.create(PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()) - .participantContextId(PARTICIPANT_CONTEXT_ID).build()) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - store.create(PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()) - .participantContextId(otherParticipantId).build()) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - var token = authServer.createAdminToken(); - - var result = context.baseRequest(token) - .contentType(ContentType.JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/request") - .then() - .log().ifError() - .statusCode(200) - .extract().body().as(Map[].class); - - assertThat(result).isNotNull().hasSize(1) - .allMatch(m -> m.get("@id").equals(ownPolicy.getId())); - } - - @Test - void query_tokenBearerNotEqualResourceOwner(ManagementEndToEndTestContext context, OauthServer authServer, - PolicyDefinitionStore store, ParticipantContextService srv) { - var participantId = UUID.randomUUID().toString(); - createParticipant(srv, participantId); - - var token = authServer.createToken(participantId); - - store.create(PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()) - .participantContextId(PARTICIPANT_CONTEXT_ID).build()) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - var query = context.query(criterion("foo", "=", "bar")).toString(); - - context.baseRequest(token) - .contentType(ContentType.JSON) - .body(query) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/assets/request") - .then() - .log().ifError() - .statusCode(403) - .body(containsString("User '%s' is not authorized to access this resource.".formatted(participantId))); - - } - - @Test - void update(ManagementEndToEndTestContext context, PolicyDefinitionStore store) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .build(); - - var id = context.baseRequest(participantTokenJwt) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions") - .then() - .statusCode(200) - .extract().jsonPath().getString(ID); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + id) - .then() - .statusCode(200); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(createObjectBuilder(requestBody) - .add(ID, id) - .add("privateProperties", createObjectBuilder().add("privateProperty", "value")) - .build() - .toString()) - .put("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + id) - .then() - .statusCode(204); - - var policyDefinition = store.findById(id); - assertThat(policyDefinition) - .extracting(PolicyDefinition::getPrivateProperties) - .asInstanceOf(MAP) - .isNotEmpty(); - - assertThat(policyDefinition.getParticipantContextId()).isEqualTo(PARTICIPANT_CONTEXT_ID); - - } - - @Test - void update_whenPolicyValidationFails(ManagementEndToEndTestContext context) { - var validRequestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", emptyOdrlPolicy()) - .build(); - - var id = context.baseRequest(participantTokenJwt) - .body(validRequestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions") - .then() - .contentType(JSON) - .extract().jsonPath().getString(ID); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + id) - .then() - .statusCode(200); - - var inValidRequestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleInvalidOdrlPolicy()) - .build(); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(inValidRequestBody.toString()) - .put("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + id) - .then() - .statusCode(400) - .contentType(JSON) - .body("size()", is(2)) - .body("[0].message", startsWith("leftOperand 'https://w3id.org/edc/v0.0.1/ns/left' is not bound to any scopes")) - .body("[1].message", startsWith("leftOperand 'https://w3id.org/edc/v0.0.1/ns/left' is not bound to any functions")); - } - - @Test - void update_whenSchemaValidationFails(ManagementEndToEndTestContext context) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .build(); - - context.baseRequest(participantTokenJwt) - .body(requestBody.toString()) - .contentType(JSON) - .put("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/id") - .then() - .log().ifValidationFails() - .contentType(JSON) - .statusCode(400); - } - - @Test - void update_tokenBearerDoesNotOwnResource(ManagementEndToEndTestContext context, OauthServer authServer, - PolicyDefinitionStore store, ParticipantContextService srv) { - - var policy = PolicyDefinition.Builder.newInstance() - .participantContextId(PARTICIPANT_CONTEXT_ID) - .policy(Policy.Builder.newInstance().build()) - .build(); - var stored = store.create(policy) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .build(); - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .body(createObjectBuilder(requestBody) - .add(ID, stored.getId()) - .add("privateProperties", createObjectBuilder().add("privateProperty", "value")) - .build() - .toString()) - .put("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + stored.getId()) - .then() - .statusCode(403); - - } - - @Test - void update_tokenLacksRequiredScope(ManagementEndToEndTestContext context, OauthServer authServer, - PolicyDefinitionStore store, ParticipantContextService srv) { - - var policy = PolicyDefinition.Builder.newInstance() - .participantContextId(PARTICIPANT_CONTEXT_ID) - .policy(Policy.Builder.newInstance().build()) - .build(); - var stored = store.create(policy) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .build(); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:read")); - - context.baseRequest(token) - .contentType(JSON) - .body(createObjectBuilder(requestBody) - .add(ID, stored.getId()) - .add("privateProperties", createObjectBuilder().add("privateProperty", "value")) - .build() - .toString()) - .put("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + stored.getId()) - .then() - .statusCode(403); - - } - - - @Test - void delete(ManagementEndToEndTestContext context) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .build(); - - var id = context.baseRequest(participantTokenJwt) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions") - .then() - .log().ifError() - .statusCode(200) - .extract().jsonPath().getString(ID); - - context.baseRequest(participantTokenJwt) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + id) - .then() - .statusCode(204); - - context.baseRequest(participantTokenJwt) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + id) - .then() - .statusCode(404); - } - - @Test - void delete_withProperties(ManagementEndToEndTestContext context) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .add("privateProperties", createObjectBuilder() - .add("newKey", "newValue") - .build()) - .build(); - - var id = context.baseRequest(participantTokenJwt) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions") - .then() - .statusCode(200) - .extract().jsonPath().getString(ID); - - context.baseRequest(participantTokenJwt) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + id) - .then() - .statusCode(204); - - context.baseRequest(participantTokenJwt) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + id) - .then() - .statusCode(404); - } - - @Test - void delete_tokenBearerDoesNotOwnResource(ManagementEndToEndTestContext context, OauthServer authServer, - PolicyDefinitionStore store, ParticipantContextService srv) { - - var policy = PolicyDefinition.Builder.newInstance() - .participantContextId(PARTICIPANT_CONTEXT_ID) - .policy(Policy.Builder.newInstance().build()) - .build(); - var stored = store.create(policy) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + stored.getId()) - .then() - .statusCode(403); - } - - @Test - void delete_tokenLacksRequiredScope(ManagementEndToEndTestContext context, OauthServer authServer, - PolicyDefinitionStore store, ParticipantContextService srv) { - - var policy = PolicyDefinition.Builder.newInstance() - .participantContextId(PARTICIPANT_CONTEXT_ID) - .policy(Policy.Builder.newInstance().build()) - .build(); - var stored = store.create(policy) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:read")); - - context.baseRequest(token) - .delete("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + stored.getId()) - .then() - .statusCode(403); - - } - - @Test - void validate(ManagementEndToEndTestContext context) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleInvalidOdrlPolicy()) - .build(); - - context.baseRequest(participantTokenJwt) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions") - .then() - .log().ifError() - .statusCode(400) - .body("size()", is(2)) - .body("[0].message", startsWith("leftOperand 'https://w3id.org/edc/v0.0.1/ns/left' is not bound to any scopes")) - .body("[1].message", startsWith("leftOperand 'https://w3id.org/edc/v0.0.1/ns/left' is not bound to any functions")); - } - - @Test - void evaluationPlan(ManagementEndToEndTestContext context) { - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyDefinition") - .add("policy", sampleOdrlPolicy()) - .build(); - - var id = context.baseRequest(participantTokenJwt) - .body(requestBody.toString()) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions") - .then() - .statusCode(200) - .extract().jsonPath().getString(ID); - - var planBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyEvaluationPlanRequest") - .add("policyScope", "catalog") - .build(); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(planBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + id + "/evaluationplan") - .then() - .statusCode(200) - .body("preValidators.size()", is(0)) - .body("permissionSteps[0].isFiltered", is(false)) - .body("permissionSteps[0].filteringReasons.size()", is(0)) - .body("permissionSteps[0].constraintSteps[0].'@type'", is("AtomicConstraintStep")) - .body("permissionSteps[0].constraintSteps[0].isFiltered", is(true)) - .body("permissionSteps[0].constraintSteps[0].filteringReasons.size()", is(2)) - .body("permissionSteps[0].constraintSteps[0].functionName", nullValue()) - .body("permissionSteps[0].constraintSteps[0].functionParams.size()", is(3)) - .body("prohibitionSteps[0].isFiltered", is(false)) - .body("prohibitionSteps[0].filteringReasons", notNullValue()) - .body("prohibitionSteps[0].constraintSteps.size()", is(0)) - .body("obligationSteps[0].isFiltered", is(false)) - .body("obligationSteps[0].filteringReasons.size()", is(0)) - .body("obligationSteps[0].constraintSteps.size()", is(0)) - .body("postValidators.size()", is(0)); - - } - - @Test - void evaluationPlan_tokenBearerDoesNotOwnResource(ManagementEndToEndTestContext context, OauthServer authServer, - PolicyDefinitionStore store, ParticipantContextService srv) { - - var planBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyEvaluationPlanRequest") - .add("policyScope", "catalog") - .build(); - - var policy = PolicyDefinition.Builder.newInstance() - .participantContextId(PARTICIPANT_CONTEXT_ID) - .policy(Policy.Builder.newInstance().build()) - .build(); - var stored = store.create(policy) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - var otherParticipantId = UUID.randomUUID().toString(); - createParticipant(srv, otherParticipantId); - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .body(planBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + stored.getId() + "/evaluationplan") - .then() - .log().ifError() - .statusCode(403); - } - - @Test - void evaluationPlan_tokenLacksRequiredScope(ManagementEndToEndTestContext context, OauthServer authServer, - PolicyDefinitionStore store) { - var planBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "PolicyEvaluationPlanRequest") - .add("policyScope", "catalog") - .build(); - - var policy = PolicyDefinition.Builder.newInstance() - .participantContextId(PARTICIPANT_CONTEXT_ID) - .policy(Policy.Builder.newInstance().build()) - .build(); - var stored = store.create(policy) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:missing")); - - context.baseRequest(token) - .contentType(JSON) - .body(planBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/policydefinitions/" + stored.getId() + "/evaluationplan") - .then() - .statusCode(403); - - } - - private JsonObject emptyOdrlPolicy() { - return createObjectBuilder() - .add(TYPE, "Set") - .build(); - } - - private JsonObject sampleInvalidOdrlPolicy() { - return createObjectBuilder() - .add(TYPE, "Set") - .add("permission", createArrayBuilder() - .add(createObjectBuilder() - .add("action", "use") - .add("constraint", createArrayBuilder().add(createObjectBuilder() - .add("leftOperand", "https://w3id.org/edc/v0.0.1/ns/left") - .add("operator", "eq") - .add("rightOperand", "value")) - .build())) - .build()) - .build(); - } - - private JsonObject sampleOdrlPolicy() { - return createObjectBuilder() - .add(TYPE, "Set") - .add("permission", createArrayBuilder() - .add(createObjectBuilder() - .add("action", "use") - .add("constraint", createArrayBuilder().add(createObjectBuilder() - .add("leftOperand", "inForceDate") - .add("operator", "gteq") - .add("rightOperand", "contractAgreement+0s")) - .build())) - .build()) - .add("prohibition", createArrayBuilder() - .add(createObjectBuilder() - .add("action", "use") - )) - .add("obligation", createArrayBuilder() - .add(createObjectBuilder() - .add("action", "use") - ) - ) - .build(); - } - - } - - @Nested - @EndToEndTest - class InMemory extends Tests { - - @Order(0) - @RegisterExtension - static final OauthServerEndToEndExtension AUTH_SERVER_EXTENSION = OauthServerEndToEndExtension.Builder.newInstance().build(); - - @Order(1) - @RegisterExtension - static RuntimeExtension runtime = ComponentRuntimeExtension.Builder.newInstance() - .name(Runtimes.ControlPlane.NAME) - .modules(Runtimes.ControlPlane.MODULES) - .endpoints(Runtimes.ControlPlane.ENDPOINTS.build()) - .configurationProvider(Runtimes.ControlPlane::config) - .paramProvider(ManagementEndToEndTestContext.class, ManagementEndToEndTestContext::forContext) - .configurationProvider(AUTH_SERVER_EXTENSION::getConfig) - .build(); - } - - @Nested - @PostgresqlIntegrationTest - class Postgres extends Tests { - - @Order(0) - @RegisterExtension - static final OauthServerEndToEndExtension AUTH_SERVER_EXTENSION = OauthServerEndToEndExtension.Builder.newInstance().build(); - - @RegisterExtension - @Order(0) - static final PostgresqlEndToEndExtension POSTGRES_EXTENSION = new PostgresqlEndToEndExtension(createPgContainer()); - - @Order(0) - @RegisterExtension - static final NatsEndToEndExtension NATS_EXTENSION = new NatsEndToEndExtension(); - - @Order(1) - @RegisterExtension - static final BeforeAllCallback SETUP = context -> { - POSTGRES_EXTENSION.createDatabase(Runtimes.ControlPlane.NAME.toLowerCase()); - }; - - @Order(2) - @RegisterExtension - static RuntimeExtension runtime = ComponentRuntimeExtension.Builder.newInstance() - .name(Runtimes.ControlPlane.NAME) - .modules(Runtimes.ControlPlane.MODULES) - .modules(Runtimes.ControlPlane.PG_MODULES) - .endpoints(Runtimes.ControlPlane.ENDPOINTS.build()) - .configurationProvider(Runtimes.ControlPlane::config) - .configurationProvider(() -> POSTGRES_EXTENSION.configFor(Runtimes.ControlPlane.NAME.toLowerCase())) - .configurationProvider(NATS_EXTENSION::configFor) - .configurationProvider(AUTH_SERVER_EXTENSION::getConfig) - .paramProvider(ManagementEndToEndTestContext.class, ManagementEndToEndTestContext::forContext) - .build(); - - } - -} diff --git a/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/TransferProcessApiV5EndToEndTest.java b/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/TransferProcessApiV5EndToEndTest.java deleted file mode 100644 index fe20210e..00000000 --- a/system-tests/management-api/management-api-tests/src/test/java/org/eclipse/edc/test/e2e/managementapi/v5/TransferProcessApiV5EndToEndTest.java +++ /dev/null @@ -1,752 +0,0 @@ -/* - * Copyright (c) 2025 Metaform Systems, Inc. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.test.e2e.managementapi.v5; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.nimbusds.jose.JOSEException; -import jakarta.json.Json; -import jakarta.json.JsonArrayBuilder; -import jakarta.json.JsonObject; -import jakarta.json.JsonValue; -import org.eclipse.edc.api.authentication.OauthServer; -import org.eclipse.edc.api.authentication.OauthServerEndToEndExtension; -import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.store.ContractNegotiationStore; -import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.controlplane.transfer.spi.store.TransferProcessStore; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; -import org.eclipse.edc.junit.annotations.EndToEndTest; -import org.eclipse.edc.junit.annotations.PostgresqlIntegrationTest; -import org.eclipse.edc.junit.extensions.ComponentRuntimeExtension; -import org.eclipse.edc.junit.extensions.RuntimeExtension; -import org.eclipse.edc.participantcontext.spi.service.ParticipantContextService; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.types.domain.DataAddress; -import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; -import org.eclipse.edc.sql.testfixtures.PostgresqlEndToEndExtension; -import org.eclipse.edc.test.e2e.managementapi.ManagementEndToEndTestContext; -import org.eclipse.edc.test.e2e.managementapi.Runtimes; -import org.eclipse.edc.virtual.nats.testfixtures.NatsEndToEndExtension; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneId; -import java.time.temporal.ChronoUnit; -import java.util.List; -import java.util.Map; -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.Collections.emptySet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates.COMPLETED; -import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates.REQUESTED; -import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates.STARTED; -import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates.SUSPENDED; -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.constants.CoreConstants.EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2; -import static org.eclipse.edc.spi.query.Criterion.criterion; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.createParticipant; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.jsonLdContext; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.jsonLdContextArray; -import static org.eclipse.edc.test.e2e.managementapi.v5.TestFunction.participantContext; -import static org.eclipse.edc.virtual.test.system.fixtures.DockerImages.createPgContainer; -import static org.hamcrest.CoreMatchers.anyOf; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; - -public class TransferProcessApiV5EndToEndTest { - - @SuppressWarnings("JUnitMalformedDeclaration") - abstract static class Tests { - - private static final String PARTICIPANT_CONTEXT_ID = "test-participant"; - - private String participantTokenJwt; - - @BeforeEach - void setup(OauthServer authServer, ParticipantContextService participantContextService) throws JOSEException { - createParticipant(participantContextService, PARTICIPANT_CONTEXT_ID); - - participantTokenJwt = authServer.createToken(PARTICIPANT_CONTEXT_ID); - } - - @AfterEach - void teardown(ParticipantContextService participantContextService, TransferProcessStore store) { - participantContextService.deleteParticipantContext(PARTICIPANT_CONTEXT_ID) - .orElseThrow(f -> new AssertionError(f.getFailureDetail())); - - store.findAll(QuerySpec.max()).forEach(tp -> store.delete(tp.getId())); - - } - - @Test - void initiate(ManagementEndToEndTestContext context, TransferProcessStore transferProcessStore, ContractNegotiationStore contractNegotiationStore) { - var assetId = UUID.randomUUID().toString(); - var contractId = UUID.randomUUID().toString(); - var contractNegotiation = ContractNegotiation.Builder.newInstance() - .id(UUID.randomUUID().toString()) - .counterPartyId("counterPartyId") - .counterPartyAddress("http://counterparty") - .protocol("dataspace-protocol-http") - .participantContextId("participantContextId") - .contractAgreement(createContractAgreement(contractId, assetId).build()) - .build(); - contractNegotiationStore.save(contractNegotiation); - - var requestBody = createTransferRequestJson(contractId, assetId); - - var id = context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(requestBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses") - .then() - .log().ifError() - .statusCode(200) - .extract().jsonPath().getString(ID); - - assertThat(transferProcessStore.findById(id)).isNotNull(); - } - - private JsonObject createTransferRequestJson(String contractId, String assetId) { - return createObjectBuilder() - .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .add(TYPE, "TransferRequest") - .add("dataDestination", createObjectBuilder() - .add(TYPE, "DataAddress") - .add("type", "HttpData") - .add("properties", createObjectBuilder() - .add("baseUrl", "http://any") - .build()) - .build() - ) - .add("transferType", "HttpData-PUSH") - .add("callbackAddresses", createCallbackAddress()) - .add("protocol", "dataspace-protocol-http") - .add("counterPartyAddress", "http://connector-address") - .add("contractId", contractId) - .add("assetId", assetId) - .build(); - } - - @Test - void initiate_whenValidationFails(ManagementEndToEndTestContext context) { - - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "TransferRequest") - .build(); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(requestBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses") - .then() - .log().ifError() - .statusCode(400); - } - - @Test - void initiate_tokenBearerWrong(ManagementEndToEndTestContext context, OauthServer authServer, ParticipantContextService service) { - var assetId = UUID.randomUUID().toString(); - var contractId = UUID.randomUUID().toString(); - var requestBody = createTransferRequestJson(contractId, assetId); - - var otherParticipantId = UUID.randomUUID().toString(); - - service.createParticipantContext(participantContext(otherParticipantId)) - .orElseThrow(f -> new AssertionError("ParticipantContext " + otherParticipantId + " not created.")); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .body(requestBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses") - .then() - .log().ifError() - .statusCode(403); - } - - @Test - void initiate_tokenLacksWriteScope(ManagementEndToEndTestContext context, OauthServer authServer) { - var assetId = UUID.randomUUID().toString(); - var contractId = UUID.randomUUID().toString(); - var requestBody = createTransferRequestJson(contractId, assetId); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:read")); - - context.baseRequest(token) - .contentType(JSON) - .body(requestBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses") - .then() - .log().ifError() - .statusCode(403); - } - - @Test - void getById(ManagementEndToEndTestContext context, TransferProcessStore store) { - store.save(createTransferProcess("tp1")); - store.save(createTransferProcess("tp2")); - - context.baseRequest(participantTokenJwt) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/tp2") - .then() - .statusCode(200) - .body(TYPE, is("TransferProcess")) - .body(ID, is("tp2")) - .body(CONTEXT, contains(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)); - } - - @Test - void getById_tokenBearerWrong(ManagementEndToEndTestContext context, OauthServer authServer, - TransferProcessStore store, ParticipantContextService service) { - store.save(createTransferProcess("tp1")); - - var otherParticipantId = UUID.randomUUID().toString(); - - service.createParticipantContext(participantContext(otherParticipantId)) - .orElseThrow(f -> new AssertionError("ParticipantContext " + otherParticipantId + " not created.")); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/tp1") - .then() - .statusCode(403); - } - - @Test - void getById_tokenLacksReadScope(ManagementEndToEndTestContext context, OauthServer authServer, TransferProcessStore store) { - store.save(createTransferProcess("tp1")); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:wrong")); - - context.baseRequest(token) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/tp1") - .then() - .statusCode(403); - } - - @Test - void getState(ManagementEndToEndTestContext context, TransferProcessStore store) { - store.save(createTransferProcessBuilder("tp2").state(COMPLETED.code()).build()); - - context.baseRequest(participantTokenJwt) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/tp2/state") - .then() - .statusCode(200) - .contentType(JSON) - .body(TYPE, is("TransferState")) - .body(CONTEXT, contains(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .body("state", is("COMPLETED")); - } - - @Test - void getState_tokenBearerWrong(ManagementEndToEndTestContext context, OauthServer authServer, - TransferProcessStore store, ParticipantContextService service) { - store.save(createTransferProcess("tp1")); - - var otherParticipantId = UUID.randomUUID().toString(); - - service.createParticipantContext(participantContext(otherParticipantId)) - .orElseThrow(f -> new AssertionError("ParticipantContext " + otherParticipantId + " not created.")); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/tp1/state") - .then() - .statusCode(403); - } - - @Test - void getState_tokenLacksReadScope(ManagementEndToEndTestContext context, OauthServer authServer, - TransferProcessStore store) { - store.save(createTransferProcess("tp1")); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:wrong")); - - context.baseRequest(token) - .get("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/tp1/state") - .then() - .statusCode(403); - } - - @Test - void terminate(ManagementEndToEndTestContext context, TransferProcessStore store) { - var id = UUID.randomUUID().toString(); - store.save(createTransferProcessBuilder(id).state(REQUESTED.code()).build()); - var requestBody = createObjectBuilder() - .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .add(TYPE, "TerminateTransfer") - .add("reason", "any") - .build(); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(requestBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/" + id + "/terminate") - .then() - .log().ifError() - .statusCode(204); - } - - @Test - void terminate_tokenBearerWrong(ManagementEndToEndTestContext context, OauthServer authServer, - TransferProcessStore store, ParticipantContextService service) { - var id = UUID.randomUUID().toString(); - store.save(createTransferProcessBuilder(id).state(REQUESTED.code()).build()); - var requestBody = createObjectBuilder() - .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .add(TYPE, "TerminateTransfer") - .add("reason", "any") - .build(); - - var otherParticipantId = UUID.randomUUID().toString(); - - service.createParticipantContext(participantContext(otherParticipantId)) - .orElseThrow(f -> new AssertionError("ParticipantContext " + otherParticipantId + " not created.")); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .body(requestBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/" + id + "/terminate") - .then() - .log().ifError() - .statusCode(403); - } - - @Test - void terminate_tokenLacksWriteScope(ManagementEndToEndTestContext context, OauthServer authServer, TransferProcessStore store) { - var id = UUID.randomUUID().toString(); - store.save(createTransferProcessBuilder(id).state(REQUESTED.code()).build()); - var requestBody = createObjectBuilder() - .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .add(TYPE, "TerminateTransfer") - .add("reason", "any") - .build(); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:read")); - - context.baseRequest(token) - .contentType(JSON) - .body(requestBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/" + id + "/terminate") - .then() - .log().ifError() - .statusCode(403); - } - - @Test - void suspend(ManagementEndToEndTestContext context, TransferProcessStore store) { - var id = UUID.randomUUID().toString(); - store.save(createTransferProcessBuilder(id).state(STARTED.code()).build()); - var requestBody = createObjectBuilder() - .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .add(TYPE, "SuspendTransfer") - .add("reason", "any") - .build(); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(requestBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/" + id + "/suspend") - .then() - .log().ifError() - .statusCode(204); - } - - @Test - void suspend_tokenBearerWrong(ManagementEndToEndTestContext context, OauthServer authServer, - TransferProcessStore store, ParticipantContextService service) { - var id = UUID.randomUUID().toString(); - store.save(createTransferProcessBuilder(id).state(STARTED.code()).build()); - var requestBody = createObjectBuilder() - .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .add(TYPE, "SuspendTransfer") - .add("reason", "any") - .build(); - - var otherParticipantId = UUID.randomUUID().toString(); - - service.createParticipantContext(participantContext(otherParticipantId)) - .orElseThrow(f -> new AssertionError("ParticipantContext " + otherParticipantId + " not created.")); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .body(requestBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/" + id + "/suspend") - .then() - .log().ifError() - .statusCode(403); - } - - @Test - void suspend_tokenLacksWriteScope(ManagementEndToEndTestContext context, OauthServer authServer, TransferProcessStore store) { - var id = UUID.randomUUID().toString(); - store.save(createTransferProcessBuilder(id).state(STARTED.code()).build()); - var requestBody = createObjectBuilder() - .add(CONTEXT, jsonLdContext()) - .add(TYPE, "SuspendTransfer") - .add("reason", "any") - .build(); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:read")); - - context.baseRequest(token) - .contentType(JSON) - .body(requestBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/" + id + "/suspend") - .then() - .log().ifError() - .statusCode(403); - } - - @Test - void resume(ManagementEndToEndTestContext context, TransferProcessStore store) { - var id = UUID.randomUUID().toString(); - store.save(createTransferProcessBuilder(id).state(SUSPENDED.code()).build()); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/" + id + "/resume") - .then() - .log().ifError() - .statusCode(204); - } - - @Test - void resume_tokenBearerWrong(ManagementEndToEndTestContext context, OauthServer authServer, - TransferProcessStore store, ParticipantContextService service) { - var id = UUID.randomUUID().toString(); - store.save(createTransferProcessBuilder(id).state(SUSPENDED.code()).build()); - - var otherParticipantId = UUID.randomUUID().toString(); - - service.createParticipantContext(participantContext(otherParticipantId)) - .orElseThrow(f -> new AssertionError("ParticipantContext " + otherParticipantId + " not created.")); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/" + id + "/resume") - .then() - .log().ifError() - .statusCode(403); - } - - @Test - void resume_tokenLacksWriteScope(ManagementEndToEndTestContext context, OauthServer authServer, TransferProcessStore store) { - var id = UUID.randomUUID().toString(); - store.save(createTransferProcessBuilder(id).state(SUSPENDED.code()).build()); - - var token = authServer.createToken(PARTICIPANT_CONTEXT_ID, Map.of("scope", "management-api:read")); - - context.baseRequest(token) - .contentType(JSON) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/" + id + "/resume") - .then() - .log().ifError() - .statusCode(403); - } - - @Test - void query(ManagementEndToEndTestContext context, TransferProcessStore store) { - var id1 = UUID.randomUUID().toString(); - var id2 = UUID.randomUUID().toString(); - store.save(createTransferProcess(id1)); - store.save(createTransferProcess(id2)); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(context.query(criterion("id", "in", List.of(id1, id2))).toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(2)) - .body("[0].@id", anyOf(is(id1), is(id2))) - .body("[0].@context", contains(jsonLdContextArray())) - .body("[1].@id", anyOf(is(id1), is(id2))) - .body("[1].@context", contains(jsonLdContextArray())); - - } - - @SuppressWarnings("unchecked") - @ParameterizedTest - @ValueSource(strings = {"600", "STARTED"}) - void query_byState(String state, ManagementEndToEndTestContext context, TransferProcessStore store) { - var actualState = STARTED; - var tp = createTransferProcessBuilder("test-tp") - .state(actualState.code()) - .build(); - store.save(tp); - - JsonValue stateValue; - try { - stateValue = Json.createValue(Integer.valueOf(state)); - } catch (NumberFormatException e) { - stateValue = Json.createValue(state); - } - - var requestBody = createObjectBuilder() - .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2)) - .add(TYPE, "QuerySpec") - .add("filterExpression", createArrayBuilder().add(createObjectBuilder() - .add(TYPE, "Criterion") - .add("operandLeft", "state") - .add("operator", "=") - .add("operandRight", stateValue)) - ) - .add("limit", 100) - .add("offset", 0) - .build(); - - List> result = context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(requestBody.toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/request") - .then() - .statusCode(200) - .extract().body().as(List.class); - - assertThat(result) - .isNotEmpty() - .anySatisfy(it -> assertThat(it.get("state")).isEqualTo(actualState.name())); - } - - @SuppressWarnings("unchecked") - @Test - void query_sortByStateTimestamp(ManagementEndToEndTestContext context, TransferProcessStore store) throws JsonProcessingException { - var tp1 = createTransferProcessBuilder("test-tp1").build(); - var tp2 = createTransferProcessBuilder("test-tp2") - .clock(Clock.fixed(Instant.now().plus(1, ChronoUnit.HOURS), ZoneId.systemDefault())) - .build(); - store.save(tp1); - store.save(tp2); - - - var content = """ - { - "@context": ["%s"], - "@type": "QuerySpec", - "sortField": "stateTimestamp", - "sortOrder": "ASC", - "limit": 100, - "offset": 0 - } - """.formatted(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2); - - List> result = context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(content) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/request") - .then() - .log().ifError() - .statusCode(200) - .extract().body().as(List.class); - - assertThat(result).isNotEmpty().hasSizeGreaterThanOrEqualTo(2); - assertThat(result).isSortedAccordingTo((o1, o2) -> { - var l1 = (Long) o1.get("stateTimestamp"); - var l2 = (Long) o2.get("stateTimestamp"); - return Long.compare(l1, l2); - }); - } - - @Test - void query_tokenBearerIsAdmin_shouldReturnAll(ManagementEndToEndTestContext context, OauthServer authServer, TransferProcessStore store) { - var id1 = UUID.randomUUID().toString(); - var id2 = UUID.randomUUID().toString(); - store.save(createTransferProcess(id1)); - store.save(createTransferProcess(id2)); - - var token = authServer.createAdminToken(); - - - context.baseRequest(token) - .contentType(JSON) - .body(context.query(criterion("id", "in", List.of(id1, id2))).toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/request") - .then() - .log().ifError() - .statusCode(200); - - } - - @Test - void query_shouldLimitToResourceOwner(ManagementEndToEndTestContext context, TransferProcessStore store) { - var otherParticipantId = UUID.randomUUID().toString(); - - var id1 = UUID.randomUUID().toString(); - var id2 = UUID.randomUUID().toString(); - store.save(createTransferProcess(id1)); - store.save(createTransferProcessBuilder(id2).participantContextId(otherParticipantId).build()); - - context.baseRequest(participantTokenJwt) - .contentType(JSON) - .body(context.query(criterion("id", "in", List.of(id1, id2))).toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/request") - .then() - .log().ifError() - .statusCode(200) - .body("size()", is(1)) - .body("[0].@id", is(id1)); - - } - - @Test - void query_tokenBearerNotEqualResourceOwner(ManagementEndToEndTestContext context, OauthServer authServer, - TransferProcessStore store, ParticipantContextService srv) { - var otherParticipantId = UUID.randomUUID().toString(); - srv.createParticipantContext(participantContext(otherParticipantId)) - .orElseThrow(f -> new AssertionError("ParticipantContext " + otherParticipantId + " not created.")); - - var id1 = UUID.randomUUID().toString(); - store.save(createTransferProcess(id1)); - - var token = authServer.createToken(otherParticipantId); - - context.baseRequest(token) - .contentType(JSON) - .body(context.query(criterion("id", "in", List.of(id1))).toString()) - .post("/v5alpha/participants/" + PARTICIPANT_CONTEXT_ID + "/transferprocesses/request") - .then() - .log().ifError() - .statusCode(403) - .body(containsString("User '%s' is not authorized to access this resource.".formatted(otherParticipantId))); - - } - - 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())) - .correlationId(UUID.randomUUID().toString()) - .dataDestination(DataAddress.Builder.newInstance() - .type("type") - .build()) - .protocol("dataspace-protocol-http") - .assetId("asset-id") - .contractId("contractId") - .participantContextId(PARTICIPANT_CONTEXT_ID) - .counterPartyAddress("http://connector/address"); - } - - private JsonArrayBuilder createCallbackAddress() { - var builder = Json.createArrayBuilder(); - return builder.add(createObjectBuilder() - .add(TYPE, "CallbackAddress") - .add("transactional", false) - .add("uri", "http://test.local/") - .add("events", Json.createArrayBuilder().build())); - } - - private ContractAgreement.Builder createContractAgreement(String contractId, String assetId) { - return ContractAgreement.Builder.newInstance() - .id(contractId) - .providerId("providerId") - .consumerId("consumerId") - .policy(Policy.Builder.newInstance().target(assetId).build()) - .participantContextId("participantContextId") - .assetId(assetId); - } - } - - @Nested - @EndToEndTest - class InMemory extends Tests { - - @Order(0) - @RegisterExtension - static final OauthServerEndToEndExtension AUTH_SERVER_EXTENSION = OauthServerEndToEndExtension.Builder.newInstance().build(); - - @Order(1) - @RegisterExtension - static RuntimeExtension runtime = ComponentRuntimeExtension.Builder.newInstance() - .name(Runtimes.ControlPlane.NAME) - .modules(Runtimes.ControlPlane.MODULES) - .endpoints(Runtimes.ControlPlane.ENDPOINTS.build()) - .configurationProvider(Runtimes.ControlPlane::config) - .paramProvider(ManagementEndToEndTestContext.class, ManagementEndToEndTestContext::forContext) - .configurationProvider(AUTH_SERVER_EXTENSION::getConfig) - .build(); - } - - @Nested - @PostgresqlIntegrationTest - class Postgres extends Tests { - - @Order(0) - @RegisterExtension - static final OauthServerEndToEndExtension AUTH_SERVER_EXTENSION = OauthServerEndToEndExtension.Builder.newInstance().build(); - - @RegisterExtension - @Order(0) - static final PostgresqlEndToEndExtension POSTGRES_EXTENSION = new PostgresqlEndToEndExtension(createPgContainer()); - - @Order(0) - @RegisterExtension - static final NatsEndToEndExtension NATS_EXTENSION = new NatsEndToEndExtension(); - - @Order(1) - @RegisterExtension - static final BeforeAllCallback SETUP = context -> { - POSTGRES_EXTENSION.createDatabase(Runtimes.ControlPlane.NAME.toLowerCase()); - }; - - @Order(2) - @RegisterExtension - static RuntimeExtension runtime = ComponentRuntimeExtension.Builder.newInstance() - .name(Runtimes.ControlPlane.NAME) - .modules(Runtimes.ControlPlane.MODULES) - .modules(Runtimes.ControlPlane.PG_MODULES) - .endpoints(Runtimes.ControlPlane.ENDPOINTS.build()) - .configurationProvider(Runtimes.ControlPlane::config) - .configurationProvider(() -> POSTGRES_EXTENSION.configFor(Runtimes.ControlPlane.NAME.toLowerCase())) - .configurationProvider(NATS_EXTENSION::configFor) - .configurationProvider(AUTH_SERVER_EXTENSION::getConfig) - .paramProvider(ManagementEndToEndTestContext.class, ManagementEndToEndTestContext::forContext) - .build(); - - } - -} diff --git a/system-tests/runtime-tests/src/test/java/org/eclipse/edc/virtual/transfer/DcpTransferPullEndToEndTest.java b/system-tests/runtime-tests/src/test/java/org/eclipse/edc/virtual/transfer/DcpTransferPullEndToEndTest.java index 5177c396..9b8dd01b 100644 --- a/system-tests/runtime-tests/src/test/java/org/eclipse/edc/virtual/transfer/DcpTransferPullEndToEndTest.java +++ b/system-tests/runtime-tests/src/test/java/org/eclipse/edc/virtual/transfer/DcpTransferPullEndToEndTest.java @@ -253,10 +253,10 @@ private static Config runtimeConfiguration() { { put("edc.iam.dcp.scopes.membership.id", "membership-scope"); put("edc.iam.dcp.scopes.membership.type", "DEFAULT"); - put("edc.iam.dcp.scopes.membership.value", "org.eclipse.edc.vc.type:MembershipCredential:read"); + put("edc.iam.dcp.scopes.membership.value", "org.eclipse.dspace.dcp.vc.type:MembershipCredential:read"); put("edc.iam.dcp.scopes.data-access.id", "data-access-scope"); put("edc.iam.dcp.scopes.data-access.type", "POLICY"); - put("edc.iam.dcp.scopes.data-access.value", "org.eclipse.edc.vc.type:DataAccessCredential:read"); + put("edc.iam.dcp.scopes.data-access.value", "org.eclipse.dspace.dcp.vc.type:DataAccessCredential:read"); put("edc.iam.dcp.scopes.data-access.prefix-mapping", "https://w3id.org/example/credentials/DataAccessCredential"); } });