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

refactor: multi-namespace in dsp controllers #4557

Merged
merged 2 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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
Loading