azure-functions-durable 1.2.9__py3-none-any.whl → 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. azure/durable_functions/__init__.py +81 -81
  2. azure/durable_functions/constants.py +9 -9
  3. azure/durable_functions/decorators/__init__.py +3 -3
  4. azure/durable_functions/decorators/durable_app.py +260 -249
  5. azure/durable_functions/decorators/metadata.py +109 -109
  6. azure/durable_functions/entity.py +129 -125
  7. azure/durable_functions/models/DurableEntityContext.py +201 -201
  8. azure/durable_functions/models/DurableHttpRequest.py +58 -58
  9. azure/durable_functions/models/DurableOrchestrationBindings.py +66 -66
  10. azure/durable_functions/models/DurableOrchestrationClient.py +812 -781
  11. azure/durable_functions/models/DurableOrchestrationContext.py +761 -707
  12. azure/durable_functions/models/DurableOrchestrationStatus.py +156 -156
  13. azure/durable_functions/models/EntityStateResponse.py +23 -23
  14. azure/durable_functions/models/FunctionContext.py +7 -7
  15. azure/durable_functions/models/OrchestrationRuntimeStatus.py +32 -32
  16. azure/durable_functions/models/OrchestratorState.py +117 -116
  17. azure/durable_functions/models/PurgeHistoryResult.py +33 -33
  18. azure/durable_functions/models/ReplaySchema.py +9 -8
  19. azure/durable_functions/models/RetryOptions.py +69 -69
  20. azure/durable_functions/models/RpcManagementOptions.py +86 -86
  21. azure/durable_functions/models/Task.py +540 -426
  22. azure/durable_functions/models/TaskOrchestrationExecutor.py +352 -336
  23. azure/durable_functions/models/TokenSource.py +56 -56
  24. azure/durable_functions/models/__init__.py +26 -24
  25. azure/durable_functions/models/actions/Action.py +23 -23
  26. azure/durable_functions/models/actions/ActionType.py +18 -18
  27. azure/durable_functions/models/actions/CallActivityAction.py +41 -41
  28. azure/durable_functions/models/actions/CallActivityWithRetryAction.py +45 -45
  29. azure/durable_functions/models/actions/CallEntityAction.py +46 -46
  30. azure/durable_functions/models/actions/CallHttpAction.py +35 -35
  31. azure/durable_functions/models/actions/CallSubOrchestratorAction.py +40 -40
  32. azure/durable_functions/models/actions/CallSubOrchestratorWithRetryAction.py +44 -44
  33. azure/durable_functions/models/actions/CompoundAction.py +35 -35
  34. azure/durable_functions/models/actions/ContinueAsNewAction.py +36 -36
  35. azure/durable_functions/models/actions/CreateTimerAction.py +48 -48
  36. azure/durable_functions/models/actions/NoOpAction.py +35 -35
  37. azure/durable_functions/models/actions/SignalEntityAction.py +47 -47
  38. azure/durable_functions/models/actions/WaitForExternalEventAction.py +63 -63
  39. azure/durable_functions/models/actions/WhenAllAction.py +14 -14
  40. azure/durable_functions/models/actions/WhenAnyAction.py +14 -14
  41. azure/durable_functions/models/actions/__init__.py +24 -24
  42. azure/durable_functions/models/entities/EntityState.py +74 -74
  43. azure/durable_functions/models/entities/OperationResult.py +94 -76
  44. azure/durable_functions/models/entities/RequestMessage.py +53 -53
  45. azure/durable_functions/models/entities/ResponseMessage.py +48 -48
  46. azure/durable_functions/models/entities/Signal.py +62 -62
  47. azure/durable_functions/models/entities/__init__.py +17 -17
  48. azure/durable_functions/models/history/HistoryEvent.py +92 -92
  49. azure/durable_functions/models/history/HistoryEventType.py +27 -27
  50. azure/durable_functions/models/history/__init__.py +8 -8
  51. azure/durable_functions/models/utils/__init__.py +7 -7
  52. azure/durable_functions/models/utils/entity_utils.py +103 -91
  53. azure/durable_functions/models/utils/http_utils.py +80 -69
  54. azure/durable_functions/models/utils/json_utils.py +96 -56
  55. azure/durable_functions/orchestrator.py +73 -71
  56. azure/durable_functions/testing/OrchestratorGeneratorWrapper.py +42 -0
  57. azure/durable_functions/testing/__init__.py +6 -0
  58. {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/LICENSE +21 -21
  59. {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/METADATA +59 -58
  60. azure_functions_durable-1.3.0.dist-info/RECORD +103 -0
  61. {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/WHEEL +1 -1
  62. tests/models/test_DecoratorMetadata.py +135 -135
  63. tests/models/test_Decorators.py +107 -107
  64. tests/models/test_DurableOrchestrationBindings.py +68 -68
  65. tests/models/test_DurableOrchestrationClient.py +730 -730
  66. tests/models/test_DurableOrchestrationContext.py +102 -102
  67. tests/models/test_DurableOrchestrationStatus.py +59 -59
  68. tests/models/test_OrchestrationState.py +28 -28
  69. tests/models/test_RpcManagementOptions.py +79 -79
  70. tests/models/test_TokenSource.py +10 -10
  71. tests/orchestrator/models/OrchestrationInstance.py +18 -18
  72. tests/orchestrator/orchestrator_test_utils.py +130 -130
  73. tests/orchestrator/schemas/OrchetrationStateSchema.py +66 -66
  74. tests/orchestrator/test_call_http.py +235 -176
  75. tests/orchestrator/test_continue_as_new.py +67 -67
  76. tests/orchestrator/test_create_timer.py +126 -126
  77. tests/orchestrator/test_entity.py +397 -395
  78. tests/orchestrator/test_external_event.py +53 -53
  79. tests/orchestrator/test_fan_out_fan_in.py +175 -175
  80. tests/orchestrator/test_is_replaying_flag.py +101 -101
  81. tests/orchestrator/test_retries.py +308 -308
  82. tests/orchestrator/test_sequential_orchestrator.py +841 -841
  83. tests/orchestrator/test_sequential_orchestrator_custom_status.py +119 -119
  84. tests/orchestrator/test_sequential_orchestrator_with_retry.py +465 -465
  85. tests/orchestrator/test_serialization.py +30 -30
  86. tests/orchestrator/test_sub_orchestrator.py +95 -95
  87. tests/orchestrator/test_sub_orchestrator_with_retry.py +129 -129
  88. tests/orchestrator/test_task_any.py +60 -60
  89. tests/tasks/tasks_test_utils.py +17 -17
  90. tests/tasks/test_long_timers.py +70 -0
  91. tests/tasks/test_new_uuid.py +34 -34
  92. tests/test_utils/ContextBuilder.py +174 -174
  93. tests/test_utils/EntityContextBuilder.py +56 -56
  94. tests/test_utils/constants.py +1 -1
  95. tests/test_utils/json_utils.py +30 -30
  96. tests/test_utils/testClasses.py +56 -56
  97. tests/utils/__init__.py +1 -0
  98. tests/utils/test_entity_utils.py +24 -0
  99. azure_functions_durable-1.2.9.data/data/_manifest/bsi.json +0 -1
  100. azure_functions_durable-1.2.9.data/data/_manifest/manifest.cat +0 -0
  101. azure_functions_durable-1.2.9.data/data/_manifest/manifest.spdx.json +0 -11985
  102. azure_functions_durable-1.2.9.data/data/_manifest/manifest.spdx.json.sha256 +0 -1
  103. azure_functions_durable-1.2.9.dist-info/RECORD +0 -102
  104. {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.3.0.dist-info}/top_level.txt +0 -0
@@ -1,730 +1,730 @@
1
- import json
2
- from typing import Any
3
-
4
- import pytest
5
-
6
- from azure.durable_functions.models.OrchestrationRuntimeStatus import OrchestrationRuntimeStatus
7
- from azure.durable_functions.models.DurableOrchestrationClient \
8
- import DurableOrchestrationClient
9
- from azure.durable_functions.models.DurableOrchestrationStatus import DurableOrchestrationStatus
10
- from tests.conftest import replace_stand_in_bits
11
- from tests.test_utils.constants import RPC_BASE_URL
12
- from unittest.mock import Mock
13
-
14
- TEST_INSTANCE_ID = '2e2568e7-a906-43bd-8364-c81733c5891e'
15
- TEST_CREATED_TIME = '2020-01-01T05:00:00Z'
16
- TEST_LAST_UPDATED_TIME = '2020-01-01T05:00:00Z'
17
- MESSAGE_400 = 'instance failed or terminated'
18
- MESSAGE_404 = 'instance not found or pending'
19
- MESSAGE_500 = 'instance failed with unhandled exception'
20
- MESSAGE_501 = "well we didn't expect that"
21
-
22
- INSTANCE_ID = "2e2568e7-a906-43bd-8364-c81733c5891e"
23
- REASON = "Stuff"
24
-
25
- TEST_ORCHESTRATOR = "MyDurableOrchestrator"
26
- EXCEPTION_ORCHESTRATOR_NOT_FOUND_EXMESSAGE = "The function <orchestrator> doesn't exist,"\
27
- " is disabled, or is not an orchestrator function. Additional info: "\
28
- "the following are the known orchestrator functions: <list>"
29
- EXCEPTION_ORCHESTRATOR_NOT_FOUND_MESSAGE = "One or more of the arguments submitted is incorrect"
30
- EXCEPTION_TYPE_ORCHESTRATOR_NOT_FOUND = "System.ArgumentException"
31
- STACK_TRACE = "' at Microsoft.Azure.WebJobs.Extensions.DurableTask..."
32
-
33
- import azure.durable_functions as df
34
- import azure.functions as func
35
-
36
- app = df.DFApp(http_auth_level=func.AuthLevel.ANONYMOUS)
37
-
38
- @app.route(route="orchestrators/{functionName}", binding_arg_name="message")
39
- @app.durable_client_input(client_name="my_client")
40
- async def durable_trigger_with_pystein(req, my_client, message):
41
- return isinstance(my_client, DurableOrchestrationClient)
42
-
43
- @pytest.mark.asyncio
44
- async def test_pystein_provides_rich_client(binding_string):
45
- user_code = durable_trigger_with_pystein # returns True if DFClient is a rich binding
46
- args = []
47
- kwargs = {"req": None, "my_client": binding_string, "message": None }
48
- received_rich_client = await user_code._function._func(*args, **kwargs)
49
- assert received_rich_client
50
-
51
- class MockRequest:
52
- def __init__(self, expected_url: str, response: [int, any]):
53
- self._expected_url = expected_url
54
- self._response = response
55
- self._get_count = 0
56
-
57
- @property
58
- def get_count(self):
59
- return self._get_count
60
-
61
- async def get(self, url: str):
62
- self._get_count += 1
63
- assert url == self._expected_url
64
- return self._response
65
-
66
- async def delete(self, url: str):
67
- assert url == self._expected_url
68
- return self._response
69
-
70
- async def post(self, url: str, data: Any = None):
71
- assert url == self._expected_url
72
- return self._response
73
-
74
-
75
- def test_get_start_new_url(binding_string):
76
- client = DurableOrchestrationClient(binding_string)
77
- instance_id = "2e2568e7-a906-43bd-8364-c81733c5891e"
78
- function_name = "my_function"
79
- start_new_url = client._get_start_new_url(instance_id, function_name)
80
- expected_url = replace_stand_in_bits(
81
- f"{RPC_BASE_URL}orchestrators/{function_name}/{instance_id}")
82
- assert expected_url == start_new_url
83
-
84
-
85
- def test_get_input_returns_none_when_none_supplied():
86
- result = DurableOrchestrationClient._get_json_input(None)
87
- assert result is None
88
-
89
-
90
- def test_get_input_returns_json_string(binding_string):
91
- input_ = json.loads(binding_string)
92
- result = DurableOrchestrationClient._get_json_input(input_)
93
- input_as_string = json.dumps(input_)
94
- assert input_as_string == result
95
-
96
-
97
- def test_get_raise_event_url(binding_string):
98
- client = DurableOrchestrationClient(binding_string)
99
- instance_id = "2e2568e7-a906-43bd-8364-c81733c5891e"
100
- event_name = "test_event_name"
101
- task_hub_name = "test_task_hub"
102
- connection_name = "test_connection"
103
- raise_event_url = client._get_raise_event_url(instance_id, event_name, task_hub_name,
104
- connection_name)
105
-
106
- expected_url = replace_stand_in_bits(
107
- f"{RPC_BASE_URL}instances/{instance_id}/raiseEvent/{event_name}"
108
- f"?taskHub=test_task_hub&connection=test_connection")
109
-
110
- assert expected_url == raise_event_url
111
-
112
-
113
- def test_create_check_status_response(binding_string):
114
- client = DurableOrchestrationClient(binding_string)
115
- instance_id = "2e2568e7-a906-43bd-8364-c81733c5891e"
116
- request = Mock(url="http://test_azure.net/api/orchestrators/DurableOrchestrationTrigger")
117
- returned_response = client.create_check_status_response(request, instance_id)
118
-
119
- http_management_payload = {
120
- "id": instance_id,
121
- "statusQueryGetUri":
122
- r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
123
- r"2e2568e7-a906-43bd-8364-c81733c5891e?taskHub"
124
- r"=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE",
125
- "sendEventPostUri":
126
- r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
127
- r"2e2568e7-a906-43bd-8364-c81733c5891e/raiseEvent/{"
128
- r"eventName}?taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE",
129
- "terminatePostUri":
130
- r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
131
- r"2e2568e7-a906-43bd-8364-c81733c5891e/terminate"
132
- r"?reason={text}&taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE",
133
- "rewindPostUri":
134
- r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
135
- r"2e2568e7-a906-43bd-8364-c81733c5891e/rewind?reason"
136
- r"={text}&taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE",
137
- "purgeHistoryDeleteUri":
138
- r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
139
- r"2e2568e7-a906-43bd-8364-c81733c5891e"
140
- r"?taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE",
141
- "suspendPostUri":
142
- r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
143
- r"2e2568e7-a906-43bd-8364-c81733c5891e/suspend"
144
- r"?reason={text}&taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE",
145
- "resumePostUri":
146
- r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
147
- r"2e2568e7-a906-43bd-8364-c81733c5891e/resume"
148
- r"?reason={text}&taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE"
149
- }
150
- for key, _ in http_management_payload.items():
151
- http_management_payload[key] = replace_stand_in_bits(http_management_payload[key])
152
- expected_response = {
153
- "status_code": 202,
154
- "body": json.dumps(http_management_payload),
155
- "headers": {
156
- "Content-Type": "application/json",
157
- "Location": http_management_payload["statusQueryGetUri"],
158
- "Retry-After": "10",
159
- },
160
- }
161
-
162
- for k, v in expected_response.get("headers").items():
163
- assert v == returned_response.headers.get(k)
164
- assert expected_response.get("status_code") == returned_response.status_code
165
- assert expected_response.get("body") == returned_response.get_body().decode()
166
-
167
-
168
- @pytest.mark.asyncio
169
- async def test_get_202_get_status_success(binding_string):
170
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
171
- response=[202, dict(createdTime=TEST_CREATED_TIME,
172
- lastUpdatedTime=TEST_LAST_UPDATED_TIME,
173
- runtimeStatus="Running")])
174
- client = DurableOrchestrationClient(binding_string)
175
- client._get_async_request = mock_request.get
176
-
177
- result = await client.get_status(TEST_INSTANCE_ID)
178
- assert result is not None
179
- assert result.runtime_status.name == "Running"
180
-
181
-
182
- @pytest.mark.asyncio
183
- async def test_get_200_get_status_success(binding_string):
184
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
185
- response=[200, dict(createdTime=TEST_CREATED_TIME,
186
- lastUpdatedTime=TEST_LAST_UPDATED_TIME,
187
- runtimeStatus="Completed")])
188
- client = DurableOrchestrationClient(binding_string)
189
- client._get_async_request = mock_request.get
190
-
191
- result = await client.get_status(TEST_INSTANCE_ID)
192
- assert result is not None
193
- assert result.runtime_status.name == "Completed"
194
-
195
-
196
- @pytest.mark.asyncio
197
- async def test_get_500_get_status_failed(binding_string):
198
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
199
- response=[500, MESSAGE_500])
200
- client = DurableOrchestrationClient(binding_string)
201
- client._get_async_request = mock_request.get
202
-
203
- result = await client.get_status(TEST_INSTANCE_ID)
204
- assert result is not None
205
- assert result.message == MESSAGE_500
206
-
207
-
208
- @pytest.mark.asyncio
209
- async def test_get_400_get_status_failed(binding_string):
210
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
211
- response=[400, MESSAGE_400])
212
- client = DurableOrchestrationClient(binding_string)
213
- client._get_async_request = mock_request.get
214
-
215
- result = await client.get_status(TEST_INSTANCE_ID)
216
- assert result is not None
217
- assert result.message == MESSAGE_400
218
-
219
-
220
- @pytest.mark.asyncio
221
- async def test_get_404_get_status_failed(binding_string):
222
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
223
- response=[404, MESSAGE_404])
224
- client = DurableOrchestrationClient(binding_string)
225
- client._get_async_request = mock_request.get
226
-
227
- result = await client.get_status(TEST_INSTANCE_ID)
228
- assert result is not None
229
- assert result.message == MESSAGE_404
230
-
231
-
232
- @pytest.mark.asyncio
233
- async def test_get_501_get_status_failed(binding_string):
234
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
235
- response=[501, MESSAGE_501])
236
- client = DurableOrchestrationClient(binding_string)
237
- client._get_async_request = mock_request.get
238
-
239
- with pytest.raises(Exception):
240
- await client.get_status(TEST_INSTANCE_ID)
241
-
242
-
243
- @pytest.mark.asyncio
244
- async def test_get_200_get_status_by_success(binding_string):
245
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/?runtimeStatus=Running",
246
- response=[200, [dict(createdTime=TEST_CREATED_TIME,
247
- lastUpdatedTime=TEST_LAST_UPDATED_TIME,
248
- runtimeStatus="Running"),
249
- dict(createdTime=TEST_CREATED_TIME,
250
- lastUpdatedTime=TEST_LAST_UPDATED_TIME,
251
- runtimeStatus="Running")
252
- ]])
253
- client = DurableOrchestrationClient(binding_string)
254
- client._get_async_request = mock_request.get
255
-
256
- result = await client.get_status_by(runtime_status=[OrchestrationRuntimeStatus.Running])
257
- assert result is not None
258
- assert len(result) == 2
259
-
260
-
261
- @pytest.mark.asyncio
262
- async def test_get_500_get_status_by_failed(binding_string):
263
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/?runtimeStatus=Running",
264
- response=[500, MESSAGE_500])
265
- client = DurableOrchestrationClient(binding_string)
266
- client._get_async_request = mock_request.get
267
-
268
- with pytest.raises(Exception):
269
- await client.get_status_by(runtime_status=[OrchestrationRuntimeStatus.Running])
270
-
271
-
272
- @pytest.mark.asyncio
273
- async def test_get_200_get_status_all_success(binding_string):
274
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/",
275
- response=[200, [dict(createdTime=TEST_CREATED_TIME,
276
- lastUpdatedTime=TEST_LAST_UPDATED_TIME,
277
- runtimeStatus="Running"),
278
- dict(createdTime=TEST_CREATED_TIME,
279
- lastUpdatedTime=TEST_LAST_UPDATED_TIME,
280
- runtimeStatus="Running")
281
- ]])
282
- client = DurableOrchestrationClient(binding_string)
283
- client._get_async_request = mock_request.get
284
-
285
- result = await client.get_status_all()
286
- assert result is not None
287
- assert len(result) == 2
288
-
289
-
290
- @pytest.mark.asyncio
291
- async def test_get_500_get_status_all_failed(binding_string):
292
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/",
293
- response=[500, MESSAGE_500])
294
- client = DurableOrchestrationClient(binding_string)
295
- client._get_async_request = mock_request.get
296
-
297
- with pytest.raises(Exception):
298
- await client.get_status_all()
299
-
300
-
301
- @pytest.mark.asyncio
302
- async def test_delete_200_purge_instance_history(binding_string):
303
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
304
- response=[200, dict(instancesDeleted=1)])
305
- client = DurableOrchestrationClient(binding_string)
306
- client._delete_async_request = mock_request.delete
307
-
308
- result = await client.purge_instance_history(TEST_INSTANCE_ID)
309
- assert result is not None
310
- assert result.instances_deleted == 1
311
-
312
-
313
- @pytest.mark.asyncio
314
- async def test_delete_404_purge_instance_history(binding_string):
315
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
316
- response=[404, MESSAGE_404])
317
- client = DurableOrchestrationClient(binding_string)
318
- client._delete_async_request = mock_request.delete
319
-
320
- result = await client.purge_instance_history(TEST_INSTANCE_ID)
321
- assert result is not None
322
- assert result.instances_deleted == 0
323
-
324
-
325
- @pytest.mark.asyncio
326
- async def test_delete_500_purge_instance_history(binding_string):
327
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
328
- response=[500, MESSAGE_500])
329
- client = DurableOrchestrationClient(binding_string)
330
- client._delete_async_request = mock_request.delete
331
-
332
- with pytest.raises(Exception):
333
- await client.purge_instance_history(TEST_INSTANCE_ID)
334
-
335
-
336
- @pytest.mark.asyncio
337
- async def test_delete_200_purge_instance_history_by(binding_string):
338
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/?runtimeStatus=Running",
339
- response=[200, dict(instancesDeleted=1)])
340
- client = DurableOrchestrationClient(binding_string)
341
- client._delete_async_request = mock_request.delete
342
-
343
- result = await client.purge_instance_history_by(
344
- runtime_status=[OrchestrationRuntimeStatus.Running])
345
- assert result is not None
346
- assert result.instances_deleted == 1
347
-
348
-
349
- @pytest.mark.asyncio
350
- async def test_delete_404_purge_instance_history_by(binding_string):
351
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/?runtimeStatus=Running",
352
- response=[404, MESSAGE_404])
353
- client = DurableOrchestrationClient(binding_string)
354
- client._delete_async_request = mock_request.delete
355
-
356
- result = await client.purge_instance_history_by(
357
- runtime_status=[OrchestrationRuntimeStatus.Running])
358
- assert result is not None
359
- assert result.instances_deleted == 0
360
-
361
-
362
- @pytest.mark.asyncio
363
- async def test_delete_500_purge_instance_history_by(binding_string):
364
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/?runtimeStatus=Running",
365
- response=[500, MESSAGE_500])
366
- client = DurableOrchestrationClient(binding_string)
367
- client._delete_async_request = mock_request.delete
368
-
369
- with pytest.raises(Exception):
370
- await client.purge_instance_history_by(
371
- runtime_status=[OrchestrationRuntimeStatus.Running])
372
-
373
-
374
- @pytest.mark.asyncio
375
- async def test_post_202_terminate(binding_string):
376
- raw_reason = 'stuff and things'
377
- reason = 'stuff%20and%20things'
378
- mock_request = MockRequest(
379
- expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/terminate?reason={reason}",
380
- response=[202, None])
381
- client = DurableOrchestrationClient(binding_string)
382
- client._post_async_request = mock_request.post
383
-
384
- result = await client.terminate(TEST_INSTANCE_ID, raw_reason)
385
- assert result is None
386
-
387
-
388
- @pytest.mark.asyncio
389
- async def test_post_410_terminate(binding_string):
390
- raw_reason = 'stuff and things'
391
- reason = 'stuff%20and%20things'
392
- mock_request = MockRequest(
393
- expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/terminate?reason={reason}",
394
- response=[410, None])
395
- client = DurableOrchestrationClient(binding_string)
396
- client._post_async_request = mock_request.post
397
-
398
- result = await client.terminate(TEST_INSTANCE_ID, raw_reason)
399
- assert result is None
400
-
401
-
402
- @pytest.mark.asyncio
403
- async def test_post_404_terminate(binding_string):
404
- raw_reason = 'stuff and things'
405
- reason = 'stuff%20and%20things'
406
- mock_request = MockRequest(
407
- expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/terminate?reason={reason}",
408
- response=[404, MESSAGE_404])
409
- client = DurableOrchestrationClient(binding_string)
410
- client._post_async_request = mock_request.post
411
-
412
- with pytest.raises(Exception):
413
- await client.terminate(TEST_INSTANCE_ID, raw_reason)
414
-
415
-
416
- @pytest.mark.asyncio
417
- async def test_post_500_terminate(binding_string):
418
- raw_reason = 'stuff and things'
419
- reason = 'stuff%20and%20things'
420
- mock_request = MockRequest(
421
- expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/terminate?reason={reason}",
422
- response=[500, MESSAGE_500])
423
- client = DurableOrchestrationClient(binding_string)
424
- client._post_async_request = mock_request.post
425
-
426
- with pytest.raises(Exception):
427
- await client.terminate(TEST_INSTANCE_ID, raw_reason)
428
-
429
-
430
- @pytest.mark.asyncio
431
- async def test_wait_or_response_200_completed(binding_string):
432
- output = 'Some output'
433
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
434
- response=[200, dict(createdTime=TEST_CREATED_TIME,
435
- lastUpdatedTime=TEST_LAST_UPDATED_TIME,
436
- runtimeStatus="Completed",
437
- output=output)])
438
- client = DurableOrchestrationClient(binding_string)
439
- client._get_async_request = mock_request.get
440
-
441
- result = await client.wait_for_completion_or_create_check_status_response(
442
- None, TEST_INSTANCE_ID)
443
- assert result is not None
444
- assert result.status_code == 200
445
- assert result.mimetype == 'application/json'
446
- assert result.get_body().decode() == output
447
-
448
-
449
- @pytest.mark.asyncio
450
- async def test_wait_or_response_200_canceled(binding_string):
451
- status = dict(createdTime=TEST_CREATED_TIME,
452
- lastUpdatedTime=TEST_LAST_UPDATED_TIME,
453
- runtimeStatus="Canceled")
454
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
455
- response=[200, status])
456
- client = DurableOrchestrationClient(binding_string)
457
- client._get_async_request = mock_request.get
458
-
459
- result = await client.wait_for_completion_or_create_check_status_response(
460
- None, TEST_INSTANCE_ID)
461
- assert result is not None
462
- assert result.status_code == 200
463
- assert result.mimetype == 'application/json'
464
- assert json.loads(result.get_body().decode()) == DurableOrchestrationStatus.from_json(
465
- status).to_json()
466
-
467
-
468
- @pytest.mark.asyncio
469
- async def test_wait_or_response_200_terminated(binding_string):
470
- status = dict(createdTime=TEST_CREATED_TIME,
471
- lastUpdatedTime=TEST_LAST_UPDATED_TIME,
472
- runtimeStatus="Terminated")
473
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
474
- response=[200, status])
475
- client = DurableOrchestrationClient(binding_string)
476
- client._get_async_request = mock_request.get
477
-
478
- result = await client.wait_for_completion_or_create_check_status_response(
479
- None, TEST_INSTANCE_ID)
480
- assert result is not None
481
- assert result.status_code == 200
482
- assert result.mimetype == 'application/json'
483
- assert json.loads(result.get_body().decode()) == DurableOrchestrationStatus.from_json(
484
- status).to_json()
485
-
486
-
487
- @pytest.mark.asyncio
488
- async def test_wait_or_response_200_failed(binding_string):
489
- status = dict(createdTime=TEST_CREATED_TIME,
490
- lastUpdatedTime=TEST_LAST_UPDATED_TIME,
491
- runtimeStatus="Failed")
492
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
493
- response=[200, status])
494
- client = DurableOrchestrationClient(binding_string)
495
- client._get_async_request = mock_request.get
496
-
497
- result = await client.wait_for_completion_or_create_check_status_response(
498
- None, TEST_INSTANCE_ID)
499
- assert result is not None
500
- assert result.status_code == 500
501
- assert result.mimetype == 'application/json'
502
- assert json.loads(result.get_body().decode()) == DurableOrchestrationStatus.from_json(
503
- status).to_json()
504
-
505
-
506
- @pytest.mark.asyncio
507
- async def test_wait_or_response_check_status_response(binding_string):
508
- status = dict(createdTime=TEST_CREATED_TIME,
509
- lastUpdatedTime=TEST_LAST_UPDATED_TIME,
510
- runtimeStatus="Running")
511
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
512
- response=[200, status])
513
- client = DurableOrchestrationClient(binding_string)
514
- client._get_async_request = mock_request.get
515
-
516
- request = Mock(url="http://test_azure.net/api/orchestrators/DurableOrchestrationTrigger")
517
- result = await client.wait_for_completion_or_create_check_status_response(
518
- request, TEST_INSTANCE_ID, timeout_in_milliseconds=2000)
519
- assert result is not None
520
- assert mock_request.get_count == 3
521
-
522
-
523
- @pytest.mark.asyncio
524
- async def test_wait_or_response_null_request(binding_string):
525
- status = dict(createdTime=TEST_CREATED_TIME,
526
- lastUpdatedTime=TEST_LAST_UPDATED_TIME,
527
- runtimeStatus="Running")
528
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
529
- response=[200, status])
530
- client = DurableOrchestrationClient(binding_string)
531
- client._get_async_request = mock_request.get
532
-
533
- with pytest.raises(Exception):
534
- await client.wait_for_completion_or_create_check_status_response(
535
- None, TEST_INSTANCE_ID, timeout_in_milliseconds=500)
536
-
537
- @pytest.mark.asyncio
538
- async def test_start_new_orchestrator_not_found(binding_string):
539
- """Test that we throw the right exception when the orchestrator is not found.
540
- """
541
- status = dict(ExceptionMessage=EXCEPTION_ORCHESTRATOR_NOT_FOUND_EXMESSAGE,
542
- StackTrace=STACK_TRACE,
543
- Message=EXCEPTION_ORCHESTRATOR_NOT_FOUND_MESSAGE,
544
- ExceptionType=EXCEPTION_TYPE_ORCHESTRATOR_NOT_FOUND)
545
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}orchestrators/{TEST_ORCHESTRATOR}",
546
- response=[400, status])
547
- client = DurableOrchestrationClient(binding_string)
548
- client._post_async_request = mock_request.post
549
-
550
- with pytest.raises(Exception) as ex:
551
- await client.start_new(TEST_ORCHESTRATOR)
552
- ex.match(EXCEPTION_ORCHESTRATOR_NOT_FOUND_EXMESSAGE)
553
-
554
-
555
- @pytest.mark.asyncio
556
- async def test_start_new_orchestrator_internal_exception(binding_string):
557
- """Test that we throw the right exception when the extension fails internally.
558
- """
559
- status = dict(ExceptionMessage=EXCEPTION_ORCHESTRATOR_NOT_FOUND_EXMESSAGE,
560
- StackTrace=STACK_TRACE,
561
- Message=EXCEPTION_ORCHESTRATOR_NOT_FOUND_MESSAGE,
562
- ExceptionType=EXCEPTION_TYPE_ORCHESTRATOR_NOT_FOUND)
563
- mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}orchestrators/{TEST_ORCHESTRATOR}",
564
- response=[500, status])
565
- client = DurableOrchestrationClient(binding_string)
566
- client._post_async_request = mock_request.post
567
-
568
- status_str = str(status)
569
- with pytest.raises(Exception) as ex:
570
- await client.start_new(TEST_ORCHESTRATOR)
571
- ex.match(status_str)
572
-
573
- @pytest.mark.asyncio
574
- async def test_rewind_works_under_200_and_200_http_codes(binding_string):
575
- """Tests that the rewind API works as expected under 'successful' http codes: 200, 202"""
576
- client = DurableOrchestrationClient(binding_string)
577
- for code in [200, 202]:
578
- mock_request = MockRequest(
579
- expected_url=f"{RPC_BASE_URL}instances/{INSTANCE_ID}/rewind?reason={REASON}",
580
- response=[code, ""])
581
- client._post_async_request = mock_request.post
582
- result = await client.rewind(INSTANCE_ID, REASON)
583
- assert result is None
584
-
585
- @pytest.mark.asyncio
586
- async def test_rewind_throws_exception_during_404_410_and_500_errors(binding_string):
587
- """Tests the behaviour of rewind under 'exception' http codes: 404, 410, 500"""
588
- client = DurableOrchestrationClient(binding_string)
589
- codes = [404, 410, 500]
590
- exception_strs = [
591
- f"No instance with ID {INSTANCE_ID} found.",
592
- "The rewind operation is only supported on failed orchestration instances.",
593
- "Something went wrong"
594
- ]
595
- for http_code, expected_exception_str in zip(codes, exception_strs):
596
- mock_request = MockRequest(
597
- expected_url=f"{RPC_BASE_URL}instances/{INSTANCE_ID}/rewind?reason={REASON}",
598
- response=[http_code, "Something went wrong"])
599
- client._post_async_request = mock_request.post
600
-
601
- with pytest.raises(Exception) as ex:
602
- await client.rewind(INSTANCE_ID, REASON)
603
- ex_message = str(ex.value)
604
- assert ex_message == expected_exception_str
605
-
606
- @pytest.mark.asyncio
607
- async def test_rewind_with_no_rpc_endpoint(binding_string):
608
- """Tests the behaviour of rewind without an RPC endpoint / under the legacy HTTP endpoint."""
609
- client = DurableOrchestrationClient(binding_string)
610
- mock_request = MockRequest(
611
- expected_url=f"{RPC_BASE_URL}instances/{INSTANCE_ID}/rewind?reason={REASON}",
612
- response=[-1, ""])
613
- client._post_async_request = mock_request.post
614
- client._orchestration_bindings._rpc_base_url = None
615
- expected_exception_str = "The Python SDK only supports RPC endpoints."\
616
- + "Please remove the `localRpcEnabled` setting from host.json"
617
- with pytest.raises(Exception) as ex:
618
- await client.rewind(INSTANCE_ID, REASON)
619
- ex_message = str(ex.value)
620
- assert ex_message == expected_exception_str
621
-
622
- @pytest.mark.asyncio
623
- async def test_post_202_suspend(binding_string):
624
- raw_reason = 'stuff and things'
625
- reason = 'stuff%20and%20things'
626
- mock_request = MockRequest(
627
- expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/suspend?reason={reason}",
628
- response=[202, None])
629
- client = DurableOrchestrationClient(binding_string)
630
- client._post_async_request = mock_request.post
631
-
632
- result = await client.suspend(TEST_INSTANCE_ID, raw_reason)
633
- assert result is None
634
-
635
-
636
- @pytest.mark.asyncio
637
- async def test_post_410_suspend(binding_string):
638
- raw_reason = 'stuff and things'
639
- reason = 'stuff%20and%20things'
640
- mock_request = MockRequest(
641
- expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/suspend?reason={reason}",
642
- response=[410, None])
643
- client = DurableOrchestrationClient(binding_string)
644
- client._post_async_request = mock_request.post
645
-
646
- result = await client.suspend(TEST_INSTANCE_ID, raw_reason)
647
- assert result is None
648
-
649
-
650
- @pytest.mark.asyncio
651
- async def test_post_404_suspend(binding_string):
652
- raw_reason = 'stuff and things'
653
- reason = 'stuff%20and%20things'
654
- mock_request = MockRequest(
655
- expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/suspend?reason={reason}",
656
- response=[404, MESSAGE_404])
657
- client = DurableOrchestrationClient(binding_string)
658
- client._post_async_request = mock_request.post
659
-
660
- with pytest.raises(Exception):
661
- await client.suspend(TEST_INSTANCE_ID, raw_reason)
662
-
663
-
664
- @pytest.mark.asyncio
665
- async def test_post_500_suspend(binding_string):
666
- raw_reason = 'stuff and things'
667
- reason = 'stuff%20and%20things'
668
- mock_request = MockRequest(
669
- expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/suspend?reason={reason}",
670
- response=[500, MESSAGE_500])
671
- client = DurableOrchestrationClient(binding_string)
672
- client._post_async_request = mock_request.post
673
-
674
- with pytest.raises(Exception):
675
- await client.suspend(TEST_INSTANCE_ID, raw_reason)
676
-
677
- @pytest.mark.asyncio
678
- async def test_post_202_resume(binding_string):
679
- raw_reason = 'stuff and things'
680
- reason = 'stuff%20and%20things'
681
- mock_request = MockRequest(
682
- expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/resume?reason={reason}",
683
- response=[202, None])
684
- client = DurableOrchestrationClient(binding_string)
685
- client._post_async_request = mock_request.post
686
-
687
- result = await client.resume(TEST_INSTANCE_ID, raw_reason)
688
- assert result is None
689
-
690
-
691
- @pytest.mark.asyncio
692
- async def test_post_410_resume(binding_string):
693
- raw_reason = 'stuff and things'
694
- reason = 'stuff%20and%20things'
695
- mock_request = MockRequest(
696
- expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/resume?reason={reason}",
697
- response=[410, None])
698
- client = DurableOrchestrationClient(binding_string)
699
- client._post_async_request = mock_request.post
700
-
701
- result = await client.resume(TEST_INSTANCE_ID, raw_reason)
702
- assert result is None
703
-
704
-
705
- @pytest.mark.asyncio
706
- async def test_post_404_resume(binding_string):
707
- raw_reason = 'stuff and things'
708
- reason = 'stuff%20and%20things'
709
- mock_request = MockRequest(
710
- expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/resume?reason={reason}",
711
- response=[404, MESSAGE_404])
712
- client = DurableOrchestrationClient(binding_string)
713
- client._post_async_request = mock_request.post
714
-
715
- with pytest.raises(Exception):
716
- await client.resume(TEST_INSTANCE_ID, raw_reason)
717
-
718
-
719
- @pytest.mark.asyncio
720
- async def test_post_500_resume(binding_string):
721
- raw_reason = 'stuff and things'
722
- reason = 'stuff%20and%20things'
723
- mock_request = MockRequest(
724
- expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/resume?reason={reason}",
725
- response=[500, MESSAGE_500])
726
- client = DurableOrchestrationClient(binding_string)
727
- client._post_async_request = mock_request.post
728
-
729
- with pytest.raises(Exception):
730
- await client.resume(TEST_INSTANCE_ID, raw_reason)
1
+ import json
2
+ from typing import Any
3
+
4
+ import pytest
5
+
6
+ from azure.durable_functions.models.OrchestrationRuntimeStatus import OrchestrationRuntimeStatus
7
+ from azure.durable_functions.models.DurableOrchestrationClient \
8
+ import DurableOrchestrationClient
9
+ from azure.durable_functions.models.DurableOrchestrationStatus import DurableOrchestrationStatus
10
+ from tests.conftest import replace_stand_in_bits
11
+ from tests.test_utils.constants import RPC_BASE_URL
12
+ from unittest.mock import Mock
13
+
14
+ TEST_INSTANCE_ID = '2e2568e7-a906-43bd-8364-c81733c5891e'
15
+ TEST_CREATED_TIME = '2020-01-01T05:00:00Z'
16
+ TEST_LAST_UPDATED_TIME = '2020-01-01T05:00:00Z'
17
+ MESSAGE_400 = 'instance failed or terminated'
18
+ MESSAGE_404 = 'instance not found or pending'
19
+ MESSAGE_500 = 'instance failed with unhandled exception'
20
+ MESSAGE_501 = "well we didn't expect that"
21
+
22
+ INSTANCE_ID = "2e2568e7-a906-43bd-8364-c81733c5891e"
23
+ REASON = "Stuff"
24
+
25
+ TEST_ORCHESTRATOR = "MyDurableOrchestrator"
26
+ EXCEPTION_ORCHESTRATOR_NOT_FOUND_EXMESSAGE = "The function <orchestrator> doesn't exist,"\
27
+ " is disabled, or is not an orchestrator function. Additional info: "\
28
+ "the following are the known orchestrator functions: <list>"
29
+ EXCEPTION_ORCHESTRATOR_NOT_FOUND_MESSAGE = "One or more of the arguments submitted is incorrect"
30
+ EXCEPTION_TYPE_ORCHESTRATOR_NOT_FOUND = "System.ArgumentException"
31
+ STACK_TRACE = "' at Microsoft.Azure.WebJobs.Extensions.DurableTask..."
32
+
33
+ import azure.durable_functions as df
34
+ import azure.functions as func
35
+
36
+ app = df.DFApp(http_auth_level=func.AuthLevel.ANONYMOUS)
37
+
38
+ @app.route(route="orchestrators/{functionName}", binding_arg_name="message")
39
+ @app.durable_client_input(client_name="my_client")
40
+ async def durable_trigger_with_pystein(req, my_client, message):
41
+ return isinstance(my_client, DurableOrchestrationClient)
42
+
43
+ @pytest.mark.asyncio
44
+ async def test_pystein_provides_rich_client(binding_string):
45
+ user_code = durable_trigger_with_pystein # returns True if DFClient is a rich binding
46
+ args = []
47
+ kwargs = {"req": None, "my_client": binding_string, "message": None }
48
+ received_rich_client = await user_code._function._func(*args, **kwargs)
49
+ assert received_rich_client
50
+
51
+ class MockRequest:
52
+ def __init__(self, expected_url: str, response: [int, any]):
53
+ self._expected_url = expected_url
54
+ self._response = response
55
+ self._get_count = 0
56
+
57
+ @property
58
+ def get_count(self):
59
+ return self._get_count
60
+
61
+ async def get(self, url: str):
62
+ self._get_count += 1
63
+ assert url == self._expected_url
64
+ return self._response
65
+
66
+ async def delete(self, url: str):
67
+ assert url == self._expected_url
68
+ return self._response
69
+
70
+ async def post(self, url: str, data: Any = None, trace_parent: str = None, trace_state: str = None):
71
+ assert url == self._expected_url
72
+ return self._response
73
+
74
+
75
+ def test_get_start_new_url(binding_string):
76
+ client = DurableOrchestrationClient(binding_string)
77
+ instance_id = "2e2568e7-a906-43bd-8364-c81733c5891e"
78
+ function_name = "my_function"
79
+ start_new_url = client._get_start_new_url(instance_id, function_name)
80
+ expected_url = replace_stand_in_bits(
81
+ f"{RPC_BASE_URL}orchestrators/{function_name}/{instance_id}")
82
+ assert expected_url == start_new_url
83
+
84
+
85
+ def test_get_input_returns_none_when_none_supplied():
86
+ result = DurableOrchestrationClient._get_json_input(None)
87
+ assert result is None
88
+
89
+
90
+ def test_get_input_returns_json_string(binding_string):
91
+ input_ = json.loads(binding_string)
92
+ result = DurableOrchestrationClient._get_json_input(input_)
93
+ input_as_string = json.dumps(input_)
94
+ assert input_as_string == result
95
+
96
+
97
+ def test_get_raise_event_url(binding_string):
98
+ client = DurableOrchestrationClient(binding_string)
99
+ instance_id = "2e2568e7-a906-43bd-8364-c81733c5891e"
100
+ event_name = "test_event_name"
101
+ task_hub_name = "test_task_hub"
102
+ connection_name = "test_connection"
103
+ raise_event_url = client._get_raise_event_url(instance_id, event_name, task_hub_name,
104
+ connection_name)
105
+
106
+ expected_url = replace_stand_in_bits(
107
+ f"{RPC_BASE_URL}instances/{instance_id}/raiseEvent/{event_name}"
108
+ f"?taskHub=test_task_hub&connection=test_connection")
109
+
110
+ assert expected_url == raise_event_url
111
+
112
+
113
+ def test_create_check_status_response(binding_string):
114
+ client = DurableOrchestrationClient(binding_string)
115
+ instance_id = "2e2568e7-a906-43bd-8364-c81733c5891e"
116
+ request = Mock(url="http://test_azure.net/api/orchestrators/DurableOrchestrationTrigger")
117
+ returned_response = client.create_check_status_response(request, instance_id)
118
+
119
+ http_management_payload = {
120
+ "id": instance_id,
121
+ "statusQueryGetUri":
122
+ r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
123
+ r"2e2568e7-a906-43bd-8364-c81733c5891e?taskHub"
124
+ r"=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE",
125
+ "sendEventPostUri":
126
+ r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
127
+ r"2e2568e7-a906-43bd-8364-c81733c5891e/raiseEvent/{"
128
+ r"eventName}?taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE",
129
+ "terminatePostUri":
130
+ r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
131
+ r"2e2568e7-a906-43bd-8364-c81733c5891e/terminate"
132
+ r"?reason={text}&taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE",
133
+ "rewindPostUri":
134
+ r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
135
+ r"2e2568e7-a906-43bd-8364-c81733c5891e/rewind?reason"
136
+ r"={text}&taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE",
137
+ "purgeHistoryDeleteUri":
138
+ r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
139
+ r"2e2568e7-a906-43bd-8364-c81733c5891e"
140
+ r"?taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE",
141
+ "suspendPostUri":
142
+ r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
143
+ r"2e2568e7-a906-43bd-8364-c81733c5891e/suspend"
144
+ r"?reason={text}&taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE",
145
+ "resumePostUri":
146
+ r"http://test_azure.net/runtime/webhooks/durabletask/instances/"
147
+ r"2e2568e7-a906-43bd-8364-c81733c5891e/resume"
148
+ r"?reason={text}&taskHub=TASK_HUB_NAME&connection=Storage&code=AUTH_CODE"
149
+ }
150
+ for key, _ in http_management_payload.items():
151
+ http_management_payload[key] = replace_stand_in_bits(http_management_payload[key])
152
+ expected_response = {
153
+ "status_code": 202,
154
+ "body": json.dumps(http_management_payload),
155
+ "headers": {
156
+ "Content-Type": "application/json",
157
+ "Location": http_management_payload["statusQueryGetUri"],
158
+ "Retry-After": "10",
159
+ },
160
+ }
161
+
162
+ for k, v in expected_response.get("headers").items():
163
+ assert v == returned_response.headers.get(k)
164
+ assert expected_response.get("status_code") == returned_response.status_code
165
+ assert expected_response.get("body") == returned_response.get_body().decode()
166
+
167
+
168
+ @pytest.mark.asyncio
169
+ async def test_get_202_get_status_success(binding_string):
170
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
171
+ response=[202, dict(createdTime=TEST_CREATED_TIME,
172
+ lastUpdatedTime=TEST_LAST_UPDATED_TIME,
173
+ runtimeStatus="Running")])
174
+ client = DurableOrchestrationClient(binding_string)
175
+ client._get_async_request = mock_request.get
176
+
177
+ result = await client.get_status(TEST_INSTANCE_ID)
178
+ assert result is not None
179
+ assert result.runtime_status.name == "Running"
180
+
181
+
182
+ @pytest.mark.asyncio
183
+ async def test_get_200_get_status_success(binding_string):
184
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
185
+ response=[200, dict(createdTime=TEST_CREATED_TIME,
186
+ lastUpdatedTime=TEST_LAST_UPDATED_TIME,
187
+ runtimeStatus="Completed")])
188
+ client = DurableOrchestrationClient(binding_string)
189
+ client._get_async_request = mock_request.get
190
+
191
+ result = await client.get_status(TEST_INSTANCE_ID)
192
+ assert result is not None
193
+ assert result.runtime_status.name == "Completed"
194
+
195
+
196
+ @pytest.mark.asyncio
197
+ async def test_get_500_get_status_failed(binding_string):
198
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
199
+ response=[500, MESSAGE_500])
200
+ client = DurableOrchestrationClient(binding_string)
201
+ client._get_async_request = mock_request.get
202
+
203
+ result = await client.get_status(TEST_INSTANCE_ID)
204
+ assert result is not None
205
+ assert result.message == MESSAGE_500
206
+
207
+
208
+ @pytest.mark.asyncio
209
+ async def test_get_400_get_status_failed(binding_string):
210
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
211
+ response=[400, MESSAGE_400])
212
+ client = DurableOrchestrationClient(binding_string)
213
+ client._get_async_request = mock_request.get
214
+
215
+ result = await client.get_status(TEST_INSTANCE_ID)
216
+ assert result is not None
217
+ assert result.message == MESSAGE_400
218
+
219
+
220
+ @pytest.mark.asyncio
221
+ async def test_get_404_get_status_failed(binding_string):
222
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
223
+ response=[404, MESSAGE_404])
224
+ client = DurableOrchestrationClient(binding_string)
225
+ client._get_async_request = mock_request.get
226
+
227
+ result = await client.get_status(TEST_INSTANCE_ID)
228
+ assert result is not None
229
+ assert result.message == MESSAGE_404
230
+
231
+
232
+ @pytest.mark.asyncio
233
+ async def test_get_501_get_status_failed(binding_string):
234
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
235
+ response=[501, MESSAGE_501])
236
+ client = DurableOrchestrationClient(binding_string)
237
+ client._get_async_request = mock_request.get
238
+
239
+ with pytest.raises(Exception):
240
+ await client.get_status(TEST_INSTANCE_ID)
241
+
242
+
243
+ @pytest.mark.asyncio
244
+ async def test_get_200_get_status_by_success(binding_string):
245
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/?runtimeStatus=Running",
246
+ response=[200, [dict(createdTime=TEST_CREATED_TIME,
247
+ lastUpdatedTime=TEST_LAST_UPDATED_TIME,
248
+ runtimeStatus="Running"),
249
+ dict(createdTime=TEST_CREATED_TIME,
250
+ lastUpdatedTime=TEST_LAST_UPDATED_TIME,
251
+ runtimeStatus="Running")
252
+ ]])
253
+ client = DurableOrchestrationClient(binding_string)
254
+ client._get_async_request = mock_request.get
255
+
256
+ result = await client.get_status_by(runtime_status=[OrchestrationRuntimeStatus.Running])
257
+ assert result is not None
258
+ assert len(result) == 2
259
+
260
+
261
+ @pytest.mark.asyncio
262
+ async def test_get_500_get_status_by_failed(binding_string):
263
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/?runtimeStatus=Running",
264
+ response=[500, MESSAGE_500])
265
+ client = DurableOrchestrationClient(binding_string)
266
+ client._get_async_request = mock_request.get
267
+
268
+ with pytest.raises(Exception):
269
+ await client.get_status_by(runtime_status=[OrchestrationRuntimeStatus.Running])
270
+
271
+
272
+ @pytest.mark.asyncio
273
+ async def test_get_200_get_status_all_success(binding_string):
274
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/",
275
+ response=[200, [dict(createdTime=TEST_CREATED_TIME,
276
+ lastUpdatedTime=TEST_LAST_UPDATED_TIME,
277
+ runtimeStatus="Running"),
278
+ dict(createdTime=TEST_CREATED_TIME,
279
+ lastUpdatedTime=TEST_LAST_UPDATED_TIME,
280
+ runtimeStatus="Running")
281
+ ]])
282
+ client = DurableOrchestrationClient(binding_string)
283
+ client._get_async_request = mock_request.get
284
+
285
+ result = await client.get_status_all()
286
+ assert result is not None
287
+ assert len(result) == 2
288
+
289
+
290
+ @pytest.mark.asyncio
291
+ async def test_get_500_get_status_all_failed(binding_string):
292
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/",
293
+ response=[500, MESSAGE_500])
294
+ client = DurableOrchestrationClient(binding_string)
295
+ client._get_async_request = mock_request.get
296
+
297
+ with pytest.raises(Exception):
298
+ await client.get_status_all()
299
+
300
+
301
+ @pytest.mark.asyncio
302
+ async def test_delete_200_purge_instance_history(binding_string):
303
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
304
+ response=[200, dict(instancesDeleted=1)])
305
+ client = DurableOrchestrationClient(binding_string)
306
+ client._delete_async_request = mock_request.delete
307
+
308
+ result = await client.purge_instance_history(TEST_INSTANCE_ID)
309
+ assert result is not None
310
+ assert result.instances_deleted == 1
311
+
312
+
313
+ @pytest.mark.asyncio
314
+ async def test_delete_404_purge_instance_history(binding_string):
315
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
316
+ response=[404, MESSAGE_404])
317
+ client = DurableOrchestrationClient(binding_string)
318
+ client._delete_async_request = mock_request.delete
319
+
320
+ result = await client.purge_instance_history(TEST_INSTANCE_ID)
321
+ assert result is not None
322
+ assert result.instances_deleted == 0
323
+
324
+
325
+ @pytest.mark.asyncio
326
+ async def test_delete_500_purge_instance_history(binding_string):
327
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
328
+ response=[500, MESSAGE_500])
329
+ client = DurableOrchestrationClient(binding_string)
330
+ client._delete_async_request = mock_request.delete
331
+
332
+ with pytest.raises(Exception):
333
+ await client.purge_instance_history(TEST_INSTANCE_ID)
334
+
335
+
336
+ @pytest.mark.asyncio
337
+ async def test_delete_200_purge_instance_history_by(binding_string):
338
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/?runtimeStatus=Running",
339
+ response=[200, dict(instancesDeleted=1)])
340
+ client = DurableOrchestrationClient(binding_string)
341
+ client._delete_async_request = mock_request.delete
342
+
343
+ result = await client.purge_instance_history_by(
344
+ runtime_status=[OrchestrationRuntimeStatus.Running])
345
+ assert result is not None
346
+ assert result.instances_deleted == 1
347
+
348
+
349
+ @pytest.mark.asyncio
350
+ async def test_delete_404_purge_instance_history_by(binding_string):
351
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/?runtimeStatus=Running",
352
+ response=[404, MESSAGE_404])
353
+ client = DurableOrchestrationClient(binding_string)
354
+ client._delete_async_request = mock_request.delete
355
+
356
+ result = await client.purge_instance_history_by(
357
+ runtime_status=[OrchestrationRuntimeStatus.Running])
358
+ assert result is not None
359
+ assert result.instances_deleted == 0
360
+
361
+
362
+ @pytest.mark.asyncio
363
+ async def test_delete_500_purge_instance_history_by(binding_string):
364
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/?runtimeStatus=Running",
365
+ response=[500, MESSAGE_500])
366
+ client = DurableOrchestrationClient(binding_string)
367
+ client._delete_async_request = mock_request.delete
368
+
369
+ with pytest.raises(Exception):
370
+ await client.purge_instance_history_by(
371
+ runtime_status=[OrchestrationRuntimeStatus.Running])
372
+
373
+
374
+ @pytest.mark.asyncio
375
+ async def test_post_202_terminate(binding_string):
376
+ raw_reason = 'stuff and things'
377
+ reason = 'stuff%20and%20things'
378
+ mock_request = MockRequest(
379
+ expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/terminate?reason={reason}",
380
+ response=[202, None])
381
+ client = DurableOrchestrationClient(binding_string)
382
+ client._post_async_request = mock_request.post
383
+
384
+ result = await client.terminate(TEST_INSTANCE_ID, raw_reason)
385
+ assert result is None
386
+
387
+
388
+ @pytest.mark.asyncio
389
+ async def test_post_410_terminate(binding_string):
390
+ raw_reason = 'stuff and things'
391
+ reason = 'stuff%20and%20things'
392
+ mock_request = MockRequest(
393
+ expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/terminate?reason={reason}",
394
+ response=[410, None])
395
+ client = DurableOrchestrationClient(binding_string)
396
+ client._post_async_request = mock_request.post
397
+
398
+ result = await client.terminate(TEST_INSTANCE_ID, raw_reason)
399
+ assert result is None
400
+
401
+
402
+ @pytest.mark.asyncio
403
+ async def test_post_404_terminate(binding_string):
404
+ raw_reason = 'stuff and things'
405
+ reason = 'stuff%20and%20things'
406
+ mock_request = MockRequest(
407
+ expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/terminate?reason={reason}",
408
+ response=[404, MESSAGE_404])
409
+ client = DurableOrchestrationClient(binding_string)
410
+ client._post_async_request = mock_request.post
411
+
412
+ with pytest.raises(Exception):
413
+ await client.terminate(TEST_INSTANCE_ID, raw_reason)
414
+
415
+
416
+ @pytest.mark.asyncio
417
+ async def test_post_500_terminate(binding_string):
418
+ raw_reason = 'stuff and things'
419
+ reason = 'stuff%20and%20things'
420
+ mock_request = MockRequest(
421
+ expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/terminate?reason={reason}",
422
+ response=[500, MESSAGE_500])
423
+ client = DurableOrchestrationClient(binding_string)
424
+ client._post_async_request = mock_request.post
425
+
426
+ with pytest.raises(Exception):
427
+ await client.terminate(TEST_INSTANCE_ID, raw_reason)
428
+
429
+
430
+ @pytest.mark.asyncio
431
+ async def test_wait_or_response_200_completed(binding_string):
432
+ output = 'Some output'
433
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
434
+ response=[200, dict(createdTime=TEST_CREATED_TIME,
435
+ lastUpdatedTime=TEST_LAST_UPDATED_TIME,
436
+ runtimeStatus="Completed",
437
+ output=output)])
438
+ client = DurableOrchestrationClient(binding_string)
439
+ client._get_async_request = mock_request.get
440
+
441
+ result = await client.wait_for_completion_or_create_check_status_response(
442
+ None, TEST_INSTANCE_ID)
443
+ assert result is not None
444
+ assert result.status_code == 200
445
+ assert result.mimetype == 'application/json'
446
+ assert result.get_body().decode() == output
447
+
448
+
449
+ @pytest.mark.asyncio
450
+ async def test_wait_or_response_200_canceled(binding_string):
451
+ status = dict(createdTime=TEST_CREATED_TIME,
452
+ lastUpdatedTime=TEST_LAST_UPDATED_TIME,
453
+ runtimeStatus="Canceled")
454
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
455
+ response=[200, status])
456
+ client = DurableOrchestrationClient(binding_string)
457
+ client._get_async_request = mock_request.get
458
+
459
+ result = await client.wait_for_completion_or_create_check_status_response(
460
+ None, TEST_INSTANCE_ID)
461
+ assert result is not None
462
+ assert result.status_code == 200
463
+ assert result.mimetype == 'application/json'
464
+ assert json.loads(result.get_body().decode()) == DurableOrchestrationStatus.from_json(
465
+ status).to_json()
466
+
467
+
468
+ @pytest.mark.asyncio
469
+ async def test_wait_or_response_200_terminated(binding_string):
470
+ status = dict(createdTime=TEST_CREATED_TIME,
471
+ lastUpdatedTime=TEST_LAST_UPDATED_TIME,
472
+ runtimeStatus="Terminated")
473
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
474
+ response=[200, status])
475
+ client = DurableOrchestrationClient(binding_string)
476
+ client._get_async_request = mock_request.get
477
+
478
+ result = await client.wait_for_completion_or_create_check_status_response(
479
+ None, TEST_INSTANCE_ID)
480
+ assert result is not None
481
+ assert result.status_code == 200
482
+ assert result.mimetype == 'application/json'
483
+ assert json.loads(result.get_body().decode()) == DurableOrchestrationStatus.from_json(
484
+ status).to_json()
485
+
486
+
487
+ @pytest.mark.asyncio
488
+ async def test_wait_or_response_200_failed(binding_string):
489
+ status = dict(createdTime=TEST_CREATED_TIME,
490
+ lastUpdatedTime=TEST_LAST_UPDATED_TIME,
491
+ runtimeStatus="Failed")
492
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
493
+ response=[200, status])
494
+ client = DurableOrchestrationClient(binding_string)
495
+ client._get_async_request = mock_request.get
496
+
497
+ result = await client.wait_for_completion_or_create_check_status_response(
498
+ None, TEST_INSTANCE_ID)
499
+ assert result is not None
500
+ assert result.status_code == 500
501
+ assert result.mimetype == 'application/json'
502
+ assert json.loads(result.get_body().decode()) == DurableOrchestrationStatus.from_json(
503
+ status).to_json()
504
+
505
+
506
+ @pytest.mark.asyncio
507
+ async def test_wait_or_response_check_status_response(binding_string):
508
+ status = dict(createdTime=TEST_CREATED_TIME,
509
+ lastUpdatedTime=TEST_LAST_UPDATED_TIME,
510
+ runtimeStatus="Running")
511
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
512
+ response=[200, status])
513
+ client = DurableOrchestrationClient(binding_string)
514
+ client._get_async_request = mock_request.get
515
+
516
+ request = Mock(url="http://test_azure.net/api/orchestrators/DurableOrchestrationTrigger")
517
+ result = await client.wait_for_completion_or_create_check_status_response(
518
+ request, TEST_INSTANCE_ID, timeout_in_milliseconds=2000)
519
+ assert result is not None
520
+ assert mock_request.get_count == 3
521
+
522
+
523
+ @pytest.mark.asyncio
524
+ async def test_wait_or_response_null_request(binding_string):
525
+ status = dict(createdTime=TEST_CREATED_TIME,
526
+ lastUpdatedTime=TEST_LAST_UPDATED_TIME,
527
+ runtimeStatus="Running")
528
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}",
529
+ response=[200, status])
530
+ client = DurableOrchestrationClient(binding_string)
531
+ client._get_async_request = mock_request.get
532
+
533
+ with pytest.raises(Exception):
534
+ await client.wait_for_completion_or_create_check_status_response(
535
+ None, TEST_INSTANCE_ID, timeout_in_milliseconds=500)
536
+
537
+ @pytest.mark.asyncio
538
+ async def test_start_new_orchestrator_not_found(binding_string):
539
+ """Test that we throw the right exception when the orchestrator is not found.
540
+ """
541
+ status = dict(ExceptionMessage=EXCEPTION_ORCHESTRATOR_NOT_FOUND_EXMESSAGE,
542
+ StackTrace=STACK_TRACE,
543
+ Message=EXCEPTION_ORCHESTRATOR_NOT_FOUND_MESSAGE,
544
+ ExceptionType=EXCEPTION_TYPE_ORCHESTRATOR_NOT_FOUND)
545
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}orchestrators/{TEST_ORCHESTRATOR}",
546
+ response=[400, status])
547
+ client = DurableOrchestrationClient(binding_string)
548
+ client._post_async_request = mock_request.post
549
+
550
+ with pytest.raises(Exception) as ex:
551
+ await client.start_new(TEST_ORCHESTRATOR)
552
+ ex.match(EXCEPTION_ORCHESTRATOR_NOT_FOUND_EXMESSAGE)
553
+
554
+
555
+ @pytest.mark.asyncio
556
+ async def test_start_new_orchestrator_internal_exception(binding_string):
557
+ """Test that we throw the right exception when the extension fails internally.
558
+ """
559
+ status = dict(ExceptionMessage=EXCEPTION_ORCHESTRATOR_NOT_FOUND_EXMESSAGE,
560
+ StackTrace=STACK_TRACE,
561
+ Message=EXCEPTION_ORCHESTRATOR_NOT_FOUND_MESSAGE,
562
+ ExceptionType=EXCEPTION_TYPE_ORCHESTRATOR_NOT_FOUND)
563
+ mock_request = MockRequest(expected_url=f"{RPC_BASE_URL}orchestrators/{TEST_ORCHESTRATOR}",
564
+ response=[500, status])
565
+ client = DurableOrchestrationClient(binding_string)
566
+ client._post_async_request = mock_request.post
567
+
568
+ status_str = str(status)
569
+ with pytest.raises(Exception) as ex:
570
+ await client.start_new(TEST_ORCHESTRATOR)
571
+ ex.match(status_str)
572
+
573
+ @pytest.mark.asyncio
574
+ async def test_rewind_works_under_200_and_200_http_codes(binding_string):
575
+ """Tests that the rewind API works as expected under 'successful' http codes: 200, 202"""
576
+ client = DurableOrchestrationClient(binding_string)
577
+ for code in [200, 202]:
578
+ mock_request = MockRequest(
579
+ expected_url=f"{RPC_BASE_URL}instances/{INSTANCE_ID}/rewind?reason={REASON}",
580
+ response=[code, ""])
581
+ client._post_async_request = mock_request.post
582
+ result = await client.rewind(INSTANCE_ID, REASON)
583
+ assert result is None
584
+
585
+ @pytest.mark.asyncio
586
+ async def test_rewind_throws_exception_during_404_410_and_500_errors(binding_string):
587
+ """Tests the behaviour of rewind under 'exception' http codes: 404, 410, 500"""
588
+ client = DurableOrchestrationClient(binding_string)
589
+ codes = [404, 410, 500]
590
+ exception_strs = [
591
+ f"No instance with ID {INSTANCE_ID} found.",
592
+ "The rewind operation is only supported on failed orchestration instances.",
593
+ "Something went wrong"
594
+ ]
595
+ for http_code, expected_exception_str in zip(codes, exception_strs):
596
+ mock_request = MockRequest(
597
+ expected_url=f"{RPC_BASE_URL}instances/{INSTANCE_ID}/rewind?reason={REASON}",
598
+ response=[http_code, "Something went wrong"])
599
+ client._post_async_request = mock_request.post
600
+
601
+ with pytest.raises(Exception) as ex:
602
+ await client.rewind(INSTANCE_ID, REASON)
603
+ ex_message = str(ex.value)
604
+ assert ex_message == expected_exception_str
605
+
606
+ @pytest.mark.asyncio
607
+ async def test_rewind_with_no_rpc_endpoint(binding_string):
608
+ """Tests the behaviour of rewind without an RPC endpoint / under the legacy HTTP endpoint."""
609
+ client = DurableOrchestrationClient(binding_string)
610
+ mock_request = MockRequest(
611
+ expected_url=f"{RPC_BASE_URL}instances/{INSTANCE_ID}/rewind?reason={REASON}",
612
+ response=[-1, ""])
613
+ client._post_async_request = mock_request.post
614
+ client._orchestration_bindings._rpc_base_url = None
615
+ expected_exception_str = "The Python SDK only supports RPC endpoints."\
616
+ + "Please remove the `localRpcEnabled` setting from host.json"
617
+ with pytest.raises(Exception) as ex:
618
+ await client.rewind(INSTANCE_ID, REASON)
619
+ ex_message = str(ex.value)
620
+ assert ex_message == expected_exception_str
621
+
622
+ @pytest.mark.asyncio
623
+ async def test_post_202_suspend(binding_string):
624
+ raw_reason = 'stuff and things'
625
+ reason = 'stuff%20and%20things'
626
+ mock_request = MockRequest(
627
+ expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/suspend?reason={reason}",
628
+ response=[202, None])
629
+ client = DurableOrchestrationClient(binding_string)
630
+ client._post_async_request = mock_request.post
631
+
632
+ result = await client.suspend(TEST_INSTANCE_ID, raw_reason)
633
+ assert result is None
634
+
635
+
636
+ @pytest.mark.asyncio
637
+ async def test_post_410_suspend(binding_string):
638
+ raw_reason = 'stuff and things'
639
+ reason = 'stuff%20and%20things'
640
+ mock_request = MockRequest(
641
+ expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/suspend?reason={reason}",
642
+ response=[410, None])
643
+ client = DurableOrchestrationClient(binding_string)
644
+ client._post_async_request = mock_request.post
645
+
646
+ result = await client.suspend(TEST_INSTANCE_ID, raw_reason)
647
+ assert result is None
648
+
649
+
650
+ @pytest.mark.asyncio
651
+ async def test_post_404_suspend(binding_string):
652
+ raw_reason = 'stuff and things'
653
+ reason = 'stuff%20and%20things'
654
+ mock_request = MockRequest(
655
+ expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/suspend?reason={reason}",
656
+ response=[404, MESSAGE_404])
657
+ client = DurableOrchestrationClient(binding_string)
658
+ client._post_async_request = mock_request.post
659
+
660
+ with pytest.raises(Exception):
661
+ await client.suspend(TEST_INSTANCE_ID, raw_reason)
662
+
663
+
664
+ @pytest.mark.asyncio
665
+ async def test_post_500_suspend(binding_string):
666
+ raw_reason = 'stuff and things'
667
+ reason = 'stuff%20and%20things'
668
+ mock_request = MockRequest(
669
+ expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/suspend?reason={reason}",
670
+ response=[500, MESSAGE_500])
671
+ client = DurableOrchestrationClient(binding_string)
672
+ client._post_async_request = mock_request.post
673
+
674
+ with pytest.raises(Exception):
675
+ await client.suspend(TEST_INSTANCE_ID, raw_reason)
676
+
677
+ @pytest.mark.asyncio
678
+ async def test_post_202_resume(binding_string):
679
+ raw_reason = 'stuff and things'
680
+ reason = 'stuff%20and%20things'
681
+ mock_request = MockRequest(
682
+ expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/resume?reason={reason}",
683
+ response=[202, None])
684
+ client = DurableOrchestrationClient(binding_string)
685
+ client._post_async_request = mock_request.post
686
+
687
+ result = await client.resume(TEST_INSTANCE_ID, raw_reason)
688
+ assert result is None
689
+
690
+
691
+ @pytest.mark.asyncio
692
+ async def test_post_410_resume(binding_string):
693
+ raw_reason = 'stuff and things'
694
+ reason = 'stuff%20and%20things'
695
+ mock_request = MockRequest(
696
+ expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/resume?reason={reason}",
697
+ response=[410, None])
698
+ client = DurableOrchestrationClient(binding_string)
699
+ client._post_async_request = mock_request.post
700
+
701
+ result = await client.resume(TEST_INSTANCE_ID, raw_reason)
702
+ assert result is None
703
+
704
+
705
+ @pytest.mark.asyncio
706
+ async def test_post_404_resume(binding_string):
707
+ raw_reason = 'stuff and things'
708
+ reason = 'stuff%20and%20things'
709
+ mock_request = MockRequest(
710
+ expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/resume?reason={reason}",
711
+ response=[404, MESSAGE_404])
712
+ client = DurableOrchestrationClient(binding_string)
713
+ client._post_async_request = mock_request.post
714
+
715
+ with pytest.raises(Exception):
716
+ await client.resume(TEST_INSTANCE_ID, raw_reason)
717
+
718
+
719
+ @pytest.mark.asyncio
720
+ async def test_post_500_resume(binding_string):
721
+ raw_reason = 'stuff and things'
722
+ reason = 'stuff%20and%20things'
723
+ mock_request = MockRequest(
724
+ expected_url=f"{RPC_BASE_URL}instances/{TEST_INSTANCE_ID}/resume?reason={reason}",
725
+ response=[500, MESSAGE_500])
726
+ client = DurableOrchestrationClient(binding_string)
727
+ client._post_async_request = mock_request.post
728
+
729
+ with pytest.raises(Exception):
730
+ await client.resume(TEST_INSTANCE_ID, raw_reason)