diff --git a/__tests__/__snapshots__/ord.test.js.snap b/__tests__/__snapshots__/ord.test.js.snap index b546b4a..257d704 100644 --- a/__tests__/__snapshots__/ord.test.js.snap +++ b/__tests__/__snapshots__/ord.test.js.snap @@ -22,7 +22,7 @@ exports[`Tests for default ORD document Successfully create ORD Documents with d "partOfGroups": [ "sap.cds:service:capjs.ord:undefined.AdminService", ], - "partOfPackage": undefined, + "partOfPackage": "capjsord:package:undefined:v1", "releaseStatus": "active", "resourceDefinitions": [ { @@ -69,7 +69,7 @@ exports[`Tests for default ORD document Successfully create ORD Documents with d "partOfGroups": [ "sap.cds:service:capjs.ord:undefined.CatalogService", ], - "partOfPackage": undefined, + "partOfPackage": "capjsord:package:undefined:v1", "releaseStatus": "active", "resourceDefinitions": [ { @@ -100,6 +100,64 @@ exports[`Tests for default ORD document Successfully create ORD Documents with d }, ], "description": "this is an application description", + "eventResources": [ + { + "description": "CAP Event resource describing events / messages.", + "extensible": { + "supported": "no", + }, + "ordId": "capjs.ord:eventResource:undefined.AdminService:v1", + "partOfGroups": [ + "sap.cds:service:capjs.ord:undefined.AdminService", + ], + "partOfPackage": "capjsord:package:undefined:v1", + "releaseStatus": "beta", + "resourceDefinitions": [ + { + "accessStrategies": [ + { + "type": "open", + }, + ], + "mediaType": "application/json", + "type": "asyncapi-v2", + "url": "/.well-known/open-resource-discovery/v1/api-metadata/AdminService.asyncapi2.json", + }, + ], + "shortDescription": "Example ODM Event", + "title": "ODM capjsord Events", + "version": "1.0.0", + "visibility": "public", + }, + { + "description": "CAP Event resource describing events / messages.", + "extensible": { + "supported": "no", + }, + "ordId": "capjs.ord:eventResource:undefined.CatalogService:v1", + "partOfGroups": [ + "sap.cds:service:capjs.ord:undefined.CatalogService", + ], + "partOfPackage": "capjsord:package:undefined:v1", + "releaseStatus": "beta", + "resourceDefinitions": [ + { + "accessStrategies": [ + { + "type": "open", + }, + ], + "mediaType": "application/json", + "type": "asyncapi-v2", + "url": "/.well-known/open-resource-discovery/v1/api-metadata/CatalogService.asyncapi2.json", + }, + ], + "shortDescription": "Example ODM Event", + "title": "ODM capjsord Events", + "version": "1.0.0", + "visibility": "public", + }, + ], "groups": [ { "groupId": "sap.cds:service:capjs.ord:undefined.AdminService", @@ -113,6 +171,19 @@ exports[`Tests for default ORD document Successfully create ORD Documents with d }, ], "openResourceDiscovery": "1.9", + "packages": [ + { + "description": "Description for capjs ord", + "ordId": "capjsord:package:undefined:v1", + "partOfProducts": [ + "customer:product:capjs.ord:", + ], + "shortDescription": "Short description for capjs ord", + "title": "capjs ord", + "vendor": "customer:vendor:Customer:", + "version": "1.0.0", + }, + ], "policyLevel": "none", "products": [ { diff --git a/__tests__/bookshop/srv/admin-service.cds b/__tests__/bookshop/srv/admin-service.cds index b787951..da011b3 100644 --- a/__tests__/bookshop/srv/admin-service.cds +++ b/__tests__/bookshop/srv/admin-service.cds @@ -2,4 +2,18 @@ using { sap.capire.bookshop as my } from '../db/schema'; service AdminService @(requires:'authenticated-user') { entity Books as projection on my.Books; entity Authors as projection on my.Authors; + + event BookCreated : { + ID : Integer; + title : String @title: 'Title'; + }; + + event BookDeleted : { + ID : Integer; + }; + + event BookUpdated : { + ID : Integer; + title : String @title: 'Title'; + } } diff --git a/__tests__/bookshop/srv/cat-service.cds b/__tests__/bookshop/srv/cat-service.cds index 342b330..0c3423c 100644 --- a/__tests__/bookshop/srv/cat-service.cds +++ b/__tests__/bookshop/srv/cat-service.cds @@ -7,4 +7,18 @@ service CatalogService @(path:'/browse') { @requires: 'authenticated-user' action submitOrder (book: Books:ID, quantity: Integer); + + event BookCreated : { + ID : Integer; + title : String @title: 'Title'; + }; + + event BookDeleted : { + ID : Integer; + }; + + event BookUpdated : { + ID : Integer; + title : String @title: 'Title'; + } } diff --git a/__tests__/ord.test.js b/__tests__/ord.test.js index 2215b6f..cde5605 100644 --- a/__tests__/ord.test.js +++ b/__tests__/ord.test.js @@ -13,4 +13,37 @@ describe("Tests for default ORD document", () => { const document = ord(csn); expect(document).toMatchSnapshot(); }); + + + describe("eventResources", () => { + 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 excactly 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__/unittest/templates.test.js b/__tests__/unittest/templates.test.js index be450e6..03bdc28 100644 --- a/__tests__/unittest/templates.test.js +++ b/__tests__/unittest/templates.test.js @@ -61,14 +61,6 @@ describe('templates', () => { }; expect(templates.fCreateGroupsTemplateForService(testSrv, linkedModel, testGroupIds)).toEqual(testResult); }); - - it('should return null when groupIds has groupId', () => { - const testSrv = 'testServiceName'; - global.namespace = 'customer'; - const testGroupIds = new Set(['sap.cds:service:customer:undefined.testServiceName']); - const testResult = null; - expect(templates.fCreateGroupsTemplateForService(testSrv, linkedModel, testGroupIds)).toEqual(testResult); - }); }); describe('fCreateGroupsTemplateForEvent', () => { diff --git a/lib/ord.js b/lib/ord.js index 746acd3..96b3997 100644 --- a/lib/ord.js +++ b/lib/ord.js @@ -6,7 +6,6 @@ const { fCreateAPIResourceTemplate, fCreateEventResourceTemplate, fCreateGroupsTemplateForService, - fCreateGroupsTemplateForEvent, fCreateEntityTypeTemplate } = require('./templates'); @@ -110,17 +109,9 @@ const fGetProducts = (global) => global.env?.products || defaults.products(globa * @returns {Array} The groups array. */ const fGetGroups = (csn, global) => { - // storing the group ids in a set to avoid duplicates - let groupIds = new Set(); - - let serviceGroups = global.aServices - .map((srv) => fCreateGroupsTemplateForService(srv, csn.definitions[srv], groupIds)) - .filter((resource) => resource !== null && resource !== undefined); - let eventGroups = global.aEvents - .map((event) => fCreateGroupsTemplateForEvent(event, csn.definitions[event], groupIds)) + return global.aServices + .map((srv) => fCreateGroupsTemplateForService(srv, csn.definitions[srv])) .filter((resource) => resource !== null && resource !== undefined); - - return [...serviceGroups, ...eventGroups]; }; /** @@ -156,7 +147,19 @@ const fGetAPIResources = (csn, global,packageIds) => { * @param {Object} csn object * @returns {Array} The Event Resources array. */ -const fGetEventResources = (csn, global,packageIds) => global.aEvents.map((srv) => fCreateEventResourceTemplate(srv, csn.definitions[srv], global, packageIds)).filter((resource) => resource !== null && resource !== undefined); +const fGetEventResources = (csn, global, packageIds) => { + if(global.aEvents.length === 0) return [] + + const services = [] + for (const serviceName of global.aServices) { + const hasEvents = global.aEvents.find((eventName) => eventName.startsWith(serviceName)); + if (hasEvents) { + services.push(serviceName) + } + } + + return services.map((serviceName) => fCreateEventResourceTemplate(serviceName, csn.definitions[serviceName], global, packageIds)) +}; module.exports = (csn) => { const linkedCsn = cds.linked(csn); diff --git a/lib/templates.js b/lib/templates.js index fdb5f0d..e573035 100644 --- a/lib/templates.js +++ b/lib/templates.js @@ -115,28 +115,28 @@ function _getTitleFromServiceName(srv) { * @param {Set} groupIds A set of group ids. * @returns {Object} A group object. */ -const fCreateGroupsTemplateForService = (srv, srvDefinition, groupIds) => { - const ordExtensions = fReadORDExtensions(srvDefinition); +const fCreateGroupsTemplateForService = (srv, srvDefinition) => { + const ordExtensions = fReadORDExtensions(srvDefinition); let fullyQualifiedServiceName = srv; if (!srv.includes(global.capNamespace)) { fullyQualifiedServiceName = global.capNamespace + "." + srv; } - if (checkEntityFunctionAction(srvDefinition, global).length > 0) { - let groupId = _getGroupID(fullyQualifiedServiceName, defaults.groupTypeId); - if (groupIds.has(groupId)) { - return null; - } else { - groupIds.add(groupId); + if(!srvDefinition) { + console.warn("Unable to find service definition:", srv) + return undefined + } + + if (srvDefinition && checkEntityFunctionAction(srvDefinition, global).length > 0) { + let groupId = _getGroupID(fullyQualifiedServiceName, defaults.groupTypeId); return { - groupId: groupId, - groupTypeId: `${defaults.groupTypeId}`, - title: ordExtensions.title ?? _getTitleFromServiceName(srv), + groupId: groupId, + groupTypeId: `${defaults.groupTypeId}`, + title: ordExtensions.title ?? _getTitleFromServiceName(srv) }; } } -}; /** * @@ -291,7 +291,7 @@ const fCreateEventResourceTemplate = (srv, srvDefinition, global,packageIds) => { type: "asyncapi-v2", mediaType: "application/json", - url: `/.well-known/open-resource-discovery/v1/api-metadata/${srvDefinition._service.name}.asyncapi2.json`, + url: `/.well-known/open-resource-discovery/v1/api-metadata/${srv}.asyncapi2.json`, accessStrategies: [ { type: "open",