@linkup-ai/abap-ai 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +384 -0
- package/dist/adt-client.js +364 -0
- package/dist/cli/activate.js +113 -0
- package/dist/cli/init.js +333 -0
- package/dist/cli/remove.js +80 -0
- package/dist/cli/status.js +229 -0
- package/dist/cli/systems.js +68 -0
- package/dist/cli.js +81 -0
- package/dist/index.js +1318 -0
- package/dist/knowledge/abap/abap-dictionary.md +199 -0
- package/dist/knowledge/abap/abap-sql.md +296 -0
- package/dist/knowledge/abap/amdp.md +273 -0
- package/dist/knowledge/abap/clean-code.md +293 -0
- package/dist/knowledge/abap/cloud-background-processing.md +250 -0
- package/dist/knowledge/abap/cloud-communication.md +265 -0
- package/dist/knowledge/abap/cloud-development.md +176 -0
- package/dist/knowledge/abap/cloud-extensibility.md +252 -0
- package/dist/knowledge/abap/cloud-released-apis.md +261 -0
- package/dist/knowledge/abap/constructor-expressions.md +289 -0
- package/dist/knowledge/abap/enhancements.md +232 -0
- package/dist/knowledge/abap/exceptions.md +271 -0
- package/dist/knowledge/abap/internal-tables.md +205 -0
- package/dist/knowledge/abap/object-orientation.md +298 -0
- package/dist/knowledge/abap/performance.md +216 -0
- package/dist/knowledge/abap/rap-abstract-entities.md +206 -0
- package/dist/knowledge/abap/rap-business-events.md +216 -0
- package/dist/knowledge/abap/rap-draft.md +191 -0
- package/dist/knowledge/abap/rap-eml.md +453 -0
- package/dist/knowledge/abap/rap-end-to-end.md +486 -0
- package/dist/knowledge/abap/rap-feature-control.md +185 -0
- package/dist/knowledge/abap/rap-numbering.md +280 -0
- package/dist/knowledge/abap/rap-service-exposure.md +163 -0
- package/dist/knowledge/abap/rap-unmanaged.md +468 -0
- package/dist/knowledge/abap/string-processing.md +180 -0
- package/dist/knowledge/abap/unit-testing.md +303 -0
- package/dist/knowledge/abap-cds/access-control.md +241 -0
- package/dist/knowledge/abap-cds/annotations.md +331 -0
- package/dist/knowledge/abap-cds/associations.md +254 -0
- package/dist/knowledge/abap-cds/expressions.md +230 -0
- package/dist/knowledge/abap-cds/functions.md +245 -0
- package/dist/knowledge/abap-cds/metadata-extensions.md +294 -0
- package/dist/knowledge/cap/authentication.md +278 -0
- package/dist/knowledge/cap/cdl-syntax.md +247 -0
- package/dist/knowledge/cap/cql-queries.md +266 -0
- package/dist/knowledge/cap/deployment.md +343 -0
- package/dist/knowledge/cap/event-handlers.md +287 -0
- package/dist/knowledge/cap/fiori-integration.md +303 -0
- package/dist/knowledge/cap/service-definitions.md +287 -0
- package/dist/knowledge/fiori/annotations.md +347 -0
- package/dist/knowledge/fiori/deployment.md +340 -0
- package/dist/knowledge/fiori/fiori-elements.md +332 -0
- package/dist/knowledge/fiori/fiori-side-effects.md +107 -0
- package/dist/knowledge/fiori/fiori-valuelist.md +144 -0
- package/dist/knowledge/fiori/ui5-controllers.md +358 -0
- package/dist/knowledge/fiori/ui5-data-binding.md +311 -0
- package/dist/knowledge/fiori/ui5-fragments-dialogs.md +330 -0
- package/dist/knowledge/fiori/ui5-manifest.md +411 -0
- package/dist/knowledge/fiori/ui5-routing.md +303 -0
- package/dist/knowledge/fiori/ui5-xml-views.md +294 -0
- package/dist/logger.js +114 -0
- package/dist/system-profile.js +207 -0
- package/dist/tools/abap-doc.js +72 -0
- package/dist/tools/abapgit.js +161 -0
- package/dist/tools/activate.js +68 -0
- package/dist/tools/atc-check.js +117 -0
- package/dist/tools/auth-object.js +56 -0
- package/dist/tools/breakpoints.js +76 -0
- package/dist/tools/call-hierarchy.js +84 -0
- package/dist/tools/cds-annotations.js +98 -0
- package/dist/tools/cds-dependencies.js +65 -0
- package/dist/tools/check.js +47 -0
- package/dist/tools/code-completion.js +70 -0
- package/dist/tools/code-coverage.js +111 -0
- package/dist/tools/create-amdp.js +111 -0
- package/dist/tools/create-dcl.js +81 -0
- package/dist/tools/create-transport.js +38 -0
- package/dist/tools/create.js +285 -0
- package/dist/tools/data-preview.js +37 -0
- package/dist/tools/delete.js +45 -0
- package/dist/tools/deploy-bsp.js +298 -0
- package/dist/tools/discovery.js +59 -0
- package/dist/tools/element-info.js +93 -0
- package/dist/tools/enhancements.js +186 -0
- package/dist/tools/extract-method.js +44 -0
- package/dist/tools/function-group.js +59 -0
- package/dist/tools/knowledge.js +275 -0
- package/dist/tools/lock-object.js +75 -0
- package/dist/tools/message-class.js +67 -0
- package/dist/tools/navigate.js +80 -0
- package/dist/tools/number-range.js +57 -0
- package/dist/tools/object-documentation.js +43 -0
- package/dist/tools/object-structure.js +78 -0
- package/dist/tools/object-versions.js +57 -0
- package/dist/tools/package-contents.js +60 -0
- package/dist/tools/pretty-printer.js +35 -0
- package/dist/tools/publish-binding.js +49 -0
- package/dist/tools/quick-fix.js +69 -0
- package/dist/tools/read.js +167 -0
- package/dist/tools/refactor-rename.js +60 -0
- package/dist/tools/release-transport.js +24 -0
- package/dist/tools/released-apis.js +51 -0
- package/dist/tools/repository-tree.js +90 -0
- package/dist/tools/scaffold-rap.js +642 -0
- package/dist/tools/search.js +73 -0
- package/dist/tools/shared/data-format.js +101 -0
- package/dist/tools/sql-console.js +17 -0
- package/dist/tools/system-info.js +270 -0
- package/dist/tools/traces.js +66 -0
- package/dist/tools/transport-contents.js +83 -0
- package/dist/tools/transports.js +67 -0
- package/dist/tools/unit-test.js +135 -0
- package/dist/tools/where-used.js +59 -0
- package/dist/tools/write.js +101 -0
- package/package.json +49 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# ABAP Unit Testing — test classes, assertions, test doubles, RAP BO testing
|
|
2
|
+
|
|
3
|
+
## Test Class Definition
|
|
4
|
+
|
|
5
|
+
```abap
|
|
6
|
+
CLASS ltc_order_processor DEFINITION FINAL FOR TESTING
|
|
7
|
+
DURATION SHORT RISK LEVEL HARMLESS.
|
|
8
|
+
|
|
9
|
+
PRIVATE SECTION.
|
|
10
|
+
DATA mo_cut TYPE REF TO zcl_order_processor. " class under test
|
|
11
|
+
|
|
12
|
+
METHODS setup.
|
|
13
|
+
METHODS teardown.
|
|
14
|
+
METHODS test_create_order FOR TESTING.
|
|
15
|
+
METHODS test_invalid_customer FOR TESTING.
|
|
16
|
+
METHODS test_calculate_total FOR TESTING.
|
|
17
|
+
ENDCLASS.
|
|
18
|
+
|
|
19
|
+
CLASS ltc_order_processor IMPLEMENTATION.
|
|
20
|
+
METHOD setup.
|
|
21
|
+
mo_cut = NEW #( ).
|
|
22
|
+
ENDMETHOD.
|
|
23
|
+
|
|
24
|
+
METHOD teardown.
|
|
25
|
+
CLEAR mo_cut.
|
|
26
|
+
ENDMETHOD.
|
|
27
|
+
|
|
28
|
+
METHOD test_create_order.
|
|
29
|
+
DATA(lv_result) = mo_cut->create_order(
|
|
30
|
+
iv_customer = 'CUST100'
|
|
31
|
+
iv_amount = 500 ).
|
|
32
|
+
|
|
33
|
+
cl_abap_unit_assert=>assert_not_initial( lv_result ).
|
|
34
|
+
cl_abap_unit_assert=>assert_equals( exp = 'O' act = lv_result-status ).
|
|
35
|
+
ENDMETHOD.
|
|
36
|
+
|
|
37
|
+
METHOD test_invalid_customer.
|
|
38
|
+
TRY.
|
|
39
|
+
mo_cut->create_order( iv_customer = '' iv_amount = 500 ).
|
|
40
|
+
cl_abap_unit_assert=>fail( msg = 'Expected exception' ).
|
|
41
|
+
CATCH zcx_order_error INTO DATA(lx).
|
|
42
|
+
cl_abap_unit_assert=>assert_bound( lx ).
|
|
43
|
+
ENDTRY.
|
|
44
|
+
ENDMETHOD.
|
|
45
|
+
|
|
46
|
+
METHOD test_calculate_total.
|
|
47
|
+
DATA(lv_total) = mo_cut->calculate_total(
|
|
48
|
+
it_items = VALUE #( ( price = 100 qty = 2 )
|
|
49
|
+
( price = 50 qty = 3 ) ) ).
|
|
50
|
+
|
|
51
|
+
cl_abap_unit_assert=>assert_equals( exp = 350 act = lv_total ).
|
|
52
|
+
ENDMETHOD.
|
|
53
|
+
ENDCLASS.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Assertion Methods
|
|
57
|
+
|
|
58
|
+
```abap
|
|
59
|
+
" Equality
|
|
60
|
+
cl_abap_unit_assert=>assert_equals( exp = 'A' act = lv_status msg = 'Status should be A' ).
|
|
61
|
+
|
|
62
|
+
" Not initial
|
|
63
|
+
cl_abap_unit_assert=>assert_not_initial( act = lv_id ).
|
|
64
|
+
|
|
65
|
+
" Initial
|
|
66
|
+
cl_abap_unit_assert=>assert_initial( act = lv_error ).
|
|
67
|
+
|
|
68
|
+
" Bound / not bound (references)
|
|
69
|
+
cl_abap_unit_assert=>assert_bound( act = lo_ref ).
|
|
70
|
+
cl_abap_unit_assert=>assert_not_bound( act = lo_ref ).
|
|
71
|
+
|
|
72
|
+
" True / false
|
|
73
|
+
cl_abap_unit_assert=>assert_true( act = lv_flag ).
|
|
74
|
+
cl_abap_unit_assert=>assert_false( act = lv_flag ).
|
|
75
|
+
|
|
76
|
+
" Table content
|
|
77
|
+
cl_abap_unit_assert=>assert_equals( exp = 3 act = lines( lt_items ) ).
|
|
78
|
+
|
|
79
|
+
" Subrc
|
|
80
|
+
cl_abap_unit_assert=>assert_subrc( exp = 0 msg = 'SELECT should find record' ).
|
|
81
|
+
|
|
82
|
+
" Fail explicitly
|
|
83
|
+
cl_abap_unit_assert=>fail( msg = 'Should not reach here' ).
|
|
84
|
+
|
|
85
|
+
" Char contains
|
|
86
|
+
cl_abap_unit_assert=>assert_char_cp(
|
|
87
|
+
exp = '*error*' act = lv_message msg = 'Should contain error' ).
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Test Doubles — SQL (CDS)
|
|
91
|
+
|
|
92
|
+
```abap
|
|
93
|
+
" Mock database access for CDS entities
|
|
94
|
+
CLASS ltc_with_sql_double DEFINITION FINAL FOR TESTING
|
|
95
|
+
DURATION SHORT RISK LEVEL HARMLESS.
|
|
96
|
+
|
|
97
|
+
PRIVATE SECTION.
|
|
98
|
+
CLASS-DATA environment TYPE REF TO if_cds_test_environment.
|
|
99
|
+
|
|
100
|
+
CLASS-METHODS class_setup.
|
|
101
|
+
CLASS-METHODS class_teardown.
|
|
102
|
+
METHODS setup.
|
|
103
|
+
METHODS test_read FOR TESTING.
|
|
104
|
+
ENDCLASS.
|
|
105
|
+
|
|
106
|
+
CLASS ltc_with_sql_double IMPLEMENTATION.
|
|
107
|
+
METHOD class_setup.
|
|
108
|
+
environment = cl_cds_test_environment=>create( i_for_entity = 'ZI_ORDER' ).
|
|
109
|
+
ENDMETHOD.
|
|
110
|
+
|
|
111
|
+
METHOD class_teardown.
|
|
112
|
+
environment->destroy( ).
|
|
113
|
+
ENDMETHOD.
|
|
114
|
+
|
|
115
|
+
METHOD setup.
|
|
116
|
+
environment->clear_doubles( ).
|
|
117
|
+
ENDMETHOD.
|
|
118
|
+
|
|
119
|
+
METHOD test_read.
|
|
120
|
+
" Prepare test data
|
|
121
|
+
DATA lt_orders TYPE STANDARD TABLE OF ztab_order.
|
|
122
|
+
lt_orders = VALUE #(
|
|
123
|
+
( order_id = '0000000001' customer_id = 'CUST100' status = 'O' total_amount = 500 )
|
|
124
|
+
( order_id = '0000000002' customer_id = 'CUST200' status = 'A' total_amount = 800 ) ).
|
|
125
|
+
|
|
126
|
+
environment->insert_test_data( lt_orders ).
|
|
127
|
+
|
|
128
|
+
" Execute code under test
|
|
129
|
+
SELECT * FROM ZI_Order INTO TABLE @DATA(lt_result).
|
|
130
|
+
|
|
131
|
+
cl_abap_unit_assert=>assert_equals( exp = 2 act = lines( lt_result ) ).
|
|
132
|
+
ENDMETHOD.
|
|
133
|
+
ENDCLASS.
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Test Doubles — ABAP OO (Interface Mock)
|
|
137
|
+
|
|
138
|
+
```abap
|
|
139
|
+
" Using CL_ABAP_TESTDOUBLE
|
|
140
|
+
CLASS ltc_with_mock DEFINITION FINAL FOR TESTING
|
|
141
|
+
DURATION SHORT RISK LEVEL HARMLESS.
|
|
142
|
+
|
|
143
|
+
PRIVATE SECTION.
|
|
144
|
+
METHODS test_with_mock FOR TESTING.
|
|
145
|
+
ENDCLASS.
|
|
146
|
+
|
|
147
|
+
CLASS ltc_with_mock IMPLEMENTATION.
|
|
148
|
+
METHOD test_with_mock.
|
|
149
|
+
" Create test double for interface
|
|
150
|
+
DATA lo_double TYPE REF TO zif_customer_service.
|
|
151
|
+
lo_double ?= cl_abap_testdouble=>create( 'zif_customer_service' ).
|
|
152
|
+
|
|
153
|
+
" Configure behavior
|
|
154
|
+
cl_abap_testdouble=>configure_call( lo_double
|
|
155
|
+
)->returning( VALUE zs_customer( id = 'C100' name = 'Test Customer' ) ).
|
|
156
|
+
lo_double->get_customer( 'C100' ). " records the call
|
|
157
|
+
|
|
158
|
+
" Inject and test
|
|
159
|
+
DATA(lo_cut) = NEW zcl_order_processor( io_customer_svc = lo_double ).
|
|
160
|
+
DATA(ls_order) = lo_cut->create_order( iv_customer = 'C100' ).
|
|
161
|
+
|
|
162
|
+
cl_abap_unit_assert=>assert_equals( exp = 'Test Customer' act = ls_order-customer_name ).
|
|
163
|
+
|
|
164
|
+
" Verify call was made
|
|
165
|
+
cl_abap_testdouble=>verify_expectations( lo_double ).
|
|
166
|
+
ENDMETHOD.
|
|
167
|
+
ENDCLASS.
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## RAP BO Test — Managed BO with EML
|
|
171
|
+
|
|
172
|
+
```abap
|
|
173
|
+
CLASS ltc_travel_bo DEFINITION FINAL FOR TESTING
|
|
174
|
+
DURATION SHORT RISK LEVEL HARMLESS.
|
|
175
|
+
|
|
176
|
+
PRIVATE SECTION.
|
|
177
|
+
CLASS-DATA environment TYPE REF TO if_cds_test_environment.
|
|
178
|
+
CLASS-DATA bo_test_env TYPE REF TO if_botd_txbufdbl_bo_test_env.
|
|
179
|
+
|
|
180
|
+
CLASS-METHODS class_setup.
|
|
181
|
+
CLASS-METHODS class_teardown.
|
|
182
|
+
METHODS setup.
|
|
183
|
+
METHODS test_create_travel FOR TESTING.
|
|
184
|
+
METHODS test_accept_travel FOR TESTING.
|
|
185
|
+
METHODS test_validate_dates FOR TESTING.
|
|
186
|
+
ENDCLASS.
|
|
187
|
+
|
|
188
|
+
CLASS ltc_travel_bo IMPLEMENTATION.
|
|
189
|
+
METHOD class_setup.
|
|
190
|
+
" Create BO test double environment
|
|
191
|
+
bo_test_env = cl_botd_txbufdbl_bo_test_env=>create(
|
|
192
|
+
VALUE #( ( 'ZI_TRAVEL' ) ) ).
|
|
193
|
+
|
|
194
|
+
" CDS test environment for related entities
|
|
195
|
+
environment = cl_cds_test_environment=>create(
|
|
196
|
+
i_for_entity = 'ZI_TRAVEL'
|
|
197
|
+
i_dependency_list = VALUE #( ( '/DMO/I_AGENCY' ) ( '/DMO/I_CUSTOMER' ) ) ).
|
|
198
|
+
ENDMETHOD.
|
|
199
|
+
|
|
200
|
+
METHOD class_teardown.
|
|
201
|
+
bo_test_env->destroy( ).
|
|
202
|
+
environment->destroy( ).
|
|
203
|
+
ENDMETHOD.
|
|
204
|
+
|
|
205
|
+
METHOD setup.
|
|
206
|
+
environment->clear_doubles( ).
|
|
207
|
+
ENDMETHOD.
|
|
208
|
+
|
|
209
|
+
METHOD test_create_travel.
|
|
210
|
+
MODIFY ENTITIES OF ZI_Travel
|
|
211
|
+
ENTITY Travel
|
|
212
|
+
CREATE FIELDS ( AgencyID CustomerID BeginDate EndDate Description )
|
|
213
|
+
WITH VALUE #( (
|
|
214
|
+
%cid = 'CID_1'
|
|
215
|
+
AgencyID = '000001'
|
|
216
|
+
CustomerID = '000001'
|
|
217
|
+
BeginDate = sy-datum
|
|
218
|
+
EndDate = sy-datum + 10
|
|
219
|
+
Description = 'Test Travel' ) )
|
|
220
|
+
MAPPED DATA(mapped)
|
|
221
|
+
FAILED DATA(failed)
|
|
222
|
+
REPORTED DATA(reported).
|
|
223
|
+
|
|
224
|
+
cl_abap_unit_assert=>assert_not_initial( mapped-travel ).
|
|
225
|
+
cl_abap_unit_assert=>assert_initial( failed-travel ).
|
|
226
|
+
ENDMETHOD.
|
|
227
|
+
|
|
228
|
+
METHOD test_accept_travel.
|
|
229
|
+
" First create
|
|
230
|
+
MODIFY ENTITIES OF ZI_Travel
|
|
231
|
+
ENTITY Travel
|
|
232
|
+
CREATE FIELDS ( AgencyID CustomerID BeginDate EndDate )
|
|
233
|
+
WITH VALUE #( ( %cid = 'CID_1' AgencyID = '000001' CustomerID = '000001'
|
|
234
|
+
BeginDate = sy-datum EndDate = sy-datum + 10 ) )
|
|
235
|
+
MAPPED DATA(mapped) FAILED DATA(failed) REPORTED DATA(reported).
|
|
236
|
+
|
|
237
|
+
" Then execute action
|
|
238
|
+
MODIFY ENTITIES OF ZI_Travel
|
|
239
|
+
ENTITY Travel
|
|
240
|
+
EXECUTE acceptTravel
|
|
241
|
+
FROM VALUE #( ( %tky = mapped-travel[ 1 ]-%tky ) )
|
|
242
|
+
RESULT DATA(result)
|
|
243
|
+
FAILED failed
|
|
244
|
+
REPORTED reported.
|
|
245
|
+
|
|
246
|
+
cl_abap_unit_assert=>assert_equals(
|
|
247
|
+
exp = 'A' act = result[ 1 ]-%param-OverallStatus ).
|
|
248
|
+
ENDMETHOD.
|
|
249
|
+
|
|
250
|
+
METHOD test_validate_dates.
|
|
251
|
+
" Create with invalid dates (end before begin)
|
|
252
|
+
MODIFY ENTITIES OF ZI_Travel
|
|
253
|
+
ENTITY Travel
|
|
254
|
+
CREATE FIELDS ( AgencyID CustomerID BeginDate EndDate )
|
|
255
|
+
WITH VALUE #( ( %cid = 'CID_1' AgencyID = '000001' CustomerID = '000001'
|
|
256
|
+
BeginDate = '20260315' EndDate = '20260310' ) )
|
|
257
|
+
MAPPED DATA(mapped) FAILED DATA(failed) REPORTED DATA(reported).
|
|
258
|
+
|
|
259
|
+
COMMIT ENTITIES
|
|
260
|
+
RESPONSE OF ZI_Travel
|
|
261
|
+
FAILED DATA(commit_failed)
|
|
262
|
+
REPORTED DATA(commit_reported).
|
|
263
|
+
|
|
264
|
+
" Validation should have produced error
|
|
265
|
+
cl_abap_unit_assert=>assert_not_initial( commit_failed-travel ).
|
|
266
|
+
ENDMETHOD.
|
|
267
|
+
ENDCLASS.
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Test Class Annotations
|
|
271
|
+
|
|
272
|
+
```abap
|
|
273
|
+
" Duration
|
|
274
|
+
DURATION SHORT " < 1 second per method (default)
|
|
275
|
+
DURATION MEDIUM " < 5 seconds
|
|
276
|
+
DURATION LONG " > 5 seconds
|
|
277
|
+
|
|
278
|
+
" Risk level
|
|
279
|
+
RISK LEVEL HARMLESS " No DB changes (default)
|
|
280
|
+
RISK LEVEL DANGEROUS " May change DB
|
|
281
|
+
RISK LEVEL CRITICAL " May change system config
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Rules
|
|
285
|
+
- Test class: `FOR TESTING` + `DURATION SHORT` + `RISK LEVEL HARMLESS`
|
|
286
|
+
- Use `setup` for per-test initialization, `class_setup` for one-time setup
|
|
287
|
+
- One assertion per logical check — multiple assertions OK if testing one behavior
|
|
288
|
+
- Use CDS test environment (`cl_cds_test_environment`) for SQL test doubles
|
|
289
|
+
- Use `cl_abap_testdouble` for OO interface mocking
|
|
290
|
+
- RAP BO tests: use `cl_botd_txbufdbl_bo_test_env` for transaction buffer doubles
|
|
291
|
+
- Always `destroy()` environments in `class_teardown`
|
|
292
|
+
- Test method names should describe what is tested: `test_<scenario>`
|
|
293
|
+
|
|
294
|
+
## Anti-Patterns
|
|
295
|
+
| Anti-Pattern | Correct |
|
|
296
|
+
|---|---|
|
|
297
|
+
| Testing private methods directly | Test via public interface |
|
|
298
|
+
| No assertions in test method | Always assert expected outcome |
|
|
299
|
+
| `CATCH cx_root` hiding test failures | Let exceptions propagate or assert them |
|
|
300
|
+
| DB access in RISK LEVEL HARMLESS | Use test doubles or set RISK LEVEL DANGEROUS |
|
|
301
|
+
| Missing `setup` / `class_setup` | Initialize test data properly |
|
|
302
|
+
| Huge test methods testing everything | One scenario per test method |
|
|
303
|
+
| Hardcoded dates that expire | Use `sy-datum` + offset |
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# CDS Access Control — DCL Row-Level Authorization
|
|
2
|
+
|
|
3
|
+
## DCL Structure
|
|
4
|
+
|
|
5
|
+
```sql
|
|
6
|
+
@EndUserText.label: 'Access Control for Z_SALES'
|
|
7
|
+
@MappingRole: true
|
|
8
|
+
define role Z_SALES_DCL {
|
|
9
|
+
grant select on Z_SALES
|
|
10
|
+
where condition;
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## PFCG Authorization — Single Object
|
|
15
|
+
|
|
16
|
+
```sql
|
|
17
|
+
@MappingRole: true
|
|
18
|
+
define role Z_FIN_DCL {
|
|
19
|
+
grant select on Z_FINANCIAL_DATA
|
|
20
|
+
where (bukrs) = aspect pfcg_auth(F_BKPF_BUK, BUKRS, ACTVT = '03');
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Syntax: `(cds_field) = aspect pfcg_auth(AUTH_OBJECT, AUTH_FIELD, ACTVT = 'value')`
|
|
25
|
+
|
|
26
|
+
## PFCG — Multiple Fields
|
|
27
|
+
|
|
28
|
+
```sql
|
|
29
|
+
@MappingRole: true
|
|
30
|
+
define role Z_SD_DCL {
|
|
31
|
+
grant select on Z_SALES_ORDER
|
|
32
|
+
where (vkorg, vtweg, spart) =
|
|
33
|
+
aspect pfcg_auth(V_VBAK_VKO, VKORG, VTWEG, SPART, ACTVT = '03');
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Fields map positionally to authorization fields.
|
|
38
|
+
|
|
39
|
+
## PFCG — Multiple Objects
|
|
40
|
+
|
|
41
|
+
```sql
|
|
42
|
+
@MappingRole: true
|
|
43
|
+
define role Z_MULTI_DCL {
|
|
44
|
+
grant select on Z_VIEW
|
|
45
|
+
where (bukrs) = aspect pfcg_auth(F_BKPF_BUK, BUKRS, ACTVT = '03')
|
|
46
|
+
and (vkorg, vtweg, spart) =
|
|
47
|
+
aspect pfcg_auth(V_VBAK_VKO, VKORG, VTWEG, SPART, ACTVT = '03');
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Literal Condition
|
|
52
|
+
|
|
53
|
+
```sql
|
|
54
|
+
@MappingRole: true
|
|
55
|
+
define role Z_ACTIVE_DCL {
|
|
56
|
+
grant select on Z_VIEW
|
|
57
|
+
where status = 'ACTIVE';
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Operators: `=`, `<>`, `<`, `>`, `<=`, `>=`, `between ... and ...`, `like`
|
|
62
|
+
|
|
63
|
+
## User Aspect
|
|
64
|
+
|
|
65
|
+
```sql
|
|
66
|
+
@MappingRole: true
|
|
67
|
+
define role Z_OWN_DCL {
|
|
68
|
+
grant select on Z_TASKS
|
|
69
|
+
where assigned_to ?= aspect user;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`?=` allows NULL/initial values to pass.
|
|
74
|
+
|
|
75
|
+
## Environment Aspect
|
|
76
|
+
|
|
77
|
+
```sql
|
|
78
|
+
where client = aspect environment.client
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Combining Conditions — AND
|
|
82
|
+
|
|
83
|
+
```sql
|
|
84
|
+
where (bukrs) = aspect pfcg_auth(F_BKPF_BUK, BUKRS, ACTVT = '03')
|
|
85
|
+
and status = 'ACTIVE'
|
|
86
|
+
and created_by ?= aspect user;
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Combining Conditions — OR
|
|
90
|
+
|
|
91
|
+
```sql
|
|
92
|
+
where status = 'PUBLIC'
|
|
93
|
+
or created_by ?= aspect user;
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Complex Logic
|
|
97
|
+
|
|
98
|
+
```sql
|
|
99
|
+
where (
|
|
100
|
+
(bukrs) = aspect pfcg_auth(F_BKPF_BUK, BUKRS, ACTVT = '03')
|
|
101
|
+
and status = 'ACTIVE'
|
|
102
|
+
)
|
|
103
|
+
or status = 'PUBLIC';
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Sales Org + Status Filter
|
|
107
|
+
|
|
108
|
+
```sql
|
|
109
|
+
@MappingRole: true
|
|
110
|
+
define role Z_SALES_FILTER_DCL {
|
|
111
|
+
grant select on Z_SALES_ORDER
|
|
112
|
+
where (vkorg, vtweg, spart) =
|
|
113
|
+
aspect pfcg_auth(V_VBAK_VKO, VKORG, VTWEG, SPART, ACTVT = '03')
|
|
114
|
+
and status <> 'DELETED';
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Public + Owned Records
|
|
119
|
+
|
|
120
|
+
```sql
|
|
121
|
+
@MappingRole: true
|
|
122
|
+
define role Z_MIXED_DCL {
|
|
123
|
+
grant select on Z_DOCUMENTS
|
|
124
|
+
where visibility = 'PUBLIC'
|
|
125
|
+
or created_by ?= aspect user;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Multi-level Authorization
|
|
130
|
+
|
|
131
|
+
```sql
|
|
132
|
+
@MappingRole: true
|
|
133
|
+
define role Z_MATERIAL_DCL {
|
|
134
|
+
grant select on Z_MATERIAL_DATA
|
|
135
|
+
where (werks) = aspect pfcg_auth(M_MATE_WRK, WERKS, ACTVT = '03')
|
|
136
|
+
and (mtart) = aspect pfcg_auth(M_MATE_MAR, MTART, ACTVT = '03');
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## CDS View Annotation
|
|
141
|
+
|
|
142
|
+
```sql
|
|
143
|
+
@AccessControl.authorizationCheck: #CHECK
|
|
144
|
+
define view Z_SECURED as select from ...
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
| Value | Behavior |
|
|
148
|
+
|-------|----------|
|
|
149
|
+
| `#NOT_REQUIRED` | No DCL needed, full access |
|
|
150
|
+
| `#CHECK` | Warning if DCL missing |
|
|
151
|
+
| `#MANDATORY` | Syntax error if DCL missing |
|
|
152
|
+
| `#NOT_ALLOWED` | DCL ignored |
|
|
153
|
+
|
|
154
|
+
## Common Authorization Objects
|
|
155
|
+
|
|
156
|
+
| Object | Fields | Domain |
|
|
157
|
+
|--------|--------|--------|
|
|
158
|
+
| `F_BKPF_BUK` | BUKRS, ACTVT | Company code |
|
|
159
|
+
| `F_BKPF_GSB` | GSBER, ACTVT | Business area |
|
|
160
|
+
| `V_VBAK_VKO` | VKORG, VTWEG, SPART, ACTVT | Sales org |
|
|
161
|
+
| `V_VBAK_AAT` | AUART, ACTVT | Order type |
|
|
162
|
+
| `M_MATE_WRK` | WERKS, ACTVT | Plant |
|
|
163
|
+
| `M_MATE_MAR` | MTART, ACTVT | Material type |
|
|
164
|
+
| `K_CCA` | KOKRS, KOSTL, ACTVT | Cost center |
|
|
165
|
+
| `K_ORDER` | AUFNR, ACTVT | Internal order |
|
|
166
|
+
|
|
167
|
+
## Activity Values (ACTVT)
|
|
168
|
+
|
|
169
|
+
| Value | Activity |
|
|
170
|
+
|-------|----------|
|
|
171
|
+
| `01` | Create |
|
|
172
|
+
| `02` | Change |
|
|
173
|
+
| `03` | Display |
|
|
174
|
+
| `06` | Delete |
|
|
175
|
+
| `16` | Execute |
|
|
176
|
+
|
|
177
|
+
Most CDS views use `ACTVT = '03'`.
|
|
178
|
+
|
|
179
|
+
## ABAP — Bypass Check
|
|
180
|
+
|
|
181
|
+
```abap
|
|
182
|
+
" DCL automatically applied
|
|
183
|
+
SELECT * FROM z_secured INTO TABLE @DATA(lt_data).
|
|
184
|
+
|
|
185
|
+
" BYPASSING BUFFER does NOT bypass DCL
|
|
186
|
+
" There is no way to bypass DCL from ABAP
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Full Example — Sales Order DCL
|
|
190
|
+
|
|
191
|
+
```sql
|
|
192
|
+
@EndUserText.label: 'Sales Order Access Control'
|
|
193
|
+
@MappingRole: true
|
|
194
|
+
define role Z_SO_FULL_DCL {
|
|
195
|
+
grant select on Z_SALES_ORDER
|
|
196
|
+
where (vkorg, vtweg, spart) =
|
|
197
|
+
aspect pfcg_auth(V_VBAK_VKO, VKORG, VTWEG, SPART, ACTVT = '03')
|
|
198
|
+
and (bukrs) = aspect pfcg_auth(F_BKPF_BUK, BUKRS, ACTVT = '03')
|
|
199
|
+
and status <> 'DELETED';
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Hierarchy Pattern — Consistent DCL
|
|
204
|
+
|
|
205
|
+
```sql
|
|
206
|
+
-- Interface view
|
|
207
|
+
@MappingRole: true
|
|
208
|
+
define role Z_I_SO_DCL {
|
|
209
|
+
grant select on Z_I_SALES_ORDER
|
|
210
|
+
where (bukrs) = aspect pfcg_auth(F_BKPF_BUK, BUKRS, ACTVT = '03');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
-- Consumption view (same auth, different entity)
|
|
214
|
+
@MappingRole: true
|
|
215
|
+
define role Z_C_SO_DCL {
|
|
216
|
+
grant select on Z_C_SALES_ORDER
|
|
217
|
+
where (bukrs) = aspect pfcg_auth(F_BKPF_BUK, BUKRS, ACTVT = '03');
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Rules
|
|
222
|
+
|
|
223
|
+
- `@MappingRole: true` is required on every DCL role
|
|
224
|
+
- DCL does NOT inherit to consuming views — each view needs its own DCL
|
|
225
|
+
- Use `?=` (optional operator) for fields that may be NULL/initial
|
|
226
|
+
- Most CDS views use ACTVT = '03' (display)
|
|
227
|
+
- Use `#CHECK` or `#MANDATORY` on sensitive views
|
|
228
|
+
- Create DCL for all views in a hierarchy, not just the base
|
|
229
|
+
- Find auth objects in SU21, test with SU53
|
|
230
|
+
|
|
231
|
+
## Anti-Patterns
|
|
232
|
+
|
|
233
|
+
| Anti-Pattern | Correct |
|
|
234
|
+
|---|---|
|
|
235
|
+
| `#NOT_REQUIRED` on sensitive data | Use `#CHECK` or `#MANDATORY` |
|
|
236
|
+
| Assuming DCL inheritance from base view | Create DCL per view in chain |
|
|
237
|
+
| Using `=` instead of `?=` when field can be NULL | Use `?=` for nullable fields |
|
|
238
|
+
| Missing `@MappingRole: true` | Always add — required for role mapping |
|
|
239
|
+
| Complex OR chains without parentheses | Use explicit parentheses for logic clarity |
|
|
240
|
+
| Relying only on app-level auth checks | Add DCL as defense in depth |
|
|
241
|
+
| Using ACTVT = '02' for read-only views | Use ACTVT = '03' for display |
|