Back to Livekit

LiveKit SDK test server

cmd/test-server/README.md

1.13.36.3 KB
Original Source

LiveKit SDK test server

A stateless, per-request programmable mock of the LiveKit server HTTP API. It exists so the server SDKs (Go, Rust, Python, Node, Kotlin, Ruby) can exercise client-side behavior against one shared implementation, published as a Docker image and booted by each SDK's CI.

Why it looks the way it does

  • Stateless. All behavior is selected by per-request X-Lk-Mock-* headers, so the server holds no mutable state and tests run in parallel.
  • Multi-port = multi-region. The process binds one listener per simulated region (--ports). A port's position in the list is its region index; index 0 is the primary the SDK is initially pointed at. GET /settings/regions advertises all of them in order.
  • One header drives every attempt. The SDK sends the same control header on the initial request and every failover retry. Each listener decides what to do from its own index, so a single X-Lk-Mock-Fail-Regions: 0 makes the primary fail while the first fallback succeeds — no coordination needed.
  • The whole API is mocked with populated responses. Every RoomService, Egress, Ingress, SIP, and Connector method returns a type-correct, populated response: scalar fields that share a name with the request are echoed (e.g. name, metadata, identity, timeouts), id/sid fields get placeholder values, and list endpoints return one element. Both protobuf and JSON Twirp clients are supported. A client can override the response entirely with the X-Lk-Mock-Response header (see below). Unregistered/future methods fall back to an empty (all-default) message, which still decodes cleanly.

Running

bash
go run ./cmd/test-server                       # primary :9999, regions :10000-10002
go run ./cmd/test-server --ports 9999,10000    # primary + one fallback

# Docker
docker build -f cmd/test-server/Dockerfile -t livekit/test-server .
docker run -p 9999-10002:9999-10002 livekit/test-server
FlagEnvDefaultMeaning
--portsLK_TEST_SERVER_PORTS9999,10000,10001,10002listener ports; index = position
--advertise-hostLK_TEST_SERVER_ADVERTISE_HOSThttp://127.0.0.1base URL used in /settings/regions
--bindLK_TEST_SERVER_BIND0.0.0.0bind address
--twirp-prefixLK_TEST_SERVER_TWIRP_PREFIX/twirpTwirp path prefix

Control protocol

Request headers (sent by the SDK on API calls; the SDK must forward client-configured custom headers onto the /settings/regions fetch and every failover retry):

HeaderDefaultEffect
X-Lk-Mock-Fail-Regionscomma list of region indices that fail this request, e.g. 0 or 0,1. Each listener fails only if its own index is listed.
X-Lk-Mock-Fail-Modestatushow a failing region fails: status, drop (close connection → transport error), delay.
X-Lk-Mock-Fail-Status503HTTP status when failing with status/delay.
X-Lk-Mock-Fail-Twirp-Codederived from statusTwirp error code string in the failure body.
X-Lk-Mock-Delay-Ms30000delay before a delay-mode region responds (for timeout tests).
X-Lk-Mock-Regions-Status200override the status of GET /settings/regions.
X-Lk-Mock-Responseprotojson of the response message for the called method; replaces the populated default, giving full control over the returned payload.
X-Lk-Mock-Skip-Authtrue disables permission enforcement for the request (use for tests that aren't about authz, e.g. failover tests with a placeholder token).

Response headers:

HeaderMeaning
X-Lk-Mock-Regionindex of the region that served the response (blank on a failed region). Assert on this to confirm which region a failover landed on.

Permission enforcement

Every API method requires the same token grants the real LiveKit server checks (see pkg/service/auth.go), so the mock doubles as a conformance check that an SDK attaches the right permissions automatically. Tokens are parsed and verified with the protocol's own auth helpers — the same code path the real server uses — against the mock's configured API secret (default secret, matching livekit-server --dev; override with --api-secret / LK_TEST_SERVER_API_SECRET).

  • Missing, malformed, or wrongly-signed Authorization401 unauthenticated.
  • Validly-signed token without the required grant → 403 permission_denied.
  • roomAdmin-scoped methods also require the token's room to match the request's room; ForwardParticipant/MoveParticipant additionally require destinationRoom to match.

SDKs exercising permissions should sign tokens with the same API secret the mock is configured with (secret by default).

GrantMethods
video.roomCreateCreateRoom, DeleteRoom, all Connector calls
video.roomListListRooms
video.roomRecordall Egress methods
video.ingressAdminall Ingress methods
video.roomAdmin (+ room)room participant/data/metadata methods, AgentDispatchService methods
video.roomAdmin (+ room + destinationRoom)ForwardParticipant, MoveParticipant
sip.adminSIP trunk & dispatch-rule CRUD
sip.callCreateSIPParticipant; TransferSIPParticipant (also needs roomAdmin)

Send X-Lk-Mock-Skip-Auth: true to bypass enforcement for tests that aren't about permissions.

Common recipes

GoalHeaders
Happy pathvalid token with the method's grant → 200 from region 0
Bypass auth (failover tests)X-Lk-Mock-Skip-Auth: true
Missing-permission errortoken without the required grant → 403
Failover succeeds on region 1X-Lk-Mock-Fail-Regions: 0
Exhaust to region 2X-Lk-Mock-Fail-Regions: 0,1
All regions downX-Lk-Mock-Fail-Regions: 0,1,2,3
4xx, no retryX-Lk-Mock-Fail-Regions: 0 + X-Lk-Mock-Fail-Status: 400
Transport-error failoverX-Lk-Mock-Fail-Regions: 0 + X-Lk-Mock-Fail-Mode: drop
Region discovery unreachableX-Lk-Mock-Regions-Status: 500
Custom response payloadX-Lk-Mock-Response: {"sid":"RM_x","name":"my-room"}

Note: SDK region failover normally only engages for *.livekit.cloud hosts. Since tests point at 127.0.0.1, set the SDK's failover-enable option to its forced-on value so failover engages against localhost.