azure-functions-durable 1.2.9__py3-none-any.whl → 1.2.10__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.
- azure/durable_functions/__init__.py +81 -81
- azure/durable_functions/constants.py +9 -9
- azure/durable_functions/decorators/__init__.py +3 -3
- azure/durable_functions/decorators/durable_app.py +249 -249
- azure/durable_functions/decorators/metadata.py +109 -109
- azure/durable_functions/entity.py +125 -125
- azure/durable_functions/models/DurableEntityContext.py +201 -201
- azure/durable_functions/models/DurableHttpRequest.py +58 -58
- azure/durable_functions/models/DurableOrchestrationBindings.py +66 -66
- azure/durable_functions/models/DurableOrchestrationClient.py +781 -781
- azure/durable_functions/models/DurableOrchestrationContext.py +722 -707
- azure/durable_functions/models/DurableOrchestrationStatus.py +156 -156
- azure/durable_functions/models/EntityStateResponse.py +23 -23
- azure/durable_functions/models/FunctionContext.py +7 -7
- azure/durable_functions/models/OrchestrationRuntimeStatus.py +32 -32
- azure/durable_functions/models/OrchestratorState.py +117 -116
- azure/durable_functions/models/PurgeHistoryResult.py +33 -33
- azure/durable_functions/models/ReplaySchema.py +8 -8
- azure/durable_functions/models/RetryOptions.py +69 -69
- azure/durable_functions/models/RpcManagementOptions.py +86 -86
- azure/durable_functions/models/Task.py +426 -426
- azure/durable_functions/models/TaskOrchestrationExecutor.py +346 -336
- azure/durable_functions/models/TokenSource.py +56 -56
- azure/durable_functions/models/__init__.py +24 -24
- azure/durable_functions/models/actions/Action.py +23 -23
- azure/durable_functions/models/actions/ActionType.py +18 -18
- azure/durable_functions/models/actions/CallActivityAction.py +41 -41
- azure/durable_functions/models/actions/CallActivityWithRetryAction.py +45 -45
- azure/durable_functions/models/actions/CallEntityAction.py +46 -46
- azure/durable_functions/models/actions/CallHttpAction.py +35 -35
- azure/durable_functions/models/actions/CallSubOrchestratorAction.py +40 -40
- azure/durable_functions/models/actions/CallSubOrchestratorWithRetryAction.py +44 -44
- azure/durable_functions/models/actions/CompoundAction.py +35 -35
- azure/durable_functions/models/actions/ContinueAsNewAction.py +36 -36
- azure/durable_functions/models/actions/CreateTimerAction.py +48 -48
- azure/durable_functions/models/actions/NoOpAction.py +35 -35
- azure/durable_functions/models/actions/SignalEntityAction.py +47 -47
- azure/durable_functions/models/actions/WaitForExternalEventAction.py +63 -63
- azure/durable_functions/models/actions/WhenAllAction.py +14 -14
- azure/durable_functions/models/actions/WhenAnyAction.py +14 -14
- azure/durable_functions/models/actions/__init__.py +24 -24
- azure/durable_functions/models/entities/EntityState.py +74 -74
- azure/durable_functions/models/entities/OperationResult.py +76 -76
- azure/durable_functions/models/entities/RequestMessage.py +53 -53
- azure/durable_functions/models/entities/ResponseMessage.py +48 -48
- azure/durable_functions/models/entities/Signal.py +62 -62
- azure/durable_functions/models/entities/__init__.py +17 -17
- azure/durable_functions/models/history/HistoryEvent.py +92 -92
- azure/durable_functions/models/history/HistoryEventType.py +27 -27
- azure/durable_functions/models/history/__init__.py +8 -8
- azure/durable_functions/models/utils/__init__.py +7 -7
- azure/durable_functions/models/utils/entity_utils.py +103 -91
- azure/durable_functions/models/utils/http_utils.py +69 -69
- azure/durable_functions/models/utils/json_utils.py +56 -56
- azure/durable_functions/orchestrator.py +71 -71
- {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.2.10.dist-info}/LICENSE +21 -21
- {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.2.10.dist-info}/METADATA +58 -58
- azure_functions_durable-1.2.10.dist-info/RECORD +100 -0
- tests/models/test_DecoratorMetadata.py +135 -135
- tests/models/test_Decorators.py +107 -107
- tests/models/test_DurableOrchestrationBindings.py +68 -68
- tests/models/test_DurableOrchestrationClient.py +730 -730
- tests/models/test_DurableOrchestrationContext.py +102 -102
- tests/models/test_DurableOrchestrationStatus.py +59 -59
- tests/models/test_OrchestrationState.py +28 -28
- tests/models/test_RpcManagementOptions.py +79 -79
- tests/models/test_TokenSource.py +10 -10
- tests/orchestrator/models/OrchestrationInstance.py +18 -18
- tests/orchestrator/orchestrator_test_utils.py +130 -130
- tests/orchestrator/schemas/OrchetrationStateSchema.py +66 -66
- tests/orchestrator/test_call_http.py +235 -176
- tests/orchestrator/test_continue_as_new.py +67 -67
- tests/orchestrator/test_create_timer.py +126 -126
- tests/orchestrator/test_entity.py +395 -395
- tests/orchestrator/test_external_event.py +53 -53
- tests/orchestrator/test_fan_out_fan_in.py +175 -175
- tests/orchestrator/test_is_replaying_flag.py +101 -101
- tests/orchestrator/test_retries.py +308 -308
- tests/orchestrator/test_sequential_orchestrator.py +841 -841
- tests/orchestrator/test_sequential_orchestrator_custom_status.py +119 -119
- tests/orchestrator/test_sequential_orchestrator_with_retry.py +465 -465
- tests/orchestrator/test_serialization.py +30 -30
- tests/orchestrator/test_sub_orchestrator.py +95 -95
- tests/orchestrator/test_sub_orchestrator_with_retry.py +129 -129
- tests/orchestrator/test_task_any.py +60 -60
- tests/tasks/tasks_test_utils.py +17 -17
- tests/tasks/test_new_uuid.py +34 -34
- tests/test_utils/ContextBuilder.py +174 -174
- tests/test_utils/EntityContextBuilder.py +56 -56
- tests/test_utils/constants.py +1 -1
- tests/test_utils/json_utils.py +30 -30
- tests/test_utils/testClasses.py +56 -56
- tests/utils/__init__.py +1 -0
- tests/utils/test_entity_utils.py +24 -0
- azure_functions_durable-1.2.9.data/data/_manifest/bsi.json +0 -1
- azure_functions_durable-1.2.9.data/data/_manifest/manifest.cat +0 -0
- azure_functions_durable-1.2.9.data/data/_manifest/manifest.spdx.json +0 -11985
- azure_functions_durable-1.2.9.data/data/_manifest/manifest.spdx.json.sha256 +0 -1
- azure_functions_durable-1.2.9.dist-info/RECORD +0 -102
- {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.2.10.dist-info}/WHEEL +0 -0
- {azure_functions_durable-1.2.9.dist-info → azure_functions_durable-1.2.10.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):
|
|
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)
|