@@ -135,6 +135,32 @@ def run(self, ctx): # type: ignore[no-untyped-def]
135135 yield ctx .wait_condition (lambda : False , key = "done" )
136136
137137
138+ @workflow .defn (name = "replay-query-snapshot-wf" )
139+ class ReplayQuerySnapshotWorkflow :
140+ def __init__ (self ) -> None :
141+ self .activity_result : str | None = None
142+ self .approved_by : str | None = None
143+ self .finished = False
144+
145+ @workflow .signal ("approve" )
146+ def approve (self , approved_by : str ) -> None :
147+ self .approved_by = approved_by
148+
149+ @workflow .query ("state" )
150+ def state (self ) -> dict [str , object ]:
151+ return {
152+ "activity_result" : self .activity_result ,
153+ "approved_by" : self .approved_by ,
154+ "finished" : self .finished ,
155+ }
156+
157+ def run (self , ctx ): # type: ignore[no-untyped-def]
158+ self .activity_result = yield ctx .schedule_activity ("load-state" , [])
159+ yield ctx .wait_condition (lambda : self .approved_by is not None , key = "approval" )
160+ self .finished = True
161+ yield ctx .schedule_activity ("after-signal" , [self .approved_by ])
162+
163+
138164@workflow .defn (name = "async-query-wf" )
139165class AsyncQueryWorkflow :
140166 @workflow .query ("current" )
@@ -1268,6 +1294,139 @@ async def test_query_task_replays_repeated_condition_wait_signal_arguments(
12681294 )
12691295 mock_client .fail_query_task .assert_not_called ()
12701296
1297+ @pytest .mark .asyncio
1298+ async def test_query_task_replays_history_from_export_after_worker_restart (
1299+ self , mock_client : AsyncMock
1300+ ) -> None :
1301+ worker = Worker (mock_client , task_queue = "q1" , workflows = [ReplayQuerySnapshotWorkflow ], activities = [])
1302+ approval_arguments = serializer .encode (["alice" ], codec = "json" )
1303+ task = {
1304+ "query_task_id" : "qt-export-history" ,
1305+ "query_task_attempt" : 1 ,
1306+ "workflow_type" : "replay-query-snapshot-wf" ,
1307+ "workflow_id" : "wf-replay-query" ,
1308+ "run_id" : "run-replay-query" ,
1309+ "query_name" : "state" ,
1310+ "history_events" : [],
1311+ "history_export" : {
1312+ "payloads" : {"codec" : "json" },
1313+ "history_events" : [
1314+ {
1315+ "type" : "ActivityCompleted" ,
1316+ "payload" : {
1317+ "sequence" : 1 ,
1318+ "activity_type" : "load-state" ,
1319+ "payload_codec" : "json" ,
1320+ "result" : serializer .encode ("loaded" , codec = "json" ),
1321+ },
1322+ },
1323+ {
1324+ "type" : "ConditionWaitOpened" ,
1325+ "payload" : {
1326+ "sequence" : 2 ,
1327+ "condition_wait_id" : "wait-approval" ,
1328+ "condition_key" : "approval" ,
1329+ },
1330+ },
1331+ {
1332+ "type" : "SignalReceived" ,
1333+ "payload" : {
1334+ "signal_id" : "sig-approve" ,
1335+ "workflow_command_id" : "cmd-approve" ,
1336+ "signal_name" : "approve" ,
1337+ },
1338+ },
1339+ ],
1340+ "signals" : [
1341+ {
1342+ "id" : "sig-approve" ,
1343+ "command_id" : "cmd-approve" ,
1344+ "name" : "approve" ,
1345+ "payload_codec" : "json" ,
1346+ "arguments" : approval_arguments ,
1347+ },
1348+ ],
1349+ },
1350+ "workflow_arguments" : serializer .envelope ([], codec = "json" ),
1351+ "query_arguments" : serializer .envelope ([], codec = "json" ),
1352+ "payload_codec" : "json" ,
1353+ }
1354+
1355+ outcome = await worker ._run_query_task (task )
1356+
1357+ assert outcome == "completed"
1358+ mock_client .complete_query_task .assert_awaited_once_with (
1359+ query_task_id = "qt-export-history" ,
1360+ lease_owner = worker .worker_id ,
1361+ query_task_attempt = 1 ,
1362+ result = {
1363+ "activity_result" : "loaded" ,
1364+ "approved_by" : "alice" ,
1365+ "finished" : True ,
1366+ },
1367+ codec = "json" ,
1368+ workflow_id = "wf-replay-query" ,
1369+ run_id = "run-replay-query" ,
1370+ query_name = "state" ,
1371+ )
1372+ mock_client .fail_query_task .assert_not_called ()
1373+
1374+ @pytest .mark .asyncio
1375+ async def test_query_task_enriches_compact_activity_completion_from_export (
1376+ self , mock_client : AsyncMock
1377+ ) -> None :
1378+ worker = Worker (mock_client , task_queue = "q1" , workflows = [ReplayQuerySnapshotWorkflow ], activities = [])
1379+ task = {
1380+ "query_task_id" : "qt-compact-activity" ,
1381+ "query_task_attempt" : 1 ,
1382+ "workflow_type" : "replay-query-snapshot-wf" ,
1383+ "workflow_id" : "wf-compact-activity" ,
1384+ "run_id" : "run-compact-activity" ,
1385+ "query_name" : "state" ,
1386+ "history_events" : [
1387+ {
1388+ "event_type" : "ActivityCompleted" ,
1389+ "payload" : {
1390+ "sequence" : 1 ,
1391+ "activity_type" : "load-state" ,
1392+ },
1393+ },
1394+ ],
1395+ "history_export" : {
1396+ "payloads" : {"codec" : "json" },
1397+ "activities" : [
1398+ {
1399+ "sequence" : 1 ,
1400+ "activity_type" : "load-state" ,
1401+ "payload_codec" : "json" ,
1402+ "result" : serializer .encode ("loaded" , codec = "json" ),
1403+ },
1404+ ],
1405+ },
1406+ "workflow_arguments" : serializer .envelope ([], codec = "json" ),
1407+ "query_arguments" : serializer .envelope ([], codec = "json" ),
1408+ "payload_codec" : "json" ,
1409+ }
1410+
1411+ outcome = await worker ._run_query_task (task )
1412+
1413+ assert outcome == "completed"
1414+ mock_client .complete_query_task .assert_awaited_once_with (
1415+ query_task_id = "qt-compact-activity" ,
1416+ lease_owner = worker .worker_id ,
1417+ query_task_attempt = 1 ,
1418+ result = {
1419+ "activity_result" : "loaded" ,
1420+ "approved_by" : None ,
1421+ "finished" : False ,
1422+ },
1423+ codec = "json" ,
1424+ workflow_id = "wf-compact-activity" ,
1425+ run_id = "run-compact-activity" ,
1426+ query_name = "state" ,
1427+ )
1428+ mock_client .fail_query_task .assert_not_called ()
1429+
12711430 @pytest .mark .asyncio
12721431 async def test_query_task_awaits_async_query_result (self , mock_client : AsyncMock ) -> None :
12731432 worker = Worker (mock_client , task_queue = "q1" , workflows = [AsyncQueryWorkflow ], activities = [])
0 commit comments