mock-sap 0.1.0__tar.gz
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.
- mock_sap-0.1.0/Dockerfile +7 -0
- mock_sap-0.1.0/LICENSE +21 -0
- mock_sap-0.1.0/MANIFEST.in +3 -0
- mock_sap-0.1.0/PKG-INFO +369 -0
- mock_sap-0.1.0/README.md +339 -0
- mock_sap-0.1.0/examples/client.py +90 -0
- mock_sap-0.1.0/examples/demo.sh +105 -0
- mock_sap-0.1.0/mock_sap.egg-info/PKG-INFO +369 -0
- mock_sap-0.1.0/mock_sap.egg-info/SOURCES.txt +25 -0
- mock_sap-0.1.0/mock_sap.egg-info/dependency_links.txt +1 -0
- mock_sap-0.1.0/mock_sap.egg-info/entry_points.txt +2 -0
- mock_sap-0.1.0/mock_sap.egg-info/top_level.txt +1 -0
- mock_sap-0.1.0/mocksap/__init__.py +9 -0
- mock_sap-0.1.0/mocksap/__main__.py +64 -0
- mock_sap-0.1.0/mocksap/bapi.py +509 -0
- mock_sap-0.1.0/mocksap/batch.py +194 -0
- mock_sap-0.1.0/mocksap/db.py +442 -0
- mock_sap-0.1.0/mocksap/idoc.py +218 -0
- mock_sap-0.1.0/mocksap/metadata.py +169 -0
- mock_sap-0.1.0/mocksap/odata.py +553 -0
- mock_sap-0.1.0/mocksap/schema.py +449 -0
- mock_sap-0.1.0/mocksap/server.py +578 -0
- mock_sap-0.1.0/mocksap/service.py +400 -0
- mock_sap-0.1.0/mocksap/store.py +284 -0
- mock_sap-0.1.0/pyproject.toml +42 -0
- mock_sap-0.1.0/setup.cfg +4 -0
- mock_sap-0.1.0/tests/test_mocksap.py +525 -0
mock_sap-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mock-sap contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mock_sap-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mock-sap
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A black-box mock SAP endpoint: OData V2, BAPI/RFC and IDoc shapes over SQLite, with zero dependencies.
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/rseufert/mock-sap
|
|
7
|
+
Project-URL: Repository, https://github.com/rseufert/mock-sap
|
|
8
|
+
Project-URL: Issues, https://github.com/rseufert/mock-sap/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/rseufert/mock-sap/releases
|
|
10
|
+
Keywords: sap,odata,mock,bapi,rfc,idoc,s4hana,testing,integration
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
24
|
+
Classifier: Topic :: Software Development :: Testing
|
|
25
|
+
Classifier: Topic :: Software Development :: Testing :: Mocking
|
|
26
|
+
Requires-Python: >=3.8
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# mock-sap
|
|
32
|
+
|
|
33
|
+
[](https://github.com/rseufert/mock-sap/actions/workflows/ci.yml)
|
|
34
|
+
[](https://www.python.org/downloads/)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
[](https://pypi.org/project/mock-sap/)
|
|
37
|
+
|
|
38
|
+
A **black-box mock SAP endpoint**. It does not implement any SAP business logic —
|
|
39
|
+
it speaks the shapes: SAP Gateway **OData V2** services, **BAPI/RFC** calls over JSON
|
|
40
|
+
and SOAP, and **IDocs** in XML and EDI_DC40 flat-file form. Data is kept in SQLite.
|
|
41
|
+
|
|
42
|
+
Point your integration at it and develop, test and demo without an SAP system,
|
|
43
|
+
an SAP licence, or a VPN tunnel to someone's sandbox.
|
|
44
|
+
|
|
45
|
+
- **Zero dependencies.** Python 3.8+ standard library and SQLite, nothing else.
|
|
46
|
+
- **Real wire shapes.** `{"d":{"results":[…]}}`, `__metadata`, `__deferred`,
|
|
47
|
+
`/Date(1700000000000)/`, decimals as strings, EDMX `$metadata` with `sap:` annotations,
|
|
48
|
+
CSRF tokens, `$batch` with atomic changesets, BAPIRET2 return tables, EDI_DC40 control records.
|
|
49
|
+
- **Deterministic demo data.** Business partners, products, sales orders, purchase orders —
|
|
50
|
+
same data every run for the same `--seed`.
|
|
51
|
+
- **Failure modes on demand.** Latency, 500s, locked documents, "no work process available",
|
|
52
|
+
expired CSRF tokens — per request, or as programmable rules.
|
|
53
|
+
|
|
54
|
+
MIT licensed. SAP, S/4HANA, ABAP, NetWeaver, BAPI and IDoc are trademarks of SAP SE;
|
|
55
|
+
this project is not affiliated with, endorsed by, or connected to SAP SE, and imitates
|
|
56
|
+
publicly documented wire formats for testing purposes only.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install mock-sap
|
|
64
|
+
mock-sap --port 8000
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
curl "http://127.0.0.1:8000/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder?\$top=1&\$format=json"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"d": {
|
|
74
|
+
"results": [
|
|
75
|
+
{
|
|
76
|
+
"__metadata": {
|
|
77
|
+
"id": "http://127.0.0.1:8000/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder('0000004712')",
|
|
78
|
+
"uri": "http://127.0.0.1:8000/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder('0000004712')",
|
|
79
|
+
"type": "API_SALES_ORDER_SRV.A_SalesOrderType"
|
|
80
|
+
},
|
|
81
|
+
"SalesOrder": "0000004712",
|
|
82
|
+
"SalesOrderType": "OR",
|
|
83
|
+
"SalesOrganization": "1710",
|
|
84
|
+
"SoldToParty": "1000004",
|
|
85
|
+
"TransactionCurrency": "EUR",
|
|
86
|
+
"TotalNetAmount": "123991.820",
|
|
87
|
+
"CreationDate": "/Date(1754611200000)/",
|
|
88
|
+
"to_Item": { "__deferred": { "uri": ".../A_SalesOrder('0000004712')/to_Item" } }
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Or run it straight from a checkout, with no install at all, or in a container:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
python3 -m mocksap --port 8000
|
|
99
|
+
docker build -t mock-sap . && docker run -p 8000:8000 mock-sap
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
A guided tour of every endpoint, in curl:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
bash examples/demo.sh
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## What it serves
|
|
109
|
+
|
|
110
|
+
| Surface | Endpoint |
|
|
111
|
+
| --- | --- |
|
|
112
|
+
| Service catalog | `GET /sap/opu/odata/IWFND/CATALOGSERVICE;v=2/ServiceCollection` |
|
|
113
|
+
| Business Partner | `/sap/opu/odata/sap/API_BUSINESS_PARTNER_SRV` |
|
|
114
|
+
| Product | `/sap/opu/odata/sap/API_PRODUCT_SRV` |
|
|
115
|
+
| Sales Order | `/sap/opu/odata/sap/API_SALES_ORDER_SRV` |
|
|
116
|
+
| Purchase Order | `/sap/opu/odata/sap/API_PURCHASEORDER_PROCESS_SRV` |
|
|
117
|
+
| BAPI over JSON | `POST /sap/bc/rfc/<FUNCTION_MODULE>` |
|
|
118
|
+
| BAPI over SOAP | `POST /sap/bc/srt/rfc/sap/<service>/<client>/<name>/<binding>` |
|
|
119
|
+
| IDoc inbound | `POST /sap/bc/idoc` (XML or flat file) |
|
|
120
|
+
| IDoc outbound | `POST /sap/bc/idoc/generate` → ORDERS05 |
|
|
121
|
+
| Mock control plane | `/_mock/health`, `/_mock/state`, `/_mock/requests`, `/_mock/faults`, `/_mock/reset` |
|
|
122
|
+
|
|
123
|
+
Entity sets carry the S/4HANA field names — `A_SalesOrder` with `SoldToParty`,
|
|
124
|
+
`TotalNetAmount`, `OverallSDProcessStatus`, `to_Item`; `A_BusinessPartner` with
|
|
125
|
+
`BusinessPartnerCategory`, `to_BusinessPartnerAddress`; and so on. Browse
|
|
126
|
+
`/sap/opu/odata/sap/<SERVICE>/$metadata` for the full picture, or open
|
|
127
|
+
`http://127.0.0.1:8000/` for an index page.
|
|
128
|
+
|
|
129
|
+
## OData V2 support
|
|
130
|
+
|
|
131
|
+
| Feature | Notes |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `$filter` | `eq ne gt ge lt le`, `and or not`, parentheses, arithmetic, `substringof`, `startswith`, `endswith`, `contains`, `tolower`, `toupper`, `trim`, `length`, `concat`, `substring`, `indexof`, `year`…`second`. Compiled to parameterised SQL. |
|
|
134
|
+
| `$select` `$expand` | `$expand` follows to-one and to-many navigations, nested paths included |
|
|
135
|
+
| `$orderby` `$top` `$skip` | |
|
|
136
|
+
| `$inlinecount=allpages`, `/$count` | |
|
|
137
|
+
| `$format=json`, Accept negotiation | XML/Atom for the service document and errors |
|
|
138
|
+
| `$metadata` | EDMX 1.0 with associations, referential constraints and `sap:label`/`sap:creatable`/`sap:updatable` |
|
|
139
|
+
| `$batch` | multipart/mixed, changesets execute atomically and roll back as a unit |
|
|
140
|
+
| Writes | `POST` (incl. deep insert), `PATCH`/`MERGE`, `PUT`, `DELETE`, `POST` to a navigation |
|
|
141
|
+
| Conventions | CSRF tokens, `sap-client`, `DataServiceVersion`, `Location` on create, SAP error envelope with `/IWBEP/CX_MGW_*` codes |
|
|
142
|
+
|
|
143
|
+
### Writing requires a CSRF token
|
|
144
|
+
|
|
145
|
+
Exactly as against a real Gateway:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
TOKEN=$(curl -s -D - -o /dev/null -H 'X-CSRF-Token: Fetch' \
|
|
149
|
+
http://127.0.0.1:8000/sap/opu/odata/sap/API_SALES_ORDER_SRV/ \
|
|
150
|
+
| awk 'tolower($1)=="x-csrf-token:"{print $2}' | tr -d '\r')
|
|
151
|
+
|
|
152
|
+
curl -X POST "http://127.0.0.1:8000/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder?\$expand=to_Item" \
|
|
153
|
+
-H "X-CSRF-Token: $TOKEN" -H 'Content-Type: application/json' \
|
|
154
|
+
-d '{"SalesOrderType":"OR","SalesOrganization":"1710","DistributionChannel":"10",
|
|
155
|
+
"OrganizationDivision":"00","SoldToParty":"1000001","TransactionCurrency":"EUR",
|
|
156
|
+
"to_Item":[{"Material":"TG11","RequestedQuantity":"2","NetAmount":"1998.00"}]}'
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The document number comes from a number range, items are numbered `000010`, `000020`, …,
|
|
160
|
+
`TotalNetAmount` is recalculated from the items, administrative fields are filled in,
|
|
161
|
+
and unset fields come back as ABAP initial values (`""`, `0.000`) rather than `null`.
|
|
162
|
+
Run with `--no-csrf` to switch the check off.
|
|
163
|
+
|
|
164
|
+
## BAPI / RFC
|
|
165
|
+
|
|
166
|
+
The same function modules are reachable over JSON and over SOAP. Parameter names are
|
|
167
|
+
matched case- and underscore-insensitively, so `ORDER_HEADER_IN` and `OrderHeaderIn`
|
|
168
|
+
both work.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
curl -X POST http://127.0.0.1:8000/sap/bc/rfc/BAPI_SALESORDER_CREATEFROMDAT2 \
|
|
172
|
+
-H "X-CSRF-Token: $TOKEN" -H 'Content-Type: application/json' \
|
|
173
|
+
-d '{"ORDER_HEADER_IN":{"DOC_TYPE":"OR","SALES_ORG":"1710","DISTR_CHAN":"10","DIVISION":"00"},
|
|
174
|
+
"ORDER_PARTNERS":[{"PARTN_ROLE":"AG","PARTN_NUMB":"0001000001"}],
|
|
175
|
+
"ORDER_ITEMS_IN":[{"ITM_NUMBER":"000010","MATERIAL":"TG11","REQ_QTY":"3","COND_VALUE":"1500.00"}]}'
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"SALESDOCUMENT": "0000004737",
|
|
181
|
+
"RETURN": [{
|
|
182
|
+
"TYPE": "S", "ID": "V1", "NUMBER": "311",
|
|
183
|
+
"MESSAGE": "Standard Order 4737 has been saved",
|
|
184
|
+
"MESSAGE_V1": "Standard Order", "MESSAGE_V2": "4737",
|
|
185
|
+
"LOG_NO": "", "LOG_MSG_NO": "000000", "PARAMETER": "", "ROW": 0, "FIELD": "",
|
|
186
|
+
"SYSTEM": "MCKCLNT100"
|
|
187
|
+
}]
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Errors arrive the way BAPIs report them — `TYPE: "E"` in the `RETURN` table with a
|
|
192
|
+
message number, not an HTTP error. Unknown material, unknown customer, missing
|
|
193
|
+
sold-to party and test runs (`TESTRUN: "X"`) are all modelled.
|
|
194
|
+
|
|
195
|
+
Available: `BAPI_SALESORDER_CREATEFROMDAT2`, `BAPI_SALESORDER_GETLIST`,
|
|
196
|
+
`BAPI_SALESORDER_GETSTATUS`, `BAPI_PO_CREATE1`, `BAPI_PO_GETDETAIL1`,
|
|
197
|
+
`BAPI_MATERIAL_GET_DETAIL`, `BAPI_BUSINESS_PARTNER_GETDETAIL`,
|
|
198
|
+
`BAPI_TRANSACTION_COMMIT`, `BAPI_TRANSACTION_ROLLBACK`, `RFC_PING`, `STFC_CONNECTION`.
|
|
199
|
+
`GET /_mock/services` lists them; `POST /sap/bc/rfc/` with no name does too.
|
|
200
|
+
|
|
201
|
+
SOAP uses the `urn:sap-com:document:sap:soap:functions:mc-style` namespace, returns
|
|
202
|
+
`<…Response>` envelopes, and answers unknown functions with a SOAP fault.
|
|
203
|
+
|
|
204
|
+
## IDoc
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# outbound: render a stored sales order as ORDERS05
|
|
208
|
+
curl -X POST "http://127.0.0.1:8000/sap/bc/idoc/generate?format=xml" \
|
|
209
|
+
-H "X-CSRF-Token: $TOKEN" -H 'Content-Type: application/json' \
|
|
210
|
+
-d '{"SalesOrder":"0000004712"}'
|
|
211
|
+
|
|
212
|
+
# inbound: post an IDoc and get its status record back
|
|
213
|
+
curl -X POST http://127.0.0.1:8000/sap/bc/idoc \
|
|
214
|
+
-H "X-CSRF-Token: $TOKEN" -H 'Content-Type: application/xml' -H 'Accept: application/json' \
|
|
215
|
+
--data-binary @order.xml
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Generated IDocs carry a full `EDI_DC40` control record plus `E1EDK01`, `E1EDK14`,
|
|
219
|
+
`E1EDK03`, `E1EDKA1`, `E1EDP01`/`E1EDP19` segments. Inbound IDocs are parsed (XML, or a
|
|
220
|
+
flat file read at the documented EDI_DC40 offsets), stored with a 16-digit IDoc number
|
|
221
|
+
and status `53`, and can be inspected or re-statused:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
curl http://127.0.0.1:8000/_mock/idocs
|
|
225
|
+
curl -X PUT http://127.0.0.1:8000/sap/bc/idoc/<DOCNUM>/status \
|
|
226
|
+
-H "X-CSRF-Token: $TOKEN" -d '{"status":"51"}'
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Simulating a bad day
|
|
230
|
+
|
|
231
|
+
Per request, with a header or a query parameter:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
curl -H 'sap-mock-scenario: busy' http://127.0.0.1:8000/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
| Scenario | Effect |
|
|
238
|
+
| --- | --- |
|
|
239
|
+
| `slow` | delays by `--slow-ms` (default 3s) |
|
|
240
|
+
| `timeout` | sleeps past any client timeout |
|
|
241
|
+
| `error` | 500, Gateway technical exception |
|
|
242
|
+
| `busy` | 503 with `Retry-After`, "no dialog work process available" |
|
|
243
|
+
| `auth` / `forbidden` | 401 with a NetWeaver realm / 403 missing authorization |
|
|
244
|
+
| `lock` | 423, document locked by another user |
|
|
245
|
+
| `csrf` | 403 with `x-csrf-token: Required` |
|
|
246
|
+
| `notfound` | 404 |
|
|
247
|
+
|
|
248
|
+
As rules, matched by regex on the path:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
curl -X POST http://127.0.0.1:8000/_mock/faults -H 'Content-Type: application/json' \
|
|
252
|
+
-d '{"match":"A_SalesOrder","method":"POST","status":500,"message":"Backend unreachable","count":1}'
|
|
253
|
+
curl -X DELETE http://127.0.0.1:8000/_mock/faults # clear all rules
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Or globally, at startup: `--latency-ms 250 --error-rate 0.05`.
|
|
257
|
+
|
|
258
|
+
## Inspecting what your client did
|
|
259
|
+
|
|
260
|
+
Every request is recorded, which makes the mock useful as a contract check in CI:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
curl "http://127.0.0.1:8000/_mock/requests?limit=10" # method, path, query, status, duration
|
|
264
|
+
curl http://127.0.0.1:8000/_mock/rfc-log # which BAPIs were called
|
|
265
|
+
curl http://127.0.0.1:8000/_mock/state # row counts per entity
|
|
266
|
+
curl -X POST http://127.0.0.1:8000/_mock/reset \
|
|
267
|
+
-H 'Content-Type: application/json' -d '{"seed":7,"orders":50}'
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`POST /_mock/reset` restores a known dataset between test cases.
|
|
271
|
+
|
|
272
|
+
## Command line
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
mock-sap [--host 127.0.0.1] [--port 8000] [--db :memory:|path.db] [--client 100]
|
|
276
|
+
[--user MOCKUSER] [--auth USER:PASSWORD] [--no-csrf] [--seed 42]
|
|
277
|
+
[--latency-ms 0] [--error-rate 0.0] [--slow-ms 3000] [--no-request-log] [-q]
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
`--db mock.db` keeps data across restarts; the default in-memory system starts fresh
|
|
281
|
+
every time. `--auth` turns on HTTP basic authentication with a NetWeaver realm.
|
|
282
|
+
|
|
283
|
+
## Using it from tests
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
from mocksap import Config, make_server
|
|
287
|
+
|
|
288
|
+
httpd = make_server(Config(port=0, db_path=":memory:", csrf=False, quiet=True))
|
|
289
|
+
port = httpd.server_address[1]
|
|
290
|
+
threading.Thread(target=httpd.serve_forever, daemon=True).start()
|
|
291
|
+
# point the code under test at http://127.0.0.1:{port} ...
|
|
292
|
+
httpd.shutdown()
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`examples/client.py` is a dependency-free client showing the token/cookie flow.
|
|
296
|
+
|
|
297
|
+
## Adding entity sets
|
|
298
|
+
|
|
299
|
+
Everything is generated from the declarations in
|
|
300
|
+
[`mocksap/schema.py`](mocksap/schema.py) — tables, `$metadata`, payload shapes and
|
|
301
|
+
key handling. Add an `EntityType` with its `Prop`s and `Nav`s, list it in a `Service`,
|
|
302
|
+
and it is fully queryable and writable:
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
_register(EntityType("A_BillingDocument", label="Billing Document", props=[
|
|
306
|
+
S("BillingDocument", key=True, nullable=False, max_length=10),
|
|
307
|
+
S("BillingDocumentType", max_length=4),
|
|
308
|
+
DEC("TotalNetAmount", precision=16, scale=3),
|
|
309
|
+
DT("BillingDocumentDate"),
|
|
310
|
+
], navs=[Nav("to_Item", "A_BillingDocumentItem", "*", [("BillingDocument", "BillingDocument")])]))
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Seed data for it goes in `mocksap/db.py`, a BAPI wrapper (if you need one) in
|
|
314
|
+
`mocksap/bapi.py`.
|
|
315
|
+
|
|
316
|
+
## Tests
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
python3 -m unittest discover -s tests -v
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
31 tests, every one of them over real HTTP against a running mock: metadata,
|
|
323
|
+
query options, error envelopes, CSRF, deep insert, `$batch` rollback, BAPI JSON and
|
|
324
|
+
SOAP, IDoc round-trip, fault injection and authentication.
|
|
325
|
+
|
|
326
|
+
CI runs them on Python 3.8-3.13 across Linux, macOS and Windows, and additionally
|
|
327
|
+
checks that `examples/demo.sh`, the packaged wheel and the Docker image still work.
|
|
328
|
+
|
|
329
|
+
## Releasing
|
|
330
|
+
|
|
331
|
+
Releases go to PyPI through [Trusted Publishing](https://docs.pypi.org/trusted-publishers/):
|
|
332
|
+
PyPI trusts this repository's `publish.yml` workflow directly, so no API token is
|
|
333
|
+
stored in the repository or on anyone's laptop. Publishing a GitHub Release runs the
|
|
334
|
+
test suite, builds the sdist and wheel, checks that the tag matches the version in
|
|
335
|
+
`pyproject.toml`, and uploads. Running the workflow manually publishes to TestPyPI
|
|
336
|
+
instead, to rehearse.
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
# bump version in pyproject.toml and mocksap/__init__.py first
|
|
340
|
+
git tag v0.1.0 && git push origin v0.1.0
|
|
341
|
+
gh release create v0.1.0 --generate-notes
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Layout
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
mocksap/schema.py entity types, navigations, services (add shapes here)
|
|
348
|
+
mocksap/db.py SQLite schema, number ranges, seed data
|
|
349
|
+
mocksap/odata.py $filter parser, key predicates, JSON shaping, error envelope
|
|
350
|
+
mocksap/metadata.py EDMX / service document
|
|
351
|
+
mocksap/store.py CRUD, deep insert, cascades, document defaults
|
|
352
|
+
mocksap/service.py OData request dispatcher
|
|
353
|
+
mocksap/batch.py $batch multipart and atomic changesets
|
|
354
|
+
mocksap/bapi.py BAPI/RFC functions, JSON and SOAP transports
|
|
355
|
+
mocksap/idoc.py IDoc inbox/outbox, ORDERS05 generation
|
|
356
|
+
mocksap/server.py HTTP front end, CSRF, auth, fault injection, /_mock API
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## Scope
|
|
360
|
+
|
|
361
|
+
This is a black box. It stores what you send and returns it in SAP's shapes.
|
|
362
|
+
It does **not** do ATP checks, pricing procedures, output determination,
|
|
363
|
+
authorization objects, workflow or any other real SAP logic. What it is good for:
|
|
364
|
+
developing and testing integrations, contract tests in CI, demos, and load-testing
|
|
365
|
+
your side of the wire.
|
|
366
|
+
|
|
367
|
+
Not implemented yet, and the obvious next contributions: OData V4 services,
|
|
368
|
+
complex (structured) types, `$links`, ETags with `If-Match`, and OAuth/SAML instead
|
|
369
|
+
of basic authentication.
|