diff --git a/docker-compose-dev.yaml b/docker-compose-dev.yaml index 01d93ec40..8dfd7e1af 100644 --- a/docker-compose-dev.yaml +++ b/docker-compose-dev.yaml @@ -42,6 +42,7 @@ services: EDC_WEB_REST_CORS_HEADERS: 'origin,content-type,accept,authorization,X-Api-Key' EDC_WEB_REST_CORS_ORIGINS: '*' EDC_AGENT_IDENTITY_KEY: 'client_id' # required for Mock IAM to work + ports: - '11001:11001' - '11002:11002' @@ -89,6 +90,7 @@ services: EDC_WEB_REST_CORS_HEADERS: 'origin,content-type,accept,authorization,X-Api-Key' EDC_WEB_REST_CORS_ORIGINS: '*' EDC_AGENT_IDENTITY_KEY: 'client_id' # required for Mock IAM to work + ports: - '22001:11001' - '22002:11002' diff --git a/docs/api/postman_collection.json b/docs/api/postman_collection.json index 7c9901f10..ef1d557fa 100644 --- a/docs/api/postman_collection.json +++ b/docs/api/postman_collection.json @@ -1,10 +1,10 @@ { "info": { - "_postman_id": "c01dc51f-eb36-43db-b8db-a33ff2f5baa5", + "_postman_id": "a842e697-4b5e-4308-b802-35c446ea6135", "name": "sovity EDC Community Edition", "description": "This is the official postman collection for the sovity EDC Community Edition.\n\nThe Management-API is based on core-edc v0.2.1.\n\nsovity EDC Community Edition: [https://github.com/sovity/edc-ce](https://github.com/sovity/edc-ce)\n\nLicense: [https://github.com/sovity/edc-ce/blob/main/LICENSE](https://github.com/sovity/edc-ce/blob/main/LICENSE)", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "31514741" + "_exporter_id": "32949497" }, "item": [ { @@ -84,7 +84,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"id\": \"testname-v1.0\",\r\n \"title\": \"TestName\",\r\n \"language\": \"https://w3id.org/idsa/code/EN\",\r\n \"description\": \"Testdescription\",\r\n \"publisherHomepage\": \"https://www.sovity.de\",\r\n \"licenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\",\r\n \"version\": \"v1.0\",\r\n \"keywords\": [\r\n \"keyword1\",\r\n \"keyword2\"\r\n ],\r\n \"mediaType\": \"application/json\",\r\n \"landingPageUrl\": \"https://www.google.com\",\r\n \"dataAddressProperties\": {\r\n \"https://w3id.org/edc/v0.0.1/ns/type\": \"HttpData\",\r\n \"https://w3id.org/edc/v0.0.1/ns/baseUrl\": \"https://www.google.com\",\r\n \"https://w3id.org/edc/v0.0.1/ns/method\": \"GET\",\r\n \"https://w3id.org/edc/v0.0.1/ns/queryParams\": \"\"\r\n }\r\n}", + "raw": "{\r\n \"id\": \"testname-v1.0\",\r\n \"title\": \"TestName\",\r\n \"language\": \"https://w3id.org/idsa/code/EN\",\r\n \"description\": \"Testdescription\",\r\n \"publisherHomepage\": \"https://www.sovity.de\",\r\n \"licenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\",\r\n \"version\": \"v1.0\",\r\n \"keywords\": [\r\n \"keyword1\",\r\n \"keyword2\"\r\n ],\r\n \"mediaType\": \"application/json\",\r\n \"landingPageUrl\": \"https://www.google.com\",\r\n \"dataAddressProperties\": {\r\n \"https://w3id.org/edc/v0.0.1/ns/type\": \"HttpData\",\r\n \"https://w3id.org/edc/v0.0.1/ns/baseUrl\": \"https://www.google.com\",\r\n \"https://w3id.org/edc/v0.0.1/ns/method\": \"GET\",\r\n \"https://w3id.org/edc/v0.0.1/ns/queryParams\": \"\"\r\n },\r\n \"dataSource\": {\r\n \"type\": \"HTTP_DATA\",\r\n \"httpData\": {\r\n \"baseUrl\": \"http://example.com/baseUrl/\"\r\n }\r\n }\r\n}", "options": { "raw": { "language": "json" @@ -386,7 +386,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"contractDefinitionId\": \"testCD\",\r\n \"contractPolicyId\": \"always-true\",\r\n \"accessPolicyId\": \"always-true\",\r\n \"assetSelector\": [\r\n {\r\n \"operandLeft\": \"https://w3id.org/edc/v0.0.1/ns/id\",\r\n \"operator\": \"IN\",\r\n \"operandRight\": {\r\n \"type\": \"VALUE_LIST\",\r\n \"valueList\": [\r\n \"testparamterization-v1.0\"\r\n ]\r\n }\r\n }\r\n ]\r\n}", + "raw": "{\r\n \"contractDefinitionId\": \"testCD\",\r\n \"contractPolicyId\": \"always-true\",\r\n \"accessPolicyId\": \"always-true\",\r\n \"assetSelector\": [\r\n {\r\n \"operandLeft\": \"https://w3id.org/edc/v0.0.1/ns/id\",\r\n \"operator\": \"IN\",\r\n \"operandRight\": {\r\n \"type\": \"VALUE_LIST\",\r\n \"valueList\": [\r\n \"testname-v1.0\"\r\n ]\r\n }\r\n }\r\n ]\r\n}", "options": { "raw": { "language": "json" @@ -476,7 +476,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"counterPartyAddress\": \"http://edc:11003/api/dsp\",\r\n \"counterPartyParticipantId\": \"my-edc\",\r\n \"contractOfferId\": \"dGVzdENE:dGVzdHBhcmFtdGVyaXphdGlvbi12MS4w:NTUxNjZiOGMtMzk4My00MWMzLTkxN2UtZTQ1ZGVlNzMyNTlj\",\r\n \"policyJsonLd\": \"{\\\"@id\\\":\\\"f15e4b97-0d99-42f5-8f6a-525daf0b72c6\\\",\\\"@type\\\":\\\"http://www.w3.org/ns/odrl/2/Set\\\",\\\"http://www.w3.org/ns/odrl/2/permission\\\":[{\\\"http://www.w3.org/ns/odrl/2/target\\\":\\\"testparamterization-v1.0\\\",\\\"http://www.w3.org/ns/odrl/2/action\\\":{\\\"http://www.w3.org/ns/odrl/2/type\\\":\\\"USE\\\"},\\\"http://www.w3.org/ns/odrl/2/constraint\\\":[{\\\"http://www.w3.org/ns/odrl/2/leftOperand\\\":{\\\"@value\\\":\\\"ALWAYS_TRUE\\\"},\\\"http://www.w3.org/ns/odrl/2/operator\\\":[{\\\"@id\\\":\\\"http://www.w3.org/ns/odrl/2/eq\\\"}],\\\"http://www.w3.org/ns/odrl/2/rightOperand\\\":{\\\"@value\\\":\\\"true\\\"}}]}],\\\"http://www.w3.org/ns/odrl/2/prohibition\\\":[],\\\"http://www.w3.org/ns/odrl/2/obligation\\\":[],\\\"http://www.w3.org/ns/odrl/2/target\\\":\\\"testparamterization-v1.0\\\"}\",\r\n \"assetId\": \"testparamterization-v1.0\"\r\n}", + "raw": "{\r\n \"counterPartyAddress\": \"http://edc:11003/api/dsp\",\r\n \"counterPartyParticipantId\": \"my-edc\",\r\n \"contractOfferId\": \"{{CONTRACT_OFFER_ID}}\",\r\n \"policyJsonLd\": \"{{POLICY_JSON_LD}}\",\r\n \"assetId\": \"{{ASSET_ID}}\"\r\n}", "options": { "raw": { "language": "json" @@ -545,6 +545,48 @@ } }, "response": [] + }, + { + "name": "Terminate Contract Agreement", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"detail\": \"Some detail between 1 and 1000 chars long\",\n \"reason\": \"Some reason between 1 and 100 chars\"\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{PROVIDER_EDC_MANAGEMENT_URL}}/wrapper/ui/pages/content-agreement-page/{{CONTRACT_AGREEMENT_ID}}/terminate", + "host": [ + "{{PROVIDER_EDC_MANAGEMENT_URL}}" + ], + "path": [ + "wrapper", + "ui", + "pages", + "content-agreement-page", + "{{CONTRACT_AGREEMENT_ID}}", + "terminate" + ] + } + }, + "response": [] } ] }, @@ -1960,4 +2002,4 @@ "type": "default" } ] -} +} \ No newline at end of file diff --git a/docs/api/sovity-edc-api-wrapper.yaml b/docs/api/sovity-edc-api-wrapper.yaml index 851967da8..eb0ab902c 100644 --- a/docs/api/sovity-edc-api-wrapper.yaml +++ b/docs/api/sovity-edc-api-wrapper.yaml @@ -1786,6 +1786,7 @@ components: description: "For type PARAMS_ONLY: Required data for starting a Transfer Process" ContractTerminationRequest: required: + - detail - reason type: object properties: diff --git a/docs/dev/debugging.md b/docs/dev/debugging.md new file mode 100644 index 000000000..0bf1f90b8 --- /dev/null +++ b/docs/dev/debugging.md @@ -0,0 +1,50 @@ +# Debugging + +## Connect to docker images + +### Configure + +The images are started with the [docker-entrypoint.sh](../../launchers/docker-entrypoint.sh). + +This entry point supports debugging via environment variables. + +In `docker-compose-dev`, add the following environment variables in `services.edc.environment` + +```yaml + REMOTE_DEBUG: y + REMOTE_DEBUG_BIND: 0.0.0.0:5005 +``` + +If you also want to wait for the debugger to connect before starting the EDC, also add + +```yaml + REMOTE_DEBUG_SUSPEND: y +``` + +Then shutdown and restart the EDC with docker compose. + +If you used the `dev` set of files: + +```bash +docker compose --env-file .env.dev --file docker-compose-dev.yaml down +docker compose --env-file .env.dev --file docker-compose-dev.yaml up +``` + +### Connect + +Each EDC will start on a different set of ports, but they all bind to the same address mentioned above in docker. + +To connect to the EDC, in IJ, do: + +* Edit configurations +* Add New Configuration +* Remote JVM debugger +* Debugger mode: attach to remote JVM +* Host: localhost +* Post: it depends on the EDC: + * check the `ports` mapping in the docker compose file where you added the remote debugging options + * look for the entry that is `#####:5005` + * This `#####` port is the port you want to connect to. +* Use module classpath: use sovity-edc-ce + +Enjoy! :) diff --git a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java index 9c28a1be2..5b8e8eece 100644 --- a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java +++ b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/UiResource.java @@ -34,10 +34,8 @@ import de.sovity.edc.ext.wrapper.api.ui.model.UiContractNegotiation; import de.sovity.edc.ext.wrapper.api.ui.model.UiDataOffer; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; diff --git a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractTerminationRequest.java b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractTerminationRequest.java index 6f570e607..b0615f2ae 100644 --- a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractTerminationRequest.java +++ b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/ContractTerminationRequest.java @@ -15,6 +15,9 @@ package de.sovity.edc.ext.wrapper.api.ui.model; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Getter; @@ -35,8 +38,11 @@ public class ContractTerminationRequest { @Schema( title = "Termination detail", description = "A user explanation to detail why the contract was terminated.", - requiredMode = Schema.RequiredMode.NOT_REQUIRED) + requiredMode = Schema.RequiredMode.REQUIRED) @Size(max = MAX_DETAIL_SIZE) + @NotNull + @NotEmpty + @NotBlank String detail; @Schema( @@ -44,5 +50,8 @@ public class ContractTerminationRequest { description = "A short reason why this contract was terminated", requiredMode = Schema.RequiredMode.REQUIRED) @Size(max = MAX_REASON_SIZE) + @NotBlank + @NotEmpty + @NotNull String reason; } diff --git a/tests/src/test/java/de/sovity/edc/e2e/ContractTerminationTest.java b/tests/src/test/java/de/sovity/edc/e2e/ContractTerminationTest.java index a73735d83..595619c5d 100644 --- a/tests/src/test/java/de/sovity/edc/e2e/ContractTerminationTest.java +++ b/tests/src/test/java/de/sovity/edc/e2e/ContractTerminationTest.java @@ -33,13 +33,16 @@ import org.awaitility.Awaitility; import org.eclipse.edc.connector.contract.spi.ContractId; import org.eclipse.edc.connector.transfer.spi.types.TransferProcessStates; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.extension.ExtendWith; import org.mockserver.integration.ClientAndServer; import org.mockserver.model.HttpRequest; import org.mockserver.model.HttpResponse; import java.time.OffsetDateTime; +import java.util.List; import java.util.Map; import java.util.stream.IntStream; @@ -52,11 +55,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; @ExtendWith(E2eTestExtension.class) public class ContractTerminationTest { - @Test void canGetAgreementPageForNonTerminatedContract( E2eScenario scenario, @@ -249,6 +252,44 @@ void limitTheDetailSizeAt1000Chars( // termination completed == success } + @TestFactory + List theDetailsAreMandatory( + E2eScenario scenario, + @Consumer EdcClient consumerClient, + @Provider EdcClient providerClient + ) { + val invalidDetails = List.of( + "", + " ", + " ", + "\t", + "\n" + ); + + return invalidDetails.stream().map( + detail -> dynamicTest("Can't use '%s' for details".formatted(detail), + () -> { + val assetId = scenario.createAsset(); + scenario.createContractDefinition(assetId); + val negotiation = scenario.negotiateAssetAndAwait(assetId); + + // act + val reason = "Some reason"; + + // assert when too big + + assertThrows( + ApiException.class, + () -> consumerClient.uiApi().terminateContractAgreement( + negotiation.getContractAgreementId(), + ContractTerminationRequest.builder() + .detail(detail) + .reason(reason) + .build()) + ); + })).toList(); + } + @Test @SneakyThrows void canTerminateFromProvider(