From cc76d9bb7216431af786bd1c78c6e601e82a43ba Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Thu, 14 Nov 2024 11:31:16 +0100 Subject: [PATCH 01/15] initial commit --- lib/defaults.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/defaults.js b/lib/defaults.js index 4f1255e..236f369 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -80,7 +80,7 @@ module.exports = { apiResources: [], eventResources: [], - entityTypes: [], + entityTypes: [], // TODO: Add entity types baseTemplate: { openResourceDiscoveryV1: { documents: [ From 30cc29ec3d22dc250f86c2617e511f677c48ee10 Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Wed, 4 Dec 2024 11:26:12 +0100 Subject: [PATCH 02/15] construct entityTypes and entityTypeMappings arrays --- .../localAndNonODMReferencedEntities.json | 121 ++++++++++++++++++ .../__snapshots__/noOrdInCdsrc.test.js.snap | 13 +- __tests__/__snapshots__/ordCdsrc.test.js.snap | 27 +++- .../__snapshots__/ordPackageJson.test.js.snap | 5 +- __tests__/entityTypesAndMappings.test.js | 40 ++++++ .../__snapshots__/defaults.test.js.snap | 30 ++++- .../__snapshots__/templates.test.js.snap | 6 +- __tests__/unittest/templates.test.js | 10 +- lib/constants.js | 9 +- lib/defaults.js | 24 ++-- lib/ord.js | 21 ++- lib/templates.js | 53 +++++++- 12 files changed, 314 insertions(+), 45 deletions(-) create mode 100644 __tests__/__mocks__/localAndNonODMReferencedEntities.json create mode 100644 __tests__/entityTypesAndMappings.test.js diff --git a/__tests__/__mocks__/localAndNonODMReferencedEntities.json b/__tests__/__mocks__/localAndNonODMReferencedEntities.json new file mode 100644 index 0000000..7f40b5d --- /dev/null +++ b/__tests__/__mocks__/localAndNonODMReferencedEntities.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__/noOrdInCdsrc.test.js.snap index 5afac3e..9a30725 100644 --- a/__tests__/__snapshots__/noOrdInCdsrc.test.js.snap +++ b/__tests__/__snapshots__/noOrdInCdsrc.test.js.snap @@ -60,9 +60,7 @@ exports[`Tests for ORD document when .cdsrc.json has no \`ord\` property Success "entityTypeMappings": [ { "entityTypeTargets": [ - { - "ordId": "sap.odm:entityType:odm.bookshop.Authors:v1", - }, + "sap.odm:entityType:odm.bookshop.Authors:v1", ], }, ], @@ -112,9 +110,7 @@ exports[`Tests for ORD document when .cdsrc.json has no \`ord\` property Success "entityTypeMappings": [ { "entityTypeTargets": [ - { - "ordId": "sap.odm:entityType:odm.bookshop.Authors:v1", - }, + "sap.odm:entityType:odm.bookshop.Authors:v1", ], }, ], @@ -170,6 +166,7 @@ exports[`Tests for ORD document when .cdsrc.json has no \`ord\` property Success }, ], "description": "this is an application description", + "entityTypes": [], "eventResources": [ { "description": "CAP Event resource describing events / messages.", @@ -291,7 +288,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 +298,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__/ordCdsrc.test.js.snap b/__tests__/__snapshots__/ordCdsrc.test.js.snap index 1449d55..1400c62 100644 --- a/__tests__/__snapshots__/ordCdsrc.test.js.snap +++ b/__tests__/__snapshots__/ordCdsrc.test.js.snap @@ -91,6 +91,7 @@ exports[`Tests for default ORD document when .cdsrc.json is present Successfully }, ], "description": "this is my custom description", + "entityTypes": [], "eventResources": [ { "description": "CAP Event resource describing events / messages.", @@ -154,7 +155,7 @@ exports[`Tests for default ORD document when .cdsrc.json is present Successfully "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", @@ -169,6 +170,28 @@ exports[`Tests for default ORD document when .cdsrc.json is present Successfully "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", @@ -185,7 +208,7 @@ exports[`Tests for default ORD document when .cdsrc.json is present Successfully "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__/ordPackageJson.test.js.snap b/__tests__/__snapshots__/ordPackageJson.test.js.snap index 7c72444..e1fdf1a 100644 --- a/__tests__/__snapshots__/ordPackageJson.test.js.snap +++ b/__tests__/__snapshots__/ordPackageJson.test.js.snap @@ -66,6 +66,7 @@ exports[`Tests for default ORD document when .cdsrc.json is not present Successf }, ], "description": "this is an application description", + "entityTypes": [], "eventResources": [ { "description": "CAP Event resource describing events / messages.", @@ -129,7 +130,7 @@ exports[`Tests for default ORD document when .cdsrc.json is not present Successf "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", @@ -139,7 +140,7 @@ exports[`Tests for default ORD document when .cdsrc.json is not present Successf "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__/entityTypesAndMappings.test.js b/__tests__/entityTypesAndMappings.test.js new file mode 100644 index 0000000..52fedc5 --- /dev/null +++ b/__tests__/entityTypesAndMappings.test.js @@ -0,0 +1,40 @@ +const cds = require("@sap/cds"); +const csn = require("./__mocks__/localAndNonODMReferencedEntities.json"); +const path = require("path"); + +describe("Tests for ORD document checking if entityTypes and entityTypeMappings are generated correctly", () => { + 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 = { + "ord": { + "namespace": "sap.sm", + "openResourceDiscovery": "1.9", + "description": "this is my custom description", + "policyLevel": "sap:core:v1" + } + }; + }); + + afterAll(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + }); + + test("Successfully create ORD Document: entityTypes with local entities and entityTypeMappings containing referenced entities", () => { + const document = ord(csn); + + // ...(appConfig.entityTypeMappings?.length > 0 && { entityTypeMappings: [{ entityTypeTargets: appConfig.entityTypeTargets.map(m => m.ordId) }] }), + + 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([ + "sap.odm:entityType:SomeODMEntity:v1", + "sap.sm:entityType:SomeAribaDummyEntity:v1" + ])); + }); +}); \ No newline at end of file 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..6539572 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", @@ -123,7 +123,9 @@ exports[`templates ordExtension should add apiResources with ORD Extension "visi "description": "Description for MyService", "entityTypeMappings": [ { - "entityTypeTargets": "sap.odm:entityType:test:v1", + "entityTypeTargets": [ + "sap.odm:entityType:test:v1", + ], }, ], "entryPoints": [ diff --git a/__tests__/unittest/templates.test.js b/__tests__/unittest/templates.test.js index 3d2e098..16fb835 100644 --- a/__tests__/unittest/templates.test.js +++ b/__tests__/unittest/templates.test.js @@ -27,9 +27,7 @@ describe('templates', () => { 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(); }); }); @@ -112,7 +110,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 +144,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 +179,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..3e75a01 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -19,6 +19,8 @@ const CONTENT_MERGE_KEY = "ordId"; const DESCRIPTION_PREFIX = "Description for "; +const ENTITY_RELATIONSHIP_ANNOTATION = "@EntityRelationship.entityType"; + const OPEN_RESOURCE_DISCOVERY_VERSION = "1.9"; const ORD_EXTENSIONS_PREFIX = "@ORD.Extensions."; @@ -27,7 +29,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,13 +40,14 @@ const RESOURCE_VISIBILITY = Object.freeze({ private: "private", }); -const SHORT_DESCRIPTION_PREFIX = "Short description for "; +const SHORT_DESCRIPTION_PREFIX = "Short description of "; module.exports = { CDS_ELEMENT_KIND, COMPILER_TYPES, CONTENT_MERGE_KEY, DESCRIPTION_PREFIX, + ENTITY_RELATIONSHIP_ANNOTATION, OPEN_RESOURCE_DISCOVERY_VERSION, ORD_EXTENSIONS_PREFIX, ORD_ODM_ENTITY_NAME_ANNOTATION, diff --git a/lib/defaults.js b/lib/defaults.js index 8dc84ac..c698f29 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -1,20 +1,22 @@ -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, ""); + return name?.replace(/[^a-zA-Z0-9]/g, ""); }; 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")]; @@ -78,7 +82,7 @@ module.exports = { apiResources: [], eventResources: [], - entityTypes: [], // TODO: Add entity types + entityTypes: [], baseTemplate: { openResourceDiscoveryV1: { documents: [ diff --git a/lib/ord.js b/lib/ord.js index a217285..516cdbe 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,16 @@ const _getPackages = (policyLevel, appConfig) => .packages(appConfig.appName, policyLevel, appConfig.ordNamespace) .slice(0, 1); +const _getEntityTypes = (appConfig, packageIds) => { + if (appConfig.entityTypeTargets?.length === 0 || + appConfig.entityTypeTargets.filter(m => m[ENTITY_RELATIONSHIP_ANNOTATION]).length === 0) return []; + + const internalEntityTypes = appConfig.entityTypeTargets.filter(m => m[ENTITY_RELATIONSHIP_ANNOTATION]); + return internalEntityTypes.map((entity) => { + return createEntityTypeTemplate(appConfig, packageIds, entity); + }); +}; + const _getAPIResources = (csn, appConfig, packageIds) => { if (appConfig.apiEndpoints.length === 0) return []; return appConfig.serviceNames @@ -173,6 +185,7 @@ module.exports = (csn) => { let ordDocument = createDefaultORDDocument(linkedCsn, appConfig); const packageIds = extractPackageIds(ordDocument); + ordDocument.entityTypes = _getEntityTypes(appConfig, packageIds); 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 675f39e..a34e047 100644 --- a/lib/templates.js +++ b/lib/templates.js @@ -3,6 +3,7 @@ const defaults = require("./defaults"); const _ = require("lodash"); const { DESCRIPTION_PREFIX, + ENTITY_RELATIONSHIP_ANNOTATION, ORD_EXTENSIONS_PREFIX, ORD_ODM_ENTITY_NAME_ANNOTATION, ORD_RESOURCE_TYPE, @@ -68,9 +69,23 @@ 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 entityName = ordIdParts[1]; + return { + ordId: `${ordIdParts[0]}:entityType:${entityName}:${ordIdParts[2] || "v1"}`, + entityName, + ...entity + }; + } +}; function _getGroupID( fullyQualifiedName, @@ -120,6 +135,33 @@ 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) => { + // 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: "1.0.0", + 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"] ? "root-entity" : "sub-entity", // TODO + extensible: { + supported: "no", + } + }; +}; + /** * 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 +218,8 @@ 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 => m.ordId) }] }), ...ordExtensions, }; @@ -242,6 +284,7 @@ function _getPackageID(namespace, packageIds, resourceType) { } module.exports = { + createEntityTypeTemplate, createEntityTypeMappingsItemTemplate, createGroupsTemplateForService, createAPIResourceTemplate, From b157ed4611b56de014e702ae3874cac64e9e721b Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Wed, 4 Dec 2024 12:36:31 +0100 Subject: [PATCH 03/15] Fix entityTypeTargets to be array of objects and not strings --- __tests__/__snapshots__/noOrdInCdsrc.test.js.snap | 8 ++++++-- __tests__/entityTypesAndMappings.test.js | 4 ++-- .../unittest/__snapshots__/templates.test.js.snap | 4 +++- lib/templates.js | 13 ++++++++++++- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/__tests__/__snapshots__/noOrdInCdsrc.test.js.snap b/__tests__/__snapshots__/noOrdInCdsrc.test.js.snap index 9a30725..476b1e8 100644 --- a/__tests__/__snapshots__/noOrdInCdsrc.test.js.snap +++ b/__tests__/__snapshots__/noOrdInCdsrc.test.js.snap @@ -60,7 +60,9 @@ exports[`Tests for ORD document when .cdsrc.json has no \`ord\` property Success "entityTypeMappings": [ { "entityTypeTargets": [ - "sap.odm:entityType:odm.bookshop.Authors:v1", + { + "ordId": "sap.odm:entityType:odm.bookshop.Authors:v1", + }, ], }, ], @@ -110,7 +112,9 @@ exports[`Tests for ORD document when .cdsrc.json has no \`ord\` property Success "entityTypeMappings": [ { "entityTypeTargets": [ - "sap.odm:entityType:odm.bookshop.Authors:v1", + { + "ordId": "sap.odm:entityType:odm.bookshop.Authors:v1", + }, ], }, ], diff --git a/__tests__/entityTypesAndMappings.test.js b/__tests__/entityTypesAndMappings.test.js index 52fedc5..20b1313 100644 --- a/__tests__/entityTypesAndMappings.test.js +++ b/__tests__/entityTypesAndMappings.test.js @@ -33,8 +33,8 @@ describe("Tests for ORD document checking if entityTypes and entityTypeMappings 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([ - "sap.odm:entityType:SomeODMEntity:v1", - "sap.sm:entityType:SomeAribaDummyEntity:v1" + {"ordId": "sap.odm:entityType:SomeODMEntity:v1"}, + {"ordId": "sap.sm:entityType:SomeAribaDummyEntity:v1"} ])); }); }); \ No newline at end of file diff --git a/__tests__/unittest/__snapshots__/templates.test.js.snap b/__tests__/unittest/__snapshots__/templates.test.js.snap index 6539572..b03ac62 100644 --- a/__tests__/unittest/__snapshots__/templates.test.js.snap +++ b/__tests__/unittest/__snapshots__/templates.test.js.snap @@ -124,7 +124,9 @@ exports[`templates ordExtension should add apiResources with ORD Extension "visi "entityTypeMappings": [ { "entityTypeTargets": [ - "sap.odm:entityType:test:v1", + { + "ordId": "sap.odm:entityType:test:v1", + }, ], }, ], diff --git a/lib/templates.js b/lib/templates.js index a34e047..aef5023 100644 --- a/lib/templates.js +++ b/lib/templates.js @@ -219,7 +219,18 @@ const createAPIResourceTemplate = (serviceName, serviceDefinition, appConfig, pa supported: "no", }, // conditionally setting the entityTypeMappings field based on the presence of entityTypeTargets in appConfig - ...(appConfig.entityTypeTargets?.length > 0 && { entityTypeMappings: [{ entityTypeTargets: appConfig.entityTypeTargets.map(m => m.ordId) }] }), + ...(appConfig.entityTypeTargets?.length > 0 && + { + entityTypeMappings: [ + { + entityTypeTargets: appConfig.entityTypeTargets.map(m => { return ( + { + "ordId" : m.ordId + } + )} ) + } + ] + }), ...ordExtensions, }; From 44031df2ba194734484b1895877018a2d8857997 Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Wed, 4 Dec 2024 12:53:44 +0100 Subject: [PATCH 04/15] check existance of @ORDExtensions on entity level --- lib/templates.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/templates.js b/lib/templates.js index aef5023..d9882c1 100644 --- a/lib/templates.js +++ b/lib/templates.js @@ -143,6 +143,7 @@ const createGroupsTemplateForService = (serviceName, serviceDefinition, appConfi * @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, @@ -158,7 +159,8 @@ const createEntityTypeTemplate = (appConfig, packageIds, entity) => { level: entity["@ObjectModel.compositionRoot"] || entity["@ODM.root"] ? "root-entity" : "sub-entity", // TODO extensible: { supported: "no", - } + }, + ...ordExtensions, }; }; From 02c5e6f109cd68689c163e54f4315eaa83f26f80 Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Wed, 4 Dec 2024 13:50:15 +0100 Subject: [PATCH 05/15] change schema and service in the xmpl folder so the sample ord document is valid --- xmpl/db/schema.cds | 20 ++++++++++++++++++++ xmpl/ord/custom.ord.json | 39 ++++++++++++++++++++++++++++++++++++--- xmpl/srv/services.cds | 9 +++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 xmpl/db/schema.cds diff --git a/xmpl/db/schema.cds b/xmpl/db/schema.cds new file mode 100644 index 0000000..5f4f3f7 --- /dev/null +++ b/xmpl/db/schema.cds @@ -0,0 +1,20 @@ +namespace sap.cds.demo; + +@ODM.root : true +@ODM.entityName : 'SomeODMEntity' +@ODM.oid : 'id' +@title : 'Dummy title of Entity with corresponding ODM entity title' +entity EntityWithCorrespondingODMEntity { + key id : UUID; + propertyA: String(10); + propertyB: String(20); +} + +@ObjectModel.compositionRoot : true +@EntityRelationship.entityType: 'sap.sample:SomeAribaDummyEntity' +@title : 'Dummy title of Ariba entity' +entity SomeAribaEntity { + key id : UUID; + propertyC: String(10); + propertyD: String(20); +} 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..8a44ccd 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 { + entity DummyEntityA as projection on my.EntityWithCorrespondingODMEntity; + + entity DummyEntityB as projection on my.SomeAribaEntity; + event TitleChange2 : { ID : Integer; title : String @title: 'Title'; } } +annotate LocalService with @ORD.Extensions: { + title : 'This is Local Service title' +}; + annotate AdminService with @ORD.Extensions: { title : 'This is Admin Service title', industry : [ From b232ba89aee97de5d77ba98c7efe7bce9f414b59 Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Thu, 5 Dec 2024 12:18:45 +0100 Subject: [PATCH 06/15] handle entity type version --- .../__snapshots__/noOrdInCdsrc.test.js.snap | 1 - __tests__/__snapshots__/ordCdsrc.test.js.snap | 1 - .../__snapshots__/ordPackageJson.test.js.snap | 1 - __tests__/entityTypesAndMappings.test.js | 2 - .../__snapshots__/templates.test.js.snap | 38 +++++++++++++++++++ __tests__/unittest/templates.test.js | 37 ++++++++++++++++++ lib/constants.js | 3 ++ lib/ord.js | 10 +++-- lib/templates.js | 21 +++++++++- 9 files changed, 104 insertions(+), 10 deletions(-) diff --git a/__tests__/__snapshots__/noOrdInCdsrc.test.js.snap b/__tests__/__snapshots__/noOrdInCdsrc.test.js.snap index 476b1e8..31a3dd1 100644 --- a/__tests__/__snapshots__/noOrdInCdsrc.test.js.snap +++ b/__tests__/__snapshots__/noOrdInCdsrc.test.js.snap @@ -170,7 +170,6 @@ exports[`Tests for ORD document when .cdsrc.json has no \`ord\` property Success }, ], "description": "this is an application description", - "entityTypes": [], "eventResources": [ { "description": "CAP Event resource describing events / messages.", diff --git a/__tests__/__snapshots__/ordCdsrc.test.js.snap b/__tests__/__snapshots__/ordCdsrc.test.js.snap index 1400c62..c26c38a 100644 --- a/__tests__/__snapshots__/ordCdsrc.test.js.snap +++ b/__tests__/__snapshots__/ordCdsrc.test.js.snap @@ -91,7 +91,6 @@ exports[`Tests for default ORD document when .cdsrc.json is present Successfully }, ], "description": "this is my custom description", - "entityTypes": [], "eventResources": [ { "description": "CAP Event resource describing events / messages.", diff --git a/__tests__/__snapshots__/ordPackageJson.test.js.snap b/__tests__/__snapshots__/ordPackageJson.test.js.snap index e1fdf1a..16b943d 100644 --- a/__tests__/__snapshots__/ordPackageJson.test.js.snap +++ b/__tests__/__snapshots__/ordPackageJson.test.js.snap @@ -66,7 +66,6 @@ exports[`Tests for default ORD document when .cdsrc.json is not present Successf }, ], "description": "this is an application description", - "entityTypes": [], "eventResources": [ { "description": "CAP Event resource describing events / messages.", diff --git a/__tests__/entityTypesAndMappings.test.js b/__tests__/entityTypesAndMappings.test.js index 20b1313..a062da0 100644 --- a/__tests__/entityTypesAndMappings.test.js +++ b/__tests__/entityTypesAndMappings.test.js @@ -26,8 +26,6 @@ describe("Tests for ORD document checking if entityTypes and entityTypeMappings test("Successfully create ORD Document: entityTypes with local entities and entityTypeMappings containing referenced entities", () => { const document = ord(csn); - // ...(appConfig.entityTypeMappings?.length > 0 && { entityTypeMappings: [{ entityTypeTargets: appConfig.entityTypeTargets.map(m => m.ordId) }] }), - expect(document).not.toBeUndefined(); expect(document.entityTypes).toHaveLength(1); expect(document.entityTypes[0].partOfPackage).toEqual(expect.stringContaining("entityType")); diff --git a/__tests__/unittest/__snapshots__/templates.test.js.snap b/__tests__/unittest/__snapshots__/templates.test.js.snap index b03ac62..6ba86c5 100644 --- a/__tests__/unittest/__snapshots__/templates.test.js.snap +++ b/__tests__/unittest/__snapshots__/templates.test.js.snap @@ -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:v1.2b.3", + "partOfPackage": "sap.test.cdsrc.sample:package:test-entityType:v1", + "releaseStatus": "active", + "shortDescription": "Short description of SomeAribaDummyEntity", + "title": "Title of SomeAribaDummyEntity", + "version": "v1.2b.3", + "visibility": "public", +} +`; + exports[`templates createEventResourceTemplate should create event resource template correctly 1`] = ` [ { diff --git a/__tests__/unittest/templates.test.js b/__tests__/unittest/templates.test.js index 16fb835..6abfe8d 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,6 +25,7 @@ describe('templates', () => { title: String; } `); + warningSpy = jest.spyOn(console, "warn"); }); describe('createEntityTypeMappingsItemTemplate', () => { @@ -31,6 +34,40 @@ describe('templates', () => { }); }); + 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:v1.2b.3", + 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('v1.2b.3'); + 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'); + }); + }); + describe('createGroupsTemplateForService', () => { it('should return default value when groupIds do not have groupId', () => { const testServiceName = 'testServiceName'; diff --git a/lib/constants.js b/lib/constants.js index 3e75a01..150b929 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -40,6 +40,8 @@ const RESOURCE_VISIBILITY = Object.freeze({ private: "private", }); +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 = { @@ -53,5 +55,6 @@ module.exports = { ORD_ODM_ENTITY_NAME_ANNOTATION, ORD_RESOURCE_TYPE, RESOURCE_VISIBILITY, + SEM_VERSION_REGEX, SHORT_DESCRIPTION_PREFIX, }; diff --git a/lib/ord.js b/lib/ord.js index 516cdbe..59a342a 100644 --- a/lib/ord.js +++ b/lib/ord.js @@ -108,10 +108,9 @@ const _getPackages = (policyLevel, appConfig) => .slice(0, 1); const _getEntityTypes = (appConfig, packageIds) => { - if (appConfig.entityTypeTargets?.length === 0 || - appConfig.entityTypeTargets.filter(m => m[ENTITY_RELATIONSHIP_ANNOTATION]).length === 0) return []; - const internalEntityTypes = appConfig.entityTypeTargets.filter(m => m[ENTITY_RELATIONSHIP_ANNOTATION]); + if (appConfig.entityTypeTargets?.length === 0 || internalEntityTypes.length === 0) return []; + return internalEntityTypes.map((entity) => { return createEntityTypeTemplate(appConfig, packageIds, entity); }); @@ -185,7 +184,10 @@ module.exports = (csn) => { let ordDocument = createDefaultORDDocument(linkedCsn, appConfig); const packageIds = extractPackageIds(ordDocument); - ordDocument.entityTypes = _getEntityTypes(appConfig, packageIds); + 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 d9882c1..33e371e 100644 --- a/lib/templates.js +++ b/lib/templates.js @@ -8,6 +8,7 @@ const { ORD_ODM_ENTITY_NAME_ANNOTATION, ORD_RESOURCE_TYPE, RESOURCE_VISIBILITY, + SEM_VERSION_REGEX, SHORT_DESCRIPTION_PREFIX } = require("./constants"); const { Logger } = require("./logger"); @@ -111,6 +112,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 === "v1" ? "1.0.0" : entityVersion; + 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. * @@ -144,6 +162,7 @@ const createGroupsTemplateForService = (serviceName, serviceDefinition, appConfi */ 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, @@ -151,7 +170,7 @@ const createEntityTypeTemplate = (appConfig, packageIds, entity) => { title: entity["@title"] ?? entity["@Common.Label"] ?? entity.entityName, shortDescription: SHORT_DESCRIPTION_PREFIX + entity.entityName, description: DESCRIPTION_PREFIX + entity.entityName, - version: "1.0.0", + version: _getEntityVersion(entity), lastUpdate: appConfig.lastUpdate, visibility: RESOURCE_VISIBILITY.public, partOfPackage: _getPackageID(appConfig.ordNamespace, packageIds, ORD_RESOURCE_TYPE.entityType), From 80c7ccc142614275a779e2cbd0c23eae72289117 Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Thu, 5 Dec 2024 15:59:46 +0100 Subject: [PATCH 07/15] tests clean-up --- ... localAndNonODMReferencedEntitiesCsn.json} | 0 ...rc.test.js.snap => mockedCsn.test.js.snap} | 2 +- __tests__/__snapshots__/ord.e2e.test.js.snap | 431 ++++++++++++++++++ __tests__/__snapshots__/ordCdsrc.test.js.snap | 216 --------- .../__snapshots__/ordPackageJson.test.js.snap | 148 ------ __tests__/entityTypesAndMappings.test.js | 38 -- __tests__/mockedCsn.test.js | 103 +++++ __tests__/noApisOrNoEvents.test.js | 33 -- .../noEntitiesInServiceDefinition.test.js | 29 -- __tests__/noOrdInCdsrc.test.js | 26 -- __tests__/ord.e2e.test.js | 169 +++++++ __tests__/ordCdsrc.test.js | 84 ---- __tests__/ordPackageJson.test.js | 84 ---- __tests__/protectedServices.test.js | 41 -- 14 files changed, 704 insertions(+), 700 deletions(-) rename __tests__/__mocks__/{localAndNonODMReferencedEntities.json => localAndNonODMReferencedEntitiesCsn.json} (100%) rename __tests__/__snapshots__/{noOrdInCdsrc.test.js.snap => mockedCsn.test.js.snap} (98%) 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 delete mode 100644 __tests__/entityTypesAndMappings.test.js 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 diff --git a/__tests__/__mocks__/localAndNonODMReferencedEntities.json b/__tests__/__mocks__/localAndNonODMReferencedEntitiesCsn.json similarity index 100% rename from __tests__/__mocks__/localAndNonODMReferencedEntities.json rename to __tests__/__mocks__/localAndNonODMReferencedEntitiesCsn.json diff --git a/__tests__/__snapshots__/noOrdInCdsrc.test.js.snap b/__tests__/__snapshots__/mockedCsn.test.js.snap similarity index 98% rename from __tests__/__snapshots__/noOrdInCdsrc.test.js.snap rename to __tests__/__snapshots__/mockedCsn.test.js.snap index 31a3dd1..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": [ 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 c26c38a..0000000 --- a/__tests__/__snapshots__/ordCdsrc.test.js.snap +++ /dev/null @@ -1,216 +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 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__/ordPackageJson.test.js.snap b/__tests__/__snapshots__/ordPackageJson.test.js.snap deleted file mode 100644 index 16b943d..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 of 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": "Short description of capire bookshop ord sample", - "title": "capire bookshop ord sample", - "vendor": "customer:vendor:customer:", - }, - ], -} -`; diff --git a/__tests__/entityTypesAndMappings.test.js b/__tests__/entityTypesAndMappings.test.js deleted file mode 100644 index a062da0..0000000 --- a/__tests__/entityTypesAndMappings.test.js +++ /dev/null @@ -1,38 +0,0 @@ -const cds = require("@sap/cds"); -const csn = require("./__mocks__/localAndNonODMReferencedEntities.json"); -const path = require("path"); - -describe("Tests for ORD document checking if entityTypes and entityTypeMappings are generated correctly", () => { - 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 = { - "ord": { - "namespace": "sap.sm", - "openResourceDiscovery": "1.9", - "description": "this is my custom description", - "policyLevel": "sap:core:v1" - } - }; - }); - - afterAll(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - test("Successfully create ORD Document: entityTypes with local entities and entityTypeMappings containing referenced entities", () => { - 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"} - ])); - }); -}); \ No newline at end of file diff --git a/__tests__/mockedCsn.test.js b/__tests__/mockedCsn.test.js new file mode 100644 index 0000000..713ce62 --- /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.clearAllMocks(); + jest.resetAllMocks(); + }); + + 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..3b2cfc6 --- /dev/null +++ b/__tests__/ord.e2e.test.js @@ -0,0 +1,169 @@ +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"); + }); + + 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"); + } + } + }); + }); + }); + + 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); - }); -}); From 96fef2a1180ba5adc02f037681cf4dec334d51b3 Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Thu, 5 Dec 2024 16:06:13 +0100 Subject: [PATCH 08/15] minor change:rewrite the map statement --- lib/ord.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/ord.js b/lib/ord.js index 59a342a..bd6db03 100644 --- a/lib/ord.js +++ b/lib/ord.js @@ -111,9 +111,7 @@ 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) => { - return createEntityTypeTemplate(appConfig, packageIds, entity); - }); + return internalEntityTypes.map(entity => createEntityTypeTemplate(appConfig, packageIds, entity)); }; const _getAPIResources = (csn, appConfig, packageIds) => { From 6a456b0262b9d2fd5b939074706d2cf72d79e9dd Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Thu, 5 Dec 2024 16:12:10 +0100 Subject: [PATCH 09/15] introduct LEVEL constant --- lib/constants.js | 7 +++++++ lib/templates.js | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/constants.js b/lib/constants.js index 150b929..b4f3b40 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -21,6 +21,12 @@ 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."; @@ -50,6 +56,7 @@ module.exports = { CONTENT_MERGE_KEY, DESCRIPTION_PREFIX, ENTITY_RELATIONSHIP_ANNOTATION, + LEVEL, OPEN_RESOURCE_DISCOVERY_VERSION, ORD_EXTENSIONS_PREFIX, ORD_ODM_ENTITY_NAME_ANNOTATION, diff --git a/lib/templates.js b/lib/templates.js index 33e371e..b8b603c 100644 --- a/lib/templates.js +++ b/lib/templates.js @@ -4,6 +4,7 @@ const _ = require("lodash"); const { DESCRIPTION_PREFIX, ENTITY_RELATIONSHIP_ANNOTATION, + LEVEL, ORD_EXTENSIONS_PREFIX, ORD_ODM_ENTITY_NAME_ANNOTATION, ORD_RESOURCE_TYPE, @@ -175,7 +176,7 @@ const createEntityTypeTemplate = (appConfig, packageIds, entity) => { visibility: RESOURCE_VISIBILITY.public, partOfPackage: _getPackageID(appConfig.ordNamespace, packageIds, ORD_RESOURCE_TYPE.entityType), releaseStatus: "active", - level: entity["@ObjectModel.compositionRoot"] || entity["@ODM.root"] ? "root-entity" : "sub-entity", // TODO + level: entity["@ObjectModel.compositionRoot"] || entity["@ODM.root"] ? LEVEL.rootEntity : LEVEL.subEntity, extensible: { supported: "no", }, From 05a373619eeec4ecb7de0a9a493020528d28e55c Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Thu, 5 Dec 2024 16:46:40 +0100 Subject: [PATCH 10/15] rename sample entities in xmpl app --- xmpl/db/schema.cds | 21 +++++++++++---------- xmpl/srv/services.cds | 8 ++++---- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/xmpl/db/schema.cds b/xmpl/db/schema.cds index 5f4f3f7..f4fe8c7 100644 --- a/xmpl/db/schema.cds +++ b/xmpl/db/schema.cds @@ -1,20 +1,21 @@ namespace sap.cds.demo; @ODM.root : true -@ODM.entityName : 'SomeODMEntity' +@ODM.entityName : 'Cinema' @ODM.oid : 'id' -@title : 'Dummy title of Entity with corresponding ODM entity title' -entity EntityWithCorrespondingODMEntity { +@title : 'Cinema Title' +entity Cinema { key id : UUID; - propertyA: String(10); - propertyB: String(20); + name: String(50); + location: String(100); } @ObjectModel.compositionRoot : true -@EntityRelationship.entityType: 'sap.sample:SomeAribaDummyEntity' -@title : 'Dummy title of Ariba entity' -entity SomeAribaEntity { +@EntityRelationship.entityType: 'sap.sample:Movie' +@title : 'Movie Title' +entity Movie { key id : UUID; - propertyC: String(10); - propertyD: String(20); + title: String(100); + genre: String(50); + duration: Integer; } diff --git a/xmpl/srv/services.cds b/xmpl/srv/services.cds index 8a44ccd..74db55b 100644 --- a/xmpl/srv/services.cds +++ b/xmpl/srv/services.cds @@ -26,13 +26,13 @@ extend service ProcessorService { @AsyncAPI.Title : 'SAP Incident Management' @AsyncAPI.SchemaVersion: '1.0' service LocalService { - entity DummyEntityA as projection on my.EntityWithCorrespondingODMEntity; + entity Entertainment as projection on my.Cinema; - entity DummyEntityB as projection on my.SomeAribaEntity; + entity Film as projection on my.Movie; - event TitleChange2 : { + event TitleChange : { ID : Integer; - title : String @title: 'Title'; + title : String @title: 'Changed Title'; } } From a5fdaee97af3c8058c2b2cfe97170cef44abd6ea Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Mon, 9 Dec 2024 10:03:13 +0100 Subject: [PATCH 11/15] change the version handling --- __tests__/mockedCsn.test.js | 13 +++++++++++-- lib/ord.js | 4 ++-- lib/templates.js | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/__tests__/mockedCsn.test.js b/__tests__/mockedCsn.test.js index 713ce62..d79389b 100644 --- a/__tests__/mockedCsn.test.js +++ b/__tests__/mockedCsn.test.js @@ -2,7 +2,7 @@ const cds = require("@sap/cds"); const path = require("path"); describe("Tests for ORD document generated out of mocked csn files", () => { - let ord; + let ord; //, errorSpy; function checkOrdDocument(csn) { const document = ord(csn); @@ -26,11 +26,12 @@ describe("Tests for ORD document generated out of mocked csn files", () => { description: "this is my custom description", policyLevel: "sap:core:v1" }; + // errorSpy = jest.spyOn(console, "error"); }); afterAll(() => { + jest.restoreAllMocks(); jest.clearAllMocks(); - jest.resetAllMocks(); }); describe("Tests for ORD document when .cdsrc.json has no `ord` property", () => { @@ -100,4 +101,12 @@ describe("Tests for ORD document generated out of mocked csn files", () => { expect(document.eventResources[0].ordId).toEqual(expect.stringContaining("CatalogService")); }); }); + + describe("Tests for ORD document when namespace is not correctly defined", () => { + test("Namespace is not correctly defined in cdsrc.json: throw error", () => { + cds.env.ord.namespace = "invalid_namespace"; + const csn = require("./__mocks__/publicResourcesCsn.json"); + expect(() => ord(csn)).toThrowError(expect.objectContaining({"message": expect.stringContaining("Namespace is not correctly defined in cdsrc.json")})); + }); + }); }); \ No newline at end of file diff --git a/lib/ord.js b/lib/ord.js index bd6db03..edcd44a 100644 --- a/lib/ord.js +++ b/lib/ord.js @@ -139,11 +139,11 @@ function _getConsumptionBundles(appConfig) { function validateNamespace(appConfig) { const validateSystemNamespace = new RegExp(`^${appConfig.eventApplicationNamespace}\\.[^.]+\\..+$`); if ( - appConfig.ordNamespace === undefined && + 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}..` + `Namespace is not correctly defined in cdsrc.json or it is not in the format of ${appConfig.eventApplicationNamespace}..` ); Logger.error('Namespace error:', error.message); throw error; diff --git a/lib/templates.js b/lib/templates.js index b8b603c..200fbcf 100644 --- a/lib/templates.js +++ b/lib/templates.js @@ -123,7 +123,7 @@ function _getTitleFromServiceName(srv) { */ function _getEntityVersion(entity) { const entityVersion = entity.ordId.split(":").pop(); - const version = entityVersion === "v1" ? "1.0.0" : entityVersion; + 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.`); } From 286072166d6a489155529b9b5de5a5ccfc661401 Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Mon, 9 Dec 2024 11:01:14 +0100 Subject: [PATCH 12/15] minor change --- __tests__/mockedCsn.test.js | 11 +---------- .../unittest/__snapshots__/templates.test.js.snap | 4 ++-- __tests__/unittest/templates.test.js | 4 ++-- lib/ord.js | 4 ++-- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/__tests__/mockedCsn.test.js b/__tests__/mockedCsn.test.js index d79389b..066d3b4 100644 --- a/__tests__/mockedCsn.test.js +++ b/__tests__/mockedCsn.test.js @@ -2,7 +2,7 @@ const cds = require("@sap/cds"); const path = require("path"); describe("Tests for ORD document generated out of mocked csn files", () => { - let ord; //, errorSpy; + let ord; function checkOrdDocument(csn) { const document = ord(csn); @@ -26,7 +26,6 @@ describe("Tests for ORD document generated out of mocked csn files", () => { description: "this is my custom description", policyLevel: "sap:core:v1" }; - // errorSpy = jest.spyOn(console, "error"); }); afterAll(() => { @@ -101,12 +100,4 @@ describe("Tests for ORD document generated out of mocked csn files", () => { expect(document.eventResources[0].ordId).toEqual(expect.stringContaining("CatalogService")); }); }); - - describe("Tests for ORD document when namespace is not correctly defined", () => { - test("Namespace is not correctly defined in cdsrc.json: throw error", () => { - cds.env.ord.namespace = "invalid_namespace"; - const csn = require("./__mocks__/publicResourcesCsn.json"); - expect(() => ord(csn)).toThrowError(expect.objectContaining({"message": expect.stringContaining("Namespace is not correctly defined in cdsrc.json")})); - }); - }); }); \ No newline at end of file diff --git a/__tests__/unittest/__snapshots__/templates.test.js.snap b/__tests__/unittest/__snapshots__/templates.test.js.snap index 6ba86c5..1019d37 100644 --- a/__tests__/unittest/__snapshots__/templates.test.js.snap +++ b/__tests__/unittest/__snapshots__/templates.test.js.snap @@ -76,12 +76,12 @@ exports[`templates createEntityTypeTemplate should return entity type with incor "lastUpdate": "2022-12-19T15:47:04+00:00", "level": "root-entity", "localId": "SomeAribaDummyEntity", - "ordId": "sap.sm:entityType:SomeAribaDummyEntity:v1.2b.3", + "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": "v1.2b.3", + "version": "3b.0.0", "visibility": "public", } `; diff --git a/__tests__/unittest/templates.test.js b/__tests__/unittest/templates.test.js index 6abfe8d..0a84b33 100644 --- a/__tests__/unittest/templates.test.js +++ b/__tests__/unittest/templates.test.js @@ -38,7 +38,7 @@ describe('templates', () => { 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:v1.2b.3", + ordId: "sap.sm:entityType:SomeAribaDummyEntity:v3b", entityName: "SomeAribaDummyEntity", "@title": "Title of SomeAribaDummyEntity", "@ObjectModel.compositionRoot": true, @@ -48,7 +48,7 @@ describe('templates', () => { expect(entityType).toBeDefined(); expect(entityType).toMatchSnapshot(); expect(warningSpy).toHaveBeenCalledTimes(1); - expect(entityType.version).toEqual('v1.2b.3'); + 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'); }); diff --git a/lib/ord.js b/lib/ord.js index edcd44a..bd6db03 100644 --- a/lib/ord.js +++ b/lib/ord.js @@ -139,11 +139,11 @@ function _getConsumptionBundles(appConfig) { function validateNamespace(appConfig) { const validateSystemNamespace = new RegExp(`^${appConfig.eventApplicationNamespace}\\.[^.]+\\..+$`); if ( - appConfig.ordNamespace === undefined || + appConfig.ordNamespace === undefined && !validateSystemNamespace.test(appConfig.ordNamespace) ) { let error = new Error( - `Namespace is not correctly defined in cdsrc.json or it is not in the format of ${appConfig.eventApplicationNamespace}..` + `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; From c44969658551afa64132ea5003ca5c3b184e7448 Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Mon, 9 Dec 2024 11:23:26 +0100 Subject: [PATCH 13/15] remove incorrect validateNamespace function --- lib/ord.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/ord.js b/lib/ord.js index bd6db03..07b3cfb 100644 --- a/lib/ord.js +++ b/lib/ord.js @@ -136,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", @@ -178,7 +164,6 @@ 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); From 64fbeecb2e22632220bf86aa365faede75b5a012 Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Mon, 9 Dec 2024 11:50:08 +0100 Subject: [PATCH 14/15] add test for missing package.json --- __tests__/ord.e2e.test.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/__tests__/ord.e2e.test.js b/__tests__/ord.e2e.test.js index 3b2cfc6..028e9ad 100644 --- a/__tests__/ord.e2e.test.js +++ b/__tests__/ord.e2e.test.js @@ -12,9 +12,8 @@ describe("End-to-end test for ORD document", () => { ord = require("../lib/ord"); }); - afterAll(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); + afterEach(() => { + cds.root = path.join(__dirname, "bookshop"); }); test("Successfully create ORD Documents with defaults", () => { @@ -22,6 +21,11 @@ describe("End-to-end test for ORD document", () => { 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]*)$/; From 1218ec650f478deb461954c59f8ea089e66f2d18 Mon Sep 17 00:00:00 2001 From: Albin Ramovic Date: Tue, 10 Dec 2024 16:08:21 +0100 Subject: [PATCH 15/15] use constants with miningful names instead of oidIdParts[N] --- lib/templates.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/templates.js b/lib/templates.js index 200fbcf..8fb9eb6 100644 --- a/lib/templates.js +++ b/lib/templates.js @@ -80,9 +80,11 @@ const createEntityTypeMappingsItemTemplate = (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: `${ordIdParts[0]}:entityType:${entityName}:${ordIdParts[2] || "v1"}`, + ordId: `${namespace}:entityType:${entityName}:${version}`, entityName, ...entity };