MTK Testing SDK β€” Interactive docs

Hover diagram boxes and action pills to see explanations and matching code highlights. Animations show how traffic moves through the stack.

Tip: Hover or Tab + Enter on flow boxes for full tooltips Β· Pills below the proxy diagram sync with code lines Β· Reduced motion: respects system accessibility setting

πŸ—οΈ Architecture Overview

β–Ό

Hover the Client, MikroTik, mtk-serve, Upstream, and Dashboard shapes β€” your browser shows a tooltip from each block’s description.

Test device (phone, laptop, TV). Traffic leaves here toward your router. Use the dashboard Network tab to simulate slow links per client IP. Client Mobile/Browser RouterOS applies queues, NAT, and optional DNS static entries. Network simulation (throttle, loss) runs here when using SSH; mtk-serve can mirror the same over HTTP. MikroTik RouterOS Queue + NAT Router mtk-serve: HTTP proxy + REST API. Rules mock responses, errors, delays; logs every request. Start with mtk-serve or point MTK_SERVICE_URL here. mtk-serve HTTP Proxy Rule Engine Log Store REST API Proxy Server Real backend or public API. If a rule says passthrough or no rule matches, traffic is forwarded here (when proxy is in forward mode). ☁️ Real API (or mocked) Upstream This browser UI: reads health, drives the same SDK as mtk CLI, streams live ticks over WebSocket. This Dashboard WebSocket + REST Traffic flow: | Network simulation applied at router level

The MTK SDK orchestrates testing across three layers: client traffic flows through MikroTik (network shaping), then mtk-serve (HTTP interception), reaching upstream APIs. This dashboard controls all layers via WebSocket and REST.

Testing stack

Network shaping, DNS, HTTP proxy rules, scenarios, HAR recording, and pytest fixtures. The accordions below map to MtkTestKit and the dashboard tabs.

πŸ“Ά Network Simulation

β–Ό

Simulate real-world network conditions: bandwidth throttling, latency injection, packet loss, and connection interrupts.

Normal: Client 100 Mbps Server Throttled: Client 2 Mbps + 200ms Server Packet loss: Client βœ— 5% loss Server Profiles: wifi-excellent: 100Mbps, 5ms 4g: 20Mbps, 50ms 3g: 2Mbps, 200ms, 1% loss 2g: 256Kbps, 500ms, 5% loss offline: 0Kbps, 100% loss
Python
from mtk_router_sdk.testing import MtkTestKit
kit = MtkTestKit.from_env()
# Apply network profile One-liner: sets throttle + latency + loss from preset (3g, 4g, wifi-poor, etc.)
kit.network.apply_profile("192.168.1.100", "3g")
# Custom throttling Set download/upload limits in Kbps β€” affects only this client IP
kit.network.throttle("192.168.1.100",
download_kbps=2000,
upload_kbps=500)
# Add latency with jitter delay_ms adds fixed RTT; jitter_ms is random Β± variance
kit.network.latency("192.168.1.100",
delay_ms=200,
jitter_ms=50)
# Simulate packet loss Randomly drops X% of packets β€” tests retry logic
kit.network.packet_loss("192.168.1.100", percent=5)
# Temporary disconnect (offline for 10s) 100% drop for N seconds, then auto-restores
kit.network.disconnect("192.168.1.100", duration_seconds=10)
# Clear conditions Remove all shaping for this client
kit.network.clear("192.168.1.100")

🌐 DNS Override

β–Ό

Redirect domain resolution to capture traffic or test with different backends.

App requests api.example.com MikroTik DNS Static entry: β†’ 192.168.1.50 (proxy IP) mtk-serve Intercepts traffic Applies rules Logs requests ☁️ Real API DNS redirect points domain to proxy β†’ traffic intercepted β†’ forwarded to real API
Python
# Redirect domain to proxy (captures traffic) DNS resolves to mtk-serve IP so traffic is intercepted
kit.dns.redirect("api.example.com", client_ip="192.168.1.100")
# Redirect to custom IP (bypass proxy) Point domain to any IP β€” test with staging servers
kit.dns.redirect_to_ip("api.example.com",
target_ip="10.0.0.50",
client_ip="192.168.1.100")
# Wildcard redirect (all subdomains) *.example.com matches cdn.example.com, api.example.com, etc.
kit.dns.redirect("*.example.com")
# List active redirects
redirects = kit.dns.list_redirects()
# Clear all DNS rules
kit.dns.clear_all()
kit.dns.flush_cache() # Force clients to re-resolve

