Skip to content

Commit

Permalink
chore: extract base controllers for all DSP apis (#4666)
Browse files Browse the repository at this point in the history
  • Loading branch information
wolf4ood authored Dec 9, 2024
1 parent 81feb70 commit 60a20fe
Show file tree
Hide file tree
Showing 33 changed files with 1,303 additions and 1,050 deletions.
1 change: 1 addition & 0 deletions data-protocols/dsp/dsp-catalog/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ dependencies {
api(project(":data-protocols:dsp:dsp-catalog:dsp-catalog-transform"))
api(project(":data-protocols:dsp:dsp-catalog:lib:dsp-catalog-validation-lib"))
api(project(":data-protocols:dsp:dsp-catalog:lib:dsp-catalog-transform-lib"))
api(project(":data-protocols:dsp:dsp-catalog:lib:dsp-catalog-http-api-lib"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,18 @@ dependencies {
api(project(":spi:common:json-ld-spi"))

api(project(":spi:control-plane:control-plane-spi"))

implementation(project(":extensions:common:http:lib:jersey-providers-lib"))
implementation(project(":data-protocols:dsp:dsp-catalog:lib:dsp-catalog-validation-lib"))
implementation(project(":data-protocols:dsp:dsp-catalog:lib:dsp-catalog-http-api-lib"))

implementation(libs.jakarta.rsApi)

testImplementation(testFixtures(project(":extensions:common:http:jersey-core")))
testImplementation(project(":core:common:junit"))
testImplementation(project(":core:common:lib:transform-lib"))
testImplementation(project(":data-protocols:dsp:dsp-catalog:dsp-catalog-transform"))
testImplementation(libs.restAssured)
testImplementation(testFixtures(project(":data-protocols:dsp:dsp-catalog:lib:dsp-catalog-http-api-lib")))

}

edcBuild {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,16 @@

package org.eclipse.edc.protocol.dsp.catalog.http.api.controller;

import jakarta.json.JsonObject;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import org.eclipse.edc.connector.controlplane.catalog.spi.Catalog;
import org.eclipse.edc.connector.controlplane.catalog.spi.CatalogError;
import org.eclipse.edc.connector.controlplane.catalog.spi.CatalogRequestMessage;
import org.eclipse.edc.connector.controlplane.catalog.spi.Dataset;
import org.eclipse.edc.connector.controlplane.services.spi.catalog.CatalogProtocolService;
import org.eclipse.edc.jsonld.spi.JsonLdNamespace;
import org.eclipse.edc.protocol.dsp.http.spi.message.ContinuationTokenManager;
import org.eclipse.edc.protocol.dsp.http.spi.message.DspRequestHandler;
import org.eclipse.edc.protocol.dsp.http.spi.message.GetDspRequest;
import org.eclipse.edc.protocol.dsp.http.spi.message.PostDspRequest;

import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION;
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.eclipse.edc.protocol.dsp.catalog.http.api.CatalogApiPaths.BASE_PATH;
import static org.eclipse.edc.protocol.dsp.catalog.http.api.CatalogApiPaths.CATALOG_REQUEST;
import static org.eclipse.edc.protocol.dsp.catalog.http.api.CatalogApiPaths.DATASET_REQUEST;
import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP;
import static org.eclipse.edc.protocol.dsp.spi.type.DspCatalogPropertyAndTypeNames.DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM;
import static org.eclipse.edc.protocol.dsp.spi.type.DspConstants.DSP_NAMESPACE_V_08;

/**
Expand All @@ -53,63 +32,10 @@
@Consumes({ APPLICATION_JSON })
@Produces({ APPLICATION_JSON })
@Path(BASE_PATH)
public class DspCatalogApiController {

private final CatalogProtocolService service;
private final DspRequestHandler dspRequestHandler;
private final ContinuationTokenManager continuationTokenManager;
private final String protocol;
private final JsonLdNamespace namespace;

public class DspCatalogApiController extends BaseDspCatalogApiController {

public DspCatalogApiController(CatalogProtocolService service, DspRequestHandler dspRequestHandler, ContinuationTokenManager continuationTokenManager) {
this(service, dspRequestHandler, continuationTokenManager, DATASPACE_PROTOCOL_HTTP, DSP_NAMESPACE_V_08);
}

public DspCatalogApiController(CatalogProtocolService service, DspRequestHandler dspRequestHandler, ContinuationTokenManager continuationTokenManager, String protocol, JsonLdNamespace namespace) {
this.service = service;
this.dspRequestHandler = dspRequestHandler;
this.continuationTokenManager = continuationTokenManager;
this.protocol = protocol;
this.namespace = namespace;
}

@POST
@Path(CATALOG_REQUEST)
public Response requestCatalog(JsonObject jsonObject, @HeaderParam(AUTHORIZATION) String token, @Context UriInfo uriInfo,
@QueryParam("continuationToken") String continuationToken) {
JsonObject messageJson;
if (continuationToken == null) {
messageJson = jsonObject;
} else {
messageJson = continuationTokenManager.applyQueryFromToken(jsonObject, continuationToken)
.orElseThrow(f -> new BadRequestException(f.getFailureDetail()));
}

var request = PostDspRequest.Builder.newInstance(CatalogRequestMessage.class, Catalog.class, CatalogError.class)
.token(token)
.expectedMessageType(namespace.toIri(DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM))
.message(messageJson)
.serviceCall(service::getCatalog)
.errorProvider(CatalogError.Builder::newInstance)
.protocol(protocol)
.build();

var responseDecorator = continuationTokenManager.createResponseDecorator(uriInfo.getAbsolutePath().toString());
return dspRequestHandler.createResource(request, responseDecorator);
}

@GET
@Path(DATASET_REQUEST + "/{id}")
public Response getDataset(@PathParam("id") String id, @HeaderParam(AUTHORIZATION) String token) {
var request = GetDspRequest.Builder.newInstance(Dataset.class, CatalogError.class)
.token(token)
.id(id)
.serviceCall(service::getDataset)
.errorProvider(CatalogError.Builder::newInstance)
.protocol(protocol)
.build();

return dspRequestHandler.getResource(request);
super(service, dspRequestHandler, continuationTokenManager, DATASPACE_PROTOCOL_HTTP, DSP_NAMESPACE_V_08);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
import static org.eclipse.edc.protocol.dsp.spi.version.DspVersions.V_2024_1_PATH;

/**
* Versioned Catalog endpoint, same as {@link DspCatalogApiController} but exposed on the /2024/1 path
* Versioned Catalog endpoint for 2024/1 protocol version
*/
@Consumes({ APPLICATION_JSON })
@Produces({ APPLICATION_JSON })
@Path(V_2024_1_PATH + BASE_PATH)
public class DspCatalogApiController20241 extends DspCatalogApiController {
public class DspCatalogApiController20241 extends BaseDspCatalogApiController {

public DspCatalogApiController20241(CatalogProtocolService service, DspRequestHandler dspRequestHandler,
ContinuationTokenManager responseDecorator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,168 +14,21 @@

package org.eclipse.edc.protocol.dsp.catalog.http.api.controller;

import io.restassured.specification.RequestSpecification;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
import org.eclipse.edc.connector.controlplane.catalog.spi.Catalog;
import org.eclipse.edc.connector.controlplane.catalog.spi.CatalogRequestMessage;
import org.eclipse.edc.connector.controlplane.catalog.spi.Dataset;
import org.eclipse.edc.connector.controlplane.services.spi.catalog.CatalogProtocolService;
import org.eclipse.edc.jsonld.spi.JsonLdKeywords;
import org.eclipse.edc.jsonld.spi.JsonLdNamespace;
import org.eclipse.edc.junit.annotations.ApiTest;
import org.eclipse.edc.protocol.dsp.http.spi.message.ContinuationTokenManager;
import org.eclipse.edc.protocol.dsp.http.spi.message.DspRequestHandler;
import org.eclipse.edc.protocol.dsp.http.spi.message.GetDspRequest;
import org.eclipse.edc.protocol.dsp.http.spi.message.PostDspRequest;
import org.eclipse.edc.protocol.dsp.http.spi.message.ResponseDecorator;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

import static io.restassured.RestAssured.given;
import static io.restassured.http.ContentType.JSON;
import static jakarta.json.Json.createObjectBuilder;
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE;
import static org.eclipse.edc.protocol.dsp.catalog.http.api.CatalogApiPaths.BASE_PATH;
import static org.eclipse.edc.protocol.dsp.catalog.http.api.CatalogApiPaths.CATALOG_REQUEST;
import static org.eclipse.edc.protocol.dsp.catalog.http.api.CatalogApiPaths.DATASET_REQUEST;
import static org.eclipse.edc.protocol.dsp.spi.type.DspCatalogPropertyAndTypeNames.DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM;
import static org.eclipse.edc.protocol.dsp.spi.type.DspConstants.DSP_NAMESPACE_V_08;
import static org.eclipse.edc.protocol.dsp.spi.type.DspConstants.DSP_NAMESPACE_V_2024_1;
import static org.eclipse.edc.protocol.dsp.spi.version.DspVersions.V_2024_1_PATH;
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;

class DspCatalogApiControllerTest {

abstract static class Tests extends RestControllerTestBase {

protected final TypeTransformerRegistry transformerRegistry = mock();
protected final CatalogProtocolService service = mock();
protected final DspRequestHandler dspRequestHandler = mock();
protected final ContinuationTokenManager continuationTokenManager = mock();

@Test
void getDataset_shouldGetResource() {
when(dspRequestHandler.getResource(any())).thenReturn(Response.ok().type(APPLICATION_JSON).build());

baseRequest()
.get(DATASET_REQUEST + "/datasetId")
.then()
.statusCode(200)
.contentType(JSON);

var captor = ArgumentCaptor.forClass(GetDspRequest.class);
verify(dspRequestHandler).getResource(captor.capture());
var request = captor.getValue();
assertThat(request.getToken()).isEqualTo("auth");
assertThat(request.getResultClass()).isEqualTo(Dataset.class);
assertThat(request.getId()).isEqualTo("datasetId");
}


protected abstract String basePath();

protected abstract JsonLdNamespace namespace();

private RequestSpecification baseRequest() {
return given()
.baseUri("http://localhost:" + port)
.basePath(basePath())
.header(HttpHeaders.AUTHORIZATION, "auth")
.when();
}

@Nested
class RequestCatalog {

@Test
void shouldCreateResource() {
var requestBody = createObjectBuilder().add(TYPE, namespace().toIri(DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM)).build();
var catalog = createObjectBuilder().add(JsonLdKeywords.TYPE, "catalog").build();
when(transformerRegistry.transform(any(Catalog.class), eq(JsonObject.class))).thenReturn(Result.success(catalog));
when(dspRequestHandler.createResource(any(), any())).thenReturn(Response.ok().type(APPLICATION_JSON_TYPE).build());
when(continuationTokenManager.createResponseDecorator(any())).thenReturn(mock());

baseRequest()
.contentType(JSON)
.body(requestBody)
.post(CATALOG_REQUEST)
.then()
.statusCode(200)
.contentType(JSON);

var captor = ArgumentCaptor.forClass(PostDspRequest.class);
verify(dspRequestHandler).createResource(captor.capture(), isA(ResponseDecorator.class));
var request = captor.getValue();
assertThat(request.getInputClass()).isEqualTo(CatalogRequestMessage.class);
assertThat(request.getResultClass()).isEqualTo(Catalog.class);
assertThat(request.getExpectedMessageType()).isEqualTo(namespace().toIri(DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM));
assertThat(request.getProcessId()).isNull();
assertThat(request.getToken()).isEqualTo("auth");
assertThat(request.getMessage()).isEqualTo(requestBody);
verify(continuationTokenManager).createResponseDecorator("http://localhost:%d%s".formatted(port, basePath() + CATALOG_REQUEST));
}

@Test
void shouldApplyContinuationToken_whenPassed() {
var requestBody = createObjectBuilder().add(TYPE, namespace().toIri(DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM)).build();
var catalog = createObjectBuilder().add(JsonLdKeywords.TYPE, "catalog").build();
when(transformerRegistry.transform(any(Catalog.class), eq(JsonObject.class))).thenReturn(Result.success(catalog));
when(dspRequestHandler.createResource(any(), any())).thenReturn(Response.ok().type(APPLICATION_JSON_TYPE).build());
when(continuationTokenManager.createResponseDecorator(any())).thenReturn(mock());
var enrichedRequestBody = createObjectBuilder(requestBody).add("query", Json.createObjectBuilder()).build();
when(continuationTokenManager.applyQueryFromToken(any(), any())).thenReturn(Result.success(enrichedRequestBody));

baseRequest()
.contentType(JSON)
.body(requestBody)
.post(CATALOG_REQUEST + "?continuationToken=pagination-token")
.then()
.statusCode(200)
.contentType(JSON);

var captor = ArgumentCaptor.forClass(PostDspRequest.class);
verify(dspRequestHandler).createResource(captor.capture(), isA(ResponseDecorator.class));
var request = captor.getValue();
assertThat(request.getMessage()).isSameAs(enrichedRequestBody);
verify(continuationTokenManager).applyQueryFromToken(requestBody, "pagination-token");
}

@Test
void shouldReturnBadRequest_whenContinuationTokenIsNotValid() {
var requestBody = createObjectBuilder().add(TYPE, namespace().toIri(DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM)).build();
when(continuationTokenManager.applyQueryFromToken(any(), any())).thenReturn(Result.failure("error"));

baseRequest()
.contentType(JSON)
.body(requestBody)
.post(CATALOG_REQUEST + "?continuationToken=pagination-token")
.then()
.statusCode(400);

verifyNoInteractions(dspRequestHandler, transformerRegistry);
}
}
}

@ApiTest
@Nested
class DspCatalogApiControllerV08Test extends Tests {
class DspCatalogApiControllerV08Test extends DspCatalogApiControllerTestBase {

@Override
protected String basePath() {
Expand All @@ -195,7 +48,7 @@ protected Object controller() {

@ApiTest
@Nested
class DspCatalogApiControllerV20241Test extends Tests {
class DspCatalogApiControllerV20241Test extends DspCatalogApiControllerTestBase {

@Override
protected String basePath() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
*
*/

plugins {
`java-library`
`java-test-fixtures`
}

dependencies {
api(project(":data-protocols:dsp:dsp-spi"))
api(project(":data-protocols:dsp:dsp-http-spi"))
api(project(":spi:common:json-ld-spi"))

testImplementation(project(":core:common:lib:transform-lib"))
testImplementation(project(":data-protocols:dsp:dsp-catalog:dsp-catalog-transform"))
testImplementation(testFixtures(project(":extensions:common:http:jersey-core")))
testImplementation(libs.restAssured)

testFixturesApi(project(":core:common:junit"))
testFixturesImplementation(testFixtures(project(":extensions:common:http:jersey-core")))
testFixturesImplementation(libs.restAssured)
testFixturesImplementation(libs.assertj)
testFixturesImplementation(libs.mockito.core)
}
Loading

0 comments on commit 60a20fe

Please sign in to comment.