Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(test): adds E2E tests to the CI #294

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions .github/workflows/run-terraform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
---
name: "Run DCP Demo locally"
on:
push:
pull_request:
branches:
- main

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
workflow_run:
workflows: [ "Verify" ]
types:
- completed

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -49,7 +51,7 @@ jobs:
- uses: eclipse-edc/.github/.github/actions/setup-build@main

- name: "Build runtime images"
working-directory: ./runtimes
working-directory: ./
run: |
./gradlew -Ppersistence=true dockerize

Expand All @@ -61,7 +63,7 @@ jobs:
cluster_name: dcp-demo

- name: "Load runtime images into KinD"
run: kind load docker-image controlplane:latest identity-hub:latest catalog-server:latest -n dcp-demo
run: kind load docker-image controlplane:latest dataplane:latest identity-hub:latest catalog-server:latest -n dcp-demo

- name: "Install nginx ingress controller"
run: |-
Expand All @@ -88,11 +90,14 @@ jobs:
run: |-
terraform apply "$GITHUB_SHA.out"

- name: "Run Health Checks"
- name: "Seed dataspace"
run: |-
chmod +x seed-k8s.sh
./seed-k8s.sh

- name: "Run E2E Test"
run: |-
curl --fail http://localhost/provider-qna/health/check/readiness -H "x-api-key: password"
curl --fail http://localhost/provider-manufacturing/health/check/readiness -H "x-api-key: password"
curl --fail http://localhost/consumer/health/check/readiness -H "x-api-key: password"
./gradlew -DincludeTags="EndToEndTest" test -DverboseTest=true

- name: "Destroy the KinD cluster"
run: >-
Expand Down
13 changes: 10 additions & 3 deletions .github/workflows/verify.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ name: "Verify"

on:
push:
branches:
- main
pull_request:
branches:
- main
Expand Down Expand Up @@ -51,14 +49,23 @@ jobs:
exit 1;
fi

validate-formating:
checkstyle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: eclipse-edc/.github/.github/actions/setup-build@main
- name: Run Checkstyle
run: ./gradlew checkstyleMain checkstyleTest

- name: Check Terraform files are properly formatted (run "terraform fmt -recursive" to fix)
run: |
terraform fmt -recursive
git diff --exit-code

validate-terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check Terraform files are properly formatted (run "terraform fmt -recursive" to fix)
run: |
terraform fmt -recursive
Expand Down
12 changes: 9 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ format.version = "1.1"

