Skip to main content

Mock server for local development & CI

Goal

Run adt-cli commands, plugin tests, and integration suites without a live SAP system. @abapify/adt-fixtures ships a minimal in-process HTTP server that speaks the ADT protocol (security sessions, CSRF, locks, ETags) and serves real captured XML fixtures for the most common endpoints.

When to use it

Use caseFit
Unit/integration tests for plugins
CI smoke tests that don't need real data
Local offline development (trains, planes)
AI-agent tool development (MCP)
End-to-end regression against prod-like data❌ — use real system

Prerequisites

  • Node.js ≥ 20
  • @abapify/adt-fixtures available — already a workspace dep in this repo, or install with npm i -D @abapify/adt-fixtures in external projects.

Starting the server

// tests/mock.test.ts
import { createMockAdtServer } from '@abapify/adt-fixtures';

const mock = createMockAdtServer();
const { port } = await mock.start();
const baseUrl = `http://localhost:${port}`;

// ... run tests against baseUrl ...

await mock.stop();

The server:

  • Binds to a random free port (0) — safe to parallelise.
  • Generates a fresh CSRF token per instance.
  • Tracks locks in-memory via LockRegistry.
  • Returns real (sanitised) SAP XML / JSON from the fixtures catalog.

Pointing adt-cli at it

cat > ~/.adt/auth.json <<JSON
{
"default": "MOCK",
"sessions": {
"MOCK": {
"sid": "MOCK",
"host": "http://localhost:$PORT",
"client": "100",
"auth": {
"method": "basic",
"plugin": "@abapify/adt-auth",
"credentials": { "username": "TESTUSER", "password": "MOCK" }
}
}
}
}
JSON

adt info
adt cts tr list
adt get class/CL_SALV_TABLE

Using fixtures directly (no server)

import { fixtures, load } from '@abapify/adt-fixtures';

// Handle — lazy, nothing loaded yet
const handle = fixtures.transport.single;
console.log(handle.path); // 'transport/single.xml'

// Load when actually needed
const xml = await handle.load();

// Or by path
const xml2 = await load('transport/single.xml');

The fixtures library is lazy — nothing reads from disk until .load() runs, so importing it has no startup cost.

Adding a new fixture

  1. Capture the real SAP response:

    adt fetch /sap/bc/adt/cts/transportrequests/DEVK900001 -o tmp/transport.xml
  2. Sanitize — replace every real value with a MOCK / TESTUSER / MOCK900001 placeholder. The raw fixture goes to git, so nothing with a real system name, username, or transport number is allowed.

  3. Drop it into the fixtures tree inside packages/adt-fixtures/src/fixtures/:

    packages/adt-fixtures/src/fixtures/
    transport/
    mynew.xml ← here
  4. Register it in src/fixtures.ts:

    const registry = {
    transport: {
    single: 'transport/single.xml',
    mynew: 'transport/mynew.xml', // ← added
    },
    } as const;
  5. Build the package:

    bunx nx build adt-fixtures

See adt-fixtures package guide for the full convention.

Adding a new mock route

The mock server dispatches through matchRoute(method, url) in packages/adt-fixtures/src/mock-server/routes.ts. To add an endpoint:

  1. Capture and register the fixture (previous section).
  2. Add a branch to matchRoute matching your method + URL pattern.
  3. Return a RouteResult with the loaded fixture body and appropriate status / content-type.
  4. Add a test to packages/adt-fixtures/tests/ that exercises the new route end-to-end.

Troubleshooting

ErrorCauseFix
404 No route for GET /sap/bc/adt/...Endpoint not registeredAdd a matchRoute branch (see above)
Security session fullTest didn't call mock.stop() / cookie carried overOne createMockAdtServer() per test; await mock.stop() in afterEach
CSRF token mismatchCLI reused an old token against a new server instanceRestart the CLI / use a fresh auth file per mock run
ECONNREFUSEDServer stopped before test finishedIncrease test timeout; check order of awaits
Real JFrog URLs leaking in CIbun.lock not in .git/info/excludeKeep it excluded (see root AGENTS.md)

See also