πŸ”§ Proxy Rules

β–Ό

Mock API responses, inject errors, add delays. Hover each step for what it means in practice; hover the pills to light up the matching Python below.

Step 1 β€” Real traffic A device on your LAN hits an API. The proxy sees method, path, client IP, and optional body. Nothing is mocked yet β€” this is the β€œquestion” your app asks the network.

Incoming

Request
GET /api/users
client 192.168.1.100
headers / body …
Step 2 β€” Rule engine Rules are matched by client IP (optional), path pattern (globs like /api/*), and method. First match wins. override returns your JSON; error skips upstream; delay waits then continues; modify patches JSON or headers.

mtk-serve

Rule engine
βœ“ match /api/users/*
action: override
return mock JSON
Step 3 β€” Response Your app receives the mocked status and body as if the real server answered. Use this to test UI states (empty list, errors) without touching production backends.

Back to client

Mock response
200 OK
{ "users": [ … ] }
Python
# override β€” fake success body (diagram: Rule engine β†’ Mock response) Maps to the green β€œmock response” box: your dict becomes the HTTP body.
kit.proxy.set_response(
  client_ip="192.168.1.100",
  path="/api/users",
  body={"users": [{"id": 1, "name": "Test"}]},
  status=200
)
# error β€” force failure paths in your UI Same path matching; app sees 500 and your message string.
kit.proxy.set_error(
  client_ip="192.168.1.100", path="/api/payment",
  status=500, message="Service unavailable"
)
# delay β€” e.g. 5000 ms before handling (timeout tests) Client waits; use to test spinners and retry policies.
kit.proxy.set_delay(
  client_ip="192.168.1.100", path="/api/*", delay_ms=5000
)
# modify β€” json_set / header tweaks (see SDK set_modifier) For partial edits to real responses; advanced flows.
# logs β€” debug what matched Each line is one proxied request: method, path, status, timing.
logs = kit.proxy.get_log(client_ip="192.168.1.100", limit=50)
for entry in logs:
  print(f"{entry.method} {entry.path} β†’ {entry.response_status}")

⚑ Test Scenarios

β–Ό

Pre-built test flows combining network, DNS, and proxy actions. Run complex test scenarios with a single command.

geo-block-test

Simulates geo-blocking by returning 403 Forbidden on specific endpoints

subscription-states

Tests premium, trial, and expired user states via API mocking

network-degradation

Progressive network quality decrease: 4G β†’ 3G β†’ 2G β†’ Edge

error-resilience

Random 5xx errors on API calls to test retry logic

offline-recovery

Simulates 10-second offline period then recovery

Python
# Run built-in scenario Executes all steps defined in the scenario (network + proxy + wait)
kit.scenarios.run("offline-recovery", client_ip="192.168.1.100")
# List available scenarios
for name in kit.scenarios.list_scenarios():
print(name)
# Create custom scenario (YAML file) Save to MTK_SCENARIOS_DIR; structure shown below
# scenarios/my-test.yaml:
# name: my-test
# steps:
# - action: network_profile
# profile: 3g
# - action: wait
# seconds: 5
# - action: proxy_error
# path: /api/data
# status: 503

πŸ”΄ Recording & Replay

β–Ό

Capture HTTP sessions as HAR files for replay testing, golden file comparison, and regression testing.

● Recording Mode Live traffic Capture πŸ“„ session.har (HAR format) β–Ά Replay Mode πŸ“„ HAR file Create rules Mock server
Python
# Start recording All HTTP traffic from this client is logged to HAR
session = kit.recorder.start(
client_ip="192.168.1.100",
name="checkout-flow"
)
# ... user performs actions in app ...
# Stop and save to HAR HAR file contains all requests/responses in standard format
har_file = kit.recorder.stop(session)
print(f"Saved: {har_file}")
# Replay session (mock all recorded responses) Creates proxy rules from HAR β€” no real server needed
kit.recorder.replay(
client_ip="192.168.1.100",
har_file="recordings/checkout-flow.har"
)
# Compare with golden file Detects API changes between recorded sessions
diff = kit.recorder.compare(
client_ip="192.168.1.100",
golden="golden/checkout.har"
)
if diff.has_changes:
print(diff.summary())

πŸ§ͺ pytest Integration

β–Ό

Built-in fixtures and markers for seamless pytest integration.

pytest
# conftest.py - fixtures auto-loaded pip install mtk-router-sdk[dev] β€” fixtures registered automatically
# Available fixtures: mtk, tracked_mtk, mtk_network, mtk_dns,
# mtk_proxy, client_ip, slow_network, offline, premium_user
import pytest
# Basic test with MTK mtk fixture gives you MtkTestKit instance
def test_api_response(mtk, client_ip):
mtk.proxy.set_response(
client_ip, "/api/data",
{"status": "ok"}
)
# Test your app...
# Use markers for conditions Markers auto-apply network profiles before test runs
@pytest.mark.slow_network
def test_loading_indicator(mtk, client_ip):
"""Runs with 3G network applied."""
assert app.shows_loading_spinner()
@pytest.mark.offline
def test_offline_banner(mtk, client_ip):
"""Runs with 100% packet loss."""
assert app.shows_offline_warning()
@pytest.mark.premium
def test_premium_features(mtk, client_ip):
"""Premium user profile applied."""
assert app.has_premium_features()
# Tracked MTK with automatic cleanup tracked_mtk records changes and reverts them after test
def test_with_cleanup(tracked_mtk, client_ip):
tracked_mtk.network.throttle(client_ip, 1000)
tracked_mtk.dns.redirect("api.com")
# All changes auto-reverted after test!

pip install Install with: pip install mtk-router-sdk[dev]
markers Available markers: @pytest.mark.slow_network, @pytest.mark.offline, @pytest.mark.premium, @pytest.mark.mtk

VPN & per-slot routing

L2TP tunnels on the router, policy routing per Wi‑Fi slot, and CLI helpers for debugging stuck sessions. Requires SSH to the router and a valid site.json when using slot routing.

πŸ” VPN, L2TP & slot-vpn

β–Ό

List L2TP clients, inspect a tunnel, reset it, or point a slot’s traffic at a tunnel interface (routing mark from site JSON).

CLI
# List L2TP clients (passwords redacted)
mtk l2tp --site my-site.json
# Deep debug: print, monitor, ping, logs (default tunnel vpn_usa)
mtk vpn-debug l2tp-uk --site my-site.json
mtk vpn-debug --json l2tp-uk # machine-readable
# Bounce tunnel (disable β†’ enable) if session is stuck
mtk vpn-reset l2tp-uk --site my-site.json --pause 2
# Route slot 2 via tunnel l2tp-uk (uses site slots + routing-mark)
mtk slot-vpn --site my-site.json 2 l2tp-uk
mtk slot-vpn --site my-site.json --ssid MyWiFi l2tp-uk

REST mtk-serve exposes GET /v1/vpn/clients, POST /v1/slots/{id}/vpn, POST /v1/by-client/vpn, POST /v1/by-ssid/vpn β€” see the API table below.

Router control

SSH-backed commands and the shared RouterClient used by discovery, bootstrap, and exec. Prefer mtk doctor before deeper scripting.

πŸ› οΈ Doctor, exec, interfaces & IP

β–Ό

doctor connects (env or --site) and runs capability discovery. exec runs a single RouterOS command and prints JSON. interfaces / ip dump terse tables as JSON.

CLI
mtk doctor --site my-site.json
mtk exec --site my-site.json '/ip dns print'
mtk interfaces --site my-site.json
mtk ip --site my-site.json
Python
from mtk_router_sdk import RouterClient, connection_config_from_env
client = RouterClient.from_env()
client.connect(connection_config_from_env())
try:
rows = client.exec_print_terse("/interface print")
finally:
client.disconnect()

REST GET /v1/doctor, GET /v1/discovery/profile, POST /v1/discovery/refresh, GET /v1/dashboard/snapshot.

πŸ“‹ Bootstrap & dry-run

β–Ό

Generate a plan from site.json plus discovery; default is dry-run. Add --apply to push scripts to the router.

CLI
mtk bootstrap my-site.json # plan only
mtk bootstrap my-site.json --apply # execute on router

REST POST /v1/bootstrap/dry-run, POST /v1/bootstrap.

Client management

Resolve a device IP to a slot, block internet per client (runtime firewall), and (via REST) renew or release DHCP leases by IP or SSID.

πŸ‘€ client-resolve & client-block

β–Ό
CLI
# Map IP β†’ slot using site subnets (+ optional DHCP via SSH)
mtk client-resolve --site my-site.json --ip 192.168.2.55
mtk client-resolve --ip 192.168.2.55 --no-ssh # JSON only
# Per-client internet block (runtime)
mtk client-block block 192.168.2.55 --site my-site.json
mtk client-block status 192.168.2.55 --site my-site.json
mtk client-block list --site my-site.json
mtk client-block unblock 192.168.2.55 --site my-site.json

REST GET /v1/client/resolve, POST /v1/client/block, POST /v1/client/unblock, GET /v1/client/blocked, GET /v1/client/blocked/{ip}, GET /v1/dhcp/leases.

Packet sniffer

RouterOS /tool sniffer on a slot bridge, filtered to one IPv4; .pcap saved on the router for download or analysis.

πŸ“‘ mtk sniffer

β–Ό
CLI
mtk sniffer start --slot 1 --ip 192.168.2.55 --site my-site.json
mtk sniffer start --ssid LabWiFi --ip 192.168.2.55 --only-headers
mtk sniffer status --site my-site.json
mtk sniffer stop --site my-site.json

REST POST /v1/slots/{id}/sniffer/start, POST /v1/slots/{id}/sniffer/stop, and by-client / by-ssid variants under /v1/by-client/sniffer/*, /v1/by-ssid/sniffer/*.

Advanced HTTP proxy

Beyond mock responses: modify JSON and headers, clear rules, search logs, and manage MITM certificates. Same surface as ProxyClient / kit.proxy when mtk-serve is up.

βš™οΈ Modifiers, clear, log search & certs

β–Ό
Python
# Patch upstream JSON/headers without replacing the whole body
kit.proxy.set_modifier(
client_ip, "/api/user",
json_set={"tier": "premium"},
headers_set={"X-Debug": "1"},
)
# Remove rules for one path or entire device
kit.proxy.clear_endpoint(client_ip, "/api/user")
kit.proxy.clear_client(client_ip)
# Filter captured proxy log client-side
entries = kit.proxy.search_log(
client_ip=client_ip, path_contains="checkout", status_min=400
)
CLI
mtk proxy list
mtk proxy log
mtk proxy log-clear
mtk proxy certs
mtk proxy certs-generate --force

REST POST /v1/proxy/rules (action modify), POST /v1/proxy/clear, GET /v1/proxy/log, DELETE /v1/proxy/log, GET|POST /v1/proxy/certs, GET /v1/proxy/certs/ca.

Site JSON & environment

Physical lab layout, SSH targets, slot SSIDs, and optional mtk-serve URL for the testing kit. mtk init-site scaffolds a file; env vars override file fields where documented.

πŸ“ init-site, MTK_* env & serving

β–Ό
CLI
mtk init-site # interactive wizard

Common environment variables (see package docs for the full list):

  • MTK_SITE_CONFIG β€” path to site.json (mtk-serve / tooling)
  • MTK_HOST, MTK_USER, MTK_PASSWORD, MTK_PORT β€” SSH to the router
  • MTK_SERVICE_URL, MTK_HTTP_PORT, MTK_SERVICE_TOKEN β€” mtk-serve base URL and optional auth
  • MTK_TEST_CLIENT_IP, MTK_PROFILES_DIR, MTK_SCENARIOS_DIR, MTK_RECORDINGS_DIR β€” testing kit defaults

REST GET|PUT /v1/config/slots, GET|PATCH /v1/config.

HAR compare & change tracking

Recorder sessions can be diffed against a golden HAR. In tests, TrackedMtkTestKit wraps the kit and reverts network/DNS/proxy mutations after each case.

πŸ“Š recorder.compare & TrackedMtkTestKit

β–Ό
Python
# After recording, detect API drift vs golden HAR
diff = kit.recorder.compare(
client_ip=client_ip,
golden="golden/checkout.har",
)
assert not diff.has_changes, diff.summary()
# Explicit tracking (pytest fixture tracked_mtk does this)
from mtk_router_sdk.testing import TrackedMtkTestKit, ChangeTracker
tracked = TrackedMtkTestKit.from_env()
tracked.network.throttle(client_ip, 500)
tracked.teardown() # pytest fixture tracked_mtk does this automatically

ChangeTracker records operations from wrapped network, dns, and proxy objects. tracked.teardown() clears them via the underlying MtkTestKit; use tracker.session(kit) for a context-managed scope.

mtk-serve REST (v1)

Curated routes the dashboard and SDK call. Base URL is your mtk-serve origin (e.g. http://router:8780). WebSocket /v1/stream pushes live events.

πŸ“‘ Endpoint reference

β–Ό

OpenAPI merges these under /v1/*. Methods and paths below match v1_routes.py.

MethodPathSummary
GET/v1/versionService version
GET/v1/doctorHealth / discovery snapshot
POST/v1/sessionSession cookie helpers
DELETE/v1/sessionClear session
GET/v1/discovery/profileCapability profile
POST/v1/discovery/refreshRe-run discovery
GET/v1/dashboard/snapshotAggregate UI snapshot
GET/v1/wirelessWireless table
GET/v1/vpn/clientsL2TP clients
GET/v1/dhcp/leasesDHCP leases
GET/v1/routing/policyPolicy routing
GET/PATCH/v1/configSite config
GET/PUT/v1/config/slotsSlot definitions
POST/v1/bootstrap/dry-runBootstrap plan
POST/v1/bootstrapApply bootstrap
POST/v1/slots/{id}/control/leaseLease control
POST/v1/slots/{id}/control/lease/releaseRelease lease
GET/v1/slots/{id}/control/statusControl status
GET/v1/slots/{id}/vpn/statusSlot VPN status
POST/v1/slots/{id}/vpnSet slot VPN gateway
GET/v1/slots/{id}Slot detail
POST/v1/slots/{id}/sniffer/startStart sniffer
POST/v1/slots/{id}/sniffer/stopStop sniffer
GET/v1/client/resolveResolve IP β†’ slot
POST/v1/by-client/control/leaseLease by client IP
POST/v1/by-client/control/lease/releaseRelease by client IP
POST/v1/by-client/vpnVPN by client IP
POST/v1/by-client/sniffer/startSniffer by client IP
POST/v1/by-client/sniffer/stopStop sniffer by client
POST/v1/by-ssid/control/leaseLease by SSID
POST/v1/by-ssid/control/lease/releaseRelease by SSID
POST/v1/by-ssid/vpnVPN by SSID
POST/v1/by-ssid/sniffer/startSniffer by SSID
POST/v1/by-ssid/sniffer/stopStop sniffer by SSID
POST/v1/ai/intentAI intent (experimental)
POST/v1/client/blockBlock client internet
POST/v1/client/unblockUnblock client
GET/v1/client/blockedList blocked IPs
GET/v1/client/blocked/{ip}Blocked status for IP
WS/v1/streamLive event stream
GET/v1/proxy/statusProxy status
GET/POST/v1/proxy/rulesList / create rules
PUT/DELETE/v1/proxy/rules/{rule_id}Update / delete rule
DELETE/v1/proxy/rulesClear all rules
POST/v1/proxy/setQuick mock response
POST/v1/proxy/errorQuick error response
POST/v1/proxy/clearClear by client/path
GET/v1/proxy/logRequest log
DELETE/v1/proxy/logClear log
GET|POST/v1/proxy/certsCert status / generate
GET/v1/proxy/certs/caDownload CA