Skip to content

Commit db1cecf

Browse files
committed
merge test
1 parent 4c92664 commit db1cecf

2 files changed

Lines changed: 47 additions & 144 deletions

File tree

‎tests/api/test_failover.py‎

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@
2828
import json
2929
import os
3030
import urllib.request
31+
from typing import List, Optional
3132

3233
import aiohttp
3334
import pytest
3435

3536
from livekit.api import CreateRoomRequest, Room, ServerError
36-
from livekit.api.twirp_client import TwirpClient
37+
from livekit.api.twirp_client import REQUEST_ID_HEADER, TwirpClient
3738

3839
BASE = os.getenv("LK_TEST_SERVER_URL", "http://127.0.0.1:9999")
3940

@@ -53,8 +54,15 @@ def _server_up() -> bool:
5354

5455
# _failover_force bypasses the cloud-host check (the mock is on 127.0.0.1) and a
5556
# tiny backoff keeps the tests fast — both are internal, test-only knobs.
56-
async def _call(mock: dict, *, failover: bool = True, force: bool = True) -> Room:
57-
async with aiohttp.ClientSession() as session:
57+
async def _call(
58+
mock: dict,
59+
*,
60+
failover: bool = True,
61+
force: bool = True,
62+
extra_headers: Optional[dict] = None,
63+
trace_configs: Optional[List[aiohttp.TraceConfig]] = None,
64+
) -> Room:
65+
async with aiohttp.ClientSession(trace_configs=trace_configs) as session:
5866
client = TwirpClient(
5967
session,
6068
BASE,
@@ -67,6 +75,7 @@ async def _call(mock: dict, *, failover: bool = True, force: bool = True) -> Roo
6775
"authorization": "Bearer test-token",
6876
# These tests exercise failover, not authz; skip the mock's permission check.
6977
"X-Lk-Mock": json.dumps({"skipAuth": True, **mock}),
78+
**(extra_headers or {}),
7079
}
7180
return await client.request("RoomService", "CreateRoom", CreateRoomRequest(), headers, Room)
7281

@@ -113,3 +122,38 @@ def test_disabled():
113122
# failover=False disables failover entirely.
114123
with pytest.raises(ServerError):
115124
asyncio.run(_call({"failRegions": [0]}, failover=False))
125+
126+
127+
# Records the request id header(s) the SDK put on the wire for each Twirp
128+
# attempt. Region discovery is a separate request, so it is not recorded.
129+
def _request_id_recorder(seen: List[List[str]]) -> aiohttp.TraceConfig:
130+
trace = aiohttp.TraceConfig()
131+
132+
async def on_request_start(_session, _ctx, params) -> None:
133+
if not params.url.path.endswith("/settings/regions"):
134+
seen.append(list(params.headers.getall(REQUEST_ID_HEADER, [])))
135+
136+
trace.on_request_start.append(on_request_start)
137+
return trace
138+
139+
140+
def test_request_id_stable_across_attempts():
141+
# The id is generated once per logical call, so a replayed request carries
142+
# the same idempotency key on every attempt and the server can dedup it.
143+
seen: List[List[str]] = []
144+
asyncio.run(_call({"failRegions": [0, 1]}, trace_configs=[_request_id_recorder(seen)]))
145+
assert len(seen) == 3 # primary + two fallbacks
146+
assert all(len(ids) == 1 for ids in seen) # never duplicated
147+
assert seen[0][0]
148+
assert len({ids[0] for ids in seen}) == 1
149+
150+
151+
def test_request_id_unique_per_call():
152+
# A new logical call is a new request, so it gets its own id.
153+
seen: List[List[str]] = []
154+
recorder = _request_id_recorder(seen)
155+
asyncio.run(_call({}, trace_configs=[recorder]))
156+
asyncio.run(_call({}, trace_configs=[recorder]))
157+
assert len(seen) == 2
158+
assert seen[0][0] and seen[1][0]
159+
assert seen[0][0] != seen[1][0]

‎tests/api/test_request_id.py‎

Lines changed: 0 additions & 141 deletions
This file was deleted.

0 commit comments

Comments
 (0)