From 3a2bb7c168bd8331ef24f48429757dcbc6921168 Mon Sep 17 00:00:00 2001 From: Alexliu Date: Tue, 23 Jun 2026 15:17:34 +0800 Subject: [PATCH 1/2] fix: resolve e2e cloud host from region, not a hardcoded default The e2e cloud suite still encoded an invalid host and a key-only flow that now fails fast, so running it without XAGENT_BASE_URL routed runtime traffic to a dead host. - Resolve the cloud host from XAGENT_REGION (au/sg) or XAGENT_BASE_URL; there is no default host, since the service is per-region and a key only authenticates against the region that minted it. - The bad-key probe now takes a resolved host via the cloud_base_url fixture, so an empty host no longer raises ValueError in the constructor before the unauthorized path is exercised. - Drop the documentation describing a key-only workflow and a hosted default that no longer exist. --- python/tests/e2e/conftest.py | 56 +++++++++++++++++++++++----- python/tests/e2e/test_cloud_smoke.py | 28 +++++++------- 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/python/tests/e2e/conftest.py b/python/tests/e2e/conftest.py index d3d311f..8506589 100644 --- a/python/tests/e2e/conftest.py +++ b/python/tests/e2e/conftest.py @@ -30,7 +30,33 @@ import pytest from xagent_sdk import AgentClient, UserClient -from xagent_sdk.cloud import WorkspaceClient +from xagent_sdk.cloud import Region, WorkspaceClient + + +def _cloud_base_url() -> str | None: + """Resolve the cloud host for e2e: explicit ``XAGENT_BASE_URL`` wins, + else the host for ``XAGENT_REGION`` (``au``/``sg``). Returns ``None`` + when neither is set -- there is no hosted default to guess, since the + service is per-region. + """ + explicit = os.environ.get("XAGENT_BASE_URL") + if explicit: + return explicit + region = os.environ.get("XAGENT_REGION") + if region: + return Region(region).base_url + return None + + +def _need_workspace() -> tuple[str, str]: + workspace_key = os.environ.get("XAGENT_WORKSPACE_KEY") + base_url = _cloud_base_url() + if not (workspace_key and base_url): + pytest.skip( + "e2e cloud surface requires XAGENT_WORKSPACE_KEY and " + "XAGENT_BASE_URL or XAGENT_REGION" + ) + return workspace_key, base_url def _need_personal() -> tuple[str, str]: @@ -91,20 +117,30 @@ def patient_agent_client() -> Iterator[AgentClient]: yield c +@pytest.fixture +def cloud_base_url() -> str: + """Resolved cloud host for tests that build their own client (e.g. a + bad-key probe). Skips when no ``XAGENT_BASE_URL`` / ``XAGENT_REGION`` + is set, since the service is per-region and has no default host. + """ + base_url = _cloud_base_url() + if base_url is None: + pytest.skip("e2e cloud surface requires XAGENT_BASE_URL or XAGENT_REGION") + return base_url + + @pytest.fixture def workspace_client() -> Iterator[WorkspaceClient]: """WorkspaceClient authenticated with a workspace key. - Requires ``XAGENT_WORKSPACE_KEY``; ``base_url`` falls back to - ``XAGENT_BASE_URL`` and then the hosted default, so a developer - pointing at a staging deploy sets ``XAGENT_BASE_URL`` while one - hitting the hosted service sets only the key. 60s per-request timeout - so agent runs created here have room to complete. + Requires ``XAGENT_WORKSPACE_KEY`` plus an explicit host -- either + ``XAGENT_BASE_URL`` (staging / self-host) or ``XAGENT_REGION`` + (``au``/``sg``). The service is per-region, so a key only authenticates + against the region that minted it; there is no default host to fall + back to. 60s per-request timeout so agent runs created here have room + to complete. """ - workspace_key = os.environ.get("XAGENT_WORKSPACE_KEY") - if not workspace_key: - pytest.skip("e2e workspace surface requires XAGENT_WORKSPACE_KEY") - base_url = os.environ.get("XAGENT_BASE_URL") + workspace_key, base_url = _need_workspace() with WorkspaceClient( workspace_key=workspace_key, base_url=base_url, timeout=60.0 ) as c: diff --git a/python/tests/e2e/test_cloud_smoke.py b/python/tests/e2e/test_cloud_smoke.py index 5fc144f..93c4280 100644 --- a/python/tests/e2e/test_cloud_smoke.py +++ b/python/tests/e2e/test_cloud_smoke.py @@ -3,8 +3,9 @@ Marked ``@pytest.mark.e2e`` so the default ``pytest`` run skips them. Run against a SaaS deploy with a workspace key:: - XAGENT_WORKSPACE_KEY=xag_workspace_... uv run pytest -m e2e - # XAGENT_BASE_URL optional; defaults to the hosted endpoint + XAGENT_WORKSPACE_KEY=xag_workspace_... XAGENT_REGION=sg uv run pytest -m e2e + # Set XAGENT_REGION (au/sg) or XAGENT_BASE_URL (staging/self-host); + # the service is per-region and has no default host. ``E2E_TEMPLATE_ID`` picks the template to instantiate (defaults to the first listed). The created agent is not deleted -- run on a scratch @@ -27,13 +28,9 @@ pytestmark = pytest.mark.e2e -def _runtime_base_url() -> str: - # The minted runtime key drives the existing /v1/chat/tasks* surface - # on the same host the workspace client targets. - return os.environ.get("XAGENT_BASE_URL") or "https://cloud.xagent.run" - - -def test_workspace_full_flow(workspace_client: WorkspaceClient) -> None: +def test_workspace_full_flow( + workspace_client: WorkspaceClient, cloud_base_url: str +) -> None: templates = workspace_client.templates.list() if not templates: pytest.skip("workspace exposes no templates; nothing to instantiate") @@ -46,8 +43,10 @@ def test_workspace_full_flow(workspace_client: WorkspaceClient) -> None: assert created.runtime_full_key is not None assert created.runtime_full_key.startswith("xag_") + # The minted runtime key drives the /v1/chat/tasks* surface on the + # same host the workspace client targets. with AgentClient( - api_key=created.runtime_full_key, base_url=_runtime_base_url(), timeout=120.0 + api_key=created.runtime_full_key, base_url=cloud_base_url, timeout=120.0 ) as runtime: result = runtime.tasks.run( agent_id=created.agent_id, @@ -65,12 +64,11 @@ def test_unknown_template_raises(workspace_client: WorkspaceClient) -> None: workspace_client.agents.create_from_template("does-not-exist-12345") -def test_bad_workspace_key_unauthorized() -> None: - base_url = os.environ.get("XAGENT_BASE_URL") - if not os.environ.get("XAGENT_WORKSPACE_KEY"): - pytest.skip("e2e workspace surface requires XAGENT_WORKSPACE_KEY") +def test_bad_workspace_key_unauthorized(cloud_base_url: str) -> None: with ( - WorkspaceClient(workspace_key="xag_workspace_bad_key", base_url=base_url) as c, + WorkspaceClient( + workspace_key="xag_workspace_bad_key", base_url=cloud_base_url + ) as c, pytest.raises(InvalidAPIKey), ): c.agents.list() From 1cffeb19faf96928f0f80655018646ac4be0a62a Mon Sep 17 00:00:00 2001 From: Alexliu Date: Tue, 23 Jun 2026 15:20:22 +0800 Subject: [PATCH 2/2] fix: normalize XAGENT_REGION case before resolving the host XAGENT_REGION=SG should resolve like sg; the Region enum matches on its lowercase value, so strip and lowercase the env var first. --- python/tests/e2e/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/tests/e2e/conftest.py b/python/tests/e2e/conftest.py index 8506589..bf28a8a 100644 --- a/python/tests/e2e/conftest.py +++ b/python/tests/e2e/conftest.py @@ -44,7 +44,7 @@ def _cloud_base_url() -> str | None: return explicit region = os.environ.get("XAGENT_REGION") if region: - return Region(region).base_url + return Region(region.strip().lower()).base_url return None