From 04441d1f8bcd99012255582db60dcc4b71a739bb Mon Sep 17 00:00:00 2001 From: Albin Ramovic <68844975+aramovic79@users.noreply.github.com> Date: Thu, 12 Dec 2024 17:28:11 +0100 Subject: [PATCH] fix: added top level property entityTypes (#95) * initial commit * construct entityTypes and entityTypeMappings arrays * Fix entityTypeTargets to be array of objects and not strings * check existance of @ORDExtensions on entity level * change schema and service in the xmpl folder so the sample ord document is valid * handle entity type version * tests clean-up * minor change:rewrite the map statement * introduct LEVEL constant * rename sample entities in xmpl app * change the version handling * minor change * remove incorrect validateNamespace function * add test for missing package.json * use constants with miningful names instead of oidIdParts[N] --------- Co-authored-by: RoshniNaveenaS <132035609+RoshniNaveenaS@users.noreply.github.com> --- .../localAndNonODMReferencedEntitiesCsn.json | 121 +++++ ...rc.test.js.snap => mockedCsn.test.js.snap} | 6 +- __tests__/__snapshots__/ord.e2e.test.js.snap | 431 ++++++++++++++++++ __tests__/__snapshots__/ordCdsrc.test.js.snap | 194 -------- .../__snapshots__/ordPackageJson.test.js.snap | 148 ------ __tests__/mockedCsn.test.js | 103 +++++ __tests__/noApisOrNoEvents.test.js | 33 -- .../noEntitiesInServiceDefinition.test.js | 29 -- __tests__/noOrdInCdsrc.test.js | 26 -- __tests__/ord.e2e.test.js | 173 +++++++ __tests__/ordCdsrc.test.js | 84 ---- __tests__/ordPackageJson.test.js | 84 ---- __tests__/protectedServices.test.js | 41 -- .../__snapshots__/defaults.test.js.snap | 30 +- .../__snapshots__/templates.test.js.snap | 46 +- __tests__/unittest/templates.test.js | 47 +- lib/constants.js | 19 +- lib/defaults.js | 20 +- lib/ord.js | 36 +- lib/templates.js | 88 +++- xmpl/db/schema.cds | 21 + xmpl/ord/custom.ord.json | 39 +- xmpl/srv/services.cds | 13 +- 23 files changed, 1139 insertions(+), 693 deletions(-) create mode 100644 __tests__/__mocks__/localAndNonODMReferencedEntitiesCsn.json rename __tests__/__snapshots__/{noOrdInCdsrc.test.js.snap => mockedCsn.test.js.snap} (96%) create mode 100644 __tests__/__snapshots__/ord.e2e.test.js.snap delete mode 100644 __tests__/__snapshots__/ordCdsrc.test.js.snap delete mode 100644 __tests__/__snapshots__/ordPackageJson.test.js.snap create mode 100644 __tests__/mockedCsn.test.js delete mode 100644 __tests__/noApisOrNoEvents.test.js delete mode 100644 __tests__/noEntitiesInServiceDefinition.test.js delete mode 100644 __tests__/noOrdInCdsrc.test.js create mode 100644 __tests__/ord.e2e.test.js delete mode 100644 __tests__/ordCdsrc.test.js delete mode 100644 __tests__/ordPackageJson.test.js delete mode 100644 __tests__/protectedServices.test.js create mode 100644 xmpl/db/schema.cds diff --git a/__tests__/__mocks__/localAndNonODMReferencedEntitiesCsn.json b/__tests__/__mocks__/localAndNonODMReferencedEntitiesCsn.json new file mode 100644 index 0000000..7f40b5d --- /dev/null +++ b/__tests__/__mocks__/localAndNonODMReferencedEntitiesCsn.json @@ -0,0 +1,121 @@ +{ + "$version": "2.0", + "definitions": { + "LocalService": { + "@ORD.Extensions.title": "This is Local Service title", + "kind": "service" + }, + "LocalService.DummyEntityA": { + "@ODM.entityName": "SomeODMEntity", + "@ODM.oid": "id", + "@ODM.root": true, + "@title": "Dummy title of Entity with corresponding ODM entity title", + "elements": { + "id": { + "key": true, + "type": "cds.UUID" + }, + "propertyA": { + "length": 10, + "type": "cds.String" + }, + "propertyB": { + "length": 20, + "type": "cds.String" + } + }, + "kind": "entity", + "projection": { + "from": { + "ref": [ + "sap.cds.demo.EntityWithCorrespondingODMEntity" + ] + } + } + }, + "LocalService.DummyEntityB": { + "@EntityRelationship.entityType": "sap.sm:SomeAribaDummyEntity", + "@ObjectModel.compositionRoot": true, + "@title": "Dummy title of Ariba entity", + "elements": { + "id": { + "key": true, + "type": "cds.UUID" + }, + "propertyC": { + "length": 10, + "type": "cds.String" + }, + "propertyD": { + "length": 20, + "type": "cds.String" + } + }, + "kind": "entity", + "projection": { + "from": { + "ref": [ + "sap.cds.demo.SomeAribaEntity" + ] + } + } + }, + "LocalService.TitleChange2": { + "elements": { + "ID": { + "type": "cds.Integer" + }, + "title": { + "@title": "Changed title", + "type": "cds.String" + } + }, + "kind": "event" + }, + "sap.cds.demo.EntityWithCorrespondingODMEntity": { + "@ODM.entityName": "SomeODMEntity", + "@ODM.oid": "id", + "@ODM.root": true, + "@title": "Dummy title of Entity with corresponding ODM entity title", + "elements": { + "id": { + "key": true, + "type": "cds.UUID" + }, + "propertyA": { + "length": 10, + "type": "cds.String" + }, + "propertyB": { + "length": 20, + "type": "cds.String" + } + }, + "kind": "entity" + }, + "sap.cds.demo.SomeAribaEntity": { + "@EntityRelationship.entityType": "sap.sm:SomeAribaDummyEntity", + "@ObjectModel.compositionRoot": true, + "@title": "Dummy title of Ariba entity", + "elements": { + "id": { + "key": true, + "type": "cds.UUID" + }, + "propertyC": { + "length": 10, + "type": "cds.String" + }, + "propertyD": { + "length": 20, + "type": "cds.String" + } + }, + "kind": "entity" + } + }, + "meta": { + "creator": "CDS Compiler v5.4.4", + "flavor": "inferred" + } +} \ No newline at end of file diff --git a/__tests__/__snapshots__/noOrdInCdsrc.test.js.snap b/__tests__/__snapshots__/mockedCsn.test.js.snap similarity index 96% rename from __tests__/__snapshots__/noOrdInCdsrc.test.js.snap rename to __tests__/__snapshots__/mockedCsn.test.js.snap index 5afac3e..9644d09 100644 --- a/__tests__/__snapshots__/noOrdInCdsrc.test.js.snap +++ b/__tests__/__snapshots__/mockedCsn.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Tests for ORD document when .cdsrc.json has no \`ord\` property Successfully create ORD Documents with no \`ord\` in .cdsrc.json 1`] = ` +exports[`Tests for ORD document generated out of mocked csn files Tests for ORD document when .cdsrc.json has no \`ord\` property Successfully create ORD Documents with no \`ord\` in .cdsrc.json 1`] = ` { "$schema": "https://sap.github.io/open-resource-discovery/spec-v1/interfaces/Document.schema.json", "apiResources": [ @@ -291,7 +291,7 @@ exports[`Tests for ORD document when .cdsrc.json has no \`ord\` property Success "partOfProducts": [ "customer:product:capire.bookshop.ord.sample:", ], - "shortDescription": "Short description for capire bookshop ord sample", + "shortDescription": "Short description of capire bookshop ord sample", "title": "capire bookshop ord sample", "vendor": "customer:vendor:Customer:", "version": "1.0.0", @@ -301,7 +301,7 @@ exports[`Tests for ORD document when .cdsrc.json has no \`ord\` property Success "products": [ { "ordId": "customer:product:capire.bookshop.ord.sample:", - "shortDescription": "Description for capire bookshop ord sample", + "shortDescription": "Short description of capire bookshop ord sample", "title": "capire bookshop ord sample", "vendor": "customer:vendor:customer:", }, diff --git a/__tests__/__snapshots__/ord.e2e.test.js.snap b/__tests__/__snapshots__/ord.e2e.test.js.snap new file mode 100644 index 0000000..315de0d --- /dev/null +++ b/__tests__/__snapshots__/ord.e2e.test.js.snap @@ -0,0 +1,431 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`End-to-end test for ORD document Tests for default ORD document when .cdsrc.json is not present Successfully create ORD Documents with defaults 1`] = ` +{ + "$schema": "https://sap.github.io/open-resource-discovery/spec-v1/interfaces/Document.schema.json", + "apiResources": [ + { + "apiProtocol": "odata-v4", + "description": "Description for AdminService", + "entityTypeMappings": [ + { + "entityTypeTargets": [ + { + "ordId": "sap.odm:entityType:BusinessPartner:v1", + }, + ], + }, + ], + "entryPoints": [ + "/odata/v4/admin", + ], + "extensible": { + "supported": "yes", + }, + "lastUpdate": "2024-11-04T14:33:25+01:00", + "ordId": "sap.test.cdsrc.sample:apiResource:AdminService:v1", + "partOfPackage": "sap.test.cdsrc.sample:package:capirebookshopordsample-api:v1", + "releaseStatus": "active", + "resourceDefinitions": [ + { + "accessStrategies": [ + { + "type": "open", + }, + ], + "mediaType": "application/json", + "type": "openapi-v3", + "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.oas3.json", + }, + { + "accessStrategies": [ + { + "type": "open", + }, + ], + "mediaType": "application/xml", + "type": "edmx", + "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.edmx", + }, + ], + "shortDescription": "short description for test AdminService", + "title": "This is test AdminService title", + "version": "2.0.0", + "visibility": "public", + }, + ], + "consumptionBundles": [ + { + "description": "This Consumption Bundle contains all resources of the reference app which are unprotected and do not require authentication", + "lastUpdate": "2024-11-04T14:33:25+01:00", + "ordId": "capirebookshopordsample:consumptionBundle:noAuth:v1", + "shortDescription": "If we have another protected API then it will be another object", + "title": "Unprotected resources", + "version": "1.0.0", + }, + ], + "dataProducts": [ + { + "category": "business-object", + "description": "The Supplier data product offers access to all customers.", + "entityTypes": [ + "sap.odm:entityType:BusinessPartner:v1", + "sap.sm:entityType:BusinessPartner:v1", + ], + "lastUpdate": "2024-06-20T14:04:01+01:00", + "localId": "Supplier", + "ordId": "sap.sm:dataProduct:Supplier:v1", + "outputPorts": [ + { + "ordId": "sap.sm:apiResource:SupplierService:v1", + }, + ], + "partOfPackage": "sap.sm:package:smDataProducts:v1", + "releaseStatus": "active", + "responsible": "sap:ach:CIC-DP-CO", + "shortDescription": "Ariba Supplier data product", + "title": "Supplier", + "type": "primary", + "version": "1.1.11", + "visibility": "public", + }, + ], + "description": "this is my custom description", + "eventResources": [ + { + "description": "CAP Event resource describing events / messages.", + "entityTypeMappings": { + "entityTypeTargets": [ + { + "ordId": "sap.odm:entityType:test-from-extension:v1", + }, + ], + }, + "extensible": { + "supported": "yes", + }, + "lastUpdate": "2024-11-04T14:33:25+01:00", + "ordId": "sap.test.cdsrc.sample:eventResource:AdminService:v1", + "partOfGroups": [ + "sap.cds:service:sap.test.cdsrc.sample:AdminService", + ], + "partOfPackage": "sap.test.cdsrc.sample:package:capirebookshopordsample-event:v1", + "releaseStatus": "active", + "resourceDefinitions": [ + { + "accessStrategies": [ + { + "type": "open", + }, + ], + "mediaType": "application/json", + "type": "asyncapi-v2", + "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.asyncapi2.json", + }, + ], + "shortDescription": "short description for test AdminService", + "title": "This is test AdminService title", + "version": "2.0.0", + "visibility": "public", + }, + ], + "groups": [ + { + "groupId": "sap.cds:service:sap.test.cdsrc.sample:AdminService", + "groupTypeId": "sap.cds:service", + "title": "This is test AdminService title", + }, + { + "groupId": "sap.cds:service:sap.test.cdsrc.sample:CatalogService", + "groupTypeId": "sap.cds:service", + "title": "This is test Catalog Service title", + }, + { + "groupId": "sap.cds:service:sap.test.cdsrc.sample:CinemaService", + "groupTypeId": "sap.cds:service", + "title": "This is test Cinema Service title", + }, + ], + "openResourceDiscovery": "1.10", + "packages": [ + { + "description": "Description for capire bookshop ord sample", + "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-api:v1", + "partOfProducts": [ + "customer:product:capire.bookshop.ord.sample:", + ], + "shortDescription": "Short description of capire bookshop ord sample", + "title": "capire bookshop ord sample", + "vendor": "customer:vendor:Customer:", + "version": "1.0.0", + }, + { + "description": "Description for capire bookshop ord sample", + "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-event:v1", + "partOfProducts": [ + "customer:product:capire.bookshop.ord.sample:", + ], + "title": "capire bookshop ord sample", + "vendor": "customer:vendor:Customer:", + "version": "1.0.1", + }, + { + "description": "Description for capire bookshop ord sample", + "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-integrationDependency:v1", + "partOfProducts": [ + "customer:product:capire.bookshop.ord.sample:", + ], + "shortDescription": "Short description of capire bookshop ord sample", + "title": "capire bookshop ord sample", + "vendor": "customer:vendor:Customer:", + "version": "1.0.0", + }, + { + "description": "Description for capire bookshop ord sample", + "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-entityType:v1", + "partOfProducts": [ + "customer:product:capire.bookshop.ord.sample:", + ], + "shortDescription": "Short description of capire bookshop ord sample", + "title": "capire bookshop ord sample", + "vendor": "customer:vendor:Customer:", + "version": "1.0.0", + }, + { + "description": "Description for capire bookshop ord sample version 2", + "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-api:v2", + "partOfProducts": [ + "customer:product:capire.bookshop.ord.sample:", + ], + "shortDescription": "Short description for capire bookshop ord sample version 2", + "title": "capire bookshop ord sample", + "vendor": "customer:vendor:Customer:", + "version": "2.0.0", + }, + ], + "policyLevel": "sap:core:v1", + "products": [ + { + "ordId": "customer:product:capire.bookshop.ord.sample:", + "shortDescription": "Short description of capire bookshop ord sample", + "title": "capire bookshop ord sample", + "vendor": "customer:vendor:customer:", + }, + ], +} +`; + +exports[`End-to-end test for ORD document Tests for default ORD document when .cdsrc.json is present Successfully create ORD Documents with defaults 1`] = ` +{ + "$schema": "https://sap.github.io/open-resource-discovery/spec-v1/interfaces/Document.schema.json", + "apiResources": [ + { + "apiProtocol": "odata-v4", + "description": "Description for AdminService", + "entityTypeMappings": [ + { + "entityTypeTargets": [ + { + "ordId": "sap.odm:entityType:BusinessPartner:v1", + }, + ], + }, + ], + "entryPoints": [ + "/odata/v4/admin", + ], + "extensible": { + "supported": "yes", + }, + "lastUpdate": "2024-11-04T14:33:25+01:00", + "ordId": "sap.test.cdsrc.sample:apiResource:AdminService:v1", + "partOfPackage": "sap.test.cdsrc.sample:package:capirebookshopordsample-api:v1", + "releaseStatus": "active", + "resourceDefinitions": [ + { + "accessStrategies": [ + { + "type": "open", + }, + ], + "mediaType": "application/json", + "type": "openapi-v3", + "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.oas3.json", + }, + { + "accessStrategies": [ + { + "type": "open", + }, + ], + "mediaType": "application/xml", + "type": "edmx", + "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.edmx", + }, + ], + "shortDescription": "short description for test AdminService", + "title": "This is test AdminService title", + "version": "2.0.0", + "visibility": "public", + }, + ], + "consumptionBundles": [ + { + "description": "This Consumption Bundle contains all resources of the reference app which are unprotected and do not require authentication", + "lastUpdate": "2024-11-04T14:33:25+01:00", + "ordId": "capirebookshopordsample:consumptionBundle:noAuth:v1", + "shortDescription": "If we have another protected API then it will be another object", + "title": "Unprotected resources", + "version": "1.0.0", + }, + ], + "dataProducts": [ + { + "category": "business-object", + "description": "The Supplier data product offers access to all customers.", + "entityTypes": [ + "sap.odm:entityType:BusinessPartner:v1", + "sap.sm:entityType:BusinessPartner:v1", + ], + "lastUpdate": "2024-06-20T14:04:01+01:00", + "localId": "Supplier", + "ordId": "sap.sm:dataProduct:Supplier:v1", + "outputPorts": [ + { + "ordId": "sap.sm:apiResource:SupplierService:v1", + }, + ], + "partOfPackage": "sap.sm:package:smDataProducts:v1", + "releaseStatus": "active", + "responsible": "sap:ach:CIC-DP-CO", + "shortDescription": "Ariba Supplier data product", + "title": "Supplier", + "type": "primary", + "version": "1.1.11", + "visibility": "public", + }, + ], + "description": "this is my custom description", + "eventResources": [ + { + "description": "CAP Event resource describing events / messages.", + "entityTypeMappings": { + "entityTypeTargets": [ + { + "ordId": "sap.odm:entityType:test-from-extension:v1", + }, + ], + }, + "extensible": { + "supported": "yes", + }, + "lastUpdate": "2024-11-04T14:33:25+01:00", + "ordId": "sap.test.cdsrc.sample:eventResource:AdminService:v1", + "partOfGroups": [ + "sap.cds:service:sap.test.cdsrc.sample:AdminService", + ], + "partOfPackage": "sap.test.cdsrc.sample:package:capirebookshopordsample-event:v1", + "releaseStatus": "active", + "resourceDefinitions": [ + { + "accessStrategies": [ + { + "type": "open", + }, + ], + "mediaType": "application/json", + "type": "asyncapi-v2", + "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.asyncapi2.json", + }, + ], + "shortDescription": "short description for test AdminService", + "title": "This is test AdminService title", + "version": "2.0.0", + "visibility": "public", + }, + ], + "groups": [ + { + "groupId": "sap.cds:service:sap.test.cdsrc.sample:AdminService", + "groupTypeId": "sap.cds:service", + "title": "This is test AdminService title", + }, + { + "groupId": "sap.cds:service:sap.test.cdsrc.sample:CatalogService", + "groupTypeId": "sap.cds:service", + "title": "This is test Catalog Service title", + }, + { + "groupId": "sap.cds:service:sap.test.cdsrc.sample:CinemaService", + "groupTypeId": "sap.cds:service", + "title": "This is test Cinema Service title", + }, + ], + "openResourceDiscovery": "1.10", + "packages": [ + { + "description": "Description for capire bookshop ord sample", + "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-api:v1", + "partOfProducts": [ + "customer:product:capire.bookshop.ord.sample:", + ], + "shortDescription": "Short description of capire bookshop ord sample", + "title": "capire bookshop ord sample", + "vendor": "customer:vendor:Customer:", + "version": "1.0.0", + }, + { + "description": "Description for capire bookshop ord sample", + "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-event:v1", + "partOfProducts": [ + "customer:product:capire.bookshop.ord.sample:", + ], + "title": "capire bookshop ord sample", + "vendor": "customer:vendor:Customer:", + "version": "1.0.1", + }, + { + "description": "Description for capire bookshop ord sample", + "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-integrationDependency:v1", + "partOfProducts": [ + "customer:product:capire.bookshop.ord.sample:", + ], + "shortDescription": "Short description of capire bookshop ord sample", + "title": "capire bookshop ord sample", + "vendor": "customer:vendor:Customer:", + "version": "1.0.0", + }, + { + "description": "Description for capire bookshop ord sample", + "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-entityType:v1", + "partOfProducts": [ + "customer:product:capire.bookshop.ord.sample:", + ], + "shortDescription": "Short description of capire bookshop ord sample", + "title": "capire bookshop ord sample", + "vendor": "customer:vendor:Customer:", + "version": "1.0.0", + }, + { + "description": "Description for capire bookshop ord sample version 2", + "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-api:v2", + "partOfProducts": [ + "customer:product:capire.bookshop.ord.sample:", + ], + "shortDescription": "Short description for capire bookshop ord sample version 2", + "title": "capire bookshop ord sample", + "vendor": "customer:vendor:Customer:", + "version": "2.0.0", + }, + ], + "policyLevel": "sap:core:v1", + "products": [ + { + "ordId": "customer:product:capire.bookshop.ord.sample:", + "shortDescription": "Short description of capire bookshop ord sample", + "title": "capire bookshop ord sample", + "vendor": "customer:vendor:customer:", + }, + ], +} +`; diff --git a/__tests__/__snapshots__/ordCdsrc.test.js.snap b/__tests__/__snapshots__/ordCdsrc.test.js.snap deleted file mode 100644 index 1449d55..0000000 --- a/__tests__/__snapshots__/ordCdsrc.test.js.snap +++ /dev/null @@ -1,194 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Tests for default ORD document when .cdsrc.json is present Successfully create ORD Documents with defaults 1`] = ` -{ - "$schema": "https://sap.github.io/open-resource-discovery/spec-v1/interfaces/Document.schema.json", - "apiResources": [ - { - "apiProtocol": "odata-v4", - "description": "Description for AdminService", - "entityTypeMappings": [ - { - "entityTypeTargets": [ - { - "ordId": "sap.odm:entityType:BusinessPartner:v1", - }, - ], - }, - ], - "entryPoints": [ - "/odata/v4/admin", - ], - "extensible": { - "supported": "yes", - }, - "lastUpdate": "2024-11-04T14:33:25+01:00", - "ordId": "sap.test.cdsrc.sample:apiResource:AdminService:v1", - "partOfPackage": "sap.test.cdsrc.sample:package:capirebookshopordsample-api:v1", - "releaseStatus": "active", - "resourceDefinitions": [ - { - "accessStrategies": [ - { - "type": "open", - }, - ], - "mediaType": "application/json", - "type": "openapi-v3", - "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.oas3.json", - }, - { - "accessStrategies": [ - { - "type": "open", - }, - ], - "mediaType": "application/xml", - "type": "edmx", - "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.edmx", - }, - ], - "shortDescription": "short description for test AdminService", - "title": "This is test AdminService title", - "version": "2.0.0", - "visibility": "public", - }, - ], - "consumptionBundles": [ - { - "description": "This Consumption Bundle contains all resources of the reference app which are unprotected and do not require authentication", - "lastUpdate": "2024-11-04T14:33:25+01:00", - "ordId": "capirebookshopordsample:consumptionBundle:noAuth:v1", - "shortDescription": "If we have another protected API then it will be another object", - "title": "Unprotected resources", - "version": "1.0.0", - }, - ], - "dataProducts": [ - { - "category": "business-object", - "description": "The Supplier data product offers access to all customers.", - "entityTypes": [ - "sap.odm:entityType:BusinessPartner:v1", - "sap.sm:entityType:BusinessPartner:v1", - ], - "lastUpdate": "2024-06-20T14:04:01+01:00", - "localId": "Supplier", - "ordId": "sap.sm:dataProduct:Supplier:v1", - "outputPorts": [ - { - "ordId": "sap.sm:apiResource:SupplierService:v1", - }, - ], - "partOfPackage": "sap.sm:package:smDataProducts:v1", - "releaseStatus": "active", - "responsible": "sap:ach:CIC-DP-CO", - "shortDescription": "Ariba Supplier data product", - "title": "Supplier", - "type": "primary", - "version": "1.1.11", - "visibility": "public", - }, - ], - "description": "this is my custom description", - "eventResources": [ - { - "description": "CAP Event resource describing events / messages.", - "entityTypeMappings": { - "entityTypeTargets": [ - { - "ordId": "sap.odm:entityType:test-from-extension:v1", - }, - ], - }, - "extensible": { - "supported": "yes", - }, - "lastUpdate": "2024-11-04T14:33:25+01:00", - "ordId": "sap.test.cdsrc.sample:eventResource:AdminService:v1", - "partOfGroups": [ - "sap.cds:service:sap.test.cdsrc.sample:AdminService", - ], - "partOfPackage": "sap.test.cdsrc.sample:package:capirebookshopordsample-event:v1", - "releaseStatus": "active", - "resourceDefinitions": [ - { - "accessStrategies": [ - { - "type": "open", - }, - ], - "mediaType": "application/json", - "type": "asyncapi-v2", - "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.asyncapi2.json", - }, - ], - "shortDescription": "short description for test AdminService", - "title": "This is test AdminService title", - "version": "2.0.0", - "visibility": "public", - }, - ], - "groups": [ - { - "groupId": "sap.cds:service:sap.test.cdsrc.sample:AdminService", - "groupTypeId": "sap.cds:service", - "title": "This is test AdminService title", - }, - { - "groupId": "sap.cds:service:sap.test.cdsrc.sample:CatalogService", - "groupTypeId": "sap.cds:service", - "title": "This is test Catalog Service title", - }, - { - "groupId": "sap.cds:service:sap.test.cdsrc.sample:CinemaService", - "groupTypeId": "sap.cds:service", - "title": "This is test Cinema Service title", - }, - ], - "openResourceDiscovery": "1.10", - "packages": [ - { - "description": "Description for capire bookshop ord sample", - "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-api:v1", - "partOfProducts": [ - "customer:product:capire.bookshop.ord.sample:", - ], - "shortDescription": "Short description for capire bookshop ord sample", - "title": "capire bookshop ord sample", - "vendor": "customer:vendor:Customer:", - "version": "1.0.0", - }, - { - "description": "Description for capire bookshop ord sample", - "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-event:v1", - "partOfProducts": [ - "customer:product:capire.bookshop.ord.sample:", - ], - "title": "capire bookshop ord sample", - "vendor": "customer:vendor:Customer:", - "version": "1.0.1", - }, - { - "description": "Description for capire bookshop ord sample version 2", - "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-api:v2", - "partOfProducts": [ - "customer:product:capire.bookshop.ord.sample:", - ], - "shortDescription": "Short description for capire bookshop ord sample version 2", - "title": "capire bookshop ord sample", - "vendor": "customer:vendor:Customer:", - "version": "2.0.0", - }, - ], - "policyLevel": "sap:core:v1", - "products": [ - { - "ordId": "customer:product:capire.bookshop.ord.sample:", - "shortDescription": "Description for capire bookshop ord sample", - "title": "capire bookshop ord sample", - "vendor": "customer:vendor:customer:", - }, - ], -} -`; diff --git a/__tests__/__snapshots__/ordPackageJson.test.js.snap b/__tests__/__snapshots__/ordPackageJson.test.js.snap deleted file mode 100644 index 7c72444..0000000 --- a/__tests__/__snapshots__/ordPackageJson.test.js.snap +++ /dev/null @@ -1,148 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Tests for default ORD document when .cdsrc.json is not present Successfully create ORD Documents with defaults 1`] = ` -{ - "$schema": "https://sap.github.io/open-resource-discovery/spec-v1/interfaces/Document.schema.json", - "apiResources": [ - { - "apiProtocol": "odata-v4", - "description": "Description for AdminService", - "entityTypeMappings": { - "entityTypeTargets": [ - { - "ordId": "sap.odm:entityType:test-from-extension:v1", - }, - ], - }, - "entryPoints": [ - "/odata/v4/admin", - ], - "extensible": { - "supported": "yes", - }, - "lastUpdate": "2024-11-04T14:33:25+01:00", - "ordId": "customer.capirebookshopordsample:apiResource:AdminService:v1", - "partOfGroups": [ - "sap.cds:service:customer.capirebookshopordsample:AdminService", - ], - "partOfPackage": "customer.capirebookshopordsample:package:capirebookshopordsample:v1", - "releaseStatus": "active", - "resourceDefinitions": [ - { - "accessStrategies": [ - { - "type": "open", - }, - ], - "mediaType": "application/json", - "type": "openapi-v3", - "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.oas3.json", - }, - { - "accessStrategies": [ - { - "type": "open", - }, - ], - "mediaType": "application/xml", - "type": "edmx", - "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.edmx", - }, - ], - "shortDescription": "short description for test AdminService", - "title": "This is test AdminService title", - "version": "2.0.0", - "visibility": "public", - }, - ], - "consumptionBundles": [ - { - "description": "This Consumption Bundle contains all resources of the reference app which are unprotected and do not require authentication", - "lastUpdate": "2024-11-04T14:33:25+01:00", - "ordId": "capirebookshopordsample:consumptionBundle:noAuth:v1", - "shortDescription": "If we have another protected API then it will be another object", - "title": "Unprotected resources", - "version": "1.0.0", - }, - ], - "description": "this is an application description", - "eventResources": [ - { - "description": "CAP Event resource describing events / messages.", - "entityTypeMappings": { - "entityTypeTargets": [ - { - "ordId": "sap.odm:entityType:test-from-extension:v1", - }, - ], - }, - "extensible": { - "supported": "yes", - }, - "lastUpdate": "2024-11-04T14:33:25+01:00", - "ordId": "customer.capirebookshopordsample:eventResource:AdminService:v1", - "partOfGroups": [ - "sap.cds:service:customer.capirebookshopordsample:AdminService", - ], - "partOfPackage": "customer.capirebookshopordsample:package:capirebookshopordsample:v1", - "releaseStatus": "active", - "resourceDefinitions": [ - { - "accessStrategies": [ - { - "type": "open", - }, - ], - "mediaType": "application/json", - "type": "asyncapi-v2", - "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.asyncapi2.json", - }, - ], - "shortDescription": "short description for test AdminService", - "title": "This is test AdminService title", - "version": "2.0.0", - "visibility": "public", - }, - ], - "groups": [ - { - "groupId": "sap.cds:service:customer.capirebookshopordsample:AdminService", - "groupTypeId": "sap.cds:service", - "title": "This is test AdminService title", - }, - { - "groupId": "sap.cds:service:customer.capirebookshopordsample:CatalogService", - "groupTypeId": "sap.cds:service", - "title": "This is test Catalog Service title", - }, - { - "groupId": "sap.cds:service:customer.capirebookshopordsample:CinemaService", - "groupTypeId": "sap.cds:service", - "title": "This is test Cinema Service title", - }, - ], - "openResourceDiscovery": "1.9", - "packages": [ - { - "description": "Description for capire bookshop ord sample", - "ordId": "customer.capirebookshopordsample:package:capirebookshopordsample:v1", - "partOfProducts": [ - "customer:product:capire.bookshop.ord.sample:", - ], - "shortDescription": "Short description for capire bookshop ord sample", - "title": "capire bookshop ord sample", - "vendor": "customer:vendor:Customer:", - "version": "1.0.0", - }, - ], - "policyLevel": "none", - "products": [ - { - "ordId": "customer:product:capire.bookshop.ord.sample:", - "shortDescription": "Description for capire bookshop ord sample", - "title": "capire bookshop ord sample", - "vendor": "customer:vendor:customer:", - }, - ], -} -`; diff --git a/__tests__/mockedCsn.test.js b/__tests__/mockedCsn.test.js new file mode 100644 index 0000000..066d3b4 --- /dev/null +++ b/__tests__/mockedCsn.test.js @@ -0,0 +1,103 @@ +const cds = require("@sap/cds"); +const path = require("path"); + +describe("Tests for ORD document generated out of mocked csn files", () => { + let ord; + + function checkOrdDocument(csn) { + const document = ord(csn); + + expect(document).not.toBeUndefined(); + expect(document.packages).not.toBeDefined(); + expect(document.apiResources).toHaveLength(0); + expect(document.eventResources).toHaveLength(0); + } + + beforeAll(() => { + cds.root = path.join(__dirname, "bookshop"); + jest.spyOn(require("../lib/date"), "getRFC3339Date").mockReturnValue("2024-11-04T14:33:25+01:00"); + ord = require("../lib/ord"); + }); + + beforeEach(() => { + cds.env.ord = { + namespace: "sap.test.cdsrc.sample", + openResourceDiscovery: "1.10", + description: "this is my custom description", + policyLevel: "sap:core:v1" + }; + }); + + afterAll(() => { + jest.restoreAllMocks(); + jest.clearAllMocks(); + }); + + describe("Tests for ORD document when .cdsrc.json has no `ord` property", () => { + test("Successfully create ORD Documents with no `ord` in .cdsrc.json", () => { + cds.env = {}; + const csn = require("./__mocks__/publicResourcesCsn.json"); + const document = ord(csn); + expect(document).not.toBeUndefined(); + expect(document).toMatchSnapshot(); + }); + }); + + describe("Tests for ORD document when there no events or entities in service definitions", () => { + test("Successfully create ORD Document: no entityTypeMappings field in apiResource", () => { + cds.env = {}; + const csn = require("./__mocks__/noEntitiesInServiceDefinitionCsn.json"); + const document = ord(csn); + + expect(document).not.toBeUndefined(); + expect(document.apiResources).toHaveLength(1); + expect(document.eventResources).toHaveLength(1); + expect(document.apiResources[0].ordId).toEqual(expect.stringContaining("EbMtEmitter")); + expect(document.eventResources[0].ordId).toEqual(expect.stringContaining("EbMtEmitter")); + expect(document.apiResources[0].entityTypeMappings).toBeUndefined(); + }); + }); + + describe("Tests for ORD document checking if entityTypes and entityTypeMappings are generated correctly", () => { + test("Successfully create ORD Document: entityTypes with local entities and entityTypeMappings containing referenced entities", () => { + const csn = require("./__mocks__/localAndNonODMReferencedEntitiesCsn.json"); + const document = ord(csn); + + expect(document).not.toBeUndefined(); + expect(document.entityTypes).toHaveLength(1); + expect(document.entityTypes[0].partOfPackage).toEqual(expect.stringContaining("entityType")); + expect(document.entityTypes[0].level).toEqual(expect.stringContaining("root-entity")); + expect(document.apiResources[0].entityTypeMappings[0].entityTypeTargets).toEqual(expect.arrayContaining([ + { "ordId": "sap.odm:entityType:SomeODMEntity:v1" }, + { "ordId": "sap.sm:entityType:SomeAribaDummyEntity:v1" } + ])); + }); + }); + + describe("Tests for ORD document when all the resources are private", () => { + test("All services are private: Successfully create ORD Documents without packages, empty apiResources and eventResources lists", () => { + const csn = require("./__mocks__/privateResourcesCsn.json"); + checkOrdDocument(csn); + }); + }); + + describe("Tests for ORD document when all the resources are internal", () => { + test("All services are interal: Successfully create ORD Documents without packages, empty apiResources and eventResources lists", () => { + const csn = require("./__mocks__/internalResourcesCsn.json"); + checkOrdDocument(csn); + }); + }); + + describe("Tests for ORD document when there no events or entities in service definitions", () => { + test("Successfully create ORD Documents: no Catalog service in apiResource; no Admin service in eventResources", () => { + const csn = require("./__mocks__/noApisOrNoEventsCsn.json"); + const document = ord(csn); + + expect(document).not.toBeUndefined(); + expect(document.apiResources).toHaveLength(1); + expect(document.eventResources).toHaveLength(1); + expect(document.apiResources[0].ordId).toEqual(expect.stringContaining("AdminService")); + expect(document.eventResources[0].ordId).toEqual(expect.stringContaining("CatalogService")); + }); + }); +}); \ No newline at end of file diff --git a/__tests__/noApisOrNoEvents.test.js b/__tests__/noApisOrNoEvents.test.js deleted file mode 100644 index c5f13f1..0000000 --- a/__tests__/noApisOrNoEvents.test.js +++ /dev/null @@ -1,33 +0,0 @@ -const cds = require("@sap/cds"); -const csn = require("./__mocks__/noApisOrNoEventsCsn.json"); -const path = require("path"); - -describe("Tests for ORD document when there no events or entities in service definitions", () => { - let ord; - beforeAll(() => { - cds.root = path.join(__dirname, "bookshop"); - cds.env.ord = { - namespace: "sap.test.cdsrc.sample", - openResourceDiscovery: "1.10", - description: "this is my custom description", - policyLevel: "sap:core:v1" - }; - jest.spyOn(require("../lib/date"), "getRFC3339Date").mockReturnValue("2024-11-04T14:33:25+01:00"); - ord = require("../lib/ord"); - }); - - afterAll(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - test("Successfully create ORD Documents: no Catalog service in apiResource; no Admin service in eventResources", () => { - const document = ord(csn); - - expect(document).not.toBeUndefined(); - expect(document.apiResources).toHaveLength(1); - expect(document.eventResources).toHaveLength(1); - expect(document.apiResources[0].ordId).toEqual(expect.stringContaining("AdminService")); - expect(document.eventResources[0].ordId).toEqual(expect.stringContaining("CatalogService")); - }); -}); diff --git a/__tests__/noEntitiesInServiceDefinition.test.js b/__tests__/noEntitiesInServiceDefinition.test.js deleted file mode 100644 index baccd7f..0000000 --- a/__tests__/noEntitiesInServiceDefinition.test.js +++ /dev/null @@ -1,29 +0,0 @@ -const cds = require("@sap/cds"); -const csn = require("./__mocks__/noEntitiesInServiceDefinitionCsn.json"); -const path = require("path"); - -describe("Tests for ORD document when there no events or entities in service definitions", () => { - let ord; - beforeAll(() => { - jest.spyOn(require("../lib/date"), "getRFC3339Date").mockReturnValue("2024-11-04T14:33:25+01:00"); - ord = require("../lib/ord"); - cds.root = path.join(__dirname, "bookshop"); - cds.env = {}; - }); - - afterAll(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - test("Successfully create ORD Document: no entityTypeMappings field in apiResource", () => { - const document = ord(csn); - - expect(document).not.toBeUndefined(); - expect(document.apiResources).toHaveLength(1); - expect(document.eventResources).toHaveLength(1); - expect(document.apiResources[0].ordId).toEqual(expect.stringContaining("EbMtEmitter")); - expect(document.eventResources[0].ordId).toEqual(expect.stringContaining("EbMtEmitter")); - expect(document.apiResources[0].entityTypeMappings).toBeUndefined(); - }); -}); \ No newline at end of file diff --git a/__tests__/noOrdInCdsrc.test.js b/__tests__/noOrdInCdsrc.test.js deleted file mode 100644 index d86b5d2..0000000 --- a/__tests__/noOrdInCdsrc.test.js +++ /dev/null @@ -1,26 +0,0 @@ -const cds = require("@sap/cds"); -const path = require("path"); -const csn = require("./__mocks__/publicResourcesCsn.json"); - -describe("Tests for ORD document when .cdsrc.json has no `ord` property", () => { - let ord; - - beforeAll(() => { - jest.spyOn(require("../lib/date"), "getRFC3339Date").mockReturnValue("2024-11-04T14:33:25+01:00"); - ord = require("../lib/ord"); - cds.root = path.join(__dirname, "bookshop"); - cds.env = {}; - }); - - afterAll(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - test("Successfully create ORD Documents with no `ord` in .cdsrc.json", () => { - - const document = ord(csn); - expect(document).not.toBeUndefined(); - expect(document).toMatchSnapshot(); - }); -}); diff --git a/__tests__/ord.e2e.test.js b/__tests__/ord.e2e.test.js new file mode 100644 index 0000000..028e9ad --- /dev/null +++ b/__tests__/ord.e2e.test.js @@ -0,0 +1,173 @@ +const cds = require("@sap/cds"); +const path = require("path"); + +describe("End-to-end test for ORD document", () => { + describe("Tests for default ORD document when .cdsrc.json is present", () => { + let csn, ord; + + beforeAll(async () => { + cds.root = path.join(__dirname, "bookshop"); + csn = await cds.load(path.join(cds.root, "srv")); + jest.spyOn(require("../lib/date"), "getRFC3339Date").mockReturnValue("2024-11-04T14:33:25+01:00"); + ord = require("../lib/ord"); + }); + + afterEach(() => { + cds.root = path.join(__dirname, "bookshop"); + }); + + test("Successfully create ORD Documents with defaults", () => { + const document = ord(csn); + expect(document).toMatchSnapshot(); + }); + + test("Exception thrown while package.json not found", () => { + cds.root = path.join(__dirname, "folderWithNoPackageJson"); + expect(() => ord(csn)).toThrowError("package.json not found in the project root directory"); + }); + + describe("apiResources", () => { + // eslint-disable-next-line no-useless-escape + const PACKAGE_ID_REGEX = /^([a-z0-9]+(?:[.][a-z0-9]+)*):(package):([a-zA-Z0-9._\-]+):(v0|v[1-9][0-9]*)$/; + + let document; + + beforeAll(() => { + document = ord(csn); + }); + + test("PartOfPackage values are valid ORD IDs ", () => { + for (const apiResource of document.apiResources) { + expect(apiResource.partOfPackage).toMatch(PACKAGE_ID_REGEX); + } + }); + + test("The partOfPackage references an existing package", () => { + for (const apiResource of document.apiResources) { + expect( + document.packages.find( + (pck) => pck.ordId === apiResource.partOfPackage + ) + ).toBeDefined(); + } + }); + }); + + describe("eventResources", () => { + // eslint-disable-next-line no-useless-escape + const GROUP_ID_REGEX = /^([a-z0-9-]+(?:[.][a-z0-9-]+)*):([a-zA-Z0-9._\-/]+):([a-z0-9-]+(?:[.][a-z0-9-]+)*):(?[a-zA-Z0-9._\-/]+)$/; + + let document; + + beforeAll(() => { + document = ord(csn); + }); + + test("Assigned to exactly one CDS Service group", () => { + for (const eventResource of document.eventResources) { + expect(eventResource.partOfGroups.length).toEqual(1); + } + }); + + test("The CDS Service Group ID includes the CDS Service identifier", () => { + for (const eventResource of document.eventResources) { + const [groupId] = eventResource.partOfGroups; + expect(groupId).toMatch(GROUP_ID_REGEX); + + const match = GROUP_ID_REGEX.exec(groupId); + if (match && match.groups?.service) { + let service = match.groups?.service; + if (service.startsWith("undefined")) + service = service.replace("undefined.", ""); + const definition = csn.definitions[service]; + expect(definition).toBeDefined(); + expect(definition.kind).toEqual("service"); + } + } + }); + }); + }); + + describe("Tests for default ORD document when .cdsrc.json is not present", () => { + let csn, ord; + + beforeAll(async () => { + jest.spyOn(require("../lib/date"), "getRFC3339Date").mockReturnValue("2024-11-04T14:33:25+01:00"); + ord = require("../lib/ord"); + cds.root = path.join(__dirname, "bookshop"); + csn = await cds.load(path.join(cds.root, "srv")); + }); + + afterAll(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + }); + + test("Successfully create ORD Documents with defaults", () => { + const document = ord(csn); + expect(document).toMatchSnapshot(); + }); + + describe("apiResources", () => { + // eslint-disable-next-line no-useless-escape + const PACKAGE_ID_REGEX = /^([a-z0-9]+(?:[.][a-z0-9]+)*):(package):([a-zA-Z0-9._\-]+):(v0|v[1-9][0-9]*)$/; + + let document; + + beforeAll(() => { + document = ord(csn); + }); + + test("partOfPackage values are valid ORD IDs ", () => { + for (const apiResource of document.apiResources) { + expect(apiResource.partOfPackage).toMatch(PACKAGE_ID_REGEX); + } + }); + + test("The partOfPackage references an existing package", () => { + for (const apiResource of document.apiResources) { + expect( + document.packages.find( + (pck) => pck.ordId === apiResource.partOfPackage + ) + ).toBeDefined(); + } + }); + }); + + describe("eventResources", () => { + // eslint-disable-next-line no-useless-escape + const GROUP_ID_REGEX = /^([a-z0-9-]+(?:[.][a-z0-9-]+)*):([a-zA-Z0-9._\-/]+):([a-z0-9-]+(?:[.][a-z0-9-]+)*):(?[a-zA-Z0-9._\-/]+)$/; + + let document; + + beforeAll(() => { + document = ord(csn); + }); + + test("Assigned to exactly one CDS Service group", () => { + for (const eventResource of document.eventResources) { + expect(eventResource.partOfGroups.length).toEqual(1); + } + }); + + test("The CDS Service Group ID includes the CDS Service identifier", () => { + for (const eventResource of document.eventResources) { + const [groupId] = eventResource.partOfGroups; + expect(groupId).toMatch(GROUP_ID_REGEX); + + const match = GROUP_ID_REGEX.exec(groupId); + if (match && match.groups?.service) { + let service = match.groups?.service; + if (service.startsWith("undefined")) + service = service.replace("undefined.", ""); + const definition = csn.definitions[service]; + expect(definition).toBeDefined(); + expect(definition.kind).toEqual("service"); + } + } + }); + }); + }); +}); + diff --git a/__tests__/ordCdsrc.test.js b/__tests__/ordCdsrc.test.js deleted file mode 100644 index 85208f0..0000000 --- a/__tests__/ordCdsrc.test.js +++ /dev/null @@ -1,84 +0,0 @@ -const cds = require("@sap/cds"); -const path = require("path"); - -describe("Tests for default ORD document when .cdsrc.json is present", () => { - let csn, ord; - - beforeAll(async () => { - cds.root = path.join(__dirname, "bookshop"); - csn = await cds.load(path.join(cds.root, "srv")); - jest.spyOn(require("../lib/date"), "getRFC3339Date").mockReturnValue("2024-11-04T14:33:25+01:00"); - ord = require("../lib/ord"); - }); - - afterAll(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - test("Successfully create ORD Documents with defaults", () => { - const document = ord(csn); - expect(document).toMatchSnapshot(); - }); - - describe("apiResources", () => { - // eslint-disable-next-line no-useless-escape - const PACKAGE_ID_REGEX = /^([a-z0-9]+(?:[.][a-z0-9]+)*):(package):([a-zA-Z0-9._\-]+):(v0|v[1-9][0-9]*)$/; - - let document; - - beforeAll(() => { - document = ord(csn); - }); - - test("PartOfPackage values are valid ORD IDs ", () => { - for (const apiResource of document.apiResources) { - expect(apiResource.partOfPackage).toMatch(PACKAGE_ID_REGEX); - } - }); - - test("The partOfPackage references an existing package", () => { - for (const apiResource of document.apiResources) { - expect( - document.packages.find( - (pck) => pck.ordId === apiResource.partOfPackage - ) - ).toBeDefined(); - } - }); - }); - - describe("eventResources", () => { - // eslint-disable-next-line no-useless-escape - const GROUP_ID_REGEX = /^([a-z0-9-]+(?:[.][a-z0-9-]+)*):([a-zA-Z0-9._\-/]+):([a-z0-9-]+(?:[.][a-z0-9-]+)*):(?[a-zA-Z0-9._\-/]+)$/; - - let document; - - beforeAll(() => { - document = ord(csn); - }); - - test("Assigned to exactly one CDS Service group", () => { - for (const eventResource of document.eventResources) { - expect(eventResource.partOfGroups.length).toEqual(1); - } - }); - - test("The CDS Service Group ID includes the CDS Service identifier", () => { - for (const eventResource of document.eventResources) { - const [groupId] = eventResource.partOfGroups; - expect(groupId).toMatch(GROUP_ID_REGEX); - - const match = GROUP_ID_REGEX.exec(groupId); - if (match && match.groups?.service) { - let service = match.groups?.service; - if (service.startsWith("undefined")) - service = service.replace("undefined.", ""); - const definition = csn.definitions[service]; - expect(definition).toBeDefined(); - expect(definition.kind).toEqual("service"); - } - } - }); - }); -}); diff --git a/__tests__/ordPackageJson.test.js b/__tests__/ordPackageJson.test.js deleted file mode 100644 index 641ce6f..0000000 --- a/__tests__/ordPackageJson.test.js +++ /dev/null @@ -1,84 +0,0 @@ -const cds = require("@sap/cds"); -const path = require("path"); - -describe("Tests for default ORD document when .cdsrc.json is not present", () => { - let csn, ord; - - beforeAll(async () => { - jest.spyOn(require("../lib/date"), "getRFC3339Date").mockReturnValue("2024-11-04T14:33:25+01:00"); - ord = require("../lib/ord"); - cds.root = path.join(__dirname, "bookshop"); - csn = await cds.load(path.join(cds.root, "srv")); - }); - - afterAll(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - test("Successfully create ORD Documents with defaults", () => { - const document = ord(csn); - expect(document).toMatchSnapshot(); - }); - - describe("apiResources", () => { - // eslint-disable-next-line no-useless-escape - const PACKAGE_ID_REGEX = /^([a-z0-9]+(?:[.][a-z0-9]+)*):(package):([a-zA-Z0-9._\-]+):(v0|v[1-9][0-9]*)$/; - - let document; - - beforeAll(() => { - document = ord(csn); - }); - - test("partOfPackage values are valid ORD IDs ", () => { - for (const apiResource of document.apiResources) { - expect(apiResource.partOfPackage).toMatch(PACKAGE_ID_REGEX); - } - }); - - test("The partOfPackage references an existing package", () => { - for (const apiResource of document.apiResources) { - expect( - document.packages.find( - (pck) => pck.ordId === apiResource.partOfPackage - ) - ).toBeDefined(); - } - }); - }); - - describe("eventResources", () => { - // eslint-disable-next-line no-useless-escape - const GROUP_ID_REGEX = /^([a-z0-9-]+(?:[.][a-z0-9-]+)*):([a-zA-Z0-9._\-/]+):([a-z0-9-]+(?:[.][a-z0-9-]+)*):(?[a-zA-Z0-9._\-/]+)$/; - - let document; - - beforeAll(() => { - document = ord(csn); - }); - - test("Assigned to exactly one CDS Service group", () => { - for (const eventResource of document.eventResources) { - expect(eventResource.partOfGroups.length).toEqual(1); - } - }); - - test("The CDS Service Group ID includes the CDS Service identifier", () => { - for (const eventResource of document.eventResources) { - const [groupId] = eventResource.partOfGroups; - expect(groupId).toMatch(GROUP_ID_REGEX); - - const match = GROUP_ID_REGEX.exec(groupId); - if (match && match.groups?.service) { - let service = match.groups?.service; - if (service.startsWith("undefined")) - service = service.replace("undefined.", ""); - const definition = csn.definitions[service]; - expect(definition).toBeDefined(); - expect(definition.kind).toEqual("service"); - } - } - }); - }); -}); diff --git a/__tests__/protectedServices.test.js b/__tests__/protectedServices.test.js deleted file mode 100644 index b3ef58c..0000000 --- a/__tests__/protectedServices.test.js +++ /dev/null @@ -1,41 +0,0 @@ -const cds = require("@sap/cds"); -const csnInternal = require("./__mocks__/internalResourcesCsn.json"); -const csnPrivate = require("./__mocks__/privateResourcesCsn.json"); -const path = require("path"); - -let ord; -function checkOrdDocument(csn) { - const document = ord(csn); - - expect(document).not.toBeUndefined(); - expect(document.packages).not.toBeDefined(); - expect(document.apiResources).toHaveLength(0); - expect(document.eventResources).toHaveLength(0); -} - -describe("Tests for ORD document when there is no public service", () => { - beforeAll(() => { - cds.root = path.join(__dirname, "bookshop"); - cds.env.ord = { - namespace: "sap.test.cdsrc.sample", - openResourceDiscovery: "1.10", - description: "this is my custom description", - policyLevel: "sap:core:v1" - }; - jest.spyOn(require("../lib/date"), "getRFC3339Date").mockReturnValue("2024-11-04T14:33:25+01:00"); - ord = require("../lib/ord"); - }); - - afterAll(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - test("All services are private: Successfully create ORD Documents without packages, empty apiResources and eventResources lists", () => { - checkOrdDocument(csnPrivate); - }); - - test("All services are internal: Successfully create ORD Documents without packages, empty apiResources and eventResources lists", () => { - checkOrdDocument(csnInternal); - }); -}); diff --git a/__tests__/unittest/__snapshots__/defaults.test.js.snap b/__tests__/unittest/__snapshots__/defaults.test.js.snap index f80260c..6bc962c 100644 --- a/__tests__/unittest/__snapshots__/defaults.test.js.snap +++ b/__tests__/unittest/__snapshots__/defaults.test.js.snap @@ -46,7 +46,7 @@ exports[`defaults packages should return default value if policyLevel contains s "partOfProducts": [ "customer:product:My.Package:", ], - "shortDescription": "Short description for My Package", + "shortDescription": "Short description of My Package", "title": "My Package", "vendor": "customer:vendor:Customer:", "version": "1.0.0", @@ -57,7 +57,29 @@ exports[`defaults packages should return default value if policyLevel contains s "partOfProducts": [ "customer:product:My.Package:", ], - "shortDescription": "Short description for My Package", + "shortDescription": "Short description of My Package", + "title": "My Package", + "vendor": "customer:vendor:Customer:", + "version": "1.0.0", + }, + { + "description": "Description for My Package", + "ordId": "customer.sample:package:MyPackage-integrationDependency:v1", + "partOfProducts": [ + "customer:product:My.Package:", + ], + "shortDescription": "Short description of My Package", + "title": "My Package", + "vendor": "customer:vendor:Customer:", + "version": "1.0.0", + }, + { + "description": "Description for My Package", + "ordId": "customer.sample:package:MyPackage-entityType:v1", + "partOfProducts": [ + "customer:product:My.Package:", + ], + "shortDescription": "Short description of My Package", "title": "My Package", "vendor": "customer:vendor:Customer:", "version": "1.0.0", @@ -73,7 +95,7 @@ exports[`defaults packages should return default value if policyLevel does not c "partOfProducts": [ "customer:product:My.Package:", ], - "shortDescription": "Short description for My Package", + "shortDescription": "Short description of My Package", "title": "My Package", "vendor": "customer:vendor:Customer:", "version": "1.0.0", @@ -87,7 +109,7 @@ exports[`defaults products should return default value 1`] = ` [ { "ordId": "customer:product:My.Product:", - "shortDescription": "Description for My Product", + "shortDescription": "Short description of My Product", "title": "My Product", "vendor": "customer:vendor:customer:", }, diff --git a/__tests__/unittest/__snapshots__/templates.test.js.snap b/__tests__/unittest/__snapshots__/templates.test.js.snap index 7be4bca..1019d37 100644 --- a/__tests__/unittest/__snapshots__/templates.test.js.snap +++ b/__tests__/unittest/__snapshots__/templates.test.js.snap @@ -40,7 +40,7 @@ exports[`templates createAPIResourceTemplate should create API resource template "url": "/.well-known/open-resource-discovery/v1/api-metadata/MyService.edmx", }, ], - "shortDescription": "Short description for MyService", + "shortDescription": "Short description of MyService", "title": "MyService", "version": "1.0.0", "visibility": "public", @@ -48,6 +48,44 @@ exports[`templates createAPIResourceTemplate should create API resource template ] `; +exports[`templates createEntityTypeTemplate should return entity type with default version, title and level:sub-entity 1`] = ` +{ + "description": "Description for SomeAribaDummyEntity", + "extensible": { + "supported": "no", + }, + "lastUpdate": "2022-12-19T15:47:04+00:00", + "level": "sub-entity", + "localId": "SomeAribaDummyEntity", + "ordId": "sap.sm:entityType:SomeAribaDummyEntity:v1", + "partOfPackage": "sap.test.cdsrc.sample:package:test-entityType:v1", + "releaseStatus": "active", + "shortDescription": "Short description of SomeAribaDummyEntity", + "title": "SomeAribaDummyEntity", + "version": "1.0.0", + "visibility": "public", +} +`; + +exports[`templates createEntityTypeTemplate should return entity type with incorrect version, title and level:root-entity 1`] = ` +{ + "description": "Description for SomeAribaDummyEntity", + "extensible": { + "supported": "no", + }, + "lastUpdate": "2022-12-19T15:47:04+00:00", + "level": "root-entity", + "localId": "SomeAribaDummyEntity", + "ordId": "sap.sm:entityType:SomeAribaDummyEntity:v3b", + "partOfPackage": "sap.test.cdsrc.sample:package:test-entityType:v1", + "releaseStatus": "active", + "shortDescription": "Short description of SomeAribaDummyEntity", + "title": "Title of SomeAribaDummyEntity", + "version": "3b.0.0", + "visibility": "public", +} +`; + exports[`templates createEventResourceTemplate should create event resource template correctly 1`] = ` [ { @@ -123,7 +161,11 @@ exports[`templates ordExtension should add apiResources with ORD Extension "visi "description": "Description for MyService", "entityTypeMappings": [ { - "entityTypeTargets": "sap.odm:entityType:test:v1", + "entityTypeTargets": [ + { + "ordId": "sap.odm:entityType:test:v1", + }, + ], }, ], "entryPoints": [ diff --git a/__tests__/unittest/templates.test.js b/__tests__/unittest/templates.test.js index 3d2e098..0a84b33 100644 --- a/__tests__/unittest/templates.test.js +++ b/__tests__/unittest/templates.test.js @@ -1,5 +1,6 @@ const cds = require('@sap/cds'); const { + createEntityTypeTemplate, createEntityTypeMappingsItemTemplate, createGroupsTemplateForService, createAPIResourceTemplate, @@ -8,6 +9,7 @@ const { describe('templates', () => { let linkedModel; + let warningSpy; const appConfig = { ordNamespace: 'customer.testNamespace', @@ -23,13 +25,46 @@ describe('templates', () => { title: String; } `); + warningSpy = jest.spyOn(console, "warn"); }); describe('createEntityTypeMappingsItemTemplate', () => { it('should return default value', () => { - expect(createEntityTypeMappingsItemTemplate(linkedModel)).toEqual({ - ordId: 'sap.odm:entityType:undefined:v1' - }); + expect(createEntityTypeMappingsItemTemplate(linkedModel.definitions['customer.testNamespace123.Books'])).toBeUndefined(); + }); + }); + + describe('createEntityTypeTemplate', () => { + const packageIds = ['sap.test.cdsrc.sample:package:test-entityType:v1']; + it('should return entity type with incorrect version, title and level:root-entity', () => { + const entityWithVersion = { + ordId: "sap.sm:entityType:SomeAribaDummyEntity:v3b", + entityName: "SomeAribaDummyEntity", + "@title": "Title of SomeAribaDummyEntity", + "@ObjectModel.compositionRoot": true, + }; + + const entityType = createEntityTypeTemplate(appConfig, packageIds, entityWithVersion); + expect(entityType).toBeDefined(); + expect(entityType).toMatchSnapshot(); + expect(warningSpy).toHaveBeenCalledTimes(1); + expect(entityType.version).toEqual('3b.0.0'); + expect(entityType.level).toEqual('root-entity'); + expect(entityType.partOfPackage).toEqual('sap.test.cdsrc.sample:package:test-entityType:v1'); + }); + + + it('should return entity type with default version, title and level:sub-entity', () => { + const entityWithoutVersion = { + ordId: "sap.sm:entityType:SomeAribaDummyEntity:v1", + entityName: "SomeAribaDummyEntity" + }; + + const entityType = createEntityTypeTemplate(appConfig, packageIds, entityWithoutVersion); + expect(entityType).toBeDefined(); + expect(entityType).toMatchSnapshot(); + expect(entityType.version).toEqual('1.0.0'); + expect(entityType.level).toEqual('sub-entity'); }); }); @@ -112,7 +147,7 @@ describe('templates', () => { }; `); const srvDefinition = linkedModel.definitions[serviceName]; - appConfig['odmEntities'] = 'sap.odm:entityType:test:v1' + appConfig['entityTypeTargets'] = [{ 'ordId': 'sap.odm:entityType:test:v1' }] const packageIds = ['customer.testNamespace:package:test:v1']; const apiResourceTemplate = createAPIResourceTemplate(serviceName, srvDefinition, appConfig, packageIds); @@ -146,7 +181,7 @@ describe('templates', () => { }; `); const srvDefinition = linkedModel.definitions[serviceName]; - appConfig['odmEntities'] = 'sap.odm:entityType:test:v1' + appConfig['entityTypeTargets'] = [{ 'ordId': 'sap.odm:entityType:test:v1' }] const packageIds = ['customer.testNamespace:package:test:v1']; const apiResourceTemplate = createAPIResourceTemplate(serviceName, srvDefinition, appConfig, packageIds); @@ -181,7 +216,7 @@ describe('templates', () => { }; `); const srvDefinition = linkedModel.definitions[serviceName]; - appConfig['odmEntities'] = 'sap.odm:entityType:test:v1' + appConfig['entityTypeTargets'] = [{ 'ordId': 'sap.odm:entityType:test:v1' }] const packageIds = ['customer.testNamespace:package:test:v1']; const apiResourceTemplate = createAPIResourceTemplate(serviceName, srvDefinition, appConfig, packageIds); diff --git a/lib/constants.js b/lib/constants.js index 7b2761c..b4f3b40 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -19,6 +19,14 @@ const CONTENT_MERGE_KEY = "ordId"; const DESCRIPTION_PREFIX = "Description for "; +const ENTITY_RELATIONSHIP_ANNOTATION = "@EntityRelationship.entityType"; + +const LEVEL = Object.freeze({ + aggregate: "aggregate", + rootEntity: "root-entity", + subEntity: "sub-entity", +}); + const OPEN_RESOURCE_DISCOVERY_VERSION = "1.9"; const ORD_EXTENSIONS_PREFIX = "@ORD.Extensions."; @@ -27,7 +35,9 @@ const ORD_ODM_ENTITY_NAME_ANNOTATION = "@ODM.entityName"; const ORD_RESOURCE_TYPE = Object.freeze({ api: "api", - event: "event" + event: "event", + integrationDependency: "integrationDependency", + entityType: "entityType", }); const RESOURCE_VISIBILITY = Object.freeze({ @@ -36,17 +46,22 @@ const RESOURCE_VISIBILITY = Object.freeze({ private: "private", }); -const SHORT_DESCRIPTION_PREFIX = "Short description for "; +const SEM_VERSION_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; + +const SHORT_DESCRIPTION_PREFIX = "Short description of "; module.exports = { CDS_ELEMENT_KIND, COMPILER_TYPES, CONTENT_MERGE_KEY, DESCRIPTION_PREFIX, + ENTITY_RELATIONSHIP_ANNOTATION, + LEVEL, OPEN_RESOURCE_DISCOVERY_VERSION, ORD_EXTENSIONS_PREFIX, ORD_ODM_ENTITY_NAME_ANNOTATION, ORD_RESOURCE_TYPE, RESOURCE_VISIBILITY, + SEM_VERSION_REGEX, SHORT_DESCRIPTION_PREFIX, }; diff --git a/lib/defaults.js b/lib/defaults.js index 7b585eb..c698f29 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -1,4 +1,8 @@ -const { OPEN_RESOURCE_DISCOVERY_VERSION } = require("./constants"); +const { + DESCRIPTION_PREFIX, + OPEN_RESOURCE_DISCOVERY_VERSION, + SHORT_DESCRIPTION_PREFIX } + = require("./constants"); const regexWithRemoval = (name) => { return name?.replace(/[^a-zA-Z0-9]/g, ""); @@ -6,15 +10,13 @@ const regexWithRemoval = (name) => { const nameWithDot = (name) => { return ( - regexWithRemoval(name.charAt(0)) + - name.slice(1, name.length).replace(/[^a-zA-Z0-9]/g, ".") + regexWithRemoval(name.charAt(0)) + name.slice(1, name.length).replace(/[^a-zA-Z0-9]/g, ".") ); }; const nameWithSpaces = (name) => { return ( - regexWithRemoval(name.charAt(0)) + - name.slice(1, name.length).replace(/[^a-zA-Z0-9]/g, " ") + regexWithRemoval(name.charAt(0)) + name.slice(1, name.length).replace(/[^a-zA-Z0-9]/g, " ") ); }; @@ -34,7 +36,7 @@ module.exports = { { ordId: defaultProductOrdId(name), title: nameWithSpaces(name), - shortDescription: "Description for " + nameWithSpaces(name), + shortDescription: SHORT_DESCRIPTION_PREFIX + nameWithSpaces(name), vendor: "customer:vendor:customer:", }, ], @@ -46,8 +48,8 @@ module.exports = { )}${tag}`, title: nameWithSpaces(name), shortDescription: - "Short description for " + nameWithSpaces(name), - description: "Description for " + nameWithSpaces(name), + SHORT_DESCRIPTION_PREFIX + nameWithSpaces(name), + description: DESCRIPTION_PREFIX + nameWithSpaces(name), version: "1.0.0", partOfProducts: [defaultProductOrdId(name)], vendor: "customer:vendor:Customer:", @@ -58,6 +60,8 @@ module.exports = { return [ createPackage(name, "-api:v1"), createPackage(name, "-event:v1"), + createPackage(name, "-integrationDependency:v1"), + createPackage(name, "-entityType:v1") ]; } else { return [createPackage(name, ":v1")]; diff --git a/lib/ord.js b/lib/ord.js index a217285..07b3cfb 100644 --- a/lib/ord.js +++ b/lib/ord.js @@ -1,10 +1,12 @@ const { CDS_ELEMENT_KIND, CONTENT_MERGE_KEY, + ENTITY_RELATIONSHIP_ANNOTATION, ORD_ODM_ENTITY_NAME_ANNOTATION } = require('./constants'); const { createAPIResourceTemplate, + createEntityTypeTemplate, createEntityTypeMappingsItemTemplate, createEventResourceTemplate, createGroupsTemplateForService @@ -30,9 +32,9 @@ const initializeAppConfig = (csn) => { const modelKeys = Object.keys(csn.definitions); const apiEndpoints = new Set(); const events = []; + const entityTypeTargets = []; const serviceNames = []; const lastUpdate = getRFC3339Date(); - const odmEntities = []; const vendorNamespace = "customer"; const ordNamespace = cds.env["ord"]?.namespace || `${vendorNamespace}.${packageName.replace(/[^a-zA-Z0-9]/g, "")}`; @@ -54,8 +56,8 @@ const initializeAppConfig = (csn) => { case CDS_ELEMENT_KIND.entity: if (!key.includes(".texts")) { apiEndpoints.add(key); - if (keyDefinition[ORD_ODM_ENTITY_NAME_ANNOTATION]) { - odmEntities.push(createEntityTypeMappingsItemTemplate(keyDefinition)); + if (keyDefinition[ORD_ODM_ENTITY_NAME_ANNOTATION] || keyDefinition[ENTITY_RELATIONSHIP_ANNOTATION]) { + entityTypeTargets.push(createEntityTypeMappingsItemTemplate(keyDefinition)); } } break; @@ -76,7 +78,7 @@ const initializeAppConfig = (csn) => { apiEndpoints: Array.from(apiEndpoints), events, serviceNames, - odmEntities: _.uniqBy(odmEntities, CONTENT_MERGE_KEY), + entityTypeTargets: _.uniqBy(entityTypeTargets, CONTENT_MERGE_KEY), ordNamespace, eventApplicationNamespace, packageName, @@ -105,6 +107,13 @@ const _getPackages = (policyLevel, appConfig) => .packages(appConfig.appName, policyLevel, appConfig.ordNamespace) .slice(0, 1); +const _getEntityTypes = (appConfig, packageIds) => { + const internalEntityTypes = appConfig.entityTypeTargets.filter(m => m[ENTITY_RELATIONSHIP_ANNOTATION]); + if (appConfig.entityTypeTargets?.length === 0 || internalEntityTypes.length === 0) return []; + + return internalEntityTypes.map(entity => createEntityTypeTemplate(appConfig, packageIds, entity)); +}; + const _getAPIResources = (csn, appConfig, packageIds) => { if (appConfig.apiEndpoints.length === 0) return []; return appConfig.serviceNames @@ -127,20 +136,6 @@ function _getConsumptionBundles(appConfig) { return appConfig.env?.consumptionBundles || defaults.consumptionBundles(appConfig); } -function validateNamespace(appConfig) { - const validateSystemNamespace = new RegExp(`^${appConfig.eventApplicationNamespace}\\.[^.]+\\..+$`); - if ( - appConfig.ordNamespace === undefined && - !validateSystemNamespace.test(appConfig.ordNamespace) - ) { - let error = new Error( - `Namespace is not defined in cdsrc.json or it is not in the format of ${appConfig.eventApplicationNamespace}..` - ); - Logger.error('Namespace error:', error.message); - throw error; - } -} - function createDefaultORDDocument(linkedCsn, appConfig) { let ordDocument = { $schema: "https://sap.github.io/open-resource-discovery/spec-v1/interfaces/Document.schema.json", @@ -169,10 +164,13 @@ function extractPackageIds(ordDocument) { module.exports = (csn) => { const linkedCsn = cds.linked(csn); const appConfig = initializeAppConfig(linkedCsn); - validateNamespace(appConfig); let ordDocument = createDefaultORDDocument(linkedCsn, appConfig); const packageIds = extractPackageIds(ordDocument); + const entityTypes = _getEntityTypes(appConfig, packageIds); + if (entityTypes.length != 0) { + ordDocument.entityTypes = entityTypes; + } ordDocument.apiResources = _getAPIResources(linkedCsn, appConfig, packageIds); ordDocument.eventResources = _getEventResources(linkedCsn, appConfig, packageIds); ordDocument = extendCustomORDContentIfExists(appConfig, ordDocument); diff --git a/lib/templates.js b/lib/templates.js index da95796..8fb9eb6 100644 --- a/lib/templates.js +++ b/lib/templates.js @@ -3,10 +3,13 @@ const defaults = require("./defaults"); const _ = require("lodash"); const { DESCRIPTION_PREFIX, + ENTITY_RELATIONSHIP_ANNOTATION, + LEVEL, ORD_EXTENSIONS_PREFIX, ORD_ODM_ENTITY_NAME_ANNOTATION, ORD_RESOURCE_TYPE, RESOURCE_VISIBILITY, + SEM_VERSION_REGEX, SHORT_DESCRIPTION_PREFIX } = require("./constants"); const { Logger } = require("./logger"); @@ -68,9 +71,25 @@ const _generatePaths = (srv, srvDefinition) => { * @param {string} entity The entity definition. * @returns {Object} An entry of the entityTypeMappings array. */ -const createEntityTypeMappingsItemTemplate = (entity) => ({ - ordId: `sap.odm:entityType:${entity[ORD_ODM_ENTITY_NAME_ANNOTATION]}:v1`, -}); +const createEntityTypeMappingsItemTemplate = (entity) => { + if (entity[ORD_ODM_ENTITY_NAME_ANNOTATION]) { + return { + ordId: `sap.odm:entityType:${entity[ORD_ODM_ENTITY_NAME_ANNOTATION]}:v1`, + entityName: entity[ORD_ODM_ENTITY_NAME_ANNOTATION], + ...entity + } + } else if (entity[ENTITY_RELATIONSHIP_ANNOTATION]) { + const ordIdParts = entity[ENTITY_RELATIONSHIP_ANNOTATION].split(":"); + const namespace = ordIdParts[0]; + const entityName = ordIdParts[1]; + const version = ordIdParts[2] || "v1"; + return { + ordId: `${namespace}:entityType:${entityName}:${version}`, + entityName, + ...entity + }; + } +}; function _getGroupID( fullyQualifiedName, @@ -96,6 +115,23 @@ function _getTitleFromServiceName(srv) { } } + +/** + * This is a function to get the version of the entity, + * validate it and log if it is not a valid semantical version. + * + * @param {object} entity An entity object. + * @returns Version of the entity with '1.0.0' as fallback value. + */ +function _getEntityVersion(entity) { + const entityVersion = entity.ordId.split(":").pop(); + const version = entityVersion.replace("v", "") + ".0.0"; // TODO: version can be stated/overwritten by annotation + if (!SEM_VERSION_REGEX.test(version)) { + Logger.warn(`Entity version "${version}" is not a valid semantic version.`); + } + return version; +} + /** * This is a template function to create group object of a service for groups array in ORD doc. * @@ -120,6 +156,36 @@ const createGroupsTemplateForService = (serviceName, serviceDefinition, appConfi }; } +/** + * This is a template function to create EntityType object for EntityTypes Array. + * @param { object } appConfig The configuration object. + * @param { Array } packageIds The identifiers of packages. + * @param { object } entity + * @returns { object } An object for the EntityType. + */ +const createEntityTypeTemplate = (appConfig, packageIds, entity) => { + const ordExtensions = readORDExtensions(entity); + + // we collect only entities annotated by @EntityRelationship.entityType,e.g. 'sap.sm:AribaEntity1' + return { + ordId: entity.ordId, + localId: entity.entityName, + title: entity["@title"] ?? entity["@Common.Label"] ?? entity.entityName, + shortDescription: SHORT_DESCRIPTION_PREFIX + entity.entityName, + description: DESCRIPTION_PREFIX + entity.entityName, + version: _getEntityVersion(entity), + lastUpdate: appConfig.lastUpdate, + visibility: RESOURCE_VISIBILITY.public, + partOfPackage: _getPackageID(appConfig.ordNamespace, packageIds, ORD_RESOURCE_TYPE.entityType), + releaseStatus: "active", + level: entity["@ObjectModel.compositionRoot"] || entity["@ODM.root"] ? LEVEL.rootEntity : LEVEL.subEntity, + extensible: { + supported: "no", + }, + ...ordExtensions, + }; +}; + /** * This is a template function to create API Resource object for API Resource Array. * Properties of an API resource can be overwritten by the ORD extensions. Example: visibility. @@ -176,8 +242,19 @@ const createAPIResourceTemplate = (serviceName, serviceDefinition, appConfig, pa extensible: { supported: "no", }, - // conditionally setting the entityTypeMappings field based on odmEntities in appConfig - ...(appConfig.odmEntities?.length > 0 && { entityTypeMappings: [{ entityTypeTargets: appConfig.odmEntities }] }), + // conditionally setting the entityTypeMappings field based on the presence of entityTypeTargets in appConfig + ...(appConfig.entityTypeTargets?.length > 0 && + { + entityTypeMappings: [ + { + entityTypeTargets: appConfig.entityTypeTargets.map(m => { return ( + { + "ordId" : m.ordId + } + )} ) + } + ] + }), ...ordExtensions, }; @@ -242,6 +319,7 @@ function _getPackageID(namespace, packageIds, resourceType) { } module.exports = { + createEntityTypeTemplate, createEntityTypeMappingsItemTemplate, createGroupsTemplateForService, createAPIResourceTemplate, diff --git a/xmpl/db/schema.cds b/xmpl/db/schema.cds new file mode 100644 index 0000000..f4fe8c7 --- /dev/null +++ b/xmpl/db/schema.cds @@ -0,0 +1,21 @@ +namespace sap.cds.demo; + +@ODM.root : true +@ODM.entityName : 'Cinema' +@ODM.oid : 'id' +@title : 'Cinema Title' +entity Cinema { + key id : UUID; + name: String(50); + location: String(100); +} + +@ObjectModel.compositionRoot : true +@EntityRelationship.entityType: 'sap.sample:Movie' +@title : 'Movie Title' +entity Movie { + key id : UUID; + title: String(100); + genre: String(50); + duration: Integer; +} diff --git a/xmpl/ord/custom.ord.json b/xmpl/ord/custom.ord.json index 7ec683f..ec06a24 100644 --- a/xmpl/ord/custom.ord.json +++ b/xmpl/ord/custom.ord.json @@ -3,7 +3,7 @@ "openResourceDiscovery": "should not update since not defined in cdsrc.json", "packages": [ { - "description": "Description for capire bookshop ord sample version 2", + "description": "Description of capire bookshop ord sample version 2", "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-api:v2", "partOfProducts": [ "customer:product:capire.bookshop.ord.sample:" @@ -14,14 +14,47 @@ "version": "2.0.0" }, { + "description": "Description of capire bookshop ord sample version 1.0.1", "ordId": "sap.test.cdsrc.sample:package:capirebookshopordsample-event:v1", - "shortDescription": null, + "shortDescription": "Short description for capire bookshop ord sample version 1.0.1", + "title": "capire bookshop ord sample", + "vendor": "customer:vendor:Customer:", "version": "1.0.1" } ], "apiResources": [ { + "title": "This is the title of Manually Implemented Service", "partOfGroups": null, + "shortDescription": "Short description of Manually Implemented Service", + "description": "Description for Manually Implemented Service", + "visibility": "public", + "releaseStatus": "active", + "version": "1.0.0", + "partOfPackage": "sap.sample:package:capireordsample-api:v1", + "apiProtocol": "odata-v4", + "resourceDefinitions": [ + { + "type": "openapi-v3", + "mediaType": "application/json", + "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.oas3.json", + "accessStrategies": [ + { + "type": "open" + } + ] + }, + { + "type": "edmx", + "mediaType": "application/xml", + "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.edmx", + "accessStrategies": [ + { + "type": "open" + } + ] + } + ], "ordId": "sap.sample:apiResource:manualImplementedService:v1", "entityTypeMappings": [ { @@ -33,7 +66,7 @@ } ], "extensible": { - "supported": "yes" + "supported": "no" } } ] diff --git a/xmpl/srv/services.cds b/xmpl/srv/services.cds index 4e134c3..74db55b 100644 --- a/xmpl/srv/services.cds +++ b/xmpl/srv/services.cds @@ -1,3 +1,4 @@ +using {sap.cds.demo as my} from '../db/schema'; using { ProcessorService, AdminService @@ -25,12 +26,20 @@ extend service ProcessorService { @AsyncAPI.Title : 'SAP Incident Management' @AsyncAPI.SchemaVersion: '1.0' service LocalService { - event TitleChange2 : { + entity Entertainment as projection on my.Cinema; + + entity Film as projection on my.Movie; + + event TitleChange : { ID : Integer; - title : String @title: 'Title'; + title : String @title: 'Changed Title'; } } +annotate LocalService with @ORD.Extensions: { + title : 'This is Local Service title' +}; + annotate AdminService with @ORD.Extensions: { title : 'This is Admin Service title', industry : [