@@ -55,6 +55,31 @@ def run(self, ctx): # type: ignore[no-untyped-def]
5555 ]
5656
5757
58+ @workflow .defn (name = "two-cross-queue-wf" )
59+ class TwoCrossQueueWorkflow :
60+ def run (self , ctx , request ): # type: ignore[no-untyped-def]
61+ marker = yield ctx .schedule_activity (
62+ "external.marker" ,
63+ [request ],
64+ queue = "external-queue" ,
65+ )
66+ description = yield ctx .schedule_activity (
67+ "external.describe" ,
68+ [marker ],
69+ queue = "external-queue" ,
70+ )
71+ return {
72+ "marker" : marker ,
73+ "description" : description ,
74+ }
75+
76+
77+ @workflow .defn (name = "unserializable-result-wf" )
78+ class UnserializableResultWorkflow :
79+ def run (self , ctx ): # type: ignore[no-untyped-def]
80+ return object ()
81+
82+
5883@workflow .defn (name = "update-wf" )
5984class UpdateWorkflow :
6085 def __init__ (self ) -> None :
@@ -780,6 +805,124 @@ async def test_complete_on_resolved_activity(self, mock_client: AsyncMock) -> No
780805 assert commands [0 ]["result" ]["codec" ] == "json"
781806 assert serializer .decode (commands [0 ]["result" ]["blob" ], codec = "json" ) == "done"
782807
808+ @pytest .mark .asyncio
809+ async def test_cross_queue_second_activity_uses_completed_first_result (
810+ self , mock_client : AsyncMock
811+ ) -> None :
812+ worker = Worker (mock_client , task_queue = "workflow-queue" , workflows = [TwoCrossQueueWorkflow ], activities = [])
813+ marker = {"runtime" : "external" , "name" : "Grace" , "message" : "hello" }
814+ task = {
815+ "task_id" : "t-cross-queue-second" ,
816+ "workflow_type" : "two-cross-queue-wf" ,
817+ "workflow_task_attempt" : 2 ,
818+ "history_events" : [
819+ {
820+ "event_type" : "ActivityCompleted" ,
821+ "payload" : {
822+ "sequence" : 1 ,
823+ "activity_type" : "external.marker" ,
824+ "payload_codec" : "json" ,
825+ "result" : serializer .envelope (marker , codec = "json" ),
826+ },
827+ },
828+ ],
829+ "arguments" : serializer .encode ([{"name" : "Grace" }], codec = "json" ),
830+ "payload_codec" : "json" ,
831+ }
832+
833+ await worker ._run_workflow_task (task )
834+
835+ mock_client .complete_workflow_task .assert_called_once ()
836+ commands = mock_client .complete_workflow_task .call_args .kwargs ["commands" ]
837+ assert commands == [
838+ {
839+ "type" : "schedule_activity" ,
840+ "activity_type" : "external.describe" ,
841+ "queue" : "external-queue" ,
842+ "arguments" : commands [0 ]["arguments" ],
843+ }
844+ ]
845+ assert commands [0 ]["arguments" ]["codec" ] == "json"
846+ assert serializer .decode (commands [0 ]["arguments" ]["blob" ], codec = "json" ) == [marker ]
847+
848+ @pytest .mark .asyncio
849+ async def test_cross_queue_workflow_completes_after_second_activity (
850+ self , mock_client : AsyncMock
851+ ) -> None :
852+ worker = Worker (mock_client , task_queue = "workflow-queue" , workflows = [TwoCrossQueueWorkflow ], activities = [])
853+ marker = {"runtime" : "external" , "name" : "Grace" , "message" : "hello" }
854+ description = {"runtime" : "external" , "description" : "Grace handled by external activity" }
855+ task = {
856+ "task_id" : "t-cross-queue-complete" ,
857+ "workflow_type" : "two-cross-queue-wf" ,
858+ "workflow_task_attempt" : 3 ,
859+ "history_events" : [
860+ {
861+ "event_type" : "ActivityCompleted" ,
862+ "payload" : {
863+ "sequence" : 1 ,
864+ "activity_type" : "external.marker" ,
865+ "payload_codec" : "json" ,
866+ "result" : serializer .envelope (marker , codec = "json" ),
867+ },
868+ },
869+ {
870+ "event_type" : "ActivityCompleted" ,
871+ "payload" : {
872+ "sequence" : 2 ,
873+ "activity_type" : "external.describe" ,
874+ "payload_codec" : "json" ,
875+ "result" : serializer .envelope (description , codec = "json" ),
876+ },
877+ },
878+ ],
879+ "arguments" : serializer .encode ([{"name" : "Grace" }], codec = "json" ),
880+ "payload_codec" : "json" ,
881+ }
882+
883+ await worker ._run_workflow_task (task )
884+
885+ mock_client .complete_workflow_task .assert_called_once ()
886+ commands = mock_client .complete_workflow_task .call_args .kwargs ["commands" ]
887+ assert commands [0 ]["type" ] == "complete_workflow"
888+ assert commands [0 ]["result" ]["codec" ] == "json"
889+ assert serializer .decode (commands [0 ]["result" ]["blob" ], codec = "json" ) == {
890+ "marker" : marker ,
891+ "description" : description ,
892+ }
893+
894+ @pytest .mark .asyncio
895+ async def test_dispatch_reports_unhandled_workflow_task_error (
896+ self , mock_client : AsyncMock
897+ ) -> None :
898+ worker = Worker (
899+ mock_client ,
900+ task_queue = "q1" ,
901+ workflows = [UnserializableResultWorkflow ],
902+ activities = [],
903+ worker_id = "w-unserializable" ,
904+ )
905+ task = {
906+ "task_id" : "t-unserializable" ,
907+ "workflow_type" : "unserializable-result-wf" ,
908+ "workflow_task_attempt" : 4 ,
909+ "history_events" : [],
910+ "arguments" : "[]" ,
911+ "payload_codec" : "json" ,
912+ }
913+
914+ await worker ._dispatch_workflow_task (task )
915+
916+ mock_client .complete_workflow_task .assert_not_called ()
917+ mock_client .fail_workflow_task .assert_awaited_once ()
918+ call_kwargs = mock_client .fail_workflow_task .await_args .kwargs
919+ assert call_kwargs ["task_id" ] == "t-unserializable"
920+ assert call_kwargs ["workflow_task_attempt" ] == 4
921+ assert call_kwargs ["lease_owner" ] == worker .worker_id
922+ assert call_kwargs ["failure_type" ] == "TypeError"
923+ assert "unhandled workflow task execution error" in call_kwargs ["message" ]
924+ assert "Object of type object" in call_kwargs ["message" ]
925+
783926 @pytest .mark .asyncio
784927 async def test_unknown_workflow_type_fails_task (self , mock_client : AsyncMock ) -> None :
785928 worker = Worker (mock_client , task_queue = "q1" , workflows = [], activities = [])
0 commit comments