Skip to content

Add srvb srvd crud#178

Merged
jfilak merged 14 commits into
jfilak:masterfrom
filak-sap:add_srvb_srvd_crud
Jun 23, 2026
Merged

Add srvb srvd crud#178
jfilak merged 14 commits into
jfilak:masterfrom
filak-sap:add_srvb_srvd_crud

Conversation

@filak-sap

Copy link
Copy Markdown
Contributor

No description provided.

Service Definitions (SRVD/SRV) carry their CDS source body at
.../source/main with Content-Type text/plain - exactly the same shape
as DDLS, programs, data elements and similar objects. The current
OBJTYPE declared empty typeuris and no editor_factory, so neither
`obj.text` nor `obj.open_editor()` worked end-to-end.

Set typeuris={'text/plain': 'source/main'} and editor_factory to
ADTObjectSourceEditor.plain_text. The standard source-editor code path
takes care of GET/PUT against the source URI and the lock handshake.

Add unit tests proving the round-trip - read returns the body verbatim,
write PUTs it to .../source/main with the correct headers and lockHandle.
A bare `ServiceBinding(connection, name)` POST is rejected by the ADT
back-end with PreconditionFailed ("Provide valid details for Service
Binding creation") - the server requires the binding shape (type,
version, category, implementation) AND a wired service entry to be
present in the create body. The legacy two-step "create-empty,
then-mutate" dance does not work; v1 must POST a fully-formed binding
in one request.

Extend ServiceBinding.__init__ to accept the missing pieces:
- package      sets adtcore:packageRef
- typ          srvb:type (ODATA, INA, SQL)
- version      srvb:version (V2, V4, 1)
- service_name adds one <srvb:content><srvb:serviceDefinition.../>
- service_version defaults to '0001' (matches what the server fills in)

When typ/version/service_name are unset the constructor stays a no-op
beyond setting the package, which preserves backwards compatibility for
the existing `rap binding publish` callers (covered by the new
test_init_without_typ_version_keeps_binding_unset test).

The marshalled POST body matches the live-captured request shape
exactly - verified by test_init_creates_full_post_body which feeds the
serialised XML through the mock Connection and asserts the request
body byte-for-byte.
Add the top-level `srvd` command group for Service Definition (SRVD/SRV)
CRUD. Mirrors sap/cli/datadefinition.py - a thin
CommandGroupObjectMaster subclass that hands off to sap.adt.ServiceDefinition.

The inherited create/read/write/activate/delete/whereused commands
work without overrides because the previous commit enabled the
text/plain source editor on the ADT class.

User-facing surface:

  sapcli srvd create  NAME DESCRIPTION PACKAGE [--corrnr CORRNR]
  sapcli srvd read    NAME
  sapcli srvd write   NAME [FILEPATH+|-] [-a] [--corrnr CORRNR] ...
  sapcli srvd activate NAME [NAME ...]
  sapcli srvd delete  NAME [NAME ...] [--corrnr CORRNR]
  sapcli srvd whereused NAME

Register the group in sap/cli/__init__.py next to the existing `rap`
alias, and add doc/commands/srvd.md mirroring the ddl.md layout.
Add the top-level `srvb` command group for Service Binding (SRVB/SVB).
The binding has no text/plain source body so the inherited `write`
command from CommandGroupObjectMaster does not apply - override
define_write to return None and lock the v1 contract with an explicit
test (test_cli_srvb_has_no_write_command). v2 will replace this with a
JSON round-trip implementation.

