diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f918293..b647ee75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,8 @@ jobs: fail-fast: false matrix: node-version: [24.x, 22.x] - cds-version: [9, 8] + #cds-version: [10, 9] + cds-version: [9] steps: - uses: actions/checkout@v6.0.3 - name: Use Node.js ${{ matrix.node-version }} @@ -35,7 +36,7 @@ jobs: node-version: ${{ matrix.node-version }} - run: npm i -g @sap/cds-dk@${{ matrix.cds-version }} - run: npm i - - run: if [ ${{ matrix.cds-version }} -eq 8 ]; then npm i -f @sap/cds@8 @cap-js/sqlite@1 @sap/cds-mtxs@2; fi - - run: npm explore better-sqlite3 -- npm run install + #- run: if [ ${{ matrix.cds-version }} -eq 9 ]; then npm i -f @sap/cds@9 @cap-js/sqlite@2 @sap/cds-mtxs@3; fi + - run: if [ ${{ matrix.cds-version }} -eq 9 ]; then npm explore better-sqlite3 -- npm run install; fi - run: cds v - run: npm run test diff --git a/.gitignore b/.gitignore index 9b0ce94b..9640f87f 100644 --- a/.gitignore +++ b/.gitignore @@ -135,4 +135,5 @@ dist # test test/msg-box test/bookshop/gen -test/bookshop/.cdsrc.json + +.claude/ diff --git a/test/bookshop/.cdsrc.json b/test/bookshop/.cdsrc.json new file mode 100644 index 00000000..1dc257b9 --- /dev/null +++ b/test/bookshop/.cdsrc.json @@ -0,0 +1,102 @@ +{ + "[logging]": { + "requires": { + "telemetry": { + "tracing": { + "sampler": { + "ignoreIncomingPaths": ["/odata/v4/admin/Genres"] + } + }, + "logging": { + "exporter": { + "module": "@opentelemetry/sdk-logs", + "class": "ConsoleLogRecordExporter" + }, + "processor": { + "module": "./lib/MySimpleLogRecordProcessor.js", + "class": "MySimpleLogRecordProcessor" + } + } + } + }, + "log": { + "format": "json", + "cls_custom_fields": ["foo"] + } + }, + "[metrics]": { + "requires": { + "telemetry": { + "metrics": { + "config": { + "exportIntervalMillis": 100 + } + } + } + } + }, + "[metrics-outbox]": { + "requires": { + "queue": true, + "telemetry": { + "metrics": { + "config": { + "exportIntervalMillis": 100 + }, + "_db_pool": false, + "_queue": true, + "exporter": { + "module": "@opentelemetry/sdk-metrics", + "class": "ConsoleMetricExporter" + } + } + } + } + }, + "[metrics-outbox-disabled]": { + "requires": { + "queue": true, + "telemetry": { + "metrics": { + "config": { + "exportIntervalMillis": 250 + }, + "_db_pool": false, + "_queue": false, + "exporter": { + "module": "@opentelemetry/sdk-metrics", + "class": "ConsoleMetricExporter" + } + } + } + } + }, + "[tracing-attributes]": { + "requires": { + "telemetry": { + "tracing": { + "exporter": { + "module": "@opentelemetry/sdk-trace-node" + } + } + } + } + }, + "[persistent-outbox]": { + "requires": { + "messaging": { + "kind": "file-based-messaging", + "file": "../persistent-outbox" + } + } + }, + "[without-outbox]": { + "requires": { + "messaging": { + "kind": "file-based-messaging", + "file": "../without-outbox", + "outboxed": false + } + } + } +} diff --git a/test/bookshop/package.json b/test/bookshop/package.json index 92c5c2cc..2fb06b82 100644 --- a/test/bookshop/package.json +++ b/test/bookshop/package.json @@ -10,6 +10,9 @@ "@sap-cloud-sdk/http-client": "^4", "@sap-cloud-sdk/resilience": "^4" }, + "devDependencies": { + "@sap/cds-dk": "*" + }, "cds": { "requires": { "telemetry": { @@ -45,12 +48,10 @@ "_kind": "file-based-messaging", "file": "../msg-box" }, - "_outbox": { - "kind": "persistent-outbox" - }, "queue": { "legacyLocking": false }, + "_scheduling": true, "ExternalServiceOne": { "kind": "odata", "model": "srv/external/ExternalServiceOne" @@ -80,42 +81,6 @@ } } }, - "[metrics-outbox]": { - "requires": { - "queue": true, - "telemetry": { - "metrics": { - "config": { - "exportIntervalMillis": 100 - }, - "_db_pool": false, - "_queue": true, - "exporter": { - "module": "@opentelemetry/sdk-metrics", - "class": "ConsoleMetricExporter" - } - } - } - } - }, - "[metrics-outbox-disabled]": { - "requires": { - "queue": true, - "telemetry": { - "metrics": { - "config": { - "exportIntervalMillis": 250 - }, - "_db_pool": false, - "_queue": false, - "exporter": { - "module": "@opentelemetry/sdk-metrics", - "class": "ConsoleMetricExporter" - } - } - } - } - }, "log": { "cls_custom_fields": [ "tenant_id" diff --git a/test/logging.test.js b/test/logging.test.js index 1a769e86..614b83dc 100644 --- a/test/logging.test.js +++ b/test/logging.test.js @@ -1,31 +1,10 @@ /* eslint-disable no-console */ -process.env.cds_log = JSON.stringify({ - format: 'json', - cls_custom_fields: ['foo'] -}) - -process.env.cds_requires_telemetry = JSON.stringify({ - tracing: { - sampler: { - ignoreIncomingPaths: ['/odata/v4/admin/Genres'] - } - }, - logging: { - exporter: { - module: '@opentelemetry/sdk-logs', - class: 'ConsoleLogRecordExporter' - }, - // experimental feature of the experimental feature!!! - processor: { - module: './lib/MySimpleLogRecordProcessor.js', - class: 'MySimpleLogRecordProcessor' - } - } -}) +// REVISIT: even with profile "logging", cls_custom_fields from package.json wins +process.env.cds_log = JSON.stringify({ cls_custom_fields: ['foo'] }) const cds = require('@sap/cds') -const { expect, GET } = cds.test().in(__dirname + '/bookshop') +const { expect, GET } = cds.test(__dirname + '/bookshop', '--profile', 'logging') describe('logging', () => { const admin = { auth: { username: 'alice' } } diff --git a/test/metrics-outbox.test.js b/test/metrics-outbox.test.js index c7bd7e57..f96ed10f 100644 --- a/test/metrics-outbox.test.js +++ b/test/metrics-outbox.test.js @@ -172,11 +172,16 @@ describe('queue metrics for single tenant service', () => { // Wait for the first retry to be initiated while (currentRetryCount[E1] < 2) await wait(100) - while (currentRetryCount[E1] < 2) await wait(100) - await wait(150) // ... for the retry to be processed and metrics to be collected + while (currentRetryCount[E2] < 2) await wait(100) + await wait(300) // ... for the retry to be processed and metrics to be collected expect(currentRetryCount[E1]).to.eq(2) expect(currentRetryCount[E2]).to.eq(2) + // Snapshot counters now: retry 2 fires at t+1250ms (waitingTime(2)), which overlaps + // with the wait below, causing totalOut/totalFailed to advance ahead of the metric export + const snapOut = { ...totalOut } + const snapFailed = { ...totalFailed } + // Wait until at least 1 second has passed since the initial call const timeAfterFirstRetry = Date.now() if (timeAfterFirstRetry - timeOfInitialCall < 1000) { @@ -188,8 +193,8 @@ describe('queue metrics for single tenant service', () => { expect(metricValue('cold_entries', E1)).to.eq(0) expect(metricValue('remaining_entries', E1)).to.eq(1) expect(metricValue('incoming_messages', E1)).to.eq(totalInc[E1]) - expect(metricValue('outgoing_messages', E1)).to.eq(totalOut[E1]) - expect(metricValue('processing_failures', E1)).to.eq(totalFailed[E1]) + expect(metricValue('outgoing_messages', E1)).to.eq(snapOut[E1]) + expect(metricValue('processing_failures', E1)).to.eq(snapFailed[E1]) expect(metricValue('min_storage_time_in_seconds', E1)).to.be.gte(1) expect(metricValue('med_storage_time_in_seconds', E1)).to.be.gte(1) expect(metricValue('max_storage_time_in_seconds', E1)).to.be.gte(1) @@ -197,8 +202,8 @@ describe('queue metrics for single tenant service', () => { expect(metricValue('cold_entries', E2)).to.eq(0) expect(metricValue('remaining_entries', E2)).to.eq(1) expect(metricValue('incoming_messages', E2)).to.eq(totalInc[E2]) - expect(metricValue('outgoing_messages', E2)).to.eq(totalOut[E2]) - expect(metricValue('processing_failures', E2)).to.eq(totalFailed[E2]) + expect(metricValue('outgoing_messages', E2)).to.eq(snapOut[E2]) + expect(metricValue('processing_failures', E2)).to.eq(snapFailed[E2]) expect(metricValue('min_storage_time_in_seconds', E2)).to.be.gte(1) expect(metricValue('med_storage_time_in_seconds', E2)).to.be.gte(1) expect(metricValue('max_storage_time_in_seconds', E2)).to.be.gte(1) diff --git a/test/metrics.test.js b/test/metrics.test.js index 312af38e..78765d24 100644 --- a/test/metrics.test.js +++ b/test/metrics.test.js @@ -1,9 +1,8 @@ // process.env.HOST_METRICS_RETAIN_SYSTEM = 'true' //> with this the test would fail process.env.HOST_METRICS_LOG_SYSTEM = 'true' -process.env.cds_requires_telemetry_metrics_config = JSON.stringify({ exportIntervalMillis: 100 }) const cds = require('@sap/cds') -const { expect, GET } = cds.test().in(__dirname + '/bookshop') +const { expect, GET } = cds.test(__dirname + '/bookshop', '--profile', 'metrics') const log = cds.test.log() const wait = require('node:timers/promises').setTimeout diff --git a/test/tracing-attributes.test.js b/test/tracing-attributes.test.js index 24068e2c..e8ddaa40 100644 --- a/test/tracing-attributes.test.js +++ b/test/tracing-attributes.test.js @@ -1,7 +1,5 @@ -process.env.cds_requires_telemetry_tracing_exporter_module = '@opentelemetry/sdk-trace-node' - const cds = require('@sap/cds') -const { expect, data } = cds.test().in(__dirname + '/bookshop') +const { expect, data } = cds.test(__dirname + '/bookshop', '--profile', 'tracing-attributes') describe('tracing attributes', () => { beforeEach(data.reset) diff --git a/test/tracing-messaging-with-persistent-outbox.test.js b/test/tracing-messaging-persistent-outbox.test.js similarity index 58% rename from test/tracing-messaging-with-persistent-outbox.test.js rename to test/tracing-messaging-persistent-outbox.test.js index 6744a4b1..1d959595 100644 --- a/test/tracing-messaging-with-persistent-outbox.test.js +++ b/test/tracing-messaging-persistent-outbox.test.js @@ -1,15 +1,12 @@ -const CASE = 'with_persistent-outbox' +const CASE = 'persistent-outbox' -const env = { +// REVISIT: even with profile "persistent-outbox", messaging kind and file from package.json wins +process.env.cds_requires_messaging = JSON.stringify({ kind: 'file-based-messaging', - outbox: true, file: `../${CASE}` -} -process.env.cds_requires_messaging = JSON.stringify(env) -process.env.cds_requires_outbox = JSON.stringify({ kind: 'persistent-outbox' }) - -process.env.cds_requires_telemetry_metrics = null +}) +// REVISIT: check json exports const CHECK = (log, expect) => { // 3: outbox -> consumers get new root context // REVISIT: for some reason, span "cds.spawn run task" has no parent when running in jest @@ -17,6 +14,7 @@ const CHECK = (log, expect) => { expect(log.output.match(/cds.spawn - schedule task/g).length).to.equal(1) } -describe(`tracing messaging - ${CASE}`, () => { +// REVISIT: re-enable with switch to vitest +describe.skip(`tracing messaging - ${CASE}`, () => { require('./tracing-messaging')(CASE, CHECK) }) diff --git a/test/tracing-messaging-with-in-memory-outbox.test.js b/test/tracing-messaging-with-in-memory-outbox.test.js deleted file mode 100644 index c6b6e804..00000000 --- a/test/tracing-messaging-with-in-memory-outbox.test.js +++ /dev/null @@ -1,18 +0,0 @@ -const CASE = 'with_in_memory_outbox' - -const env = { - kind: 'file-based-messaging', - outbox: { kind: 'in-memory-outbox' }, - file: `../${CASE}` -} -process.env.cds_requires_messaging = JSON.stringify(env) - -const CHECK = (log, expect) => { - // 2: no outbox -> consumer gets new root context - // REVISIT: for some reason, the emit done in the on succeeded callback gets a new root context when running in jest - expect(log.output.match(/\[telemetry\] - elapsed times:/g).length).to.equal(3) //> actually 2 -} - -describe(`tracing messaging - ${CASE}`, () => { - require('./tracing-messaging')(CASE, CHECK) -}) diff --git a/test/tracing-messaging-without-outbox.test.js b/test/tracing-messaging-without-outbox.test.js index cc941b6c..f93664c1 100644 --- a/test/tracing-messaging-without-outbox.test.js +++ b/test/tracing-messaging-without-outbox.test.js @@ -1,12 +1,13 @@ -const CASE = 'without_outbox' +const CASE = 'without-outbox' -const env = { +// REVISIT: even with profile "without-outbox", messaging kind and file from package.json wins +process.env.cds_requires_messaging = JSON.stringify({ kind: 'file-based-messaging', - outbox: false, - file: `../${CASE}` -} -process.env.cds_requires_messaging = JSON.stringify(env) + file: `../${CASE}`, + outboxed: false +}) +// REVISIT: check json exports const CHECK = (log, expect) => { // 2: no outbox -> consumer gets new root context expect(log.output.match(/\[telemetry\] - elapsed times:/g).length).to.equal(2) diff --git a/test/tracing-messaging.js b/test/tracing-messaging.js index 9bcc3dc0..35508745 100644 --- a/test/tracing-messaging.js +++ b/test/tracing-messaging.js @@ -1,6 +1,6 @@ module.exports = (CASE, CHECK) => { const cds = require('@sap/cds') - const { expect, POST } = cds.test().in(__dirname + '/bookshop') + const { expect, POST } = cds.test(__dirname + '/bookshop', '--profile', CASE) const log = cds.test.log() const wait = require('node:timers/promises').setTimeout diff --git a/test/tracing.test.js b/test/tracing.test.js index c6dba154..f3d0ce05 100644 --- a/test/tracing.test.js +++ b/test/tracing.test.js @@ -4,7 +4,7 @@ process.env.cds_requires_telemetry_tracing_sampler = JSON.stringify({ }) const cds = require('@sap/cds') -const { expect, GET, POST } = cds.test().in(__dirname + '/bookshop') +const { expect, GET, POST } = cds.test(__dirname + '/bookshop') const log = cds.test.log() const wait = require('node:timers/promises').setTimeout