From 21d1b183989431b4b369daee87c043f299b001c3 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Fri, 14 Aug 2026 10:04:38 -0700 Subject: [PATCH 01/19] test: run Nexus tests against Cloud --- .github/scripts/cloud_namespace.py | 6 +- .github/workflows/ci.yml | 77 ++++++++++++++++- tests/nexus/conftest.py | 133 +++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 tests/nexus/conftest.py diff --git a/.github/scripts/cloud_namespace.py b/.github/scripts/cloud_namespace.py index c13d1e65e..456f649fc 100644 --- a/.github/scripts/cloud_namespace.py +++ b/.github/scripts/cloud_namespace.py @@ -51,8 +51,10 @@ async def wait_for_operation( async def create() -> None: client = await cloud_client() - namespace_name = "sdk-python-ci-{}-{}".format( - os.environ["GITHUB_RUN_ID"], os.environ["GITHUB_RUN_ATTEMPT"] + namespace_name = "sdk-python-ci-{}-{}{}".format( + os.environ["GITHUB_RUN_ID"], + os.environ["GITHUB_RUN_ATTEMPT"], + os.environ.get("TEMPORAL_CLOUD_NAMESPACE_SUFFIX", ""), ) result = await client.cloud_service.create_namespace( CreateNamespaceRequest( diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1098dcdc..c632ab98b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -254,8 +254,9 @@ jobs: env: TEMPORAL_CLIENT_CLOUD_API_KEY: ${{ secrets.TEMPORAL_CLIENT_CLOUD_API_KEY }} TEMPORAL_CLIENT_CLOUD_API_VERSION: v0.19.1 + TEMPORAL_CLOUD_NAMESPACE_SUFFIX: -general - run: mkdir junit-xml - - run: poe test -s --workflow-environment envconfig --junit-xml=junit-xml/cloud.xml + - run: poe test -s --workflow-environment envconfig --ignore=tests/nexus --junit-xml=junit-xml/cloud.xml timeout-minutes: 15 env: TEMPORAL_ADDRESS: ${{ steps.create-cloud-namespace.outputs.namespace }}.tmprl.cloud:7233 @@ -278,6 +279,80 @@ jobs: path: junit-xml retention-days: 14 + # Nexus endpoint provisioning is slow on Cloud, so run these tests separately. + cloud-nexus-test: + if: ${{ github.event.pull_request.head.repo.full_name == '' || github.event.pull_request.head.repo.full_name == 'temporalio/sdk-python' }} + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + submodules: recursive + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.14" + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + with: + workspaces: temporalio/bridge -> target + key: ${{ env.pythonLocation }} + - uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3 + with: + version: "23.x" + repo-token: ${{ secrets.GITHUB_TOKEN }} + - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8 + - run: uv tool install poethepoet + - run: uv sync --all-extras + - run: poe build-develop + - name: Generate Cloud test certificates + run: | + cert_dir="$RUNNER_TEMP/cloud-test-certs" + mkdir "$cert_dir" + openssl req -x509 -newkey rsa:2048 -nodes -days 1 \ + -keyout "$cert_dir/ca.key" -out "$cert_dir/ca.pem" \ + -subj '/CN=Temporal Python SDK Cloud CI CA' + openssl req -newkey rsa:2048 -nodes \ + -keyout "$cert_dir/client.key" -out "$cert_dir/client.csr" \ + -subj '/CN=Temporal Python SDK Cloud CI' + openssl x509 -req -days 1 -in "$cert_dir/client.csr" \ + -CA "$cert_dir/ca.pem" -CAkey "$cert_dir/ca.key" -CAcreateserial \ + -out "$cert_dir/client.pem" -extfile <(printf 'extendedKeyUsage=clientAuth') + { + echo "TEMPORAL_CLOUD_CLIENT_CA_PATH=$cert_dir/ca.pem" + echo "TEMPORAL_TLS_CLIENT_CERT_PATH=$cert_dir/client.pem" + echo "TEMPORAL_TLS_CLIENT_KEY_PATH=$cert_dir/client.key" + } >> "$GITHUB_ENV" + - name: Create Cloud namespace + id: create-cloud-namespace + run: uv run python .github/scripts/cloud_namespace.py create + env: + TEMPORAL_CLIENT_CLOUD_API_KEY: ${{ secrets.TEMPORAL_CLIENT_CLOUD_API_KEY }} + TEMPORAL_CLIENT_CLOUD_API_VERSION: v0.19.1 + TEMPORAL_CLOUD_NAMESPACE_SUFFIX: -nexus + - run: mkdir junit-xml + - run: poe test -n 16 -s --workflow-environment envconfig tests/nexus --junit-xml=junit-xml/cloud-nexus.xml + timeout-minutes: 45 + env: + TEMPORAL_ADDRESS: ${{ steps.create-cloud-namespace.outputs.namespace }}.tmprl.cloud:7233 + TEMPORAL_NAMESPACE: ${{ steps.create-cloud-namespace.outputs.namespace }} + TEMPORAL_IS_CLOUD_TESTS: true + TEMPORAL_CLIENT_CLOUD_API_KEY: ${{ secrets.TEMPORAL_CLIENT_CLOUD_API_KEY }} + TEMPORAL_CLIENT_CLOUD_API_VERSION: v0.19.1 + TEMPORAL_CLIENT_CLOUD_NAMESPACE: ${{ steps.create-cloud-namespace.outputs.namespace }} + - name: Delete Cloud namespace + if: ${{ always() && steps.create-cloud-namespace.outputs.namespace != '' }} + run: uv run python .github/scripts/cloud_namespace.py delete "${{ steps.create-cloud-namespace.outputs.namespace }}" + env: + TEMPORAL_CLIENT_CLOUD_API_KEY: ${{ secrets.TEMPORAL_CLIENT_CLOUD_API_KEY }} + TEMPORAL_CLIENT_CLOUD_API_VERSION: v0.19.1 + - name: Upload junit-xml artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + if: always() + with: + name: junit-xml--${{github.run_id}}--${{github.run_attempt}}--cloud-nexus + path: junit-xml + retention-days: 14 + # Runs the sdk features repo tests with this repo's current SDK code features-tests: uses: temporalio/features/.github/workflows/python.yaml@main diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py new file mode 100644 index 000000000..8097a4533 --- /dev/null +++ b/tests/nexus/conftest.py @@ -0,0 +1,133 @@ +import asyncio +import os +import time +from collections.abc import AsyncGenerator +from dataclasses import dataclass + +import pytest +import pytest_asyncio + +from temporalio.api.cloud.cloudservice.v1 import ( + CreateNexusEndpointRequest, + DeleteNexusEndpointRequest, + GetAsyncOperationRequest, + GetNamespaceRequest, + GetNexusEndpointRequest, +) +from temporalio.api.cloud.nexus.v1 import ( + Endpoint, + EndpointSpec, + EndpointTargetSpec, + WorkerTargetSpec, +) +from temporalio.api.cloud.operation.v1 import AsyncOperation +from temporalio.client import CloudOperationsClient +from temporalio.testing import WorkflowEnvironment + + +@dataclass +class _CloudNexusEndpointClient: + client: CloudOperationsClient + namespace_id: str + + async def wait_for_operation(self, operation: AsyncOperation) -> None: + deadline = time.monotonic() + 10 * 60 + while True: + operation = ( + await self.client.cloud_service.get_async_operation( + GetAsyncOperationRequest(async_operation_id=operation.id) + ) + ).async_operation + if operation.state == AsyncOperation.STATE_FULFILLED: + return + if operation.state in { + AsyncOperation.STATE_FAILED, + AsyncOperation.STATE_CANCELLED, + AsyncOperation.STATE_REJECTED, + }: + raise RuntimeError( + "Cloud operation " + f"{operation.id} " + f"{AsyncOperation.State.Name(operation.state).lower()}: " + f"{operation.failure_reason}" + ) + if time.monotonic() >= deadline: + raise TimeoutError( + f"Timed out waiting for Cloud operation {operation.id}" + ) + delay = max( + operation.check_duration.seconds + + operation.check_duration.nanos / 1_000_000_000, + 1, + ) + await asyncio.sleep(min(delay, deadline - time.monotonic())) + + +@pytest_asyncio.fixture(scope="session") +async def cloud_nexus_endpoint_client() -> AsyncGenerator[ + _CloudNexusEndpointClient | None, None +]: + if "TEMPORAL_IS_CLOUD_TESTS" not in os.environ: + yield None + return + + client = await CloudOperationsClient.connect( + api_key=os.environ["TEMPORAL_CLIENT_CLOUD_API_KEY"], + version=os.environ["TEMPORAL_CLIENT_CLOUD_API_VERSION"], + ) + namespace = await client.cloud_service.get_namespace( + GetNamespaceRequest(namespace=os.environ["TEMPORAL_NAMESPACE"]) + ) + yield _CloudNexusEndpointClient(client, namespace.namespace.namespace) + + +@pytest_asyncio.fixture(autouse=True) +async def cloud_nexus_endpoints( + cloud_nexus_endpoint_client: _CloudNexusEndpointClient | None, + monkeypatch: pytest.MonkeyPatch, + request: pytest.FixtureRequest, +) -> AsyncGenerator[None, None]: + if cloud_nexus_endpoint_client is None: + yield + return + + env: WorkflowEnvironment = request.getfixturevalue("env") + endpoints: list[Endpoint] = [] + + async def create_nexus_endpoint(endpoint_name: str, task_queue: str) -> Endpoint: + response = await cloud_nexus_endpoint_client.client.cloud_service.create_nexus_endpoint( + CreateNexusEndpointRequest( + spec=EndpointSpec( + name=endpoint_name, + target_spec=EndpointTargetSpec( + worker_target_spec=WorkerTargetSpec( + namespace_id=cloud_nexus_endpoint_client.namespace_id, + task_queue=task_queue, + ) + ), + ) + ) + ) + await cloud_nexus_endpoint_client.wait_for_operation(response.async_operation) + endpoint = ( + await cloud_nexus_endpoint_client.client.cloud_service.get_nexus_endpoint( + GetNexusEndpointRequest(endpoint_id=response.endpoint_id) + ) + ).endpoint + endpoints.append(endpoint) + return endpoint + + monkeypatch.setattr(env, "create_nexus_endpoint", create_nexus_endpoint) + try: + yield + finally: + for endpoint in reversed(endpoints): + response = await cloud_nexus_endpoint_client.client.cloud_service.delete_nexus_endpoint( + DeleteNexusEndpointRequest( + endpoint_id=endpoint.id, + resource_version=endpoint.resource_version, + ) + ) + await cloud_nexus_endpoint_client.wait_for_operation( + response.async_operation + ) From 6de11075358cfad51ecb9736aa56e9c32d95f9b2 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Fri, 14 Aug 2026 10:14:58 -0700 Subject: [PATCH 02/19] test: fix Cloud Nexus fixture setup --- tests/nexus/conftest.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 8097a4533..ebf0259d5 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -63,7 +63,7 @@ async def wait_for_operation(self, operation: AsyncOperation) -> None: await asyncio.sleep(min(delay, deadline - time.monotonic())) -@pytest_asyncio.fixture(scope="session") +@pytest_asyncio.fixture(scope="session") # type: ignore[reportUntypedFunctionDecorator] async def cloud_nexus_endpoint_client() -> AsyncGenerator[ _CloudNexusEndpointClient | None, None ]: @@ -81,17 +81,16 @@ async def cloud_nexus_endpoint_client() -> AsyncGenerator[ yield _CloudNexusEndpointClient(client, namespace.namespace.namespace) -@pytest_asyncio.fixture(autouse=True) +@pytest_asyncio.fixture(autouse=True) # type: ignore[reportUntypedFunctionDecorator] async def cloud_nexus_endpoints( cloud_nexus_endpoint_client: _CloudNexusEndpointClient | None, + env: WorkflowEnvironment, monkeypatch: pytest.MonkeyPatch, - request: pytest.FixtureRequest, ) -> AsyncGenerator[None, None]: if cloud_nexus_endpoint_client is None: yield return - env: WorkflowEnvironment = request.getfixturevalue("env") endpoints: list[Endpoint] = [] async def create_nexus_endpoint(endpoint_name: str, task_queue: str) -> Endpoint: From a42284af2704750dc408c62b4e77f96d366ca0dc Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Fri, 14 Aug 2026 10:22:12 -0700 Subject: [PATCH 03/19] test: enable Nexus tests in Cloud --- tests/nexus/test_dynamic_creation_of_user_handler_classes.py | 4 ---- tests/nexus/test_nexus_client_updates.py | 5 ----- tests/nexus/test_nexus_worker_shutdown.py | 4 ---- tests/nexus/test_signal_link_propagation_e2e.py | 4 ---- tests/nexus/test_standalone_operations.py | 5 ----- tests/nexus/test_temporal_extstore.py | 4 ---- tests/nexus/test_temporal_operation.py | 4 ---- tests/nexus/test_use_existing_conflict_policy.py | 5 ----- tests/nexus/test_workflow_caller.py | 5 ----- tests/nexus/test_workflow_caller_cancellation_types.py | 4 ---- ...ow_caller_cancellation_types_when_cancel_handler_fails.py | 4 ---- tests/nexus/test_workflow_caller_error_chains.py | 4 ---- tests/nexus/test_workflow_caller_errors.py | 4 ---- tests/nexus/test_workflow_run_operation.py | 4 ---- 14 files changed, 60 deletions(-) diff --git a/tests/nexus/test_dynamic_creation_of_user_handler_classes.py b/tests/nexus/test_dynamic_creation_of_user_handler_classes.py index 214e02ab9..f7306a46b 100644 --- a/tests/nexus/test_dynamic_creation_of_user_handler_classes.py +++ b/tests/nexus/test_dynamic_creation_of_user_handler_classes.py @@ -10,10 +10,6 @@ from temporalio.worker import Worker from tests.helpers.nexus import make_nexus_endpoint_name -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - @workflow.defn class MyWorkflow: diff --git a/tests/nexus/test_nexus_client_updates.py b/tests/nexus/test_nexus_client_updates.py index 97dd251da..302ede54e 100644 --- a/tests/nexus/test_nexus_client_updates.py +++ b/tests/nexus/test_nexus_client_updates.py @@ -3,7 +3,6 @@ import uuid import nexusrpc -import pytest from nexusrpc.handler import StartOperationContext, service_handler, sync_operation import temporalio.nexus @@ -12,10 +11,6 @@ from temporalio.testing import WorkflowEnvironment from temporalio.worker import Worker -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - @nexusrpc.service class ClientTestService: diff --git a/tests/nexus/test_nexus_worker_shutdown.py b/tests/nexus/test_nexus_worker_shutdown.py index 2a94027d5..bd9063237 100644 --- a/tests/nexus/test_nexus_worker_shutdown.py +++ b/tests/nexus/test_nexus_worker_shutdown.py @@ -23,10 +23,6 @@ make_nexus_endpoint_name, ) -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - @nexusrpc.service class ShutdownTestService: diff --git a/tests/nexus/test_signal_link_propagation_e2e.py b/tests/nexus/test_signal_link_propagation_e2e.py index 9e51d4b93..e489ad8a7 100644 --- a/tests/nexus/test_signal_link_propagation_e2e.py +++ b/tests/nexus/test_signal_link_propagation_e2e.py @@ -56,10 +56,6 @@ workflow_event_link_event_type, ) -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - EventType = temporalio.api.enums.v1.EventType diff --git a/tests/nexus/test_standalone_operations.py b/tests/nexus/test_standalone_operations.py index 26a8316b4..c71337b20 100644 --- a/tests/nexus/test_standalone_operations.py +++ b/tests/nexus/test_standalone_operations.py @@ -71,11 +71,6 @@ # --------------------------------------------------------------------------- -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - - @dataclass class EchoInput: value: str diff --git a/tests/nexus/test_temporal_extstore.py b/tests/nexus/test_temporal_extstore.py index f23a69259..2afbac44c 100644 --- a/tests/nexus/test_temporal_extstore.py +++ b/tests/nexus/test_temporal_extstore.py @@ -41,10 +41,6 @@ from tests.helpers.nexus import make_nexus_endpoint_name from tests.test_extstore import InMemoryTestDriver -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - PAYLOAD_SIZE = 4096 PAYLOAD_SIZE_THRESHOLD = 1024 _STORE_FAILURE_MESSAGE = "external storage store failed" diff --git a/tests/nexus/test_temporal_operation.py b/tests/nexus/test_temporal_operation.py index 85948deb3..45a6fb1f0 100644 --- a/tests/nexus/test_temporal_operation.py +++ b/tests/nexus/test_temporal_operation.py @@ -40,10 +40,6 @@ make_nexus_endpoint_name, ) -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - @dataclass class Input: diff --git a/tests/nexus/test_use_existing_conflict_policy.py b/tests/nexus/test_use_existing_conflict_policy.py index 8ffa9f8f8..e7cee9e1c 100644 --- a/tests/nexus/test_use_existing_conflict_policy.py +++ b/tests/nexus/test_use_existing_conflict_policy.py @@ -4,7 +4,6 @@ import uuid from dataclasses import dataclass -import pytest from nexusrpc.handler import service_handler from temporalio import nexus, workflow @@ -14,10 +13,6 @@ from temporalio.worker import Worker from tests.helpers.nexus import make_nexus_endpoint_name -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - @dataclass class OpInput: diff --git a/tests/nexus/test_workflow_caller.py b/tests/nexus/test_workflow_caller.py index 0c47f17c1..328637525 100644 --- a/tests/nexus/test_workflow_caller.py +++ b/tests/nexus/test_workflow_caller.py @@ -90,11 +90,6 @@ class OpDefinitionType(IntEnum): LONGHAND = 1 -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - - @dataclass class SyncResponse: op_definition_type: OpDefinitionType diff --git a/tests/nexus/test_workflow_caller_cancellation_types.py b/tests/nexus/test_workflow_caller_cancellation_types.py index eca269984..bf33983a5 100644 --- a/tests/nexus/test_workflow_caller_cancellation_types.py +++ b/tests/nexus/test_workflow_caller_cancellation_types.py @@ -25,10 +25,6 @@ from tests.helpers import LogCapturer, assert_event_subsequence, assert_eventually from tests.helpers.nexus import make_nexus_endpoint_name -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - @dataclass class TestContext: diff --git a/tests/nexus/test_workflow_caller_cancellation_types_when_cancel_handler_fails.py b/tests/nexus/test_workflow_caller_cancellation_types_when_cancel_handler_fails.py index 44d91a7d1..4cdeeeb15 100644 --- a/tests/nexus/test_workflow_caller_cancellation_types_when_cancel_handler_fails.py +++ b/tests/nexus/test_workflow_caller_cancellation_types_when_cancel_handler_fails.py @@ -30,10 +30,6 @@ has_event, ) -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - @dataclass class TestContext: diff --git a/tests/nexus/test_workflow_caller_error_chains.py b/tests/nexus/test_workflow_caller_error_chains.py index 1012d8a94..9ff84f405 100644 --- a/tests/nexus/test_workflow_caller_error_chains.py +++ b/tests/nexus/test_workflow_caller_error_chains.py @@ -25,10 +25,6 @@ from temporalio.worker import Worker from tests.helpers.nexus import make_nexus_endpoint_name -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - @dataclass class ExpectedError: diff --git a/tests/nexus/test_workflow_caller_errors.py b/tests/nexus/test_workflow_caller_errors.py index 0f1b6a789..9246b9fd7 100644 --- a/tests/nexus/test_workflow_caller_errors.py +++ b/tests/nexus/test_workflow_caller_errors.py @@ -42,10 +42,6 @@ from tests.helpers import LogCapturer, assert_eq_eventually from tests.helpers.nexus import make_nexus_endpoint_name -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - operation_invocation_counts = Counter[str]() logger = getLogger(__name__) diff --git a/tests/nexus/test_workflow_run_operation.py b/tests/nexus/test_workflow_run_operation.py index 7135fde71..851f408ec 100644 --- a/tests/nexus/test_workflow_run_operation.py +++ b/tests/nexus/test_workflow_run_operation.py @@ -23,10 +23,6 @@ from temporalio.worker import Worker from tests.helpers.nexus import make_nexus_endpoint_name -# Cloud CI's namespace credentials cannot manage Nexus endpoints. -# See https://github.com/temporalio/sdk-python/issues/1704. -pytestmark = pytest.mark.requires_local_server - @dataclass class Input: From 116445d5e5f7742cd55056b7b1c124ed433cf230 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Fri, 14 Aug 2026 10:37:54 -0700 Subject: [PATCH 04/19] test: allow Cloud Nexus endpoint callers --- tests/nexus/conftest.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index ebf0259d5..7756cf06a 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -15,7 +15,9 @@ GetNexusEndpointRequest, ) from temporalio.api.cloud.nexus.v1 import ( + AllowedCloudNamespacePolicySpec, Endpoint, + EndpointPolicySpec, EndpointSpec, EndpointTargetSpec, WorkerTargetSpec, @@ -104,6 +106,13 @@ async def create_nexus_endpoint(endpoint_name: str, task_queue: str) -> Endpoint task_queue=task_queue, ) ), + policy_specs=[ + EndpointPolicySpec( + allowed_cloud_namespace_policy_spec=AllowedCloudNamespacePolicySpec( + namespace_id=cloud_nexus_endpoint_client.namespace_id + ) + ) + ], ) ) ) From e3ab619baf9a1b119dbaa602eaaf4c41f3283246 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Fri, 14 Aug 2026 10:52:06 -0700 Subject: [PATCH 05/19] test: wait for Cloud Nexus endpoints to activate --- tests/nexus/conftest.py | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 7756cf06a..921828fe7 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -23,6 +23,7 @@ WorkerTargetSpec, ) from temporalio.api.cloud.operation.v1 import AsyncOperation +from temporalio.api.cloud.resource.v1 import ResourceState from temporalio.client import CloudOperationsClient from temporalio.testing import WorkflowEnvironment @@ -64,6 +65,34 @@ async def wait_for_operation(self, operation: AsyncOperation) -> None: ) await asyncio.sleep(min(delay, deadline - time.monotonic())) + async def wait_for_endpoint(self, endpoint_id: str) -> Endpoint: + deadline = time.monotonic() + 10 * 60 + while True: + endpoint = ( + await self.client.cloud_service.get_nexus_endpoint( + GetNexusEndpointRequest(endpoint_id=endpoint_id) + ) + ).endpoint + if endpoint.state == ResourceState.RESOURCE_STATE_ACTIVE: + return endpoint + if endpoint.state in { + ResourceState.RESOURCE_STATE_ACTIVATION_FAILED, + ResourceState.RESOURCE_STATE_UPDATE_FAILED, + ResourceState.RESOURCE_STATE_DELETE_FAILED, + ResourceState.RESOURCE_STATE_SUSPENDED, + ResourceState.RESOURCE_STATE_EXPIRED, + }: + raise RuntimeError( + "Cloud Nexus endpoint " + f"{endpoint_id} " + f"{ResourceState.Name(endpoint.state).lower()}" + ) + if time.monotonic() >= deadline: + raise TimeoutError( + f"Timed out waiting for Cloud Nexus endpoint {endpoint_id}" + ) + await asyncio.sleep(1) + @pytest_asyncio.fixture(scope="session") # type: ignore[reportUntypedFunctionDecorator] async def cloud_nexus_endpoint_client() -> AsyncGenerator[ @@ -117,11 +146,9 @@ async def create_nexus_endpoint(endpoint_name: str, task_queue: str) -> Endpoint ) ) await cloud_nexus_endpoint_client.wait_for_operation(response.async_operation) - endpoint = ( - await cloud_nexus_endpoint_client.client.cloud_service.get_nexus_endpoint( - GetNexusEndpointRequest(endpoint_id=response.endpoint_id) - ) - ).endpoint + endpoint = await cloud_nexus_endpoint_client.wait_for_endpoint( + response.endpoint_id + ) endpoints.append(endpoint) return endpoint From 012a08a23d9ae2423c621413e0abe819e3b2ddf5 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Fri, 14 Aug 2026 11:06:04 -0700 Subject: [PATCH 06/19] test: wait for Cloud Nexus endpoint propagation --- tests/nexus/conftest.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 921828fe7..97b276a75 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -24,7 +24,11 @@ ) from temporalio.api.cloud.operation.v1 import AsyncOperation from temporalio.api.cloud.resource.v1 import ResourceState +from temporalio.api.operatorservice.v1 import ( + GetNexusEndpointRequest as GetDataPlaneNexusEndpointRequest, +) from temporalio.client import CloudOperationsClient +from temporalio.service import RPCError, RPCStatusCode from temporalio.testing import WorkflowEnvironment @@ -93,6 +97,26 @@ async def wait_for_endpoint(self, endpoint_id: str) -> Endpoint: ) await asyncio.sleep(1) + async def wait_for_data_plane_endpoint( + self, env: WorkflowEnvironment, endpoint_id: str + ) -> None: + deadline = time.monotonic() + 10 * 60 + while True: + try: + await env.client.operator_service.get_nexus_endpoint( + GetDataPlaneNexusEndpointRequest(id=endpoint_id) + ) + return + except RPCError as err: + if err.status != RPCStatusCode.NOT_FOUND: + raise + if time.monotonic() >= deadline: + raise TimeoutError( + "Timed out waiting for Cloud Nexus endpoint " + f"{endpoint_id} to reach the data plane" + ) + await asyncio.sleep(1) + @pytest_asyncio.fixture(scope="session") # type: ignore[reportUntypedFunctionDecorator] async def cloud_nexus_endpoint_client() -> AsyncGenerator[ @@ -149,6 +173,7 @@ async def create_nexus_endpoint(endpoint_name: str, task_queue: str) -> Endpoint endpoint = await cloud_nexus_endpoint_client.wait_for_endpoint( response.endpoint_id ) + await cloud_nexus_endpoint_client.wait_for_data_plane_endpoint(env, endpoint.id) endpoints.append(endpoint) return endpoint From 8be8de09b18aa3c6badd542762b83fd66e2cd877 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Mon, 17 Aug 2026 11:01:45 -0700 Subject: [PATCH 07/19] test: probe Cloud Nexus endpoint readiness --- .github/scripts/cloud_namespace.py | 23 ++++++++ tests/nexus/conftest.py | 88 ++++++++++++++++++++++-------- 2 files changed, 89 insertions(+), 22 deletions(-) diff --git a/.github/scripts/cloud_namespace.py b/.github/scripts/cloud_namespace.py index 456f649fc..04c064117 100644 --- a/.github/scripts/cloud_namespace.py +++ b/.github/scripts/cloud_namespace.py @@ -9,8 +9,10 @@ from temporalio.api.cloud.cloudservice.v1 import ( CreateNamespaceRequest, DeleteNamespaceRequest, + DeleteNexusEndpointRequest, GetAsyncOperationRequest, GetNamespaceRequest, + GetNexusEndpointsRequest, ) from temporalio.api.cloud.namespace.v1 import MtlsAuthSpec, NamespaceSpec from temporalio.api.cloud.operation.v1 import AsyncOperation @@ -82,6 +84,27 @@ async def delete(namespace: str) -> None: existing = await client.cloud_service.get_namespace( GetNamespaceRequest(namespace=namespace) ) + endpoints = [] + page_token = "" + while True: + response = await client.cloud_service.get_nexus_endpoints( + GetNexusEndpointsRequest( + target_namespace_id=existing.namespace.namespace, + page_token=page_token, + ) + ) + endpoints.extend(response.endpoints) + if not response.next_page_token: + break + page_token = response.next_page_token + for endpoint in endpoints: + result = await client.cloud_service.delete_nexus_endpoint( + DeleteNexusEndpointRequest( + endpoint_id=endpoint.id, + resource_version=endpoint.resource_version, + ) + ) + await wait_for_operation(client, result.async_operation) result = await client.cloud_service.delete_namespace( DeleteNamespaceRequest( namespace=namespace, diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 97b276a75..158a90f11 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -1,11 +1,14 @@ import asyncio import os import time +import uuid from collections.abc import AsyncGenerator from dataclasses import dataclass +import nexusrpc import pytest import pytest_asyncio +from nexusrpc.handler import StartOperationContext, service_handler, sync_operation from temporalio.api.cloud.cloudservice.v1 import ( CreateNexusEndpointRequest, @@ -24,12 +27,23 @@ ) from temporalio.api.cloud.operation.v1 import AsyncOperation from temporalio.api.cloud.resource.v1 import ResourceState -from temporalio.api.operatorservice.v1 import ( - GetNexusEndpointRequest as GetDataPlaneNexusEndpointRequest, -) +from temporalio.api.workflowservice.v1 import DeleteNexusOperationExecutionRequest from temporalio.client import CloudOperationsClient from temporalio.service import RPCError, RPCStatusCode from temporalio.testing import WorkflowEnvironment +from temporalio.worker import Worker + + +@nexusrpc.service +class CloudNexusEndpointReadinessService: + ready: nexusrpc.Operation[None, None] + + +@service_handler(service=CloudNexusEndpointReadinessService) +class CloudNexusEndpointReadinessServiceHandler: + @sync_operation + async def ready(self, _ctx: StartOperationContext, _input: None) -> None: + pass @dataclass @@ -97,25 +111,47 @@ async def wait_for_endpoint(self, endpoint_id: str) -> Endpoint: ) await asyncio.sleep(1) - async def wait_for_data_plane_endpoint( - self, env: WorkflowEnvironment, endpoint_id: str + async def wait_for_endpoint_readiness( + self, env: WorkflowEnvironment, endpoint_name: str, task_queue: str ) -> None: deadline = time.monotonic() + 10 * 60 - while True: - try: - await env.client.operator_service.get_nexus_endpoint( - GetDataPlaneNexusEndpointRequest(id=endpoint_id) - ) - return - except RPCError as err: - if err.status != RPCStatusCode.NOT_FOUND: - raise - if time.monotonic() >= deadline: - raise TimeoutError( - "Timed out waiting for Cloud Nexus endpoint " - f"{endpoint_id} to reach the data plane" - ) - await asyncio.sleep(1) + operation_id = f"cloud-nexus-readiness-{uuid.uuid4()}" + nexus_client = env.client.create_nexus_client( + CloudNexusEndpointReadinessService, endpoint_name + ) + # Cloud reports endpoint activation before the Workflow Service can always + # resolve it. A successful disposable operation verifies that propagation. + async with Worker( + env.client, + task_queue=task_queue, + nexus_service_handlers=[CloudNexusEndpointReadinessServiceHandler()], + ): + while True: + try: + await nexus_client.execute_operation( + CloudNexusEndpointReadinessService.ready, + None, + id=operation_id, + ) + break + except RPCError as err: + if ( + err.status != RPCStatusCode.NOT_FOUND + or str(err) != "endpoint not found" + ): + raise + if time.monotonic() >= deadline: + raise TimeoutError( + f"Timed out waiting for Cloud Nexus endpoint {endpoint_name} " + "to become available" + ) from err + await asyncio.sleep(1) + await env.client.workflow_service.delete_nexus_operation_execution( + DeleteNexusOperationExecutionRequest( + namespace=env.client.namespace, + operation_id=operation_id, + ) + ) @pytest_asyncio.fixture(scope="session") # type: ignore[reportUntypedFunctionDecorator] @@ -170,11 +206,19 @@ async def create_nexus_endpoint(endpoint_name: str, task_queue: str) -> Endpoint ) ) await cloud_nexus_endpoint_client.wait_for_operation(response.async_operation) + endpoint = ( + await cloud_nexus_endpoint_client.client.cloud_service.get_nexus_endpoint( + GetNexusEndpointRequest(endpoint_id=response.endpoint_id) + ) + ).endpoint + endpoints.append(endpoint) endpoint = await cloud_nexus_endpoint_client.wait_for_endpoint( response.endpoint_id ) - await cloud_nexus_endpoint_client.wait_for_data_plane_endpoint(env, endpoint.id) - endpoints.append(endpoint) + endpoints[-1] = endpoint + await cloud_nexus_endpoint_client.wait_for_endpoint_readiness( + env, endpoint_name, task_queue + ) return endpoint monkeypatch.setattr(env, "create_nexus_endpoint", create_nexus_endpoint) From f71b48f6fc0b4bc208da0a53e43e4b24b6b272f4 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Mon, 17 Aug 2026 11:30:04 -0700 Subject: [PATCH 08/19] test: avoid Cloud Nexus readiness worker conflicts --- tests/nexus/conftest.py | 60 ++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 158a90f11..6c9d544e1 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -8,7 +8,6 @@ import nexusrpc import pytest import pytest_asyncio -from nexusrpc.handler import StartOperationContext, service_handler, sync_operation from temporalio.api.cloud.cloudservice.v1 import ( CreateNexusEndpointRequest, @@ -31,7 +30,6 @@ from temporalio.client import CloudOperationsClient from temporalio.service import RPCError, RPCStatusCode from temporalio.testing import WorkflowEnvironment -from temporalio.worker import Worker @nexusrpc.service @@ -39,13 +37,6 @@ class CloudNexusEndpointReadinessService: ready: nexusrpc.Operation[None, None] -@service_handler(service=CloudNexusEndpointReadinessService) -class CloudNexusEndpointReadinessServiceHandler: - @sync_operation - async def ready(self, _ctx: StartOperationContext, _input: None) -> None: - pass - - @dataclass class _CloudNexusEndpointClient: client: CloudOperationsClient @@ -112,7 +103,7 @@ async def wait_for_endpoint(self, endpoint_id: str) -> Endpoint: await asyncio.sleep(1) async def wait_for_endpoint_readiness( - self, env: WorkflowEnvironment, endpoint_name: str, task_queue: str + self, env: WorkflowEnvironment, endpoint_name: str ) -> None: deadline = time.monotonic() + 10 * 60 operation_id = f"cloud-nexus-readiness-{uuid.uuid4()}" @@ -120,32 +111,27 @@ async def wait_for_endpoint_readiness( CloudNexusEndpointReadinessService, endpoint_name ) # Cloud reports endpoint activation before the Workflow Service can always - # resolve it. A successful disposable operation verifies that propagation. - async with Worker( - env.client, - task_queue=task_queue, - nexus_service_handlers=[CloudNexusEndpointReadinessServiceHandler()], - ): - while True: - try: - await nexus_client.execute_operation( - CloudNexusEndpointReadinessService.ready, - None, - id=operation_id, - ) - break - except RPCError as err: - if ( - err.status != RPCStatusCode.NOT_FOUND - or str(err) != "endpoint not found" - ): - raise - if time.monotonic() >= deadline: - raise TimeoutError( - f"Timed out waiting for Cloud Nexus endpoint {endpoint_name} " - "to become available" - ) from err - await asyncio.sleep(1) + # resolve it. A successful disposable start request verifies that propagation. + while True: + try: + await nexus_client.start_operation( + CloudNexusEndpointReadinessService.ready, + None, + id=operation_id, + ) + break + except RPCError as err: + if ( + err.status != RPCStatusCode.NOT_FOUND + or str(err) != "endpoint not found" + ): + raise + if time.monotonic() >= deadline: + raise TimeoutError( + f"Timed out waiting for Cloud Nexus endpoint {endpoint_name} " + "to become available" + ) from err + await asyncio.sleep(1) await env.client.workflow_service.delete_nexus_operation_execution( DeleteNexusOperationExecutionRequest( namespace=env.client.namespace, @@ -217,7 +203,7 @@ async def create_nexus_endpoint(endpoint_name: str, task_queue: str) -> Endpoint ) endpoints[-1] = endpoint await cloud_nexus_endpoint_client.wait_for_endpoint_readiness( - env, endpoint_name, task_queue + env, endpoint_name ) return endpoint From 1b9f2a50d33a9b2af23180383b693a3b9276a178 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Tue, 18 Aug 2026 11:52:25 -0700 Subject: [PATCH 09/19] test: log Cloud Nexus endpoint readiness --- tests/nexus/conftest.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 6c9d544e1..0fe250e89 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -1,4 +1,5 @@ import asyncio +import logging import os import time import uuid @@ -31,6 +32,8 @@ from temporalio.service import RPCError, RPCStatusCode from temporalio.testing import WorkflowEnvironment +logger = logging.getLogger(__name__) + @nexusrpc.service class CloudNexusEndpointReadinessService: @@ -112,13 +115,22 @@ async def wait_for_endpoint_readiness( ) # Cloud reports endpoint activation before the Workflow Service can always # resolve it. A successful disposable start request verifies that propagation. + attempts = 0 while True: + attempts += 1 try: await nexus_client.start_operation( CloudNexusEndpointReadinessService.ready, None, id=operation_id, ) + logger.info( + "Cloud Nexus endpoint %s accepted readiness operation %s " + "after %d attempt(s)", + endpoint_name, + operation_id, + attempts, + ) break except RPCError as err: if ( @@ -131,7 +143,19 @@ async def wait_for_endpoint_readiness( f"Timed out waiting for Cloud Nexus endpoint {endpoint_name} " "to become available" ) from err + if attempts == 1: + logger.info( + "Cloud Nexus endpoint %s is not yet resolvable; retrying " + "readiness operation %s", + endpoint_name, + operation_id, + ) await asyncio.sleep(1) + logger.info( + "Deleting Cloud Nexus readiness operation %s for endpoint %s", + operation_id, + endpoint_name, + ) await env.client.workflow_service.delete_nexus_operation_execution( DeleteNexusOperationExecutionRequest( namespace=env.client.namespace, @@ -171,6 +195,11 @@ async def cloud_nexus_endpoints( endpoints: list[Endpoint] = [] async def create_nexus_endpoint(endpoint_name: str, task_queue: str) -> Endpoint: + logger.info( + "Creating Cloud Nexus endpoint %s for task queue %s", + endpoint_name, + task_queue, + ) response = await cloud_nexus_endpoint_client.client.cloud_service.create_nexus_endpoint( CreateNexusEndpointRequest( spec=EndpointSpec( @@ -202,9 +231,15 @@ async def create_nexus_endpoint(endpoint_name: str, task_queue: str) -> Endpoint response.endpoint_id ) endpoints[-1] = endpoint + logger.info( + "Cloud Nexus endpoint %s (%s) is active; checking Workflow Service readiness", + endpoint_name, + endpoint.id, + ) await cloud_nexus_endpoint_client.wait_for_endpoint_readiness( env, endpoint_name ) + logger.info("Cloud Nexus endpoint %s is ready for the test", endpoint_name) return endpoint monkeypatch.setattr(env, "create_nexus_endpoint", create_nexus_endpoint) @@ -212,6 +247,9 @@ async def create_nexus_endpoint(endpoint_name: str, task_queue: str) -> Endpoint yield finally: for endpoint in reversed(endpoints): + logger.info( + "Deleting Cloud Nexus endpoint %s (%s)", endpoint.spec.name, endpoint.id + ) response = await cloud_nexus_endpoint_client.client.cloud_service.delete_nexus_endpoint( DeleteNexusEndpointRequest( endpoint_id=endpoint.id, From fa5248daeda52ae62d24dbba2bcd181ccf642d5b Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Tue, 18 Aug 2026 15:56:52 -0700 Subject: [PATCH 10/19] test: wait for Cloud Nexus endpoint routing --- tests/nexus/conftest.py | 67 +++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 0fe250e89..8332af3ac 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -5,6 +5,7 @@ import uuid from collections.abc import AsyncGenerator from dataclasses import dataclass +from datetime import timedelta import nexusrpc import pytest @@ -28,7 +29,7 @@ from temporalio.api.cloud.operation.v1 import AsyncOperation from temporalio.api.cloud.resource.v1 import ResourceState from temporalio.api.workflowservice.v1 import DeleteNexusOperationExecutionRequest -from temporalio.client import CloudOperationsClient +from temporalio.client import CloudOperationsClient, NexusOperationFailureError from temporalio.service import RPCError, RPCStatusCode from temporalio.testing import WorkflowEnvironment @@ -109,29 +110,23 @@ async def wait_for_endpoint_readiness( self, env: WorkflowEnvironment, endpoint_name: str ) -> None: deadline = time.monotonic() + 10 * 60 - operation_id = f"cloud-nexus-readiness-{uuid.uuid4()}" nexus_client = env.client.create_nexus_client( CloudNexusEndpointReadinessService, endpoint_name ) # Cloud reports endpoint activation before the Workflow Service can always - # resolve it. A successful disposable start request verifies that propagation. + # resolve it. Waiting for the disposable operation's terminal state verifies + # routing, which happens asynchronously after its start request is accepted. attempts = 0 while True: attempts += 1 + operation_id = f"cloud-nexus-readiness-{uuid.uuid4()}" try: - await nexus_client.start_operation( + operation = await nexus_client.start_operation( CloudNexusEndpointReadinessService.ready, None, id=operation_id, + schedule_to_close_timeout=timedelta(seconds=5), ) - logger.info( - "Cloud Nexus endpoint %s accepted readiness operation %s " - "after %d attempt(s)", - endpoint_name, - operation_id, - attempts, - ) - break except RPCError as err: if ( err.status != RPCStatusCode.NOT_FOUND @@ -151,17 +146,45 @@ async def wait_for_endpoint_readiness( operation_id, ) await asyncio.sleep(1) - logger.info( - "Deleting Cloud Nexus readiness operation %s for endpoint %s", - operation_id, - endpoint_name, - ) - await env.client.workflow_service.delete_nexus_operation_execution( - DeleteNexusOperationExecutionRequest( - namespace=env.client.namespace, - operation_id=operation_id, + continue + try: + await operation.result() + except NexusOperationFailureError as err: + if str(err.cause) == "nexus endpoint not found": + if time.monotonic() >= deadline: + raise TimeoutError( + f"Timed out waiting for Cloud Nexus endpoint {endpoint_name} " + "to become available" + ) from err + if attempts == 1: + logger.info( + "Cloud Nexus endpoint %s is not yet routable; retrying " + "readiness operation %s", + endpoint_name, + operation_id, + ) + await asyncio.sleep(1) + continue + finally: + logger.info( + "Deleting Cloud Nexus readiness operation %s for endpoint %s", + operation_id, + endpoint_name, + ) + await env.client.workflow_service.delete_nexus_operation_execution( + DeleteNexusOperationExecutionRequest( + namespace=env.client.namespace, + operation_id=operation_id, + ) + ) + logger.info( + "Cloud Nexus endpoint %s completed readiness operation %s after " + "%d attempt(s)", + endpoint_name, + operation_id, + attempts, ) - ) + break @pytest_asyncio.fixture(scope="session") # type: ignore[reportUntypedFunctionDecorator] From e60cefb9f613859d59eeacb87bf76b24a4156c19 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Tue, 18 Aug 2026 17:17:01 -0700 Subject: [PATCH 11/19] test: retry unregistered Cloud Nexus endpoints --- tests/nexus/conftest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 8332af3ac..9497ed1c0 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -150,7 +150,10 @@ async def wait_for_endpoint_readiness( try: await operation.result() except NexusOperationFailureError as err: - if str(err.cause) == "nexus endpoint not found": + if str(err.cause) in { + "endpoint not registered", + "nexus endpoint not found", + }: if time.monotonic() >= deadline: raise TimeoutError( f"Timed out waiting for Cloud Nexus endpoint {endpoint_name} " From 04fbe94058e677dc92dd8703b4e0121445c2e291 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 19 Aug 2026 09:51:40 -0700 Subject: [PATCH 12/19] Revert "test: retry unregistered Cloud Nexus endpoints" This reverts commit e60cefb9f613859d59eeacb87bf76b24a4156c19. --- tests/nexus/conftest.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 9497ed1c0..8332af3ac 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -150,10 +150,7 @@ async def wait_for_endpoint_readiness( try: await operation.result() except NexusOperationFailureError as err: - if str(err.cause) in { - "endpoint not registered", - "nexus endpoint not found", - }: + if str(err.cause) == "nexus endpoint not found": if time.monotonic() >= deadline: raise TimeoutError( f"Timed out waiting for Cloud Nexus endpoint {endpoint_name} " From de8bbc66bc6977e45999ad45ebbed01152b887c4 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 19 Aug 2026 09:51:40 -0700 Subject: [PATCH 13/19] Revert "test: wait for Cloud Nexus endpoint routing" This reverts commit fa5248daeda52ae62d24dbba2bcd181ccf642d5b. --- tests/nexus/conftest.py | 67 ++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 45 deletions(-) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 8332af3ac..0fe250e89 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -5,7 +5,6 @@ import uuid from collections.abc import AsyncGenerator from dataclasses import dataclass -from datetime import timedelta import nexusrpc import pytest @@ -29,7 +28,7 @@ from temporalio.api.cloud.operation.v1 import AsyncOperation from temporalio.api.cloud.resource.v1 import ResourceState from temporalio.api.workflowservice.v1 import DeleteNexusOperationExecutionRequest -from temporalio.client import CloudOperationsClient, NexusOperationFailureError +from temporalio.client import CloudOperationsClient from temporalio.service import RPCError, RPCStatusCode from temporalio.testing import WorkflowEnvironment @@ -110,23 +109,29 @@ async def wait_for_endpoint_readiness( self, env: WorkflowEnvironment, endpoint_name: str ) -> None: deadline = time.monotonic() + 10 * 60 + operation_id = f"cloud-nexus-readiness-{uuid.uuid4()}" nexus_client = env.client.create_nexus_client( CloudNexusEndpointReadinessService, endpoint_name ) # Cloud reports endpoint activation before the Workflow Service can always - # resolve it. Waiting for the disposable operation's terminal state verifies - # routing, which happens asynchronously after its start request is accepted. + # resolve it. A successful disposable start request verifies that propagation. attempts = 0 while True: attempts += 1 - operation_id = f"cloud-nexus-readiness-{uuid.uuid4()}" try: - operation = await nexus_client.start_operation( + await nexus_client.start_operation( CloudNexusEndpointReadinessService.ready, None, id=operation_id, - schedule_to_close_timeout=timedelta(seconds=5), ) + logger.info( + "Cloud Nexus endpoint %s accepted readiness operation %s " + "after %d attempt(s)", + endpoint_name, + operation_id, + attempts, + ) + break except RPCError as err: if ( err.status != RPCStatusCode.NOT_FOUND @@ -146,45 +151,17 @@ async def wait_for_endpoint_readiness( operation_id, ) await asyncio.sleep(1) - continue - try: - await operation.result() - except NexusOperationFailureError as err: - if str(err.cause) == "nexus endpoint not found": - if time.monotonic() >= deadline: - raise TimeoutError( - f"Timed out waiting for Cloud Nexus endpoint {endpoint_name} " - "to become available" - ) from err - if attempts == 1: - logger.info( - "Cloud Nexus endpoint %s is not yet routable; retrying " - "readiness operation %s", - endpoint_name, - operation_id, - ) - await asyncio.sleep(1) - continue - finally: - logger.info( - "Deleting Cloud Nexus readiness operation %s for endpoint %s", - operation_id, - endpoint_name, - ) - await env.client.workflow_service.delete_nexus_operation_execution( - DeleteNexusOperationExecutionRequest( - namespace=env.client.namespace, - operation_id=operation_id, - ) - ) - logger.info( - "Cloud Nexus endpoint %s completed readiness operation %s after " - "%d attempt(s)", - endpoint_name, - operation_id, - attempts, + logger.info( + "Deleting Cloud Nexus readiness operation %s for endpoint %s", + operation_id, + endpoint_name, + ) + await env.client.workflow_service.delete_nexus_operation_execution( + DeleteNexusOperationExecutionRequest( + namespace=env.client.namespace, + operation_id=operation_id, ) - break + ) @pytest_asyncio.fixture(scope="session") # type: ignore[reportUntypedFunctionDecorator] From b27b8c36f37beec7ceb6253b0e3aca02d19894d6 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 19 Aug 2026 10:04:54 -0700 Subject: [PATCH 14/19] Reapply "test: wait for Cloud Nexus endpoint routing" This reverts commit de8bbc66bc6977e45999ad45ebbed01152b887c4. --- tests/nexus/conftest.py | 67 +++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 0fe250e89..8332af3ac 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -5,6 +5,7 @@ import uuid from collections.abc import AsyncGenerator from dataclasses import dataclass +from datetime import timedelta import nexusrpc import pytest @@ -28,7 +29,7 @@ from temporalio.api.cloud.operation.v1 import AsyncOperation from temporalio.api.cloud.resource.v1 import ResourceState from temporalio.api.workflowservice.v1 import DeleteNexusOperationExecutionRequest -from temporalio.client import CloudOperationsClient +from temporalio.client import CloudOperationsClient, NexusOperationFailureError from temporalio.service import RPCError, RPCStatusCode from temporalio.testing import WorkflowEnvironment @@ -109,29 +110,23 @@ async def wait_for_endpoint_readiness( self, env: WorkflowEnvironment, endpoint_name: str ) -> None: deadline = time.monotonic() + 10 * 60 - operation_id = f"cloud-nexus-readiness-{uuid.uuid4()}" nexus_client = env.client.create_nexus_client( CloudNexusEndpointReadinessService, endpoint_name ) # Cloud reports endpoint activation before the Workflow Service can always - # resolve it. A successful disposable start request verifies that propagation. + # resolve it. Waiting for the disposable operation's terminal state verifies + # routing, which happens asynchronously after its start request is accepted. attempts = 0 while True: attempts += 1 + operation_id = f"cloud-nexus-readiness-{uuid.uuid4()}" try: - await nexus_client.start_operation( + operation = await nexus_client.start_operation( CloudNexusEndpointReadinessService.ready, None, id=operation_id, + schedule_to_close_timeout=timedelta(seconds=5), ) - logger.info( - "Cloud Nexus endpoint %s accepted readiness operation %s " - "after %d attempt(s)", - endpoint_name, - operation_id, - attempts, - ) - break except RPCError as err: if ( err.status != RPCStatusCode.NOT_FOUND @@ -151,17 +146,45 @@ async def wait_for_endpoint_readiness( operation_id, ) await asyncio.sleep(1) - logger.info( - "Deleting Cloud Nexus readiness operation %s for endpoint %s", - operation_id, - endpoint_name, - ) - await env.client.workflow_service.delete_nexus_operation_execution( - DeleteNexusOperationExecutionRequest( - namespace=env.client.namespace, - operation_id=operation_id, + continue + try: + await operation.result() + except NexusOperationFailureError as err: + if str(err.cause) == "nexus endpoint not found": + if time.monotonic() >= deadline: + raise TimeoutError( + f"Timed out waiting for Cloud Nexus endpoint {endpoint_name} " + "to become available" + ) from err + if attempts == 1: + logger.info( + "Cloud Nexus endpoint %s is not yet routable; retrying " + "readiness operation %s", + endpoint_name, + operation_id, + ) + await asyncio.sleep(1) + continue + finally: + logger.info( + "Deleting Cloud Nexus readiness operation %s for endpoint %s", + operation_id, + endpoint_name, + ) + await env.client.workflow_service.delete_nexus_operation_execution( + DeleteNexusOperationExecutionRequest( + namespace=env.client.namespace, + operation_id=operation_id, + ) + ) + logger.info( + "Cloud Nexus endpoint %s completed readiness operation %s after " + "%d attempt(s)", + endpoint_name, + operation_id, + attempts, ) - ) + break @pytest_asyncio.fixture(scope="session") # type: ignore[reportUntypedFunctionDecorator] From b5a344a2129d328a7e35ce4ea70bd31136f8725f Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 19 Aug 2026 10:04:54 -0700 Subject: [PATCH 15/19] Reapply "test: retry unregistered Cloud Nexus endpoints" This reverts commit 04fbe94058e677dc92dd8703b4e0121445c2e291. --- tests/nexus/conftest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 8332af3ac..9497ed1c0 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -150,7 +150,10 @@ async def wait_for_endpoint_readiness( try: await operation.result() except NexusOperationFailureError as err: - if str(err.cause) == "nexus endpoint not found": + if str(err.cause) in { + "endpoint not registered", + "nexus endpoint not found", + }: if time.monotonic() >= deadline: raise TimeoutError( f"Timed out waiting for Cloud Nexus endpoint {endpoint_name} " From e51228b0df7829b2f10441c9c7b3433f97e0f5bf Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 19 Aug 2026 10:05:35 -0700 Subject: [PATCH 16/19] test: log pending Cloud Nexus readiness operations --- tests/nexus/conftest.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 9497ed1c0..854cc956c 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -148,6 +148,11 @@ async def wait_for_endpoint_readiness( await asyncio.sleep(1) continue try: + print( + "Waiting for Cloud Nexus endpoint " + f"{endpoint_name} readiness operation {operation_id}", + flush=True, + ) await operation.result() except NexusOperationFailureError as err: if str(err.cause) in { From 82fcfbff21024f5573608cd484430709a50b640f Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 19 Aug 2026 10:14:44 -0700 Subject: [PATCH 17/19] test: retain Cloud Nexus readiness operations --- tests/nexus/conftest.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 854cc956c..2bd75ae72 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -28,7 +28,6 @@ ) from temporalio.api.cloud.operation.v1 import AsyncOperation from temporalio.api.cloud.resource.v1 import ResourceState -from temporalio.api.workflowservice.v1 import DeleteNexusOperationExecutionRequest from temporalio.client import CloudOperationsClient, NexusOperationFailureError from temporalio.service import RPCError, RPCStatusCode from temporalio.testing import WorkflowEnvironment @@ -173,18 +172,6 @@ async def wait_for_endpoint_readiness( ) await asyncio.sleep(1) continue - finally: - logger.info( - "Deleting Cloud Nexus readiness operation %s for endpoint %s", - operation_id, - endpoint_name, - ) - await env.client.workflow_service.delete_nexus_operation_execution( - DeleteNexusOperationExecutionRequest( - namespace=env.client.namespace, - operation_id=operation_id, - ) - ) logger.info( "Cloud Nexus endpoint %s completed readiness operation %s after " "%d attempt(s)", From abd72ae0e97461aff7c0baddbd29c665ac995b27 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 19 Aug 2026 10:39:37 -0700 Subject: [PATCH 18/19] test: prepare Nexus endpoints before workers --- tests/nexus/conftest.py | 176 +++++++++--------- ...ynamic_creation_of_user_handler_classes.py | 4 +- .../nexus/test_signal_link_propagation_e2e.py | 20 +- tests/nexus/test_standalone_operations.py | 139 +++++++------- tests/nexus/test_temporal_extstore.py | 33 +++- tests/nexus/test_temporal_operation.py | 119 +++++------- .../test_use_existing_conflict_policy.py | 7 +- tests/nexus/test_workflow_caller.py | 111 +++++------ ...test_workflow_caller_cancellation_types.py | 8 +- ...llation_types_when_cancel_handler_fails.py | 8 +- .../test_workflow_caller_error_chains.py | 10 +- tests/nexus/test_workflow_caller_errors.py | 68 ++----- tests/nexus/test_workflow_run_operation.py | 13 +- 13 files changed, 318 insertions(+), 398 deletions(-) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index 2bd75ae72..d4437c116 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -10,6 +10,7 @@ import nexusrpc import pytest import pytest_asyncio +from nexusrpc.handler import StartOperationContext, service_handler, sync_operation from temporalio.api.cloud.cloudservice.v1 import ( CreateNexusEndpointRequest, @@ -31,15 +32,12 @@ from temporalio.client import CloudOperationsClient, NexusOperationFailureError from temporalio.service import RPCError, RPCStatusCode from temporalio.testing import WorkflowEnvironment +from temporalio.worker import Worker +from tests.helpers.nexus import make_nexus_endpoint_name logger = logging.getLogger(__name__) -@nexusrpc.service -class CloudNexusEndpointReadinessService: - ready: nexusrpc.Operation[None, None] - - @dataclass class _CloudNexusEndpointClient: client: CloudOperationsClient @@ -105,81 +103,23 @@ async def wait_for_endpoint(self, endpoint_id: str) -> Endpoint: ) await asyncio.sleep(1) - async def wait_for_endpoint_readiness( - self, env: WorkflowEnvironment, endpoint_name: str - ) -> None: - deadline = time.monotonic() + 10 * 60 - nexus_client = env.client.create_nexus_client( - CloudNexusEndpointReadinessService, endpoint_name - ) - # Cloud reports endpoint activation before the Workflow Service can always - # resolve it. Waiting for the disposable operation's terminal state verifies - # routing, which happens asynchronously after its start request is accepted. - attempts = 0 - while True: - attempts += 1 - operation_id = f"cloud-nexus-readiness-{uuid.uuid4()}" - try: - operation = await nexus_client.start_operation( - CloudNexusEndpointReadinessService.ready, - None, - id=operation_id, - schedule_to_close_timeout=timedelta(seconds=5), - ) - except RPCError as err: - if ( - err.status != RPCStatusCode.NOT_FOUND - or str(err) != "endpoint not found" - ): - raise - if time.monotonic() >= deadline: - raise TimeoutError( - f"Timed out waiting for Cloud Nexus endpoint {endpoint_name} " - "to become available" - ) from err - if attempts == 1: - logger.info( - "Cloud Nexus endpoint %s is not yet resolvable; retrying " - "readiness operation %s", - endpoint_name, - operation_id, - ) - await asyncio.sleep(1) - continue - try: - print( - "Waiting for Cloud Nexus endpoint " - f"{endpoint_name} readiness operation {operation_id}", - flush=True, - ) - await operation.result() - except NexusOperationFailureError as err: - if str(err.cause) in { - "endpoint not registered", - "nexus endpoint not found", - }: - if time.monotonic() >= deadline: - raise TimeoutError( - f"Timed out waiting for Cloud Nexus endpoint {endpoint_name} " - "to become available" - ) from err - if attempts == 1: - logger.info( - "Cloud Nexus endpoint %s is not yet routable; retrying " - "readiness operation %s", - endpoint_name, - operation_id, - ) - await asyncio.sleep(1) - continue - logger.info( - "Cloud Nexus endpoint %s completed readiness operation %s after " - "%d attempt(s)", - endpoint_name, - operation_id, - attempts, - ) - break + +@dataclass(frozen=True) +class NexusEndpoint: + name: str + task_queue: str + + +@nexusrpc.service +class _EndpointReadinessService: + ready: nexusrpc.Operation[None, None] + + +@service_handler(service=_EndpointReadinessService) +class _EndpointReadinessHandler: + @sync_operation + async def ready(self, _ctx: StartOperationContext, _input: None) -> None: + return None @pytest_asyncio.fixture(scope="session") # type: ignore[reportUntypedFunctionDecorator] @@ -250,14 +190,8 @@ async def create_nexus_endpoint(endpoint_name: str, task_queue: str) -> Endpoint ) endpoints[-1] = endpoint logger.info( - "Cloud Nexus endpoint %s (%s) is active; checking Workflow Service readiness", - endpoint_name, - endpoint.id, + "Cloud Nexus endpoint %s (%s) is active", endpoint_name, endpoint.id ) - await cloud_nexus_endpoint_client.wait_for_endpoint_readiness( - env, endpoint_name - ) - logger.info("Cloud Nexus endpoint %s is ready for the test", endpoint_name) return endpoint monkeypatch.setattr(env, "create_nexus_endpoint", create_nexus_endpoint) @@ -277,3 +211,71 @@ async def create_nexus_endpoint(endpoint_name: str, task_queue: str) -> Endpoint await cloud_nexus_endpoint_client.wait_for_operation( response.async_operation ) + + +@pytest_asyncio.fixture +async def nexus_endpoint( + cloud_nexus_endpoint_client: _CloudNexusEndpointClient | None, + env: WorkflowEnvironment, +) -> NexusEndpoint: + """Create and, on Cloud, route-check a Nexus endpoint before a test worker.""" + if env.supports_time_skipping: + pytest.skip("Nexus tests don't work with time-skipping server") + + task_queue = str(uuid.uuid4()) + endpoint = NexusEndpoint( + name=make_nexus_endpoint_name(task_queue), task_queue=task_queue + ) + await env.create_nexus_endpoint(endpoint.name, endpoint.task_queue) + + if cloud_nexus_endpoint_client is None: + return endpoint + + deadline = time.monotonic() + 10 * 60 + nexus_client = env.client.create_nexus_client( + _EndpointReadinessService, endpoint.name + ) + attempt = 0 + async with Worker( + env.client, + task_queue=endpoint.task_queue, + nexus_service_handlers=[_EndpointReadinessHandler()], + ): + while True: + attempt += 1 + try: + operation = await nexus_client.start_operation( + _EndpointReadinessService.ready, + None, + id=f"cloud-nexus-readiness-{uuid.uuid4()}", + schedule_to_close_timeout=timedelta(seconds=10), + ) + await asyncio.wait_for(operation.result(), timeout=15) + break + except RPCError as err: + retryable = ( + err.status == RPCStatusCode.NOT_FOUND + and str(err) == "endpoint not found" + ) + except NexusOperationFailureError as err: + retryable = str(err.cause) in { + "endpoint not registered", + "nexus endpoint not found", + } + except TimeoutError: + retryable = True + if not retryable: + raise + if time.monotonic() >= deadline: + raise TimeoutError( + f"Timed out waiting for Cloud Nexus endpoint {endpoint.name} " + "to route operations" + ) + logger.info( + "Cloud Nexus endpoint %s did not route readiness operation on " + "attempt %d; retrying", + endpoint.name, + attempt, + ) + await asyncio.sleep(1) + return endpoint diff --git a/tests/nexus/test_dynamic_creation_of_user_handler_classes.py b/tests/nexus/test_dynamic_creation_of_user_handler_classes.py index f7306a46b..31d85fef1 100644 --- a/tests/nexus/test_dynamic_creation_of_user_handler_classes.py +++ b/tests/nexus/test_dynamic_creation_of_user_handler_classes.py @@ -52,11 +52,12 @@ async def run(self, input: int, task_queue: str) -> int: async def test_run_nexus_service_from_programmatically_created_service_handler( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue service_handler = nexusrpc.handler._core.ServiceHandler( service=nexusrpc.ServiceDefinition( @@ -75,7 +76,6 @@ async def test_run_nexus_service_from_programmatically_created_service_handler( }, ) - await env.create_nexus_endpoint(make_nexus_endpoint_name(task_queue), task_queue) async with Worker( client, task_queue=task_queue, diff --git a/tests/nexus/test_signal_link_propagation_e2e.py b/tests/nexus/test_signal_link_propagation_e2e.py index e489ad8a7..a92f0b738 100644 --- a/tests/nexus/test_signal_link_propagation_e2e.py +++ b/tests/nexus/test_signal_link_propagation_e2e.py @@ -266,12 +266,12 @@ def _assert_backlink( async def test_sync_signal_operation_links( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ) -> None: if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) - await env.create_nexus_endpoint(make_nexus_endpoint_name(task_queue), task_queue) + task_queue = nexus_endpoint.task_queue callee_id = f"callee-{uuid.uuid4()}" caller_id = f"caller-{uuid.uuid4()}" @@ -315,12 +315,12 @@ async def test_sync_signal_operation_links( async def test_async_signal_operation_links( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ) -> None: if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) - await env.create_nexus_endpoint(make_nexus_endpoint_name(task_queue), task_queue) + task_queue = nexus_endpoint.task_queue callee_id = f"async-callee-{uuid.uuid4()}" caller_id = f"async-caller-{uuid.uuid4()}" @@ -400,12 +400,12 @@ def _assert_standalone_forward_link( async def test_standalone_sync_signal_operation_links( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ) -> None: if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) - await env.create_nexus_endpoint(make_nexus_endpoint_name(task_queue), task_queue) + task_queue = nexus_endpoint.task_queue callee_id = f"standalone-callee-{uuid.uuid4()}" operation_id = f"standalone-op-{uuid.uuid4()}" @@ -439,12 +439,12 @@ async def test_standalone_sync_signal_operation_links( async def test_standalone_async_signal_operation_links( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ) -> None: if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) - await env.create_nexus_endpoint(make_nexus_endpoint_name(task_queue), task_queue) + task_queue = nexus_endpoint.task_queue callee_id = f"standalone-async-callee-{uuid.uuid4()}" operation_id = f"standalone-async-op-{uuid.uuid4()}" @@ -497,12 +497,12 @@ async def _callee_result() -> str: async def test_start_from_handler_attaches_on_conflict_options( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ) -> None: if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) - await env.create_nexus_endpoint(make_nexus_endpoint_name(task_queue), task_queue) + task_queue = nexus_endpoint.task_queue callee_id = f"conflict-callee-{uuid.uuid4()}" operation_id = f"conflict-op-{uuid.uuid4()}" diff --git a/tests/nexus/test_standalone_operations.py b/tests/nexus/test_standalone_operations.py index c71337b20..bc543e8af 100644 --- a/tests/nexus/test_standalone_operations.py +++ b/tests/nexus/test_standalone_operations.py @@ -63,7 +63,6 @@ expected_nexus_operation_link, expected_workflow_event_link, links_from_workflow_execution_started_event, - make_nexus_endpoint_name, ) # --------------------------------------------------------------------------- @@ -194,7 +193,7 @@ async def raise_err( async def test_start_sync_operation_and_get_result( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): """Start a sync nexus operation, call handle.result(), verify return value.""" if env.supports_time_skipping: @@ -202,8 +201,8 @@ async def test_start_sync_operation_and_get_result( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( client, @@ -211,8 +210,6 @@ async def test_start_sync_operation_and_get_result( nexus_service_handlers=[StandaloneTestServiceHandler()], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) @@ -234,7 +231,7 @@ async def test_start_sync_operation_and_get_result( async def test_start_async_operation_and_poll_result( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): """Start a workflow_run operation, poll result, verify.""" if env.supports_time_skipping: @@ -242,8 +239,8 @@ async def test_start_async_operation_and_poll_result( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( client, @@ -251,8 +248,6 @@ async def test_start_async_operation_and_poll_result( nexus_service_handlers=[StandaloneTestServiceHandler()], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) @@ -268,7 +263,7 @@ async def test_start_async_operation_and_poll_result( async def test_started_workflow_has_link_to_standalone_nexus_operation( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): """Start a workflow_run operation and verify its workflow links back to the Nexus op.""" if env.supports_time_skipping: @@ -276,8 +271,8 @@ async def test_started_workflow_has_link_to_standalone_nexus_operation( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name service_handler = StandaloneTestServiceHandler() async with Worker( @@ -286,8 +281,6 @@ async def test_started_workflow_has_link_to_standalone_nexus_operation( nexus_service_handlers=[service_handler], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) @@ -324,15 +317,17 @@ async def test_started_workflow_has_link_to_standalone_nexus_operation( assert result.value == input_value -async def test_execute_operation(client: Client, env: WorkflowEnvironment): +async def test_execute_operation( + client: Client, env: WorkflowEnvironment, nexus_endpoint +): """Use execute_operation convenience method, verify it returns result directly.""" if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( client, @@ -340,8 +335,6 @@ async def test_execute_operation(client: Client, env: WorkflowEnvironment): nexus_service_handlers=[StandaloneTestServiceHandler()], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) @@ -358,7 +351,7 @@ async def test_execute_operation(client: Client, env: WorkflowEnvironment): async def test_execute_operation_named_service( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): """Verify that the name on the service decorator is respected by the standalone nexus client""" if env.supports_time_skipping: @@ -366,8 +359,8 @@ async def test_execute_operation_named_service( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( client, @@ -376,8 +369,6 @@ async def test_execute_operation_named_service( nexus_service_handlers=[StandaloneTestServiceHandler()], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - # Create client using the service that is uses the name "StandaloneTestService" nexus_client = client.create_nexus_client( service=NamedService, endpoint=endpoint_name @@ -394,15 +385,15 @@ async def test_execute_operation_named_service( assert result.value == "execute" -async def test_errors(client: Client, env: WorkflowEnvironment): +async def test_errors(client: Client, env: WorkflowEnvironment, nexus_endpoint): """Execute operations that raise errors""" if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( client, @@ -410,8 +401,6 @@ async def test_errors(client: Client, env: WorkflowEnvironment): nexus_service_handlers=[StandaloneTestServiceHandler()], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) @@ -454,15 +443,17 @@ async def test_errors(client: Client, env: WorkflowEnvironment): assert isinstance(err.value.__cause__.__cause__, ApplicationError) -async def test_describe_operation(client: Client, env: WorkflowEnvironment): +async def test_describe_operation( + client: Client, env: WorkflowEnvironment, nexus_endpoint +): """Start op, get result first, then describe, verify fields populated.""" if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( client, @@ -470,8 +461,6 @@ async def test_describe_operation(client: Client, env: WorkflowEnvironment): nexus_service_handlers=[StandaloneTestServiceHandler()], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) @@ -501,7 +490,9 @@ async def test_describe_operation(client: Client, env: WorkflowEnvironment): assert summary == StandaloneTestService.echo_async.name -async def test_cancel_operation(client: Client, env: WorkflowEnvironment): +async def test_cancel_operation( + client: Client, env: WorkflowEnvironment, nexus_endpoint +): """Start blocking async op, cancel it, verify awaiting result raises NexusOperationFailureError from a CancelledError. """ @@ -510,8 +501,8 @@ async def test_cancel_operation(client: Client, env: WorkflowEnvironment): "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( client, @@ -519,8 +510,6 @@ async def test_cancel_operation(client: Client, env: WorkflowEnvironment): nexus_service_handlers=[StandaloneTestServiceHandler()], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) @@ -543,7 +532,9 @@ async def test_cancel_operation(client: Client, env: WorkflowEnvironment): assert isinstance(err.value.__cause__, CancelledError) -async def test_terminate_operation(client: Client, env: WorkflowEnvironment): +async def test_terminate_operation( + client: Client, env: WorkflowEnvironment, nexus_endpoint +): """Start blocking async op, terminate it, verify awaiting the result raises NexusOperationFailureError from a TerminatedError. """ @@ -552,8 +543,8 @@ async def test_terminate_operation(client: Client, env: WorkflowEnvironment): "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( client, @@ -561,8 +552,6 @@ async def test_terminate_operation(client: Client, env: WorkflowEnvironment): nexus_service_handlers=[StandaloneTestServiceHandler()], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) @@ -585,15 +574,17 @@ async def test_terminate_operation(client: Client, env: WorkflowEnvironment): assert isinstance(err.value.__cause__, TerminatedError) -async def test_list_operations(client: Client, env: WorkflowEnvironment): +async def test_list_operations( + client: Client, env: WorkflowEnvironment, nexus_endpoint +): """Start multiple ops, list them, verify iteration yields correct results.""" if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( client, @@ -601,8 +592,6 @@ async def test_list_operations(client: Client, env: WorkflowEnvironment): nexus_service_handlers=[StandaloneTestServiceHandler()], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) @@ -633,15 +622,17 @@ async def check_ids() -> None: await assert_eventually(check_ids) -async def test_count_operations(client: Client, env: WorkflowEnvironment): +async def test_count_operations( + client: Client, env: WorkflowEnvironment, nexus_endpoint +): """Start ops, count, verify count.""" if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( client, @@ -649,8 +640,6 @@ async def test_count_operations(client: Client, env: WorkflowEnvironment): nexus_service_handlers=[StandaloneTestServiceHandler()], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) @@ -676,15 +665,17 @@ async def check_count() -> None: await assert_eventually(check_count) -async def test_get_nexus_operation_handle(client: Client, env: WorkflowEnvironment): +async def test_get_nexus_operation_handle( + client: Client, env: WorkflowEnvironment, nexus_endpoint +): """Start op, get result, then get handle by ID and get result again.""" if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( client, @@ -692,8 +683,6 @@ async def test_get_nexus_operation_handle(client: Client, env: WorkflowEnvironme nexus_service_handlers=[StandaloneTestServiceHandler()], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) @@ -722,7 +711,7 @@ async def test_get_nexus_operation_handle(client: Client, env: WorkflowEnvironme async def test_id_conflict_policy_use_existing( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): """Start op, re-start with USE_EXISTING, verify same op/run ID and expected result""" if env.supports_time_skipping: @@ -730,8 +719,8 @@ async def test_id_conflict_policy_use_existing( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name service_handler = StandaloneTestServiceHandler() @@ -741,8 +730,6 @@ async def test_id_conflict_policy_use_existing( nexus_service_handlers=[service_handler], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) @@ -788,15 +775,17 @@ async def test_id_conflict_policy_use_existing( assert first_result.value == second_result.value -async def test_id_conflict_policy_fail(client: Client, env: WorkflowEnvironment): +async def test_id_conflict_policy_fail( + client: Client, env: WorkflowEnvironment, nexus_endpoint +): """Start op, re-start with FAIL, verify raises NexusOperationAlreadyStartedError.""" if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( client, @@ -804,8 +793,6 @@ async def test_id_conflict_policy_fail(client: Client, env: WorkflowEnvironment) nexus_service_handlers=[StandaloneTestServiceHandler()], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) @@ -902,15 +889,17 @@ def intercept_client(self, next: OutboundInterceptor) -> OutboundInterceptor: return _RecordingOutboundInterceptor(next, self) -async def test_interceptor_receives_inputs(client: Client, env: WorkflowEnvironment): +async def test_interceptor_receives_inputs( + client: Client, env: WorkflowEnvironment, nexus_endpoint +): """Custom OutboundInterceptor records calls, verify correct input types.""" if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name interceptor = _RecordingInterceptor() intercepted_client = Client( @@ -925,8 +914,6 @@ async def test_interceptor_receives_inputs(client: Client, env: WorkflowEnvironm nexus_service_handlers=[StandaloneTestServiceHandler()], workflows=[EchoHandlerWorkflow, BlockingHandlerWorkflow], ): - await env.create_nexus_endpoint(endpoint_name, task_queue) - nexus_client = intercepted_client.create_nexus_client( service=StandaloneTestService, endpoint=endpoint_name ) diff --git a/tests/nexus/test_temporal_extstore.py b/tests/nexus/test_temporal_extstore.py index 2afbac44c..087d647ee 100644 --- a/tests/nexus/test_temporal_extstore.py +++ b/tests/nexus/test_temporal_extstore.py @@ -150,9 +150,9 @@ async def _run_caller( env: WorkflowEnvironment, driver: InMemoryTestDriver, workflow_run: MethodAsyncSingleParam[Any, str, int], + task_queue: str, ) -> int: client = _client_with_extstore(env, driver) - task_queue = str(uuid.uuid4()) async with Worker( client, task_queue=task_queue, @@ -160,9 +160,6 @@ async def _run_caller( nexus_service_handlers=[ExtStoreNexusServiceHandler()], workflow_runner=UnsandboxedWorkflowRunner(), ): - await env.create_nexus_endpoint( - make_nexus_endpoint_name(task_queue), task_queue - ) return await client.execute_workflow( workflow_run, task_queue, @@ -181,13 +178,17 @@ def _cause_chain(err: BaseException) -> list[BaseException]: return chain -async def test_nexus_operation_input_offloaded_and_retrieved(env: WorkflowEnvironment): +async def test_nexus_operation_input_offloaded_and_retrieved( + env: WorkflowEnvironment, nexus_endpoint +): """The offloaded operation input is retrieved before the handler runs.""" if env.supports_time_skipping: pytest.skip("Nexus tests don't work with the Java test server") driver = InMemoryTestDriver() - result = await _run_caller(env, driver, SizeOpCallerWorkflow.run) + result = await _run_caller( + env, driver, SizeOpCallerWorkflow.run, nexus_endpoint.task_queue + ) assert result == PAYLOAD_SIZE assert driver._store_calls >= 1 @@ -196,13 +197,16 @@ async def test_nexus_operation_input_offloaded_and_retrieved(env: WorkflowEnviro async def test_nexus_operation_sync_result_offloaded_and_retrieved( env: WorkflowEnvironment, + nexus_endpoint, ): """A large synchronous result is offloaded and retrieved by the caller.""" if env.supports_time_skipping: pytest.skip("Nexus tests don't work with the Java test server") driver = InMemoryTestDriver() - result = await _run_caller(env, driver, BigResultOpCallerWorkflow.run) + result = await _run_caller( + env, driver, BigResultOpCallerWorkflow.run, nexus_endpoint.task_queue + ) assert result == PAYLOAD_SIZE assert driver._store_calls >= 1 @@ -211,13 +215,16 @@ async def test_nexus_operation_sync_result_offloaded_and_retrieved( async def test_nexus_operation_transient_retrieve_failure_recovers( env: WorkflowEnvironment, + nexus_endpoint, ): """A transient retrieve failure fails the task retryably; it then recovers.""" if env.supports_time_skipping: pytest.skip("Nexus tests don't work with the Java test server") driver = TransientFailureDriver(fail_first_retrieve=True) - result = await _run_caller(env, driver, SizeOpCallerWorkflow.run) + result = await _run_caller( + env, driver, SizeOpCallerWorkflow.run, nexus_endpoint.task_queue + ) assert result == PAYLOAD_SIZE assert driver.retrieve_attempts >= 2 @@ -225,13 +232,16 @@ async def test_nexus_operation_transient_retrieve_failure_recovers( async def test_nexus_operation_transient_store_failure_recovers( env: WorkflowEnvironment, + nexus_endpoint, ): """A transient store failure fails the task retryably; it then recovers.""" if env.supports_time_skipping: pytest.skip("Nexus tests don't work with the Java test server") driver = TransientFailureDriver(fail_first_store=True) - result = await _run_caller(env, driver, BigResultOpCallerWorkflow.run) + result = await _run_caller( + env, driver, BigResultOpCallerWorkflow.run, nexus_endpoint.task_queue + ) assert result == PAYLOAD_SIZE assert driver.store_attempts >= 2 @@ -250,6 +260,7 @@ async def store( async def test_nexus_operation_store_failure_fails_operation( env: WorkflowEnvironment, + nexus_endpoint, ): """A non-retryable store failure fails the operation and surfaces the driver error to the caller (deterministically, with no retries).""" @@ -258,7 +269,9 @@ async def test_nexus_operation_store_failure_fails_operation( driver = PermanentFailStoreDriver() with pytest.raises(WorkflowFailureError) as exc_info: - await _run_caller(env, driver, BigResultOpCallerWorkflow.run) + await _run_caller( + env, driver, BigResultOpCallerWorkflow.run, nexus_endpoint.task_queue + ) causes = _cause_chain(exc_info.value) assert [type(c) for c in causes] == [ diff --git a/tests/nexus/test_temporal_operation.py b/tests/nexus/test_temporal_operation.py index 45a6fb1f0..29ce76041 100644 --- a/tests/nexus/test_temporal_operation.py +++ b/tests/nexus/test_temporal_operation.py @@ -426,11 +426,9 @@ async def run(self, input: Input) -> str: async def test_temporal_operation_start_workflow( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue async with Worker( env.client, task_queue=task_queue, @@ -457,15 +455,13 @@ async def test_temporal_operation_start_workflow( async def test_temporal_operation_update_workflow( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ) -> None: if ( env.supports_time_skipping ): # time skipping server uses different dynamic configs pytest.skip("Update workflow tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue async with Worker( env.client, task_queue=task_queue, @@ -840,11 +836,9 @@ async def wait_operation_started(self): async def test_temporal_operation_cancel_workflow( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue async with Worker( env.client, task_queue=task_queue, @@ -875,16 +869,15 @@ async def test_temporal_operation_cancel_workflow( async def test_customized_temporal_operation_cancel_workflow( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name service_handler = TestServiceHandler() async with Worker( @@ -949,11 +942,9 @@ async def run(self, input: Input) -> str: async def test_temporal_operation_double_start_raises_handler_err( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue async with Worker( env.client, task_queue=task_queue, @@ -978,11 +969,9 @@ async def test_temporal_operation_double_start_raises_handler_err( async def test_temporal_operation_concurrent_start_raises_handler_err( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue async with Worker( env.client, task_queue=task_queue, @@ -1000,12 +989,10 @@ async def test_temporal_operation_concurrent_start_raises_handler_err( async def test_temporal_operation_failed_start_allows_retry( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) + task_queue = nexus_endpoint.task_queue conflict_id = f"failed-start-rollback-{uuid.uuid4()}" - await env.create_nexus_endpoint(endpoint_name, task_queue) async with Worker( env.client, task_queue=task_queue, @@ -1035,16 +1022,15 @@ async def test_temporal_operation_failed_start_allows_retry( async def test_temporal_operation_mixed_start_raises_handler_err( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( env.client, task_queue=task_queue, @@ -1079,10 +1065,10 @@ async def run(self, input: Input) -> str: return await client.execute_operation(TestService.sync_result, input) -async def test_temporal_operation_sync_result(client: Client, env: WorkflowEnvironment): - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) +async def test_temporal_operation_sync_result( + client: Client, env: WorkflowEnvironment, nexus_endpoint +): + task_queue = nexus_endpoint.task_queue async with Worker( env.client, task_queue=task_queue, @@ -1109,16 +1095,15 @@ async def test_temporal_operation_sync_result(client: Client, env: WorkflowEnvir async def test_temporal_operation_start_activity( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( env.client, task_queue=task_queue, @@ -1136,16 +1121,15 @@ async def test_temporal_operation_start_activity( async def test_temporal_operation_backing_activity_does_not_duplicate_links( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name activity_id = f"link-activity-{uuid.uuid4()}" @service_handler @@ -1197,16 +1181,15 @@ async def echo_activity( async def test_temporal_operation_start_activity_raises_error( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( env.client, task_queue=task_queue, @@ -1236,16 +1219,15 @@ async def test_temporal_operation_start_activity_raises_error( async def test_temporal_operation_cancel_activity( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( env.client, task_queue=task_queue, @@ -1273,16 +1255,15 @@ async def check_cancelled(): async def test_customized_temporal_operation_cancel_activity( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name service_handler = TestServiceHandler() async with Worker( @@ -1314,16 +1295,15 @@ async def check_cancelled(): async def test_temporal_operation_double_start_activity_raises_handler_err( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( env.client, task_queue=task_queue, @@ -1483,11 +1463,9 @@ async def run( ], ) async def test_temporal_operation_overloads( - client: Client, env: WorkflowEnvironment, op: str + client: Client, env: WorkflowEnvironment, op: str, nexus_endpoint ): - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue async with Worker( client, task_queue=task_queue, @@ -1512,11 +1490,9 @@ async def test_temporal_operation_overloads( async def test_temporal_operation_includes_token_in_callback( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue async with Worker( env.client, task_queue=task_queue, @@ -1595,15 +1571,14 @@ async def do_update(self, value: str) -> str: async def test_temporal_operation_includes_activity_token_in_callback( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip( "Standalone Nexus Operation tests don't work with time-skipping server" ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name @service_handler class ActivityTokenHandler: diff --git a/tests/nexus/test_use_existing_conflict_policy.py b/tests/nexus/test_use_existing_conflict_policy.py index e7cee9e1c..eebfe7e4b 100644 --- a/tests/nexus/test_use_existing_conflict_policy.py +++ b/tests/nexus/test_use_existing_conflict_policy.py @@ -88,9 +88,9 @@ async def nexus_operations_have_started(self) -> None: async def test_multiple_operation_invocations_can_connect_to_same_handler_workflow( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue workflow_id = str(uuid.uuid4()) async with Worker( @@ -99,9 +99,6 @@ async def test_multiple_operation_invocations_can_connect_to_same_handler_workfl workflows=[CallerWorkflow, HandlerWorkflow], task_queue=task_queue, ): - await env.create_nexus_endpoint( - make_nexus_endpoint_name(task_queue), task_queue - ) caller_handle = await client.start_workflow( CallerWorkflow.run, args=[ diff --git a/tests/nexus/test_workflow_caller.py b/tests/nexus/test_workflow_caller.py index 328637525..18124e312 100644 --- a/tests/nexus/test_workflow_caller.py +++ b/tests/nexus/test_workflow_caller.py @@ -663,8 +663,10 @@ async def run(self, input: WorkflowRunHeaderTestCallerWfInput) -> HeaderTestOutp # -async def test_sync_operation_happy_path(client: Client, env: WorkflowEnvironment): - task_queue = str(uuid.uuid4()) +async def test_sync_operation_happy_path( + client: Client, env: WorkflowEnvironment, nexus_endpoint +): + task_queue = nexus_endpoint.task_queue async with Worker( client, nexus_service_handlers=[ServiceImpl()], @@ -672,8 +674,6 @@ async def test_sync_operation_happy_path(client: Client, env: WorkflowEnvironmen task_queue=task_queue, workflow_failure_exception_types=[Exception], ): - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) wf_output = await client.execute_workflow( CallerWorkflow.run, args=[ @@ -722,16 +722,17 @@ async def run(self, task_queue: str) -> dict[str, str]: return await nexus_client.execute_operation(NexusInfoService.get_info, None) -async def test_nexus_info_includes_namespace(client: Client, env: WorkflowEnvironment): - task_queue = str(uuid.uuid4()) +async def test_nexus_info_includes_namespace( + client: Client, env: WorkflowEnvironment, nexus_endpoint +): + task_queue = nexus_endpoint.task_queue + endpoint_name = nexus_endpoint.name async with Worker( client, nexus_service_handlers=[NexusInfoService()], workflows=[NexusInfoCallerWorkflow], task_queue=task_queue, ): - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) result = await client.execute_workflow( NexusInfoCallerWorkflow.run, task_queue, @@ -746,9 +747,9 @@ async def test_nexus_info_includes_namespace(client: Client, env: WorkflowEnviro async def test_workflow_run_operation_happy_path( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue async with Worker( client, nexus_service_handlers=[ServiceImpl()], @@ -756,8 +757,6 @@ async def test_workflow_run_operation_happy_path( task_queue=task_queue, workflow_failure_exception_types=[Exception], ): - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) wf_output = await client.execute_workflow( CallerWorkflow.run, args=[ @@ -899,12 +898,13 @@ async def start_nexus_operation( async def test_start_operation_headers( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ): """Test headers from workflow and interceptors are propagated to start operation handler.""" if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue inbound_interceptor = HeaderModifyingNexusInterceptor() async with Worker( @@ -914,9 +914,6 @@ async def test_start_operation_headers( task_queue=task_queue, interceptors=[HeaderAddingOutboundInterceptor(), inbound_interceptor], ): - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) - workflow_headers = {"x-custom-from-workflow": "workflow-value"} result = await client.execute_workflow( HeaderTestCallerWorkflow.run, @@ -946,9 +943,10 @@ async def test_start_operation_headers( async def test_workflow_run_operation_headers( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ): """Test that headers are propagated to @workflow_run_operation handlers.""" - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue test_headers = {"x-custom-workflow-run": "workflow-run-value"} async with Worker( @@ -957,9 +955,6 @@ async def test_workflow_run_operation_headers( workflows=[WorkflowRunHeaderTestCallerWorkflow, HeaderEchoWorkflow], task_queue=task_queue, ): - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) - result = await client.execute_workflow( WorkflowRunHeaderTestCallerWorkflow.run, WorkflowRunHeaderTestCallerWfInput( @@ -976,12 +971,13 @@ async def test_workflow_run_operation_headers( async def test_cancel_operation_headers( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ): """Test headers from workflow and interceptor are propagated to cancel operation handler.""" if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue workflow_id = str(uuid.uuid4()) inbound_interceptor = HeaderModifyingNexusInterceptor() service_handler = HeaderTestServiceImpl() @@ -993,9 +989,6 @@ async def test_cancel_operation_headers( task_queue=task_queue, interceptors=[inbound_interceptor], ): - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) - workflow_headers = {"x-custom-cancel": "cancel-value"} await client.execute_workflow( CancelHeaderTestCallerWorkflow.run, @@ -1036,11 +1029,12 @@ async def test_sync_response( request_cancel: bool, op_definition_type: OpDefinitionType, caller_reference: CallerReference, + nexus_endpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue async with Worker( client, nexus_service_handlers=[ServiceImpl()], @@ -1048,8 +1042,6 @@ async def test_sync_response( task_queue=task_queue, workflow_failure_exception_types=[Exception], ): - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) caller_wf_handle = await client.start_workflow( CallerWorkflow.run, args=[ @@ -1110,11 +1102,12 @@ async def test_async_response( request_cancel: bool, op_definition_type: OpDefinitionType, caller_reference: CallerReference, + nexus_endpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue async with Worker( client, nexus_service_handlers=[ServiceImpl()], @@ -1123,7 +1116,6 @@ async def test_async_response( workflow_failure_exception_types=[Exception], ): caller_wf_handle, handler_wf_handle = await _start_wf_and_nexus_op( - env, client, task_queue, exception_in_operation_start, @@ -1199,7 +1191,6 @@ async def test_async_response( async def _start_wf_and_nexus_op( - env: WorkflowEnvironment, client: Client, task_queue: str, exception_in_operation_start: bool, @@ -1213,8 +1204,6 @@ async def _start_wf_and_nexus_op( """ Start the caller workflow and wait until the Nexus operation has started. """ - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) operation_workflow_id = str(uuid.uuid4()) # Start the caller workflow and wait until it confirms the Nexus operation has started. @@ -1274,11 +1263,12 @@ async def test_untyped_caller( op_definition_type: OpDefinitionType, caller_reference: CallerReference, response_type: ResponseType, + nexus_endpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue async with Worker( client, workflows=[UntypedCallerWorkflow, HandlerWorkflow], @@ -1299,8 +1289,6 @@ async def test_untyped_caller( op_definition_type=op_definition_type, exception_in_operation_start=exception_in_operation_start, ) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) caller_wf_handle = await client.start_workflow( UntypedCallerWorkflow.run, args=[ @@ -1430,7 +1418,7 @@ async def run( async def test_service_interface_and_implementation_names( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): # Note that: # - The caller can specify the service & operation via a reference to either the @@ -1445,7 +1433,7 @@ async def test_service_interface_and_implementation_names( # # This test checks that the request is routed to the expected service under a variety # of scenarios related to the above considerations. - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue async with Worker( client, nexus_service_handlers=[ @@ -1458,8 +1446,6 @@ async def test_service_interface_and_implementation_names( task_queue=task_queue, workflow_failure_exception_types=[Exception], ): - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) assert await client.execute_workflow( ServiceInterfaceAndImplCallerWorkflow.run, args=(CallerReference.INTERFACE, NameOverride.YES, task_queue), @@ -1559,11 +1545,12 @@ async def run(self, _input: str, task_queue: str) -> str: async def test_workflow_run_operation_can_execute_workflow_before_starting_backing_workflow( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue async with Worker( client, workflows=[ @@ -1575,8 +1562,6 @@ async def test_workflow_run_operation_can_execute_workflow_before_starting_backi ], task_queue=task_queue, ): - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) result = await client.execute_workflow( WorkflowCallingNexusOperationThatExecutesWorkflowBeforeStartingBackingWorkflow.run, args=("result-1", task_queue), @@ -1615,8 +1600,9 @@ async def run(self, input: str, task_queue: str) -> str: async def test_nexus_operation_summary( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ): - task_queue = f"task-queue-{uuid.uuid4()}" + task_queue = nexus_endpoint.task_queue async with Worker( client, workflows=[ExecuteNexusOperationWithSummaryWorkflow], @@ -1625,8 +1611,6 @@ async def test_nexus_operation_summary( ], task_queue=task_queue, ): - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) wf_id = f"wf-{uuid.uuid4()}" handle = await client.start_workflow( ExecuteNexusOperationWithSummaryWorkflow.run, @@ -1896,9 +1880,9 @@ async def run(self, op: str, input: OverloadTestValue) -> OverloadTestValue: ], ) async def test_workflow_run_operation_overloads( - client: Client, env: WorkflowEnvironment, op: str + client: Client, env: WorkflowEnvironment, op: str, nexus_endpoint ): - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue async with Worker( client, task_queue=task_queue, @@ -1909,8 +1893,6 @@ async def test_workflow_run_operation_overloads( ], nexus_service_handlers=[OverloadTestServiceHandler()], ): - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) res = await client.execute_workflow( OverloadTestCallerWorkflow.run, args=[op, OverloadTestValue(value=2)], @@ -1965,10 +1947,10 @@ async def run(self, task_queue: str) -> None: ) -async def test_workflow_caller_custom_metrics(client: Client, env: WorkflowEnvironment): - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) +async def test_workflow_caller_custom_metrics( + client: Client, env: WorkflowEnvironment, nexus_endpoint +): + task_queue = nexus_endpoint.task_queue # Create new runtime with Prom server prom_addr = f"127.0.0.1:{find_free_port()}" @@ -2038,7 +2020,7 @@ async def test_workflow_caller_custom_metrics(client: Client, env: WorkflowEnvir async def test_workflow_caller_buffered_metrics( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): # Create runtime with metric buffer buffer = MetricBuffer(10000) @@ -2053,9 +2035,7 @@ async def test_workflow_caller_buffered_metrics( client = await env.connect_client( runtime=runtime, ) - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue async with new_worker( client, CustomMetricsWorkflow, @@ -2214,13 +2194,17 @@ def non_async_cancel_op(self) -> OperationHandler[None, str]: @pytest.mark.parametrize("use_async_cancel", [True, False]) async def test_task_executor_operation_cancel_method( - self, client: Client, env: WorkflowEnvironment, use_async_cancel: bool + self, + client: Client, + env: WorkflowEnvironment, + use_async_cancel: bool, + nexus_endpoint, ): """Test that both async and non-async cancel methods work for TaskExecutor-based operations.""" if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue async with Worker( client, task_queue=task_queue, @@ -2232,9 +2216,6 @@ async def test_task_executor_operation_cancel_method( ], nexus_task_executor=concurrent.futures.ThreadPoolExecutor(), ): - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) - caller_wf_handle = await client.start_workflow( CancelTestCallerWorkflow.run, args=[use_async_cancel, task_queue], @@ -2263,12 +2244,13 @@ async def test_task_executor_operation_cancel_method( async def test_request_deadline_is_accessible_in_operation( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ): """Test that request_deadline is accessible in StartOperationContext.""" if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue service_handler = RequestDeadlineServiceImpl() async with Worker( @@ -2277,9 +2259,6 @@ async def test_request_deadline_is_accessible_in_operation( workflows=[CancelDeadlineCallerWorkflow], task_queue=task_queue, ): - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) - await client.execute_workflow( CancelDeadlineCallerWorkflow.run, task_queue, diff --git a/tests/nexus/test_workflow_caller_cancellation_types.py b/tests/nexus/test_workflow_caller_cancellation_types.py index bf33983a5..88a2276cd 100644 --- a/tests/nexus/test_workflow_caller_cancellation_types.py +++ b/tests/nexus/test_workflow_caller_cancellation_types.py @@ -260,6 +260,7 @@ async def run(self, input: Input) -> CancellationResult: async def test_cancellation_type( env: WorkflowEnvironment, cancellation_type_name: str, + nexus_endpoint, ): cancellation_type = workflow.NexusOperationCancellationType[cancellation_type_name] global test_context @@ -269,6 +270,7 @@ async def test_cancellation_type( ) client = env.client + task_queue = nexus_endpoint.task_queue log_capturer = LogCapturer() with log_capturer.logs_captured( @@ -276,14 +278,10 @@ async def test_cancellation_type( ): async with Worker( client, - task_queue=str(uuid.uuid4()), + task_queue=task_queue, workflows=[CallerWorkflow, HandlerWorkflow], nexus_service_handlers=[ServiceHandler()], ) as worker: - await env.create_nexus_endpoint( - make_nexus_endpoint_name(worker.task_queue), worker.task_queue - ) - # Start the caller workflow, wait for the nexus op to have started and retrieve the nexus op # token with_start_workflow = WithStartWorkflowOperation( diff --git a/tests/nexus/test_workflow_caller_cancellation_types_when_cancel_handler_fails.py b/tests/nexus/test_workflow_caller_cancellation_types_when_cancel_handler_fails.py index 4cdeeeb15..57ca6ba49 100644 --- a/tests/nexus/test_workflow_caller_cancellation_types_when_cancel_handler_fails.py +++ b/tests/nexus/test_workflow_caller_cancellation_types_when_cancel_handler_fails.py @@ -224,6 +224,7 @@ async def run(self, input: Input) -> CancellationResult: async def test_cancellation_type( env: WorkflowEnvironment, cancellation_type_name: str, + nexus_endpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -233,17 +234,14 @@ async def test_cancellation_type( test_context = TestContext(cancellation_type=cancellation_type) client = env.client + task_queue = nexus_endpoint.task_queue async with Worker( client, - task_queue=str(uuid.uuid4()), + task_queue=task_queue, workflows=[CallerWorkflow, HandlerWorkflow], nexus_service_handlers=[ServiceHandler()], ) as worker: - await env.create_nexus_endpoint( - make_nexus_endpoint_name(worker.task_queue), worker.task_queue - ) - # Start the caller workflow, wait for the nexus op to have started and retrieve the nexus op # token with_start_workflow = WithStartWorkflowOperation( diff --git a/tests/nexus/test_workflow_caller_error_chains.py b/tests/nexus/test_workflow_caller_error_chains.py index 9ff84f405..627181ecc 100644 --- a/tests/nexus/test_workflow_caller_error_chains.py +++ b/tests/nexus/test_workflow_caller_error_chains.py @@ -623,21 +623,21 @@ async def run(self, input: ErrorTestInput) -> None: ids=lambda tc: tc.name, ) async def test_errors_raised_by_nexus_operation( - client: Client, env: WorkflowEnvironment, test_case: ErrorTestCase + client: Client, + env: WorkflowEnvironment, + test_case: ErrorTestCase, + nexus_endpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue async with Worker( client, nexus_service_handlers=[ErrorTestService()], workflows=[ErrorTestCallerWorkflow], task_queue=task_queue, ): - await env.create_nexus_endpoint( - make_nexus_endpoint_name(task_queue), task_queue - ) await client.execute_workflow( ErrorTestCallerWorkflow.run, ErrorTestInput( diff --git a/tests/nexus/test_workflow_caller_errors.py b/tests/nexus/test_workflow_caller_errors.py index 9246b9fd7..9ced0f920 100644 --- a/tests/nexus/test_workflow_caller_errors.py +++ b/tests/nexus/test_workflow_caller_errors.py @@ -169,7 +169,7 @@ async def run(self, input: RPCErrorInput) -> None: ], ) async def test_nexus_operation_is_retried( - client: Client, env: WorkflowEnvironment, operation_name: str + client: Client, env: WorkflowEnvironment, operation_name: str, nexus_endpoint ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -177,7 +177,7 @@ async def test_nexus_operation_is_retried( input = ErrorTestInput( service_name="ErrorTestService", operation_name=operation_name, - task_queue=str(uuid.uuid4()), + task_queue=nexus_endpoint.task_queue, id=str(uuid.uuid4()), ) async with Worker( @@ -187,9 +187,6 @@ async def test_nexus_operation_is_retried( workflows=[CallerWorkflow], task_queue=input.task_queue, ): - await env.create_nexus_endpoint( - make_nexus_endpoint_name(input.task_queue), input.task_queue - ) asyncio.create_task( client.execute_workflow( CallerWorkflow.run, @@ -231,6 +228,7 @@ async def test_nexus_operation_fails_without_retry_as_handler_error( operation_name: str, handler_error_type: nexusrpc.HandlerErrorType, handler_error_message: str, + nexus_endpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -242,7 +240,7 @@ async def test_nexus_operation_fails_without_retry_as_handler_error( else "NonExistentService" ), operation_name=operation_name, - task_queue=str(uuid.uuid4()), + task_queue=nexus_endpoint.task_queue, id=str(uuid.uuid4()), ) async with Worker( @@ -252,9 +250,6 @@ async def test_nexus_operation_fails_without_retry_as_handler_error( workflows=[CallerWorkflow], task_queue=input.task_queue, ): - await env.create_nexus_endpoint( - make_nexus_endpoint_name(input.task_queue), input.task_queue - ) try: await client.execute_workflow( CallerWorkflow.run, @@ -313,12 +308,12 @@ async def run(self, operation: str) -> None: async def test_error_raised_by_timeout_of_nexus_start_operation( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue async with Worker( client, nexus_service_handlers=[StartTimeoutTestService()], @@ -326,9 +321,6 @@ async def test_error_raised_by_timeout_of_nexus_start_operation( task_queue=task_queue, nexus_task_executor=concurrent.futures.ThreadPoolExecutor(), ): - await env.create_nexus_endpoint( - make_nexus_endpoint_name(task_queue), task_queue - ) try: await client.execute_workflow( StartTimeoutTestCallerWorkflow.run, @@ -395,12 +387,12 @@ async def run(self) -> None: async def test_error_raised_by_schedule_to_start_timeout_of_nexus_operation( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue async with Worker( client, nexus_service_handlers=[ScheduleToStartTimeoutTestService()], @@ -408,9 +400,6 @@ async def test_error_raised_by_schedule_to_start_timeout_of_nexus_operation( task_queue=task_queue, nexus_task_executor=concurrent.futures.ThreadPoolExecutor(), ): - await env.create_nexus_endpoint( - make_nexus_endpoint_name(task_queue), task_queue - ) try: await client.execute_workflow( ScheduleToStartTimeoutTestCallerWorkflow.run, @@ -471,12 +460,12 @@ async def run(self) -> None: async def test_error_raised_by_start_to_close_timeout_of_nexus_operation( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue async with Worker( client, nexus_service_handlers=[StartToCloseTimeoutTestService()], @@ -484,9 +473,6 @@ async def test_error_raised_by_start_to_close_timeout_of_nexus_operation( task_queue=task_queue, nexus_task_executor=concurrent.futures.ThreadPoolExecutor(), ): - await env.create_nexus_endpoint( - make_nexus_endpoint_name(task_queue), task_queue - ) try: await client.execute_workflow( StartToCloseTimeoutTestCallerWorkflow.run, @@ -552,12 +538,12 @@ async def run(self) -> None: async def test_error_raised_by_timeout_of_nexus_cancel_operation( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue async with Worker( client, nexus_service_handlers=[CancellationTimeoutTestService()], @@ -565,9 +551,6 @@ async def test_error_raised_by_timeout_of_nexus_cancel_operation( task_queue=task_queue, ): with LogCapturer().logs_captured(logger) as capturer: - await env.create_nexus_endpoint( - make_nexus_endpoint_name(task_queue), task_queue - ) try: await client.execute_workflow( CancellationTimeoutTestCallerWorkflow.run, @@ -605,13 +588,14 @@ async def test_rpc_error_fails_without_retry( env: WorkflowEnvironment, status_code: RPCStatusCode, expected_handler_error_type: nexusrpc.HandlerErrorType, + nexus_endpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") input = RPCErrorInput( status_code_value=status_code.value, - task_queue=str(uuid.uuid4()), + task_queue=nexus_endpoint.task_queue, id=str(uuid.uuid4()), ) async with Worker( @@ -621,9 +605,6 @@ async def test_rpc_error_fails_without_retry( workflows=[RPCErrorCallerWorkflow], task_queue=input.task_queue, ): - await env.create_nexus_endpoint( - make_nexus_endpoint_name(input.task_queue), input.task_queue - ) try: await client.execute_workflow( RPCErrorCallerWorkflow.run, @@ -664,13 +645,14 @@ async def test_rpc_error_is_retried( client: Client, env: WorkflowEnvironment, status_code: RPCStatusCode, + nexus_endpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") input = RPCErrorInput( status_code_value=status_code.value, - task_queue=str(uuid.uuid4()), + task_queue=nexus_endpoint.task_queue, id=str(uuid.uuid4()), ) async with Worker( @@ -680,10 +662,6 @@ async def test_rpc_error_is_retried( workflows=[RPCErrorCallerWorkflow], task_queue=input.task_queue, ): - await env.create_nexus_endpoint( - make_nexus_endpoint_name(input.task_queue), input.task_queue - ) - handle = await client.start_workflow( RPCErrorCallerWorkflow.run, input, @@ -739,12 +717,12 @@ def from_payloads( async def test_nexus_operation_retried_on_codec_decode_failure( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue codec = FailOnFirstDecodeCodec() handler_client = Client( client.service_client, @@ -770,9 +748,6 @@ async def test_nexus_operation_retried_on_codec_decode_failure( task_queue=task_queue, ), ): - await env.create_nexus_endpoint( - make_nexus_endpoint_name(input.task_queue), input.task_queue - ) await client.execute_workflow( CallerWorkflow.run, input, @@ -783,12 +758,12 @@ async def test_nexus_operation_retried_on_codec_decode_failure( async def test_nexus_operation_fails_without_retry_on_converter_failure( - client: Client, env: WorkflowEnvironment + client: Client, env: WorkflowEnvironment, nexus_endpoint ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) + task_queue = nexus_endpoint.task_queue handler_client = Client( client.service_client, namespace=client.namespace, @@ -815,9 +790,6 @@ async def test_nexus_operation_fails_without_retry_on_converter_failure( task_queue=task_queue, ), ): - await env.create_nexus_endpoint( - make_nexus_endpoint_name(input.task_queue), input.task_queue - ) try: await client.execute_workflow( CallerWorkflow.run, diff --git a/tests/nexus/test_workflow_run_operation.py b/tests/nexus/test_workflow_run_operation.py index 851f408ec..10e59391c 100644 --- a/tests/nexus/test_workflow_run_operation.py +++ b/tests/nexus/test_workflow_run_operation.py @@ -134,12 +134,12 @@ async def test_workflow_run_operation( client: Client, env: WorkflowEnvironment, service_handler_cls: type[Any], + nexus_endpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) - await env.create_nexus_endpoint(make_nexus_endpoint_name(task_queue), task_queue) + task_queue = nexus_endpoint.task_queue assert (service_defn := nexusrpc.get_service_definition(service_handler_cls)) async with Worker( client, @@ -160,14 +160,13 @@ async def test_workflow_run_operation( async def test_request_deadline_is_accessible_in_workflow_run_operation( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ): """Test that request_deadline is accessible in WorkflowRunOperationContext.""" if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) - endpoint_name = make_nexus_endpoint_name(task_queue) - await env.create_nexus_endpoint(endpoint_name, task_queue) + task_queue = nexus_endpoint.task_queue service_handler = RequestDeadlineHandler() async with Worker( env.client, @@ -194,12 +193,12 @@ async def test_request_deadline_is_accessible_in_workflow_run_operation( async def test_workflow_run_operation_includes_token_in_callback( client: Client, env: WorkflowEnvironment, + nexus_endpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") - task_queue = str(uuid.uuid4()) - await env.create_nexus_endpoint(make_nexus_endpoint_name(task_queue), task_queue) + task_queue = nexus_endpoint.task_queue async with Worker( client, task_queue=task_queue, From cd17e54c12a65232c1224d13375bbb0803318ecc Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 19 Aug 2026 10:56:59 -0700 Subject: [PATCH 19/19] test: type Nexus endpoint fixture --- tests/nexus/conftest.py | 2 +- ...ynamic_creation_of_user_handler_classes.py | 3 +- .../nexus/test_signal_link_propagation_e2e.py | 11 ++--- tests/nexus/test_standalone_operations.py | 33 ++++++++------- tests/nexus/test_temporal_extstore.py | 11 ++--- tests/nexus/test_temporal_operation.py | 39 ++++++++++-------- .../test_use_existing_conflict_policy.py | 4 +- tests/nexus/test_workflow_caller.py | 41 +++++++++---------- ...test_workflow_caller_cancellation_types.py | 3 +- ...llation_types_when_cancel_handler_fails.py | 3 +- .../test_workflow_caller_error_chains.py | 3 +- tests/nexus/test_workflow_caller_errors.py | 24 ++++++----- tests/nexus/test_workflow_run_operation.py | 7 ++-- 13 files changed, 100 insertions(+), 84 deletions(-) diff --git a/tests/nexus/conftest.py b/tests/nexus/conftest.py index d4437c116..8f45bef3f 100644 --- a/tests/nexus/conftest.py +++ b/tests/nexus/conftest.py @@ -213,7 +213,7 @@ async def create_nexus_endpoint(endpoint_name: str, task_queue: str) -> Endpoint ) -@pytest_asyncio.fixture +@pytest_asyncio.fixture # type: ignore[reportUntypedFunctionDecorator] async def nexus_endpoint( cloud_nexus_endpoint_client: _CloudNexusEndpointClient | None, env: WorkflowEnvironment, diff --git a/tests/nexus/test_dynamic_creation_of_user_handler_classes.py b/tests/nexus/test_dynamic_creation_of_user_handler_classes.py index 31d85fef1..17de5407d 100644 --- a/tests/nexus/test_dynamic_creation_of_user_handler_classes.py +++ b/tests/nexus/test_dynamic_creation_of_user_handler_classes.py @@ -9,6 +9,7 @@ from temporalio.testing import WorkflowEnvironment from temporalio.worker import Worker from tests.helpers.nexus import make_nexus_endpoint_name +from tests.nexus.conftest import NexusEndpoint @workflow.defn @@ -52,7 +53,7 @@ async def run(self, input: int, task_queue: str) -> int: async def test_run_nexus_service_from_programmatically_created_service_handler( client: Client, env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") diff --git a/tests/nexus/test_signal_link_propagation_e2e.py b/tests/nexus/test_signal_link_propagation_e2e.py index a92f0b738..bf062594e 100644 --- a/tests/nexus/test_signal_link_propagation_e2e.py +++ b/tests/nexus/test_signal_link_propagation_e2e.py @@ -55,6 +55,7 @@ make_nexus_endpoint_name, workflow_event_link_event_type, ) +from tests.nexus.conftest import NexusEndpoint EventType = temporalio.api.enums.v1.EventType @@ -266,7 +267,7 @@ def _assert_backlink( async def test_sync_signal_operation_links( client: Client, env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ) -> None: if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -315,7 +316,7 @@ async def test_sync_signal_operation_links( async def test_async_signal_operation_links( client: Client, env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ) -> None: if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -400,7 +401,7 @@ def _assert_standalone_forward_link( async def test_standalone_sync_signal_operation_links( client: Client, env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ) -> None: if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -439,7 +440,7 @@ async def test_standalone_sync_signal_operation_links( async def test_standalone_async_signal_operation_links( client: Client, env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ) -> None: if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -497,7 +498,7 @@ async def _callee_result() -> str: async def test_start_from_handler_attaches_on_conflict_options( client: Client, env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ) -> None: if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") diff --git a/tests/nexus/test_standalone_operations.py b/tests/nexus/test_standalone_operations.py index bc543e8af..b470f734c 100644 --- a/tests/nexus/test_standalone_operations.py +++ b/tests/nexus/test_standalone_operations.py @@ -64,6 +64,7 @@ expected_workflow_event_link, links_from_workflow_execution_started_event, ) +from tests.nexus.conftest import NexusEndpoint # --------------------------------------------------------------------------- # Data types @@ -193,7 +194,7 @@ async def raise_err( async def test_start_sync_operation_and_get_result( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Start a sync nexus operation, call handle.result(), verify return value.""" if env.supports_time_skipping: @@ -231,7 +232,7 @@ async def test_start_sync_operation_and_get_result( async def test_start_async_operation_and_poll_result( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Start a workflow_run operation, poll result, verify.""" if env.supports_time_skipping: @@ -263,7 +264,7 @@ async def test_start_async_operation_and_poll_result( async def test_started_workflow_has_link_to_standalone_nexus_operation( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Start a workflow_run operation and verify its workflow links back to the Nexus op.""" if env.supports_time_skipping: @@ -318,7 +319,7 @@ async def test_started_workflow_has_link_to_standalone_nexus_operation( async def test_execute_operation( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Use execute_operation convenience method, verify it returns result directly.""" if env.supports_time_skipping: @@ -351,7 +352,7 @@ async def test_execute_operation( async def test_execute_operation_named_service( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Verify that the name on the service decorator is respected by the standalone nexus client""" if env.supports_time_skipping: @@ -385,7 +386,9 @@ async def test_execute_operation_named_service( assert result.value == "execute" -async def test_errors(client: Client, env: WorkflowEnvironment, nexus_endpoint): +async def test_errors( + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint +): """Execute operations that raise errors""" if env.supports_time_skipping: pytest.skip( @@ -444,7 +447,7 @@ async def test_errors(client: Client, env: WorkflowEnvironment, nexus_endpoint): async def test_describe_operation( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Start op, get result first, then describe, verify fields populated.""" if env.supports_time_skipping: @@ -491,7 +494,7 @@ async def test_describe_operation( async def test_cancel_operation( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Start blocking async op, cancel it, verify awaiting result raises NexusOperationFailureError from a CancelledError. @@ -533,7 +536,7 @@ async def test_cancel_operation( async def test_terminate_operation( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Start blocking async op, terminate it, verify awaiting the result raises NexusOperationFailureError from a TerminatedError. @@ -575,7 +578,7 @@ async def test_terminate_operation( async def test_list_operations( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Start multiple ops, list them, verify iteration yields correct results.""" if env.supports_time_skipping: @@ -623,7 +626,7 @@ async def check_ids() -> None: async def test_count_operations( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Start ops, count, verify count.""" if env.supports_time_skipping: @@ -666,7 +669,7 @@ async def check_count() -> None: async def test_get_nexus_operation_handle( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Start op, get result, then get handle by ID and get result again.""" if env.supports_time_skipping: @@ -711,7 +714,7 @@ async def test_get_nexus_operation_handle( async def test_id_conflict_policy_use_existing( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Start op, re-start with USE_EXISTING, verify same op/run ID and expected result""" if env.supports_time_skipping: @@ -776,7 +779,7 @@ async def test_id_conflict_policy_use_existing( async def test_id_conflict_policy_fail( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Start op, re-start with FAIL, verify raises NexusOperationAlreadyStartedError.""" if env.supports_time_skipping: @@ -890,7 +893,7 @@ def intercept_client(self, next: OutboundInterceptor) -> OutboundInterceptor: async def test_interceptor_receives_inputs( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """Custom OutboundInterceptor records calls, verify correct input types.""" if env.supports_time_skipping: diff --git a/tests/nexus/test_temporal_extstore.py b/tests/nexus/test_temporal_extstore.py index 087d647ee..dadf73d00 100644 --- a/tests/nexus/test_temporal_extstore.py +++ b/tests/nexus/test_temporal_extstore.py @@ -39,6 +39,7 @@ from temporalio.types import MethodAsyncSingleParam from temporalio.worker import UnsandboxedWorkflowRunner, Worker from tests.helpers.nexus import make_nexus_endpoint_name +from tests.nexus.conftest import NexusEndpoint from tests.test_extstore import InMemoryTestDriver PAYLOAD_SIZE = 4096 @@ -179,7 +180,7 @@ def _cause_chain(err: BaseException) -> list[BaseException]: async def test_nexus_operation_input_offloaded_and_retrieved( - env: WorkflowEnvironment, nexus_endpoint + env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): """The offloaded operation input is retrieved before the handler runs.""" if env.supports_time_skipping: @@ -197,7 +198,7 @@ async def test_nexus_operation_input_offloaded_and_retrieved( async def test_nexus_operation_sync_result_offloaded_and_retrieved( env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): """A large synchronous result is offloaded and retrieved by the caller.""" if env.supports_time_skipping: @@ -215,7 +216,7 @@ async def test_nexus_operation_sync_result_offloaded_and_retrieved( async def test_nexus_operation_transient_retrieve_failure_recovers( env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): """A transient retrieve failure fails the task retryably; it then recovers.""" if env.supports_time_skipping: @@ -232,7 +233,7 @@ async def test_nexus_operation_transient_retrieve_failure_recovers( async def test_nexus_operation_transient_store_failure_recovers( env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): """A transient store failure fails the task retryably; it then recovers.""" if env.supports_time_skipping: @@ -260,7 +261,7 @@ async def store( async def test_nexus_operation_store_failure_fails_operation( env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): """A non-retryable store failure fails the operation and surfaces the driver error to the caller (deterministically, with no retries).""" diff --git a/tests/nexus/test_temporal_operation.py b/tests/nexus/test_temporal_operation.py index 29ce76041..42602180f 100644 --- a/tests/nexus/test_temporal_operation.py +++ b/tests/nexus/test_temporal_operation.py @@ -39,6 +39,7 @@ expected_nexus_operation_link, make_nexus_endpoint_name, ) +from tests.nexus.conftest import NexusEndpoint @dataclass @@ -426,7 +427,7 @@ async def run(self, input: Input) -> str: async def test_temporal_operation_start_workflow( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): task_queue = nexus_endpoint.task_queue async with Worker( @@ -455,7 +456,7 @@ async def test_temporal_operation_start_workflow( async def test_temporal_operation_update_workflow( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ) -> None: if ( env.supports_time_skipping @@ -836,7 +837,7 @@ async def wait_operation_started(self): async def test_temporal_operation_cancel_workflow( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): task_queue = nexus_endpoint.task_queue async with Worker( @@ -869,7 +870,7 @@ async def test_temporal_operation_cancel_workflow( async def test_customized_temporal_operation_cancel_workflow( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip( @@ -942,7 +943,7 @@ async def run(self, input: Input) -> str: async def test_temporal_operation_double_start_raises_handler_err( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): task_queue = nexus_endpoint.task_queue async with Worker( @@ -969,7 +970,7 @@ async def test_temporal_operation_double_start_raises_handler_err( async def test_temporal_operation_concurrent_start_raises_handler_err( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): task_queue = nexus_endpoint.task_queue async with Worker( @@ -989,7 +990,7 @@ async def test_temporal_operation_concurrent_start_raises_handler_err( async def test_temporal_operation_failed_start_allows_retry( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): task_queue = nexus_endpoint.task_queue conflict_id = f"failed-start-rollback-{uuid.uuid4()}" @@ -1022,7 +1023,7 @@ async def test_temporal_operation_failed_start_allows_retry( async def test_temporal_operation_mixed_start_raises_handler_err( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip( @@ -1066,7 +1067,7 @@ async def run(self, input: Input) -> str: async def test_temporal_operation_sync_result( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): task_queue = nexus_endpoint.task_queue async with Worker( @@ -1095,7 +1096,7 @@ async def test_temporal_operation_sync_result( async def test_temporal_operation_start_activity( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip( @@ -1121,7 +1122,7 @@ async def test_temporal_operation_start_activity( async def test_temporal_operation_backing_activity_does_not_duplicate_links( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip( @@ -1181,7 +1182,7 @@ async def echo_activity( async def test_temporal_operation_start_activity_raises_error( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip( @@ -1219,7 +1220,7 @@ async def test_temporal_operation_start_activity_raises_error( async def test_temporal_operation_cancel_activity( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip( @@ -1255,7 +1256,7 @@ async def check_cancelled(): async def test_customized_temporal_operation_cancel_activity( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip( @@ -1295,7 +1296,7 @@ async def check_cancelled(): async def test_temporal_operation_double_start_activity_raises_handler_err( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip( @@ -1463,7 +1464,9 @@ async def run( ], ) async def test_temporal_operation_overloads( - client: Client, env: WorkflowEnvironment, op: str, nexus_endpoint + client: Client, + op: str, + nexus_endpoint: NexusEndpoint, ): task_queue = nexus_endpoint.task_queue async with Worker( @@ -1490,7 +1493,7 @@ async def test_temporal_operation_overloads( async def test_temporal_operation_includes_token_in_callback( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): task_queue = nexus_endpoint.task_queue async with Worker( @@ -1571,7 +1574,7 @@ async def do_update(self, value: str) -> str: async def test_temporal_operation_includes_activity_token_in_callback( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip( diff --git a/tests/nexus/test_use_existing_conflict_policy.py b/tests/nexus/test_use_existing_conflict_policy.py index eebfe7e4b..82f2b2829 100644 --- a/tests/nexus/test_use_existing_conflict_policy.py +++ b/tests/nexus/test_use_existing_conflict_policy.py @@ -9,9 +9,9 @@ from temporalio import nexus, workflow from temporalio.client import Client from temporalio.common import WorkflowIDConflictPolicy -from temporalio.testing import WorkflowEnvironment from temporalio.worker import Worker from tests.helpers.nexus import make_nexus_endpoint_name +from tests.nexus.conftest import NexusEndpoint @dataclass @@ -88,7 +88,7 @@ async def nexus_operations_have_started(self) -> None: async def test_multiple_operation_invocations_can_connect_to_same_handler_workflow( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, nexus_endpoint: NexusEndpoint ): task_queue = nexus_endpoint.task_queue workflow_id = str(uuid.uuid4()) diff --git a/tests/nexus/test_workflow_caller.py b/tests/nexus/test_workflow_caller.py index 18124e312..6f6fb2197 100644 --- a/tests/nexus/test_workflow_caller.py +++ b/tests/nexus/test_workflow_caller.py @@ -71,6 +71,7 @@ links_from_workflow_execution_started_event, make_nexus_endpoint_name, ) +from tests.nexus.conftest import NexusEndpoint # TODO(nexus-preview): test worker shutdown, wait_all_completed, drain etc @@ -663,9 +664,7 @@ async def run(self, input: WorkflowRunHeaderTestCallerWfInput) -> HeaderTestOutp # -async def test_sync_operation_happy_path( - client: Client, env: WorkflowEnvironment, nexus_endpoint -): +async def test_sync_operation_happy_path(client: Client, nexus_endpoint: NexusEndpoint): task_queue = nexus_endpoint.task_queue async with Worker( client, @@ -723,7 +722,7 @@ async def run(self, task_queue: str) -> dict[str, str]: async def test_nexus_info_includes_namespace( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): task_queue = nexus_endpoint.task_queue endpoint_name = nexus_endpoint.name @@ -747,7 +746,7 @@ async def test_nexus_info_includes_namespace( async def test_workflow_run_operation_happy_path( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, nexus_endpoint: NexusEndpoint ): task_queue = nexus_endpoint.task_queue async with Worker( @@ -898,7 +897,7 @@ async def start_nexus_operation( async def test_start_operation_headers( client: Client, env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): """Test headers from workflow and interceptors are propagated to start operation handler.""" if env.supports_time_skipping: @@ -942,8 +941,7 @@ async def test_start_operation_headers( async def test_workflow_run_operation_headers( client: Client, - env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): """Test that headers are propagated to @workflow_run_operation handlers.""" task_queue = nexus_endpoint.task_queue @@ -971,7 +969,7 @@ async def test_workflow_run_operation_headers( async def test_cancel_operation_headers( client: Client, env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): """Test headers from workflow and interceptor are propagated to cancel operation handler.""" if env.supports_time_skipping: @@ -1029,7 +1027,7 @@ async def test_sync_response( request_cancel: bool, op_definition_type: OpDefinitionType, caller_reference: CallerReference, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -1102,7 +1100,7 @@ async def test_async_response( request_cancel: bool, op_definition_type: OpDefinitionType, caller_reference: CallerReference, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -1263,7 +1261,7 @@ async def test_untyped_caller( op_definition_type: OpDefinitionType, caller_reference: CallerReference, response_type: ResponseType, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -1418,7 +1416,7 @@ async def run( async def test_service_interface_and_implementation_names( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, nexus_endpoint: NexusEndpoint ): # Note that: # - The caller can specify the service & operation via a reference to either the @@ -1545,7 +1543,7 @@ async def run(self, _input: str, task_queue: str) -> str: async def test_workflow_run_operation_can_execute_workflow_before_starting_backing_workflow( client: Client, env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -1599,8 +1597,7 @@ async def run(self, input: str, task_queue: str) -> str: async def test_nexus_operation_summary( client: Client, - env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): task_queue = nexus_endpoint.task_queue async with Worker( @@ -1880,7 +1877,9 @@ async def run(self, op: str, input: OverloadTestValue) -> OverloadTestValue: ], ) async def test_workflow_run_operation_overloads( - client: Client, env: WorkflowEnvironment, op: str, nexus_endpoint + client: Client, + op: str, + nexus_endpoint: NexusEndpoint, ): task_queue = nexus_endpoint.task_queue async with Worker( @@ -1948,7 +1947,7 @@ async def run(self, task_queue: str) -> None: async def test_workflow_caller_custom_metrics( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): task_queue = nexus_endpoint.task_queue @@ -2020,7 +2019,7 @@ async def test_workflow_caller_custom_metrics( async def test_workflow_caller_buffered_metrics( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): # Create runtime with metric buffer buffer = MetricBuffer(10000) @@ -2198,7 +2197,7 @@ async def test_task_executor_operation_cancel_method( client: Client, env: WorkflowEnvironment, use_async_cancel: bool, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): """Test that both async and non-async cancel methods work for TaskExecutor-based operations.""" if env.supports_time_skipping: @@ -2244,7 +2243,7 @@ async def test_task_executor_operation_cancel_method( async def test_request_deadline_is_accessible_in_operation( client: Client, env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): """Test that request_deadline is accessible in StartOperationContext.""" if env.supports_time_skipping: diff --git a/tests/nexus/test_workflow_caller_cancellation_types.py b/tests/nexus/test_workflow_caller_cancellation_types.py index 88a2276cd..7a777a684 100644 --- a/tests/nexus/test_workflow_caller_cancellation_types.py +++ b/tests/nexus/test_workflow_caller_cancellation_types.py @@ -24,6 +24,7 @@ from temporalio.worker import Worker from tests.helpers import LogCapturer, assert_event_subsequence, assert_eventually from tests.helpers.nexus import make_nexus_endpoint_name +from tests.nexus.conftest import NexusEndpoint @dataclass @@ -260,7 +261,7 @@ async def run(self, input: Input) -> CancellationResult: async def test_cancellation_type( env: WorkflowEnvironment, cancellation_type_name: str, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): cancellation_type = workflow.NexusOperationCancellationType[cancellation_type_name] global test_context diff --git a/tests/nexus/test_workflow_caller_cancellation_types_when_cancel_handler_fails.py b/tests/nexus/test_workflow_caller_cancellation_types_when_cancel_handler_fails.py index 57ca6ba49..5c42f6202 100644 --- a/tests/nexus/test_workflow_caller_cancellation_types_when_cancel_handler_fails.py +++ b/tests/nexus/test_workflow_caller_cancellation_types_when_cancel_handler_fails.py @@ -25,6 +25,7 @@ from temporalio.worker import Worker from tests.helpers import assert_event_subsequence, assert_eventually from tests.helpers.nexus import make_nexus_endpoint_name +from tests.nexus.conftest import NexusEndpoint from tests.nexus.test_workflow_caller_cancellation_types import ( get_event_time, has_event, @@ -224,7 +225,7 @@ async def run(self, input: Input) -> CancellationResult: async def test_cancellation_type( env: WorkflowEnvironment, cancellation_type_name: str, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") diff --git a/tests/nexus/test_workflow_caller_error_chains.py b/tests/nexus/test_workflow_caller_error_chains.py index 627181ecc..cf3da7685 100644 --- a/tests/nexus/test_workflow_caller_error_chains.py +++ b/tests/nexus/test_workflow_caller_error_chains.py @@ -24,6 +24,7 @@ from temporalio.testing import WorkflowEnvironment from temporalio.worker import Worker from tests.helpers.nexus import make_nexus_endpoint_name +from tests.nexus.conftest import NexusEndpoint @dataclass @@ -626,7 +627,7 @@ async def test_errors_raised_by_nexus_operation( client: Client, env: WorkflowEnvironment, test_case: ErrorTestCase, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") diff --git a/tests/nexus/test_workflow_caller_errors.py b/tests/nexus/test_workflow_caller_errors.py index 9ced0f920..97556b09d 100644 --- a/tests/nexus/test_workflow_caller_errors.py +++ b/tests/nexus/test_workflow_caller_errors.py @@ -41,6 +41,7 @@ from temporalio.worker import Worker from tests.helpers import LogCapturer, assert_eq_eventually from tests.helpers.nexus import make_nexus_endpoint_name +from tests.nexus.conftest import NexusEndpoint operation_invocation_counts = Counter[str]() @@ -169,7 +170,10 @@ async def run(self, input: RPCErrorInput) -> None: ], ) async def test_nexus_operation_is_retried( - client: Client, env: WorkflowEnvironment, operation_name: str, nexus_endpoint + client: Client, + env: WorkflowEnvironment, + operation_name: str, + nexus_endpoint: NexusEndpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -228,7 +232,7 @@ async def test_nexus_operation_fails_without_retry_as_handler_error( operation_name: str, handler_error_type: nexusrpc.HandlerErrorType, handler_error_message: str, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -308,7 +312,7 @@ async def run(self, operation: str) -> None: async def test_error_raised_by_timeout_of_nexus_start_operation( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -387,7 +391,7 @@ async def run(self) -> None: async def test_error_raised_by_schedule_to_start_timeout_of_nexus_operation( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -460,7 +464,7 @@ async def run(self) -> None: async def test_error_raised_by_start_to_close_timeout_of_nexus_operation( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -538,7 +542,7 @@ async def run(self) -> None: async def test_error_raised_by_timeout_of_nexus_cancel_operation( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -588,7 +592,7 @@ async def test_rpc_error_fails_without_retry( env: WorkflowEnvironment, status_code: RPCStatusCode, expected_handler_error_type: nexusrpc.HandlerErrorType, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -645,7 +649,7 @@ async def test_rpc_error_is_retried( client: Client, env: WorkflowEnvironment, status_code: RPCStatusCode, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -717,7 +721,7 @@ def from_payloads( async def test_nexus_operation_retried_on_codec_decode_failure( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -758,7 +762,7 @@ async def test_nexus_operation_retried_on_codec_decode_failure( async def test_nexus_operation_fails_without_retry_on_converter_failure( - client: Client, env: WorkflowEnvironment, nexus_endpoint + client: Client, env: WorkflowEnvironment, nexus_endpoint: NexusEndpoint ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") diff --git a/tests/nexus/test_workflow_run_operation.py b/tests/nexus/test_workflow_run_operation.py index 10e59391c..faa78faca 100644 --- a/tests/nexus/test_workflow_run_operation.py +++ b/tests/nexus/test_workflow_run_operation.py @@ -22,6 +22,7 @@ from temporalio.testing import WorkflowEnvironment from temporalio.worker import Worker from tests.helpers.nexus import make_nexus_endpoint_name +from tests.nexus.conftest import NexusEndpoint @dataclass @@ -134,7 +135,7 @@ async def test_workflow_run_operation( client: Client, env: WorkflowEnvironment, service_handler_cls: type[Any], - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server") @@ -160,7 +161,7 @@ async def test_workflow_run_operation( async def test_request_deadline_is_accessible_in_workflow_run_operation( client: Client, env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): """Test that request_deadline is accessible in WorkflowRunOperationContext.""" if env.supports_time_skipping: @@ -193,7 +194,7 @@ async def test_request_deadline_is_accessible_in_workflow_run_operation( async def test_workflow_run_operation_includes_token_in_callback( client: Client, env: WorkflowEnvironment, - nexus_endpoint, + nexus_endpoint: NexusEndpoint, ): if env.supports_time_skipping: pytest.skip("Nexus tests don't work with time-skipping server")