[versions]
assertj = "3.24.2"
awaitility = "4.2.0"
awaitility = "4.2.1"
edc = "0.8.1-SNAPSHOT"
failsafe = "3.3.2"
jackson = "2.14.2"
jakarta-json = "2.1.3"
jupiter = "5.10.1"
mockserver = "5.15.0"
nimbus = "9.40"
parsson = "1.1.6"
postgres = "42.7.3"
restAssured = "5.3.2"
restAssured = "5.5.0"
swagger = "2.2.18"
rsApi = "3.1.0"
testcontainers = "1.19.1"
Expand Down Expand Up @@ -148,7 +150,11 @@ edc-controlplane-contract = { module = "org.eclipse.edc:control-plane-contract",
# Third party libs
nimbus-jwt = { module = "com.nimbusds:nimbus-jose-jwt", version.ref = "nimbus" }
postgres = { module = "org.postgresql:postgresql", version.ref = "postgres" }

awaitility = { module = "org.awaitility:awaitility", version.ref = "awaitility" }
restAssured = { module = "io.rest-assured:rest-assured", version.ref = "restAssured" }
jakarta-json-api = { module = "jakarta.json:jakarta.json-api", version.ref = "jakarta-json" }
jackson-datatype-jakarta-jsonp = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jakarta-jsonp", version.ref = "jackson" }
parsson = { module = "org.eclipse.parsson:parsson", version.ref = "parsson" }

[bundles]
dpf = ["edc-dpf-selector-core", "edc-spi-dataplane-selector", "edc-dpf-selector-control-api", "edc-dpf-signaling-client", "edc-dpf-transfer-signaling"]
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ include(":extensions:catalog-node-resolver")
include(":extensions:dcp-impl")
include(":extensions:superuser-seed")
//include(":tests:performance")
include(":tests:system-tests")
include(":tests:end2end")

// launcher modules
include(":launchers:identity-hub")
Expand Down
26 changes: 26 additions & 0 deletions tests/end2end/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Metaform Systems, Inc. - initial API and implementation
*
*/

plugins {
`java-library`
}

dependencies {
testImplementation(libs.edc.junit)
testImplementation(libs.jakarta.json.api)
testImplementation(libs.jackson.datatype.jakarta.jsonp)
testImplementation(libs.parsson)
testImplementation(libs.restAssured)
testImplementation(libs.awaitility)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.edc.demo.tests.transfer;

import io.restassured.specification.RequestSpecification;
import jakarta.json.Json;
import org.eclipse.edc.junit.annotations.EndToEndTest;
import org.eclipse.edc.junit.testfixtures.TestUtils;
import org.junit.jupiter.api.Test;

import java.time.Duration;
import java.util.concurrent.atomic.AtomicReference;

import static io.restassured.RestAssured.given;
import static io.restassured.http.ContentType.JSON;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

/**
* This test is designed to run against an MVD deployed in a Kubernetes cluster, with an active ingress controller.
* The cluster MUST be deployed and seeded according to the README before running this test!
*/
@EndToEndTest
public class TransferEndToEndTest {
// Management API base URL of the consumer connector, goes through Ingress controller
private static final String CONSUMER_MANAGEMENT_URL = "http://127.0.0.1/consumer/cp";
// Catalog Query API URL of the consumer connector, goes through ingress controller
private static final String CONSUMER_CATALOG_URL = "http://127.0.0.1/consumer/fc";
// DSP service URL of the provider, not reachable outside the cluster
private static final String PROVIDER_DSP_URL = "http://provider-qna-controlplane:8082";
// DID of the provider company
private static final String PROVIDER_ID = "did:web:provider-identityhub%3A7083:provider";
// public API endpoint of the provider-qna connector, goes through the incress controller
private static final String PROVIDER_PUBLIC_URL = "http://127.0.0.1/provider-qna/public";
private static final Duration TEST_TIMEOUT_DURATION = Duration.ofSeconds(60);

private static RequestSpecification baseRequest() {
return given()
.header("X-Api-Key", "password")
.contentType(JSON)
.when();
}

@Test
void transferData() {
var emptyQueryBody = Json.createObjectBuilder()
.add("@context", Json.createObjectBuilder().add("edc", "https://w3id.org/edc/v0.0.1/ns/"))
.add("@type", "QuerySpec")
.build();
var offerId = new AtomicReference<String>();
// get catalog, extract offer ID
await().atMost(TEST_TIMEOUT_DURATION)
.untilAsserted(() -> {
var oid = baseRequest()
.body(emptyQueryBody)
.post(CONSUMER_CATALOG_URL + "/api/catalog/v1alpha/catalog/query")
.then()
.log().ifError()
.statusCode(200)
// yes, it's a bit brittle with the hardcoded indexes, but it appears to work.
.extract().body().jsonPath().getString("[0]['http://www.w3.org/ns/dcat#dataset'][1]['http://www.w3.org/ns/dcat#dataset'][0]['odrl:hasPolicy']['@id']");

assertThat(oid).isNotNull();
offerId.set(oid);
});

// initiate negotiation
var negotiationRequest = TestUtils.getResourceFileContentAsString("negotiation-request.json")
.replace("{{PROVIDER_ID}}", PROVIDER_ID)
.replace("{{PROVIDER_DSP_URL}}", PROVIDER_DSP_URL)
.replace("{{OFFER_ID}}", offerId.get());
var negotiationId = baseRequest()
.body(negotiationRequest)
.post(CONSUMER_MANAGEMENT_URL + "/api/management/v3/contractnegotiations")
.then()
.log().ifError()
.statusCode(200)
.extract().body().jsonPath().getString("@id");
assertThat(negotiationId).isNotNull();

//wait until negotiation is FINALIZED
var agreementId = new AtomicReference<String>();
await().atMost(TEST_TIMEOUT_DURATION)
.untilAsserted(() -> {
var jp = baseRequest()
.get(CONSUMER_MANAGEMENT_URL + "/api/management/v3/contractnegotiations/" + negotiationId)
.then()
.statusCode(200)
.extract().body().jsonPath();
var state = jp.getString("state");
assertThat(state).isEqualTo("FINALIZED");
agreementId.set(jp.getString("contractAgreementId"));

});

//start transfer process
var tpRequest = TestUtils.getResourceFileContentAsString("transfer-request.json")
.replace("{{PROVIDER_ID}}", PROVIDER_ID)
.replace("{{PROVIDER_DSP_URL}}", PROVIDER_DSP_URL)
.replace("{{CONTRACT_ID}}", agreementId.get());

var transferProcessId = baseRequest()
.body(tpRequest)
.post(CONSUMER_MANAGEMENT_URL + "/api/management/v3/transferprocesses")
.then()
.log().ifError()
.statusCode(200)
.extract().body().jsonPath().getString("@id");

// fetch EDR for transfer process
var endpoint = new AtomicReference<String>();
var token = new AtomicReference<String>();
await().atMost(TEST_TIMEOUT_DURATION)
.untilAsserted(() -> {
var jp = baseRequest()
.get(CONSUMER_MANAGEMENT_URL + "/api/management/v3/edrs/%s/dataaddress".formatted(transferProcessId))
.then()
.statusCode(200)
.extract().body().jsonPath();

endpoint.set(jp.getString("endpoint"));
token.set(jp.getString("authorization"));

assertThat(endpoint.get()).isNotNull().endsWith("/api/public");
assertThat(token.get()).isNotNull();
});

//download exemplary JSON data from public endpoint
var response = given()
.header("Authorization", token.get())
.get(PROVIDER_PUBLIC_URL + "/api/public")
.then()
.log().ifError()
.statusCode(200)
.extract().body().asString();

assertThat(response).isNotEmpty();
}
}
33 changes: 33 additions & 0 deletions tests/end2end/src/test/resources/negotiation-request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"@context": {
"@vocab": "https://w3id.org/edc/v0.0.1/ns/"
},
"@type": "https://w3id.org/edc/v0.0.1/ns/ContractRequest",
"counterPartyAddress": "{{PROVIDER_DSP_URL}}/api/dsp",
"counterPartyId": "{{PROVIDER_ID}}",
"protocol": "dataspace-protocol-http",
"policy": {
"@context": "http://www.w3.org/ns/odrl.jsonld",
"@type": "http://www.w3.org/ns/odrl/2/Offer",
"@id": "{{OFFER_ID}}",
"assigner": "{{PROVIDER_ID}}",
"permission": [],
"prohibition": [],
"odrl:obligation": {
"odrl:action": {
"@id": "use"
},
"odrl:constraint": {
"odrl:leftOperand": {
"@id": "FrameworkCredential.pcf"
},
"odrl:operator": {
"@id": "odrl:eq"
},
"odrl:rightOperand": "active"
}
},
"target": "asset-1"
},
"callbackAddresses": []
}
14 changes: 14 additions & 0 deletions tests/end2end/src/test/resources/transfer-request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"@context": {
"odrl": "http://www.w3.org/ns/odrl/2/"
},
"assetId": "asset-1",
"counterPartyAddress": "{{PROVIDER_DSP_URL}}/api/dsp",
"connectorId": "{{PROVIDER_ID}}",
"contractId": "{{CONTRACT_ID}}",
"dataDestination": {
"type": "HttpProxy"
},
"protocol": "dataspace-protocol-http",
"transferType": "HttpData-PULL"
}
22 changes: 0 additions & 22 deletions tests/system-tests/build.gradle.kts

This file was deleted.

Loading