Skip to content

Commit

Permalink
refactor: multi-namespace dsp controllers
Browse files Browse the repository at this point in the history
  • Loading branch information
wolf4ood committed Oct 17, 2024
1 parent c79e11c commit b3645de
Show file tree
Hide file tree
Showing 10 changed files with 577 additions and 383 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@
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.spi.type.DspNamespace;

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_IRI;
import static org.eclipse.edc.protocol.dsp.spi.type.DspCatalogPropertyAndTypeNames.DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM;

/**
* Provides the HTTP endpoint for receiving catalog requests.
Expand All @@ -57,16 +58,18 @@ public class DspCatalogApiController {
private final DspRequestHandler dspRequestHandler;
private final ContinuationTokenManager continuationTokenManager;
private final String protocol;
private final DspNamespace namespace;

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

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

@POST
Expand All @@ -83,7 +86,7 @@ public Response requestCatalog(JsonObject jsonObject, @HeaderParam(AUTHORIZATION

var request = PostDspRequest.Builder.newInstance(CatalogRequestMessage.class, Catalog.class, CatalogError.class)
.token(token)
.expectedMessageType(DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_IRI)
.expectedMessageType(namespace.toIri(DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM))
.message(messageJson)
.serviceCall(service::getCatalog)
.errorProvider(CatalogError.Builder::newInstance)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.eclipse.edc.connector.controlplane.services.spi.catalog.CatalogProtocolService;
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.spi.type.DspNamespace;

import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.eclipse.edc.protocol.dsp.catalog.http.api.CatalogApiPaths.BASE_PATH;
Expand All @@ -36,6 +37,6 @@ public class DspCatalogApiController20241 extends DspCatalogApiController {

public DspCatalogApiController20241(CatalogProtocolService service, DspRequestHandler dspRequestHandler,
ContinuationTokenManager responseDecorator) {
super(service, dspRequestHandler, responseDecorator, DATASPACE_PROTOCOL_HTTP_V_2024_1);
super(service, dspRequestHandler, responseDecorator, DATASPACE_PROTOCOL_HTTP_V_2024_1, DspNamespace.V_2024_1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
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.protocol.dsp.spi.type.DspNamespace;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase;
Expand All @@ -47,7 +48,8 @@
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_IRI;
import static org.eclipse.edc.protocol.dsp.spi.type.DspCatalogPropertyAndTypeNames.DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM;
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;
Expand All @@ -56,115 +58,156 @@
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

@ApiTest
class DspCatalogApiControllerTest extends RestControllerTestBase {

private final TypeTransformerRegistry transformerRegistry = mock();
private final CatalogProtocolService service = mock();
private final DspRequestHandler dspRequestHandler = mock();
private 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");
}

@Override
protected Object controller() {
return new DspCatalogApiController(service, dspRequestHandler, continuationTokenManager);
}
class DspCatalogApiControllerTest {

private RequestSpecification baseRequest() {
return given()
.baseUri("http://localhost:" + port)
.basePath(BASE_PATH)
.header(HttpHeaders.AUTHORIZATION, "auth")
.when();
}
abstract static class Tests extends RestControllerTestBase {

@Nested
class RequestCatalog {
protected final TypeTransformerRegistry transformerRegistry = mock();
protected final CatalogProtocolService service = mock();
protected final DspRequestHandler dspRequestHandler = mock();
protected final ContinuationTokenManager continuationTokenManager = mock();

@Test
void shouldCreateResource() {
var requestBody = createObjectBuilder().add(TYPE, DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_IRI).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());
void getDataset_shouldGetResource() {
when(dspRequestHandler.getResource(any())).thenReturn(Response.ok().type(APPLICATION_JSON).build());

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

var captor = ArgumentCaptor.forClass(PostDspRequest.class);
verify(dspRequestHandler).createResource(captor.capture(), isA(ResponseDecorator.class));
var captor = ArgumentCaptor.forClass(GetDspRequest.class);
verify(dspRequestHandler).getResource(captor.capture());
var request = captor.getValue();
assertThat(request.getInputClass()).isEqualTo(CatalogRequestMessage.class);
assertThat(request.getResultClass()).isEqualTo(Catalog.class);
assertThat(request.getExpectedMessageType()).isEqualTo(DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_IRI);
assertThat(request.getProcessId()).isNull();
assertThat(request.getToken()).isEqualTo("auth");
assertThat(request.getMessage()).isEqualTo(requestBody);
verify(continuationTokenManager).createResponseDecorator("http://localhost:%d/catalog/request".formatted(port));
assertThat(request.getResultClass()).isEqualTo(Dataset.class);
assertThat(request.getId()).isEqualTo("datasetId");
}

@Test
void shouldApplyContinuationToken_whenPassed() {
var requestBody = createObjectBuilder().add(TYPE, DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_IRI).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);
protected abstract String basePath();

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");
protected abstract DspNamespace namespace();

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

@Test
void shouldReturnBadRequest_whenContinuationTokenIsNotValid() {
var requestBody = createObjectBuilder().add(TYPE, DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_IRI).build();
when(continuationTokenManager.applyQueryFromToken(any(), any())).thenReturn(Result.failure("error"));
@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);
}
}
}

baseRequest()
.contentType(JSON)
.body(requestBody)
.post(CATALOG_REQUEST + "?continuationToken=pagination-token")
.then()
.statusCode(400);
@ApiTest
@Nested
class DspCatalogApiControllerV08Test extends Tests {

@Override
protected String basePath() {
return BASE_PATH;
}

@Override
protected DspNamespace namespace() {
return DspNamespace.V_08;
}

verifyNoInteractions(dspRequestHandler, transformerRegistry);
@Override
protected Object controller() {
return new DspCatalogApiController(service, dspRequestHandler, continuationTokenManager);
}
}

@ApiTest
@Nested
class DspCatalogApiControllerV20241Test extends Tests {

@Override
protected String basePath() {
return V_2024_1_PATH + BASE_PATH;
}

@Override
protected DspNamespace namespace() {
return DspNamespace.V_2024_1;
}

@Override
protected Object controller() {
return new DspCatalogApiController20241(service, dspRequestHandler, continuationTokenManager);
}
}
}
Loading

0 comments on commit b3645de

Please sign in to comment.