Skip to content

Commit 4c6127b

Browse files
committed
test: Add external workflow signal/cancel test expecting target context
This test expects external workflow signals to use the target workflow's context (matching .NET behavior), but currently fails because Python uses the signaler's context. This is the desired behavior that needs to be fixed.
1 parent ae4402c commit 4c6127b

File tree

1 file changed

+146
-0
lines changed

1 file changed

+146
-0
lines changed

tests/test_serialization_context.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,152 @@ async def test_update_payload_conversion(
684684
)
685685

686686

687+
# External workflow test
688+
689+
690+
@workflow.defn
691+
class ExternalWorkflowTarget:
692+
def __init__(self) -> None:
693+
self.signal_received = None
694+
695+
@workflow.run
696+
async def run(self) -> TraceData:
697+
try:
698+
# Wait for signal
699+
await workflow.wait_condition(lambda: self.signal_received is not None)
700+
return self.signal_received or TraceData()
701+
except asyncio.CancelledError:
702+
# Return empty data on cancellation
703+
return TraceData()
704+
705+
@workflow.signal
706+
async def external_signal(self, data: TraceData) -> None:
707+
self.signal_received = data
708+
709+
710+
@workflow.defn
711+
class ExternalWorkflowSignaler:
712+
@workflow.run
713+
async def run(self, target_id: str, data: TraceData) -> TraceData:
714+
# Signal external workflow
715+
handle = workflow.get_external_workflow_handle(target_id)
716+
await handle.signal(ExternalWorkflowTarget.external_signal, data)
717+
return data
718+
719+
720+
@workflow.defn
721+
class ExternalWorkflowCanceller:
722+
@workflow.run
723+
async def run(self, target_id: str) -> TraceData:
724+
# Cancel external workflow
725+
handle = workflow.get_external_workflow_handle(target_id)
726+
await handle.cancel()
727+
return TraceData()
728+
729+
730+
@pytest.mark.timeout(10)
731+
async def test_external_workflow_signal_and_cancel_payload_conversion(
732+
client: Client,
733+
):
734+
target_workflow_id = str(uuid.uuid4())
735+
signaler_workflow_id = str(uuid.uuid4())
736+
canceller_workflow_id = str(uuid.uuid4())
737+
task_queue = str(uuid.uuid4())
738+
739+
data_converter = dataclasses.replace(
740+
DataConverter.default,
741+
payload_converter_class=SerializationContextTestPayloadConverter,
742+
)
743+
744+
config = client.config()
745+
config["data_converter"] = data_converter
746+
custom_client = Client(**config)
747+
748+
async with Worker(
749+
custom_client,
750+
task_queue=task_queue,
751+
workflows=[
752+
ExternalWorkflowTarget,
753+
ExternalWorkflowSignaler,
754+
ExternalWorkflowCanceller,
755+
],
756+
activities=[],
757+
workflow_runner=UnsandboxedWorkflowRunner(), # so that we can use isinstance
758+
):
759+
# Test external signal
760+
target_handle = await custom_client.start_workflow(
761+
ExternalWorkflowTarget.run,
762+
id=target_workflow_id,
763+
task_queue=task_queue,
764+
)
765+
766+
signaler_handle = await custom_client.start_workflow(
767+
ExternalWorkflowSignaler.run,
768+
args=[target_workflow_id, TraceData()],
769+
id=signaler_workflow_id,
770+
task_queue=task_queue,
771+
)
772+
773+
# Wait for both to complete
774+
signaler_result = await signaler_handle.result()
775+
target_result = await target_handle.result()
776+
777+
# Verify signal trace
778+
signaler_context = dataclasses.asdict(
779+
WorkflowSerializationContext(
780+
namespace="default",
781+
workflow_id=signaler_workflow_id,
782+
)
783+
)
784+
target_context = dataclasses.asdict(
785+
WorkflowSerializationContext(
786+
namespace="default",
787+
workflow_id=target_workflow_id,
788+
)
789+
)
790+
791+
# This test verifies that external signals SHOULD use the target workflow's context
792+
# This is the DESIRED behavior to match .NET
793+
assert_trace(
794+
signaler_result.items,
795+
[
796+
TraceItem(
797+
context_type="workflow",
798+
in_workflow=False,
799+
method="to_payload",
800+
context=signaler_context, # Outbound signaler workflow input
801+
),
802+
TraceItem(
803+
context_type="workflow",
804+
in_workflow=False,
805+
method="from_payload",
806+
context=signaler_context, # Inbound signaler workflow input
807+
),
808+
TraceItem(
809+
context_type="workflow",
810+
in_workflow=True,
811+
method="to_payload",
812+
context=target_context, # Should use target workflow's context for external signal
813+
),
814+
TraceItem(
815+
context_type="workflow",
816+
in_workflow=True,
817+
method="to_payload",
818+
context=signaler_context, # Outbound signaler workflow result
819+
),
820+
TraceItem(
821+
context_type="workflow",
822+
in_workflow=False,
823+
method="from_payload",
824+
context=signaler_context, # Inbound signaler workflow result
825+
),
826+
],
827+
)
828+
829+
# Note: External cancel doesn't send payloads, so we don't test it here
830+
# The cancel context would only be used for failure deserialization
831+
832+
687833
# Utilities
688834

689835

0 commit comments

Comments
 (0)