Appearance
Mock AIOTIC server
Python SDK
A local stand-in for a tenant, for development and CI. It implements the public API with realistic behaviour and really calls your ERP receive endpoint on POST /erp/send.
bash
aiotic mock # http://localhost:8080
python -m aiotic.mock # same, without the CLI extra
docker compose -f mock/docker-compose.yml up # mock + demo integration service| Setting | Env / flag | Default |
|---|---|---|
| Keys | fixed | mock-integration-key (all endpoints), mock-sync-key (master data only) |
| ERP receive URL / key | MOCK_ERP_URL, MOCK_ERP_KEY or --erp-url/--erp-key or POST /_mock/config | none → /erp/send answers 503 like a tenant without ERP config |
| Processing webhook | MOCK_WEBHOOK_URL, MOCK_WEBHOOK_KEY | off |
| Processing time | MOCK_PROCESSING_SECONDS / --processing-speed | 3 s |
Behaviour
- Uploads move
QUEUED→PROCESSING→PROCESSEDwith a realistic extracted order (result), PO number taken from the file name (PO-4711.pdf→PO-4711). - File name contains
attention→ATTENTIONwith one unresolved line (article_number: null,customer_item_number: "LT-ART-999"). - File name contains
fail→FAILEDwithlast_error;POST /order/retrycreates a new order that succeeds. - Raw
.emlcontaining "quotation"/"offerte" →400 not_a_purchase_order+ entry in/rejected/list; containing twoSubject:lines orSPLIT→ split into two child orders with anemail_group_id. - Master data: seeded with customer
58931, productsPROD-001,PROD-002,620206_01(nl) and one mapping; a mapping needs an existing customer and product (404otherwise); the sync key is rejected on non-master-data paths (401). /erp/send: status gate (409),SENDINGlock, realPOSTto the ERP URL withX-API-KEY,successinterpretation,422/500and rollback,erp_refon success.
Mock-only helpers
| Endpoint | Purpose |
|---|---|
POST /_mock/config {erp_url, erp_key, webhook_url, webhook_key} | set targets at runtime |
POST /_mock/orders/{id}/status {"status": "MODIFIED"} | force a status the API cannot set (e.g. MODIFIED, CANCELED) to test your handling |
In tests
A pytest fixture that starts the mock in a background thread and hands you a client:
python
import asyncio, threading, pytest, uvicorn
from aiotic import AioticClient
from aiotic.mock import create_mock_app
@pytest.fixture(scope="session")
def client():
server = uvicorn.Server(uvicorn.Config(create_mock_app(processing_seconds=0.3), host="127.0.0.1", port=0, log_level="warning"))
server.install_signal_handlers = lambda: None
threading.Thread(target=server.run, daemon=True).start()
while not server.started:
asyncio.run(asyncio.sleep(0.05))
port = server.servers[0].sockets[0].getsockname()[1]
with AioticClient(base_url=f"http://127.0.0.1:{port}", api_key="mock-integration-key", rate_limit=0) as c:
yield c
server.should_exit = TrueFor pure unit tests of the receive endpoint you do not need the mock at all — call ErpReceiver.handle() directly.
Differences from a real tenant
No AI: the extraction is canned. No mailbox. No management endpoints. No operator app. Timing is instant-ish. Everything about the contracts — paths, headers, status codes, JSON shapes, the ERP call — matches the public API.