From f13c28239c70d4f3f40f67b3ecabf093167b025c Mon Sep 17 00:00:00 2001 From: I583880 Date: Tue, 26 Aug 2025 17:26:09 +0300 Subject: [PATCH 01/12] [ALS-8139] Support usage of VCAP-SERVICES-FILE-PATH --- lib/utils.js | 26 ++++++++++++++++++- srv/log2als.js | 6 +++-- srv/log2alsng.js | 10 +++----- test/integration/ng.test.js | 51 ++++++++++++++++++++++++++++++------- 4 files changed, 75 insertions(+), 18 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index ffffc17..f5a6e62 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,4 +1,6 @@ const cds = require('@sap/cds') +const fs = require('fs'); +const path = require('path'); const { Relation, exposeRelation, relationHandler } = require('./_relation') @@ -282,6 +284,27 @@ const resolveDataSubjects = (logs, req) => { }) } +const loadVCAPServices = () => { + let vcapServices = {}; + const vcapServicesFilePath = process.env.VCAP_SERVICES_FILE_PATH; + if (vcapServicesFilePath) { + try { + const vcapServicesFileContent = fs.readFileSync(path.resolve(vcapServicesFilePath), 'utf8'); + vcapServices = JSON.parse(vcapServicesFileContent); + } catch (err) { + throw new Error(`Failed to read or parse VCAP_SERVICES from file at ${vcapServicesFilePath}: ${err.message}`); + } + } else if (process.env.VCAP_SERVICES) { + try { + vcapServices = JSON.parse(process.env.VCAP_SERVICES); + } catch (err) { + throw new Error(`Failed to parse VCAP_SERVICES from environment variable: ${err.message}`); + } + } + + return vcapServices; +} + module.exports = { hasPersonalData, getMapKeyForCurrentRequest, @@ -291,5 +314,6 @@ module.exports = { addObjectID, addDataSubject, addDataSubjectForDetailsEntity, - resolveDataSubjects + resolveDataSubjects, + loadVCAPServices, } diff --git a/srv/log2als.js b/srv/log2als.js index 5ecb159..faab4b0 100644 --- a/srv/log2als.js +++ b/srv/log2als.js @@ -1,4 +1,6 @@ -const credentials = JSON.parse(process.env.VCAP_SERVICES) || {} -const isV3 = credentials['user-provided']?.some(obj => obj.tags.includes('auditlog-ng')) +const { loadVCAPServices } = require('../lib/utils') + +const vcapServices = loadVCAPServices() +const isV3 = vcapServices['user-provided']?.some(obj => obj.tags.includes('auditlog-ng')) module.exports = isV3 ? require('./log2alsng') : require('./log2restv2') diff --git a/srv/log2alsng.js b/srv/log2alsng.js index 3208884..d860808 100644 --- a/srv/log2alsng.js +++ b/srv/log2alsng.js @@ -1,18 +1,16 @@ const cds = require('@sap/cds') - -const LOG = cds.log('audit-log') - const https = require('https') - const AuditLogService = require('./service') +const { loadVCAPServices } = require('../lib/utils') +const LOG = cds.log('audit-log') module.exports = class AuditLog2ALSNG extends AuditLogService { constructor() { super() - this._vcap = JSON.parse(process.env.VCAP_SERVICES || '{}') + this._vcap = loadVCAPServices() this._userProvided = this._vcap['user-provided']?.find(obj => obj.tags.includes('auditlog-ng')) || {} if (!this._userProvided.credentials) throw new Error('No credentials found for SAP Audit Log Service NG') - this._vcapApplication = this._vcap['VCAP_APPLICATION'] || {} + this._vcapApplication = JSON.parse(process.env.VCAP_APPLICATION || '{}') } async init() { diff --git a/test/integration/ng.test.js b/test/integration/ng.test.js index 4dc03ac..c6165dd 100644 --- a/test/integration/ng.test.js +++ b/test/integration/ng.test.js @@ -1,9 +1,13 @@ const cds = require('@sap/cds') +const fs = require('fs') +const os = require('os') +const path = require('path') const { POST } = cds.test().in(__dirname) cds.env.requires['audit-log'].kind = 'audit-log-to-alsng' cds.env.requires['audit-log'].impl = '@cap-js/audit-logging/srv/log2alsng' + const VCAP_SERVICES = { 'user-provided': [ { @@ -12,17 +16,12 @@ const VCAP_SERVICES = { } ] } -process.env.VCAP_SERVICES = JSON.stringify(VCAP_SERVICES) - -describe('Log to Audit Log Service NG ', () => { - if (!VCAP_SERVICES['user-provided'][0].credentials) - return test.skip('Skipping tests due to missing credentials', () => {}) +const ALICE = { username: 'alice', password: 'password' } +const update_attributes = [{ name: 'foo', old: 'bar', new: 'baz' }] +const runTestSuite = () => { require('./tests')(POST) - const ALICE = { username: 'alice', password: 'password' } - const update_attributes = [{ name: 'foo', old: 'bar', new: 'baz' }] - test('id flattening', async () => { expect( cds.services['audit-log'].flattenAndSortIdObject({ foo: 'bar', alpha: 'omega', ping: 'pong', fizz: 'buzz' }) @@ -50,7 +49,41 @@ describe('Log to Audit Log Service NG ', () => { test('rejects log with invalid data', async () => { await expect( - POST('/integration/passthrough', { event: 'PersonalDataModified', data: '{}' }, { auth: ALICE }) + POST('/integration/passthrough', { event: 'PersonalDataModified', data: '{}' }, { auth: ALICE }) ).rejects.toThrow('Request failed with: 403 - Forbidden') }) +} + +const setupFile = async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'vcap-')); + const filePath = path.join(dir, 'vcap.json'); + fs.writeFileSync(filePath, JSON.stringify(VCAP_SERVICES), 'utf8'); + process.env.VCAP_SERVICES_FILE_PATH = filePath; + + return () => { + try { + fs.unlinkSync(filePath) + fs.rmdirSync(dir) + } catch (err) { + console.error('Error cleaning up temporary VCAP services file:', err); + } + delete process.env.VCAP_SERVICES_FILE_PATH; + }; +}; + +describe('Log to Audit Log Service NG with credentials from VCAP_SERVICES env var', () => { + if (!VCAP_SERVICES['user-provided'][0].credentials) + return test.skip('Skipping tests due to missing credentials', () => {}) + + setupFile() + + runTestSuite() }) + +describe('Log to Audit Log Service NG with credentials from VCAP_SERVICES_FILE_PATH', () => { + if (!VCAP_SERVICES['user-provided'][0].credentials) + return test.skip('Skipping tests due to missing credentials', () => {}) + + + runTestSuite() +}) \ No newline at end of file From 22a3435fcc6422ee0972889e17e274184d34437b Mon Sep 17 00:00:00 2001 From: I583880 Date: Wed, 27 Aug 2025 10:56:03 +0300 Subject: [PATCH 02/12] [ALS-8139] Add unit tests --- lib/utils.js | 4 ++ test/integration/ng.test.js | 51 +++------------- test/utils/utils.test.js | 112 ++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 42 deletions(-) create mode 100644 test/utils/utils.test.js diff --git a/lib/utils.js b/lib/utils.js index f5a6e62..e948b1d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -291,12 +291,16 @@ const loadVCAPServices = () => { try { const vcapServicesFileContent = fs.readFileSync(path.resolve(vcapServicesFilePath), 'utf8'); vcapServices = JSON.parse(vcapServicesFileContent); + // eslint-disable-next-line no-console + console.debug(`VCAP_SERVICES loaded from file at ${vcapServicesFilePath}`); } catch (err) { throw new Error(`Failed to read or parse VCAP_SERVICES from file at ${vcapServicesFilePath}: ${err.message}`); } } else if (process.env.VCAP_SERVICES) { try { vcapServices = JSON.parse(process.env.VCAP_SERVICES); + // eslint-disable-next-line no-console + console.debug('VCAP_SERVICES loaded from environment variable'); } catch (err) { throw new Error(`Failed to parse VCAP_SERVICES from environment variable: ${err.message}`); } diff --git a/test/integration/ng.test.js b/test/integration/ng.test.js index c6165dd..8f124a9 100644 --- a/test/integration/ng.test.js +++ b/test/integration/ng.test.js @@ -1,13 +1,9 @@ const cds = require('@sap/cds') -const fs = require('fs') -const os = require('os') -const path = require('path') const { POST } = cds.test().in(__dirname) cds.env.requires['audit-log'].kind = 'audit-log-to-alsng' cds.env.requires['audit-log'].impl = '@cap-js/audit-logging/srv/log2alsng' - const VCAP_SERVICES = { 'user-provided': [ { @@ -16,15 +12,20 @@ const VCAP_SERVICES = { } ] } -const ALICE = { username: 'alice', password: 'password' } -const update_attributes = [{ name: 'foo', old: 'bar', new: 'baz' }] +process.env.VCAP_SERVICES = JSON.stringify(VCAP_SERVICES) + +describe('Log to Audit Log Service NG ', () => { + if (!VCAP_SERVICES['user-provided'][0].credentials) + return test.skip('Skipping tests due to missing credentials', () => {}) -const runTestSuite = () => { require('./tests')(POST) + const ALICE = { username: 'alice', password: 'password' } + const update_attributes = [{ name: 'foo', old: 'bar', new: 'baz' }] + test('id flattening', async () => { expect( - cds.services['audit-log'].flattenAndSortIdObject({ foo: 'bar', alpha: 'omega', ping: 'pong', fizz: 'buzz' }) + cds.services['audit-log'].flattenAndSortIdObject({ foo: 'bar', alpha: 'omega', ping: 'pong', fizz: 'buzz' }) ).toBe('alpha:omega fizz:buzz foo:bar ping:pong') }) @@ -52,38 +53,4 @@ const runTestSuite = () => { POST('/integration/passthrough', { event: 'PersonalDataModified', data: '{}' }, { auth: ALICE }) ).rejects.toThrow('Request failed with: 403 - Forbidden') }) -} - -const setupFile = async () => { - const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'vcap-')); - const filePath = path.join(dir, 'vcap.json'); - fs.writeFileSync(filePath, JSON.stringify(VCAP_SERVICES), 'utf8'); - process.env.VCAP_SERVICES_FILE_PATH = filePath; - - return () => { - try { - fs.unlinkSync(filePath) - fs.rmdirSync(dir) - } catch (err) { - console.error('Error cleaning up temporary VCAP services file:', err); - } - delete process.env.VCAP_SERVICES_FILE_PATH; - }; -}; - -describe('Log to Audit Log Service NG with credentials from VCAP_SERVICES env var', () => { - if (!VCAP_SERVICES['user-provided'][0].credentials) - return test.skip('Skipping tests due to missing credentials', () => {}) - - setupFile() - - runTestSuite() -}) - -describe('Log to Audit Log Service NG with credentials from VCAP_SERVICES_FILE_PATH', () => { - if (!VCAP_SERVICES['user-provided'][0].credentials) - return test.skip('Skipping tests due to missing credentials', () => {}) - - - runTestSuite() }) \ No newline at end of file diff --git a/test/utils/utils.test.js b/test/utils/utils.test.js new file mode 100644 index 0000000..831a531 --- /dev/null +++ b/test/utils/utils.test.js @@ -0,0 +1,112 @@ +const path = require('path'); +const { loadVCAPServices } = require('../../lib/utils'); + +jest.mock('fs', () => ({ + readFileSync: jest.fn(), +})); + +const fs = require('fs'); + +describe('Test loadVCAPServices', () => { + const ORIGINAL_ENV = process.env; + const FAKE_VCAP = { 'user-provided': [{ name: 'test', credentials: { token: 'abc' } }] }; + const INVALID_JSON = 'invalid json'; + const FAKE_PATH = '/path/to/vcap.json'; + + let logSpy; + + beforeEach(() => { + delete process.env.VCAP_SERVICES_FILE_PATH; + delete process.env.VCAP_SERVICES; + + jest.clearAllMocks(); + logSpy = jest.spyOn(console, 'debug').mockImplementation(() => {}); + }); + + afterAll(() => { + process.env = ORIGINAL_ENV; + }); + + test('loads and parses VCAP_SERVICES from VCAP_SERVICES_FILE_PATH', () => { + process.env.VCAP_SERVICES_FILE_PATH = FAKE_PATH; + + fs.readFileSync.mockReturnValueOnce(JSON.stringify(FAKE_VCAP)); + + const result = loadVCAPServices(); + + expect(fs.readFileSync).toHaveBeenCalledWith(path.resolve(FAKE_PATH), 'utf8'); + expect(result).toEqual(FAKE_VCAP); + expect(logSpy).toHaveBeenCalledWith(`VCAP_SERVICES loaded from file at ${FAKE_PATH}`); + }); + + test('throws error when reading VCAP_SERVICES_FILE_PATH fails', () => { + const errorMessage = 'ENOENT: no such file or directory'; + process.env.VCAP_SERVICES_FILE_PATH = FAKE_PATH; + + fs.readFileSync.mockImplementationOnce(() => { + const err = new Error(errorMessage); + err.code = 'ENOENT'; + throw err; + }); + + expect(() => loadVCAPServices()).toThrow( + `Failed to read or parse VCAP_SERVICES from file at ${FAKE_PATH}: ${errorMessage}` + ); + expect(logSpy).not.toHaveBeenCalled(); + }); + + test('throws error when JSON in VCAP_SERVICES_FILE_PATH is invalid', () => { + process.env.VCAP_SERVICES_FILE_PATH = FAKE_PATH; + + fs.readFileSync.mockReturnValueOnce(INVALID_JSON); + + expect(() => loadVCAPServices()).toThrow( + `Failed to read or parse VCAP_SERVICES from file at ${FAKE_PATH}: Unexpected token 'i', "${INVALID_JSON}" is not valid JSON` + ); + expect(logSpy).not.toHaveBeenCalled(); + }); + + test('loads and parses VCAP_SERVICES from environment variable', () => { + process.env.VCAP_SERVICES = JSON.stringify(FAKE_VCAP); + + const result = loadVCAPServices(); + + expect(result).toEqual(FAKE_VCAP); + expect(logSpy).toHaveBeenCalledWith('VCAP_SERVICES loaded from environment variable'); + expect(fs.readFileSync).not.toHaveBeenCalled(); + }); + + test('throws error when VCAP_SERVICES env var JSON is invalid', () => { + process.env.VCAP_SERVICES = INVALID_JSON; + + expect(() => loadVCAPServices()).toThrow( + `Failed to parse VCAP_SERVICES from environment variable: Unexpected token 'i', "${INVALID_JSON}" is not valid JSON` + ); + expect(logSpy).not.toHaveBeenCalled(); + expect(fs.readFileSync).not.toHaveBeenCalled(); + }); + + test('returns empty object when neither VCAP_SERVICES_FILE_PATH nor VCAP_SERVICES is set', () => { + const result = loadVCAPServices(); + + expect(result).toEqual({}); + expect(logSpy).not.toHaveBeenCalled(); + expect(fs.readFileSync).not.toHaveBeenCalled(); + }); + + test('VCAP_SERVICES_FILE_PATH takes precedence over VCAP_SERVICES', () => { + const fromFile = { from: 'file' }; + const fromEnv = { from: 'env' }; + + process.env.VCAP_SERVICES_FILE_PATH = FAKE_PATH; + process.env.VCAP_SERVICES = JSON.stringify(fromEnv); + + fs.readFileSync.mockReturnValueOnce(JSON.stringify(fromFile)); + + const result = loadVCAPServices(); + + expect(result).toEqual(fromFile); + expect(fs.readFileSync).toHaveBeenCalledTimes(1); + expect(logSpy).toHaveBeenCalledWith(`VCAP_SERVICES loaded from file at ${FAKE_PATH}`); + }); +}); \ No newline at end of file From 757e0c0708e02f969e989349f7ccd86dc4dd4dfb Mon Sep 17 00:00:00 2001 From: I583880 Date: Wed, 27 Aug 2025 10:58:41 +0300 Subject: [PATCH 03/12] [ALS-8139] Fix formating --- test/integration/ng.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/ng.test.js b/test/integration/ng.test.js index 8f124a9..0af29e1 100644 --- a/test/integration/ng.test.js +++ b/test/integration/ng.test.js @@ -25,7 +25,7 @@ describe('Log to Audit Log Service NG ', () => { test('id flattening', async () => { expect( - cds.services['audit-log'].flattenAndSortIdObject({ foo: 'bar', alpha: 'omega', ping: 'pong', fizz: 'buzz' }) + cds.services['audit-log'].flattenAndSortIdObject({ foo: 'bar', alpha: 'omega', ping: 'pong', fizz: 'buzz' }) ).toBe('alpha:omega fizz:buzz foo:bar ping:pong') }) @@ -50,7 +50,7 @@ describe('Log to Audit Log Service NG ', () => { test('rejects log with invalid data', async () => { await expect( - POST('/integration/passthrough', { event: 'PersonalDataModified', data: '{}' }, { auth: ALICE }) + POST('/integration/passthrough', { event: 'PersonalDataModified', data: '{}' }, { auth: ALICE }) ).rejects.toThrow('Request failed with: 403 - Forbidden') }) }) \ No newline at end of file From 5c042283470e7e17396418b1d7fd496e7dbafaa3 Mon Sep 17 00:00:00 2001 From: I583880 Date: Wed, 27 Aug 2025 10:59:56 +0300 Subject: [PATCH 04/12] [ALS-8139] Fix formating --- test/integration/ng.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/ng.test.js b/test/integration/ng.test.js index 0af29e1..4dc03ac 100644 --- a/test/integration/ng.test.js +++ b/test/integration/ng.test.js @@ -53,4 +53,4 @@ describe('Log to Audit Log Service NG ', () => { POST('/integration/passthrough', { event: 'PersonalDataModified', data: '{}' }, { auth: ALICE }) ).rejects.toThrow('Request failed with: 403 - Forbidden') }) -}) \ No newline at end of file +}) From 06b0f273bed8d62e8f9c670eea48c7fbc159ac3c Mon Sep 17 00:00:00 2001 From: I583880 Date: Wed, 27 Aug 2025 11:08:49 +0300 Subject: [PATCH 05/12] [ALS-8139] Replace err message assertion with regex --- test/utils/utils.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/utils/utils.test.js b/test/utils/utils.test.js index 831a531..c3294a6 100644 --- a/test/utils/utils.test.js +++ b/test/utils/utils.test.js @@ -61,7 +61,7 @@ describe('Test loadVCAPServices', () => { fs.readFileSync.mockReturnValueOnce(INVALID_JSON); expect(() => loadVCAPServices()).toThrow( - `Failed to read or parse VCAP_SERVICES from file at ${FAKE_PATH}: Unexpected token 'i', "${INVALID_JSON}" is not valid JSON` + new RegExp(`^Failed to read or parse VCAP_SERVICES from file at ${FAKE_PATH}:`) ); expect(logSpy).not.toHaveBeenCalled(); }); @@ -80,7 +80,7 @@ describe('Test loadVCAPServices', () => { process.env.VCAP_SERVICES = INVALID_JSON; expect(() => loadVCAPServices()).toThrow( - `Failed to parse VCAP_SERVICES from environment variable: Unexpected token 'i', "${INVALID_JSON}" is not valid JSON` + new RegExp(`^Failed to parse VCAP_SERVICES from environment variable:`) ); expect(logSpy).not.toHaveBeenCalled(); expect(fs.readFileSync).not.toHaveBeenCalled(); @@ -109,4 +109,4 @@ describe('Test loadVCAPServices', () => { expect(fs.readFileSync).toHaveBeenCalledTimes(1); expect(logSpy).toHaveBeenCalledWith(`VCAP_SERVICES loaded from file at ${FAKE_PATH}`); }); -}); \ No newline at end of file +}); From 172a3e2f31f1cfa66bb60e90a12f742a2f27652d Mon Sep 17 00:00:00 2001 From: I583880 Date: Wed, 27 Aug 2025 11:27:40 +0300 Subject: [PATCH 06/12] [ALS-8139] Add changelog and bump version --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee77c17..facae4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). The format is based on [Keep a Changelog](http://keepachangelog.com/). +## Version 1.0.2 - 2025-XX-XX + +### Added +- Support for loading `VCAP_SERVICES` from a file specified by the `VCAP_SERVICES_FILE_PATH` environment variable. + +### Fixed +- Correctly retrieve `appId` from the `VCAP_APPLICATION` environment variable. + ## Version 1.0.1 - 2025-08-05 ### Fixed diff --git a/package.json b/package.json index de4b20c..bc6bdb6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cap-js/audit-logging", - "version": "1.0.1", + "version": "1.0.2", "description": "CDS plugin providing integration to the SAP Audit Log service as well as out-of-the-box personal data-related audit logging based on annotations.", "repository": "cap-js/audit-logging", "author": "SAP SE (https://www.sap.com)", From 3be23a08897b408476543c32b1e81d965e97bbf9 Mon Sep 17 00:00:00 2001 From: sjvans <30337871+sjvans@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:18:47 +0200 Subject: [PATCH 07/12] don't disable rule no-console --- lib/utils.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index e948b1d..0ac31e1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -291,7 +291,6 @@ const loadVCAPServices = () => { try { const vcapServicesFileContent = fs.readFileSync(path.resolve(vcapServicesFilePath), 'utf8'); vcapServices = JSON.parse(vcapServicesFileContent); - // eslint-disable-next-line no-console console.debug(`VCAP_SERVICES loaded from file at ${vcapServicesFilePath}`); } catch (err) { throw new Error(`Failed to read or parse VCAP_SERVICES from file at ${vcapServicesFilePath}: ${err.message}`); @@ -299,7 +298,6 @@ const loadVCAPServices = () => { } else if (process.env.VCAP_SERVICES) { try { vcapServices = JSON.parse(process.env.VCAP_SERVICES); - // eslint-disable-next-line no-console console.debug('VCAP_SERVICES loaded from environment variable'); } catch (err) { throw new Error(`Failed to parse VCAP_SERVICES from environment variable: ${err.message}`); From b64774cd849a398a2b082af3c7896198fbadef71 Mon Sep 17 00:00:00 2001 From: D050513 Date: Thu, 28 Aug 2025 16:41:17 +0200 Subject: [PATCH 08/12] eslint . --max-warnings=0 --- lib/utils.js | 26 +++++++++++++------------- package.json | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 0ac31e1..961a4b2 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,6 +1,6 @@ const cds = require('@sap/cds') -const fs = require('fs'); -const path = require('path'); +const fs = require('fs') +const path = require('path') const { Relation, exposeRelation, relationHandler } = require('./_relation') @@ -285,26 +285,26 @@ const resolveDataSubjects = (logs, req) => { } const loadVCAPServices = () => { - let vcapServices = {}; - const vcapServicesFilePath = process.env.VCAP_SERVICES_FILE_PATH; + let vcapServices = {} + const vcapServicesFilePath = process.env.VCAP_SERVICES_FILE_PATH if (vcapServicesFilePath) { try { - const vcapServicesFileContent = fs.readFileSync(path.resolve(vcapServicesFilePath), 'utf8'); - vcapServices = JSON.parse(vcapServicesFileContent); - console.debug(`VCAP_SERVICES loaded from file at ${vcapServicesFilePath}`); + const vcapServicesFileContent = fs.readFileSync(path.resolve(vcapServicesFilePath), 'utf8') + vcapServices = JSON.parse(vcapServicesFileContent) + console.debug(`VCAP_SERVICES loaded from file at ${vcapServicesFilePath}`) } catch (err) { - throw new Error(`Failed to read or parse VCAP_SERVICES from file at ${vcapServicesFilePath}: ${err.message}`); + throw new Error(`Failed to read or parse VCAP_SERVICES from file at ${vcapServicesFilePath}: ${err.message}`) } } else if (process.env.VCAP_SERVICES) { try { - vcapServices = JSON.parse(process.env.VCAP_SERVICES); - console.debug('VCAP_SERVICES loaded from environment variable'); + vcapServices = JSON.parse(process.env.VCAP_SERVICES) + console.debug('VCAP_SERVICES loaded from environment variable') } catch (err) { - throw new Error(`Failed to parse VCAP_SERVICES from environment variable: ${err.message}`); + throw new Error(`Failed to parse VCAP_SERVICES from environment variable: ${err.message}`) } } - return vcapServices; + return vcapServices } module.exports = { @@ -317,5 +317,5 @@ module.exports = { addDataSubject, addDataSubjectForDetailsEntity, resolveDataSubjects, - loadVCAPServices, + loadVCAPServices } diff --git a/package.json b/package.json index bc6bdb6..2126be0 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "srv" ], "scripts": { - "lint": "npx eslint .", + "lint": "npx eslint . --max-warnings=0", "test": "npx jest --silent" }, "peerDependencies": { From 67881b2157efbb3104e50a769e1a433dd6eb6464 Mon Sep 17 00:00:00 2001 From: D050513 Date: Thu, 28 Aug 2025 16:41:59 +0200 Subject: [PATCH 09/12] formatting --- test/utils/utils.test.js | 160 +++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 81 deletions(-) diff --git a/test/utils/utils.test.js b/test/utils/utils.test.js index c3294a6..d9d4fb7 100644 --- a/test/utils/utils.test.js +++ b/test/utils/utils.test.js @@ -1,112 +1,110 @@ -const path = require('path'); -const { loadVCAPServices } = require('../../lib/utils'); +const path = require('path') +const { loadVCAPServices } = require('../../lib/utils') jest.mock('fs', () => ({ - readFileSync: jest.fn(), -})); + readFileSync: jest.fn() +})) -const fs = require('fs'); +const fs = require('fs') describe('Test loadVCAPServices', () => { - const ORIGINAL_ENV = process.env; - const FAKE_VCAP = { 'user-provided': [{ name: 'test', credentials: { token: 'abc' } }] }; - const INVALID_JSON = 'invalid json'; - const FAKE_PATH = '/path/to/vcap.json'; + const ORIGINAL_ENV = process.env + const FAKE_VCAP = { 'user-provided': [{ name: 'test', credentials: { token: 'abc' } }] } + const INVALID_JSON = 'invalid json' + const FAKE_PATH = '/path/to/vcap.json' - let logSpy; + let logSpy - beforeEach(() => { - delete process.env.VCAP_SERVICES_FILE_PATH; - delete process.env.VCAP_SERVICES; + beforeEach(() => { + delete process.env.VCAP_SERVICES_FILE_PATH + delete process.env.VCAP_SERVICES - jest.clearAllMocks(); - logSpy = jest.spyOn(console, 'debug').mockImplementation(() => {}); - }); + jest.clearAllMocks() + logSpy = jest.spyOn(console, 'debug').mockImplementation(() => {}) + }) - afterAll(() => { - process.env = ORIGINAL_ENV; - }); + afterAll(() => { + process.env = ORIGINAL_ENV + }) - test('loads and parses VCAP_SERVICES from VCAP_SERVICES_FILE_PATH', () => { - process.env.VCAP_SERVICES_FILE_PATH = FAKE_PATH; + test('loads and parses VCAP_SERVICES from VCAP_SERVICES_FILE_PATH', () => { + process.env.VCAP_SERVICES_FILE_PATH = FAKE_PATH - fs.readFileSync.mockReturnValueOnce(JSON.stringify(FAKE_VCAP)); + fs.readFileSync.mockReturnValueOnce(JSON.stringify(FAKE_VCAP)) - const result = loadVCAPServices(); + const result = loadVCAPServices() - expect(fs.readFileSync).toHaveBeenCalledWith(path.resolve(FAKE_PATH), 'utf8'); - expect(result).toEqual(FAKE_VCAP); - expect(logSpy).toHaveBeenCalledWith(`VCAP_SERVICES loaded from file at ${FAKE_PATH}`); - }); + expect(fs.readFileSync).toHaveBeenCalledWith(path.resolve(FAKE_PATH), 'utf8') + expect(result).toEqual(FAKE_VCAP) + expect(logSpy).toHaveBeenCalledWith(`VCAP_SERVICES loaded from file at ${FAKE_PATH}`) + }) - test('throws error when reading VCAP_SERVICES_FILE_PATH fails', () => { - const errorMessage = 'ENOENT: no such file or directory'; - process.env.VCAP_SERVICES_FILE_PATH = FAKE_PATH; + test('throws error when reading VCAP_SERVICES_FILE_PATH fails', () => { + const errorMessage = 'ENOENT: no such file or directory' + process.env.VCAP_SERVICES_FILE_PATH = FAKE_PATH - fs.readFileSync.mockImplementationOnce(() => { - const err = new Error(errorMessage); - err.code = 'ENOENT'; - throw err; - }); + fs.readFileSync.mockImplementationOnce(() => { + const err = new Error(errorMessage) + err.code = 'ENOENT' + throw err + }) - expect(() => loadVCAPServices()).toThrow( - `Failed to read or parse VCAP_SERVICES from file at ${FAKE_PATH}: ${errorMessage}` - ); - expect(logSpy).not.toHaveBeenCalled(); - }); + expect(() => loadVCAPServices()).toThrow( + `Failed to read or parse VCAP_SERVICES from file at ${FAKE_PATH}: ${errorMessage}` + ) + expect(logSpy).not.toHaveBeenCalled() + }) - test('throws error when JSON in VCAP_SERVICES_FILE_PATH is invalid', () => { - process.env.VCAP_SERVICES_FILE_PATH = FAKE_PATH; + test('throws error when JSON in VCAP_SERVICES_FILE_PATH is invalid', () => { + process.env.VCAP_SERVICES_FILE_PATH = FAKE_PATH - fs.readFileSync.mockReturnValueOnce(INVALID_JSON); + fs.readFileSync.mockReturnValueOnce(INVALID_JSON) - expect(() => loadVCAPServices()).toThrow( - new RegExp(`^Failed to read or parse VCAP_SERVICES from file at ${FAKE_PATH}:`) - ); - expect(logSpy).not.toHaveBeenCalled(); - }); + expect(() => loadVCAPServices()).toThrow( + new RegExp(`^Failed to read or parse VCAP_SERVICES from file at ${FAKE_PATH}:`) + ) + expect(logSpy).not.toHaveBeenCalled() + }) - test('loads and parses VCAP_SERVICES from environment variable', () => { - process.env.VCAP_SERVICES = JSON.stringify(FAKE_VCAP); + test('loads and parses VCAP_SERVICES from environment variable', () => { + process.env.VCAP_SERVICES = JSON.stringify(FAKE_VCAP) - const result = loadVCAPServices(); + const result = loadVCAPServices() - expect(result).toEqual(FAKE_VCAP); - expect(logSpy).toHaveBeenCalledWith('VCAP_SERVICES loaded from environment variable'); - expect(fs.readFileSync).not.toHaveBeenCalled(); - }); + expect(result).toEqual(FAKE_VCAP) + expect(logSpy).toHaveBeenCalledWith('VCAP_SERVICES loaded from environment variable') + expect(fs.readFileSync).not.toHaveBeenCalled() + }) - test('throws error when VCAP_SERVICES env var JSON is invalid', () => { - process.env.VCAP_SERVICES = INVALID_JSON; + test('throws error when VCAP_SERVICES env var JSON is invalid', () => { + process.env.VCAP_SERVICES = INVALID_JSON - expect(() => loadVCAPServices()).toThrow( - new RegExp(`^Failed to parse VCAP_SERVICES from environment variable:`) - ); - expect(logSpy).not.toHaveBeenCalled(); - expect(fs.readFileSync).not.toHaveBeenCalled(); - }); + expect(() => loadVCAPServices()).toThrow(new RegExp(`^Failed to parse VCAP_SERVICES from environment variable:`)) + expect(logSpy).not.toHaveBeenCalled() + expect(fs.readFileSync).not.toHaveBeenCalled() + }) - test('returns empty object when neither VCAP_SERVICES_FILE_PATH nor VCAP_SERVICES is set', () => { - const result = loadVCAPServices(); + test('returns empty object when neither VCAP_SERVICES_FILE_PATH nor VCAP_SERVICES is set', () => { + const result = loadVCAPServices() - expect(result).toEqual({}); - expect(logSpy).not.toHaveBeenCalled(); - expect(fs.readFileSync).not.toHaveBeenCalled(); - }); + expect(result).toEqual({}) + expect(logSpy).not.toHaveBeenCalled() + expect(fs.readFileSync).not.toHaveBeenCalled() + }) - test('VCAP_SERVICES_FILE_PATH takes precedence over VCAP_SERVICES', () => { - const fromFile = { from: 'file' }; - const fromEnv = { from: 'env' }; + test('VCAP_SERVICES_FILE_PATH takes precedence over VCAP_SERVICES', () => { + const fromFile = { from: 'file' } + const fromEnv = { from: 'env' } - process.env.VCAP_SERVICES_FILE_PATH = FAKE_PATH; - process.env.VCAP_SERVICES = JSON.stringify(fromEnv); + process.env.VCAP_SERVICES_FILE_PATH = FAKE_PATH + process.env.VCAP_SERVICES = JSON.stringify(fromEnv) - fs.readFileSync.mockReturnValueOnce(JSON.stringify(fromFile)); + fs.readFileSync.mockReturnValueOnce(JSON.stringify(fromFile)) - const result = loadVCAPServices(); + const result = loadVCAPServices() - expect(result).toEqual(fromFile); - expect(fs.readFileSync).toHaveBeenCalledTimes(1); - expect(logSpy).toHaveBeenCalledWith(`VCAP_SERVICES loaded from file at ${FAKE_PATH}`); - }); -}); + expect(result).toEqual(fromFile) + expect(fs.readFileSync).toHaveBeenCalledTimes(1) + expect(logSpy).toHaveBeenCalledWith(`VCAP_SERVICES loaded from file at ${FAKE_PATH}`) + }) +}) From 577f462e3961e12b89c0dece6bbafcc0b4a5d50d Mon Sep 17 00:00:00 2001 From: D050513 Date: Fri, 29 Aug 2025 10:31:11 +0200 Subject: [PATCH 10/12] changelog formatting --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index facae4f..3b7c213 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## Version 1.0.2 - 2025-XX-XX ### Added -- Support for loading `VCAP_SERVICES` from a file specified by the `VCAP_SERVICES_FILE_PATH` environment variable. + +- Support for loading `VCAP_SERVICES` from a file specified by the `VCAP_SERVICES_FILE_PATH` environment variable ### Fixed -- Correctly retrieve `appId` from the `VCAP_APPLICATION` environment variable. + +- Correctly retrieve `appId` from the `VCAP_APPLICATION` environment variable ## Version 1.0.1 - 2025-08-05 From 531050321ae0c0ac9f5583dee53575a6df73816e Mon Sep 17 00:00:00 2001 From: I583880 Date: Mon, 1 Sep 2025 09:40:23 +0300 Subject: [PATCH 11/12] [ALS-8139] Remove console logs --- lib/utils.js | 2 -- test/utils/utils.test.js | 10 ---------- 2 files changed, 12 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 961a4b2..0f00cb1 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -291,14 +291,12 @@ const loadVCAPServices = () => { try { const vcapServicesFileContent = fs.readFileSync(path.resolve(vcapServicesFilePath), 'utf8') vcapServices = JSON.parse(vcapServicesFileContent) - console.debug(`VCAP_SERVICES loaded from file at ${vcapServicesFilePath}`) } catch (err) { throw new Error(`Failed to read or parse VCAP_SERVICES from file at ${vcapServicesFilePath}: ${err.message}`) } } else if (process.env.VCAP_SERVICES) { try { vcapServices = JSON.parse(process.env.VCAP_SERVICES) - console.debug('VCAP_SERVICES loaded from environment variable') } catch (err) { throw new Error(`Failed to parse VCAP_SERVICES from environment variable: ${err.message}`) } diff --git a/test/utils/utils.test.js b/test/utils/utils.test.js index d9d4fb7..5459309 100644 --- a/test/utils/utils.test.js +++ b/test/utils/utils.test.js @@ -13,14 +13,11 @@ describe('Test loadVCAPServices', () => { const INVALID_JSON = 'invalid json' const FAKE_PATH = '/path/to/vcap.json' - let logSpy - beforeEach(() => { delete process.env.VCAP_SERVICES_FILE_PATH delete process.env.VCAP_SERVICES jest.clearAllMocks() - logSpy = jest.spyOn(console, 'debug').mockImplementation(() => {}) }) afterAll(() => { @@ -36,7 +33,6 @@ describe('Test loadVCAPServices', () => { expect(fs.readFileSync).toHaveBeenCalledWith(path.resolve(FAKE_PATH), 'utf8') expect(result).toEqual(FAKE_VCAP) - expect(logSpy).toHaveBeenCalledWith(`VCAP_SERVICES loaded from file at ${FAKE_PATH}`) }) test('throws error when reading VCAP_SERVICES_FILE_PATH fails', () => { @@ -52,7 +48,6 @@ describe('Test loadVCAPServices', () => { expect(() => loadVCAPServices()).toThrow( `Failed to read or parse VCAP_SERVICES from file at ${FAKE_PATH}: ${errorMessage}` ) - expect(logSpy).not.toHaveBeenCalled() }) test('throws error when JSON in VCAP_SERVICES_FILE_PATH is invalid', () => { @@ -63,7 +58,6 @@ describe('Test loadVCAPServices', () => { expect(() => loadVCAPServices()).toThrow( new RegExp(`^Failed to read or parse VCAP_SERVICES from file at ${FAKE_PATH}:`) ) - expect(logSpy).not.toHaveBeenCalled() }) test('loads and parses VCAP_SERVICES from environment variable', () => { @@ -72,7 +66,6 @@ describe('Test loadVCAPServices', () => { const result = loadVCAPServices() expect(result).toEqual(FAKE_VCAP) - expect(logSpy).toHaveBeenCalledWith('VCAP_SERVICES loaded from environment variable') expect(fs.readFileSync).not.toHaveBeenCalled() }) @@ -80,7 +73,6 @@ describe('Test loadVCAPServices', () => { process.env.VCAP_SERVICES = INVALID_JSON expect(() => loadVCAPServices()).toThrow(new RegExp(`^Failed to parse VCAP_SERVICES from environment variable:`)) - expect(logSpy).not.toHaveBeenCalled() expect(fs.readFileSync).not.toHaveBeenCalled() }) @@ -88,7 +80,6 @@ describe('Test loadVCAPServices', () => { const result = loadVCAPServices() expect(result).toEqual({}) - expect(logSpy).not.toHaveBeenCalled() expect(fs.readFileSync).not.toHaveBeenCalled() }) @@ -105,6 +96,5 @@ describe('Test loadVCAPServices', () => { expect(result).toEqual(fromFile) expect(fs.readFileSync).toHaveBeenCalledTimes(1) - expect(logSpy).toHaveBeenCalledWith(`VCAP_SERVICES loaded from file at ${FAKE_PATH}`) }) }) From 4ed2a16708ca4626f586953a0355e731067fcc61 Mon Sep 17 00:00:00 2001 From: I583880 Date: Mon, 1 Sep 2025 11:32:14 +0300 Subject: [PATCH 12/12] [ALS-8139] Add logger --- lib/utils.js | 3 +++ test/utils/utils.test.js | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/utils.js b/lib/utils.js index 0f00cb1..e360f8d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,6 +1,7 @@ const cds = require('@sap/cds') const fs = require('fs') const path = require('path') +const LOG = cds.log('audit-log') const { Relation, exposeRelation, relationHandler } = require('./_relation') @@ -291,12 +292,14 @@ const loadVCAPServices = () => { try { const vcapServicesFileContent = fs.readFileSync(path.resolve(vcapServicesFilePath), 'utf8') vcapServices = JSON.parse(vcapServicesFileContent) + LOG.debug(`VCAP_SERVICES loaded from file at ${vcapServicesFilePath}`) } catch (err) { throw new Error(`Failed to read or parse VCAP_SERVICES from file at ${vcapServicesFilePath}: ${err.message}`) } } else if (process.env.VCAP_SERVICES) { try { vcapServices = JSON.parse(process.env.VCAP_SERVICES) + LOG.debug('VCAP_SERVICES loaded from environment variable') } catch (err) { throw new Error(`Failed to parse VCAP_SERVICES from environment variable: ${err.message}`) } diff --git a/test/utils/utils.test.js b/test/utils/utils.test.js index 5459309..22bcdd6 100644 --- a/test/utils/utils.test.js +++ b/test/utils/utils.test.js @@ -1,5 +1,7 @@ const path = require('path') const { loadVCAPServices } = require('../../lib/utils') +const cds = require('@sap/cds') +const LOG = cds.log('audit-log') jest.mock('fs', () => ({ readFileSync: jest.fn() @@ -13,11 +15,14 @@ describe('Test loadVCAPServices', () => { const INVALID_JSON = 'invalid json' const FAKE_PATH = '/path/to/vcap.json' + let logSpy + beforeEach(() => { delete process.env.VCAP_SERVICES_FILE_PATH delete process.env.VCAP_SERVICES jest.clearAllMocks() + logSpy = jest.spyOn(LOG, 'debug').mockImplementation(() => {}) }) afterAll(() => { @@ -33,6 +38,7 @@ describe('Test loadVCAPServices', () => { expect(fs.readFileSync).toHaveBeenCalledWith(path.resolve(FAKE_PATH), 'utf8') expect(result).toEqual(FAKE_VCAP) + expect(logSpy).toHaveBeenCalledWith(`VCAP_SERVICES loaded from file at ${FAKE_PATH}`) }) test('throws error when reading VCAP_SERVICES_FILE_PATH fails', () => { @@ -48,6 +54,7 @@ describe('Test loadVCAPServices', () => { expect(() => loadVCAPServices()).toThrow( `Failed to read or parse VCAP_SERVICES from file at ${FAKE_PATH}: ${errorMessage}` ) + expect(logSpy).not.toHaveBeenCalled() }) test('throws error when JSON in VCAP_SERVICES_FILE_PATH is invalid', () => { @@ -58,6 +65,7 @@ describe('Test loadVCAPServices', () => { expect(() => loadVCAPServices()).toThrow( new RegExp(`^Failed to read or parse VCAP_SERVICES from file at ${FAKE_PATH}:`) ) + expect(logSpy).not.toHaveBeenCalled() }) test('loads and parses VCAP_SERVICES from environment variable', () => { @@ -66,6 +74,7 @@ describe('Test loadVCAPServices', () => { const result = loadVCAPServices() expect(result).toEqual(FAKE_VCAP) + expect(logSpy).toHaveBeenCalledWith('VCAP_SERVICES loaded from environment variable') expect(fs.readFileSync).not.toHaveBeenCalled() }) @@ -73,6 +82,7 @@ describe('Test loadVCAPServices', () => { process.env.VCAP_SERVICES = INVALID_JSON expect(() => loadVCAPServices()).toThrow(new RegExp(`^Failed to parse VCAP_SERVICES from environment variable:`)) + expect(logSpy).not.toHaveBeenCalled() expect(fs.readFileSync).not.toHaveBeenCalled() }) @@ -80,6 +90,7 @@ describe('Test loadVCAPServices', () => { const result = loadVCAPServices() expect(result).toEqual({}) + expect(logSpy).not.toHaveBeenCalled() expect(fs.readFileSync).not.toHaveBeenCalled() }) @@ -96,5 +107,6 @@ describe('Test loadVCAPServices', () => { expect(result).toEqual(fromFile) expect(fs.readFileSync).toHaveBeenCalledTimes(1) + expect(logSpy).toHaveBeenCalledWith(`VCAP_SERVICES loaded from file at ${FAKE_PATH}`) }) })