diff --git a/python/tests/e2e/conftest.py b/python/tests/e2e/conftest.py index d3d311f..bf28a8a 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.strip().lower()).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()