From bd00270d28e105a1d1fe100e114f8a118383f69d Mon Sep 17 00:00:00 2001 From: OlfaBensoussia Date: Mon, 7 Aug 2023 17:21:49 +0100 Subject: [PATCH 1/2] feat: use v3 asset endpoints and drop deprecated v2 --- src/controllers/management-controller.ts | 113 +++++++------- src/entities/asset.ts | 3 +- src/entities/index.ts | 1 - tests/controllers/management.test.ts | 180 +++++++---------------- tests/test-utils.ts | 25 ++-- 5 files changed, 119 insertions(+), 203 deletions(-) diff --git a/src/controllers/management-controller.ts b/src/controllers/management-controller.ts index 3ca96aa1..09eaebdc 100644 --- a/src/controllers/management-controller.ts +++ b/src/controllers/management-controller.ts @@ -12,7 +12,6 @@ import { ContractNegotiation, ContractNegotiationRequest, ContractNegotiationState, - DataAddressProperties, Dataplane, DataplaneInput, IdResponse, @@ -54,12 +53,13 @@ export class ManagementController { async listDataplanes( context: EdcConnectorClientContext, ): Promise { - return this.#inner.request(context.management, { - path: "/v2/dataplanes", - method: "GET", - apiToken: context.apiToken, - }) - .then(body => expandArray(body, () => new Dataplane())); + return this.#inner + .request(context.management, { + path: "/v2/dataplanes", + method: "GET", + apiToken: context.apiToken, + }) + .then((body) => expandArray(body, () => new Dataplane())); } async createAsset( @@ -68,7 +68,7 @@ export class ManagementController { ): Promise { return this.#inner .request(context.management, { - path: "/v2/assets", + path: "/v3/assets", method: "POST", apiToken: context.apiToken, body: { @@ -76,7 +76,7 @@ export class ManagementController { "@context": this.defaultContextValues, }, }) - .then(body => expand(body, () => new IdResponse())); + .then((body) => expand(body, () => new IdResponse())); } async deleteAsset( @@ -84,7 +84,7 @@ export class ManagementController { assetId: string, ): Promise { return this.#inner.request(context.management, { - path: `/v2/assets/${assetId}`, + path: `/v3/assets/${assetId}`, method: "DELETE", apiToken: context.apiToken, }); @@ -95,18 +95,7 @@ export class ManagementController { assetId: string, ): Promise { return this.#inner.request(context.management, { - path: `/v2/assets/${assetId}`, - method: "GET", - apiToken: context.apiToken, - }); - } - - async getAssetDataAddress( - context: EdcConnectorClientContext, - assetId: string, - ): Promise { - return this.#inner.request(context.management, { - path: `/v2/assets/${assetId}/dataaddress`, + path: `/v3/assets/${assetId}`, method: "GET", apiToken: context.apiToken, }); @@ -117,7 +106,7 @@ export class ManagementController { query: QuerySpec = {}, ): Promise { return this.#inner.request(context.management, { - path: "/v2/assets/request", + path: "/v3/assets/request", method: "POST", apiToken: context.apiToken, body: @@ -144,7 +133,7 @@ export class ManagementController { "@context": this.defaultContextValues, }, }) - .then(body => expand(body, () => new IdResponse())); + .then((body) => expand(body, () => new IdResponse())); } async deletePolicy( @@ -162,12 +151,13 @@ export class ManagementController { context: EdcConnectorClientContext, policyId: string, ): Promise { - return this.#inner.request(context.management, { - path: `/v2/policydefinitions/${policyId}`, - method: "GET", - apiToken: context.apiToken, - }) - .then(body => expand(body, () => new PolicyDefinition())); + return this.#inner + .request(context.management, { + path: `/v2/policydefinitions/${policyId}`, + method: "GET", + apiToken: context.apiToken, + }) + .then((body) => expand(body, () => new PolicyDefinition())); } async queryAllPolicies( @@ -187,7 +177,7 @@ export class ManagementController { "@context": this.defaultContextValues, }, }) - .then(body => expandArray(body, () => new PolicyDefinition())); + .then((body) => expandArray(body, () => new PolicyDefinition())); } async createContractDefinition( @@ -204,7 +194,7 @@ export class ManagementController { "@context": this.defaultContextValues, }, }) - .then(body => expand(body, () => new IdResponse())); + .then((body) => expand(body, () => new IdResponse())); } async deleteContractDefinition( @@ -246,7 +236,7 @@ export class ManagementController { "@context": this.defaultContextValues, }, }) - .then(body => expandArray(body, () => new ContractDefinition())); + .then((body) => expandArray(body, () => new ContractDefinition())); } async requestCatalog( @@ -264,7 +254,7 @@ export class ManagementController { ...input, }, }) - .then(body => expand(body, () => new Catalog())); + .then((body) => expand(body, () => new Catalog())); } async initiateContractNegotiation( @@ -282,26 +272,27 @@ export class ManagementController { ...input, }, }) - .then(body => expand(body, () => new IdResponse())); + .then((body) => expand(body, () => new IdResponse())); } async queryNegotiations( context: EdcConnectorClientContext, query: QuerySpec = {}, ): Promise { - return this.#inner.request(context.management, { - path: "/v2/contractnegotiations/request", - method: "POST", - apiToken: context.apiToken, - body: - Object.keys(query).length === 0 - ? null - : { - ...query, - "@context": this.defaultContextValues, - }, - }) - .then(body => expandArray(body, () => new ContractNegotiation())); + return this.#inner + .request(context.management, { + path: "/v2/contractnegotiations/request", + method: "POST", + apiToken: context.apiToken, + body: + Object.keys(query).length === 0 + ? null + : { + ...query, + "@context": this.defaultContextValues, + }, + }) + .then((body) => expandArray(body, () => new ContractNegotiation())); } async getNegotiation( @@ -314,7 +305,7 @@ export class ManagementController { method: "GET", apiToken: context.apiToken, }) - .then(body => expand(body, () => new ContractNegotiation())); + .then((body) => expand(body, () => new ContractNegotiation())); } async getNegotiationState( @@ -327,7 +318,7 @@ export class ManagementController { method: "GET", apiToken: context.apiToken, }) - .then(body => expand(body, () => new ContractNegotiationState())); + .then((body) => expand(body, () => new ContractNegotiationState())); } async cancelNegotiation( @@ -362,7 +353,7 @@ export class ManagementController { method: "GET", apiToken: context.apiToken, }) - .then(body => expand(body, () => new ContractAgreement())); + .then((body) => expand(body, () => new ContractAgreement())); } async queryAllAgreements( @@ -382,7 +373,7 @@ export class ManagementController { "@context": this.defaultContextValues, }, }) - .then(body => expandArray(body, () => new ContractAgreement())); + .then((body) => expandArray(body, () => new ContractAgreement())); } async getAgreement( @@ -395,7 +386,7 @@ export class ManagementController { method: "GET", apiToken: context.apiToken, }) - .then(body => expand(body, () => new ContractAgreement())); + .then((body) => expand(body, () => new ContractAgreement())); } async initiateTransfer( @@ -413,7 +404,7 @@ export class ManagementController { ...input, }, }) - .then(body => expand(body, () => new IdResponse())); + .then((body) => expand(body, () => new IdResponse())); } async queryAllTransferProcesses( @@ -425,12 +416,14 @@ export class ManagementController { path: "/v2/transferprocesses/request", method: "POST", apiToken: context.apiToken, - body: Object.keys(query).length === 0 ? null : { - ...query, - "@context": this.defaultContextValues, - }, + body: + Object.keys(query).length === 0 + ? null + : { + ...query, + "@context": this.defaultContextValues, + }, }) - .then(body => expandArray(body, () => new TransferProcess())); + .then((body) => expandArray(body, () => new TransferProcess())); } - } diff --git a/src/entities/asset.ts b/src/entities/asset.ts index 970a608b..88d8fb6c 100644 --- a/src/entities/asset.ts +++ b/src/entities/asset.ts @@ -15,8 +15,7 @@ interface AssetProperties { contenttype?: string; } -export interface AssetInput { - asset: Partial; +export interface AssetInput extends Partial { dataAddress: Partial & { properties?: Partial }; } diff --git a/src/entities/index.ts b/src/entities/index.ts index ff3325d9..3dd96452 100644 --- a/src/entities/index.ts +++ b/src/entities/index.ts @@ -31,7 +31,6 @@ export interface ApiErrorDetail { } export interface QuerySpec { - filter?: string; filterExpression?: Criterion[]; limit?: number; offset?: number; diff --git a/tests/controllers/management.test.ts b/tests/controllers/management.test.ts index 3f4da130..4641d1fc 100644 --- a/tests/controllers/management.test.ts +++ b/tests/controllers/management.test.ts @@ -42,12 +42,10 @@ describe("ManagementController", () => { // given const context = edcClient.createContext(apiToken, consumer); const assetInput: AssetInput = { - asset: { - "@id": crypto.randomUUID(), - properties: { - name: "product description", - contenttype: "application/json", - }, + "@id": crypto.randomUUID(), + properties: { + name: "product description", + contenttype: "application/json", }, dataAddress: { type: "HttpData", @@ -72,12 +70,10 @@ describe("ManagementController", () => { // given const context = edcClient.createContext(apiToken, consumer); const assetInput: AssetInput = { - asset: { - "@id": crypto.randomUUID(), - properties: { - name: "product description", - contenttype: "application/json", - }, + "@id": crypto.randomUUID(), + properties: { + name: "product description", + contenttype: "application/json", }, dataAddress: { type: "HttpData", @@ -115,12 +111,10 @@ describe("ManagementController", () => { // given const context = edcClient.createContext(apiToken, consumer); const assetInput: AssetInput = { - asset: { - "@id": crypto.randomUUID(), - properties: { - name: "product description", - contenttype: "application/json", - }, + "@id": crypto.randomUUID(), + properties: { + name: "product description", + contenttype: "application/json", }, dataAddress: { type: "HttpData", @@ -135,7 +129,7 @@ describe("ManagementController", () => { // when const asset = await edcClient.management.deleteAsset( context, - assetInput.asset["@id"] as string, + assetInput["@id"] as string, ); // then @@ -172,12 +166,10 @@ describe("ManagementController", () => { // given const context = edcClient.createContext(apiToken, consumer); const assetInput: AssetInput = { - asset: { - "@id": crypto.randomUUID(), - properties: { - name: "product description", - contenttype: "application/json", - }, + "@id": crypto.randomUUID(), + properties: { + name: "product description", + contenttype: "application/json", }, dataAddress: { type: "HttpData", @@ -192,12 +184,12 @@ describe("ManagementController", () => { // when const asset = await edcClient.management.getAsset( context, - assetInput.asset["@id"] as string, + assetInput["@id"] as string, ); // then expect(asset).toHaveProperty("@context"); - expect(asset).toHaveProperty("@id", assetInput.asset["@id"]); + expect(asset).toHaveProperty("@id", assetInput["@id"]); }); it("fails to fetch an not existant asset", async () => { @@ -223,81 +215,15 @@ describe("ManagementController", () => { }); }); - describe("edcClient.management.getAssetDataAddress", () => { - it("returns a target asset data address", async () => { - // given - const context = edcClient.createContext(apiToken, consumer); - const assetInput = { - asset: { - "@id": crypto.randomUUID(), - properties: { - name: "product description", - contenttype: "application/json", - }, - }, - dataAddress: { - type: "HttpData", - properties: { - name: "Test asset", - baseUrl: "https://jsonplaceholder.typicode.com/users", - }, - }, - }; - await edcClient.management.createAsset(context, assetInput); - - // when - const assetDataAddress = await edcClient.management.getAssetDataAddress( - context, - assetInput.asset["@id"], - ); - - // then - expect(assetDataAddress).toHaveProperty("@type"); - expect(assetDataAddress).toEqual( - expect.objectContaining({ - [`${EDC_NAMESPACE}:name`]: assetInput.dataAddress.properties.name, - [`${EDC_NAMESPACE}:baseUrl`]: - assetInput.dataAddress.properties.baseUrl, - [`${EDC_NAMESPACE}:type`]: assetInput.dataAddress.type, - }), - ); - }); - - it("fails to fetch a data address for an inexistant asset", async () => { - // given - const context = edcClient.createContext(apiToken, consumer); - - // when - const maybeAssetDataAddress = edcClient.management.getAssetDataAddress( - context, - crypto.randomUUID(), - ); - // then - await expect(maybeAssetDataAddress).rejects.toThrowError( - "resource not found", - ); - - maybeAssetDataAddress.catch((error) => { - expect(error).toBeInstanceOf(EdcConnectorClientError); - expect(error as EdcConnectorClientError).toHaveProperty( - "type", - EdcConnectorClientErrorType.NotFound, - ); - }); - }); - }); - describe("edcClient.management.queryAllAssets", () => { it("succesfully retuns a list of assets", async () => { // given const context = edcClient.createContext(apiToken, consumer); const assetInput: AssetInput = { - asset: { - "@id": crypto.randomUUID(), - properties: { - name: "product description", - contenttype: "application/json", - }, + "@id": crypto.randomUUID(), + properties: { + name: "product description", + contenttype: "application/json", }, dataAddress: { name: "Test asset", @@ -313,7 +239,7 @@ describe("ManagementController", () => { // then expect(assets.length).toBeGreaterThan(0); expect( - assets.find((asset) => asset?.["@id"] === assetInput.asset?.["@id"]), + assets.find((asset) => asset?.["@id"] === assetInput["@id"]), ).toBeTruthy(); }); }); @@ -676,12 +602,10 @@ describe("ManagementController", () => { const assetId = crypto.randomUUID(); const assetInput: AssetInput = { - asset: { - "@id": assetId, - properties: { - name: "product description", - contenttype: "application/json", - }, + "@id": assetId, + properties: { + name: "product description", + contenttype: "application/json", }, dataAddress: { name: "Test asset", @@ -725,12 +649,14 @@ describe("ManagementController", () => { const catalog = await edcClient.management.requestCatalog( consumerContext, { - providerUrl: provider.protocol + providerUrl: provider.protocol, }, ); // then - expect(catalog).toHaveProperty("@type", ["https://www.w3.org/ns/dcat/Catalog"]); + expect(catalog).toHaveProperty("@type", [ + "https://www.w3.org/ns/dcat/Catalog", + ]); expect(catalog).toHaveProperty("datasets"); }); }); @@ -783,8 +709,7 @@ describe("ManagementController", () => { expect(contractNegotiations.length).toBeGreaterThan(0); expect( contractNegotiations.find( - (contractNegotiation) => - contractNegotiation["@id"] === idResponse.id, + (contractNegotiation) => contractNegotiation["@id"] === idResponse.id, ), ).toBeTruthy(); }); @@ -930,11 +855,10 @@ describe("ManagementController", () => { "TERMINATED", ); - const negotiationState = - await edcClient.management.getNegotiationState( - consumerContext, - negotiationId, - ); + const negotiationState = await edcClient.management.getNegotiationState( + consumerContext, + negotiationId, + ); // then expect(cancelledNegotiation).toBeUndefined(); @@ -1010,11 +934,10 @@ describe("ManagementController", () => { "TERMINATING", ); - const negotiationState = - await edcClient.management.getNegotiationState( - consumerContext, - negotiationId, - ); + const negotiationState = await edcClient.management.getNegotiationState( + consumerContext, + negotiationId, + ); // then expect(negotiationState.state).toBe("TERMINATING"); @@ -1083,7 +1006,8 @@ describe("ManagementController", () => { expect(contractAgreements.length).toBeGreaterThan(0); expect( contractAgreements.find( - agreement => agreement.id === contractNegotiation.contractAgreementId + (agreement) => + agreement.id === contractNegotiation.contractAgreementId, ), ).toBeTruthy(); }); @@ -1241,12 +1165,18 @@ describe("ManagementController", () => { dataplanes.forEach((dataplane) => { expect(dataplane).toHaveProperty("id"); expect(dataplane).toHaveProperty("url", input.url); - expect(dataplane) - .toHaveProperty("allowedDestTypes", input.allowedDestTypes); - expect(dataplane) - .toHaveProperty("allowedSourceTypes", input.allowedSourceTypes); - expect(dataplane.properties) - .toHaveProperty("edc:publicApiUrl", "http://consumer-connector:9291/public/"); + expect(dataplane).toHaveProperty( + "allowedDestTypes", + input.allowedDestTypes, + ); + expect(dataplane).toHaveProperty( + "allowedSourceTypes", + input.allowedSourceTypes, + ); + expect(dataplane.properties).toHaveProperty( + `${EDC_NAMESPACE}:publicApiUrl`, + "http://consumer-connector:9291/public/", + ); }); }); }); diff --git a/tests/test-utils.ts b/tests/test-utils.ts index fc022b0e..588545d5 100644 --- a/tests/test-utils.ts +++ b/tests/test-utils.ts @@ -39,7 +39,7 @@ export async function createContractAgreement( consumerContext, ); - const negotiationId = idResponse.id + const negotiationId = idResponse.id; await waitForNegotiationState( client, @@ -73,13 +73,11 @@ export async function createContractNegotiation( // Crate asset on the provider's side const assetId = crypto.randomUUID(); const assetInput: AssetInput = { - asset: { - "@id": assetId, - properties: { - "asset:prop:id": assetId, - "asset:prop:name": "product description", - "asset:prop:contenttype": "application/json", - }, + "@id": assetId, + properties: { + "asset:prop:id": assetId, + "asset:prop:name": "product description", + "asset:prop:contenttype": "application/json", }, dataAddress: { name: "Test asset", @@ -118,11 +116,9 @@ export async function createContractNegotiation( providerUrl: providerContext.protocol, }); - - const offer = catalog - .datasets - .flatMap(it => it.offers) - .find(offer => offer.assetId === assetId)!; + const offer = catalog.datasets + .flatMap((it) => it.offers) + .find((offer) => offer.assetId === assetId)!; offer._compacted = undefined; @@ -174,8 +170,7 @@ export async function waitForNegotiationState( waiting = actualState !== targetState; } while (waiting && times > 0); - expect(actualState).toBe(targetState) - + expect(actualState).toBe(targetState); } export function createReceiverServer() { From 3e7762425df37b06cabacc155112109130a533b8 Mon Sep 17 00:00:00 2001 From: OlfaBensoussia Date: Tue, 8 Aug 2023 01:43:52 +0100 Subject: [PATCH 2/2] feat: support PUT method on assets --- src/controllers/management-controller.ts | 15 +++++ tests/controllers/management.test.ts | 85 ++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/src/controllers/management-controller.ts b/src/controllers/management-controller.ts index 09eaebdc..6b3bcb56 100644 --- a/src/controllers/management-controller.ts +++ b/src/controllers/management-controller.ts @@ -119,6 +119,21 @@ export class ManagementController { }); } + async updateAsset( + context: EdcConnectorClientContext, + input: AssetInput, + ): Promise { + return this.#inner.request(context.management, { + path: "/v3/assets", + method: "PUT", + apiToken: context.apiToken, + body: { + ...input, + "@context": this.defaultContextValues, + }, + }); + } + async createPolicy( context: EdcConnectorClientContext, input: PolicyDefinitionInput, diff --git a/tests/controllers/management.test.ts b/tests/controllers/management.test.ts index 4641d1fc..346bf3e0 100644 --- a/tests/controllers/management.test.ts +++ b/tests/controllers/management.test.ts @@ -244,6 +244,91 @@ describe("ManagementController", () => { }); }); + describe("edcClient.management.updateAsset", () => { + it("updates a target asset", async () => { + // given + const context = edcClient.createContext(apiToken, consumer); + const assetInput = { + "@id": crypto.randomUUID(), + properties: { + name: "product description", + contenttype: "application/json", + }, + dataAddress: { + type: "HttpData", + properties: { + name: "Test asset", + baseUrl: "https://jsonplaceholder.typicode.com/users", + }, + }, + }; + await edcClient.management.createAsset(context, assetInput); + const updateAssetInput = { + "@id": assetInput["@id"], + properties: { name: "updated test asset", contenttype: "text/plain" }, + dataAddress: { + type: "s3", + }, + }; + + // when + await edcClient.management.updateAsset(context, updateAssetInput); + + const updatedAsset = await edcClient.management.getAsset( + context, + assetInput["@id"], + ); + + // then + expect(updatedAsset).toHaveProperty("@type"); + expect(updatedAsset).toEqual( + expect.objectContaining({ + [`${EDC_NAMESPACE}:properties`]: { + [`${EDC_NAMESPACE}:name`]: updateAssetInput.properties.name, + [`${EDC_NAMESPACE}:id`]: updateAssetInput["@id"], + [`${EDC_NAMESPACE}:contenttype`]: + updateAssetInput.properties.contenttype, + }, + [`${EDC_NAMESPACE}:dataAddress`]: { + [`${EDC_NAMESPACE}:type`]: updateAssetInput.dataAddress.type, + "@type": "edc:DataAddress", + }, + }), + ); + }); + + it("fails to update an inexistant asset", async () => { + // given + const context = edcClient.createContext(apiToken, consumer); + const updateAssetInput = { + "@id": crypto.randomUUID(), + properties: { name: "updated test asset", contenttype: "text/plain" }, + dataAddress: { + type: "s3", + }, + }; + + // when + const maybeUpdatedAsset = edcClient.management.updateAsset( + context, + updateAssetInput, + ); + + // then + await expect(maybeUpdatedAsset).rejects.toThrowError( + "resource not found", + ); + + maybeUpdatedAsset.catch((error) => { + expect(error).toBeInstanceOf(EdcConnectorClientError); + expect(error as EdcConnectorClientError).toHaveProperty( + "type", + EdcConnectorClientErrorType.NotFound, + ); + }); + }); + }); + describe("edcClient.management.createPolicy", () => { it("succesfully creates a new policy", async () => { // given