Conversation
Prepare `develop` for the next minor release. - bump version `2.0.1` → `2.1.0` - add `## Version 2.1.0 - tbd` changelog skeleton (Added / Changed / Fixed) Feature PRs merging into `develop` add their bullet under this section; the date is set when `develop` → `main` is promoted.
Add `develop` to the CI workflow's `push` and `pull_request` branch triggers so PRs into `develop` (the new integration branch) get lint + the test matrix. `main` triggers unchanged. HANA remains `workflow_dispatch`-only.
SummaryThe following content is AI-generated and provides a summary of the pull request: Release:
|
There was a problem hiding this comment.
This is a clean, minimal release-preparation PR. The only thing to address before merging to main is trimming the empty changelog sub-sections (### Added, ### Changed, ### Fixed) that have no entries — leaving them in would result in bare headings in the published changelog.
PR Bot Information
Version: 1.29.18
- LLM:
anthropic--claude-4.6-sonnet - Correlation ID:
fd848e30-9239-11f1-802e-ae2522cdec59 - Event Trigger:
pull_request.opened - File Content Strategy: Full file content
| ### Added | ||
|
|
||
| ### Changed | ||
|
|
||
| ### Fixed |
There was a problem hiding this comment.
Best Practices: Empty changelog sub-sections will be published as-is if merged without content.
The ### Added, ### Changed, and ### Fixed headers under ## Version 2.1.0 are currently empty placeholders. Before merging into main, any sub-section that has no entries should be removed so the published changelog doesn't contain bare headings with nothing beneath them. Consider removing the unused sub-sections as part of the final merge preparation.
| ### Added | |
| ### Changed | |
| ### Fixed | |
| ### Changed |
Double-check suggestion before committing. Edit this comment for amendments.
Please provide feedback on the review comment by checking the appropriate box:
- 🌟 Awesome comment, a human might have missed that.
- ✅ Helpful comment
- 🤷 Neutral
- ❌ This comment is not helpful
## Problem
Cloud SDK v4 exposes `executeHttpRequest` /
`executeHttpRequestWithOrigin` as **getter-only properties**. The plugin
patched them with plain assignment, which silently fails on a getter —
so the Cloud SDK outbound path (CAP's default when
`@sap-cloud-sdk/http-client` is installed) produced **no CLIENT span**
and no `sap.btp.destination`.
## Fix
Patch via `Object.defineProperty(cloudSDK, name, { value, writable:
true, configurable: true })` — `writable`/`configurable` keep the
exports re-patchable. Verified the wrapper now fires end-to-end.
## Tests
- `test/tracing-remote-cloudsdk.test.js` — Cloud SDK path: asserts a
`@cap-js/telemetry` CLIENT span with `sap.btp.destination`, and no
undici span.
- `test/tracing-remote-native.test.js` — native-fetch path: asserts the
span comes from `@opentelemetry/instrumentation-undici` (not `-http`)
with `http.*`/`url.*`/`server.*` attributes.
Both drive a real local HTTP call; gated on `cds.version >= 9`.
Changelog updated.
---------
Co-authored-by: sjvans <sjvans@users.noreply.github.com>
Add `target-branch: 'develop'` to both dependabot update entries (npm + github-actions) so dependency-bump PRs open against `develop` instead of the default branch (`main`). They then promote to `main` via the reviewed `develop → main` PR, like every other change.
…ucture (#465) ## Added Wraps `cds.Service.prototype.tx()` so the queue worker's two-transaction structure (tx1: SELECT+UPDATE lock, tx2: handle+DELETE dispatch) appears as coherent `<service> - tx` spans under the `cds.spawn - run task` root, instead of each top-level CAP call becoming an orphan root. Guarded so `$batch` sub-requests (active `EventContext`) are unaffected; file-based messaging consumer delivery (bare `{}` context) still gets a root span. ## Test infrastructure Replaces fragile `cds.test.log()` regex assertions with a structured in-memory span exporter (`MyInMemorySpanExporter`) and `groupedByTrace()` / `rootSpans()` helpers. Rewrites the existing tracing suites and adds coverage for scheduled tasks, outboxed batch fan-out, and inbox/outbox messaging combinations. ## SQLite note Queue-worker suites skip on sqlite (published `@sap/cds` uses a `setTimeout` bypass, not `cds.spawn`) — verified on HANA in CI. Follow-up #467 removes the skips once the cds queue-spawn fix ships. Changelog updated. Targets `develop`.
Supersedes the #437 stash. Migrates the test runner jest → vitest. ## Why it's a clean win Vitest with `pool: 'forks'` + per-file isolation tears each test file's child process down when the file finishes — so the OTLP exporter's lingering handles die with the child, and **the suite exits cleanly with no `--forceExit`**. This is the same open-handle class that hangs jest (see #472/#466). Verified: exit 0, ~8s, no hang across repeated runs. ## Config (`vitest.config.mjs`) - `globals: true` — `describe/test/beforeEach/...` stay available, test bodies unchanged. - `pool: 'forks'`, `isolate: true`, `teardownTimeout: 1000` — the clean-exit mechanism (documented inline). - Ports the old `jest.config.js` HANA logic faithfully: default `testTimeout: 42000`; under `CI && HANA_DRIVER` → `include` restricted to `tracing-attributes` + `passport`, timeout ×10, `cds_requires_telemetry_tracing` set when `HANA_PROM`. Verified the subset selection. ## Changes - `package.json`: `test` → `vitest run --silent`; jest removed, vitest added. `jest.config.js` deleted. - 8 `jest.spyOn`/`jest.fn` → `vi.spyOn`/`vi.fn`. - 5 `beforeAll(done => …)` hooks → promise-returning (vitest treats a hook arg as a fixture). - `eslint.config.mjs`: test-files override declaring `vi` global. Lint clean. - Lockfile regenerated against public npm (0 internal-registry URLs). ##⚠️ Behavioral note — HTTP instrumentation disabled in the test app Under jest, OTel's `require-in-the-middle` http patching was **silently broken** by jest's module sandbox, so incoming HTTP SERVER spans never existed in tests (the existing `xtest` skips document this). Under vitest (real `require`) the instrumentation works and reparents trace trees, breaking several assertions. To keep this migration **behavior-neutral**, `test/bookshop/package.json` now sets `disableIncomingRequestInstrumentation` + `disableOutgoingRequestInstrumentation` on the http instrumentation — reproducing jest's effective environment. Consequence: the HTTP-instrumentation path stays untested (same blind spot as jest, now explicit config rather than an accident). **Follow-up issue filed to enable it and assert on the real incoming spans.** The tracing-attributes client-span assertions still pass because those spans come via undici / cloud-sdk, not instrumentation-http. ## Coordination Parallel PR #473 (prettier) touches `package.json` (additive) + lockfile. Lockfile will conflict — whichever merges second rebases. #473 excludes `jest.config.js` from formatting (this PR deletes it). ## Verified `npm run test` 53 pass / 14 skip, exit 0, ~8s, clean exit ×3 · `npm run lint` clean · HANA subset selection confirmed.
Supersedes the #438 oxfmt spike — adopts **oxfmt** (`0.63.0`, pinned) properly. ## Config (`.oxfmtrc.jsonc`) Based on the #438 spike, verified against the `lib/*.js` house style: `singleQuote`, `semi: false`, `printWidth: 120`, `tabWidth: 2`, `trailingComma: none`, `arrowParens: avoid`. `ignorePatterns` excludes `*.md`, `node_modules`, `package-lock.json`, `CHANGELOG.md`, and `jest.config.js` (see coordination). ## Scripts - `format` → `npx oxfmt` (write is oxfmt's default) - `format:check` → `npx oxfmt --check` No git hook / husky / lint-staged — CI `format:check` + scripts only (the intrusive hook from the #438 spike is intentionally not carried over). ## Commits (reviewable split) 1. `chore: add oxfmt formatter tooling` — config + scripts + devDep + lockfile + CI step 2. `chore: apply oxfmt formatting` — repo-wide reformat (11 files, line-wrapping only; verified non-semantic via `git diff -w`) ## eslint coexistence `@sap/cds/eslint.config.mjs` is `recommended` + `no-unused-vars`/`no-console` only (no stylistic rules) → no conflict. `npm run lint` stays green. ## CI One line added to the `lint` job in `ci.yml`: `npm run format:check`. ## Verified `npm run format:check` ✅ (56 files) · `npm run lint` (--max-warnings=0) ✅ · `npm run test` → 53 pass / 14 skip, exit 0 · lockfile resolved from public npm (0 internal-registry URLs). ## Coordination Parallel PR #474 (jest→vitest) deletes `jest.config.js` — this PR excludes it from formatting (0-line diff confirmed) so no collision. `package.json` change here is additive (scripts + devDep); lockfile will conflict with #474 — whichever merges second rebases.
… ConsoleMetricExporter (#479) ## What Consolidates all outbox/metrics test-quality work into one PR (formerly split as #479 + the stacked #480). - **In-memory metric reader** — `test/bookshop/lib/MyInMemoryMetricReader.js`, the metrics counterpart to `MyInMemorySpanExporter` (#465). Mirrors production **DELTA** temporality: SUM counters are accumulated across flushes into per-series running totals; GAUGE datapoints keep the latest absolute value. Wired via the `metrics-outbox`, `metrics-outbox-disabled`, and `metrics` profiles in `.cdsrc.json`. - **Outbox suites off console spying** — the three `metrics-outbox*.test.js` suites drop the `console.dir` spy and fixed `wait()` sleeps in favor of the reader + an `expectEventually()` force-flush polling helper (fails fast if the meter provider isn't wired). Folds in #445's polling approach. - **ConsoleMetricExporter unit test** — new `test/console-metric-exporter.test.js`, a pure unit test of the exporter's formatting (db.pool table, queue table, other single-vs-array, tenant variants, host-metrics aggregation, shutdown→FAILED), mirroring `console-span-exporter.test.js`. - **`metrics.test.js`** converted from scraping `cds.test.log()` output to asserting on the in-memory reader's datapoints. Metrics testing now mirrors the tracing side exactly: a to-console unit test **plus** in-memory-exporter–based integration tests. ## Why Follow-up to #465 (span test infra): eliminate console/log spying in the metrics suite and give `ConsoleMetricExporter` direct unit coverage. ## Review addressed - Bot review triaged: explicit `COUNTER_METRIC_NAMES` dispatch for `isCounter`; real wall-clock debounce in the multitenant test; isolation NOTE on the module-level singletons. - Dropped the unused debug-log silencer in the multitenant suite (never asserted). Kept the single-tenant `debugLog` mock — it backs a real `unknown service` assertion. Test-only change (no `lib/` change), so no CHANGELOG entry — consistent with #465/#474/#476. closes #478 Supersedes #445 and #480 (both folded in here) — I'll close them once this merges.
…-logs, #482) (#483) ## What Recreates dependabot #472 against `develop`, bumping the OTLP dev-dependency group to 0.221: - `@opentelemetry/exporter-metrics-otlp-grpc`: `^0.219` → `^0.221` - `@opentelemetry/exporter-metrics-otlp-proto`: `^0.219` → `^0.221` - `@opentelemetry/exporter-trace-otlp-grpc`: `^0.219` → `^0.221` - `@opentelemetry/exporter-trace-otlp-proto`: `^0.219` → `^0.221` - `@opentelemetry/instrumentation-host-metrics`: `^0.2.0` → `^0.4.0` - `@opentelemetry/instrumentation-runtime-node`: `^0.32.0` → `^0.34.0` ## The pin Adds a top-level `overrides` block pinning `@opentelemetry/sdk-logs` to `0.219.0`: ```json "overrides": { "@opentelemetry/sdk-logs": "0.219.0" } ``` `@opentelemetry/sdk-logs` 0.221 introduces an unbounded memory leak that OOM-crashes `test/logging.test.js` (heap climbs to ~3.8GB, crash after ~110s). Root-caused and tracked in #482. The pin keeps sdk-logs at 0.219 while everything else moves to 0.221, so `logging.test.js` behaves like `develop` again (passes in ~5s). Remove the pin once #482 is fixed. ## Verification - `test/logging.test.js`: passes in ~4.9s, exits promptly (no OOM/hang) - Full suite: 63 passed / 14 skipped, exits cleanly in ~7.4s - Lint (`eslint . --max-warnings=0`) + format (`oxfmt --check`): clean - Lock resolves sdk-logs to `0.219.0` while `exporter-trace-otlp-proto` is `0.221.0` — pin is surgical Supersedes #472. No CHANGELOG entry (dev-deps only). Refs #482.
…, §5) (#481) ## What Started as removing dead test exclusions for #477 (§2 cds<9 guards, §5 HANA CI test-subset). Removing the HANA subset surfaced HANA-only failures — including **two real production bugs** that the old 2-file subset had been masking — so this PR also fixes those. ### Production fixes (lib/) - **fix(tracing):** raw SQL leaked into HANA `INSERT` `prepare` span names. The name-normalization regex used `.` which doesn't match newlines, so HANA's multi-line `INSERT … WITH SRC AS (…)` SQL survived in the span name. Now uses `[\s\S]` so it's stripped to operation + table (matching SELECT); the SQL stays in `db.query.text`. - **fix(metrics):** `*_storage_time_in_seconds` gauges were skewed by the machine's UTC offset on HANA — HANA's `min()`/`max()` aggregates return timezone-naive timestamps that `new Date()` parsed as local time. Normalize to UTC before parsing. Both carry CHANGELOG `### Fixed` entries. ### Test-suite changes (§2/§5 + HANA robustness) - Remove all 8 dead `cds < 9` guards (§2) and the HANA CI 2-file test-subset (§5) so the full suite runs on HANA. - Convert queue/outbox span assertions to force-flush + poll (spans export after fixed waits on slower HANA); filter the outbox-scan trace primer out of the logging assertion; fix a lifecycle bug where a retry handler fired with an undefined counter. - **HANA CI config** (`vitest.config.mjs`, HANA-only): run files serially (all files share one HDI container vs sqlite's per-file in-memory DB), raise `hookTimeout`, HANA-only outbox settle in `afterAll`, and `retry: 2` for residual shared-remote-container timing variance. sqlite unchanged (retry:0, full parallelism). ### Skips (deviation from "no skips except passport" — conscious) - Multitenancy tests (`tracing-mt`, `metrics-outbox-multitenant`) **skip on HANA** with an explanatory comment: they need a bound BTP Service Manager for MTX tenant subscription, which the single pre-provisioned HDI container in CI doesn't provide. They still run fully on sqlite. This means multitenancy has no HANA coverage — acknowledged; can be revisited if the CI HANA setup gains MTX. ## Verification - sqlite: 63 passed / 14 skipped / 0 failed. - HANA (serial + retry:2): 64 passed / 6 skipped / 0 failed, stable across repeated runs (the 6 skips = 2 multitenancy + 4 pre-existing xtest/TODO in tracing.test.js). Refs #477 (§1 queue-worker sqlite skips remain, gated on the cds queue-spawn fix; §3 stubs pending).
# Chore: Clean Up Release Workflow ♻️ **Refactor**: Removed an outdated workaround comment from the release workflow. ### Changes * `.github/workflows/release.yml`: Removed the `# REVISIT: remove "npm explore better-sqlite3 -- npm run install" with cds^10` comment that was no longer relevant, cleaning up the release workflow configuration. - [ ] 🔄 Regenerate and Update Summary <details> <summary>PR Bot Information</summary> **Version:** `1.29.26` - Output Template: [Default Template](https://github.tools.sap/Code-Change-Intelligence/pr-bot/blob/main/src/services/llm/prompts/summary_default_output_template.md) - Summary Prompt: [Default Prompt](https://github.tools.sap/Code-Change-Intelligence/pr-bot/blob/main/src/services/llm/prompts/summary_instructions_prompt.md) - File Content Strategy: Full file content - Correlation ID: `8ac5e9d0-97bf-11f1-9e0b-c00ff05b9b1b` - LLM: `anthropic--claude-4.6-sonnet` - Event Trigger: `pull_request.opened` </details>
…unpin sdk-logs (#482) (#489) ## Problem Bumping the OTLP dev-deps to the `0.221` line pulled `@opentelemetry/sdk-logs` 0.219 → 0.221, which made `test/logging.test.js` OOM-crash (heap climbed to ~3.8 GB, ~110 s of GC thrashing before dying). #483 shipped an interim workaround pinning sdk-logs to `0.219.0` via a top-level `overrides` block. This PR removes that pin and fixes the actual defect. Closes #482. ## Root cause Two things combine: 1. **Constructor signature change (the real trigger).** In sdk-logs 0.221 the `SimpleLogRecordProcessor` and `BatchLogRecordProcessor` constructors changed from positional `(exporter)` to an options object `({ exporter })`. `lib/logging/index.js` still passed the exporter positionally (both the built-in path and the custom-processor path in `_getCustomProcessor`; the test's `MySimpleLogRecordProcessor` extends the SDK base and forwards its args). So `options.exporter` was `undefined`, and every emit called `core.internal._export(undefined, …)`, which throws. 2. **Diag re-entrancy loop.** The thrown export error hits `.catch(globalErrorHandler)` → `diag.error(...)`. Because `lib/index.js` wires `diag.setLogger(cds.log('telemetry'), …)`, that diagnostic goes back through `cds.log('telemetry')` → the overridden `cds.log.format` → `logger.emit()` → export throws again → … an unbounded loop. Each hop is a **microtask** (`processTicksAndRejections`), so the recursion is asynchronous. ## Fix - Construct the log processors with the `{ exporter }` options object 0.221 expects (built-in + custom-processor paths). This stops the export from throwing, which removes the source of the diag error loop — the OOM is gone. - Add a re-entrancy guard (`let emitting`) around `logger.emit()` in the `cds.log.format` interception: while inside our own `emit()` we still run the original format work (log output unchanged) but skip re-emitting. This is defense-in-depth against any **synchronous** re-entry through the export/diag path; `try/finally` guarantees the flag resets even if `emit()` throws. ## Unpin - Removed the `@opentelemetry/sdk-logs` `overrides` pin; regenerated the lockfile. sdk-logs now floats to `0.221.0`, matching the other OTLP deps. This removes the #483 workaround pin. ## Verification - `vitest run test/logging.test.js` — passes in ~1.3 s (was ~3.8 GB / ~110 s OOM crash on 0.221). The `logging.test.js` OOM was the repro; ran it repeatedly, stable. - Full sqlite suite: 63 passed / 14 skipped, no regressions. - eslint `--max-warnings=0` clean; `oxfmt --check` clean. - Lockfile: no internal-registry URLs; `npm ci` reproduces cleanly.
…er; drop tracing-attributes profile (#478) (#490) ## What Group 1 of #478. Converts the three tracing tests that still spied on `console.dir` to read structured `ReadableSpan` objects directly from the in-memory span exporter: - `test/tracing-remote-cloudsdk.test.js` - `test/tracing-remote-native.test.js` - `test/tracing-span-names.test.js` Each now uses `--profile tracing-in-memory` (which wires `MyInMemorySpanExporter` via `.cdsrc.json`) and reads over `captured` (the module-level array of `ReadableSpan`s) instead of the `console.dir` spy. `beforeEach(reset)` clears the buffer per test. All existing assertions are preserved verbatim — `instrumentationScope.name` filters (`@cap-js/telemetry`, `@opentelemetry/instrumentation-undici`), span names, and attributes (`code.function.name`, `db.query.text`, `sap.btp.destination`, the no-raw-SQL / no-URL-in-span-name checks). `captured` holds full ReadableSpans with `instrumentationScope`, so the filters just point at `captured`. No flush/poll was needed: the spans for these single request/DB ops appear synchronously in `captured` after the awaited call. In `tracing-span-names.test.js`, `data.reset()` is itself traced, so the buffer is cleared *after* reset (mirroring `tracing-attributes.test.js`). ## Why No more `console.dir` spying in the tracing tests. With all three files migrated, the `[tracing-attributes]` profile in `test/bookshop/.cdsrc.json` has no remaining consumers (verified: only these 3 used it; `tracing-attributes.test.js` despite its name already uses `tracing-in-memory`), so it is removed. This also resolves the deferred "rename tracing-attributes → tracing-console" note — the profile simply goes away. Test-only change. No lib change, no CHANGELOG. Refs #478 (group 1 only; group 2 done via #479, group 3 stays).
…ans + traceparent propagation (#475) (#491) ## What Re-enables HTTP instrumentation in the test app (disabled by #474 to stay behavior-neutral under jest) and asserts the incoming-request tracing behavior it produces. - **Test-app config** (`test/bookshop/package.json`): removed `disableIncomingRequestInstrumentation` / `disableOutgoingRequestInstrumentation` so both default to on; kept `ignoreIncomingRequestHook`. Incoming HTTP requests now produce a SERVER span (the root of each request trace) and outgoing requests produce CLIENT spans. - **`GET with traceparent is traced`** (was `xtest`): asserts the incoming SERVER span adopts the W3C context from the `traceparent` header — trace id `0af7651916cd43dd8448eb211c80319c`, parent span id `b7ad6b7169203331`. - **`instrumentation hooks`** (was empty `xtest`): asserts both suppression mechanisms — the sampler's `ignoreIncomingPaths` (`/odata/v4/admin/Authors`) and `MyIgnoreIncomingRequestHook` (`/Books(252)`) — produce NO incoming SERVER span, while a non-ignored path (`/Books`) does. - **`$batch is traced`**: updated for reparenting — the single `$batch` POST now yields ONE incoming SERVER root with both batch sub-operations (CREATE Genres draft, READ Genres) nested beneath it (was 2 roots). - **tracing-messaging CHECKs** (`without-outbox`, `inboxed`, `persistent-outbox`): the producer trace is now rooted at the incoming SERVER span (`AdminService - tx` nests under it), so the root assertion was updated from name `AdminService - tx` to `SpanKind.SERVER` while keeping the same nested-span assertions. ## Why The test's HTTP client runs in-process; with outgoing instrumentation now on, it would itself create a CLIENT span (an artificial extra root that also overwrites a manually-set `traceparent`). A shared `asExternalClient` helper runs client-side requests under `suppressTracing` to model a real external, un-instrumented caller — the outgoing CLIENT artifact is skipped and the genuine incoming SERVER span is the trace root. No `lib/` change (config + tests only). No CHANGELOG. ## Verification (sqlite) - `test/tracing.test.js` — 11 passed / 2 skipped (the 2 formerly-`xtest` now real + passing; `$batch` fixed; #477 §3 stubs remain skipped) - `test/tracing-remote-cloudsdk.test.js` + `test/tracing-remote-native.test.js` — pass (outgoing CLIENT instrumentation didn't break them) - Full suite — 65 passed / 12 skipped (was 63 / 14; the 2 newly-enabled tests moved skipped→passed) - lint + oxfmt clean; lockfile untouched ## HANA note `inboxed` and `persistent-outbox` messaging tests are HANA-only (skipped on sqlite) but share the same reparented producer-root assertion, so they were updated too — HANA CI must be verified on this PR. closes #475
## What
Extracts the six test helpers that had been copy-pasted across ~10 test
files into one shared module, `test/bookshop/lib/test-utils.js`:
- `flushSpans()` — unwrap the ProxyTracerProvider → delegate and
`forceFlush()`.
- `eventually(fn, { flush, timeout, interval })` — the unified
state-based poll. The span `eventually` and the metric
`expectEventually` were structurally identical, differing only in the
flush target + defaults, so they are now one function. `flush` defaults
to `flushSpans` (span callers); metric callers pass the reader's
`forceFlush` and their own timeout/interval.
- `clearOutbox(timeout = 5000)` — timeout-bounded `DELETE FROM
cds.outbox.Messages`.
- `asExternalClient(fn)` — runs `fn` under `suppressTracing`.
- `isOutboxScanTrace(g)` / `meaningful(groups)` — the outbox-scan-trace
filter, reconciled to a single `meaningful(groups)` signature.
## Why
These helpers landed independently during the HANA / instrumentation
work and drifted into ~10 near-identical copies. Centralizing them
removes the duplication (net -141 lines) and gives one place to maintain
the polling / flush / outbox-scan-filter logic.
## Notes
- **Behavior-preserving refactor** — no assertion changes, no
timeout/interval drift (each call site passes through its original
values), no predicate changes.
- Test-only. No `lib/**` change, no CHANGELOG (test-only), no dependency
change.
- `test-utils.js` stays dependency-light: it only requires
`@opentelemetry/api`, `@opentelemetry/core`, and `node:timers/promises`
— it does **not** `require('@sap/cds')` at module top (same rule that
protects the sibling exporters/reader). `clearOutbox` uses the global
`DELETE` provided by `cds.test()` at call time.
## Verification
- Full sqlite suite: **65 passed / 12 skipped** — matches the develop
baseline exactly.
- Per-file runs (metrics-outbox, metrics-outbox-multitenant, metrics,
tracing, tracing-messaging-without-outbox) all pass; the sqlite-gated
tracing files (outboxed-batch, scheduled) skip as before.
- eslint `--max-warnings=0` clean, `oxfmt --check` clean.
## HANA CI
Several affected files run **only on HANA** and share these helpers, so
they cannot be validated locally — please confirm HANA CI is green for:
- `test/tracing-scheduled.test.js`
- `test/tracing-messaging-inboxed.test.js`
- `test/tracing-messaging-persistent-outbox.test.js`
- `test/tracing-outboxed-batch.test.js`
(`test/tracing-messaging-without-outbox.test.js` does run on sqlite and
exercises the shared `meaningful`/`eventually` path — confirmed
passing.)
closes #488
…#486) (#493) ## What Replaces the load-order-fragile `process.env.cds_requires_*` / `process.env.cds_*` string-JSON test config with proper cds config: `.cdsrc.json` profiles (composed with `--profile`) and `cds.test()` args. Test-only + test-app-config change; no `lib/` change. ## Why Setting config via `process.env.cds_*` string-JSON at module top is load-order-sensitive: it must run before any `@sap/cds` require or it is silently ignored (the root of the removed `delete cds.env` hack). cds already supports the same config declaratively via `.cdsrc.json` profiles. Root cause of the old `// REVISIT: ... package.json wins` comments (why some config had to be done via env): cds loads config sources sequentially, `package.json` **after** `.cdsrc.json`, last-writer-wins. So a `.cdsrc.json` profile could never override a key that `package.json` set at its base. Fix: move those base defaults (`log.cls_custom_fields`, `messaging.kind`/`file`) from `test/bookshop/package.json` into `test/bookshop/.cdsrc.json` base, so the `.cdsrc.json` profiles (same source) win as intended. ## Sites moved to profiles (all 8 — zero `process.env.cds_*` remain) | Site | Was | Now | |---|---|---| | `logging.test.js` (cls_custom_fields, tracing.exporter:false) | `cds_log`, `cds_requires_telemetry_tracing` | `[logging]` profile (+ base moved out of package.json) | | `tracing.test.js` (sampler ignoreIncomingPaths) | `cds_requires_telemetry_tracing_sampler` | new `[sampler-ignore-authors]` profile, `--profile 'tracing-in-memory, sampler-ignore-authors'` | | `tracing-remote-native.test.js`, `tracing-attributes.test.js` (native_fetch) | `cds_remote_native__fetch` | new `[native-fetch]` profile (`remote.native_fetch: true`), `--profile 'tracing-in-memory, native-fetch'` | | `passport.test.js` (scheduling off) | `cds_requires_scheduling` | new `[no-scheduling]` profile, `cds.test(dir, '--profile', 'no-scheduling')` | | `tracing-messaging-{inboxed,persistent-outbox,without-outbox}.test.js` (messaging kind/file/flags) | `cds_requires_messaging` | existing `[inboxed]`/`[persistent-outbox]`/`[without-outbox]` profiles now fully carry it (messaging base moved out of package.json) | ## .cdsrc.json changes - Added base `requires.messaging` (local-messaging / msg-box) and `log.cls_custom_fields` (moved from package.json). - `[logging]`: added `requires.telemetry.tracing.exporter: false`. - New profiles: `[sampler-ignore-authors]`, `[native-fetch]`, `[no-scheduling]`. ## Verification - Full sqlite suite: **65 passed / 12 skipped** (identical to baseline, same skip set). - Each converted file passes individually on sqlite. - `grep -rn "process.env.cds_" test/` → none remain. - eslint (test/) + oxfmt clean; `grep -c int.repositories.cloud.sap package-lock.json` = 0. ## HANA CI caveat Profile changes are DB-agnostic, but these run on HANA and could only be validated for config equivalence locally (config resolves identically to the old env), not executed: `logging` (runs on both), and the HANA-only `tracing-messaging-inboxed` + `tracing-messaging-persistent-outbox`. HANA CI must validate them. No lib change. No behavior change. Closes #486
#494) ## What Consolidates the hard-won test-suite knowledge that was scattered (and duplicated) across `test/**` comment blocks into a single, authoritative top-level **`TESTING.md`**, then trims the duplicated in-file comments down to short pointers. **New `TESTING.md`** covers: - Running the tests (`npm test` / vitest, sqlite default; CI matrix Node 22/24 × cds 9/10; where the bookshop app lives) - sqlite vs HANA (per-file in-memory DB vs one shared HDI container, and the implications: serial file execution, raised timeouts, `retry: 2`, outbox clear/settle; how the HANA path is signalled via `TELEMETRY_TEST_HANA`; HANA is a separate `workflow_dispatch`-only workflow, not PR CI) - Config via `.cdsrc.json` profiles (not env) — the profile table + compose syntax + the #486 load-order gotcha (don't reintroduce `process.env.cds_*`) - In-memory test infrastructure (`MyInMemorySpanExporter` / `MyInMemoryMetricReader`, DELTA temporality, the no-`require('@sap/cds')`-at-top rule) - Shared test helpers (`test/utils.js` from #488) and the flush+poll pattern - HTTP instrumentation (#475) and why client requests are wrapped in `asExternalClient` - The only two sanctioned skips (#477): SAP Passport on sqlite; multitenancy on HANA — plus the #477-tracked debt skips - Known caveats (the `startup > NO_TELEMETRY=true` local-env artifact; the internal-registry lockfile trap) **Trimmed duplicated comments to pointers** (comment-only, no logic change) in: `tracing-scheduled`, `tracing-outboxed-batch`, `tracing-messaging-inboxed`, `tracing-messaging-persistent-outbox`, `tracing-messaging.js`, `tracing-mt`, `metrics-outbox-multitenant`. The repeated `cds.spawn on sqlite` skip rationale, the shared-HANA-container outbox-bleed explanation, and the multitenancy Service-Manager note now live in TESTING.md; each site keeps a one-line pointer. Genuinely local rationale (timezone-bug explanation, per-test tree shapes, `test/utils.js` own doc comments) is left untouched. **README** gets a one-line link to TESTING.md under the contributing section. ## Notes - No lib/behavior change. Test-file edits are **comment-only** (verified: every changed line in `test/**` starts with `//`). No change to test logic, assertions, config, `vitest.config.mjs`, `.cdsrc.json`, the exporters/reader, or the #477 gated skip logic. - Full sqlite suite unchanged: **65 passed / 12 skipped**. - `oxfmt --check` clean; changed files lint clean; `grep -c int.repositories.cloud.sap package-lock.json` = 0 (lockfile untouched). - No CHANGELOG entry (docs/test-only, not user-facing lib). closes #487
Release:
v2.1.0Standing release PR — accumulates everything merged into
developfor the next minor. Do not squash-merge; review and merge with a team member to satisfy themainreview requirement. Description kept current as PRs land ondevelop.Changes so far
package.json/CHANGELOG.md: version →2.1.0;## Version 2.1.0changelog section.develop(ci: run on pull requests to develop #469); dependabot targetsdevelop(ci: target develop for dependabot updates #470).<service> - txspans under thecds.spawn - run taskroot (feat: trace queue worker transactions + structured span test infrastructure #465), plus structured in-memory span test infrastructure (MyInMemorySpanExporter) replacing console-log-regex assertions.@sap-cloud-sdk/http-clientexports patched viaObject.defineProperty; added to devDeps so the path runs in CI; cloud-sdk + native-fetch tracing tests (fix: trace Cloud SDK outbound requests (getter-only exports) #451).--forceExitvia forked-pool teardown (chore: migrate test runner from jest to vitest #474).oxfmtcode formatter +format/format:checkscripts + CI check (chore: adopt oxfmt for code formatting #476).MyInMemoryMetricReader(DELTA temporality) instead ofconsole.dirspying; added aConsoleMetricExporterunit test; state-basedexpectEventuallypolling replaces fixed sleeps (test: capture outbox+console metrics via in-memory reader & unit-test ConsoleMetricExporter #479).0.221, with@opentelemetry/sdk-logspinned to0.219viaoverrides(0.221 leaks memory via the log-format re-entrancy — see Memory leak: sdk-logs 0.221 re-enters cds.log.format interception → logging.test.js OOM #482) (chore(deps-dev): bump the OTLP dev-dependency group to 0.221 (pin sdk-logs, #482) #483).preparespan names, and*_storage_time_in_secondsgauges skewed by the machine's UTC offset (test: run full suite on HANA + fix two HANA span/metric bugs (#477 §2, §5) #481).@opentelemetry/sdk-logs0.221's export path — log processors are constructed with the arg shape the installed sdk-logs version expects (version-adaptive), plus a re-entrancy guard; the temporary sdk-logs 0.219 pin (chore(deps-dev): bump the OTLP dev-dependency group to 0.221 (pin sdk-logs, #482) #483) is removed (fix(logging): guard cds.log.format interception against re-entrancy; unpin sdk-logs (#482) #489, closes Memory leak: sdk-logs 0.221 re-enters cds.log.format interception → logging.test.js OOM #482).console.dirspan-scraping in tracing tests — remote/span-name tests now read the in-memory span exporter; dropped the unusedtracing-attributesprofile (test: convert remote/span-name tracing tests to in-memory span exporter; drop tracing-attributes profile (#478) #490, Replace console spying in tests with in-memory exporters #478 group 1).Follow-ups tracked (not blocking this release)
compiled of: