mirror of
https://github.com/livekit/livekit.git
synced 2026-07-09 23:12:19 +00:00
added mocking function for SIP dialing methods (#4642)
This commit is contained in:
@@ -66,6 +66,7 @@ field — for normal behavior. Every field is optional:
|
||||
| `regionsStatus` | `200` | override the status of `GET /settings/regions`. |
|
||||
| `response` | — | the response message for the called method (a JSON object, protojson-shaped); replaces the populated default, giving full control over the returned payload. |
|
||||
| `skipAuth` | `false` | `true` disables permission enforcement for the request (use for tests that aren't about authz, e.g. failover tests with a placeholder token). |
|
||||
| `sipStatus` | — | fail a SIP dial method (`CreateSIPParticipant`/`TransferSIPParticipant`) with a SIP status, e.g. `{"code":486,"status":"Busy Here"}` (`status` optional). The Twirp error code and `sip_status_code`/`sip_status`/`error_details` metadata are derived from it exactly as the real server does. Composes with `delayMs` to simulate "ring, then fail". |
|
||||
|
||||
Example: `X-Lk-Mock: {"skipAuth":true,"failRegions":[0],"failStatus":400}`
|
||||
|
||||
@@ -129,6 +130,8 @@ about permissions.
|
||||
| Timeout test | `{"delayMs":30000}` |
|
||||
| Region discovery unreachable | `{"regionsStatus":500}` |
|
||||
| Custom response payload | `{"response":{"sid":"RM_x","name":"my-room"}}` |
|
||||
| SIP busy signal | `{"sipStatus":{"code":486,"status":"Busy Here"}}` |
|
||||
| SIP carrier decline | `{"sipStatus":{"code":603}}` |
|
||||
|
||||
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
|
||||
|
||||
@@ -80,12 +80,26 @@ type mockConfig struct {
|
||||
// SkipAuth disables permission enforcement for this request (for tests that
|
||||
// aren't about authz, e.g. failover tests with a placeholder token).
|
||||
SkipAuth bool `json:"skipAuth,omitempty"`
|
||||
// SIPStatus, when set on a SIP dial method (CreateSIPParticipant /
|
||||
// TransferSIPParticipant), fails the call with this SIP status. The Twirp
|
||||
// error code and metadata (sip_status_code, sip_status, error_details) are
|
||||
// derived from it exactly as the real server does, so the SDK sees an
|
||||
// identical error. Composes with DelayMs to simulate "ring, then fail".
|
||||
SIPStatus *sipStatusConfig `json:"sipStatus,omitempty"`
|
||||
|
||||
// legacyDelayMs is the sleep used by the deprecated "delay" fail mode. It is
|
||||
// populated only from the legacy X-Lk-Mock-Delay-Ms header, never from JSON.
|
||||
legacyDelayMs int
|
||||
}
|
||||
|
||||
// sipStatusConfig is a SIP response to inject; see mockConfig.SIPStatus.
|
||||
type sipStatusConfig struct {
|
||||
// Code is the SIP response code, e.g. 486 (Busy Here) or 603 (Decline).
|
||||
Code int `json:"code"`
|
||||
// Status is the SIP reason phrase; defaults to the code's canonical name.
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// parseMockConfig builds the request's config. Deprecated individual X-Lk-Mock-*
|
||||
// headers form the base; the unified X-Lk-Mock JSON header (if present) is
|
||||
// overlaid on top, so its fields win per-field while absent fields keep the
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/utils/protojson"
|
||||
"github.com/livekit/protocol/utils/xtwirp"
|
||||
)
|
||||
|
||||
// apiSpec captures the request and response message types for one Twirp method,
|
||||
@@ -155,6 +156,13 @@ func (h *mockHandler) serveAPI(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(delay)
|
||||
}
|
||||
|
||||
// A SIP dial that fails carries a SIP status; the Twirp code and metadata are
|
||||
// derived from it exactly as the real server does.
|
||||
if cfg.SIPStatus != nil && isSIPDialMethod(key) {
|
||||
h.failSIP(w, &cfg)
|
||||
return
|
||||
}
|
||||
|
||||
if h.shouldFail(&cfg) {
|
||||
h.fail(w, &cfg)
|
||||
return
|
||||
@@ -183,6 +191,26 @@ func methodLatency(key string, req proto.Message) time.Duration {
|
||||
// mock — long enough to exercise client-side timeouts around these calls.
|
||||
const sipAnswerLatency = 11 * time.Second
|
||||
|
||||
// isSIPDialMethod reports whether key places a call that can fail with a SIP status.
|
||||
func isSIPDialMethod(key string) bool {
|
||||
switch key {
|
||||
case "livekit.SIP/CreateSIPParticipant", "livekit.SIP/TransferSIPParticipant":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// failSIP fails the request with the configured SIP status, mirroring the real
|
||||
// server: the status maps to a Twirp error code and attaches sip_status_code,
|
||||
// sip_status, and error_details metadata via xtwirp.
|
||||
func (h *mockHandler) failSIP(w http.ResponseWriter, cfg *mockConfig) {
|
||||
st := &livekit.SIPStatus{
|
||||
Code: livekit.SIPStatusCode(cfg.SIPStatus.Code),
|
||||
Status: cfg.SIPStatus.Status,
|
||||
}
|
||||
writeTwirpErr(w, xtwirp.ToError(st))
|
||||
}
|
||||
|
||||
// writeAPIResponse serves a populated, type-correct response for a known API
|
||||
// method. The response is the reflection-populated default unless the mock
|
||||
// config carries a `response` (protojson), which overrides it entirely. Content
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -28,6 +29,8 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/twitchtv/twirp"
|
||||
|
||||
"github.com/livekit/protocol/livekit"
|
||||
"github.com/livekit/protocol/utils/protojson"
|
||||
)
|
||||
@@ -163,6 +166,23 @@ func writeTwirpErrorCode(w http.ResponseWriter, status int, code, msg string) {
|
||||
_, _ = fmt.Fprintf(w, `{"code":%q,"msg":%q}`, code, msg)
|
||||
}
|
||||
|
||||
// writeTwirpErr writes a full Twirp JSON error — code, message, and metadata —
|
||||
// using the HTTP status Twirp derives from the error code.
|
||||
func writeTwirpErr(w http.ResponseWriter, terr twirp.Error) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set(headerRegion, "")
|
||||
w.WriteHeader(twirp.ServerHTTPStatusFromErrorCode(terr.Code()))
|
||||
_ = json.NewEncoder(w).Encode(struct {
|
||||
Code string `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Meta map[string]string `json:"meta,omitempty"`
|
||||
}{
|
||||
Code: string(terr.Code()),
|
||||
Msg: terr.Msg(),
|
||||
Meta: terr.MetaMap(),
|
||||
})
|
||||
}
|
||||
|
||||
func twirpCodeForStatus(status int) string {
|
||||
switch {
|
||||
case status == http.StatusBadRequest:
|
||||
|
||||
Reference in New Issue
Block a user