`srvb create` requires --type, --version, --service flags. The
back-end rejects bindings without a wired service entry with
ExceptionPreconditionFailed ("Provide valid details for Service
Binding creation"), so the CLI never offers an "empty binding" mode.
--service-version defaults to '0001' which matches the value the
server fills in for fresh bindings.

`srvb read` prints a structural summary (name / description / package
/ type / version / published / services list) instead of source code,
because there is no source body to print.

User-facing surface:

  sapcli srvb create NAME DESCRIPTION PACKAGE
                     --type {ODATA,INA,SQL} --version {V2,V4,1}
                     --service SRVD_NAME [--service-version VER]
                     [--corrnr CORRNR]
  sapcli srvb read     NAME
  sapcli srvb activate NAME [NAME ...]
  sapcli srvb delete   NAME [NAME ...] [--corrnr CORRNR]
  sapcli srvb whereused NAME

Register in sap/cli/__init__.py and add doc/commands/srvb.md.
Publish lands in the next commit.
Add `sapcli srvb publish BINDING_NAME [--service NAME] [--version VER]`.
The publish-job logic moves verbatim from sap.cli.rap.publish - same
single-service short-circuit, same multi-service filter handling, same
exit codes and error messages.

Implementation lives in two pieces:

- `sap.cli.srvb.publish_binding(connection, args)` is a module-level
  helper containing the moved logic. Keeping it as a free function
  rather than a CommandGroup method lets the rap deprecation shim
  (next commit) call it without instantiating a srvb CommandGroup.
- A thin `@CommandGroup.command('publish')` wrapper hooks the helper
  into the srvb subparser. The class-level decorator runs at module
  load and adds 'publish' to the same CommandsList that
  CommandGroupObjectMaster.define() populates with the CRUD commands.

The existing ServiceBinding.publish() method on sap/adt builds the
correct per-binding URL (`businessservices/{type}{version}/publishjobs`)
already - no ADT-layer change needed.
The new top-level `srvd` and `srvb` command groups own SRVD/SRVB
CRUD now. `sapcli rap definition activate` and `sapcli rap binding
publish` become deprecated aliases that:

 1. write a single-line warning to stderr identifying the new command
    and announcing removal in the next minor release, and
 2. delegate to the new code path (no logic duplication).

`rap binding publish` calls into sap.cli.srvb.publish_binding directly.
`rap definition activate` instantiates sap.cli.srvd.CommandGroup() and
calls its inherited activate_objects, after defaulting
ignore_errors / warning_errors to False (the rap subparser does not
declare those flags - the legacy worker had no equivalent settings).

The existing happy-path tests are updated to expect the deprecation
warning prepended to stderr; two new tests
(test_rap_binding_publish_emits_deprecation_warning and
test_rap_definition_activate_emits_deprecation_warning) lock in the
warning-once contract.

doc/commands/businessservice.md is rewritten as a deprecation pointer
to the new srvd.md / srvb.md docs.
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds two new CLI command groups (srvd and srvb) for ABAP RAP Service Definition and Service Binding management, backed by extensions to sap/adt/businessservice.py and new source editor support. Converts the existing rap commands into deprecation shims that emit stderr warnings and forward to the new groups. Registers both groups in the CLI registry and adds documentation, unit tests, and system integration tests.

Changes

SRVD/SRVB CLI commands + rap deprecation shims

Layer / File(s) Summary
ADT businessservice model extensions
sap/adt/businessservice.py, sap/adt/core.py, test/unit/fixtures_adt_businessservice.py, test/unit/test_sap_adt_businessservice.py
Imports Union, logger, ADTObjectSourceEditor, XmlListNodeProperty; adds XMLNS_ODATAV4 and OData structure classes for V2 and V4 service groups; replaces ServicesContainer alias with a class exposing srvb:name and backward-compatible properties; refactors ServiceBinding.services to XmlListNodeProperty, expands ServiceBinding.__init__ with package, typ, version, category parameters, adds add_service() helper and get_service_group() method; updates ServiceDefinition.OBJTYPE to map text/plain to source/main with ADTObjectSourceEditor.plain_text factory and adds source_type attribute; Connection.execute() gains optional complete_url parameter. Comprehensive fixture constants and tests validate source round-trip, editor operations, binding creation payloads, and initialization scenarios.
SRVD CLI command group
sap/cli/srvd.py, doc/commands/srvd.md, test/unit/test_sap_cli_srvd.py
Adds CommandGroup for srvd with instance() method constructing sap.adt.ServiceDefinition from uppercased name and optional package/metadata. Documents all srvd commands (create, read, write, activate, delete, whereused) including write options (- for stdin/file deduction) and activation flags. Comprehensive tests cover command construction and execution for all operations with mocked ADT backends.
SRVB CLI command group and publish
sap/cli/srvb.py, doc/commands/srvb.md, test/unit/test_sap_cli_srvb.py
Adds CommandGroup for srvb mapping CLI args to ServiceBinding with create options (--binding-type, --service-definition, --service-version, --corrnr); write disabled; read overridden to print structural summary with embedded services list and service-group URLs. Implements publish_binding() with service selection, filtering, validation, and status reporting. Adds preview subgroup for metadata and JSON entity-set fetch. Documents all srvb commands. End-to-end tests cover create/read/activate/delete/whereused/publish scenarios and error conditions.
CLI registry and RAP deprecation
sap/cli/__init__.py, sap/cli/rap.py, doc/commands/businessservice.md, test/unit/test_sap_cli_rap.py
Registers srvd and srvb command groups in ADT command cache via lazy imports. Replaces rap publish and definition_activate with deprecation-warning shims that delegate to srvb.publish_binding and srvd.CommandGroup().activate_objects. Updates businessservice.md to document rap as deprecated with mapping table to new commands. Centralizes deprecation constants in tests and adds explicit assertions verifying deprecation-warning emission. Updates all existing rap test stderr expectations to include the deprecation warning prefix.
System integration tests
test/system/test_cases/60-*.sh, 61-*.sh, 62-*.sh, 63-*.sh
Adds four end-to-end bash system tests exercising complete workflows: DDLS CDS creation, service definition wiring, service binding (OData V2/V4, UI/API variants), activation, publication, and preview operations (metadata/fetch) to validate integrated SRVD/SRVB command behavior against live ADT systems.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Add srvb srvd crud' is partially related to the changeset but lacks specificity and clarity about what CRUD operations are being added. Expand the title to be more descriptive, e.g. 'Add CRUD commands for ABAP RAP Service Definitions and Bindings' to clearly convey the primary change.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether the intended changes are clearly communicated. Add a pull request description explaining the purpose, scope, and implementation approach for the new srvd/srvb CRUD functionality.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
test/unit/test_sap_adt_businessservice.py (1)

303-350: ⚡ Quick win

Add negative tests for invalid ServiceBinding init combinations.

Current coverage validates happy paths only. Please add cases for (typ without version), (version without typ), and (service_name without typ/version) so constructor input validation can’t regress silently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/unit/test_sap_adt_businessservice.py` around lines 303 - 350, Add three
new negative test methods to validate that ServiceBinding constructor properly
rejects invalid parameter combinations. Create
test_init_with_typ_without_version, test_init_with_version_without_typ, and
test_init_with_service_name_without_typ_version methods that use assertRaises to
verify that ServiceBinding raises an appropriate exception when initialized with
these invalid combinations. This ensures the constructor's input validation
prevents silent regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sap/adt/businessservice.py`:
- Around line 117-136: The constructor currently accepts partial binding
configuration by using an OR condition when checking if typ and version are
provided, and allows service_name to be set without a corresponding binding
configuration, which can generate invalid XML payloads. Change the condition in
the inner_binding creation block from checking if typ is not None or version is
not None to ensuring both typ AND version are provided together, and add
validation to ensure that if service_name is provided, the required binding
configuration must also be present. This prevents building incomplete XML
structures and catches configuration errors locally before attempting server
requests.

In `@sap/cli/srvb.py`:
- Around line 109-117: The error messages in the SRVB publish validation contain
a typo where "Biding" is used instead of "Binding". Locate all error messages
that display user-facing text to the console (particularly those using
console.printerr) and correct the spelling from "Business Service Biding" to
"Business Service Binding". This includes the error message that checks if the
binding contains no services and the error message that appears when publishing
without service definition filters.

In `@test/unit/test_sap_cli_srvb.py`:
- Line 7: Remove the unused `call` import from the `unittest.mock` import
statement at the top of the file. The `call` object is imported in the line that
imports from `unittest.mock` but is never referenced anywhere in the test file,
so it should be removed from the import list while keeping `patch` and `Mock`.

---

Nitpick comments:
In `@test/unit/test_sap_adt_businessservice.py`:
- Around line 303-350: Add three new negative test methods to validate that
ServiceBinding constructor properly rejects invalid parameter combinations.
Create test_init_with_typ_without_version, test_init_with_version_without_typ,
and test_init_with_service_name_without_typ_version methods that use
assertRaises to verify that ServiceBinding raises an appropriate exception when
initialized with these invalid combinations. This ensures the constructor's
input validation prevents silent regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6b44732a-0446-4100-bc81-c263598bd37f

📥 Commits

Reviewing files that changed from the base of the PR and between 083e60e and 0f46230.

📒 Files selected for processing (13)
  • doc/commands/businessservice.md
  • doc/commands/srvb.md
  • doc/commands/srvd.md
  • sap/adt/businessservice.py
  • sap/cli/__init__.py
  • sap/cli/rap.py
  • sap/cli/srvb.py
  • sap/cli/srvd.py
  • test/unit/fixtures_adt_businessservice.py
  • test/unit/test_sap_adt_businessservice.py
  • test/unit/test_sap_cli_rap.py
  • test/unit/test_sap_cli_srvb.py
  • test/unit/test_sap_cli_srvd.py

Comment thread sap/adt/businessservice.py Outdated
Comment thread sap/cli/srvb.py
Comment thread test/unit/test_sap_cli_srvb.py Outdated
@mhoerisch

mhoerisch commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Tested PR #178 head (0f46230) end-to-end, replaying the lifecycle (delete → create → write → activate → read → publish) over an existing Z* scaffold (TABL/DDLS/BDEF/CLAS) in $TMP. Read-side commands work; both create paths have a wire-format defect.

✅ What works

  • srvd read — returns the CDS source body verbatim
  • srvb read — structured summary (Name / Description / Package / Type / Version / Published / Services list)
  • srvd whereused — returns the consuming binding correctly
  • srvb whereused — empty list (correct, nothing references the binding)
  • srvd delete and srvb delete — clean, read afterwards returns ExceptionResourceNotFound as expected
  • Help output, deprecation notices on rap definition activate / rap binding publish

🔴 Bug 1 — srvd create is missing the srvdSourceType="S" attribute

$ sapcli srvd create ZSCLI_SVCDEMO_S 'demo' '$TMP'
Exception (ExceptionResourceCreationFailure):
  Service Definition type '' does not exist

Wire format the server expects (confirmed via raw probe on the same system):

<srvd:srvdSource srvd:srvdSourceType="S" ...>

The PR's own unit-test fixture and doc/commands/srvd.md both have srvdSourceType="S", but ServiceDefinition in sap/adt/businessservice.py has no XmlNodeAttributeProperty('srvd:srvdSourceType') on the class — Marshal therefore never emits the attribute on POST. Server rejects the create.

After this failure, srvd write and srvd activate cascade-fail (PUT … Resource controller does not support method PUT; activate ExceptionResourceNotFound) but the CLI exits RC=0 for all three calls — the error is printed but not surfaced to the shell.

Suggested fix: add srvd_source_type = XmlNodeAttributeProperty('srvd:srvdSourceType', deserialize=False) to ServiceDefinition (or set it as a constant in the OBJTYPE wiring) and default to 'S'. The fixture/doc both already encode that this is the only value the server accepts.

🔴 Bug 2 — srvb create succeeds RC=0 but produces a binding with no services wired

$ sapcli srvb create ZSCLI_SVCDEMO_B 'demo' '$TMP' \
    --type ODATA --version V4 --service ZSCLI_SVCDEMO_S
$ echo $?
0
$ sapcli srvb read ZSCLI_SVCDEMO_B
Name        : ZSCLI_SVCDEMO_B
Description : demo
Package     : $TMP
Type        : ODATA
Version     : V4
Published   : false
                       ← no "Services:" block; the link to SRVD is gone

srvb activate then 500s with Syntax error in program WB2E_I_EVENT==================BD (the activation engine can't materialize an empty binding), and srvb publish correctly reports Business Service Biding ZSCLI_SVCDEMO_B does not contain any services.

For comparison, the working wire format (POST that the server accepts and which produces a wired binding) on the same system is:

<srvb:services srvb:name="ZSCLI_SVCDEMO_B">
  <srvb:content srvb:version="0001" srvb:releaseState="NOT_RELEASED">
    <srvb:serviceDefinition adtcore:name="ZSCLI_SVCDEMO_S" adtcore:type="SRVD/SRV"/>
  </srvb:content>
</srvb:services>

The ServicesContainer in sap/adt/businessservice.py (Definition, DefinitionLink, ServicesContainer) has the inner srvb:version, srvb:releaseState, adtcore:name attributes wired but is missing srvb:name on the <srvb:services> outer container and adtcore:type="SRVD/SRV" on <srvb:serviceDefinition>. Server-side this seems to result in the services subtree being silently dropped on POST, so the create succeeds but the binding is hollow.

Suggested fix: add srvb:name to the ServicesContainer outer element (matching the binding name) and adtcore:type (constant 'SRVD/SRV') on the Definition inner element so they round-trip on serialize.

🔴 Bug 3 (cross-cutting) — Errors don't propagate to RC

Both bugs above print Exception (Exception…): … to stdout while the process still exits RC=0. That makes them invisible to scripts (CI, agent loops, tooling that wraps sapcli) checking $?. Worth fixing with the above two so the lifecycle actually halts on failure.

Test environment

  • S/4HANA Public Cloud accessed via ~/.sapcli/config.yml default context
  • Scaffold ZSCLI_SVCDEMO_T (TABL) → _R (root CDS) → _P (projection CDS) → _BP (behavior pool) in $TMP
  • Reference wire-format captures from a separate raw-HTTP probe (zns_e2e.py) on the same system, used as ground truth
  • Verified by toggling: PR CLI create fails → known-good probe wire creates+activates+publishes successfully, same names

Read-side commands and delete are solid. Once the two attribute-emission gaps are fixed and RC propagation works, the lifecycle should round-trip cleanly. Happy to retest a follow-up commit on the same scaffold.

The back-end rejects POSTs that lack srvd:srvdSourceType:

  ExceptionResourceCreationFailure: Service Definition type '' does
  not exist

Caught the hard way against a live system - sapcli srvd create
was failing 100% of the time. The spec
(features/srvd_srvb_crud/srvd_srvb_crud.md, lines 142-143) flagged
this requirement explicitly when the feature was reviewed; the
implementation missed it.

Add a class-level default:

    source_type = XmlNodeAttributeProperty('srvd:srvdSourceType', value='S')

'S' (= "Definition") is the only value observed in live captures.
The attribute is positional (XmlNodeAttributeProperty), so it
deserialises from GET responses too, which lets the existing
EXAMPLE_CONFIG_SRV fixture double-check the field round-trips.

Why the unit tests missed it: SERVICE_DEFINITION_ADT_POST_REQUEST_XML
was generated from the broken Marshal().serialize() output instead of
copied from the wire capture in srvd_srvb_crud.md - the fixture
matched the bug rather than the contract. This patch:

- updates SERVICE_DEFINITION_ADT_POST_REQUEST_XML to include the
  required attribute (the test_servicedefinition_create_serializes_post_body
  assertion already uses full assertEqual, so the new fixture catches
  any future regression),
- adds test_servicedefinition_default_source_type_is_S to lock in the
  in-memory default,
- extends test_binding_fetch (the GET round-trip) to assert
  source_type='S' so the property stays wired through deserialisation.
…captures

Audit pass against the live e2e captures in
features/srvd_srvb_crud/captures/scaffold/. Two structural divergences
were caught between the fixtures I committed earlier and the actual
wire bytes the back-end exchanges:

(1) SERVICE_BINDING_ADT_GET_V4_XML lacked srvb:allowedAction on the
    root, srvb:minorVersion / srvb:patchVersion / srvb:buildVersion on
    <srvb:content>, and the <srvb:bindingTypeData><adtcore:content
    encoding="base64"/></srvb:bindingTypeData> child. The marshaller
    silently ignores attributes/elements it does not model, so the read
    summary keeps working - but the fixture is supposed to mirror the
    server, not the parser. Updated to match 06b_srvb_get_post_activate
    verbatim (modulo object name).

(2) SERVICE_BINDING_ADT_POST_ODATA_V4_REQUEST_XML and the marshalled
    body itself lacked <srvb:services srvb:name="...">. The captured
    create request 05b_srvb_create_wired_request.xml shows it is
    always sent and equals the parent binding's name. The previous
    XmlContainer.define-only ServicesContainer factory cannot carry
    container-level attributes, so introduce ServicesContainerWithName
    - a direct subclass of XmlContainer that declares the items list
    plus the srvb:name attribute. ServiceBinding.__init__ now sets
    services.name = name when wiring the create body, and the GET
    deserialiser populates it from the response.

The skill add-support-for-new-adt-object-type is explicit on this:
"Never change or deduce the XML structure. You can only update the
values of the nodes and attributes to match the new object." The
original fixtures were generated from Marshal().serialize() output
of the partial implementation, which is exactly the deduce-structure
anti-pattern the skill bans. This patch closes the gap.
…tion

Some objects like Service Definition are handled by "ADT" but returns
URL outside ADT ICF path. I am not proud of this hack but it is too late
and I need to push the solution.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/unit/test_sap_cli_srvb.py`:
- Around line 112-114: The headers parameter in the Response() function call is
under-indented, violating Flake8 E128 (continuation line under-indented).
Increase the indentation of the line containing headers={'Content-Type': to
properly align it with the function arguments in the Response() call, ensuring
it follows consistent continuation line indentation rules.
- Line 102: Remove the incorrect .call_args accessor from the mock assertion in
the line with fake_srvb.return_value.add_service. The assert_called_once_with()
method should be called directly on the add_service mock object itself, not on
its call_args property. Change
fake_srvb.return_value.add_service.call_args.assert_called_once_with(...) to
fake_srvb.return_value.add_service.assert_called_once_with(...) while keeping
the same arguments (SERVICE_BINDING_NAME, 'ZSAPCLI_TEST_SRVD', '0002').
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 694a4e2d-0535-40fc-ab96-c46150ab1d67

📥 Commits

Reviewing files that changed from the base of the PR and between 7c1b386 and cb03160.

📒 Files selected for processing (6)
  • sap/adt/businessservice.py
  • sap/adt/core.py
  • sap/cli/srvb.py
  • test/unit/fixtures_adt_businessservice.py
  • test/unit/test_sap_adt_businessservice.py
  • test/unit/test_sap_cli_srvb.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/unit/fixtures_adt_businessservice.py
  • sap/cli/srvb.py

with patch_get_print_console_with_buffer():
args.execute(fake_conn, args)

fake_srvb.return_value.add_service.call_args.assert_called_once_with(SERVICE_BINDING_NAME, 'ZSAPCLI_TEST_SRVD', '0002')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP --type=py '\.call_args\.assert_called_once_with\('
python - <<'PY'
from unittest.mock import Mock
m = Mock()
m("x")
print(hasattr(m.call_args, "assert_called_once_with"))
PY

Repository: jfilak/sapcli

Length of output: 225


Fix mock assertion target in Line 102.

The assertion should be called on the mock method itself, not on call_args. Use fake_srvb.return_value.add_service.assert_called_once_with(...) instead of fake_srvb.return_value.add_service.call_args.assert_called_once_with(...).

Proposed patch
-        fake_srvb.return_value.add_service.call_args.assert_called_once_with(SERVICE_BINDING_NAME, 'ZSAPCLI_TEST_SRVD', '0002')
+        fake_srvb.return_value.add_service.assert_called_once_with(SERVICE_BINDING_NAME, 'ZSAPCLI_TEST_SRVD', '0002')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fake_srvb.return_value.add_service.call_args.assert_called_once_with(SERVICE_BINDING_NAME, 'ZSAPCLI_TEST_SRVD', '0002')
fake_srvb.return_value.add_service.assert_called_once_with(SERVICE_BINDING_NAME, 'ZSAPCLI_TEST_SRVD', '0002')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/unit/test_sap_cli_srvb.py` at line 102, Remove the incorrect .call_args
accessor from the mock assertion in the line with
fake_srvb.return_value.add_service. The assert_called_once_with() method should
be called directly on the add_service mock object itself, not on its call_args
property. Change
fake_srvb.return_value.add_service.call_args.assert_called_once_with(...) to
fake_srvb.return_value.add_service.assert_called_once_with(...) while keeping
the same arguments (SERVICE_BINDING_NAME, 'ZSAPCLI_TEST_SRVD', '0002').

Comment thread test/unit/test_sap_cli_srvb.py
Naive implementation of parsing Service Group of Service Binding.

It is not needed for anything else just to get the published URL
so we can magically allow agents to fetch the data.
@filak-sap filak-sap force-pushed the add_srvb_srvd_crud branch from 93230af to d89af98 Compare June 22, 2026 14:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
sap/cli/srvb.py (1)

208-208: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix typo: "Biding" should be "Binding".

Three error messages contain the typo "Biding" instead of "Binding" in user-facing output.

✏️ Proposed fix
-        raise sap.errors.SAPCliError(f'Business Service Biding {args.binding_name} does not contain any services')
+        raise sap.errors.SAPCliError(f'Business Service Binding {args.binding_name} does not contain any services')
-            f'Business Service Biding {args.binding_name} contains more than one Service Definition; '
+            f'Business Service Binding {args.binding_name} contains more than one Service Definition; '
-                f'Business Service Biding {args.binding_name} has no Service Definition '
+                f'Business Service Binding {args.binding_name} has no Service Definition '

Also applies to: 212-212, 222-222

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sap/cli/srvb.py` at line 208, Fix the spelling typo in three user-facing
error messages in the file where "Biding" is incorrectly used instead of
"Binding". Locate all three occurrences of the typo in the
sap.errors.SAPCliError exception messages (at lines 208, 212, and 222) and
replace each instance of "Biding" with "Binding" to correct the spelling in the
error output messages.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/system/test_cases/60-publish_service_binding_odata_v2_ui.sh`:
- Line 7: In the file 60-publish_service_binding_odata_v2_ui.sh, the _tcn
variable is set to 61 but should be set to 60 to match the test case file name.
Change the _tcn variable assignment from 61 to 60 so that generated object names
correctly correspond to test case 60 and prevent naming collisions with adjacent
system tests.

---

Duplicate comments:
In `@sap/cli/srvb.py`:
- Line 208: Fix the spelling typo in three user-facing error messages in the
file where "Biding" is incorrectly used instead of "Binding". Locate all three
occurrences of the typo in the sap.errors.SAPCliError exception messages (at
lines 208, 212, and 222) and replace each instance of "Biding" with "Binding" to
correct the spelling in the error output messages.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f16561cd-9b90-4e35-9d6b-81ff0dcb1b08

📥 Commits

Reviewing files that changed from the base of the PR and between cb03160 and d89af98.

📒 Files selected for processing (10)
  • doc/commands/srvb.md
  • sap/adt/businessservice.py
  • sap/cli/srvb.py
  • test/system/test_cases/60-publish_service_binding_odata_v2_ui.sh
  • test/system/test_cases/61-publish_service_binding_odata_v2_api.sh
  • test/system/test_cases/62-publish_service_binding_odata_v4_ui.sh
  • test/system/test_cases/63-publish_service_binding_odata_v4_api.sh
  • test/unit/fixtures_adt_businessservice.py
  • test/unit/test_sap_adt_businessservice.py
  • test/unit/test_sap_cli_srvb.py
✅ Files skipped from review due to trivial changes (1)
  • doc/commands/srvb.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/unit/fixtures_adt_businessservice.py
  • test/unit/test_sap_adt_businessservice.py

set -o errexit
set -o pipefail

_tcn="61"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix test-case identifier mismatch in object naming.

Line 7 uses _tcn="61" in test case 60-*, so generated object names won’t match this case number and may collide with adjacent system tests.

Suggested patch
-_tcn="61"
+_tcn="60"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_tcn="61"
_tcn="60"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/system/test_cases/60-publish_service_binding_odata_v2_ui.sh` at line 7,
In the file 60-publish_service_binding_odata_v2_ui.sh, the _tcn variable is set
to 61 but should be set to 60 to match the test case file name. Change the _tcn
variable assignment from 61 to 60 so that generated object names correctly
correspond to test case 60 and prevent naming collisions with adjacent system
tests.

I would love to prepare a better patch or amend the previous commits
because Claude made many assumptions without asking for clarification
but I have no energy anymore.

This patch just finishes creating Service Bindings. There were problem
with Category and Implementation where Category 0 means UI and that
requires implementation name (I have no idea what that means).

On the contrary Category 1 means API and that does not use
implementation.

I drop the binding types SQL and INA because I have absolutely no idea
how to verify the implementation works - feel free to add it.
Thanks to Michael Hoerisch who suggested to simplify agents life by
allowing them to get the published data directly by using sapcli because
the connection is already configured and otherwise the agent would have
build new complicated HTTP request with authentication.

This is the first and minimal version - feel free to add cool stuff like
listing of EntitySets or rich arguments.
@filak-sap filak-sap force-pushed the add_srvb_srvd_crud branch from d89af98 to a0dcc76 Compare June 22, 2026 16:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
test/unit/test_sap_adt_businessservice.py (2)

319-330: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Clarify misleading comment.

The comment says "expect self.binding to remain None" but the test asserts binding.binding.typ is None (not binding.binding itself). The binding object is now always initialized; only its properties are None.

Suggested fix
     def test_init_without_typ_version_keeps_binding_unset(self):
-        # backward compatibility: existing rap binding publish callers pass
-        # only (connection, name) and expect self.binding to remain None until
-        # fetch() populates it.
+        # backward compatibility: existing rap binding publish callers pass
+        # only (connection, name); self.binding is initialized with None
+        # properties until fetch() populates them.
         binding = sap.adt.businessservice.ServiceBinding(Connection(), 'TEST_BINDING')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/unit/test_sap_adt_businessservice.py` around lines 319 - 330, The
comment in the test_init_without_typ_version_keeps_binding_unset method is
misleading because it states "expect self.binding to remain None" but the test
actually asserts that binding.binding itself is not None, only its properties
(typ, version, category) are None. Update the comment to accurately reflect that
the binding object is always initialized but remains in an unset state with None
values for its properties until fetch() populates them.

153-182: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Unused variable and potential test/fixture mismatch.

Line 156 declares service_name = 'TEST_SERVICE_2' but it's never used in the test. The test publishes services[0] (version 0001) but the status message assertion expects text referencing "TEST_SERVICE_2 with version 0002". Either remove the unused variable or verify the fixture response aligns with the intended test scenario.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/unit/test_sap_adt_businessservice.py` around lines 153 - 182, In the
test_service_published method, the variable service_name is declared but never
used. Remove this unused variable assignment on line 156. Additionally, verify
that the mocked response for the binding.publish call is consistent with the
test assertions, specifically ensuring that the status message fixture
(SAMPLE_BINDING_OBJECT_REFERENCE or the mocked response) correctly reflects the
expected version and service name in the assertion on lines 179-181.
sap/adt/businessservice.py (1)

344-344: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Fix redundant Union type hint syntax.

The type hint Union[None | ODataV4ServiceGroup | ODataV2ServiceList] mixes Union with PEP 604 | syntax. Use one or the other:

Suggested fix
-    def get_service_group(self, service: ServicesContainer) -> Union[None | ODataV4ServiceGroup | ODataV2ServiceList]:
+    def get_service_group(self, service: ServicesContainer) -> ODataV4ServiceGroup | ODataV2ServiceList | None:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sap/adt/businessservice.py` at line 344, The return type hint for the
get_service_group method is mixing Union syntax with PEP 604 pipe operator
syntax. Replace the redundant Union wrapper with just the pipe operator syntax
by changing Union[None | ODataV4ServiceGroup | ODataV2ServiceList] to None |
ODataV4ServiceGroup | ODataV2ServiceList, removing the Union call entirely since
the pipe operators already define the union of types.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sap/cli/srvb.py`:
- Around line 260-266: The `.json()` call on the response object in this section
can raise a ValueError if the backend doesn't return valid JSON, which will leak
a traceback in CLI output instead of a proper error message. Wrap the entire
response handling block (where connection.execute is called and .json() is
invoked) in a try-except block that catches ValueError exceptions and raises a
SAPCliError with a descriptive message indicating the backend did not return
valid JSON. This ensures JSON decode failures are handled consistently with the
CLI error handling guidelines.
- Around line 246-263: The URL joining in the metadata fetch and the
preview_fetch function uses string concatenation which can result in double
slashes if service_url already ends with a forward slash. In the metadata fetch
at line 246 where service_url is concatenated with '/$metadata' and in the
preview_fetch function where service_url is concatenated with '/' and
args.entity_set, replace the string concatenation with a proper URL
normalization method or utility function that handles path joining correctly.
This will ensure consistent URL formatting regardless of whether service_url
ends with a slash or not, making the calls work reliably across different
gateway configurations.

---

Nitpick comments:
In `@sap/adt/businessservice.py`:
- Line 344: The return type hint for the get_service_group method is mixing
Union syntax with PEP 604 pipe operator syntax. Replace the redundant Union
wrapper with just the pipe operator syntax by changing Union[None |
ODataV4ServiceGroup | ODataV2ServiceList] to None | ODataV4ServiceGroup |
ODataV2ServiceList, removing the Union call entirely since the pipe operators
already define the union of types.

In `@test/unit/test_sap_adt_businessservice.py`:
- Around line 319-330: The comment in the
test_init_without_typ_version_keeps_binding_unset method is misleading because
it states "expect self.binding to remain None" but the test actually asserts
that binding.binding itself is not None, only its properties (typ, version,
category) are None. Update the comment to accurately reflect that the binding
object is always initialized but remains in an unset state with None values for
its properties until fetch() populates them.
- Around line 153-182: In the test_service_published method, the variable
service_name is declared but never used. Remove this unused variable assignment
on line 156. Additionally, verify that the mocked response for the
binding.publish call is consistent with the test assertions, specifically
ensuring that the status message fixture (SAMPLE_BINDING_OBJECT_REFERENCE or the
mocked response) correctly reflects the expected version and service name in the
assertion on lines 179-181.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ce6c8d6d-b054-4af6-a459-ccf85b0fffb1

📥 Commits

Reviewing files that changed from the base of the PR and between d89af98 and a0dcc76.

📒 Files selected for processing (10)
  • doc/commands/srvb.md
  • sap/adt/businessservice.py
  • sap/cli/srvb.py
  • test/system/test_cases/60-publish_service_binding_odata_v2_ui.sh
  • test/system/test_cases/61-publish_service_binding_odata_v2_api.sh
  • test/system/test_cases/62-publish_service_binding_odata_v4_ui.sh
  • test/system/test_cases/63-publish_service_binding_odata_v4_api.sh
  • test/unit/fixtures_adt_businessservice.py
  • test/unit/test_sap_adt_businessservice.py
  • test/unit/test_sap_cli_srvb.py
✅ Files skipped from review due to trivial changes (1)
  • doc/commands/srvb.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/unit/fixtures_adt_businessservice.py

Comment thread sap/cli/srvb.py
Comment on lines +246 to +263
metadata = connection.execute('GET', service_group.services.service_url + '/$metadata', complete_url=True).text
console.printout(metadata)


@CommandGroupPreview.argument('--service', nargs='?', default=None,
help="Service name of the binding's services to preview")
@CommandGroupPreview.argument('entity_set')
@CommandGroupPreview.argument('binding_name')
@CommandGroupPreview.command('fetch')
def preview_fetch(connection, args):
"""Fetch and print entries from the specified entity set of one of the services in a Service Binding."""

console = args.console_factory()
_, service_group = _get_service_with_binding_group(connection, args)
data = connection.execute(
'GET',
service_group.services.service_url + '/' + args.entity_set,
accept='application/json',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize URL joining for preview requests.

Line 246 and Line 262 append path parts blindly; if service_url already ends with /, preview calls become //..., which is brittle across gateways.

Proposed patch
 def preview_metadata(connection, args):
@@
-    metadata = connection.execute('GET', service_group.services.service_url + '/$metadata', complete_url=True).text
+    base_url = service_group.services.service_url.rstrip('/')
+    metadata = connection.execute('GET', f'{base_url}/$metadata', complete_url=True).text
@@
 def preview_fetch(connection, args):
@@
-    data = connection.execute(
+    base_url = service_group.services.service_url.rstrip('/')
+    entity_set = args.entity_set.lstrip('/')
+    data = connection.execute(
         'GET',
-        service_group.services.service_url + '/' + args.entity_set,
+        f'{base_url}/{entity_set}',
         accept='application/json',
         complete_url=True
     ).json()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sap/cli/srvb.py` around lines 246 - 263, The URL joining in the metadata
fetch and the preview_fetch function uses string concatenation which can result
in double slashes if service_url already ends with a forward slash. In the
metadata fetch at line 246 where service_url is concatenated with '/$metadata'
and in the preview_fetch function where service_url is concatenated with '/' and
args.entity_set, replace the string concatenation with a proper URL
normalization method or utility function that handles path joining correctly.
This will ensure consistent URL formatting regardless of whether service_url
ends with a slash or not, making the calls work reliably across different
gateway configurations.

Comment thread sap/cli/srvb.py
Comment on lines +260 to +266
data = connection.execute(
'GET',
service_group.services.service_url + '/' + args.entity_set,
accept='application/json',
complete_url=True
).json()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wrap JSON decode failures in SAPCliError.

Line 265 can raise ValueError and leak a traceback in CLI output when backend doesn’t return JSON.

Proposed patch
-    data = connection.execute(
+    response = connection.execute(
         'GET',
-        service_group.services.service_url + '/' + args.entity_set,
+        service_group.services.service_url + '/' + args.entity_set,
         accept='application/json',
         complete_url=True
-    ).json()
+    )
+    try:
+        data = response.json()
+    except ValueError as exc:
+        # Convert decode errors to SAPCliError so CLI shows a clean user-facing message.
+        raise sap.errors.SAPCliError(
+            f'Failed to parse JSON response for entity set "{args.entity_set}"'
+        ) from exc
 
     console.printout(json.dumps(data, indent=2))
As per coding guidelines, "Use exception types derived from sap.errors.SAPCliError for command line errors to ensure proper error message handling instead of stacktraces."
🧰 Tools
🪛 ast-grep (0.44.0)

[info] 266-266: use jsonify instead of json.dumps for JSON output
Context: json.dumps(data, indent=2)
Note: Security best practice.

(use-jsonify)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sap/cli/srvb.py` around lines 260 - 266, The `.json()` call on the response
object in this section can raise a ValueError if the backend doesn't return
valid JSON, which will leak a traceback in CLI output instead of a proper error
message. Wrap the entire response handling block (where connection.execute is
called and .json() is invoked) in a try-except block that catches ValueError
exceptions and raises a SAPCliError with a descriptive message indicating the
backend did not return valid JSON. This ensures JSON decode failures are handled
consistently with the CLI error handling guidelines.

Source: Coding guidelines

@jfilak jfilak merged commit 12096ed into jfilak:master Jun 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants